Skip to content

Commit a7f687f

Browse files
authored
Xlsx image background in comments #1547 (#2422)
* XLSX Image background in comments * XLSX-Image-Background-In-Comments (#1547) * Test fixes, convertion for comment sizes from px to pt, fix for setting image sizes from zip, set image type * Merge remote-tracking branch 'origin/XLSX-Image-Background-In-Comments' into XLSX-Image-Background-In-Comments * Tests to check reloaded document. Co-authored-by: Burkov Sergey
1 parent ea74c96 commit a7f687f

22 files changed

+737
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
1111

1212
- Xlsx Writer Support for WMF Files [#2339](https://github.com/PHPOffice/PhpSpreadsheet/issues/2339)
1313
- Use standard temporary file for internal use of HTMLPurifier [#2383](https://github.com/PHPOffice/PhpSpreadsheet/issues/2383)
14+
- 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)
1415

1516
### Changed
1617

docs/references/features-cross-reference.md

+20-2
Original file line numberDiff line numberDiff line change
@@ -1255,7 +1255,7 @@
12551255
<td></td>
12561256
</tr>
12571257
<tr>
1258-
<td style="padding-left: 1em;">Rich Text</td>
1258+
<td style="padding-left: 2em;">Rich Text</td>
12591259
<td style="text-align: center; color: red;">✖ <sup>2</sup></td>
12601260
<td style="text-align: center; color: green;">✔</td>
12611261
<td style="text-align: center; color: red;">✖</td>
@@ -1273,7 +1273,7 @@
12731273
<td></td>
12741274
</tr>
12751275
<tr>
1276-
<td style="padding-left: 1em;">Alignment</td>
1276+
<td style="padding-left: 2em;">Alignment</td>
12771277
<td style="text-align: center; color: red;">✖ <sup>3</sup></td>
12781278
<td style="text-align: center; color: red;">✖</td>
12791279
<td style="text-align: center; color: red;">✖</td>
@@ -1290,6 +1290,24 @@
12901290
<td></td>
12911291
<td></td>
12921292
</tr>
1293+
<tr>
1294+
<td style="padding-left: 2em;">Background Image</td>
1295+
<td style="text-align: center; color: red;">✖</td>
1296+
<td style="text-align: center; color: green;">✔</td>
1297+
<td style="text-align: center; color: red;">✖</td>
1298+
<td style="text-align: center; color: red;">✖</td>
1299+
<td style="text-align: center; color: red;">✖</td>
1300+
<td style="text-align: center; color: red;">✖</td>
1301+
<td style="text-align: center; color: red;">✖</td>
1302+
<td style="text-align: center; color: red;">✖</td>
1303+
<td style="text-align: center; color: green;">✔</td>
1304+
<td style="text-align: center; color: red;">✖</td>
1305+
<td style="text-align: center; color: red;">✖</td>
1306+
<td style="text-align: center; color: red;">✖</td>
1307+
<td style="text-align: center; color: red;">✖</td>
1308+
<td>$comment->getBackgroundImage()</td>
1309+
<td>$comment->setBackgroundImage()</td>
1310+
</tr>
12931311
<tr>
12941312
<td><strong>Cell Validation</strong></td>
12951313
<td style="text-align: center; color: green;">✔</td>
Loading

docs/topics/recipes.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -957,9 +957,26 @@ $spreadsheet->getActiveSheet()
957957
->getComment('E11')
958958
->getText()->createTextRun('Total amount on the current invoice, excluding VAT.');
959959
```
960-
961960
![08-cell-comment.png](./images/08-cell-comment.png)
962961

962+
## Add a comment with background image to a cell
963+
964+
To add a comment with background image to a cell, use the following code:
965+
966+
```php
967+
$sheet = $spreadsheet->getActiveSheet();
968+
$sheet->setCellValue('B5', 'Gibli Chromo');
969+
// Add png image to comment background
970+
$drawing = new Drawing();
971+
$drawing->setName('Gibli Chromo');
972+
$drawing->setPath('/tmp/gibli_chromo.png');
973+
$comment = $sheet->getComment('B5');
974+
$comment->setBackgroundImage($drawing);
975+
// Set the size of the comment equal to the size of the image
976+
$comment->setSizeAsBackgroundImage();
977+
```
978+
![08-cell-comment-with-image.png](./images/08-cell-comment-with-image.png)
979+
963980
## Apply autofilter to a range of cells
964981

965982
To apply an autofilter to a range of cells, use the following code:

phpstan-baseline.neon

-5
Original file line numberDiff line numberDiff line change
@@ -6045,11 +6045,6 @@ parameters:
60456045
count: 1
60466046
path: src/PhpSpreadsheet/Worksheet/Column.php
60476047

6048-
-
6049-
message: "#^Cannot use array destructuring on array\\|false\\.$#"
6050-
count: 2
6051-
path: src/PhpSpreadsheet/Worksheet/Drawing.php
6052-
60536048
-
60546049
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\:\\:\\$color \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\|null\\.$#"
60556050
count: 1

src/PhpSpreadsheet/Comment.php

+60
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
namespace PhpOffice\PhpSpreadsheet;
44

5+
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
56
use PhpOffice\PhpSpreadsheet\Helper\Size;
67
use PhpOffice\PhpSpreadsheet\RichText\RichText;
8+
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
79
use PhpOffice\PhpSpreadsheet\Style\Alignment;
810
use PhpOffice\PhpSpreadsheet\Style\Color;
11+
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
912

1013
class Comment implements IComparable
1114
{
@@ -72,6 +75,13 @@ class Comment implements IComparable
7275
*/
7376
private $alignment;
7477

78+
/**
79+
* Background image in comment.
80+
*
81+
* @var Drawing
82+
*/
83+
private $backgroundImage;
84+
7585
/**
7686
* Create a new Comment.
7787
*/
@@ -82,6 +92,7 @@ public function __construct()
8292
$this->text = new RichText();
8393
$this->fillColor = new Color('FFFFFFE1');
8494
$this->alignment = Alignment::HORIZONTAL_GENERAL;
95+
$this->backgroundImage = new Drawing();
8596
}
8697

8798
/**
@@ -273,6 +284,7 @@ public function getHashCode(): string
273284
($this->visible ? 1 : 0) .
274285
$this->fillColor->getHashCode() .
275286
$this->alignment .
287+
($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') .
276288
__CLASS__
277289
);
278290
}
@@ -299,4 +311,52 @@ public function __toString(): string
299311
{
300312
return $this->text->getPlainText();
301313
}
314+
315+
/**
316+
* Check is background image exists.
317+
*/
318+
public function hasBackgroundImage(): bool
319+
{
320+
$path = $this->backgroundImage->getPath();
321+
322+
if (empty($path)) {
323+
return false;
324+
}
325+
326+
return getimagesize($path) !== false;
327+
}
328+
329+
/**
330+
* Returns background image.
331+
*/
332+
public function getBackgroundImage(): Drawing
333+
{
334+
return $this->backgroundImage;
335+
}
336+
337+
/**
338+
* Sets background image.
339+
*/
340+
public function setBackgroundImage(Drawing $objDrawing): self
341+
{
342+
if (!array_key_exists($objDrawing->getType(), Drawing::IMAGE_TYPES_CONVERTION_MAP)) {
343+
throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
344+
}
345+
$this->backgroundImage = $objDrawing;
346+
347+
return $this;
348+
}
349+
350+
/**
351+
* Sets size of comment as size of background image.
352+
*/
353+
public function setSizeAsBackgroundImage(): self
354+
{
355+
if ($this->hasBackgroundImage()) {
356+
$this->setWidth(SharedDrawing::pixelsToPoints($this->backgroundImage->getWidth()) . 'pt');
357+
$this->setHeight(SharedDrawing::pixelsToPoints($this->backgroundImage->getHeight()) . 'pt');
358+
}
359+
360+
return $this;
361+
}
302362
}

src/PhpSpreadsheet/Reader/Xlsx.php

+44
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,19 @@ public function load(string $filename, int $flags = 0): Spreadsheet
984984
continue;
985985
}
986986

987+
// Locate VML drawings image relations
988+
$drowingImages = [];
989+
$VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels';
990+
if ($zip->locateName($VMLDrawingsRelations)) {
991+
$relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS);
992+
foreach ($relsVMLDrawing->Relationship as $elex) {
993+
$ele = self::getAttributes($elex);
994+
if ($ele['Type'] == Namespaces::IMAGE) {
995+
$drowingImages[(string) $ele['Id']] = (string) $ele['Target'];
996+
}
997+
}
998+
}
999+
9871000
$shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape');
9881001
foreach ($shapes as $shape) {
9891002
$shape->registerXPathNamespace('v', Namespaces::URN_VML);
@@ -993,6 +1006,8 @@ public function load(string $filename, int $flags = 0): Spreadsheet
9931006
$fillColor = strtoupper(substr((string) $shape['fillcolor'], 1));
9941007
$column = null;
9951008
$row = null;
1009+
$fillImageRelId = null;
1010+
$fillImageTitle = '';
9961011

9971012
$clientData = $shape->xpath('.//x:ClientData');
9981013
if (is_array($clientData) && !empty($clientData)) {
@@ -1011,10 +1026,39 @@ public function load(string $filename, int $flags = 0): Spreadsheet
10111026
}
10121027
}
10131028

1029+
$fillImageRelNode = $shape->xpath('.//v:fill/@o:relid');
1030+
if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) {
1031+
$fillImageRelNode = $fillImageRelNode[0];
1032+
1033+
if (isset($fillImageRelNode['relid'])) {
1034+
$fillImageRelId = (string) $fillImageRelNode['relid'];
1035+
}
1036+
}
1037+
1038+
$fillImageTitleNode = $shape->xpath('.//v:fill/@o:title');
1039+
if (is_array($fillImageTitleNode) && !empty($fillImageTitleNode)) {
1040+
$fillImageTitleNode = $fillImageTitleNode[0];
1041+
1042+
if (isset($fillImageTitleNode['title'])) {
1043+
$fillImageTitle = (string) $fillImageTitleNode['title'];
1044+
}
1045+
}
1046+
10141047
if (($column !== null) && ($row !== null)) {
10151048
// Set comment properties
10161049
$comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1);
10171050
$comment->getFillColor()->setRGB($fillColor);
1051+
if (isset($drowingImages[$fillImageRelId])) {
1052+
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
1053+
$objDrawing->setName($fillImageTitle);
1054+
$imagePath = str_replace('../', 'xl/', $drowingImages[$fillImageRelId]);
1055+
$objDrawing->setPath(
1056+
'zip://' . File::realpath($filename) . '#' . $imagePath,
1057+
true,
1058+
$zip
1059+
);
1060+
$comment->setBackgroundImage($objDrawing);
1061+
}
10181062

10191063
// Parse style
10201064
$styleArray = explode(';', str_replace(' ', '', $style));

src/PhpSpreadsheet/Worksheet/BaseDrawing.php

+32
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ class BaseDrawing implements IComparable
106106
*/
107107
private $hyperlink;
108108

109+
/**
110+
* Image type.
111+
*
112+
* @var int
113+
*/
114+
protected $type;
115+
109116
/**
110117
* Create a new BaseDrawing.
111118
*/
@@ -123,6 +130,7 @@ public function __construct()
123130
$this->resizeProportional = true;
124131
$this->rotation = 0;
125132
$this->shadow = new Drawing\Shadow();
133+
$this->type = IMAGETYPE_UNKNOWN;
126134

127135
// Set image index
128136
++self::$imageCounter;
@@ -526,4 +534,28 @@ public function getHyperlink()
526534
{
527535
return $this->hyperlink;
528536
}
537+
538+
/**
539+
* Set Fact Sizes and Type of Image.
540+
*/
541+
protected function setSizesAndType(string $path): void
542+
{
543+
if ($this->width == 0 && $this->height == 0 && $this->type == IMAGETYPE_UNKNOWN) {
544+
$imageData = getimagesize($path);
545+
546+
if (is_array($imageData)) {
547+
$this->width = $imageData[0];
548+
$this->height = $imageData[1];
549+
$this->type = $imageData[2];
550+
}
551+
}
552+
}
553+
554+
/**
555+
* Get Image Type.
556+
*/
557+
public function getType(): int
558+
{
559+
return $this->type;
560+
}
529561
}

0 commit comments

Comments
 (0)