1<?php 2 3namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; 4 5use PhpOffice\PhpSpreadsheet\Cell\Coordinate; 6use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; 7use PhpOffice\PhpSpreadsheet\Spreadsheet; 8use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; 9use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing; 10use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; 11 12class Drawing extends WriterPart 13{ 14 /** 15 * Write drawings to XML format. 16 * 17 * @param bool $includeCharts Flag indicating if we should include drawing details for charts 18 * 19 * @return string XML Output 20 */ 21 public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $includeCharts = false) 22 { 23 // Create XML writer 24 $objWriter = null; 25 if ($this->getParentWriter()->getUseDiskCaching()) { 26 $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); 27 } else { 28 $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); 29 } 30 31 // XML header 32 $objWriter->startDocument('1.0', 'UTF-8', 'yes'); 33 34 // xdr:wsDr 35 $objWriter->startElement('xdr:wsDr'); 36 $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); 37 $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); 38 39 // Loop through images and write drawings 40 $i = 1; 41 $iterator = $pWorksheet->getDrawingCollection()->getIterator(); 42 while ($iterator->valid()) { 43 /** @var BaseDrawing $pDrawing */ 44 $pDrawing = $iterator->current(); 45 $pRelationId = $i; 46 $hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i; 47 48 $this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId); 49 50 $iterator->next(); 51 ++$i; 52 } 53 54 if ($includeCharts) { 55 $chartCount = $pWorksheet->getChartCount(); 56 // Loop through charts and write the chart position 57 if ($chartCount > 0) { 58 for ($c = 0; $c < $chartCount; ++$c) { 59 $this->writeChart($objWriter, $pWorksheet->getChartByIndex($c), $c + $i); 60 } 61 } 62 } 63 64 // unparsed AlternateContent 65 $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData(); 66 if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) { 67 foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) { 68 $objWriter->writeRaw($drawingAlternateContent); 69 } 70 } 71 72 $objWriter->endElement(); 73 74 // Return 75 return $objWriter->getData(); 76 } 77 78 /** 79 * Write drawings to XML format. 80 * 81 * @param XMLWriter $objWriter XML Writer 82 * @param int $pRelationId 83 */ 84 public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $pRelationId = -1): void 85 { 86 $tl = $pChart->getTopLeftPosition(); 87 $tl['colRow'] = Coordinate::coordinateFromString($tl['cell']); 88 $br = $pChart->getBottomRightPosition(); 89 $br['colRow'] = Coordinate::coordinateFromString($br['cell']); 90 91 $objWriter->startElement('xdr:twoCellAnchor'); 92 93 $objWriter->startElement('xdr:from'); 94 $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($tl['colRow'][0]) - 1); 95 $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset'])); 96 $objWriter->writeElement('xdr:row', $tl['colRow'][1] - 1); 97 $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset'])); 98 $objWriter->endElement(); 99 $objWriter->startElement('xdr:to'); 100 $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($br['colRow'][0]) - 1); 101 $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset'])); 102 $objWriter->writeElement('xdr:row', $br['colRow'][1] - 1); 103 $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset'])); 104 $objWriter->endElement(); 105 106 $objWriter->startElement('xdr:graphicFrame'); 107 $objWriter->writeAttribute('macro', ''); 108 $objWriter->startElement('xdr:nvGraphicFramePr'); 109 $objWriter->startElement('xdr:cNvPr'); 110 $objWriter->writeAttribute('name', 'Chart ' . $pRelationId); 111 $objWriter->writeAttribute('id', 1025 * $pRelationId); 112 $objWriter->endElement(); 113 $objWriter->startElement('xdr:cNvGraphicFramePr'); 114 $objWriter->startElement('a:graphicFrameLocks'); 115 $objWriter->endElement(); 116 $objWriter->endElement(); 117 $objWriter->endElement(); 118 119 $objWriter->startElement('xdr:xfrm'); 120 $objWriter->startElement('a:off'); 121 $objWriter->writeAttribute('x', '0'); 122 $objWriter->writeAttribute('y', '0'); 123 $objWriter->endElement(); 124 $objWriter->startElement('a:ext'); 125 $objWriter->writeAttribute('cx', '0'); 126 $objWriter->writeAttribute('cy', '0'); 127 $objWriter->endElement(); 128 $objWriter->endElement(); 129 130 $objWriter->startElement('a:graphic'); 131 $objWriter->startElement('a:graphicData'); 132 $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); 133 $objWriter->startElement('c:chart'); 134 $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); 135 $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); 136 $objWriter->writeAttribute('r:id', 'rId' . $pRelationId); 137 $objWriter->endElement(); 138 $objWriter->endElement(); 139 $objWriter->endElement(); 140 $objWriter->endElement(); 141 142 $objWriter->startElement('xdr:clientData'); 143 $objWriter->endElement(); 144 145 $objWriter->endElement(); 146 } 147 148 /** 149 * Write drawings to XML format. 150 * 151 * @param XMLWriter $objWriter XML Writer 152 * @param int $pRelationId 153 * @param null|int $hlinkClickId 154 */ 155 public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1, $hlinkClickId = null): void 156 { 157 if ($pRelationId >= 0) { 158 // xdr:oneCellAnchor 159 $objWriter->startElement('xdr:oneCellAnchor'); 160 // Image location 161 $aCoordinates = Coordinate::coordinateFromString($pDrawing->getCoordinates()); 162 $aCoordinates[0] = Coordinate::columnIndexFromString($aCoordinates[0]); 163 164 // xdr:from 165 $objWriter->startElement('xdr:from'); 166 $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1); 167 $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetX())); 168 $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1); 169 $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetY())); 170 $objWriter->endElement(); 171 172 // xdr:ext 173 $objWriter->startElement('xdr:ext'); 174 $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getWidth())); 175 $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getHeight())); 176 $objWriter->endElement(); 177 178 // xdr:pic 179 $objWriter->startElement('xdr:pic'); 180 181 // xdr:nvPicPr 182 $objWriter->startElement('xdr:nvPicPr'); 183 184 // xdr:cNvPr 185 $objWriter->startElement('xdr:cNvPr'); 186 $objWriter->writeAttribute('id', $pRelationId); 187 $objWriter->writeAttribute('name', $pDrawing->getName()); 188 $objWriter->writeAttribute('descr', $pDrawing->getDescription()); 189 190 //a:hlinkClick 191 $this->writeHyperLinkDrawing($objWriter, $hlinkClickId); 192 193 $objWriter->endElement(); 194 195 // xdr:cNvPicPr 196 $objWriter->startElement('xdr:cNvPicPr'); 197 198 // a:picLocks 199 $objWriter->startElement('a:picLocks'); 200 $objWriter->writeAttribute('noChangeAspect', '1'); 201 $objWriter->endElement(); 202 203 $objWriter->endElement(); 204 205 $objWriter->endElement(); 206 207 // xdr:blipFill 208 $objWriter->startElement('xdr:blipFill'); 209 210 // a:blip 211 $objWriter->startElement('a:blip'); 212 $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); 213 $objWriter->writeAttribute('r:embed', 'rId' . $pRelationId); 214 $objWriter->endElement(); 215 216 // a:stretch 217 $objWriter->startElement('a:stretch'); 218 $objWriter->writeElement('a:fillRect', null); 219 $objWriter->endElement(); 220 221 $objWriter->endElement(); 222 223 // xdr:spPr 224 $objWriter->startElement('xdr:spPr'); 225 226 // a:xfrm 227 $objWriter->startElement('a:xfrm'); 228 $objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getRotation())); 229 $objWriter->endElement(); 230 231 // a:prstGeom 232 $objWriter->startElement('a:prstGeom'); 233 $objWriter->writeAttribute('prst', 'rect'); 234 235 // a:avLst 236 $objWriter->writeElement('a:avLst', null); 237 238 $objWriter->endElement(); 239 240 if ($pDrawing->getShadow()->getVisible()) { 241 // a:effectLst 242 $objWriter->startElement('a:effectLst'); 243 244 // a:outerShdw 245 $objWriter->startElement('a:outerShdw'); 246 $objWriter->writeAttribute('blurRad', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getBlurRadius())); 247 $objWriter->writeAttribute('dist', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getDistance())); 248 $objWriter->writeAttribute('dir', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getShadow()->getDirection())); 249 $objWriter->writeAttribute('algn', $pDrawing->getShadow()->getAlignment()); 250 $objWriter->writeAttribute('rotWithShape', '0'); 251 252 // a:srgbClr 253 $objWriter->startElement('a:srgbClr'); 254 $objWriter->writeAttribute('val', $pDrawing->getShadow()->getColor()->getRGB()); 255 256 // a:alpha 257 $objWriter->startElement('a:alpha'); 258 $objWriter->writeAttribute('val', $pDrawing->getShadow()->getAlpha() * 1000); 259 $objWriter->endElement(); 260 261 $objWriter->endElement(); 262 263 $objWriter->endElement(); 264 265 $objWriter->endElement(); 266 } 267 $objWriter->endElement(); 268 269 $objWriter->endElement(); 270 271 // xdr:clientData 272 $objWriter->writeElement('xdr:clientData', null); 273 274 $objWriter->endElement(); 275 } else { 276 throw new WriterException('Invalid parameters passed.'); 277 } 278 } 279 280 /** 281 * Write VML header/footer images to XML format. 282 * 283 * @return string XML Output 284 */ 285 public function writeVMLHeaderFooterImages(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet) 286 { 287 // Create XML writer 288 $objWriter = null; 289 if ($this->getParentWriter()->getUseDiskCaching()) { 290 $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); 291 } else { 292 $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); 293 } 294 295 // XML header 296 $objWriter->startDocument('1.0', 'UTF-8', 'yes'); 297 298 // Header/footer images 299 $images = $pWorksheet->getHeaderFooter()->getImages(); 300 301 // xml 302 $objWriter->startElement('xml'); 303 $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); 304 $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); 305 $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel'); 306 307 // o:shapelayout 308 $objWriter->startElement('o:shapelayout'); 309 $objWriter->writeAttribute('v:ext', 'edit'); 310 311 // o:idmap 312 $objWriter->startElement('o:idmap'); 313 $objWriter->writeAttribute('v:ext', 'edit'); 314 $objWriter->writeAttribute('data', '1'); 315 $objWriter->endElement(); 316 317 $objWriter->endElement(); 318 319 // v:shapetype 320 $objWriter->startElement('v:shapetype'); 321 $objWriter->writeAttribute('id', '_x0000_t75'); 322 $objWriter->writeAttribute('coordsize', '21600,21600'); 323 $objWriter->writeAttribute('o:spt', '75'); 324 $objWriter->writeAttribute('o:preferrelative', 't'); 325 $objWriter->writeAttribute('path', 'm@4@5l@4@11@9@11@9@5xe'); 326 $objWriter->writeAttribute('filled', 'f'); 327 $objWriter->writeAttribute('stroked', 'f'); 328 329 // v:stroke 330 $objWriter->startElement('v:stroke'); 331 $objWriter->writeAttribute('joinstyle', 'miter'); 332 $objWriter->endElement(); 333 334 // v:formulas 335 $objWriter->startElement('v:formulas'); 336 337 // v:f 338 $objWriter->startElement('v:f'); 339 $objWriter->writeAttribute('eqn', 'if lineDrawn pixelLineWidth 0'); 340 $objWriter->endElement(); 341 342 // v:f 343 $objWriter->startElement('v:f'); 344 $objWriter->writeAttribute('eqn', 'sum @0 1 0'); 345 $objWriter->endElement(); 346 347 // v:f 348 $objWriter->startElement('v:f'); 349 $objWriter->writeAttribute('eqn', 'sum 0 0 @1'); 350 $objWriter->endElement(); 351 352 // v:f 353 $objWriter->startElement('v:f'); 354 $objWriter->writeAttribute('eqn', 'prod @2 1 2'); 355 $objWriter->endElement(); 356 357 // v:f 358 $objWriter->startElement('v:f'); 359 $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelWidth'); 360 $objWriter->endElement(); 361 362 // v:f 363 $objWriter->startElement('v:f'); 364 $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelHeight'); 365 $objWriter->endElement(); 366 367 // v:f 368 $objWriter->startElement('v:f'); 369 $objWriter->writeAttribute('eqn', 'sum @0 0 1'); 370 $objWriter->endElement(); 371 372 // v:f 373 $objWriter->startElement('v:f'); 374 $objWriter->writeAttribute('eqn', 'prod @6 1 2'); 375 $objWriter->endElement(); 376 377 // v:f 378 $objWriter->startElement('v:f'); 379 $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelWidth'); 380 $objWriter->endElement(); 381 382 // v:f 383 $objWriter->startElement('v:f'); 384 $objWriter->writeAttribute('eqn', 'sum @8 21600 0'); 385 $objWriter->endElement(); 386 387 // v:f 388 $objWriter->startElement('v:f'); 389 $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelHeight'); 390 $objWriter->endElement(); 391 392 // v:f 393 $objWriter->startElement('v:f'); 394 $objWriter->writeAttribute('eqn', 'sum @10 21600 0'); 395 $objWriter->endElement(); 396 397 $objWriter->endElement(); 398 399 // v:path 400 $objWriter->startElement('v:path'); 401 $objWriter->writeAttribute('o:extrusionok', 'f'); 402 $objWriter->writeAttribute('gradientshapeok', 't'); 403 $objWriter->writeAttribute('o:connecttype', 'rect'); 404 $objWriter->endElement(); 405 406 // o:lock 407 $objWriter->startElement('o:lock'); 408 $objWriter->writeAttribute('v:ext', 'edit'); 409 $objWriter->writeAttribute('aspectratio', 't'); 410 $objWriter->endElement(); 411 412 $objWriter->endElement(); 413 414 // Loop through images 415 foreach ($images as $key => $value) { 416 $this->writeVMLHeaderFooterImage($objWriter, $key, $value); 417 } 418 419 $objWriter->endElement(); 420 421 // Return 422 return $objWriter->getData(); 423 } 424 425 /** 426 * Write VML comment to XML format. 427 * 428 * @param XMLWriter $objWriter XML Writer 429 * @param string $pReference Reference 430 * @param HeaderFooterDrawing $pImage Image 431 */ 432 private function writeVMLHeaderFooterImage(XMLWriter $objWriter, $pReference, HeaderFooterDrawing $pImage): void 433 { 434 // Calculate object id 435 preg_match('{(\d+)}', md5($pReference), $m); 436 $id = 1500 + (substr($m[1], 0, 2) * 1); 437 438 // Calculate offset 439 $width = $pImage->getWidth(); 440 $height = $pImage->getHeight(); 441 $marginLeft = $pImage->getOffsetX(); 442 $marginTop = $pImage->getOffsetY(); 443 444 // v:shape 445 $objWriter->startElement('v:shape'); 446 $objWriter->writeAttribute('id', $pReference); 447 $objWriter->writeAttribute('o:spid', '_x0000_s' . $id); 448 $objWriter->writeAttribute('type', '#_x0000_t75'); 449 $objWriter->writeAttribute('style', "position:absolute;margin-left:{$marginLeft}px;margin-top:{$marginTop}px;width:{$width}px;height:{$height}px;z-index:1"); 450 451 // v:imagedata 452 $objWriter->startElement('v:imagedata'); 453 $objWriter->writeAttribute('o:relid', 'rId' . $pReference); 454 $objWriter->writeAttribute('o:title', $pImage->getName()); 455 $objWriter->endElement(); 456 457 // o:lock 458 $objWriter->startElement('o:lock'); 459 $objWriter->writeAttribute('v:ext', 'edit'); 460 $objWriter->writeAttribute('textRotation', 't'); 461 $objWriter->endElement(); 462 463 $objWriter->endElement(); 464 } 465 466 /** 467 * Get an array of all drawings. 468 * 469 * @return \PhpOffice\PhpSpreadsheet\Worksheet\Drawing[] All drawings in PhpSpreadsheet 470 */ 471 public function allDrawings(Spreadsheet $spreadsheet) 472 { 473 // Get an array of all drawings 474 $aDrawings = []; 475 476 // Loop through PhpSpreadsheet 477 $sheetCount = $spreadsheet->getSheetCount(); 478 for ($i = 0; $i < $sheetCount; ++$i) { 479 // Loop through images and add to array 480 $iterator = $spreadsheet->getSheet($i)->getDrawingCollection()->getIterator(); 481 while ($iterator->valid()) { 482 $aDrawings[] = $iterator->current(); 483 484 $iterator->next(); 485 } 486 } 487 488 return $aDrawings; 489 } 490 491 /** 492 * @param null|int $hlinkClickId 493 */ 494 private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId): void 495 { 496 if ($hlinkClickId === null) { 497 return; 498 } 499 500 $objWriter->startElement('a:hlinkClick'); 501 $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); 502 $objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId); 503 $objWriter->endElement(); 504 } 505} 506