diff --git a/docs/changes/1.x/1.5.0.md b/docs/changes/1.x/1.5.0.md
index b96865bada..25683768db 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) in [#2806](https://github.com/PHPOffice/PHPWord/pull/2806)
+
### 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..1e61ee5615 100644
--- a/docs/usage/template.md
+++ b/docs/usage/template.md
@@ -103,9 +103,13 @@ 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,
+the first item in the first argument will be replaced by the first item in the second argument.
+
Example:
``` clean
@@ -121,13 +125,22 @@ $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
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' => '100mm', 'height' => '100mm', 'ratio' => false)
+ )
+);
```
## cloneBlock
diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php
index 17d2e1a05d..2378dfb078 100644
--- a/src/PhpWord/Shared/Converter.php
+++ b/src/PhpWord/Shared/Converter.php
@@ -447,10 +447,15 @@ public static function cssToCm($value)
*
* @param string $value
*
- * @return float
+ * @return ?float
*/
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 073393ffc4..53a8df6f9a 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;
@@ -563,11 +564,28 @@ 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;
+ $actualWidth = is_numeric($actualWidth) ? $actualWidth . 'px' : $actualWidth;
+ $actualHeight = is_numeric($actualHeight) ? $actualHeight . 'px' : $actualHeight;
+ } 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,9 +597,11 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs)
$imageAttrs = [
'src' => $imgPath,
- 'mime' => image_type_to_mime_type($imageType),
+ 'mime' => $mime,
'width' => $width,
'height' => $height,
+ 'originalWidth' => $actualWidth,
+ 'originalHeight' => $actualHeight,
];
return $imageAttrs;
@@ -599,6 +619,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 +695,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 +758,57 @@ 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') {
+ $width = Converter::cssToEmu($preparedImageAttrs['width']);
+ $height = Converter::cssToEmu($preparedImageAttrs['height']);
+ if ($width === null) {
+ if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['width'], $matches)) {
+ $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 {
+ $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']);
+ }
+ }
+ if ($height === null) {
+ if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['height'], $matches)) {
+ $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, (string) $width, (string) $height, $imgIndex, 'graphic'], $svgTpl);
+ } 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..733de8e0e9 100644
--- a/tests/PhpWordTests/TemplateProcessorTest.php
+++ b/tests/PhpWordTests/TemplateProcessorTest.php
@@ -859,14 +859,17 @@ 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';
+ $imagePng = __DIR__ . '/_files/images/firefox.png';
+ $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);
@@ -906,7 +909,16 @@ 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::50:true}');
+ $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!");
@@ -914,9 +926,8 @@ 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('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!");
@@ -928,7 +939,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.');
}
/**
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 @@
+
+
+