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