Skip to content

Add svg support for template processor #2806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changes/1.x/1.5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 13 additions & 1 deletion docs/usage/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -121,13 +124,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' => 100, 'height' => 100, 'ratio' => false)
)
);
```

## cloneBlock
Expand Down
74 changes: 68 additions & 6 deletions src/PhpWord/TemplateProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'])) {
Expand All @@ -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,
];
Expand All @@ -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
Expand Down Expand Up @@ -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 = '<w:pict><v:shape type="#_x0000_t75" style="width:{WIDTH};height:{HEIGHT}" stroked="f" filled="f"><v:imagedata r:id="{RID}" o:title=""/></v:shape></w:pict>';
// use drawing for svg, see https://www.datypic.com/sc/ooxml/e-w_drawing-1.html
$svgTpl = '<w:drawing>
<wp:inline distT="0" distB="0" distL="0" distR="0">
<wp:extent cx="{WIDTH}" cy="{HEIGHT}"/>
<wp:effectExtent l="0" t="0" r="0" b="0"/>
<wp:docPr id="{ID}" name="{NAME}"/>
<wp:cNvGraphicFramePr>
<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
</wp:cNvGraphicFramePr>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="{ID}" name="{NAME}"/>
<pic:cNvPicPr/>
</pic:nvPicPr>
<pic:blipFill>
<a:blip>
<a:extLst>
<a:ext uri="{96DAC541-7B7A-43D3-8B79-37D633B846F1}">
<asvg:svgBlip xmlns:asvg="http://schemas.microsoft.com/office/drawing/2016/SVG/main" r:embed="{RID}"/>
</a:ext>
</a:extLst>
</a:blip>
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="{WIDTH}" cy="{HEIGHT}"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>';

$i = 0;
foreach ($searchParts as $partFileName => &$partContent) {
Expand All @@ -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'], $svgTpl);
} else {
$xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl);
}

// replace variable
$varNameWithArgsFixed = static::ensureMacroCompleted($varNameWithArgs);
Expand Down
24 changes: 14 additions & 10 deletions tests/PhpWordTests/TemplateProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -906,17 +909,18 @@ 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:size=10cmx7cm:ratio=false}');
$section->addText('${Test3}');
$objWriter = IOFactory::createWriter($phpWord, 'Word2007');
$objWriter->save($testFileName);
self::assertFileExists($testFileName, "Generated file '{$testFileName}' not found!");

$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', 'Test1', 'Test2', 'Test3'], [$imageJpg, $imageGif, $imageSvg, $imagePng]);
$templateProcessor->saveAs($resultFileName);
self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!");

Expand All @@ -928,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.');
}

/**
Expand Down
50 changes: 50 additions & 0 deletions tests/PhpWordTests/_files/images/phpword.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.