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