Skip to content

Commit 4ccf9c0

Browse files
committed
WIP - Write in-cell drawing
1 parent 47bf735 commit 4ccf9c0

File tree

9 files changed

+245
-5
lines changed

9 files changed

+245
-5
lines changed

src/PhpSpreadsheet/Cell/Cell.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
1616
use PhpOffice\PhpSpreadsheet\Style\Protection;
1717
use PhpOffice\PhpSpreadsheet\Style\Style;
18+
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
1819
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
1920
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
2021
use Stringable;
@@ -307,6 +308,12 @@ public function setValueExplicit(mixed $value, string $dataType = DataType::TYPE
307308
$this->value = SharedDate::convertIsoDate($value);
308309
$dataType = DataType::TYPE_NUMERIC;
309310

311+
break;
312+
case DataType::TYPE_DRAWING_IN_CELL:
313+
if ($value instanceof BaseDrawing) {
314+
$this->value = $value;
315+
}
316+
310317
break;
311318
case DataType::TYPE_ERROR:
312319
$this->value = DataType::checkErrorCode($value);

src/PhpSpreadsheet/Cell/DataType.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class DataType
1717
const TYPE_INLINE = 'inlineStr';
1818
const TYPE_ERROR = 'e';
1919
const TYPE_ISO_DATE = 'd';
20+
const TYPE_DRAWING_IN_CELL = 'drawingCell';
2021

2122
/**
2223
* List of error codes.

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -967,10 +967,11 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
967967
$objDrawing->setOffsetY(0);
968968
$objDrawing->setResizeProportional(false);
969969
$objDrawing->setWorksheet($docSheet);
970+
$objDrawing->setInCell(true);
970971

971972
$value = $objDrawing;
972-
$cellDataType = DataType::TYPE_NULL;
973-
$c->t = DataType::TYPE_NULL;
973+
$cellDataType = DataType::TYPE_DRAWING_IN_CELL;
974+
$c->t = DataType::TYPE_ERROR;
974975

975976
break;
976977
}

src/PhpSpreadsheet/Worksheet/BaseDrawing.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ class BaseDrawing implements IComparable
137137
*/
138138
protected ?int $opacity = null;
139139

140+
protected bool $inCell = false;
141+
140142
/**
141143
* Create a new BaseDrawing.
142144
*/
@@ -572,4 +574,16 @@ public function getOpacity(): ?int
572574
{
573575
return $this->opacity;
574576
}
577+
578+
public function setInCell(bool $inCell): self
579+
{
580+
$this->inCell = $inCell;
581+
582+
return $this;
583+
}
584+
585+
public function isInCell(): ?bool
586+
{
587+
return $this->inCell;
588+
}
575589
}

src/PhpSpreadsheet/Writer/Xlsx.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels;
2525
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon;
2626
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA;
27+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RichDataDrawing;
2728
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable;
2829
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style;
2930
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Table;
@@ -463,6 +464,14 @@ public function save($filename, int $flags = 0): void
463464
$zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = '<xml></xml>';
464465
}
465466

467+
$richDataDrawing = new RichDataDrawing();
468+
$richDataFiles = $richDataDrawing->generateFiles($this->spreadSheet->getSheet($i));
469+
470+
// Add all Rich Data files to ZIP
471+
foreach ($richDataFiles as $path => $content) {
472+
$zipContent[$path] = $content;
473+
}
474+
466475
// Add comment relationship parts
467476
/** @var mixed[][] */
468477
$legacyTemp = $unparsedLoadedData['sheets'] ?? [];

src/PhpSpreadsheet/Writer/Xlsx/Drawing.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $wor
4848
$pRelationId = $i;
4949
$hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i;
5050

51-
$this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
52-
51+
if (!$pDrawing->isInCell()) {
52+
$this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
53+
++$i;
54+
}
5355
$iterator->next();
54-
++$i;
5556
}
5657

