Escher.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
  3. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  4. use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
  5. use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
  6. use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
  7. use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
  8. use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
  9. use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
  10. use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
  11. class Escher
  12. {
  13. /**
  14. * The object we are writing.
  15. */
  16. private $object;
  17. /**
  18. * The written binary data.
  19. */
  20. private $data;
  21. /**
  22. * Shape offsets. Positions in binary stream where a new shape record begins.
  23. *
  24. * @var array
  25. */
  26. private $spOffsets;
  27. /**
  28. * Shape types.
  29. *
  30. * @var array
  31. */
  32. private $spTypes;
  33. /**
  34. * Constructor.
  35. *
  36. * @param mixed $object
  37. */
  38. public function __construct($object)
  39. {
  40. $this->object = $object;
  41. }
  42. /**
  43. * Process the object to be written.
  44. *
  45. * @return string
  46. */
  47. public function close()
  48. {
  49. // initialize
  50. $this->data = '';
  51. switch (get_class($this->object)) {
  52. case \PhpOffice\PhpSpreadsheet\Shared\Escher::class:
  53. if ($dggContainer = $this->object->getDggContainer()) {
  54. $writer = new self($dggContainer);
  55. $this->data = $writer->close();
  56. } elseif ($dgContainer = $this->object->getDgContainer()) {
  57. $writer = new self($dgContainer);
  58. $this->data = $writer->close();
  59. $this->spOffsets = $writer->getSpOffsets();
  60. $this->spTypes = $writer->getSpTypes();
  61. }
  62. break;
  63. case DggContainer::class:
  64. // this is a container record
  65. // initialize
  66. $innerData = '';
  67. // write the dgg
  68. $recVer = 0x0;
  69. $recInstance = 0x0000;
  70. $recType = 0xF006;
  71. $recVerInstance = $recVer;
  72. $recVerInstance |= $recInstance << 4;
  73. // dgg data
  74. $dggData =
  75. pack(
  76. 'VVVV',
  77. $this->object->getSpIdMax(), // maximum shape identifier increased by one
  78. $this->object->getCDgSaved() + 1, // number of file identifier clusters increased by one
  79. $this->object->getCSpSaved(),
  80. $this->object->getCDgSaved() // count total number of drawings saved
  81. );
  82. // add file identifier clusters (one per drawing)
  83. $IDCLs = $this->object->getIDCLs();
  84. foreach ($IDCLs as $dgId => $maxReducedSpId) {
  85. $dggData .= pack('VV', $dgId, $maxReducedSpId + 1);
  86. }
  87. $header = pack('vvV', $recVerInstance, $recType, strlen($dggData));
  88. $innerData .= $header . $dggData;
  89. // write the bstoreContainer
  90. if ($bstoreContainer = $this->object->getBstoreContainer()) {
  91. $writer = new self($bstoreContainer);
  92. $innerData .= $writer->close();
  93. }
  94. // write the record
  95. $recVer = 0xF;
  96. $recInstance = 0x0000;
  97. $recType = 0xF000;
  98. $length = strlen($innerData);
  99. $recVerInstance = $recVer;
  100. $recVerInstance |= $recInstance << 4;
  101. $header = pack('vvV', $recVerInstance, $recType, $length);
  102. $this->data = $header . $innerData;
  103. break;
  104. case BstoreContainer::class:
  105. // this is a container record
  106. // initialize
  107. $innerData = '';
  108. // treat the inner data
  109. if ($BSECollection = $this->object->getBSECollection()) {
  110. foreach ($BSECollection as $BSE) {
  111. $writer = new self($BSE);
  112. $innerData .= $writer->close();
  113. }
  114. }
  115. // write the record
  116. $recVer = 0xF;
  117. $recInstance = count($this->object->getBSECollection());
  118. $recType = 0xF001;
  119. $length = strlen($innerData);
  120. $recVerInstance = $recVer;
  121. $recVerInstance |= $recInstance << 4;
  122. $header = pack('vvV', $recVerInstance, $recType, $length);
  123. $this->data = $header . $innerData;
  124. break;
  125. case BSE::class:
  126. // this is a semi-container record
  127. // initialize
  128. $innerData = '';
  129. // here we treat the inner data
  130. if ($blip = $this->object->getBlip()) {
  131. $writer = new self($blip);
  132. $innerData .= $writer->close();
  133. }
  134. // initialize
  135. $data = '';
  136. $btWin32 = $this->object->getBlipType();
  137. $btMacOS = $this->object->getBlipType();
  138. $data .= pack('CC', $btWin32, $btMacOS);
  139. $rgbUid = pack('VVVV', 0, 0, 0, 0); // todo
  140. $data .= $rgbUid;
  141. $tag = 0;
  142. $size = strlen($innerData);
  143. $cRef = 1;
  144. $foDelay = 0; //todo
  145. $unused1 = 0x0;
  146. $cbName = 0x0;
  147. $unused2 = 0x0;
  148. $unused3 = 0x0;
  149. $data .= pack('vVVVCCCC', $tag, $size, $cRef, $foDelay, $unused1, $cbName, $unused2, $unused3);
  150. $data .= $innerData;
  151. // write the record
  152. $recVer = 0x2;
  153. $recInstance = $this->object->getBlipType();
  154. $recType = 0xF007;
  155. $length = strlen($data);
  156. $recVerInstance = $recVer;
  157. $recVerInstance |= $recInstance << 4;
  158. $header = pack('vvV', $recVerInstance, $recType, $length);
  159. $this->data = $header;
  160. $this->data .= $data;
  161. break;
  162. case Blip::class:
  163. // this is an atom record
  164. // write the record
  165. switch ($this->object->getParent()->getBlipType()) {
  166. case BSE::BLIPTYPE_JPEG:
  167. // initialize
  168. $innerData = '';
  169. $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo
  170. $innerData .= $rgbUid1;
  171. $tag = 0xFF; // todo
  172. $innerData .= pack('C', $tag);
  173. $innerData .= $this->object->getData();
  174. $recVer = 0x0;
  175. $recInstance = 0x46A;
  176. $recType = 0xF01D;
  177. $length = strlen($innerData);
  178. $recVerInstance = $recVer;
  179. $recVerInstance |= $recInstance << 4;
  180. $header = pack('vvV', $recVerInstance, $recType, $length);
  181. $this->data = $header;
  182. $this->data .= $innerData;
  183. break;
  184. case BSE::BLIPTYPE_PNG:
  185. // initialize
  186. $innerData = '';
  187. $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo
  188. $innerData .= $rgbUid1;
  189. $tag = 0xFF; // todo
  190. $innerData .= pack('C', $tag);
  191. $innerData .= $this->object->getData();
  192. $recVer = 0x0;
  193. $recInstance = 0x6E0;
  194. $recType = 0xF01E;
  195. $length = strlen($innerData);
  196. $recVerInstance = $recVer;
  197. $recVerInstance |= $recInstance << 4;
  198. $header = pack('vvV', $recVerInstance, $recType, $length);
  199. $this->data = $header;
  200. $this->data .= $innerData;
  201. break;
  202. }
  203. break;
  204. case DgContainer::class:
  205. // this is a container record
  206. // initialize
  207. $innerData = '';
  208. // write the dg
  209. $recVer = 0x0;
  210. $recInstance = $this->object->getDgId();
  211. $recType = 0xF008;
  212. $length = 8;
  213. $recVerInstance = $recVer;
  214. $recVerInstance |= $recInstance << 4;
  215. $header = pack('vvV', $recVerInstance, $recType, $length);
  216. // number of shapes in this drawing (including group shape)
  217. $countShapes = count($this->object->getSpgrContainer()->getChildren());
  218. $innerData .= $header . pack('VV', $countShapes, $this->object->getLastSpId());
  219. // write the spgrContainer
  220. if ($spgrContainer = $this->object->getSpgrContainer()) {
  221. $writer = new self($spgrContainer);
  222. $innerData .= $writer->close();
  223. // get the shape offsets relative to the spgrContainer record
  224. $spOffsets = $writer->getSpOffsets();
  225. $spTypes = $writer->getSpTypes();
  226. // save the shape offsets relative to dgContainer
  227. foreach ($spOffsets as &$spOffset) {
  228. $spOffset += 24; // add length of dgContainer header data (8 bytes) plus dg data (16 bytes)
  229. }
  230. $this->spOffsets = $spOffsets;
  231. $this->spTypes = $spTypes;
  232. }
  233. // write the record
  234. $recVer = 0xF;
  235. $recInstance = 0x0000;
  236. $recType = 0xF002;
  237. $length = strlen($innerData);
  238. $recVerInstance = $recVer;
  239. $recVerInstance |= $recInstance << 4;
  240. $header = pack('vvV', $recVerInstance, $recType, $length);
  241. $this->data = $header . $innerData;
  242. break;
  243. case SpgrContainer::class:
  244. // this is a container record
  245. // initialize
  246. $innerData = '';
  247. // initialize spape offsets
  248. $totalSize = 8;
  249. $spOffsets = [];
  250. $spTypes = [];
  251. // treat the inner data
  252. foreach ($this->object->getChildren() as $spContainer) {
  253. $writer = new self($spContainer);
  254. $spData = $writer->close();
  255. $innerData .= $spData;
  256. // save the shape offsets (where new shape records begin)
  257. $totalSize += strlen($spData);
  258. $spOffsets[] = $totalSize;
  259. $spTypes = array_merge($spTypes, $writer->getSpTypes());
  260. }
  261. // write the record
  262. $recVer = 0xF;
  263. $recInstance = 0x0000;
  264. $recType = 0xF003;
  265. $length = strlen($innerData);
  266. $recVerInstance = $recVer;
  267. $recVerInstance |= $recInstance << 4;
  268. $header = pack('vvV', $recVerInstance, $recType, $length);
  269. $this->data = $header . $innerData;
  270. $this->spOffsets = $spOffsets;
  271. $this->spTypes = $spTypes;
  272. break;
  273. case SpContainer::class:
  274. // initialize
  275. $data = '';
  276. // build the data
  277. // write group shape record, if necessary?
  278. if ($this->object->getSpgr()) {
  279. $recVer = 0x1;
  280. $recInstance = 0x0000;
  281. $recType = 0xF009;
  282. $length = 0x00000010;
  283. $recVerInstance = $recVer;
  284. $recVerInstance |= $recInstance << 4;
  285. $header = pack('vvV', $recVerInstance, $recType, $length);
  286. $data .= $header . pack('VVVV', 0, 0, 0, 0);
  287. }
  288. $this->spTypes[] = ($this->object->getSpType());
  289. // write the shape record
  290. $recVer = 0x2;
  291. $recInstance = $this->object->getSpType(); // shape type
  292. $recType = 0xF00A;
  293. $length = 0x00000008;
  294. $recVerInstance = $recVer;
  295. $recVerInstance |= $recInstance << 4;
  296. $header = pack('vvV', $recVerInstance, $recType, $length);
  297. $data .= $header . pack('VV', $this->object->getSpId(), $this->object->getSpgr() ? 0x0005 : 0x0A00);
  298. // the options
  299. if ($this->object->getOPTCollection()) {
  300. $optData = '';
  301. $recVer = 0x3;
  302. $recInstance = count($this->object->getOPTCollection());
  303. $recType = 0xF00B;
  304. foreach ($this->object->getOPTCollection() as $property => $value) {
  305. $optData .= pack('vV', $property, $value);
  306. }
  307. $length = strlen($optData);
  308. $recVerInstance = $recVer;
  309. $recVerInstance |= $recInstance << 4;
  310. $header = pack('vvV', $recVerInstance, $recType, $length);
  311. $data .= $header . $optData;
  312. }
  313. // the client anchor
  314. if ($this->object->getStartCoordinates()) {
  315. $clientAnchorData = '';
  316. $recVer = 0x0;
  317. $recInstance = 0x0;
  318. $recType = 0xF010;
  319. // start coordinates
  320. list($column, $row) = Coordinate::coordinateFromString($this->object->getStartCoordinates());
  321. $c1 = Coordinate::columnIndexFromString($column) - 1;
  322. $r1 = $row - 1;
  323. // start offsetX
  324. $startOffsetX = $this->object->getStartOffsetX();
  325. // start offsetY
  326. $startOffsetY = $this->object->getStartOffsetY();
  327. // end coordinates
  328. list($column, $row) = Coordinate::coordinateFromString($this->object->getEndCoordinates());
  329. $c2 = Coordinate::columnIndexFromString($column) - 1;
  330. $r2 = $row - 1;
  331. // end offsetX
  332. $endOffsetX = $this->object->getEndOffsetX();
  333. // end offsetY
  334. $endOffsetY = $this->object->getEndOffsetY();
  335. $clientAnchorData = pack('vvvvvvvvv', $this->object->getSpFlag(), $c1, $startOffsetX, $r1, $startOffsetY, $c2, $endOffsetX, $r2, $endOffsetY);
  336. $length = strlen($clientAnchorData);
  337. $recVerInstance = $recVer;
  338. $recVerInstance |= $recInstance << 4;
  339. $header = pack('vvV', $recVerInstance, $recType, $length);
  340. $data .= $header . $clientAnchorData;
  341. }
  342. // the client data, just empty for now
  343. if (!$this->object->getSpgr()) {
  344. $clientDataData = '';
  345. $recVer = 0x0;
  346. $recInstance = 0x0;
  347. $recType = 0xF011;
  348. $length = strlen($clientDataData);
  349. $recVerInstance = $recVer;
  350. $recVerInstance |= $recInstance << 4;
  351. $header = pack('vvV', $recVerInstance, $recType, $length);
  352. $data .= $header . $clientDataData;
  353. }
  354. // write the record
  355. $recVer = 0xF;
  356. $recInstance = 0x0000;
  357. $recType = 0xF004;
  358. $length = strlen($data);
  359. $recVerInstance = $recVer;
  360. $recVerInstance |= $recInstance << 4;
  361. $header = pack('vvV', $recVerInstance, $recType, $length);
  362. $this->data = $header . $data;
  363. break;
  364. }
  365. return $this->data;
  366. }
  367. /**
  368. * Gets the shape offsets.
  369. *
  370. * @return array
  371. */
  372. public function getSpOffsets()
  373. {
  374. return $this->spOffsets;
  375. }
  376. /**
  377. * Gets the shape types.
  378. *
  379. * @return array
  380. */
  381. public function getSpTypes()
  382. {
  383. return $this->spTypes;
  384. }
  385. }