1<?php 2/** 3 * PHPExcel 4 * 5 * Copyright (c) 2006 - 2014 PHPExcel 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 * 21 * @category PHPExcel 22 * @package PHPExcel_Reader_Excel5 23 * @copyright Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel) 24 * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL 25 * @version ##VERSION##, ##DATE## 26 */ 27 28/** 29 * PHPExcel_Reader_Excel5_Escher 30 * 31 * @category PHPExcel 32 * @package PHPExcel_Reader_Excel5 33 * @copyright Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel) 34 */ 35class PHPExcel_Reader_Excel5_Escher 36{ 37 const DGGCONTAINER = 0xF000; 38 const BSTORECONTAINER = 0xF001; 39 const DGCONTAINER = 0xF002; 40 const SPGRCONTAINER = 0xF003; 41 const SPCONTAINER = 0xF004; 42 const DGG = 0xF006; 43 const BSE = 0xF007; 44 const DG = 0xF008; 45 const SPGR = 0xF009; 46 const SP = 0xF00A; 47 const OPT = 0xF00B; 48 const CLIENTTEXTBOX = 0xF00D; 49 const CLIENTANCHOR = 0xF010; 50 const CLIENTDATA = 0xF011; 51 const BLIPJPEG = 0xF01D; 52 const BLIPPNG = 0xF01E; 53 const SPLITMENUCOLORS = 0xF11E; 54 const TERTIARYOPT = 0xF122; 55 56 /** 57 * Escher stream data (binary) 58 * 59 * @var string 60 */ 61 private $_data; 62 63 /** 64 * Size in bytes of the Escher stream data 65 * 66 * @var int 67 */ 68 private $_dataSize; 69 70 /** 71 * Current position of stream pointer in Escher stream data 72 * 73 * @var int 74 */ 75 private $_pos; 76 77 /** 78 * The object to be returned by the reader. Modified during load. 79 * 80 * @var mixed 81 */ 82 private $_object; 83 84 /** 85 * Create a new PHPExcel_Reader_Excel5_Escher instance 86 * 87 * @param mixed $object 88 */ 89 public function __construct($object) 90 { 91 $this->_object = $object; 92 } 93 94 /** 95 * Load Escher stream data. May be a partial Escher stream. 96 * 97 * @param string $data 98 */ 99 public function load($data) 100 { 101 $this->_data = $data; 102 103 // total byte size of Excel data (workbook global substream + sheet substreams) 104 $this->_dataSize = strlen($this->_data); 105 106 $this->_pos = 0; 107 108 // Parse Escher stream 109 while ($this->_pos < $this->_dataSize) { 110 111 // offset: 2; size: 2: Record Type 112 $fbt = PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos + 2); 113 114 switch ($fbt) { 115 case self::DGGCONTAINER: $this->_readDggContainer(); break; 116 case self::DGG: $this->_readDgg(); break; 117 case self::BSTORECONTAINER: $this->_readBstoreContainer(); break; 118 case self::BSE: $this->_readBSE(); break; 119 case self::BLIPJPEG: $this->_readBlipJPEG(); break; 120 case self::BLIPPNG: $this->_readBlipPNG(); break; 121 case self::OPT: $this->_readOPT(); break; 122 case self::TERTIARYOPT: $this->_readTertiaryOPT(); break; 123 case self::SPLITMENUCOLORS: $this->_readSplitMenuColors(); break; 124 case self::DGCONTAINER: $this->_readDgContainer(); break; 125 case self::DG: $this->_readDg(); break; 126 case self::SPGRCONTAINER: $this->_readSpgrContainer(); break; 127 case self::SPCONTAINER: $this->_readSpContainer(); break; 128 case self::SPGR: $this->_readSpgr(); break; 129 case self::SP: $this->_readSp(); break; 130 case self::CLIENTTEXTBOX: $this->_readClientTextbox(); break; 131 case self::CLIENTANCHOR: $this->_readClientAnchor(); break; 132 case self::CLIENTDATA: $this->_readClientData(); break; 133 default: $this->_readDefault(); break; 134 } 135 } 136 137 return $this->_object; 138 } 139 140 /** 141 * Read a generic record 142 */ 143 private function _readDefault() 144 { 145 // offset 0; size: 2; recVer and recInstance 146 $verInstance = PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos); 147 148 // offset: 2; size: 2: Record Type 149 $fbt = PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos + 2); 150 151 // bit: 0-3; mask: 0x000F; recVer 152 $recVer = (0x000F & $verInstance) >> 0; 153 154 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 155 $recordData = substr($this->_data, $this->_pos + 8, $length); 156 157 // move stream pointer to next record 158 $this->_pos += 8 + $length; 159 } 160 161 /** 162 * Read DggContainer record (Drawing Group Container) 163 */ 164 private function _readDggContainer() 165 { 166 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 167 $recordData = substr($this->_data, $this->_pos + 8, $length); 168 169 // move stream pointer to next record 170 $this->_pos += 8 + $length; 171 172 // record is a container, read contents 173 $dggContainer = new PHPExcel_Shared_Escher_DggContainer(); 174 $this->_object->setDggContainer($dggContainer); 175 $reader = new PHPExcel_Reader_Excel5_Escher($dggContainer); 176 $reader->load($recordData); 177 } 178 179 /** 180 * Read Dgg record (Drawing Group) 181 */ 182 private function _readDgg() 183 { 184 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 185 $recordData = substr($this->_data, $this->_pos + 8, $length); 186 187 // move stream pointer to next record 188 $this->_pos += 8 + $length; 189 } 190 191 /** 192 * Read BstoreContainer record (Blip Store Container) 193 */ 194 private function _readBstoreContainer() 195 { 196 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 197 $recordData = substr($this->_data, $this->_pos + 8, $length); 198 199 // move stream pointer to next record 200 $this->_pos += 8 + $length; 201 202 // record is a container, read contents 203 $bstoreContainer = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer(); 204 $this->_object->setBstoreContainer($bstoreContainer); 205 $reader = new PHPExcel_Reader_Excel5_Escher($bstoreContainer); 206 $reader->load($recordData); 207 } 208 209 /** 210 * Read BSE record 211 */ 212 private function _readBSE() 213 { 214 // offset: 0; size: 2; recVer and recInstance 215 216 // bit: 4-15; mask: 0xFFF0; recInstance 217 $recInstance = (0xFFF0 & PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos)) >> 4; 218 219 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 220 $recordData = substr($this->_data, $this->_pos + 8, $length); 221 222 // move stream pointer to next record 223 $this->_pos += 8 + $length; 224 225 // add BSE to BstoreContainer 226 $BSE = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE(); 227 $this->_object->addBSE($BSE); 228 229 $BSE->setBLIPType($recInstance); 230 231 // offset: 0; size: 1; btWin32 (MSOBLIPTYPE) 232 $btWin32 = ord($recordData[0]); 233 234 // offset: 1; size: 1; btWin32 (MSOBLIPTYPE) 235 $btMacOS = ord($recordData[1]); 236 237 // offset: 2; size: 16; MD4 digest 238 $rgbUid = substr($recordData, 2, 16); 239 240 // offset: 18; size: 2; tag 241 $tag = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 18); 242 243 // offset: 20; size: 4; size of BLIP in bytes 244 $size = PHPExcel_Reader_Excel5::_GetInt4d($recordData, 20); 245 246 // offset: 24; size: 4; number of references to this BLIP 247 $cRef = PHPExcel_Reader_Excel5::_GetInt4d($recordData, 24); 248 249 // offset: 28; size: 4; MSOFO file offset 250 $foDelay = PHPExcel_Reader_Excel5::_GetInt4d($recordData, 28); 251 252 // offset: 32; size: 1; unused1 253 $unused1 = ord($recordData{32}); 254 255 // offset: 33; size: 1; size of nameData in bytes (including null terminator) 256 $cbName = ord($recordData{33}); 257 258 // offset: 34; size: 1; unused2 259 $unused2 = ord($recordData{34}); 260 261 // offset: 35; size: 1; unused3 262 $unused3 = ord($recordData{35}); 263 264 // offset: 36; size: $cbName; nameData 265 $nameData = substr($recordData, 36, $cbName); 266 267 // offset: 36 + $cbName, size: var; the BLIP data 268 $blipData = substr($recordData, 36 + $cbName); 269 270 // record is a container, read contents 271 $reader = new PHPExcel_Reader_Excel5_Escher($BSE); 272 $reader->load($blipData); 273 } 274 275 /** 276 * Read BlipJPEG record. Holds raw JPEG image data 277 */ 278 private function _readBlipJPEG() 279 { 280 // offset: 0; size: 2; recVer and recInstance 281 282 // bit: 4-15; mask: 0xFFF0; recInstance 283 $recInstance = (0xFFF0 & PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos)) >> 4; 284 285 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 286 $recordData = substr($this->_data, $this->_pos + 8, $length); 287 288 // move stream pointer to next record 289 $this->_pos += 8 + $length; 290 291 $pos = 0; 292 293 // offset: 0; size: 16; rgbUid1 (MD4 digest of) 294 $rgbUid1 = substr($recordData, 0, 16); 295 $pos += 16; 296 297 // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 298 if (in_array($recInstance, array(0x046B, 0x06E3))) { 299 $rgbUid2 = substr($recordData, 16, 16); 300 $pos += 16; 301 } 302 303 // offset: var; size: 1; tag 304 $tag = ord($recordData{$pos}); 305 $pos += 1; 306 307 // offset: var; size: var; the raw image data 308 $data = substr($recordData, $pos); 309 310 $blip = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE_Blip(); 311 $blip->setData($data); 312 313 $this->_object->setBlip($blip); 314 } 315 316 /** 317 * Read BlipPNG record. Holds raw PNG image data 318 */ 319 private function _readBlipPNG() 320 { 321 // offset: 0; size: 2; recVer and recInstance 322 323 // bit: 4-15; mask: 0xFFF0; recInstance 324 $recInstance = (0xFFF0 & PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos)) >> 4; 325 326 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 327 $recordData = substr($this->_data, $this->_pos + 8, $length); 328 329 // move stream pointer to next record 330 $this->_pos += 8 + $length; 331 332 $pos = 0; 333 334 // offset: 0; size: 16; rgbUid1 (MD4 digest of) 335 $rgbUid1 = substr($recordData, 0, 16); 336 $pos += 16; 337 338 // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 339 if ($recInstance == 0x06E1) { 340 $rgbUid2 = substr($recordData, 16, 16); 341 $pos += 16; 342 } 343 344 // offset: var; size: 1; tag 345 $tag = ord($recordData{$pos}); 346 $pos += 1; 347 348 // offset: var; size: var; the raw image data 349 $data = substr($recordData, $pos); 350 351 $blip = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE_Blip(); 352 $blip->setData($data); 353 354 $this->_object->setBlip($blip); 355 } 356 357 /** 358 * Read OPT record. This record may occur within DggContainer record or SpContainer 359 */ 360 private function _readOPT() 361 { 362 // offset: 0; size: 2; recVer and recInstance 363 364 // bit: 4-15; mask: 0xFFF0; recInstance 365 $recInstance = (0xFFF0 & PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos)) >> 4; 366 367 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 368 $recordData = substr($this->_data, $this->_pos + 8, $length); 369 370 // move stream pointer to next record 371 $this->_pos += 8 + $length; 372 373 $this->_readOfficeArtRGFOPTE($recordData, $recInstance); 374 } 375 376 /** 377 * Read TertiaryOPT record 378 */ 379 private function _readTertiaryOPT() 380 { 381 // offset: 0; size: 2; recVer and recInstance 382 383 // bit: 4-15; mask: 0xFFF0; recInstance 384 $recInstance = (0xFFF0 & PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos)) >> 4; 385 386 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 387 $recordData = substr($this->_data, $this->_pos + 8, $length); 388 389 // move stream pointer to next record 390 $this->_pos += 8 + $length; 391 } 392 393 /** 394 * Read SplitMenuColors record 395 */ 396 private function _readSplitMenuColors() 397 { 398 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 399 $recordData = substr($this->_data, $this->_pos + 8, $length); 400 401 // move stream pointer to next record 402 $this->_pos += 8 + $length; 403 } 404 405 /** 406 * Read DgContainer record (Drawing Container) 407 */ 408 private function _readDgContainer() 409 { 410 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 411 $recordData = substr($this->_data, $this->_pos + 8, $length); 412 413 // move stream pointer to next record 414 $this->_pos += 8 + $length; 415 416 // record is a container, read contents 417 $dgContainer = new PHPExcel_Shared_Escher_DgContainer(); 418 $this->_object->setDgContainer($dgContainer); 419 $reader = new PHPExcel_Reader_Excel5_Escher($dgContainer); 420 $escher = $reader->load($recordData); 421 } 422 423 /** 424 * Read Dg record (Drawing) 425 */ 426 private function _readDg() 427 { 428 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 429 $recordData = substr($this->_data, $this->_pos + 8, $length); 430 431 // move stream pointer to next record 432 $this->_pos += 8 + $length; 433 } 434 435 /** 436 * Read SpgrContainer record (Shape Group Container) 437 */ 438 private function _readSpgrContainer() 439 { 440 // context is either context DgContainer or SpgrContainer 441 442 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 443 $recordData = substr($this->_data, $this->_pos + 8, $length); 444 445 // move stream pointer to next record 446 $this->_pos += 8 + $length; 447 448 // record is a container, read contents 449 $spgrContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer(); 450 451 if ($this->_object instanceof PHPExcel_Shared_Escher_DgContainer) { 452 // DgContainer 453 $this->_object->setSpgrContainer($spgrContainer); 454 } else { 455 // SpgrContainer 456 $this->_object->addChild($spgrContainer); 457 } 458 459 $reader = new PHPExcel_Reader_Excel5_Escher($spgrContainer); 460 $escher = $reader->load($recordData); 461 } 462 463 /** 464 * Read SpContainer record (Shape Container) 465 */ 466 private function _readSpContainer() 467 { 468 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 469 $recordData = substr($this->_data, $this->_pos + 8, $length); 470 471 // add spContainer to spgrContainer 472 $spContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer(); 473 $this->_object->addChild($spContainer); 474 475 // move stream pointer to next record 476 $this->_pos += 8 + $length; 477 478 // record is a container, read contents 479 $reader = new PHPExcel_Reader_Excel5_Escher($spContainer); 480 $escher = $reader->load($recordData); 481 } 482 483 /** 484 * Read Spgr record (Shape Group) 485 */ 486 private function _readSpgr() 487 { 488 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 489 $recordData = substr($this->_data, $this->_pos + 8, $length); 490 491 // move stream pointer to next record 492 $this->_pos += 8 + $length; 493 } 494 495 /** 496 * Read Sp record (Shape) 497 */ 498 private function _readSp() 499 { 500 // offset: 0; size: 2; recVer and recInstance 501 502 // bit: 4-15; mask: 0xFFF0; recInstance 503 $recInstance = (0xFFF0 & PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos)) >> 4; 504 505 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 506 $recordData = substr($this->_data, $this->_pos + 8, $length); 507 508 // move stream pointer to next record 509 $this->_pos += 8 + $length; 510 } 511 512 /** 513 * Read ClientTextbox record 514 */ 515 private function _readClientTextbox() 516 { 517 // offset: 0; size: 2; recVer and recInstance 518 519 // bit: 4-15; mask: 0xFFF0; recInstance 520 $recInstance = (0xFFF0 & PHPExcel_Reader_Excel5::_GetInt2d($this->_data, $this->_pos)) >> 4; 521 522 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 523 $recordData = substr($this->_data, $this->_pos + 8, $length); 524 525 // move stream pointer to next record 526 $this->_pos += 8 + $length; 527 } 528 529 /** 530 * Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet 531 */ 532 private function _readClientAnchor() 533 { 534 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 535 $recordData = substr($this->_data, $this->_pos + 8, $length); 536 537 // move stream pointer to next record 538 $this->_pos += 8 + $length; 539 540 // offset: 2; size: 2; upper-left corner column index (0-based) 541 $c1 = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 2); 542 543 // offset: 4; size: 2; upper-left corner horizontal offset in 1/1024 of column width 544 $startOffsetX = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 4); 545 546 // offset: 6; size: 2; upper-left corner row index (0-based) 547 $r1 = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 6); 548 549 // offset: 8; size: 2; upper-left corner vertical offset in 1/256 of row height 550 $startOffsetY = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 8); 551 552 // offset: 10; size: 2; bottom-right corner column index (0-based) 553 $c2 = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 10); 554 555 // offset: 12; size: 2; bottom-right corner horizontal offset in 1/1024 of column width 556 $endOffsetX = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 12); 557 558 // offset: 14; size: 2; bottom-right corner row index (0-based) 559 $r2 = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 14); 560 561 // offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height 562 $endOffsetY = PHPExcel_Reader_Excel5::_GetInt2d($recordData, 16); 563 564 // set the start coordinates 565 $this->_object->setStartCoordinates(PHPExcel_Cell::stringFromColumnIndex($c1) . ($r1 + 1)); 566 567 // set the start offsetX 568 $this->_object->setStartOffsetX($startOffsetX); 569 570 // set the start offsetY 571 $this->_object->setStartOffsetY($startOffsetY); 572 573 // set the end coordinates 574 $this->_object->setEndCoordinates(PHPExcel_Cell::stringFromColumnIndex($c2) . ($r2 + 1)); 575 576 // set the end offsetX 577 $this->_object->setEndOffsetX($endOffsetX); 578 579 // set the end offsetY 580 $this->_object->setEndOffsetY($endOffsetY); 581 } 582 583 /** 584 * Read ClientData record 585 */ 586 private function _readClientData() 587 { 588 $length = PHPExcel_Reader_Excel5::_GetInt4d($this->_data, $this->_pos + 4); 589 $recordData = substr($this->_data, $this->_pos + 8, $length); 590 591 // move stream pointer to next record 592 $this->_pos += 8 + $length; 593 } 594 595 /** 596 * Read OfficeArtRGFOPTE table of property-value pairs 597 * 598 * @param string $data Binary data 599 * @param int $n Number of properties 600 */ 601 private function _readOfficeArtRGFOPTE($data, $n) { 602 603 $splicedComplexData = substr($data, 6 * $n); 604 605 // loop through property-value pairs 606 for ($i = 0; $i < $n; ++$i) { 607 // read 6 bytes at a time 608 $fopte = substr($data, 6 * $i, 6); 609 610 // offset: 0; size: 2; opid 611 $opid = PHPExcel_Reader_Excel5::_GetInt2d($fopte, 0); 612 613 // bit: 0-13; mask: 0x3FFF; opid.opid 614 $opidOpid = (0x3FFF & $opid) >> 0; 615 616 // bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier 617 $opidFBid = (0x4000 & $opid) >> 14; 618 619 // bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data 620 $opidFComplex = (0x8000 & $opid) >> 15; 621 622 // offset: 2; size: 4; the value for this property 623 $op = PHPExcel_Reader_Excel5::_GetInt4d($fopte, 2); 624 625 if ($opidFComplex) { 626 $complexData = substr($splicedComplexData, 0, $op); 627 $splicedComplexData = substr($splicedComplexData, $op); 628 629 // we store string value with complex data 630 $value = $complexData; 631 } else { 632 // we store integer value 633 $value = $op; 634 } 635 636 $this->_object->setOPT($opidOpid, $value); 637 } 638 } 639 640} 641