PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.9.7
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.9.7
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Reader / Xls / Escher.php

Escher.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.9.7, at vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php

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