Skip to content

Commit 197dd4d

Browse files
committed
Image Transparency/Opacity Including Html Reader Changes
See discussion #4117. This PR implements image transparency, for Xlsx Reader and Writer, Html Reader, and Html/Dompdf/Mpdf Writer. (Mpdf treats 100% opacity as if it were zero, but it's otherwise okay and that would be an unusual choice anyhow.) A new property `opacity` with getter/setter is added to BaseDrawing (and therefore Drawing and MemoryDrawing). Although the Excel UI lets you set the image *transparency*, the value stored in the Xml is actually its *opacity* (expressed as an integer between 0 and 100,000). Likewise Html/Css lets you set opacity (as a float between 0 and 1). PhpSpreadsheet lets you set it as it is stored in Xml - so a value of 40,000 would indicate 40% opacity = 60% transparency. In the course of testing, some problems with Html Reader presented themselves. They are corrected as part of this ticket: - Web page title will no longer be "Untitled Spreadsheet" if a title tag is present in the header section of the html. - If a class attribute is used with the table tag representing a worksheet, showGridlines and printGridlines will be set according to whether gridlines and/or gridlinesp are among the class names. - Reader has been performing urldecode on the src attribute of the img tag. This breaks data url's by converting plus signs to spaces. It will now perform urldecode only for non-data url's. - If height and width are not used as attributes on the img tag, they will be extracted from the style attribute if they are present there.
1 parent f7c183b commit 197dd4d

File tree

11 files changed

+330
-26
lines changed

11 files changed

+330
-26
lines changed

samples/Basic4/53_ImageOpacity.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
require __DIR__ . '/../Header.php';
4+
5+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
6+
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
7+
8+
//var_dump(realpath(__DIR__ . '/../images/blue_square.png'));
9+
//exit();
10+
11+
$path = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'images/blue_square.png';
12+
$spreadsheet = new Spreadsheet();
13+
$spreadsheet->getProperties()->setTitle('53_ImageOpacity');
14+
15+
$helper->log('Add image to spreadsheet 6 times with different opacities');
16+
$sheet = $spreadsheet->getActiveSheet();
17+
$sheet->setTitle('Squares different opacities');
18+
$sheet->setShowGridLines(false);
19+
20+
$drawing = new Drawing();
21+
$drawing->setName('Blue Square opacity not specified');
22+
$drawing->setPath($path);
23+
$drawing->setCoordinates('A1');
24+
$drawing->setCoordinates2('B5');
25+
$drawing->setWorksheet($sheet);
26+
27+
$drawing = new Drawing();
28+
$drawing->setName('Blue Square opacity 80%');
29+
$drawing->setPath($path);
30+
$drawing->setCoordinates('C1');
31+
$drawing->setCoordinates2('D5');
32+
$drawing->setOpacity(80000);
33+
$drawing->setWorksheet($sheet);
34+
35+
$drawing = new Drawing();
36+
$drawing->setName('Blue Square opacity 60%');
37+
$drawing->setPath($path);
38+
$drawing->setCoordinates('E1');
39+
$drawing->setCoordinates2('F5');
40+
$drawing->setOpacity(60000);
41+
$drawing->setWorksheet($sheet);
42+
43+
$drawing = new Drawing();
44+
$drawing->setName('Blue Square opacity 40%');
45+
$drawing->setPath($path);
46+
$drawing->setCoordinates('A8');
47+
$drawing->setCoordinates2('B12');
48+
$drawing->setOpacity(40000);
49+
$drawing->setWorksheet($sheet);
50+
51+
$drawing = new Drawing();
52+
$drawing->setName('Blue Square opacity 20%');
53+
$drawing->setPath($path);
54+
$drawing->setCoordinates('C8');
55+
$drawing->setCoordinates2('D12');
56+
$drawing->setOpacity(20000);
57+
$drawing->setWorksheet($sheet);
58+
59+
$drawing = new Drawing();
60+
$drawing->setName('Blue Square opacity 0%');
61+
$drawing->setPath($path);
62+
$drawing->setCoordinates('E8');
63+
$drawing->setCoordinates2('F12');
64+
$drawing->setOpacity(0);
65+
$drawing->setWorksheet($sheet);
66+
67+
// Save
68+
$helper->write($spreadsheet, __FILE__, ['Xlsx', 'Html', 'Dompdf', 'Mpdf']);
69+
$spreadsheet->disconnectWorksheets();

