diff --git a/docs/changes/1.x/1.5.0.md b/docs/changes/1.x/1.5.0.md
index b96865bada..30cbae8707 100644
--- a/docs/changes/1.x/1.5.0.md
+++ b/docs/changes/1.x/1.5.0.md
@@ -4,6 +4,8 @@
## Enhancements
+- Word2007 Writer: Support for embedding SVG images by [@prog-klk1](https://github.com/prog-klk1) in [#2790](https://github.com/PHPOffice/PHPWord/pull/2790)
+
### 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/samples/Sample_47_SVG.php b/samples/Sample_47_SVG.php
new file mode 100644
index 0000000000..878e5468ca
--- /dev/null
+++ b/samples/Sample_47_SVG.php
@@ -0,0 +1,41 @@
+addSection();
+$section->addText('SVG image without any styles:');
+$svg = $section->addImage(__DIR__ . '/resources/sample.svg');
+
+printSeparator($section);
+
+$section->addText('SVG image with styles:');
+$svg = $section->addImage(
+ __DIR__ . '/resources/sample.svg',
+ [
+ 'width' => 200,
+ 'height' => 200,
+ 'align' => 'center',
+ 'wrappingStyle' => PhpOffice\PhpWord\Style\Image::WRAPPING_STYLE_BEHIND,
+ ]
+);
+
+function printSeparator(Section $section): void
+{
+ $section->addTextBreak();
+ $lineStyle = ['weight' => 0.2, 'width' => 150, 'height' => 0, 'align' => 'center'];
+ $section->addLine($lineStyle);
+ $section->addTextBreak(2);
+}
+
+// Save file
+echo write($phpWord, basename(__FILE__, '.php'), $writers);
+if (!CLI) {
+ include_once 'Sample_Footer.php';
+}
diff --git a/samples/resources/sample.svg b/samples/resources/sample.svg
new file mode 100644
index 0000000000..8a800c4a2c
--- /dev/null
+++ b/samples/resources/sample.svg
@@ -0,0 +1,96 @@
+
+
\ No newline at end of file
diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php
index c47e5effa5..843f0b078a 100644
--- a/src/PhpWord/Element/Image.php
+++ b/src/PhpWord/Element/Image.php
@@ -18,6 +18,7 @@
namespace PhpOffice\PhpWord\Element;
+use DOMDocument;
use PhpOffice\PhpWord\Exception\CreateTemporaryFileException;
use PhpOffice\PhpWord\Exception\InvalidImageException;
use PhpOffice\PhpWord\Exception\UnsupportedImageTypeException;
@@ -432,6 +433,20 @@ private function checkImage(): void
{
$this->setSourceType();
+ $ext = strtolower(pathinfo($this->source, PATHINFO_EXTENSION));
+ if ($ext === 'svg') {
+ [$actualWidth, $actualHeight] = $this->getSvgDimensions($this->source);
+ $this->imageType = 'image/svg+xml';
+ $this->imageExtension = 'svg';
+ $this->imageFunc = null;
+ $this->imageQuality = null;
+ $this->memoryImage = false;
+ $this->sourceType = self::SOURCE_LOCAL;
+ $this->setProportionalSize($actualWidth, $actualHeight);
+
+ return;
+ }
+
// Check image data
if ($this->sourceType == self::SOURCE_ARCHIVE) {
$imageData = $this->getArchiveImageSize($this->source);
@@ -598,4 +613,38 @@ private function setProportionalSize($actualWidth, $actualHeight): void
}
}
}
+
+ public function getSvgDimensions(string $file): array
+ {
+ $xml = @file_get_contents($file);
+ if ($xml === false) {
+ throw new InvalidImageException("Impossible de lire le fichier SVG: $file");
+ }
+ libxml_use_internal_errors(true);
+ $dom = new DOMDocument();
+ if (!$dom->loadXML($xml)) {
+ throw new InvalidImageException('SVG invalide ou mal formé');
+ }
+ $svg = $dom->documentElement;
+
+ $wAttr = round((float) $svg->getAttribute('width'));
+ $hAttr = round((float) $svg->getAttribute('height'));
+
+ $w = (int) filter_var($wAttr, FILTER_SANITIZE_NUMBER_INT);
+ $h = (int) filter_var($hAttr, FILTER_SANITIZE_NUMBER_INT);
+
+ if ($w <= 0 || $h <= 0) {
+ $vb = $svg->getAttribute('viewBox');
+ if (preg_match('/^\s*[\d.+-]+[\s,]+[\d.+-]+[\s,]+([\d.+-]+)[\s,]+([\d.+-]+)\s*$/', $vb, $m)) {
+ $w = (int) round((float) $m[1]);
+ $h = (int) round((float) $m[2]);
+ }
+ }
+
+ if ($w <= 0 || $h <= 0) {
+ throw new InvalidImageException('Impossible de déterminer width/height ou viewBox valides pour le SVG');
+ }
+
+ return [$w, $h];
+ }
}
diff --git a/src/PhpWord/Writer/Word2007/Element/Image.php b/src/PhpWord/Writer/Word2007/Element/Image.php
index 7835f32ad5..813f1e7582 100644
--- a/src/PhpWord/Writer/Word2007/Element/Image.php
+++ b/src/PhpWord/Writer/Word2007/Element/Image.php
@@ -42,6 +42,12 @@ public function write(): void
if (!$element instanceof ImageElement) {
return;
}
+ $ext = strtolower(pathinfo($element->getSource(), PATHINFO_EXTENSION));
+ if ($ext === 'svg') {
+ $this->writeSvgDrawing($xmlWriter, $element);
+
+ return;
+ }
if ($element->isWatermark()) {
$this->writeWatermark($xmlWriter, $element);
@@ -127,4 +133,136 @@ private function writeWatermark(XMLWriter $xmlWriter, ImageElement $element): vo
$xmlWriter->endElement(); // w:p
}
}
+
+ private function writeSvgDrawing(XMLWriter $xmlWriter, ImageElement $element): void
+ {
+ $rId = $element->getRelationId() + ($element->isInSection() ? 6 : 0);
+
+ $style = $element->getStyle();
+ // dimensions px, fallback sur getSvgDimensions()
+ $pxW = $style->getWidth() ?: 0;
+ $pxH = $style->getHeight() ?: 0;
+ if ($pxW <= 0 || $pxH <= 0) {
+ [$pxW, $pxH] = $element->getSvgDimensions($element->getSource());
+ }
+ $cx = \PhpOffice\PhpWord\Shared\Drawing::pixelsToEmu($pxW);
+ $cy = \PhpOffice\PhpWord\Shared\Drawing::pixelsToEmu($pxH);
+
+ // + align
+ if (!$this->withoutP) {
+ $xmlWriter->startElement('w:p');
+ (new ImageStyleWriter($xmlWriter, $style))->writeAlignment();
+ }
+ //
+ $xmlWriter->startElement('w:r');
+ //
+ $xmlWriter->startElement('w:drawing');
+
+ // avec déclarations xmlns comme python-docx-oss
+ $xmlWriter->startElement('wp:inline');
+ $xmlWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+ $xmlWriter->writeAttribute('xmlns:pic', 'http://schemas.openxmlformats.org/drawingml/2006/picture');
+ $xmlWriter->writeAttribute('xmlns:asvg', 'http://schemas.microsoft.com/office/drawing/2016/SVG/main');
+
+ //
+ $xmlWriter->startElement('wp:extent');
+ $xmlWriter->writeAttribute('cx', (string) $cx);
+ $xmlWriter->writeAttribute('cy', (string) $cy);
+ $xmlWriter->endElement();
+
+ //
+ $xmlWriter->startElement('wp:docPr');
+ $xmlWriter->writeAttribute('id', '1');
+ $xmlWriter->writeAttribute('name', 'Picture 1');
+ $xmlWriter->endElement();
+
+ //
+ $xmlWriter->startElement('wp:cNvGraphicFramePr');
+ $xmlWriter->startElement('a:graphicFrameLocks');
+ $xmlWriter->writeAttribute('noChangeAspect', '1');
+ $xmlWriter->endElement();
+ $xmlWriter->endElement();
+
+ //
+ $xmlWriter->startElement('a:graphic');
+ //
+ $xmlWriter->startElement('a:graphicData');
+ $xmlWriter->writeAttribute(
+ 'uri',
+ 'http://schemas.openxmlformats.org/drawingml/2006/picture'
+ );
+
+ //
+ $xmlWriter->startElement('pic:pic');
+
+ //
+ $xmlWriter->startElement('pic:nvPicPr');
+ $xmlWriter->startElement('pic:cNvPr');
+ $xmlWriter->writeAttribute('id', '0');
+ $xmlWriter->writeAttribute('name', basename($element->getSource()));
+ $xmlWriter->endElement();
+ $xmlWriter->startElement('pic:cNvPicPr');
+ $xmlWriter->endElement();
+ $xmlWriter->endElement();
+
+ //
+ $xmlWriter->startElement('pic:blipFill');
+ $xmlWriter->startElement('a:blip');
+ // uniquement extLst avec svgBlip
+ $xmlWriter->startElement('a:extLst');
+ $xmlWriter->startElement('a:ext');
+ $xmlWriter->writeAttribute(
+ 'uri',
+ '{96DAC541-7B7A-43D3-8B79-37D633B846F1}'
+ );
+ $xmlWriter->startElement('asvg:svgBlip');
+ $xmlWriter->writeAttribute(
+ 'r:embed',
+ 'rId' . $rId
+ );
+ $xmlWriter->endElement(); // asvg:svgBlip
+ $xmlWriter->endElement(); // a:ext
+ $xmlWriter->endElement(); // a:extLst
+ $xmlWriter->endElement(); // a:blip
+
+ //
+ $xmlWriter->startElement('a:stretch');
+ $xmlWriter->startElement('a:fillRect');
+ $xmlWriter->endElement();
+ $xmlWriter->endElement();
+
+ $xmlWriter->endElement(); // pic:blipFill
+
+ //
+ $xmlWriter->startElement('pic:spPr');
+ $xmlWriter->startElement('a:xfrm');
+ $xmlWriter->startElement('a:off');
+ $xmlWriter->writeAttribute('x', '0');
+ $xmlWriter->writeAttribute('y', '0');
+ $xmlWriter->endElement();
+ $xmlWriter->startElement('a:ext');
+ $xmlWriter->writeAttribute('cx', (string) $cx);
+ $xmlWriter->writeAttribute('cy', (string) $cy);
+ $xmlWriter->endElement();
+ $xmlWriter->endElement();
+ $xmlWriter->startElement('a:prstGeom');
+ $xmlWriter->writeAttribute('prst', 'rect');
+ $xmlWriter->endElement();
+ $xmlWriter->endElement(); // pic:spPr
+
+ $xmlWriter->endElement(); // pic:pic
+
+ $xmlWriter->endElement(); // a:graphicData
+ $xmlWriter->endElement(); // a:graphic
+
+ $xmlWriter->endElement(); // wp:inline
+
+ $xmlWriter->endElement(); // w:drawing
+ $xmlWriter->endElement(); // w:r
+
+ //
+ if (!$this->withoutP) {
+ $xmlWriter->endElement();
+ }
+ }
}
diff --git a/tests/PhpWordTests/Element/SvgImageTest.php b/tests/PhpWordTests/Element/SvgImageTest.php
new file mode 100644
index 0000000000..8873d2c261
--- /dev/null
+++ b/tests/PhpWordTests/Element/SvgImageTest.php
@@ -0,0 +1,47 @@
+addSection();
+ $image = $section->addImage($svgPath);
+
+ self::assertEquals($svgPath, $image->getSource());
+ self::assertEquals('image/svg+xml', $image->getImageType());
+ }
+
+ public function testAddSvgImageWithStyles(): void
+ {
+ $svgPath = __DIR__ . '/../_files/images/sample.svg';
+ $phpWord = new PhpWord();
+ $section = $phpWord->addSection();
+ $options = [
+ 'width' => 200,
+ 'height' => 200,
+ 'wrappingStyle' => Image::WRAPPING_STYLE_BEHIND,
+ ];
+
+ $image = $section->addImage($svgPath, $options);
+
+ self::assertEquals(200, $image->getStyle()->getWidth());
+ self::assertEquals(200, $image->getStyle()->getHeight());
+ self::assertEquals(Image::WRAPPING_STYLE_BEHIND, $image->getStyle()->getWrappingStyle());
+ }
+}
diff --git a/tests/PhpWordTests/_files/images/sample.svg b/tests/PhpWordTests/_files/images/sample.svg
new file mode 100644
index 0000000000..8a800c4a2c
--- /dev/null
+++ b/tests/PhpWordTests/_files/images/sample.svg
@@ -0,0 +1,96 @@
+
+
\ No newline at end of file