From e34604215902f9d234bc527cf1f6c4f5c1f9f963 Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Tue, 29 Jul 2025 17:13:18 +0200 Subject: [PATCH 1/9] Add svg support for template processor --- docs/changes/1.x/1.5.0.md | 2 + docs/usage/template.md | 2 +- src/PhpWord/TemplateProcessor.php | 74 ++++++++++++++++++-- tests/PhpWordTests/TemplateProcessorTest.php | 18 ++--- tests/PhpWordTests/_files/images/phpword.svg | 50 +++++++++++++ 5 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 tests/PhpWordTests/_files/images/phpword.svg diff --git a/docs/changes/1.x/1.5.0.md b/docs/changes/1.x/1.5.0.md index b96865bada..dbeab0e347 100644 --- a/docs/changes/1.x/1.5.0.md +++ b/docs/changes/1.x/1.5.0.md @@ -4,6 +4,8 @@ ## Enhancements +- Template Processor: Add support for svg images by [@geo-fret](https://github.com/geo-fret) fixing part of [#2795](https://github.com/PHPOffice/PHPWord/issues/2795) + ### Bug fixes - Set writeAttribute return type by [@radarhere](https://github.com/radarhere) fixing [#2204](https://github.com/PHPOffice/PHPWord/issues/2204) in [#2776](https://github.com/PHPOffice/PHPWord/pull/2776) diff --git a/docs/usage/template.md b/docs/usage/template.md index a0c885e75e..240e14574c 100644 --- a/docs/usage/template.md +++ b/docs/usage/template.md @@ -121,7 +121,7 @@ $templateProcessor = new TemplateProcessor('Template.docx'); $templateProcessor->setValue('Name', 'John Doe'); $templateProcessor->setValue(array('City', 'Street'), array('Detroit', '12th Street')); -$templateProcessor->setImageValue('CompanyLogo', 'path/to/company/logo.png'); +$templateProcessor->setImageValue('CompanyLogo', 'path/to/company/logo.svg'); $templateProcessor->setImageValue('UserLogo', array('path' => 'path/to/logo.png', 'width' => 100, 'height' => 100, 'ratio' => false)); $templateProcessor->setImageValue('FeatureImage', function () { // Closure will only be executed if the replacement tag is found in the template diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 073393ffc4..4bf36fe59a 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -563,11 +563,26 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) $width = $this->chooseImageDimension($width, $varInlineArgs['width'] ?? null, 115); $height = $this->chooseImageDimension($height, $varInlineArgs['height'] ?? null, 70); - $imageData = @getimagesize($imgPath); - if (!is_array($imageData)) { - throw new Exception(sprintf('Invalid image: %s', $imgPath)); + $mime = mime_content_type($imgPath); + if ($mime === 'image/svg+xml') { + $content = file_get_contents($imgPath); + if (!$content) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); + } + $svgXml = simplexml_load_string($content); + if (!$svgXml) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); + } + $svgAttributes = $svgXml->attributes(); + $actualWidth = $svgAttributes->width; + $actualHeight = $svgAttributes->height; + } else { + $imageData = @getimagesize($imgPath); + if (!is_array($imageData)) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); + } + [$actualWidth, $actualHeight] = $imageData; } - [$actualWidth, $actualHeight, $imageType] = $imageData; // fix aspect ratio (by default) if (null === $ratio && isset($varInlineArgs['ratio'])) { @@ -579,7 +594,7 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) $imageAttrs = [ 'src' => $imgPath, - 'mime' => image_type_to_mime_type($imageType), + 'mime' => $mime, 'width' => $width, 'height' => $height, ]; @@ -599,6 +614,7 @@ private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeTy 'image/png' => 'png', 'image/bmp' => 'bmp', 'image/gif' => 'gif', + 'image/svg+xml' => 'svg', ]; // get image embed name @@ -674,6 +690,48 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // define templates // result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425) $imgTpl = ''; + // use drawing for svg, see https://www.datypic.com/sc/ooxml/e-w_drawing-1.html + $svgTpl = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '; $i = 0; foreach ($searchParts as $partFileName => &$partContent) { @@ -695,7 +753,11 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // replace preparations $this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']); - $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); + if ($preparedImageAttrs['mime'] === 'image/svg+xml') { + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height'], $imgIndex, 'graphic'], $imgTpl); + } else { + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); + } // replace variable $varNameWithArgsFixed = static::ensureMacroCompleted($varNameWithArgs); diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 8ae4dfa59a..6688b14dad 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -859,14 +859,16 @@ public function testSetCheckboxWithCustomMacro(): void public function testSetImageValue(): void { $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); - $imagePath = __DIR__ . '/_files/images/earth.jpg'; + $imageJpg = __DIR__ . '/_files/images/earth.jpg'; + $imageGif = __DIR__ . '/_files/images/mario.gif'; + $imageSvg = __DIR__ . '/_files/images/phpword.svg'; $variablesReplace = [ - 'headerValue' => function () use ($imagePath) { - return $imagePath; + 'headerValue' => function () use ($imageJpg) { + return $imageJpg; }, - 'documentContent' => ['path' => $imagePath, 'width' => 500, 'height' => 500], - 'footerValue' => ['path' => $imagePath, 'width' => 100, 'height' => 50, 'ratio' => false], + 'documentContent' => ['path' => $imageJpg, 'width' => 500, 'height' => 500], + 'footerValue' => ['path' => $imageJpg, 'width' => 100, 'height' => 50, 'ratio' => false], ]; $templateProcessor->setImageValue(array_keys($variablesReplace), $variablesReplace); @@ -914,9 +916,9 @@ public function testSetImageValue(): void $resultFileName = 'images-test-result.docx'; $templateProcessor = new TemplateProcessor($testFileName); unlink($testFileName); - $templateProcessor->setImageValue('Test', $imagePath); - $templateProcessor->setImageValue('Test1', $imagePath); - $templateProcessor->setImageValue('Test2', $imagePath); + $templateProcessor->setImageValue('Test', $imageJpg); + $templateProcessor->setImageValue('Test1', $imageGif); + $templateProcessor->setImageValue('Test2', $imageSvg); $templateProcessor->saveAs($resultFileName); self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!"); diff --git a/tests/PhpWordTests/_files/images/phpword.svg b/tests/PhpWordTests/_files/images/phpword.svg new file mode 100644 index 0000000000..2fbeeb4af0 --- /dev/null +++ b/tests/PhpWordTests/_files/images/phpword.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0d8826d35af1c483f7cb59500036b92dcb30cc7a Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Tue, 29 Jul 2025 17:19:50 +0200 Subject: [PATCH 2/9] Add pull request to changelog --- docs/changes/1.x/1.5.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes/1.x/1.5.0.md b/docs/changes/1.x/1.5.0.md index dbeab0e347..25683768db 100644 --- a/docs/changes/1.x/1.5.0.md +++ b/docs/changes/1.x/1.5.0.md @@ -4,7 +4,7 @@ ## Enhancements -- Template Processor: Add support for svg images by [@geo-fret](https://github.com/geo-fret) fixing part of [#2795](https://github.com/PHPOffice/PHPWord/issues/2795) +- Template Processor: Add support for svg images by [@geo-fret](https://github.com/geo-fret) fixing part of [#2795](https://github.com/PHPOffice/PHPWord/issues/2795) in [#2806](https://github.com/PHPOffice/PHPWord/pull/2806) ### Bug fixes From 181d539529ab9e2660005e941e45e2d2bb1aa87a Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Wed, 30 Jul 2025 07:27:44 +0200 Subject: [PATCH 3/9] Fix wrong template --- src/PhpWord/TemplateProcessor.php | 2 +- tests/PhpWordTests/TemplateProcessorTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 4bf36fe59a..ff64720578 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -754,7 +754,7 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // replace preparations $this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']); if ($preparedImageAttrs['mime'] === 'image/svg+xml') { - $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height'], $imgIndex, 'graphic'], $imgTpl); + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height'], $imgIndex, 'graphic'], $svgTpl); } else { $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); } diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 6688b14dad..9d02d0bd83 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -908,7 +908,9 @@ public function testSetImageValue(): void $testFileName = 'images-test-sample.docx'; $phpWord = new PhpWord(); $section = $phpWord->addSection(); - $section->addText('${Test:width=100:ratio=true}'); + $section->addText('${Test0:width=100:ratio=true}'); + $section->addText('${Test1:height=50:ratio=true}'); + $section->addText('${Test2:width=10cm:height=7cm:ratio=false}'); $objWriter = IOFactory::createWriter($phpWord, 'Word2007'); $objWriter->save($testFileName); self::assertFileExists($testFileName, "Generated file '{$testFileName}' not found!"); @@ -916,9 +918,7 @@ public function testSetImageValue(): void $resultFileName = 'images-test-result.docx'; $templateProcessor = new TemplateProcessor($testFileName); unlink($testFileName); - $templateProcessor->setImageValue('Test', $imageJpg); - $templateProcessor->setImageValue('Test1', $imageGif); - $templateProcessor->setImageValue('Test2', $imageSvg); + $templateProcessor->setImageValue(['Test0', 'Test1', 'Test2'], [$imageJpg, $imageGif, $imageSvg]); $templateProcessor->saveAs($resultFileName); self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!"); From 1e3fc5305ff81e478d0071664a3fe734909e5523 Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Wed, 30 Jul 2025 08:36:52 +0200 Subject: [PATCH 4/9] Update test to cover more code --- docs/usage/template.md | 12 ++++++++++++ tests/PhpWordTests/TemplateProcessorTest.php | 10 ++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/usage/template.md b/docs/usage/template.md index 240e14574c..54798c1a6a 100644 --- a/docs/usage/template.md +++ b/docs/usage/template.md @@ -106,6 +106,9 @@ Where: - [width] and [height] can be just numbers or numbers with measure, which supported by Word (cm, mm, in, pt, pc, px, %, em, ex) - [ratio] uses only for ``false``, ``-`` or ``f`` to turn off respect aspect ration of image. By default template image size uses as 'container' size. +You can use an array as first argument to replace all search patterns with the same file. If you use an indexed array as second argument, +the first item in the first argument will be replaced by the first item in the second argument. + Example: ``` clean @@ -128,6 +131,15 @@ $templateProcessor->setImageValue('FeatureImage', function () { return array('path' => SlowFeatureImageGenerator::make(), 'width' => 100, 'height' => 100, 'ratio' => false); }); + +// use array to replace multiple values +$templateProcessor->setImageValue( + array('CompanyLogo', 'UserLogo'), + array( + 'path/to/company/logo.svg', + array('path' => 'path/to/logo.png', 'width' => 100, 'height' => 100, 'ratio' => false) + ) +); ``` ## cloneBlock diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 9d02d0bd83..fc37884c16 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -861,6 +861,7 @@ public function testSetImageValue(): void $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); $imageJpg = __DIR__ . '/_files/images/earth.jpg'; $imageGif = __DIR__ . '/_files/images/mario.gif'; + $imagePng = __DIR__ . '/_files/images/firefox.png'; $imageSvg = __DIR__ . '/_files/images/phpword.svg'; $variablesReplace = [ @@ -909,8 +910,9 @@ public function testSetImageValue(): void $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('${Test0:width=100:ratio=true}'); - $section->addText('${Test1:height=50:ratio=true}'); - $section->addText('${Test2:width=10cm:height=7cm:ratio=false}'); + $section->addText('${Test1::50:true}'); + $section->addText('${Test2:size=10cmx7cm:ratio=false}'); + $section->addText('${Test3}'); $objWriter = IOFactory::createWriter($phpWord, 'Word2007'); $objWriter->save($testFileName); self::assertFileExists($testFileName, "Generated file '{$testFileName}' not found!"); @@ -918,7 +920,7 @@ public function testSetImageValue(): void $resultFileName = 'images-test-result.docx'; $templateProcessor = new TemplateProcessor($testFileName); unlink($testFileName); - $templateProcessor->setImageValue(['Test0', 'Test1', 'Test2'], [$imageJpg, $imageGif, $imageSvg]); + $templateProcessor->setImageValue(['Test0', 'Test1', 'Test2', 'Test3'], [$imageJpg, $imageGif, $imageSvg, $imagePng]); $templateProcessor->saveAs($resultFileName); self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!"); @@ -930,7 +932,7 @@ public function testSetImageValue(): void } unlink($resultFileName); - self::assertStringNotContainsString('${Test}', $expectedMainPartXml, 'word/document.xml has no image.'); + self::assertStringNotContainsString('${Test', $expectedMainPartXml, 'word/document.xml has no image.'); } /** From c7e37b835ae6d5064c8bf64a89ade382a0ae46bf Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Wed, 13 Aug 2025 08:55:16 +0200 Subject: [PATCH 5/9] Use EMU for SVG --- docs/usage/template.md | 5 ++- src/PhpWord/TemplateProcessor.php | 47 +++++++++++++++++++- tests/PhpWordTests/TemplateProcessorTest.php | 14 ++++-- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/docs/usage/template.md b/docs/usage/template.md index 54798c1a6a..1e61ee5615 100644 --- a/docs/usage/template.md +++ b/docs/usage/template.md @@ -103,7 +103,8 @@ The search-pattern model for images can be like: - ``${search-image-pattern:width=[width]:height=[height]:ratio=false}`` Where: - - [width] and [height] can be just numbers or numbers with measure, which supported by Word (cm, mm, in, pt, pc, px, %, em, ex) + - [width] and [height] can be just numbers or numbers with measure, which supported by Word (cm, mm, in, pt, pc, px, %, em, ex). + For SVG the relative measures (px, %, em, ex) might have different results than other images. - [ratio] uses only for ``false``, ``-`` or ``f`` to turn off respect aspect ration of image. By default template image size uses as 'container' size. You can use an array as first argument to replace all search patterns with the same file. If you use an indexed array as second argument, @@ -137,7 +138,7 @@ $templateProcessor->setImageValue( array('CompanyLogo', 'UserLogo'), array( 'path/to/company/logo.svg', - array('path' => 'path/to/logo.png', 'width' => 100, 'height' => 100, 'ratio' => false) + array('path' => 'path/to/logo.png', 'width' => '100mm', 'height' => '100mm', 'ratio' => false) ) ); ``` diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index ff64720578..007b01ed69 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -24,6 +24,7 @@ use PhpOffice\PhpWord\Exception\CopyFileException; use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; use PhpOffice\PhpWord\Exception\Exception; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Shared\ZipArchive; @@ -576,6 +577,8 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) $svgAttributes = $svgXml->attributes(); $actualWidth = $svgAttributes->width; $actualHeight = $svgAttributes->height; + $actualWidth = is_numeric($actualWidth) ? $actualWidth . 'px' : $actualWidth; + $actualHeight = is_numeric($actualHeight) ? $actualHeight . 'px' : $actualHeight; } else { $imageData = @getimagesize($imgPath); if (!is_array($imageData)) { @@ -597,6 +600,8 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) 'mime' => $mime, 'width' => $width, 'height' => $height, + 'originalWidth' => $actualWidth, + 'originalHeight' => $actualHeight, ]; return $imageAttrs; @@ -754,7 +759,47 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // replace preparations $this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']); if ($preparedImageAttrs['mime'] === 'image/svg+xml') { - $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height'], $imgIndex, 'graphic'], $svgTpl); + $width = Converter::cssToEmu($preparedImageAttrs['width']); + $height = Converter::cssToEmu($preparedImageAttrs['height']); + if ($width === null) { + if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $width, $matches)) { + $size = $matches[1]; + $unit = $matches[2]; + switch ($unit) { + case 'ex': + $size *= 2; + // fall through + case 'em': + $width = $size * 152400; + break; + case '%': + $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']) * $size; + break; + } + } else { + $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']); + } + } + if ($height === null) { + if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $height, $matches)) { + $size = $matches[1]; + $unit = $matches[2]; + switch ($unit) { + case 'ex': + $size *= 2; + // fall through + case 'em': + $height = $size * 152400; + break; + case '%': + $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']) * $size; + break; + } + } else { + $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']); + } + } + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, $width, $height, $imgIndex, 'graphic'], $svgTpl); } else { $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); } diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index fc37884c16..84a1ece250 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -911,8 +911,14 @@ public function testSetImageValue(): void $section = $phpWord->addSection(); $section->addText('${Test0:width=100:ratio=true}'); $section->addText('${Test1::50:true}'); - $section->addText('${Test2:size=10cmx7cm:ratio=false}'); - $section->addText('${Test3}'); + $section->addText('${Test2}'); + $section->addText('${Test3:size=10cmx7cm:ratio=false}'); + $section->addText('${Test4:size=100mmx70mm:ratio=true}'); + $section->addText('${Test5:4in::true}'); + $section->addText('${Test6:300pt:200pt}'); + $section->addText('${Test7:25pc:}'); + $section->addText('${Test8:50%:50%}'); + $section->addText('${Test9::5ex}'); $objWriter = IOFactory::createWriter($phpWord, 'Word2007'); $objWriter->save($testFileName); self::assertFileExists($testFileName, "Generated file '{$testFileName}' not found!"); @@ -920,7 +926,7 @@ public function testSetImageValue(): void $resultFileName = 'images-test-result.docx'; $templateProcessor = new TemplateProcessor($testFileName); unlink($testFileName); - $templateProcessor->setImageValue(['Test0', 'Test1', 'Test2', 'Test3'], [$imageJpg, $imageGif, $imageSvg, $imagePng]); + $templateProcessor->setImageValue(['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'], [$imageJpg, $imageGif, $imagePng, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg]); $templateProcessor->saveAs($resultFileName); self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!"); @@ -932,7 +938,7 @@ public function testSetImageValue(): void } unlink($resultFileName); - self::assertStringNotContainsString('${Test', $expectedMainPartXml, 'word/document.xml has no image.'); + self::assertStringNotContainsString('${Test', $expectedMainPartXml, 'word/document.xml has not inserted all images.'); } /** From 070aab1d33006bf9daa43cceedcb763b1ce8a40d Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Wed, 13 Aug 2025 09:01:02 +0200 Subject: [PATCH 6/9] Fix CS and convert value to float --- src/PhpWord/TemplateProcessor.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 007b01ed69..e21151f825 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -763,12 +763,12 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM $height = Converter::cssToEmu($preparedImageAttrs['height']); if ($width === null) { if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $width, $matches)) { - $size = $matches[1]; + $size = floatval($matches[1]); $unit = $matches[2]; switch ($unit) { case 'ex': - $size *= 2; - // fall through + $size = $size * 2; + // no break case 'em': $width = $size * 152400; break; @@ -782,12 +782,12 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM } if ($height === null) { if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $height, $matches)) { - $size = $matches[1]; + $size = floatval($matches[1]); $unit = $matches[2]; switch ($unit) { case 'ex': $size *= 2; - // fall through + // no break case 'em': $height = $size * 152400; break; From 242ceb95dff1063b1fe585ca1423220d24383d52 Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Wed, 13 Aug 2025 09:17:28 +0200 Subject: [PATCH 7/9] Fix CS --- src/PhpWord/TemplateProcessor.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index e21151f825..fb70a40afc 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -763,17 +763,20 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM $height = Converter::cssToEmu($preparedImageAttrs['height']); if ($width === null) { if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $width, $matches)) { - $size = floatval($matches[1]); + $size = (float) ($matches[1]); $unit = $matches[2]; switch ($unit) { case 'ex': $size = $size * 2; + // no break case 'em': $width = $size * 152400; + break; case '%': $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']) * $size; + break; } } else { @@ -782,24 +785,27 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM } if ($height === null) { if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $height, $matches)) { - $size = floatval($matches[1]); + $size = (float) ($matches[1]); $unit = $matches[2]; switch ($unit) { case 'ex': $size *= 2; + // no break case 'em': $height = $size * 152400; + break; case '%': $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']) * $size; + break; } } else { $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']); } } - $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, $width, $height, $imgIndex, 'graphic'], $svgTpl); + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, (string) $width, (string) $height, $imgIndex, 'graphic'], $svgTpl); } else { $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); } From 30714f63dc7f0dd439f933a0a04a98e72964568f Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Wed, 13 Aug 2025 12:55:24 +0200 Subject: [PATCH 8/9] Fix Converter::cssToEmu with unknown unit --- src/PhpWord/Shared/Converter.php | 7 ++++++- src/PhpWord/TemplateProcessor.php | 4 ++-- tests/PhpWordTests/TemplateProcessorTest.php | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 17d2e1a05d..8568c3421f 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -451,6 +451,11 @@ public static function cssToCm($value) */ public static function cssToEmu($value) { - return self::pointToEmu(self::cssToPoint($value)); + $point = self::cssToPoint($value); + if ($point === null) { + return null; + } + + return self::pointToEmu($point); } } diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index fb70a40afc..53a8df6f9a 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -762,7 +762,7 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM $width = Converter::cssToEmu($preparedImageAttrs['width']); $height = Converter::cssToEmu($preparedImageAttrs['height']); if ($width === null) { - if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $width, $matches)) { + if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['width'], $matches)) { $size = (float) ($matches[1]); $unit = $matches[2]; switch ($unit) { @@ -784,7 +784,7 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM } } if ($height === null) { - if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $height, $matches)) { + if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['height'], $matches)) { $size = (float) ($matches[1]); $unit = $matches[2]; switch ($unit) { diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 84a1ece250..733de8e0e9 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -926,7 +926,8 @@ public function testSetImageValue(): void $resultFileName = 'images-test-result.docx'; $templateProcessor = new TemplateProcessor($testFileName); unlink($testFileName); - $templateProcessor->setImageValue(['Test0', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'], [$imageJpg, $imageGif, $imagePng, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg]); + $templateProcessor->setImageValue('Test0', $imageJpg); + $templateProcessor->setImageValue(['Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'], [$imageGif, $imagePng, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg]); $templateProcessor->saveAs($resultFileName); self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!"); From 8a29c76fe106b91042d95dcf60e769e5bc26247d Mon Sep 17 00:00:00 2001 From: Thomas Frei Date: Wed, 13 Aug 2025 14:12:58 +0200 Subject: [PATCH 9/9] Change return type to ?float --- src/PhpWord/Shared/Converter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 8568c3421f..2378dfb078 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -447,7 +447,7 @@ public static function cssToCm($value) * * @param string $value * - * @return float + * @return ?float */ public static function cssToEmu($value) {