ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Escher.php
Go to the documentation of this file.
1 <?php
2 
4 
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 
41  private $data;
42 
48  private $dataSize;
49 
55  private $pos;
56 
62  private $object;
63 
69  public function __construct($object)
70  {
71  $this->object = $object;
72  }
73 
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 
181  private function readDefault(): void
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 
202  private function readDggContainer(): void
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 
220  private function readDgg(): void
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 
232  private function readBstoreContainer(): void
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 
250  private function readBSE(): void
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 
316  private function readBlipJPEG(): void
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;
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 
357  private function readBlipPNG(): void
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;
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 
398  private function readOPT(): void
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 
417  private function readTertiaryOPT(): void
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 
434  private function readSplitMenuColors(): void
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 
446  private function readDgContainer(): void
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 
464  private function readDg(): void
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 
476  private function readSpgrContainer(): void
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 
504  private function readSpContainer(): void
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 
524  private function readSpgr(): void
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 
536  private function readSp(): void
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 
553  private function readClientTextbox(): void
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 
570  private function readClientAnchor(): void
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 
624  private function readClientData(): void
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 
639  private function readOfficeArtRGFOPTE($data, $n): void
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 }
readDggContainer()
Read DggContainer record (Drawing Group Container).
Definition: Escher.php:202
$size
Definition: RandomTest.php:84
readDg()
Read Dg record (Drawing).
Definition: Escher.php:464
load($data)
Load Escher stream data.
Definition: Escher.php:81
readClientAnchor()
Read ClientAnchor record.
Definition: Escher.php:570
readSplitMenuColors()
Read SplitMenuColors record.
Definition: Escher.php:434
static getInt4d($data, $pos)
Read 32-bit signed integer.
Definition: Xls.php:7887
readSp()
Read Sp record (Shape).
Definition: Escher.php:536
readOfficeArtRGFOPTE($data, $n)
Read OfficeArtRGFOPTE table of property-value pairs.
Definition: Escher.php:639
readBstoreContainer()
Read BstoreContainer record (Blip Store Container).
Definition: Escher.php:232
__construct($object)
Create a new Escher instance.
Definition: Escher.php:69
readSpgrContainer()
Read SpgrContainer record (Shape Group Container).
Definition: Escher.php:476
static getUInt2d($data, $pos)
Read 16-bit unsigned integer.
Definition: Xls.php:7861
readTertiaryOPT()
Read TertiaryOPT record.
Definition: Escher.php:417
readSpgr()
Read Spgr record (Shape Group).
Definition: Escher.php:524
$n
Definition: RandomTest.php:85
readSpContainer()
Read SpContainer record (Shape Container).
Definition: Escher.php:504
readBlipPNG()
Read BlipPNG record.
Definition: Escher.php:357
readDgg()
Read Dgg record (Drawing Group).
Definition: Escher.php:220
$this data['403_header']
$i
Definition: disco.tpl.php:19
readClientData()
Read ClientData record.
Definition: Escher.php:624
readClientTextbox()
Read ClientTextbox record.
Definition: Escher.php:553
static stringFromColumnIndex($columnIndex)
String from column index.
Definition: Coordinate.php:313
if(function_exists('posix_getuid') &&posix_getuid()===0) if(!array_key_exists('t', $options)) $tag
Definition: cron.php:35
readDgContainer()
Read DgContainer record (Drawing Container).
Definition: Escher.php:446
readDefault()
Read a generic record.
Definition: Escher.php:181
readBlipJPEG()
Read BlipJPEG record.
Definition: Escher.php:316