diff --git a/CHANGELOG.md b/CHANGELOG.md index f17fad9c99..38539d65d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Some earlier branches remain supported and security fixes are applied to them; i - `Document\Properties::setLanguage()`: the language of a spreadsheet as a BCP 47 tag, written and read as `dc:language` in Xlsx and Ods; the Ods writer also gives it to the default cell style, where LibreOffice keeps a spreadsheet's language, as LibreOffice writes it (the Western, Asian or complex slot by script, and `style:rfc-language-tag` for a tag with more than a language and a country), and the Ods reader reads it from there when there is no `dc:language`. Html writes and reads it as the `lang` of the `html` element. [Issue #5040](https://github.com/PHPOffice/PhpSpreadsheet/issues/5040) [PR #5041](https://github.com/PHPOffice/PhpSpreadsheet/pull/5041) - Xlsx Writer and Reader: `Chart::setDescription()` is the alternative text of a chart, written as `descr` of the chart's frame and read back, and the frame takes the chart's name; LibreOffice exports it as `/Alt` of the chart in a tagged PDF. [Issue #5038](https://github.com/PHPOffice/PhpSpreadsheet/issues/5038) [PR #5039](https://github.com/PHPOffice/PhpSpreadsheet/pull/5039) - `BaseDrawing::setDecorative()` marks an image as decorative. Xlsx writes and reads it as Excel and LibreOffice do (`adec:decorative` in the image's `cNvPr`), Ods as LibreOffice does (`loext:decorative` in the frame's graphic style, which the writer now defines; the reader also takes ODF 1.4's `draw:decorative`); LibreOffice then leaves the image out of a tagged PDF as an artifact. Html writes and reads it as an empty `alt`. [Issue #5036](https://github.com/PHPOffice/PhpSpreadsheet/issues/5036) [PR #5037](https://github.com/PHPOffice/PhpSpreadsheet/pull/5037) +- Xlsx Writer and Reader: the description of a header/footer image is its alternative text, `alt` on the VML shape as Excel writes it. [Issue #5056](https://github.com/PHPOffice/PhpSpreadsheet/issues/5056) [PR #5057](https://github.com/PHPOffice/PhpSpreadsheet/pull/5057) ### Removed diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index b551dd14a3..1f6ab3c4e5 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1429,6 +1429,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet if (isset($imageData['title'])) { $hfImages[$shapeId]->setName((string) $imageData['title']); } + $hfImages[$shapeId]->setDescription((string) $shape['alt']); $hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false, $zip); $hfImages[$shapeId]->setResizeProportional(false); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php index 9c732dd6a3..bebfb97090 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -550,6 +550,10 @@ private function writeVMLHeaderFooterImage(XMLWriter $objWriter, string $referen $objWriter->writeAttribute('o:spid', '_x0000_s' . $id); $objWriter->writeAttribute('type', '#_x0000_t75'); $objWriter->writeAttribute('style', "position:absolute;margin-left:{$marginLeft}px;margin-top:{$marginTop}px;width:{$width}px;height:{$height}px;z-index:1"); + // The alternative text, as Excel writes it; o:title below is the name + if ($image->getDescription() !== '') { + $objWriter->writeAttribute('alt', $image->getDescription()); + } // v:imagedata $objWriter->startElement('v:imagedata'); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/HeaderFooterImageDescriptionTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/HeaderFooterImageDescriptionTest.php new file mode 100644 index 0000000000..39e26055de --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/HeaderFooterImageDescriptionTest.php @@ -0,0 +1,52 @@ +getActiveSheet()->getHeaderFooter(); + $headerFooter->setOddHeader('&L&G&R&G'); + foreach ([HeaderFooter::IMAGE_HEADER_LEFT => 'Company logo', HeaderFooter::IMAGE_HEADER_RIGHT => ''] as $location => $description) { + $image = new HeaderFooterDrawing(); + $image->setName("Image $location"); + $image->setDescription($description); + $image->setPath('samples/images/blue_square.png'); + $image->setHeight(12); + $headerFooter->addImage($image, $location); + } + + return $spreadsheet; + } + + public function testWrittenAsAlt(): void + { + $spreadsheet = self::spreadsheet(); + $vml = (new XlsxWriter($spreadsheet))->getWriterPartDrawing()->writeVMLHeaderFooterImages($spreadsheet->getActiveSheet()); + self::assertMatchesRegularExpression('/]* alt="Company logo">]*o:title="Image LH"/', $vml); + self::assertSame(1, substr_count($vml, ' alt=')); + $spreadsheet->disconnectWorksheets(); + } + + public function testDescriptionSurvivesTheRoundTrip(): void + { + $spreadsheetOld = self::spreadsheet(); + $spreadsheet = $this->writeAndReload($spreadsheetOld, 'Xlsx'); + $spreadsheetOld->disconnectWorksheets(); + $images = $spreadsheet->getActiveSheet()->getHeaderFooter()->getImages(); + self::assertSame('Company logo', $images['LH']->getDescription()); + self::assertSame('Image LH', $images['LH']->getName()); + self::assertSame('', $images['RH']->getDescription()); + $spreadsheet->disconnectWorksheets(); + } +}