From 572f4e94bdcc436924f6f9e4f8263a9270c92480 Mon Sep 17 00:00:00 2001 From: naotake51 Date: Thu, 10 Mar 2022 12:21:47 +0900 Subject: [PATCH] Add two cell anchor drawing (#2532) * Add two cell anhor drawing. * Add "Support for two cell anchor drawing of images." to CHANGELOG.md * Add pull-request link to "Support for two cell anchor drawing of images." of CHANGELOG.md --- CHANGELOG.md | 9 +- phpstan-baseline.neon | 10 +- src/PhpSpreadsheet/Reader/Xlsx.php | 6 ++ src/PhpSpreadsheet/Worksheet/BaseDrawing.php | 99 +++++++++++++++++++ src/PhpSpreadsheet/Writer/Xlsx/Drawing.php | 63 +++++++++--- .../Writer/Xlsx/DrawingsTest.php | 52 ++++++++++ 6 files changed, 216 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 372000d7..1b343691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,10 +75,11 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Full support of the above CF Rules for the Xlsx Reader and Writer; even when the file being loaded has CF rules listed in the `` element for the worksheet rather than the `` element. - Provision of a CellMatcher to identify if rules are matched for a cell, and which matching style will be applied. - Improved documentation and examples, covering all supported CF rule types. -- Add support for one digit decimals (FORMAT_NUMBER_0, FORMAT_PERCENTAGE_0). [PR #2525](https://github.com/PHPOffice/PhpSpreadsheet/pull/2525) -- Initial work enabling Excel function implementations for handling arrays as arguments when used in "array formulae" [#2562](https://github.com/PHPOffice/PhpSpreadsheet/issues/2562) -- Enable most of the Date/Time functions to accept array arguments [#2573](https://github.com/PHPOffice/PhpSpreadsheet/issues/2573) -- Array ready functions - Text, Math/Trig, Statistical, Engineering and Logical [#2580](https://github.com/PHPOffice/PhpSpreadsheet/issues/2580) + - Add support for one digit decimals (FORMAT_NUMBER_0, FORMAT_PERCENTAGE_0). [PR #2525](https://github.com/PHPOffice/PhpSpreadsheet/pull/2525) + - Initial work enabling Excel function implementations for handling arrays as arguments when used in "array formulae" [#2562](https://github.com/PHPOffice/PhpSpreadsheet/issues/2562) + - Enable most of the Date/Time functions to accept array arguments [#2573](https://github.com/PHPOffice/PhpSpreadsheet/issues/2573) + - Array ready functions - Text, Math/Trig, Statistical, Engineering and Logical [#2580](https://github.com/PHPOffice/PhpSpreadsheet/issues/2580) + - Support for two cell anchor drawing of images. [#2532](https://github.com/PHPOffice/PhpSpreadsheet/pull/2532) ### Changed diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f2941bf6..ea6a7b89 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5485,6 +5485,11 @@ parameters: count: 2 path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php + - + message: "#^Parameter \\#1 \\$coordinates of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:indexesFromString\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + - message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getChartByIndex\\(\\) expects string, int\\<0, max\\> given\\.$#" count: 1 @@ -5497,12 +5502,12 @@ parameters: - message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#" - count: 12 + count: 20 path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" - count: 8 + count: 10 path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php - @@ -6009,4 +6014,3 @@ parameters: message: "#^Cannot call method getExtension\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\BaseDrawing\\|null\\.$#" count: 2 path: tests/PhpSpreadsheetTests/Writer/Xlsx/WmfTest.php - diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index eca8d7b8..5d6ef59d 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1340,6 +1340,12 @@ class Xlsx extends BaseReader $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff)); + + $objDrawing->setCoordinates2(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1)); + + $objDrawing->setOffsetX2(Drawing::EMUToPixels($twoCellAnchor->to->colOff)); + $objDrawing->setOffsetY2(Drawing::EMUToPixels($twoCellAnchor->to->rowOff)); + $objDrawing->setResizeProportional(false); if ($xfrm) { diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index 46f06158..bd90b2bc 100644 --- a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -64,6 +64,27 @@ class BaseDrawing implements IComparable */ protected $offsetY; + /** + * Coordinates2. + * + * @var null|string + */ + protected $coordinates2; + + /** + * Offset X2. + * + * @var int + */ + protected $offsetX2; + + /** + * Offset Y2. + * + * @var int + */ + protected $offsetY2; + /** * Width. * @@ -125,6 +146,9 @@ class BaseDrawing implements IComparable $this->coordinates = 'A1'; $this->offsetX = 0; $this->offsetY = 0; + $this->coordinates2 = null; + $this->offsetX2 = 0; + $this->offsetY2 = 0; $this->width = 0; $this->height = 0; $this->resizeProportional = true; @@ -301,6 +325,78 @@ class BaseDrawing implements IComparable return $this->offsetY; } + /** + * Get Coordinates2. + * + * @return null|string + */ + public function getCoordinates2() + { + return $this->coordinates2; + } + + /** + * Set Coordinates2. + * + * @param null|string $coordinates2 eg: 'A1' + * + * @return $this + */ + public function setCoordinates2($coordinates2) + { + $this->coordinates2 = $coordinates2; + + return $this; + } + + /** + * Get OffsetX2. + * + * @return int + */ + public function getOffsetX2() + { + return $this->offsetX2; + } + + /** + * Set OffsetX2. + * + * @param int $offsetX2 + * + * @return $this + */ + public function setOffsetX2($offsetX2) + { + $this->offsetX2 = $offsetX2; + + return $this; + } + + /** + * Get OffsetY2. + * + * @return int + */ + public function getOffsetY2() + { + return $this->offsetY2; + } + + /** + * Set OffsetY2. + * + * @param int $offsetY2 + * + * @return $this + */ + public function setOffsetY2($offsetY2) + { + $this->offsetY2 = $offsetY2; + + return $this; + } + /** * Set OffsetY. * @@ -497,6 +593,9 @@ class BaseDrawing implements IComparable $this->coordinates . $this->offsetX . $this->offsetY . + $this->coordinates2 . + $this->offsetX2 . + $this->offsetY2 . $this->width . $this->height . $this->rotation . diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php index fa77e2d7..6868212a 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -153,24 +153,49 @@ class Drawing extends WriterPart public function writeDrawing(XMLWriter $objWriter, BaseDrawing $drawing, $relationId = -1, $hlinkClickId = null): void { if ($relationId >= 0) { - // xdr:oneCellAnchor - $objWriter->startElement('xdr:oneCellAnchor'); - // Image location - $aCoordinates = Coordinate::indexesFromString($drawing->getCoordinates()); + $isTwoCellAnchor = $drawing->getCoordinates2() !== null; + if ($isTwoCellAnchor) { + // xdr:twoCellAnchor + $objWriter->startElement('xdr:twoCellAnchor'); + // Image location + $aCoordinates = Coordinate::indexesFromString($drawing->getCoordinates()); + $aCoordinates2 = Coordinate::indexesFromString($drawing->getCoordinates2()); - // xdr:from - $objWriter->startElement('xdr:from'); - $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1); - $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetX())); - $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1); - $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetY())); - $objWriter->endElement(); + // xdr:from + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1); + $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetX())); + $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1); + $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetY())); + $objWriter->endElement(); - // xdr:ext - $objWriter->startElement('xdr:ext'); - $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getWidth())); - $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getHeight())); - $objWriter->endElement(); + // xdr:to + $objWriter->startElement('xdr:to'); + $objWriter->writeElement('xdr:col', $aCoordinates2[0] - 1); + $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetX2())); + $objWriter->writeElement('xdr:row', $aCoordinates2[1] - 1); + $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetY2())); + $objWriter->endElement(); + } else { + // xdr:oneCellAnchor + $objWriter->startElement('xdr:oneCellAnchor'); + // Image location + $aCoordinates = Coordinate::indexesFromString($drawing->getCoordinates()); + + // xdr:from + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1); + $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetX())); + $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1); + $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetY())); + $objWriter->endElement(); + + // xdr:ext + $objWriter->startElement('xdr:ext'); + $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getWidth())); + $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getHeight())); + $objWriter->endElement(); + } // xdr:pic $objWriter->startElement('xdr:pic'); @@ -223,6 +248,12 @@ class Drawing extends WriterPart // a:xfrm $objWriter->startElement('a:xfrm'); $objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($drawing->getRotation())); + if ($isTwoCellAnchor) { + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getWidth())); + $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getHeight())); + $objWriter->endElement(); + } $objWriter->endElement(); // a:prstGeom diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php index 8c39b7ef..f9df2b1c 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -430,4 +430,56 @@ class DrawingsTest extends AbstractFunctional self::assertNotNull($reloadedSpreadsheet); } + + /** + * Test save and load XLSX file with drawing image that coordinate is two cell anchor. + */ + public function testTwoCellAnchorDrawing(): void + { + $reader = new Xlsx(); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // Add gif image that coordinates is two cell anchor. + $drawing = new Drawing(); + $drawing->setName('Green Square'); + $drawing->setPath('tests/data/Writer/XLSX/green_square.gif'); + self::assertEquals($drawing->getWidth(), 150); + self::assertEquals($drawing->getHeight(), 150); + $drawing->setCoordinates('A1'); + $drawing->setOffsetX(30); + $drawing->setOffsetY(10); + $drawing->setCoordinates2('E8'); + $drawing->setOffsetX2(-50); + $drawing->setOffsetY2(-20); + $drawing->setWorksheet($sheet); + + // Write file + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $tempFileName = File::sysGetTempDir() . '/drawings_image_that_two_cell_anchor.xlsx'; + $writer->save($tempFileName); + + // Read new file + $reloadedSpreadsheet = $reader->load($tempFileName); + $sheet = $reloadedSpreadsheet->getActiveSheet(); + + // Check image coordinates. + $drawingCollection = $sheet->getDrawingCollection(); + $drawing = $drawingCollection[0]; + self::assertNotNull($drawing); + + self::assertEquals($drawing->getWidth(), 150); + self::assertEquals($drawing->getHeight(), 150); + self::assertEquals($drawing->getCoordinates(), 'A1'); + self::assertEquals($drawing->getOffsetX(), 30); + self::assertEquals($drawing->getOffsetY(), 10); + self::assertEquals($drawing->getCoordinates2(), 'E8'); + self::assertEquals($drawing->getOffsetX2(), -50); + self::assertEquals($drawing->getOffsetY2(), -20); + self::assertEquals($drawing->getWorksheet(), $sheet); + + unlink($tempFileName); + + self::assertNotNull($reloadedSpreadsheet); + } }