5758
if ($includeCharts) {
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
4+
5+
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
6+
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
7+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
8+
9+
class RichDataDrawing
10+
{
11+
/** @var BaseDrawing[] */
12+
private array $drawings = [];
13+
14+
/**
15+
* Generate all Rich Data XML files.
16+
*
17+
* @return array<string,string> [path => XML content]
18+
*/
19+
public function generateFiles(Worksheet $worksheet): array
20+
{
21+
$iterator = $worksheet->getDrawingCollection()->getIterator();
22+
while ($iterator->valid()) {
23+
/** @var BaseDrawing $pDrawing */
24+
$pDrawing = $iterator->current();
25+
if ($pDrawing->isInCell()) {
26+
$this->drawings[] = $pDrawing;
27+
}
28+
$iterator->next();
29+
}
30+
31+
if (count($this->drawings) === 0) {
32+
return [];
33+
}
34+
35+
return [
36+
'xl/richData/rdrichvalue.xml' => $this->writeRdrichvalueXML(),
37+
'xl/richData/rdrichvaluestructure.xml' => $this->writeRdrichvaluestructureXML(),
38+
'xl/richData/rdRichValueTypes.xml' => $this->writeRdRichValueTypesXML(),
39+
'xl/richData/richValueRel.xml' => $this->writeRichValueRelXML(),
40+
'xl/richData/_rels/richValueRel.xml.rels' => $this->writeRichValueRelRelsXML(),
41+
];
42+
}
43+
44+
private function writeRdrichvalueXML(): string
45+
{
46+
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
47+
$xml->startDocument('1.0', 'UTF-8', 'yes');
48+
$xml->startElement('rvData');
49+
$xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata');
50+
$xml->writeAttribute('count', (string) count($this->drawings));
51+
52+
foreach ($this->drawings as $index => $drawing) {
53+
if (!$drawing->isInCell()) {
54+
continue;
55+
}
56+
$xml->startElement('rv');
57+
$xml->writeAttribute('s', (string) $index);
58+
59+
// Sample values; adapt to your needs
60+
$xml->writeElement('v', '0');
61+
$xml->writeElement('v', '5');
62+
63+
$xml->endElement(); // rv
64+
}
65+
66+
$xml->endElement(); // rvData
67+
68+
return $xml->getData();
69+
}
70+
71+
private function writeRdrichvaluestructureXML(): string
72+
{
73+
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
74+
$xml->startDocument('1.0', 'UTF-8', 'yes');
75+
$xml->startElement('rvStructures');
76+
$xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata');
77+
$xml->writeAttribute('count', (string) count($this->drawings));
78+
79+
foreach ($this->drawings as $drawing) {
80+
if (!$drawing->isInCell()) {
81+
continue;
82+
}
83+
84+
$xml->startElement('s');
85+
$xml->writeAttribute('t', '_localImage');
86+
87+
$xml->startElement('k');
88+
$xml->writeAttribute('n', '_rvRel:LocalImageIdentifier');
89+
$xml->writeAttribute('t', 'i');
90+
$xml->endElement();
91+
92+
$xml->startElement('k');
93+
$xml->writeAttribute('n', 'CalcOrigin');
94+
$xml->writeAttribute('t', 'i');
95+
$xml->endElement();
96+
97+
$xml->endElement(); // s
98+
}
99+
100+
$xml->endElement(); // rvStructures
101+
102+
return $xml->getData();
103+
}
104+
105+
private function writeRdRichValueTypesXML(): string
106+
{
107+
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
108+
$xml->startDocument('1.0', 'UTF-8', 'yes');
109+
$xml->startElement('rvTypesInfo');
110+
$xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2');
111+
$xml->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
112+
$xml->writeAttribute('mc:Ignorable', 'x');
113+
$xml->writeAttribute('xmlns:x', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
114+
115+
$xml->startElement('global');
116+
$xml->startElement('keyFlags');
117+
118+
$keys = [
119+
'_Self', '_DisplayString', '_Flags', '_Format',
120+
'_SubLabel', '_Attribution', '_Icon', '_Display',
121+
'_CanonicalPropertyNames', '_ClassificationId',
122+
];
123+
124+
foreach ($keys as $key) {
125+
$xml->startElement('key');
126+
$xml->writeAttribute('name', $key);
127+
128+
$xml->startElement('flag');
129+
$xml->writeAttribute('name', 'ExcludeFromCalcComparison');
130+
$xml->writeAttribute('value', '1');
131+
if ($key === '_Self') {
132+
$xml->startElement('flag');
133+
$xml->writeAttribute('name', 'ExcludeFromFile');
134+
$xml->writeAttribute('value', '1');
135+
$xml->endElement();
136+
}
137+
$xml->endElement(); // flag
138+
$xml->endElement(); // key
139+
}
140+
141+
$xml->endElement(); // keyFlags
142+
$xml->endElement(); // global
143+
$xml->endElement(); // rvTypesInfo
144+
145+
return $xml->getData();
146+
}
147+
148+
private function writeRichValueRelXML(): string
149+
{
150+
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
151+
$xml->startDocument('1.0', 'UTF-8', 'yes');
152+
$xml->startElement('richValueRels');
153+
$xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel');
154+
$xml->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
155+
156+
foreach ($this->drawings as $index => $drawing) {
157+
if (!$drawing->isInCell()) {
158+
continue;
159+
}
160+
$xml->startElement('rel');
161+
$xml->writeAttribute('r:id', 'rId' . ($index + 1));
162+
$xml->endElement();
163+
}
164+
165+
$xml->endElement(); // richValueRels
166+
167+
return $xml->getData();
168+
}
169+
170+
private function writeRichValueRelRelsXML(): string
171+
{
172+
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
173+
$xml->startDocument('1.0', 'UTF-8', 'yes');
174+
$xml->startElement('Relationships');
175+
$xml->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
176+
177+
foreach ($this->drawings as $index => $drawing) {
178+
if (!$drawing->isInCell()) {
179+
continue;
180+
}
181+
$xml->startElement('Relationship');
182+
$xml->writeAttribute('Id', 'rId' . ($index + 1));
183+
$xml->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image');
184+
$xml->writeAttribute('Target', '../media/' . $drawing->getIndexedFilename());
185+
$xml->endElement();
186+
}
187+
188+
$xml->endElement(); // Relationships
189+
190+
return $xml->getData();
191+
}
192+
}

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,13 @@ private function writeCellError(XMLWriter $objWriter, string $mappedType, string
15571557
$objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue);
15581558
}
15591559

1560+
private function writeCellDrawing(XMLWriter $objWriter): void
1561+
{
1562+
$objWriter->writeAttribute('t', 'e');
1563+
$objWriter->writeAttribute('vm', '1');
1564+
$objWriter->writeElement('v', '#VALUE!');
1565+
}
1566+
15601567
private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $cell): void
15611568
{
15621569
$attributes = $cell->getFormulaAttributes() ?? [];
@@ -1737,6 +1744,10 @@ private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksh
17371744
case 'b': // Boolean
17381745
$this->writeCellBoolean($objWriter, $mappedType, (bool) $cellValue);
17391746

1747+
break;
1748+
case 'drawingcell': // DrawingInCell
1749+
$this->writeCellDrawing($objWriter);
1750+
17401751
break;
17411752
case 'e': // Error
17421753
$this->writeCellError($objWriter, $mappedType, $cellValueString);

tests/PhpSpreadsheetTests/Reader/Xlsx/DrawingInCellTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
66

7+
use PhpOffice\PhpSpreadsheet\IOFactory;
78
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
89
use PHPUnit\Framework\TestCase;
910

@@ -31,6 +32,9 @@ public function testPictureInCell(): void
3132
self::assertSame(154, $drawings[0]->getImageHeight());
3233
}
3334

35+
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
36+
$writer->save('tests/data/Reader/XLSX/drawing_in_cell-written.xlsx');
37+
3438
$spreadsheet->disconnectWorksheets();
3539
}
3640
}

0 commit comments

Comments
 (0)