mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-19 22:47:06 +00:00
WIP - Write in-cell drawing
This commit is contained in:
@@ -15,6 +15,7 @@ use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Protection;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Style;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Stringable;
|
||||
@@ -307,6 +308,12 @@ class Cell implements Stringable
|
||||
$this->value = SharedDate::convertIsoDate($value);
|
||||
$dataType = DataType::TYPE_NUMERIC;
|
||||
|
||||
break;
|
||||
case DataType::TYPE_DRAWING_IN_CELL:
|
||||
if ($value instanceof BaseDrawing) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
break;
|
||||
case DataType::TYPE_ERROR:
|
||||
$this->value = DataType::checkErrorCode($value);
|
||||
|
||||
@@ -17,6 +17,7 @@ class DataType
|
||||
const TYPE_INLINE = 'inlineStr';
|
||||
const TYPE_ERROR = 'e';
|
||||
const TYPE_ISO_DATE = 'd';
|
||||
const TYPE_DRAWING_IN_CELL = 'drawingCell';
|
||||
|
||||
/**
|
||||
* List of error codes.
|
||||
|
||||
@@ -967,10 +967,11 @@ class Xlsx extends BaseReader
|
||||
$objDrawing->setOffsetY(0);
|
||||
$objDrawing->setResizeProportional(false);
|
||||
$objDrawing->setWorksheet($docSheet);
|
||||
$objDrawing->setInCell(true);
|
||||
|
||||
$value = $objDrawing;
|
||||
$cellDataType = DataType::TYPE_NULL;
|
||||
$c->t = DataType::TYPE_NULL;
|
||||
$cellDataType = DataType::TYPE_DRAWING_IN_CELL;
|
||||
$c->t = DataType::TYPE_ERROR;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -137,6 +137,8 @@ class BaseDrawing implements IComparable
|
||||
*/
|
||||
protected ?int $opacity = null;
|
||||
|
||||
protected bool $inCell = false;
|
||||
|
||||
/**
|
||||
* Create a new BaseDrawing.
|
||||
*/
|
||||
@@ -572,4 +574,16 @@ class BaseDrawing implements IComparable
|
||||
{
|
||||
return $this->opacity;
|
||||
}
|
||||
|
||||
public function setInCell(bool $inCell): self
|
||||
{
|
||||
$this->inCell = $inCell;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isInCell(): ?bool
|
||||
{
|
||||
return $this->inCell;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Drawing;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RichDataDrawing;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Table;
|
||||
@@ -463,6 +464,14 @@ class Xlsx extends BaseWriter
|
||||
$zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = '<xml></xml>';
|
||||
}
|
||||
|
||||
$richDataDrawing = new RichDataDrawing();
|
||||
$richDataFiles = $richDataDrawing->generateFiles($this->spreadSheet->getSheet($i));
|
||||
|
||||
// Add all Rich Data files to ZIP
|
||||
foreach ($richDataFiles as $path => $content) {
|
||||
$zipContent[$path] = $content;
|
||||
}
|
||||
|
||||
// Add comment relationship parts
|
||||
/** @var mixed[][] */
|
||||
$legacyTemp = $unparsedLoadedData['sheets'] ?? [];
|
||||
|
||||
@@ -48,10 +48,11 @@ class Drawing extends WriterPart
|
||||
$pRelationId = $i;
|
||||
$hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i;
|
||||
|
||||
$this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
|
||||
|
||||
if (!$pDrawing->isInCell()) {
|
||||
$this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
|
||||
++$i;
|
||||
}
|
||||
$iterator->next();
|
||||
++$i;
|
||||
}
|
||||
|
||||
if ($includeCharts) {
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class RichDataDrawing
|
||||
{
|
||||
/** @var BaseDrawing[] */
|
||||
private array $drawings = [];
|
||||
|
||||
/**
|
||||
* Generate all Rich Data XML files.
|
||||
*
|
||||
* @return array<string,string> [path => XML content]
|
||||
*/
|
||||
public function generateFiles(Worksheet $worksheet): array
|
||||
{
|
||||
$iterator = $worksheet->getDrawingCollection()->getIterator();
|
||||
while ($iterator->valid()) {
|
||||
/** @var BaseDrawing $pDrawing */
|
||||
$pDrawing = $iterator->current();
|
||||
if ($pDrawing->isInCell()) {
|
||||
$this->drawings[] = $pDrawing;
|
||||
}
|
||||
$iterator->next();
|
||||
}
|
||||
|
||||
if (count($this->drawings) === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'xl/richData/rdrichvalue.xml' => $this->writeRdrichvalueXML(),
|
||||
'xl/richData/rdrichvaluestructure.xml' => $this->writeRdrichvaluestructureXML(),
|
||||
'xl/richData/rdRichValueTypes.xml' => $this->writeRdRichValueTypesXML(),
|
||||
'xl/richData/richValueRel.xml' => $this->writeRichValueRelXML(),
|
||||
'xl/richData/_rels/richValueRel.xml.rels' => $this->writeRichValueRelRelsXML(),
|
||||
];
|
||||
}
|
||||
|
||||
private function writeRdrichvalueXML(): string
|
||||
{
|
||||
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
|
||||
$xml->startDocument('1.0', 'UTF-8', 'yes');
|
||||
$xml->startElement('rvData');
|
||||
$xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata');
|
||||
$xml->writeAttribute('count', (string) count($this->drawings));
|
||||
|
||||
foreach ($this->drawings as $index => $drawing) {
|
||||
if (!$drawing->isInCell()) {
|
||||
continue;
|
||||
}
|
||||
$xml->startElement('rv');
|
||||
$xml->writeAttribute('s', (string) $index);
|
||||
|
||||
// Sample values; adapt to your needs
|
||||
$xml->writeElement('v', '0');
|
||||
$xml->writeElement('v', '5');
|
||||
|
||||
$xml->endElement(); // rv
|
||||
}
|
||||
|
||||
$xml->endElement(); // rvData
|
||||
|
||||
return $xml->getData();
|
||||
}
|
||||
|
||||
private function writeRdrichvaluestructureXML(): string
|
||||
{
|
||||
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
|
||||
$xml->startDocument('1.0', 'UTF-8', 'yes');
|
||||
$xml->startElement('rvStructures');
|
||||
$xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata');
|
||||
$xml->writeAttribute('count', (string) count($this->drawings));
|
||||
|
||||
foreach ($this->drawings as $drawing) {
|
||||
if (!$drawing->isInCell()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$xml->startElement('s');
|
||||
$xml->writeAttribute('t', '_localImage');
|
||||
|
||||
$xml->startElement('k');
|
||||
$xml->writeAttribute('n', '_rvRel:LocalImageIdentifier');
|
||||
$xml->writeAttribute('t', 'i');
|
||||
$xml->endElement();
|
||||
|
||||
$xml->startElement('k');
|
||||
$xml->writeAttribute('n', 'CalcOrigin');
|
||||
$xml->writeAttribute('t', 'i');
|
||||
$xml->endElement();
|
||||
|
||||
$xml->endElement(); // s
|
||||
}
|
||||
|
||||
$xml->endElement(); // rvStructures
|
||||
|
||||
return $xml->getData();
|
||||
}
|
||||
|
||||
private function writeRdRichValueTypesXML(): string
|
||||
{
|
||||
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
|
||||
$xml->startDocument('1.0', 'UTF-8', 'yes');
|
||||
$xml->startElement('rvTypesInfo');
|
||||
$xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2');
|
||||
$xml->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
|
||||
$xml->writeAttribute('mc:Ignorable', 'x');
|
||||
$xml->writeAttribute('xmlns:x', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
|
||||
|
||||
$xml->startElement('global');
|
||||
$xml->startElement('keyFlags');
|
||||
|
||||
$keys = [
|
||||
'_Self', '_DisplayString', '_Flags', '_Format',
|
||||
'_SubLabel', '_Attribution', '_Icon', '_Display',
|
||||
'_CanonicalPropertyNames', '_ClassificationId',
|
||||
];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$xml->startElement('key');
|
||||
$xml->writeAttribute('name', $key);
|
||||
|
||||
$xml->startElement('flag');
|
||||
$xml->writeAttribute('name', 'ExcludeFromCalcComparison');
|
||||
$xml->writeAttribute('value', '1');
|
||||
if ($key === '_Self') {
|
||||
$xml->startElement('flag');
|
||||
$xml->writeAttribute('name', 'ExcludeFromFile');
|
||||
$xml->writeAttribute('value', '1');
|
||||
$xml->endElement();
|
||||
}
|
||||
$xml->endElement(); // flag
|
||||
$xml->endElement(); // key
|
||||
}
|
||||
|
||||
$xml->endElement(); // keyFlags
|
||||
$xml->endElement(); // global
|
||||
$xml->endElement(); // rvTypesInfo
|
||||
|
||||
return $xml->getData();
|
||||
}
|
||||
|
||||
private function writeRichValueRelXML(): string
|
||||
{
|
||||
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
|
||||
$xml->startDocument('1.0', 'UTF-8', 'yes');
|
||||
$xml->startElement('richValueRels');
|
||||
$xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel');
|
||||
$xml->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
|
||||
|
||||
foreach ($this->drawings as $index => $drawing) {
|
||||
if (!$drawing->isInCell()) {
|
||||
continue;
|
||||
}
|
||||
$xml->startElement('rel');
|
||||
$xml->writeAttribute('r:id', 'rId' . ($index + 1));
|
||||
$xml->endElement();
|
||||
}
|
||||
|
||||
$xml->endElement(); // richValueRels
|
||||
|
||||
return $xml->getData();
|
||||
}
|
||||
|
||||
private function writeRichValueRelRelsXML(): string
|
||||
{
|
||||
$xml = new XMLWriter(XMLWriter::STORAGE_MEMORY);
|
||||
$xml->startDocument('1.0', 'UTF-8', 'yes');
|
||||
$xml->startElement('Relationships');
|
||||
$xml->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
|
||||
|
||||
foreach ($this->drawings as $index => $drawing) {
|
||||
if (!$drawing->isInCell()) {
|
||||
continue;
|
||||
}
|
||||
$xml->startElement('Relationship');
|
||||
$xml->writeAttribute('Id', 'rId' . ($index + 1));
|
||||
$xml->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image');
|
||||
$xml->writeAttribute('Target', '../media/' . $drawing->getIndexedFilename());
|
||||
$xml->endElement();
|
||||
}
|
||||
|
||||
$xml->endElement(); // Relationships
|
||||
|
||||
return $xml->getData();
|
||||
}
|
||||
}
|
||||
@@ -1557,6 +1557,13 @@ class Worksheet extends WriterPart
|
||||
$objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue);
|
||||
}
|
||||
|
||||
private function writeCellDrawing(XMLWriter $objWriter): void
|
||||
{
|
||||
$objWriter->writeAttribute('t', 'e');
|
||||
$objWriter->writeAttribute('vm', '1');
|
||||
$objWriter->writeElement('v', '#VALUE!');
|
||||
}
|
||||
|
||||
private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $cell): void
|
||||
{
|
||||
$attributes = $cell->getFormulaAttributes() ?? [];
|
||||
@@ -1737,6 +1744,10 @@ class Worksheet extends WriterPart
|
||||
case 'b': // Boolean
|
||||
$this->writeCellBoolean($objWriter, $mappedType, (bool) $cellValue);
|
||||
|
||||
break;
|
||||
case 'drawingcell': // DrawingInCell
|
||||
$this->writeCellDrawing($objWriter);
|
||||
|
||||
break;
|
||||
case 'e': // Error
|
||||
$this->writeCellError($objWriter, $mappedType, $cellValueString);
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -31,6 +32,9 @@ class DrawingInCellTest extends TestCase
|
||||
self::assertSame(154, $drawings[0]->getImageHeight());
|
||||
}
|
||||
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
|
||||
$writer->save('tests/data/Reader/XLSX/drawing_in_cell-written.xlsx');
|
||||
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user