From 49749ec02bd7ace6c43dc21998c3619319378fc5 Mon Sep 17 00:00:00 2001 From: Burkov Sergey Date: Tue, 23 Nov 2021 09:59:33 +0300 Subject: [PATCH 1/5] XLSX Image background in comments --- CHANGELOG.md | 2 +- src/PhpSpreadsheet/Comment.php | 50 +++++++++ src/PhpSpreadsheet/Reader/Xlsx.php | 43 +++++++ src/PhpSpreadsheet/Worksheet/Drawing.php | 27 +++++ src/PhpSpreadsheet/Writer/Xlsx.php | 59 ++++++++++ src/PhpSpreadsheet/Writer/Xlsx/Comments.php | 6 + .../Writer/Xlsx/ContentTypes.php | 16 +++ src/PhpSpreadsheet/Writer/Xlsx/Rels.php | 37 ++++++ .../Writer/Xlsx/DrawingsTest.php | 106 ++++++++++++++++++ tests/data/Writer/XLSX/blue_square.png | Bin 0 -> 2360 bytes tests/data/Writer/XLSX/brown_square_256.bmp | Bin 0 -> 6118 bytes .../data/Writer/XLSX/drawing_in_comment.xlsx | Bin 0 -> 18403 bytes tests/data/Writer/XLSX/green_square.gif | Bin 0 -> 719 bytes .../data/Writer/XLSX/orange_square_24_bit.bmp | Bin 0 -> 14894 bytes tests/data/Writer/XLSX/red_square.jpeg | Bin 0 -> 5300 bytes tests/data/Writer/XLSX/yellow_square_16.bmp | Bin 0 -> 2638 bytes 16 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 tests/data/Writer/XLSX/blue_square.png create mode 100644 tests/data/Writer/XLSX/brown_square_256.bmp create mode 100644 tests/data/Writer/XLSX/drawing_in_comment.xlsx create mode 100644 tests/data/Writer/XLSX/green_square.gif create mode 100644 tests/data/Writer/XLSX/orange_square_24_bit.bmp create mode 100644 tests/data/Writer/XLSX/red_square.jpeg create mode 100644 tests/data/Writer/XLSX/yellow_square_16.bmp diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc363be4d..e3eabf9788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Added -- Nothing +- 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/src/PhpSpreadsheet/Comment.php b/src/PhpSpreadsheet/Comment.php index 7aaf975970..3f7eddf2a9 100644 --- a/src/PhpSpreadsheet/Comment.php +++ b/src/PhpSpreadsheet/Comment.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Color; +use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; class Comment implements IComparable { @@ -72,6 +73,13 @@ class Comment implements IComparable */ private $alignment; + /** + * Background image in comment. + * + * @var Drawing + */ + private $backgroundImage; + /** * Create a new Comment. */ @@ -82,6 +90,7 @@ public function __construct() $this->text = new RichText(); $this->fillColor = new Color('FFFFFFE1'); $this->alignment = Alignment::HORIZONTAL_GENERAL; + $this->backgroundImage = new Drawing(); } /** @@ -299,4 +308,45 @@ public function __toString(): string { return $this->text->getPlainText(); } + + /** + * Check is background image exists. + * + * @return bool + */ + public function hasBackgroundImage(): bool + { + return file_exists($this->backgroundImage->getPath()); + } + + /** + * Returns background image. + * + * @return Drawing + */ + public function getBackgroundImage(): Drawing + { + return $this->backgroundImage; + } + + /** + * Sets background image. + * + * @param Drawing $objDrawing + */ + public function setBackgroundImage(Drawing $objDrawing): void + { + $this->backgroundImage = $objDrawing; + } + + /** + * Sets size of comment as size of background image. + */ + public function setSizeAsBackgroundImage(): void + { + if ($this->hasBackgroundImage()) { + $this->setWidth((string) $this->backgroundImage->getWidth()); + $this->setHeight((string) $this->backgroundImage->getHeight()); + } + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 59403c6424..70fb31126d 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -985,6 +985,19 @@ public function load(string $filename, int $flags = 0): Spreadsheet 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); @@ -994,6 +1007,8 @@ public function load(string $filename, int $flags = 0): Spreadsheet $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)) { @@ -1012,10 +1027,38 @@ public function load(string $filename, int $flags = 0): Spreadsheet } } + $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, + false + ); + $comment->setBackgroundImage($objDrawing); + } // Parse style $styleArray = explode(';', str_replace(' ', '', $style)); diff --git a/src/PhpSpreadsheet/Worksheet/Drawing.php b/src/PhpSpreadsheet/Worksheet/Drawing.php index 704e70158a..88b647176e 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -6,6 +6,13 @@ 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. * @@ -68,6 +75,26 @@ public function getExtension() return $exploded[count($exploded) - 1]; } + /** + * Get full filepath to store drawing in zip archive. + * + * @return string + */ + public function getMediaFilename() + { + $imageData = getimagesize($this->getPath()); + + if ($imageData === false) { + throw new PhpSpreadsheetException('Unable to get image data of ' . $this->getPath()); + } elseif (!array_key_exists($imageData[2], self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + $newImageType = self::IMAGE_TYPES_CONVERTION_MAP[$imageData[2]]; + + return sprintf('image%d%s', $this->getImageIndex(), image_type_to_extension($newImageType)); + } + /** * Get Path. * diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 8bcdc531b4..0a9435b59a 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -439,11 +439,22 @@ public function save($filename, int $flags = 0): void // 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 @@ -667,4 +678,52 @@ private function addZipFiles(array $zipContent): void $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 565f62d25e..3ecc48d7ab 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php @@ -180,6 +180,12 @@ private function writeVMLComment(XMLWriter $objWriter, $cellReference, Comment $ // 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 e8636aa950..9229459dda 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -158,6 +158,22 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal } } } + + if (count($spreadsheet->getSheet($i)->getComments()) > 0) { + foreach ($spreadsheet->getSheet($i)->getComments() as $comment) { + if (!$comment->hasBackgroundImage()) { + continue; + } + + $bgImage = $comment->getBackgroundImage(); + + if (!isset($aMediaContentTypes[strtolower($bgImage->getExtension())])) { + $aMediaContentTypes[strtolower($bgImage->getExtension())] = $this->getImageMimeType($bgImage->getPath()); + + $this->writeDefaultContentType($objWriter, strtolower($bgImage->getExtension()), $aMediaContentTypes[strtolower($bgImage->getExtension())]); + } + } + } } // unparsed defaults diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php index 56e054e98a..f804cf15cb 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -396,6 +396,43 @@ public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\ 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 012cdbcd03..eff4e321de 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -5,6 +5,8 @@ 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 +58,108 @@ public function testSaveLoadWithDrawingWithSamePath(): void // 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); + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->save($tempFileName); + + $reloadedSpreadsheet = $reader->load($tempFileName); + unlink($tempFileName); + + // Fake assert. The only thing we need is to ensure the file is loaded without exception + 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'); + $comment = $sheet->getComment('A1'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + + // 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'); + $comment = $sheet->getComment('A2'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + + // 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'); + $comment = $sheet->getComment('A3'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + + // 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'); + $comment = $sheet->getComment('A4'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + + // 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'); + $comment = $sheet->getComment('A5'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + + // 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'); + $comment = $sheet->getComment('A6'); + $comment->setBackgroundImage($drawing); + $comment->setSizeAsBackgroundImage(); + + // Write file + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $tempFileName = File::sysGetTempDir() . '/drawings_in_comments.xlsx'; + $writer->save($tempFileName); + + // Read new file + $reloadedSpreadsheet = $reader->load($tempFileName); + unlink($tempFileName); + + // Fake assert. The only thing we need is to ensure the file is loaded without exception + 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 0000000000000000000000000000000000000000..4528c17f028cb3bb3039ee0423b00baeb5342577 GIT binary patch literal 2360 zcma)-c{J2*8^?bmiNT;k_E98;Ny(CJ1~V8-p&3h-o_dfhnbBa-*vb%Nl$x=G(vXZT z^;lv?vXury31MVORF5sGY@)NdC2CT;W-|{t7DuUTJq3CEtqD|+FnSz zv6CLHpf>CmGNU7z8X|gJC{7`eQKOxe(#kUlJ#ikzUCY0yK(%48+L2nXZiVOEIh&4r zep`RS%p5JXcUa&PtH8;9T4%SKuOY|&iFTdHu==VCbN%l#L6MzVf7N{UhVvN*um$o% z<|ltTEOj?Wr`vv`1Y~{D&7b4Ph;!<~BRY+I`$MR?olU+0#4g(>J#m*vreiYUh&w47 z+7~qA+2`u1C3s(uR{juvx-EZ1TU7RHK+!cDO&6d)K=hNk9muZS-*mJ+s2|*qN;@V% zw4Rn1 zX9~oBPE!z9SVDa3+#LtA8SI7^jJW#ZJ?GJgv@mZ=ho*~=>-(u8QeuqwiT3K#5p0qa zk7_Ce+>(6q;D(ss(CJcP7byUKUoR>{Zt~)@v*{VHyG430qEOv_Aox)?d|ffDt2ExceXpn2lKL%o7dlJ&sI&Kqe!u&vKLedo{c}Sq zS7lBImtG0-Zv0r=I3F`xT>=YKU`g;q`bk!f2VIzVp5CLle)&0>n4CA1ASbz}6*(#) zhzyX+)=5aekucgEe9g(ACJ{QT^X~intN}Z8v@eA8uAce)ZR`h{ zkNQHnNpwa#A(ALKd|-ao=+syv^Kskl`P0uB;|f{hj$A8}nZ>*svM*TpRkcY!qdF5B zY}DN_RKJ>d%ISjKa5^HnMu>i=%zzPC$<3Z0Tyak`qJ-zMecRrISQN`;Npat#;0(JC z9rk}IZMEhzl6VL|8E9W_WVVkhV{QsD@xSaD3;Q@o^QqCylrg?Nc4|2VwGUiX+ivXm zV#dgd_A*d~WgbJUmWq0Q>~yHX_b`n8QtIcH@?$A!JczjAZDiyp*Z}0<}Q%=!{+Txp9~1Lq<74)z|HtshvC1#B0{BR@6Ck zv-Ms*WX!VXvJ-83FA_Zs5{Yo=AlGc10wMB}mAsYfzvjY-soJ4-!#DkOCpA^!x1w6^ z(|<`P>%&w9&5F5Z9@0wTT>F=BIO481X5mGcHTFSV(~h}%%M9ud2kh+B z=1gVLKfC{Oqw~;7V@kN56fn6)l%E3Tn6UDsq>Z1fvwcN=^BR6D3cBMrN2_ro+{Dw% z?#sib<~4=tuwJ;iAH~^?_KtlqV{Yx~Dlq5)Kh8sWLy0~QBBzYy182rIQgc1v%dEA4 z;R00E7DU+8 zO^~N(%IDW}Orq)R$MdshGSB&aQUMwD%qfSc1=n&gV zGo3>TIL9fL@xb5gijE=F<&Sos!=+&%8^>7_y{W~YN(;<(1!_2l7sn~wT(;f|j$m}J&aB6>+_#lPpS|}`SS<%b7 z(ph|_w@4L3Anf%Lg~V#RDtYCFhvTIMpGim>8fu_rHJ9Qrg=p%o=bNO+1;Tg8;vm;S z-=qVCdrZadhxyE(WwU+i0 zu+I7~zn}MVH3}uTa&ZG}jkvQ$B^BD2@;^xKc5mB_629(?7V&dW>0{kEw;#Pukl4CA zCEKZfvrN#MwiA2TGHhc!Zn4o*4{?0rVA2$FvAq^SN$7?5K6v}>dv%E6iTGwcd~wYN z-B@=S6J1e^&WIli8J}z@?u==khUhL9*;C%zf^vUeRwf~sp)Uop}S8}s$!lOa6G5*`Jk*DW#H#Y&QXjpZ(R`SqxehZzmw?P40zs_Aw$<1v=1aIqc z2>>8%zY0W$K@_&dT||r{de^k5>`ryPvt{D!?Ny3lP!?Fy@EpHTyal8P5 z@BaT)xGNwmdl=+(^imq6g0qCiSwp}eWAlKQUmAl0+FfPp zlB7>_-pm~7-O>H+k1xO4^YQ5yvyY_Tzj& znJvc#3oB7LW^Py@_(mX6IA+_h;NXuPNEFVu?_G?JjXhb=s=eB<#<6pqC* zEI2j-iNd)*n=eMkMj%l*_b1(p(XkOo6wdv*`H~37Mj%l*_g2Wo=-3D(3g_M?wHO^6 zfkfdvZ=pDg(XkOo6pj_wU$)@b2qYfQb8oG+7#$mdMB&`q&KIL&BakSZdrR)c=-3D( z3g_Ogu^1g2fkfflTR9h_V=gZ#S9tk`jQ;)aT z<8=Z*wM%Mm=B{_m9EBS=RBol$#bb$m9EBS=RBol z$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$ zm9EBS=RBol$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$m9EBS z=RBol$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$m9EBS=RBol z$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$m9EBS=RBol$#bb$mF_FPPT=P{f&T## ChZuYS literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d654f1cdc1b84273b48537c819c323eaca027676 GIT binary patch literal 18403 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 literal 0 HcmV?d00001 diff --git a/tests/data/Writer/XLSX/green_square.gif b/tests/data/Writer/XLSX/green_square.gif new file mode 100644 index 0000000000000000000000000000000000000000..2f6ceda616186a2835a903457a903fb0cef108cd GIT binary patch literal 719 zcmV;=0x_CX>@2HM@dak03rDV0SW*I04x9i005Q%mH+?){s_9<{xHf(tGzhu&Ab0#D2`-l zo@lDBZ0o*oEYEap-*~R?eDD9jpm0bm8jr}Na>;BupU|juO08P2*sONT^$7ytuy{-^ zo6qR9dVLn)0t144POsbV_`H74@81UjZv+K|g@%WSiHeJijgF6r1c5gJgOHb)nVOlB zf}5Y9p_rYNmZGPqr=&KOsjjb_ldPt&wYG<=HLbV2x4AUCy~3`)Gr_~ip~W)C$C@{i?e5R;Eb{Zo^(*)I!}==x`t|yk zBA`HD)qq_)=!_r=gA8FgJOMExv4qAJS`;?^(L}|KsxUIs2ohnmlKw<;BZ*R?N^vY( zUOXw&WT~11U!IGJQ&h(iAA4T14+j;0OkUnL+>Y@EnGy^uYby}J+4}S*1HqpE`I3ug5kxJ zFHiO@dq(Tu!$1EpefRkC72(g1oj!p@``YctA2|Mb1mJZ74me(cKphBIf(k-7poIKU zn4g96Vfdbg>~UD0hw3p{l!JANs1%8LnfMfneW}P*9Dc+o;k*MF0c#i0=vKsF7WXAZTtPRwN2>YnG;-KpVLjgd~o?L z+UK;->6iZ{x(5x~d-nG1?b*w>0KOqzlue$Y6M(iyB_u%EMcL#TIss^VR6+ujU6f6p zp%Z|%M zfVM{^BtY3k+2k2I0cd+vLIRXslue$Y6M(iyB_u%EMcL#TIss^VR6+ujU6f6pp%Z|% zMfVM{^ zBtY3k+2k2I0cd+vLIRXslue$Y6M(iyB_u%EMcL#TIss^VR6+ujU6f6pp%Z|%MDd0c7gx8z!f;(!6yI! literal 0 HcmV?d00001 diff --git a/tests/data/Writer/XLSX/red_square.jpeg b/tests/data/Writer/XLSX/red_square.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..35d336e69fdb8c583f1310845604795b19c230ba GIT binary patch literal 5300 zcmeHLd010d7C-N0g(R9lfv z`8}$NdIZ9$lYJ%wL69&UUqICfj!cS=SOVbVGZMG}zy@=I1q7nm*xu^K?<2M&7`kx= zVkJ5dV2_5NLM+BU7F$FTQ>U{P@jh(lv9bE~-aga)WUA3h#b~7~S}IgZH>J`|;zZU_|c9|D%uHQU|_)K@l6E+QKEH;O0V8}y-A|YU<7$lRyVlkOGyAJ1pDPkE}DLvT6 z{%Vf(3b86NJ)1ksGyj4lprTps7!YBR8-_}2AXl(lKY0Ik_%+2Yi~PT6_9yf5 zwoF^IYFT(@;o=>E+AE>+&m74u_Zm~yn7V(n<*ArmbF^I!ET~&L@U0?6k)8`I2OYTz;>OsLfnuLcu@Ag1xYa^$txp3Wj%S~N@mlNE6*?Y-< zQF-rpLZ2?WasQKhS=6;zS$khqJMn0+TzELuepl{3=Zsmg@#nUDFrI(M=4VY#iv7%T zL%W;}z3r#i(=Xc!Daf5hK~|+;(TxlWh87=cyO!|WyzZ-4+|FDv1@VV+UR`&y%lSh4 z99`JKziAA|SrlwvP|{iduv4J9d;e)k%lB~!mMhD!NW`Uj2%<-F2U7?)bNH;|8~#7QOoD=P+&GgtSMwQZPyCm!$bqdzq7vz~BAC z@T^i9eiamrDNpQjJ`b-5%t(LSV79sC_RM`n8&m8)n=#%lCk6i3jj9IfF`4J%<@JGI zz>LX0zLSsxA%Dq=435$=6#!Auu`vPOo-+J9F5^4_9t=UsB?miouvX*Y=jYo)3SRHq z@6r4Q;Pxom*X#4YJ0b`P)dnM(kfGMQ!I~JPf5c+MZ^y-IXq<^y92~9=L3|3aLktFp zxQZSR(&Ha!9Hzr0LJ-FU26$m^G@X=%^}|8^aBz4`6zVvkjyxnP8vT=V^*DsaYiS%6 zxe{a337sK7G?KqOMvQP@Z0h!z5H-t#x?&2{#EXcdH3 z5fF?P6Ja?-fm&w|^d6uNN?&cYOB_|YA%gFTZ1@31wIcK8>;&jaqo`l9DXJ?6`Rjdv z(&#?>FnBXS@#Vg;pgjO*lK@PsHR>3(ZXKEulS`1AKl>CwFb}}^ zCV=4recf=5o`=Jg0D)L5-|GN-k*eE-q3?yikvIKdpto+ntJ7US9cN~cBz>VU@nUgl z_Q^8jaM)bFA)n7PwX{{1)jdL01EX!$OkGMc(-qPLRkv^~1V%HWvvs z!J@j!n~)*IgWTbVySY&mzNU4t@rXqU*M9sf&tmAUw$xG^RZT~Ft(l4IgrnbEZtx8) zY8_Ura?q@qc5fVHmMv@Wjtp70!e{MyhRexEodu1!G|hI=C6m*CQ6#fMESr%;<*dM! zxfl__BAcg9t(`L8^OofOO}2AR#+tdEUyya}?$dABUti}JR+UZPR#^3s#D7m6y0jP3 zSqhQs_Xs`xa640LIYF3AD=K|r=pmftM0Qh2B@wD|Yzh`ZIzZ(m$# z95Z(HRSlFWCoZ_!(GEfoo9q+|cOgD2eKBQF)m zC`jhKn&ST07tZ*|59!N9kSx}KY}7)LOqq>Kr|0WerZ-DiRlLe#aB_)evqV{fr~vhQryXWKHffvy7CGCq_U1kH`l4NXdEz;5wkNb|RzxHYN9fmzS4 Kju%L&>VE=VJgOl8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e5f8ac6cf6fe24ef0da30cf9dabb99fa2db27c29 GIT binary patch literal 2638 zcmZ?r_2XgygEAng0mN=l%*en37Qev>rmzqV3=BZT(9i&(fFzI!#0?Aw4jceV{0Ae3 z|NlWOAO=E^5QrEB^b3LAIKT!ZC*V-BdsGc1kVeA?GXzG{#%S8WOcfB3(X=s|HU?YT MK&tLWnY0N30BhZ*NdN!< literal 0 HcmV?d00001 From d90d319912b63627b42189311a280f586d259585 Mon Sep 17 00:00:00 2001 From: Burkov Sergey Date: Sat, 4 Dec 2021 10:47:42 +0300 Subject: [PATCH 2/5] XLSX-Image-Background-In-Comments (#1547) --- docs/references/features-cross-reference.md | 22 ++++++++++++++++-- .../images/08-cell-comment-with-image.png | Bin 0 -> 28527 bytes docs/topics/recipes.md | 19 ++++++++++++++- src/PhpSpreadsheet/Comment.php | 15 ++++++------ 4 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 docs/topics/images/08-cell-comment-with-image.png diff --git a/docs/references/features-cross-reference.md b/docs/references/features-cross-reference.md index c836a99a52..399be82ed3 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 0000000000000000000000000000000000000000..a58c39dac5a2cc3d2bae23b51910cc568b9ce707 GIT binary patch literal 28527 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 ->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/src/PhpSpreadsheet/Comment.php b/src/PhpSpreadsheet/Comment.php index 3f7eddf2a9..c4bb3da735 100644 --- a/src/PhpSpreadsheet/Comment.php +++ b/src/PhpSpreadsheet/Comment.php @@ -282,6 +282,7 @@ public function getHashCode(): string ($this->visible ? 1 : 0) . $this->fillColor->getHashCode() . $this->alignment . + ($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') . __CLASS__ ); } @@ -311,8 +312,6 @@ public function __toString(): string /** * Check is background image exists. - * - * @return bool */ public function hasBackgroundImage(): bool { @@ -321,8 +320,6 @@ public function hasBackgroundImage(): bool /** * Returns background image. - * - * @return Drawing */ public function getBackgroundImage(): Drawing { @@ -331,22 +328,24 @@ public function getBackgroundImage(): Drawing /** * Sets background image. - * - * @param Drawing $objDrawing */ - public function setBackgroundImage(Drawing $objDrawing): void + public function setBackgroundImage(Drawing $objDrawing): self { $this->backgroundImage = $objDrawing; + + return $this; } /** * Sets size of comment as size of background image. */ - public function setSizeAsBackgroundImage(): void + public function setSizeAsBackgroundImage(): self { if ($this->hasBackgroundImage()) { $this->setWidth((string) $this->backgroundImage->getWidth()); $this->setHeight((string) $this->backgroundImage->getHeight()); } + + return $this; } } From 93782f04d35eb02ed309aa39b41d8b13d82cd110 Mon Sep 17 00:00:00 2001 From: Burkov Sergey Date: Mon, 13 Dec 2021 19:13:20 +0300 Subject: [PATCH 3/5] Test fixes, convertion for comment sizes from px to pt, fix for setting image sizes from zip, set image type --- src/PhpSpreadsheet/Comment.php | 17 ++++-- src/PhpSpreadsheet/Reader/Xlsx.php | 3 +- src/PhpSpreadsheet/Worksheet/BaseDrawing.php | 30 +++++++++++ src/PhpSpreadsheet/Worksheet/Drawing.php | 25 ++++----- .../Writer/Xlsx/DrawingsTest.php | 53 ++++++++++++++++++- 5 files changed, 108 insertions(+), 20 deletions(-) diff --git a/src/PhpSpreadsheet/Comment.php b/src/PhpSpreadsheet/Comment.php index c4bb3da735..62579a99d8 100644 --- a/src/PhpSpreadsheet/Comment.php +++ b/src/PhpSpreadsheet/Comment.php @@ -2,11 +2,13 @@ namespace PhpOffice\PhpSpreadsheet; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Helper\Size; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; +use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing; class Comment implements IComparable { @@ -315,7 +317,13 @@ public function __toString(): string */ public function hasBackgroundImage(): bool { - return file_exists($this->backgroundImage->getPath()); + $path = $this->backgroundImage->getPath(); + + if (empty($path)) { + return false; + } + + return getimagesize($path) !== false; } /** @@ -331,6 +339,9 @@ public function getBackgroundImage(): Drawing */ 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; @@ -342,8 +353,8 @@ public function setBackgroundImage(Drawing $objDrawing): self public function setSizeAsBackgroundImage(): self { if ($this->hasBackgroundImage()) { - $this->setWidth((string) $this->backgroundImage->getWidth()); - $this->setHeight((string) $this->backgroundImage->getHeight()); + $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 a3a26ed3c6..6b2c2fd6e7 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1054,7 +1054,8 @@ public function load(string $filename, int $flags = 0): Spreadsheet $imagePath = str_replace('../', 'xl/', $drowingImages[$fillImageRelId]); $objDrawing->setPath( 'zip://' . File::realpath($filename) . '#' . $imagePath, - false + true, + $zip ); $comment->setBackgroundImage($objDrawing); } diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index b8cfb017de..a4fef93e4b 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 @@ public function __construct() $this->resizeProportional = true; $this->rotation = 0; $this->shadow = new Drawing\Shadow(); + $this->type = IMAGETYPE_UNKNOWN; // Set image index ++self::$imageCounter; @@ -526,4 +534,26 @@ public function getHyperlink() { return $this->hyperlink; } + + /** + * Set Fact Sizes and Type of Image. + */ + protected function setSizesAndType(string $path) + { + if ($this->width == 0 && $this->height == 0 && $this->type == IMAGETYPE_UNKNOWN) { + if ($imageData = getimagesize($path)) { + $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 55d44a716e..feba986ad6 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -80,15 +80,11 @@ public function getExtension() */ public function getMediaFilename() { - $imageData = getimagesize($this->getPath()); - - if ($imageData === false) { - throw new PhpSpreadsheetException('Unable to get image data of ' . $this->getPath()); - } elseif (!array_key_exists($imageData[2], self::IMAGE_TYPES_CONVERTION_MAP)) { + 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.'); } - $newImageType = self::IMAGE_TYPES_CONVERTION_MAP[$imageData[2]]; + $newImageType = self::IMAGE_TYPES_CONVERTION_MAP[$this->type]; return sprintf('image%d%s', $this->getImageIndex(), image_type_to_extension($newImageType)); } @@ -108,10 +104,11 @@ public function getPath() * * @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 @@ -124,18 +121,18 @@ public function setPath($path, $verifyFile = true) 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!"); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php index eff4e321de..266a428316 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -2,6 +2,8 @@ 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; @@ -77,13 +79,19 @@ public function testSaveLoadWithDrawingInComment(): void $reader = new Xlsx(); $spreadsheet = $reader->load($tempFileName); + $sheet = $spreadsheet->getActiveSheet(); + $comment = $sheet->getComment('A1'); + + self::assertTrue($comment->hasBackgroundImage()); + self::assertEquals($comment->getBackgroundImage()->getWidth(), 178); + self::assertEquals($comment->getBackgroundImage()->getHeight(), 140); + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); $writer->save($tempFileName); $reloadedSpreadsheet = $reader->load($tempFileName); unlink($tempFileName); - // Fake assert. The only thing we need is to ensure the file is loaded without exception self::assertNotNull($reloadedSpreadsheet); } @@ -101,54 +109,96 @@ public function testBuildWithDifferentImageFormats(): void $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); // 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'); // 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'); // 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'); // 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'); // 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'); + + // 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'); + try { + $comment->setBackgroundImage($drawing); + } 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'); @@ -159,7 +209,6 @@ public function testBuildWithDifferentImageFormats(): void $reloadedSpreadsheet = $reader->load($tempFileName); unlink($tempFileName); - // Fake assert. The only thing we need is to ensure the file is loaded without exception self::assertNotNull($reloadedSpreadsheet); } } From 1c1f2016f3b68ec2acd5797d5601e1ec57727ac9 Mon Sep 17 00:00:00 2001 From: Burkov Sergey Date: Mon, 13 Dec 2021 23:57:44 +0300 Subject: [PATCH 4/5] Merge remote-tracking branch 'origin/XLSX-Image-Background-In-Comments' into XLSX-Image-Background-In-Comments --- src/PhpSpreadsheet/Worksheet/Drawing.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpSpreadsheet/Worksheet/Drawing.php b/src/PhpSpreadsheet/Worksheet/Drawing.php index 5f19eacd3e..9313aec589 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -196,6 +196,7 @@ public function getImageFileExtentionForSave(bool $includeDot = true): string } $result = image_type_to_extension(self::IMAGE_TYPES_CONVERTION_MAP[$this->type], $includeDot); + return is_string($result) ? $result : ''; } From ca798fd4dc6c7a6a8b5965711150773a8c621079 Mon Sep 17 00:00:00 2001 From: Burkov Sergey Date: Thu, 16 Dec 2021 16:46:23 +0300 Subject: [PATCH 5/5] Tests to check reloaded document. --- src/PhpSpreadsheet/Worksheet/Drawing.php | 4 +- .../Writer/Xlsx/ContentTypes.php | 2 +- .../Writer/Xlsx/DrawingsTest.php | 106 +++++++++++++++++- 3 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/PhpSpreadsheet/Worksheet/Drawing.php b/src/PhpSpreadsheet/Worksheet/Drawing.php index 9313aec589..f62873bbae 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -82,7 +82,7 @@ public function getMediaFilename() throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); } - return sprintf('image%d%s', $this->getImageIndex(), $this->getImageFileExtentionForSave()); + return sprintf('image%d%s', $this->getImageIndex(), $this->getImageFileExtensionForSave()); } /** @@ -189,7 +189,7 @@ public function getImageTypeForSave(): int /** * Get Image file extention for Save. */ - public function getImageFileExtentionForSave(bool $includeDot = true): string + 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.'); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php index fb61fcbf03..f62c14af70 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -166,7 +166,7 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal } $bgImage = $comment->getBackgroundImage(); - $bgImageExtentionKey = strtolower($bgImage->getImageFileExtentionForSave(false)); + $bgImageExtentionKey = strtolower($bgImage->getImageFileExtensionForSave(false)); if (!isset($aMediaContentTypes[$bgImageExtentionKey])) { $aMediaContentTypes[$bgImageExtentionKey] = $bgImage->getImageMimeType(); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php index 5da816086c..8c39b7ef8a 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -150,7 +150,7 @@ public function testDrawingInCommentImageFormatsConversions(): void $reloadedSpreadsheet = $reader->load($tempFileName); $sheet = $reloadedSpreadsheet->getActiveSheet(); - // Add gif image to comment background + // Check first image in comment background $comment = $sheet->getComment('A1'); self::assertEquals($comment->getWidth(), '112.5pt'); self::assertEquals($comment->getHeight(), '112.5pt'); @@ -161,7 +161,7 @@ public function testDrawingInCommentImageFormatsConversions(): void self::assertEquals($comment->getBackgroundImage()->getHeight(), 150); self::assertEquals($comment->getBackgroundImage()->getType(), IMAGETYPE_PNG); - // Add bmp image to comment background + // Check second image in comment background $comment = $sheet->getComment('A2'); self::assertEquals($comment->getWidth(), '52.5pt'); self::assertEquals($comment->getHeight(), '52.5pt'); @@ -312,6 +312,31 @@ public function testBuildWithDifferentImageFormats(): void 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.'); @@ -324,6 +349,83 @@ public function testBuildWithDifferentImageFormats(): void // 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);