diff --git a/CHANGELOG.md b/CHANGELOG.md index 64b4b7ee..678ef69d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Xlsx Writer Support for WMF Files [#2339](https://github.com/PHPOffice/PhpSpreadsheet/issues/2339) - Use standard temporary file for internal use of HTMLPurifier [#2383](https://github.com/PHPOffice/PhpSpreadsheet/issues/2383) +- Ability to add a picture to the background of the comment. Supports four image formats: png, jpeg, gif, bmp. A Comment method setSizeAsBackgroundImage for changing the size of a comment to the size of an background image. [Issue #1547](https://github.com/PHPOffice/PhpSpreadsheet/issues/1547) [PR #2422](https://github.com/PHPOffice/PhpSpreadsheet/pull/2422) ### Changed diff --git a/docs/references/features-cross-reference.md b/docs/references/features-cross-reference.md index c836a99a..399be82e 100644 --- a/docs/references/features-cross-reference.md +++ b/docs/references/features-cross-reference.md @@ -1255,7 +1255,7 @@ - Rich Text + Rich Text ✖ 2 ✔ ✖ @@ -1273,7 +1273,7 @@ - Alignment + Alignment ✖ 3 ✖ ✖ @@ -1290,6 +1290,24 @@ + + Background Image + ✖ + ✔ + ✖ + ✖ + ✖ + ✖ + ✖ + ✖ + ✔ + ✖ + ✖ + ✖ + ✖ + $comment->getBackgroundImage() + $comment->setBackgroundImage() + Cell Validation ✔ diff --git a/docs/topics/images/08-cell-comment-with-image.png b/docs/topics/images/08-cell-comment-with-image.png new file mode 100644 index 00000000..a58c39da Binary files /dev/null and b/docs/topics/images/08-cell-comment-with-image.png differ diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md index c90e607c..298a52d4 100644 --- a/docs/topics/recipes.md +++ b/docs/topics/recipes.md @@ -957,9 +957,26 @@ $spreadsheet->getActiveSheet() ->getComment('E11') ->getText()->createTextRun('Total amount on the current invoice, excluding VAT.'); ``` - ![08-cell-comment.png](./images/08-cell-comment.png) +## Add a comment with background image to a cell + +To add a comment with background image to a cell, use the following code: + +```php +$sheet = $spreadsheet->getActiveSheet(); +$sheet->setCellValue('B5', 'Gibli Chromo'); +// Add png image to comment background +$drawing = new Drawing(); +$drawing->setName('Gibli Chromo'); +$drawing->setPath('/tmp/gibli_chromo.png'); +$comment = $sheet->getComment('B5'); +$comment->setBackgroundImage($drawing); +// Set the size of the comment equal to the size of the image +$comment->setSizeAsBackgroundImage(); +``` +![08-cell-comment-with-image.png](./images/08-cell-comment-with-image.png) + ## Apply autofilter to a range of cells To apply an autofilter to a range of cells, use the following code: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f14b3ed5..23b3d630 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6045,11 +6045,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Worksheet/Column.php - - - message: "#^Cannot use array destructuring on array\\|false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Worksheet/Drawing.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\:\\:\\$color \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\|null\\.$#" count: 1 diff --git a/src/PhpSpreadsheet/Comment.php b/src/PhpSpreadsheet/Comment.php index 7aaf9759..abadc7df 100644 --- a/src/PhpSpreadsheet/Comment.php +++ b/src/PhpSpreadsheet/Comment.php @@ -2,10 +2,13 @@ namespace PhpOffice\PhpSpreadsheet; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Helper\Size; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Color; +use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; class Comment implements IComparable { @@ -72,6 +75,13 @@ class Comment implements IComparable */ private $alignment; + /** + * Background image in comment. + * + * @var Drawing + */ + private $backgroundImage; + /** * Create a new Comment. */ @@ -82,6 +92,7 @@ class Comment implements IComparable $this->text = new RichText(); $this->fillColor = new Color('FFFFFFE1'); $this->alignment = Alignment::HORIZONTAL_GENERAL; + $this->backgroundImage = new Drawing(); } /** @@ -273,6 +284,7 @@ class Comment implements IComparable ($this->visible ? 1 : 0) . $this->fillColor->getHashCode() . $this->alignment . + ($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') . __CLASS__ ); } @@ -299,4 +311,52 @@ class Comment implements IComparable { return $this->text->getPlainText(); } + + /** + * Check is background image exists. + */ + public function hasBackgroundImage(): bool + { + $path = $this->backgroundImage->getPath(); + + if (empty($path)) { + return false; + } + + return getimagesize($path) !== false; + } + + /** + * Returns background image. + */ + public function getBackgroundImage(): Drawing + { + return $this->backgroundImage; + } + + /** + * Sets background image. + */ + public function setBackgroundImage(Drawing $objDrawing): self + { + if (!array_key_exists($objDrawing->getType(), Drawing::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + $this->backgroundImage = $objDrawing; + + return $this; + } + + /** + * Sets size of comment as size of background image. + */ + public function setSizeAsBackgroundImage(): self + { + if ($this->hasBackgroundImage()) { + $this->setWidth(SharedDrawing::pixelsToPoints($this->backgroundImage->getWidth()) . 'pt'); + $this->setHeight(SharedDrawing::pixelsToPoints($this->backgroundImage->getHeight()) . 'pt'); + } + + return $this; + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 4417fe7d..6b2c2fd6 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -984,6 +984,19 @@ class Xlsx extends BaseReader continue; } + // Locate VML drawings image relations + $drowingImages = []; + $VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels'; + if ($zip->locateName($VMLDrawingsRelations)) { + $relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS); + foreach ($relsVMLDrawing->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ($ele['Type'] == Namespaces::IMAGE) { + $drowingImages[(string) $ele['Id']] = (string) $ele['Target']; + } + } + } + $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape'); foreach ($shapes as $shape) { $shape->registerXPathNamespace('v', Namespaces::URN_VML); @@ -993,6 +1006,8 @@ class Xlsx extends BaseReader $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1)); $column = null; $row = null; + $fillImageRelId = null; + $fillImageTitle = ''; $clientData = $shape->xpath('.//x:ClientData'); if (is_array($clientData) && !empty($clientData)) { @@ -1011,10 +1026,39 @@ class Xlsx extends BaseReader } } + $fillImageRelNode = $shape->xpath('.//v:fill/@o:relid'); + if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) { + $fillImageRelNode = $fillImageRelNode[0]; + + if (isset($fillImageRelNode['relid'])) { + $fillImageRelId = (string) $fillImageRelNode['relid']; + } + } + + $fillImageTitleNode = $shape->xpath('.//v:fill/@o:title'); + if (is_array($fillImageTitleNode) && !empty($fillImageTitleNode)) { + $fillImageTitleNode = $fillImageTitleNode[0]; + + if (isset($fillImageTitleNode['title'])) { + $fillImageTitle = (string) $fillImageTitleNode['title']; + } + } + if (($column !== null) && ($row !== null)) { // Set comment properties $comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1); $comment->getFillColor()->setRGB($fillColor); + if (isset($drowingImages[$fillImageRelId])) { + $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); + $objDrawing->setName($fillImageTitle); + $imagePath = str_replace('../', 'xl/', $drowingImages[$fillImageRelId]); + $objDrawing->setPath( + 'zip://' . File::realpath($filename) . '#' . $imagePath, + true, + $zip + ); + $comment->setBackgroundImage($objDrawing); + } // Parse style $styleArray = explode(';', str_replace(' ', '', $style)); diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index b8cfb017..46f06158 100644 --- a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -106,6 +106,13 @@ class BaseDrawing implements IComparable */ private $hyperlink; + /** + * Image type. + * + * @var int + */ + protected $type; + /** * Create a new BaseDrawing. */ @@ -123,6 +130,7 @@ class BaseDrawing implements IComparable $this->resizeProportional = true; $this->rotation = 0; $this->shadow = new Drawing\Shadow(); + $this->type = IMAGETYPE_UNKNOWN; // Set image index ++self::$imageCounter; @@ -526,4 +534,28 @@ class BaseDrawing implements IComparable { return $this->hyperlink; } + + /** + * Set Fact Sizes and Type of Image. + */ + protected function setSizesAndType(string $path): void + { + if ($this->width == 0 && $this->height == 0 && $this->type == IMAGETYPE_UNKNOWN) { + $imageData = getimagesize($path); + + if (is_array($imageData)) { + $this->width = $imageData[0]; + $this->height = $imageData[1]; + $this->type = $imageData[2]; + } + } + } + + /** + * Get Image Type. + */ + public function getType(): int + { + return $this->type; + } } diff --git a/src/PhpSpreadsheet/Worksheet/Drawing.php b/src/PhpSpreadsheet/Worksheet/Drawing.php index ba395db2..f62873bb 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -3,9 +3,17 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use ZipArchive; class Drawing extends BaseDrawing { + const IMAGE_TYPES_CONVERTION_MAP = [ + IMAGETYPE_GIF => IMAGETYPE_PNG, + IMAGETYPE_JPEG => IMAGETYPE_JPEG, + IMAGETYPE_PNG => IMAGETYPE_PNG, + IMAGETYPE_BMP => IMAGETYPE_PNG, + ]; + /** * Path. * @@ -63,6 +71,20 @@ class Drawing extends BaseDrawing return $exploded[count($exploded) - 1]; } + /** + * Get full filepath to store drawing in zip archive. + * + * @return string + */ + public function getMediaFilename() + { + if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + return sprintf('image%d%s', $this->getImageIndex(), $this->getImageFileExtensionForSave()); + } + /** * Get Path. * @@ -78,10 +100,11 @@ class Drawing extends BaseDrawing * * @param string $path File path * @param bool $verifyFile Verify file + * @param ZipArchive $zip Zip archive instance * * @return $this */ - public function setPath($path, $verifyFile = true) + public function setPath($path, $verifyFile = true, $zip = null) { if ($verifyFile) { // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 @@ -94,18 +117,18 @@ class Drawing extends BaseDrawing if ($filePath) { file_put_contents($filePath, $imageContents); if (file_exists($filePath)) { - if ($this->width == 0 && $this->height == 0) { - // Get width/height - [$this->width, $this->height] = getimagesize($filePath); - } + $this->setSizesAndType($filePath); unlink($filePath); } } } elseif (file_exists($path)) { $this->path = $path; - if ($this->width == 0 && $this->height == 0) { - // Get width/height - [$this->width, $this->height] = getimagesize($path); + $this->setSizesAndType($path); + } elseif ($zip instanceof ZipArchive) { + $zipPath = explode('#', $path)[1]; + if ($zip->locateName($zipPath) !== false) { + $this->path = $path; + $this->setSizesAndType($path); } } else { throw new PhpSpreadsheetException("File $path not found!"); @@ -150,4 +173,42 @@ class Drawing extends BaseDrawing __CLASS__ ); } + + /** + * Get Image Type for Save. + */ + public function getImageTypeForSave(): int + { + if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + return self::IMAGE_TYPES_CONVERTION_MAP[$this->type]; + } + + /** + * Get Image file extention for Save. + */ + public function getImageFileExtensionForSave(bool $includeDot = true): string + { + if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + $result = image_type_to_extension(self::IMAGE_TYPES_CONVERTION_MAP[$this->type], $includeDot); + + return is_string($result) ? $result : ''; + } + + /** + * Get Image mime type. + */ + public function getImageMimeType(): string + { + if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + return image_type_to_mime_type(self::IMAGE_TYPES_CONVERTION_MAP[$this->type]); + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index e7bb4fea..4f506070 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -440,11 +440,22 @@ class Xlsx extends BaseWriter // Add comment relationship parts if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { + // VML Comments relationships + $zipContent['xl/drawings/_rels/vmlDrawing' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeVMLDrawingRelationships($this->spreadSheet->getSheet($i)); + // VML Comments $zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i)); // Comments $zipContent['xl/comments' . ($i + 1) . '.xml'] = $this->getWriterPartComments()->writeComments($this->spreadSheet->getSheet($i)); + + // Media + foreach ($this->spreadSheet->getSheet($i)->getComments() as $comment) { + if ($comment->hasBackgroundImage()) { + $image = $comment->getBackgroundImage(); + $zipContent['xl/media/' . $image->getMediaFilename()] = $this->processDrawing($image); + } + } } // Add unparsed relationship parts @@ -668,4 +679,52 @@ class Xlsx extends BaseWriter $this->addZipFile($path, $content); } } + + /** + * @return mixed + */ + private function processDrawing(WorksheetDrawing $drawing) + { + $data = null; + $filename = $drawing->getPath(); + $imageData = getimagesize($filename); + + if (is_array($imageData)) { + switch ($imageData[2]) { + case 1: // GIF, not supported by BIFF8, we convert to PNG + $image = imagecreatefromgif($filename); + if ($image !== false) { + ob_start(); + imagepng($image); + $data = ob_get_contents(); + ob_end_clean(); + } + + break; + + case 2: // JPEG + $data = file_get_contents($filename); + + break; + + case 3: // PNG + $data = file_get_contents($filename); + + break; + + case 6: // Windows DIB (BMP), we convert to PNG + $image = imagecreatefrombmp($filename); + if ($image !== false) { + ob_start(); + imagepng($image); + $data = ob_get_contents(); + ob_end_clean(); + } + + break; + } + } + + return $data; + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php index 62e48f8c..ea0f1faa 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php @@ -178,6 +178,12 @@ class Comments extends WriterPart // v:fill $objWriter->startElement('v:fill'); $objWriter->writeAttribute('color2', '#' . $comment->getFillColor()->getRGB()); + if ($comment->hasBackgroundImage()) { + $bgImage = $comment->getBackgroundImage(); + $objWriter->writeAttribute('o:relid', 'rId' . $bgImage->getImageIndex()); + $objWriter->writeAttribute('o:title', $bgImage->getName()); + $objWriter->writeAttribute('type', 'frame'); + } $objWriter->endElement(); // v:shadow diff --git a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php index bf0eb081..f62c14af 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -158,6 +158,23 @@ class ContentTypes extends WriterPart } } } + + if (count($spreadsheet->getSheet($i)->getComments()) > 0) { + foreach ($spreadsheet->getSheet($i)->getComments() as $comment) { + if (!$comment->hasBackgroundImage()) { + continue; + } + + $bgImage = $comment->getBackgroundImage(); + $bgImageExtentionKey = strtolower($bgImage->getImageFileExtensionForSave(false)); + + if (!isset($aMediaContentTypes[$bgImageExtentionKey])) { + $aMediaContentTypes[$bgImageExtentionKey] = $bgImage->getImageMimeType(); + + $this->writeDefaultContentType($objWriter, $bgImageExtentionKey, $aMediaContentTypes[$bgImageExtentionKey]); + } + } + } } // unparsed defaults diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php index 95cc82ee..5aa87876 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -396,6 +396,43 @@ class Rels extends WriterPart return $objWriter->getData(); } + public function writeVMLDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet): string + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Loop through images and write relationships + foreach ($worksheet->getComments() as $comment) { + if (!$comment->hasBackgroundImage()) { + continue; + } + + $bgImage = $comment->getBackgroundImage(); + $this->writeRelationship( + $objWriter, + $bgImage->getImageIndex(), + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + '../media/' . $bgImage->getMediaFilename() + ); + } + + $objWriter->endElement(); + + return $objWriter->getData(); + } + /** * Write Override content type. * diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php index 012cdbcd..8c39b7ef 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -2,9 +2,13 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Comment; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Shared\File; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; class DrawingsTest extends AbstractFunctional @@ -56,4 +60,374 @@ class DrawingsTest extends AbstractFunctional // Fake assert. The only thing we need is to ensure the file is loaded without exception self::assertNotNull($reloadedSpreadsheet); } + + /** + * Test save and load XLSX file with drawing in comment. + */ + public function testSaveLoadWithDrawingInComment(): void + { + // Read spreadsheet from file + $originalFileName = 'tests/data/Writer/XLSX/drawing_in_comment.xlsx'; + + $originalFile = file_get_contents($originalFileName); + + $tempFileName = File::sysGetTempDir() . '/drawing_in_comment.xlsx'; + + file_put_contents($tempFileName, $originalFile); + + // Load native xlsx file with drawing in comment background + $reader = new Xlsx(); + $spreadsheet = $reader->load($tempFileName); + + $sheet = $spreadsheet->getActiveSheet(); + $comment = $sheet->getComment('A1'); + + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 178); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 140); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_JPEG); + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->save($tempFileName); + + $reloadedSpreadsheet = $reader->load($tempFileName); + unlink($tempFileName); + + self::assertNotNull($reloadedSpreadsheet); + } + + /** + * Test save and load XLSX file with drawing in comment, image in BMP/GIF format saved as PNG. + */ + public function testDrawingInCommentImageFormatsConversions(): void + { + $reader = new Xlsx(); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // Add gif image to comment background + $sheet->setCellValue('A1', '.gif'); + $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); + $comment = $sheet->getComment('A1'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + self::assertEquals($comment->getWidth(), '112.5pt'); + self::assertEquals($comment->getHeight(), '112.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_GIF); + + // Add bmp image to comment background + $sheet->setCellValue('A2', '.bmp 16 colors'); + $drawing = new Drawing(); + $drawing->setName('Yellow Square'); + $drawing->setPath('tests/data/Writer/XLSX/yellow_square_16.bmp'); + self::assertEquals($drawing->getWidth(), 70); + self::assertEquals($drawing->getHeight(), 70); + $comment = $sheet->getComment('A2'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + self::assertEquals($comment->getWidth(), '52.5pt'); + self::assertEquals($comment->getHeight(), '52.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_BMP); + + // Write file + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $tempFileName = File::sysGetTempDir() . '/drawings_in_comments_conversions.xlsx'; + $writer->save($tempFileName); + + // Read new file + $reloadedSpreadsheet = $reader->load($tempFileName); + $sheet = $reloadedSpreadsheet->getActiveSheet(); + + // Check first image in comment background + $comment = $sheet->getComment('A1'); + self::assertEquals($comment->getWidth(), '112.5pt'); + self::assertEquals($comment->getHeight(), '112.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 150); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 150); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); + + // Check second image in comment background + $comment = $sheet->getComment('A2'); + self::assertEquals($comment->getWidth(), '52.5pt'); + self::assertEquals($comment->getHeight(), '52.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 70); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 70); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); + + unlink($tempFileName); + self::assertNotNull($reloadedSpreadsheet); + } + + /** + * Test build and save XLSX with drawings in comments with comment size correction. + */ + public function testBuildWithDifferentImageFormats(): void + { + $reader = new Xlsx(); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // Add png image to comment background + $sheet->setCellValue('A1', '.png'); + $drawing = new Drawing(); + $drawing->setName('Blue Square'); + $drawing->setPath('tests/data/Writer/XLSX/blue_square.png'); + self::assertEquals($drawing->getWidth(), 100); + self::assertEquals($drawing->getHeight(), 100); + $comment = $sheet->getComment('A1'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + self::assertEquals($comment->getWidth(), '75pt'); + self::assertEquals($comment->getHeight(), '75pt'); + + $comment = $sheet->getComment('A1'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); + + // Add gif image to comment background + $sheet->setCellValue('A2', '.gif'); + $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); + $comment = $sheet->getComment('A2'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + self::assertEquals($comment->getWidth(), '112.5pt'); + self::assertEquals($comment->getHeight(), '112.5pt'); + + $comment = $sheet->getComment('A2'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_GIF); + + // Add jpeg image to comment background + $sheet->setCellValue('A3', '.jpeg'); + $drawing = new Drawing(); + $drawing->setName('Red Square'); + $drawing->setPath('tests/data/Writer/XLSX/red_square.jpeg'); + self::assertEquals($drawing->getWidth(), 50); + self::assertEquals($drawing->getHeight(), 50); + $comment = $sheet->getComment('A3'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + self::assertEquals($comment->getWidth(), '37.5pt'); + self::assertEquals($comment->getHeight(), '37.5pt'); + + $comment = $sheet->getComment('A3'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_JPEG); + + // Add bmp image to comment background + $sheet->setCellValue('A4', '.bmp 16 colors'); + $drawing = new Drawing(); + $drawing->setName('Yellow Square'); + $drawing->setPath('tests/data/Writer/XLSX/yellow_square_16.bmp'); + self::assertEquals($drawing->getWidth(), 70); + self::assertEquals($drawing->getHeight(), 70); + $comment = $sheet->getComment('A4'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + self::assertEquals($comment->getWidth(), '52.5pt'); + self::assertEquals($comment->getHeight(), '52.5pt'); + + $comment = $sheet->getComment('A4'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_BMP); + + // Add bmp image to comment background + $sheet->setCellValue('A5', '.bmp 256 colors'); + $drawing = new Drawing(); + $drawing->setName('Brown Square'); + $drawing->setPath('tests/data/Writer/XLSX/brown_square_256.bmp'); + self::assertEquals($drawing->getWidth(), 70); + self::assertEquals($drawing->getHeight(), 70); + $comment = $sheet->getComment('A5'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + self::assertEquals($comment->getWidth(), '52.5pt'); + self::assertEquals($comment->getHeight(), '52.5pt'); + + $comment = $sheet->getComment('A5'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_BMP); + + // Add bmp image to comment background + $sheet->setCellValue('A6', '.bmp 24 bit'); + $drawing = new Drawing(); + $drawing->setName('Orange Square'); + $drawing->setPath('tests/data/Writer/XLSX/orange_square_24_bit.bmp'); + self::assertEquals($drawing->getWidth(), 70); + self::assertEquals($drawing->getHeight(), 70); + $comment = $sheet->getComment('A6'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + self::assertEquals($comment->getWidth(), '52.5pt'); + self::assertEquals($comment->getHeight(), '52.5pt'); + + $comment = $sheet->getComment('A6'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_BMP); + + // Add unsupported tiff image to comment background + $sheet->setCellValue('A7', '.tiff'); + $drawing = new Drawing(); + $drawing->setName('Purple Square'); + $drawing->setPath('tests/data/Writer/XLSX/purple_square.tiff'); + $comment = $sheet->getComment('A7'); + self::assertTrue($comment instanceof Comment); + self::assertFalse($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_UNKNOWN); + + try { + $comment->setBackgroundImage($drawing); + self::fail('Should throw exception when attempting to add tiff'); + } catch (PhpSpreadsheetException $e) { + self::assertTrue($e instanceof PhpSpreadsheetException); + self::assertEquals($e->getMessage(), 'Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + try { + $drawing->getImageTypeForSave(); + self::fail('Should throw exception when attempting to get image type for tiff'); + } catch (PhpSpreadsheetException $e) { + self::assertTrue($e instanceof PhpSpreadsheetException); + self::assertEquals($e->getMessage(), 'Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + try { + $drawing->getImageFileExtensionForSave(); + self::fail('Should throw exception when attempting to get image file extention for tiff'); + } catch (PhpSpreadsheetException $e) { + self::assertTrue($e instanceof PhpSpreadsheetException); + self::assertEquals($e->getMessage(), 'Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + try { + $drawing->getImageMimeType(); + self::fail('Should throw exception when attempting to get image mime type for tiff'); + } catch (PhpSpreadsheetException $e) { + self::assertTrue($e instanceof PhpSpreadsheetException); + self::assertEquals($e->getMessage(), 'Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + // Write file + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $tempFileName = File::sysGetTempDir() . '/drawings_in_comments.xlsx'; + $writer->save($tempFileName); + + // Read new file + $reloadedSpreadsheet = $reader->load($tempFileName); + $sheet = $reloadedSpreadsheet->getActiveSheet(); + + // Check first image in comment background + $comment = $sheet->getComment('A1'); + self::assertEquals($comment->getWidth(), '75pt'); + self::assertEquals($comment->getHeight(), '75pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 100); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 100); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); + + // Check second image in comment background + $comment = $sheet->getComment('A2'); + self::assertEquals($comment->getWidth(), '112.5pt'); + self::assertEquals($comment->getHeight(), '112.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 150); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 150); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); + + // Check third image in comment background + $comment = $sheet->getComment('A3'); + self::assertEquals($comment->getWidth(), '37.5pt'); + self::assertEquals($comment->getHeight(), '37.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 50); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 50); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_JPEG); + + // Check fourth image in comment background + $comment = $sheet->getComment('A4'); + self::assertEquals($comment->getWidth(), '52.5pt'); + self::assertEquals($comment->getHeight(), '52.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 70); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 70); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); + + // Check fifth image in comment background + $comment = $sheet->getComment('A5'); + self::assertEquals($comment->getWidth(), '52.5pt'); + self::assertEquals($comment->getHeight(), '52.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 70); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 70); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); + + // Check sixth image in comment background + $comment = $sheet->getComment('A6'); + self::assertEquals($comment->getWidth(), '52.5pt'); + self::assertEquals($comment->getHeight(), '52.5pt'); + self::assertTrue($comment instanceof Comment); + self::assertTrue($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 70); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 70); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); + + // Check seventh image in comment background + $comment = $sheet->getComment('A7'); + self::assertTrue($comment instanceof Comment); + self::assertFalse($comment->hasBackgroundImage()); + self::assertTrue($comment->getBackgroundImage() instanceof Drawing); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 0); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 0); + self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_UNKNOWN); + + unlink($tempFileName); + + self::assertNotNull($reloadedSpreadsheet); + } } diff --git a/tests/data/Writer/XLSX/blue_square.png b/tests/data/Writer/XLSX/blue_square.png new file mode 100644 index 00000000..4528c17f Binary files /dev/null and b/tests/data/Writer/XLSX/blue_square.png differ diff --git a/tests/data/Writer/XLSX/brown_square_256.bmp b/tests/data/Writer/XLSX/brown_square_256.bmp new file mode 100644 index 00000000..ed585a8a Binary files /dev/null and b/tests/data/Writer/XLSX/brown_square_256.bmp differ diff --git a/tests/data/Writer/XLSX/drawing_in_comment.xlsx b/tests/data/Writer/XLSX/drawing_in_comment.xlsx new file mode 100644 index 00000000..d654f1cd Binary files /dev/null and b/tests/data/Writer/XLSX/drawing_in_comment.xlsx differ diff --git a/tests/data/Writer/XLSX/green_square.gif b/tests/data/Writer/XLSX/green_square.gif new file mode 100644 index 00000000..2f6ceda6 Binary files /dev/null and b/tests/data/Writer/XLSX/green_square.gif differ diff --git a/tests/data/Writer/XLSX/orange_square_24_bit.bmp b/tests/data/Writer/XLSX/orange_square_24_bit.bmp new file mode 100644 index 00000000..079f37ff Binary files /dev/null and b/tests/data/Writer/XLSX/orange_square_24_bit.bmp differ diff --git a/tests/data/Writer/XLSX/purple_square.tiff b/tests/data/Writer/XLSX/purple_square.tiff new file mode 100644 index 00000000..741b474d Binary files /dev/null and b/tests/data/Writer/XLSX/purple_square.tiff differ diff --git a/tests/data/Writer/XLSX/red_square.jpeg b/tests/data/Writer/XLSX/red_square.jpeg new file mode 100644 index 00000000..35d336e6 Binary files /dev/null and b/tests/data/Writer/XLSX/red_square.jpeg differ diff --git a/tests/data/Writer/XLSX/yellow_square_16.bmp b/tests/data/Writer/XLSX/yellow_square_16.bmp new file mode 100644 index 00000000..e5f8ac6c Binary files /dev/null and b/tests/data/Writer/XLSX/yellow_square_16.bmp differ