samples/images/blue_square.png

2.3 KB
Loading

samples/templates/27template.xlsx

582 Bytes
Binary file not shown.

src/PhpSpreadsheet/Helper/Sample.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ public function write(Spreadsheet $spreadsheet, string $filename, array $writers
119119
// Write documents
120120
foreach ($writers as $writerType) {
121121
$path = $this->getFilename($filename, mb_strtolower($writerType));
122+
if (preg_match('/(mpdf|tcpdf)$/', $path)) {
123+
$path .= '.pdf';
124+
}
122125
$writer = IOFactory::createWriter($spreadsheet, $writerType);
123126
$writer->setIncludeCharts($withCharts);
124127
if ($writerCallback !== null) {

src/PhpSpreadsheet/Reader/Html.php

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ private function processDomElementTitle(Worksheet $sheet, int &$row, string &$co
315315

316316
try {
317317
$sheet->setTitle($cellContent, true, true);
318+
$sheet->getParent()?->getProperties()?->setTitle($cellContent);
318319
} catch (SpreadsheetException) {
319320
// leave default title if too long or illegal chars
320321
}
@@ -476,6 +477,11 @@ private function processDomElementImg(Worksheet $sheet, int &$row, string &$colu
476477
private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
477478
{
478479
if ($child->nodeName === 'table') {
480+
if (isset($attributeArray['class'])) {
481+
$classes = explode(' ', $attributeArray['class']);
482+
$sheet->setShowGridlines(in_array('gridlines', $classes, true));
483+
$sheet->setPrintGridlines(in_array('gridlinesp', $classes, true));
484+
}
479485
$this->currentColumn = 'A';
480486
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
481487
$column = $this->setTableStartColumn($column);
@@ -1038,10 +1044,38 @@ private function insertImage(Worksheet $sheet, string $column, int $row, array $
10381044
if (!isset($attributes['src'])) {
10391045
return;
10401046
}
1047+
$styleArray = [];
1048+
if (isset($attributes['style'])) {
1049+
$styles = explode(';', $attributes['style']);
1050+
foreach ($styles as $style) {
1051+
$value = explode(':', $style);
1052+
if (count($value) === 2) {
1053+
$arrayKey = trim($value[0]);
1054+
$arrayValue = trim($value[1]);
1055+
if ($arrayKey === 'width') {
1056+
if (substr($arrayValue, -2) === 'px') {
1057+
$arrayValue = (string) (((float) substr($arrayValue, 0, -2)));
1058+
} else {
1059+
$arrayValue = (new CssDimension($arrayValue))->width();
1060+
}
1061+
} elseif ($arrayKey === 'height') {
1062+
if (substr($arrayValue, -2) === 'px') {
1063+
$arrayValue = substr($arrayValue, 0, -2);
1064+
} else {
1065+
$arrayValue = (new CssDimension($arrayValue))->height();
1066+
}
1067+
}
1068+
$styleArray[$arrayKey] = $arrayValue;
1069+
}
1070+
}
1071+
}
10411072

1042-
$src = urldecode($attributes['src']);
1043-
$width = isset($attributes['width']) ? (float) $attributes['width'] : null;
1044-
$height = isset($attributes['height']) ? (float) $attributes['height'] : null;
1073+
$src = $attributes['src'];
1074+
if (substr($src, 0, 5) !== 'data:') {
1075+
$src = urldecode($src);
1076+
}
1077+
$width = isset($attributes['width']) ? (float) $attributes['width'] : ($styleArray['width'] ?? null);
1078+
$height = isset($attributes['height']) ? (float) $attributes['height'] : ($styleArray['height'] ?? null);
10451079
$name = $attributes['alt'] ?? null;
10461080

10471081
$drawing = new Drawing();
@@ -1057,10 +1091,12 @@ private function insertImage(Worksheet $sheet, string $column, int $row, array $
10571091
}
10581092

10591093
if ($width) {
1060-
$drawing->setWidth((int) $width);
1061-
}
1062-
1063-
if ($height) {
1094+
if ($height) {
1095+
$drawing->setWidthAndHeight((int) $width, (int) $height);
1096+
} else {
1097+
$drawing->setWidth((int) $width);
1098+
}
1099+
} elseif ($height) {
10641100
$drawing->setHeight((int) $height);
10651101
}
10661102

@@ -1071,6 +1107,13 @@ private function insertImage(Worksheet $sheet, string $column, int $row, array $
10711107
$sheet->getRowDimension($row)->setRowHeight(
10721108
$drawing->getHeight() * 0.9
10731109
);
1110+
1111+
if (isset($styleArray['opacity'])) {
1112+
$opacity = $styleArray['opacity'];
1113+
if (is_numeric($opacity)) {
1114+
$drawing->setOpacity((int) ($opacity * 100000));
1115+
}
1116+
}
10741117
}
10751118

10761119
private const BORDER_MAPPINGS = [

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,6 @@ public function listWorksheetInfo(string $filename): array
230230
if ($xmlWorkbook->sheets) {
231231
$dir = dirname($relTarget);
232232

233-
/** @var SimpleXMLElement $eleSheet */
234233
foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
235234
$tmpInfo = [
236235
'worksheetName' => (string) self::getAttributes($eleSheet)['name'],
@@ -738,7 +737,6 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
738737
$charts = $chartDetails = [];
739738

740739
if ($xmlWorkbookNS->sheets) {
741-
/** @var SimpleXMLElement $eleSheet */
742740
foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) {
743741
$eleSheetAttr = self::getAttributes($eleSheet);
744742
++$oldSheetId;
@@ -1420,14 +1418,17 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
14201418
foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
14211419
$oneCellAnchor = self::testSimpleXml($oneCellAnchor);
14221420
if ($oneCellAnchor->pic->blipFill) {
1423-
/** @var SimpleXMLElement $blip */
1421+
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
14241422
$blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip;
1425-
/** @var SimpleXMLElement $xfrm */
1423+
if (isset($blip, $blip->alphaModFix)) {
1424+
$temp = (string) $blip->alphaModFix->attributes()->amt;
1425+
if (is_numeric($temp)) {
1426+
$objDrawing->setOpacity((int) $temp);
1427+
}
1428+
}
14261429
$xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm;
1427-
/** @var SimpleXMLElement $outerShdw */
14281430
$outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw;
14291431

1430-
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
14311432
$objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name'));
14321433
$objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr'));
14331434
$embedImageKey = (string) self::getArrayItem(
@@ -1486,7 +1487,6 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
14861487
$height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy'));
14871488

14881489
$graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
1489-
/** @var SimpleXMLElement $chartRef */
14901490
$chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
14911491
$thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
14921492

@@ -1508,6 +1508,12 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
15081508
if ($twoCellAnchor->pic->blipFill) {
15091509
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
15101510
$blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip;
1511+
if (isset($blip, $blip->alphaModFix)) {
1512+
$temp = (string) $blip->alphaModFix->attributes()->amt;
1513+
if (is_numeric($temp)) {
1514+
$objDrawing->setOpacity((int) $temp);
1515+
}
1516+
}
15111517
if (isset($twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->srcRect)) {
15121518
$objDrawing->setSrcRect($twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->srcRect->attributes());
15131519
}
@@ -1581,7 +1587,6 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
15811587
$toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff);
15821588
$toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff);
15831589
$graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
1584-
/** @var SimpleXMLElement $chartRef */
15851590
$chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
15861591
$thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
15871592

@@ -1601,7 +1606,6 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
16011606
foreach ($xmlDrawingChildren->absoluteAnchor as $absoluteAnchor) {
16021607
if (($this->includeCharts) && ($absoluteAnchor->graphicFrame)) {
16031608
$graphic = $absoluteAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
1604-
/** @var SimpleXMLElement $chartRef */
16051609
$chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
16061610
$thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
16071611
$width = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cx')[0]);
@@ -1882,7 +1886,6 @@ private function parseRichText(?SimpleXMLElement $is): RichText
18821886
$value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t));
18831887
} elseif ($is !== null) {
18841888
if (is_object($is->r)) {
1885-
/** @var SimpleXMLElement $run */
18861889
foreach ($is->r as $run) {
18871890
if (!isset($run->rPr)) {
18881891
$value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t));

src/PhpSpreadsheet/Worksheet/BaseDrawing.php

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ class BaseDrawing implements IComparable
131131
/** @var null|SimpleXMLElement|string[] */
132132
protected $srcRect = [];
133133

134+
/**
135+
* Percentage multiplied by 100,000, e.g. 40% = 40,000.
136+
* Opacity=x is the same as transparency=100000-x.
137+
*/
138+
protected ?int $opacity = null;
139+
134140
/**
135141
* Create a new BaseDrawing.
136142
*/
@@ -344,19 +350,19 @@ public function setHeight(int $height): self
344350
*/
345351
public function setWidthAndHeight(int $width, int $height): self
346352
{
347-
$xratio = $width / ($this->width != 0 ? $this->width : 1);
348-
$yratio = $height / ($this->height != 0 ? $this->height : 1);
349-
if ($this->resizeProportional && !($width == 0 || $height == 0)) {
353+
if ($this->width === 0 || $this->height === 0 || $width === 0 || $height === 0 || !$this->resizeProportional) {
354+
$this->width = $width;
355+
$this->height = $height;
356+
} else {
357+
$xratio = $width / $this->width;
358+
$yratio = $height / $this->height;
350359
if (($xratio * $this->height) < $height) {
351360
$this->height = (int) ceil($xratio * $this->height);
352361
$this->width = $width;
353362
} else {
354363
$this->width = (int) ceil($yratio * $this->width);
355364
$this->height = $height;
356365
}
357-
} else {
358-
$this->width = $width;
359-
$this->height = $height;
360366
}
361367

362368
return $this;
@@ -546,4 +552,16 @@ public function getFlipVertical(): bool
546552
{
547553
return $this->flipVertical;
548554
}
555+
556+
public function setOpacity(?int $opacity): self
557+
{
558+
$this->opacity = $opacity;
559+
560+
return $this;
561+
}
562+
563+
public function getOpacity(): ?int
564+
{
565+
return $this->opacity;
566+
}
549567
}

src/PhpSpreadsheet/Writer/Html.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,14 @@ private function writeImageInCell(string $coordinates): string
619619
// Write images
620620
$drawing = $this->sheetDrawings[$coordinates] ?? null;
621621
if ($drawing !== null) {
622+
$opacity = '';
623+
$opacityValue = $drawing->getOpacity();
624+
if ($opacityValue !== null) {
625+
$opacityValue = $opacityValue / 100000;
626+
if ($opacityValue >= 0.0 && $opacityValue <= 1.0) {
627+
$opacity = "opacity:$opacityValue; ";
628+
}
629+
}
622630
$filedesc = $drawing->getDescription();
623631
$filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image';
624632
if ($drawing instanceof Drawing) {
@@ -649,7 +657,7 @@ private function writeImageInCell(string $coordinates): string
649657
}
650658
}
651659

652-
$html .= '<img style="position: absolute; z-index: 1; left: '
660+
$html .= '<img style="' . $opacity . 'position: absolute; z-index: 1; left: '
653661
. $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: '
654662
. $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="'
655663
. $imageData . '" alt="' . $filedesc . '" />';
@@ -672,7 +680,7 @@ private function writeImageInCell(string $coordinates): string
672680
// Its use here is suspect and is being eliminated.
673681
// width: X sets width of supplied image.
674682
// As a result, images bigger than cell will be contained and images smaller will not get stretched
675-
$html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="width:' . $drawing->getWidth() . 'px;left: '
683+
$html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="' . $opacity . 'width:' . $drawing->getWidth() . 'px;left: '
676684
. $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px;position: absolute; z-index: 1;" />';
677685
}
678686
}

src/PhpSpreadsheet/Writer/Xlsx/Drawing.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,13 @@ public function writeDrawing(XMLWriter $objWriter, BaseDrawing $drawing, int $re
263263
$objWriter->startElement('a:blip');
264264
$objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT);
265265
$objWriter->writeAttribute('r:embed', 'rId' . $relationId);
266-
$objWriter->endElement();
266+
$temp = $drawing->getOpacity();
267+
if (is_int($temp) && $temp >= 0 && $temp <= 100000) {
268+
$objWriter->startElement('a:alphaModFix');
269+
$objWriter->writeAttribute('amt', "$temp");
270+
$objWriter->endElement(); // a:alphaModFix
271+
}
272+
$objWriter->endElement(); // a:blip
267273

268274
$srcRect = $drawing->getSrcRect();
269275
if (!empty($srcRect)) {

0 commit comments

Comments
 (0)