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