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.');
```
-

+## 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();
+```
+
+
## 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