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