include/oox/export/drawingml.hxx               |   14 ++-
 oox/source/export/drawingml.cxx                |  103 +++++++++++++++++++++++--
 oox/source/token/namespaces-strict.txt         |    1 
 oox/source/token/namespaces.txt                |    1 
 oox/source/token/tokens.txt                    |    2 
 sw/qa/extras/ooxmlexport/data/SvgImageTest.odt |binary
 sw/qa/extras/ooxmlexport/ooxmlexport20.cxx     |   27 ++++++
 sw/source/filter/ww8/docxattributeoutput.cxx   |   16 +++
 test/source/xmltesttools.cxx                   |    2 
 9 files changed, 157 insertions(+), 9 deletions(-)

New commits:
commit bfbbf06bcea4d58117c14fd3f3b8743a3714f97e
Author:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
AuthorDate: Sun Dec 3 13:21:35 2023 +0900
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Mon Dec 4 10:33:34 2023 +0100

    tdf#126084 support writing SVG images into OOXML using the MS OOXML 
extension
    
    SVG files aren't supported in OOXML, but we can write it using the
    MS OOXML extension, which is supported in the latest MSO versions.
    
    For now this only implements the support in the exporter.
    
    Change-Id: I688180fb5772f3999c2ee3020bc234f90d57cc2f
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/157237
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>

diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx
index 9028cfdc0f9f..dcbb1b544390 100644
--- a/include/oox/export/drawingml.hxx
+++ b/include/oox/export/drawingml.hxx
@@ -259,16 +259,25 @@ private:
     DocumentType meDocumentType;
 
     OUString writeNewEntryToStorage(const Graphic& rGraphic, bool 
bRelPathToMedia);
+    OUString writeNewSvgEntryToStorage(const Graphic& rGraphic, bool 
bRelPathToMedia);
 
 public:
+    enum class TypeHint
+    {
+        Detect,
+        SVG
+    };
+
     GraphicExport(sax_fastparser::FSHelperPtr pFS, ::oox::core::XmlFilterBase* 
pFilterBase, DocumentType eDocumentType)
         : mpFS(pFS)
         , mpFilterBase(pFilterBase)
         , meDocumentType(eDocumentType)
     {}
 
-    OUString writeToStorage(Graphic const& rGraphic, bool bRelPathToMedia = 
false);
+    OUString writeToStorage(Graphic const& rGraphic, bool bRelPathToMedia = 
false, TypeHint eHint = TypeHint::Detect);
+
     void writeBlip(Graphic const& rGraphic, std::vector<model::BlipEffect> 
const& rEffects, bool bRelPathToMedia = false);
+    void writeSvgExtension(OUString const& rSvgRelId);
 };
 
 class OOX_DLLPUBLIC DrawingML
@@ -353,7 +362,7 @@ public:
 
     void SetBackgroundDark(bool bIsDark) { mbIsBackgroundDark = bIsDark; }
     /// If bRelPathToMedia is true add "../" to image folder path while adding 
the image relationship
-    OUString writeGraphicToStorage(const Graphic &rGraphic , bool 
bRelPathToMedia = false);
+    OUString writeGraphicToStorage(const Graphic &rGraphic , bool 
bRelPathToMedia = false, GraphicExport::TypeHint eHint = 
GraphicExport::TypeHint::Detect);
 
     void WriteColor( ::Color nColor, sal_Int32 nAlpha = MAX_PERCENT );
     void WriteColor( const OUString& sColorSchemeName, const 
css::uno::Sequence< css::beans::PropertyValue >& aTransformations, sal_Int32 
nAlpha = MAX_PERCENT );
@@ -516,6 +525,7 @@ public:
                                         const OUString& sRelationshipType,
                                         OUString* pRelationshipId );
 
+    std::shared_ptr<GraphicExport> createGraphicExport();
 };
 
 }
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index 228aa2326cc0..05c96c9ad798 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -1283,12 +1283,34 @@ OUString DrawingML::GetRelationCompPrefix() const
     return OUString(getRelationCompPrefix(meDocumentType));
 }
 
+void GraphicExport::writeSvgExtension(OUString const& rSvgRelId)
+{
+    if (rSvgRelId.isEmpty())
+        return;
+
+    mpFS->startElementNS(XML_a, XML_extLst);
+    mpFS->startElementNS(XML_a, XML_ext, XML_uri, 
"{96DAC541-7B7A-43D3-8B79-37D633B846F1}");
+    mpFS->singleElementNS(XML_asvg, XML_svgBlip,
+            FSNS(XML_xmlns, XML_asvg), 
mpFilterBase->getNamespaceURL(OOX_NS(asvg)),
+            FSNS(XML_r, XML_embed), rSvgRelId);
+    mpFS->endElementNS(XML_a, XML_ext);
+    mpFS->endElementNS( XML_a, XML_extLst);
+}
+
 void GraphicExport::writeBlip(Graphic const& rGraphic, 
std::vector<model::BlipEffect> const& rEffects, bool bRelPathToMedia)
 {
     OUString sRelId = writeToStorage(rGraphic, bRelPathToMedia);
 
     mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);
 
+    auto const& rVectorGraphicDataPtr = rGraphic.getVectorGraphicData();
+
+    if (rVectorGraphicDataPtr && rVectorGraphicDataPtr->getType() == 
VectorGraphicDataType::Svg)
+    {
+        OUString sSvgRelId = writeToStorage(rGraphic, bRelPathToMedia, 
TypeHint::SVG);
+        writeSvgExtension(sSvgRelId);
+    }
+
     for (auto const& rEffect : rEffects)
     {
         switch (rEffect.meType)
@@ -1514,19 +1536,72 @@ OUString GraphicExport::writeNewEntryToStorage(const 
Graphic& rGraphic, bool bRe
     return sPath;
 }
 
-OUString GraphicExport::writeToStorage(const Graphic& rGraphic , bool 
bRelPathToMedia)
+namespace
+{
+BitmapChecksum makeChecksumUniqueForSVG(BitmapChecksum const& rChecksum)
+{
+    // need to modify the checksum so we know it's for SVG - just invert it
+    return ~rChecksum;
+}
+
+} // end anonymous namespace
+
+OUString GraphicExport::writeNewSvgEntryToStorage(const Graphic& rGraphic, 
bool bRelPathToMedia)
+{
+    OUString sMediaType = u"image/svg"_ustr;
+    OUString aExtension = u"svg"_ustr;
+
+    GfxLink const& rLink = rGraphic.GetGfxLink();
+    if (rLink.GetType() != GfxLinkType::NativeSvg)
+        return OUString();
+
+    const void* aData = rLink.GetData();
+    std::size_t nDataSize = rLink.GetDataSize();
+
+    GraphicExportCache& rGraphicExportCache = GraphicExportCache::get();
+    auto sImageCountString = 
OUString::number(rGraphicExportCache.nextImageCount());
+
+    OUString sComponentDir(getComponentDir(meDocumentType));
+
+    OUString sImagePath = sComponentDir + u"/media/image"_ustr + 
sImageCountString + u"."_ustr + aExtension;
+
+    Reference<XOutputStream> xOutStream = 
mpFilterBase->openFragmentStream(sImagePath, sMediaType);
+    xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const 
sal_Int8*>(aData), nDataSize));
+    xOutStream->closeOutput();
+
+    OUString sRelationCompPrefix;
+    if (bRelPathToMedia)
+        sRelationCompPrefix = u"../"_ustr;
+    else
+        sRelationCompPrefix = getRelationCompPrefix(meDocumentType);
+
+    OUString sPath = sRelationCompPrefix + u"media/image"_ustr + 
sImageCountString + u"."_ustr + aExtension;
+
+    
rGraphicExportCache.addExportGraphics(makeChecksumUniqueForSVG(rGraphic.GetChecksum()),
 sPath);
+
+    return sPath;
+}
+
+OUString GraphicExport::writeToStorage(const Graphic& rGraphic, bool 
bRelPathToMedia, TypeHint eHint)
 {
     OUString sPath;
 
+    auto aChecksum = rGraphic.GetChecksum();
+    if (eHint == TypeHint::SVG)
+        aChecksum = makeChecksumUniqueForSVG(aChecksum);
+
     GraphicExportCache& rGraphicExportCache = GraphicExportCache::get();
-    sPath = rGraphicExportCache.findExportGraphics(rGraphic.GetChecksum());
+    sPath = rGraphicExportCache.findExportGraphics(aChecksum);
 
     if (sPath.isEmpty())
     {
-        sPath = writeNewEntryToStorage(rGraphic, bRelPathToMedia);
+        if (eHint == TypeHint::SVG)
+            sPath = writeNewSvgEntryToStorage(rGraphic, bRelPathToMedia);
+        else
+            sPath = writeNewEntryToStorage(rGraphic, bRelPathToMedia);
 
         if (sPath.isEmpty())
-            return OUString(); // couldn't store - just return empty string
+            return OUString(); // couldn't store
     }
 
     OUString sRelId = mpFilterBase->addRelation(mpFS->getOutputStream(), 
oox::getRelationship(Relationship::IMAGE), sPath);
@@ -1534,10 +1609,15 @@ OUString GraphicExport::writeToStorage(const Graphic& 
rGraphic , bool bRelPathTo
     return sRelId;
 }
 
-OUString DrawingML::writeGraphicToStorage( const Graphic& rGraphic , bool 
bRelPathToMedia )
+std::shared_ptr<GraphicExport> DrawingML::createGraphicExport()
+{
+    return std::make_shared<GraphicExport>(mpFS, mpFB, meDocumentType);
+}
+
+OUString DrawingML::writeGraphicToStorage(const Graphic& rGraphic , bool 
bRelPathToMedia, GraphicExport::TypeHint eHint)
 {
     GraphicExport aExporter(mpFS, mpFB, meDocumentType);
-    return aExporter.writeToStorage(rGraphic, bRelPathToMedia);
+    return aExporter.writeToStorage(rGraphic, bRelPathToMedia, eHint);
 }
 
 void DrawingML::WriteMediaNonVisualProperties(const 
css::uno::Reference<css::drawing::XShape>& xShape)
@@ -1706,10 +1786,21 @@ void 
DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rX
         return;
 
     Graphic aGraphic(rxGraphic);
+
     sRelId = writeGraphicToStorage(aGraphic, bRelPathToMedia);
 
     mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);
 
+    auto pVectorGraphicDataPtr = aGraphic.getVectorGraphicData();
+
+    if (pVectorGraphicDataPtr && pVectorGraphicDataPtr->getType() == 
VectorGraphicDataType::Svg)
+    {
+        GraphicExport aExporter(mpFS, mpFB, meDocumentType);
+        OUString sSvgRelId =  aExporter.writeToStorage(aGraphic, 
bRelPathToMedia, GraphicExport::TypeHint::SVG);
+        if (!sSvgRelId.isEmpty())
+            aExporter.writeSvgExtension(sSvgRelId);
+    }
+
     WriteImageBrightnessContrastTransparence(rXPropSet);
 
     WriteArtisticEffect(rXPropSet);
diff --git a/oox/source/token/namespaces-strict.txt 
b/oox/source/token/namespaces-strict.txt
index beed3238ae2d..2b6c53807377 100644
--- a/oox/source/token/namespaces-strict.txt
+++ b/oox/source/token/namespaces-strict.txt
@@ -94,6 +94,7 @@ xr2                     
http://schemas.microsoft.com/office/spreadsheetml/2015/r
 # extlst namespaces
 
 adec                    
http://schemas.microsoft.com/office/drawing/2017/decorative
+asvg                    
http://schemas.microsoft.com/office/drawing/2016/SVG/main
 
 # xls14Lst for features introduced by excel 2010
 xls14Lst               
http://schemas.microsoft.com/office/spreadsheetml/2009/9/main
diff --git a/oox/source/token/namespaces.txt b/oox/source/token/namespaces.txt
index dbe29f19a220..c2e30e0f8421 100644
--- a/oox/source/token/namespaces.txt
+++ b/oox/source/token/namespaces.txt
@@ -94,6 +94,7 @@ xr2                     
http://schemas.microsoft.com/office/spreadsheetml/2015/r
 # extlst namespaces
 
 adec                    
http://schemas.microsoft.com/office/drawing/2017/decorative
+asvg                    
http://schemas.microsoft.com/office/drawing/2016/SVG/main
 
 # xls14Lst for features introduced by excel 2010
 xls14Lst               
http://schemas.microsoft.com/office/spreadsheetml/2009/9/main
diff --git a/oox/source/token/tokens.txt b/oox/source/token/tokens.txt
index 56e17dc35c22..47d6b07f2470 100644
--- a/oox/source/token/tokens.txt
+++ b/oox/source/token/tokens.txt
@@ -682,6 +682,7 @@ aspectratio
 assign
 asst
 asteriskTotals
+asvg
 atEnd
 atLeast
 atMost
@@ -5091,6 +5092,7 @@ suppressTopSpacing
 suppressTopSpacingWP
 surface3DChart
 surfaceChart
+svgBlip
 swAng
 swCell
 swapBordersFacingPages
diff --git a/sw/qa/extras/ooxmlexport/data/SvgImageTest.odt 
b/sw/qa/extras/ooxmlexport/data/SvgImageTest.odt
new file mode 100644
index 000000000000..3b37fe7998ef
Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/SvgImageTest.odt 
differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport20.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport20.cxx
index 8b5f6cab824b..263e769297d7 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport20.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport20.cxx
@@ -961,6 +961,33 @@ CPPUNIT_TEST_FIXTURE(Test, testZOrderInHeader)
     CPPUNIT_ASSERT(nBackground < nFrontShape);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testSvgExtensionsSupport)
+{
+    loadAndSave("SvgImageTest.odt");
+
+    xmlDocUniquePtr pXmlDocRels = parseExport("word/_rels/document.xml.rels");
+
+    // Check we have 2 relationships - one for PNG and one for SVG files
+    assertXPath(pXmlDocRels,
+                
"/rels:Relationships/rels:Relationship[@Target='media/image1.png']"_ostr, 
"Id"_ostr,
+                "rId2");
+
+    assertXPath(pXmlDocRels,
+                
"/rels:Relationships/rels:Relationship[@Target='media/image2.svg']"_ostr, 
"Id"_ostr,
+                "rId3");
+
+    // Check there is the extension present
+    xmlDocUniquePtr pXmlDocContent = parseExport("word/document.xml");
+
+    OString aPath(
+        
"/w:document/w:body/w:p/w:r/w:drawing/wp:anchor/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip"_ostr);
+    assertXPath(pXmlDocContent, aPath, "embed"_ostr, "rId2");
+
+    assertXPath(pXmlDocContent, aPath + "/a:extLst/a:ext"_ostr, "uri"_ostr,
+                "{96DAC541-7B7A-43D3-8B79-37D633B846F1}");
+    assertXPath(pXmlDocContent, aPath + "/a:extLst/a:ext/asvg:svgBlip"_ostr, 
"embed"_ostr, "rId3");
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index bf6380b12ccd..8949ffe58a77 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -52,6 +52,7 @@
 #include <oox/token/relationship.hxx>
 #include <oox/export/vmlexport.hxx>
 #include <oox/ole/olehelper.hxx>
+#include <oox/export/drawingml.hxx>
 
 #include <editeng/autokernitem.hxx>
 #include <editeng/unoprnms.hxx>
@@ -5181,6 +5182,7 @@ void DocxAttributeOutput::FlyFrameGraphic( const 
SwGrfNode* pGrfNode, const Size
     const SwFrameFormat* pFrameFormat = pGrfNode ? pGrfNode->GetFlyFormat() : 
pOLEFrameFormat;
     // create the relation ID
     OString aRelId;
+    OUString sSvgRelId;
     sal_Int32 nImageType;
     if ( pGrfNode && pGrfNode->IsLinkedFile() )
     {
@@ -5216,9 +5218,14 @@ void DocxAttributeOutput::FlyFrameGraphic( const 
SwGrfNode* pGrfNode, const Size
             aGraphic = *pOLENode->GetGraphic();
 
         m_rDrawingML.SetFS(m_pSerializer); // to be sure that we write to the 
right stream
-        OUString aImageId = m_rDrawingML.writeGraphicToStorage(aGraphic, 
false);
+        auto pGraphicExport = m_rDrawingML.createGraphicExport();
+        OUString aImageId = pGraphicExport->writeToStorage(aGraphic, false);
         aRelId = OUStringToOString(aImageId, RTL_TEXTENCODING_UTF8);
 
+        if (aGraphic.getVectorGraphicData() && 
aGraphic.getVectorGraphicData()->getType() == VectorGraphicDataType::Svg)
+        {
+            sSvgRelId = pGraphicExport->writeToStorage(aGraphic, false, 
drawingml::GraphicExport::TypeHint::SVG);
+        }
         nImageType = XML_embed;
     }
 
@@ -5354,6 +5361,13 @@ void DocxAttributeOutput::FlyFrameGraphic( const 
SwGrfNode* pGrfNode, const Size
         else if (nMode == GraphicDrawMode::Watermark) //watermark has a 
brightness/luminance of 0,5 and contrast of -0.7 in LibreOffice
             m_pSerializer->singleElementNS( XML_a, XML_lum, XML_bright, 
OString::number(70000), XML_contrast, OString::number(-70000) );
     }
+
+    if (!sSvgRelId.isEmpty())
+    {
+        auto pGraphicExport = m_rDrawingML.createGraphicExport();
+        pGraphicExport->writeSvgExtension(sSvgRelId);
+    }
+
     m_pSerializer->endElementNS( XML_a, XML_blip );
 
     if (xShapePropSet)
diff --git a/test/source/xmltesttools.cxx b/test/source/xmltesttools.cxx
index 8f96a399caff..14f953557f5f 100644
--- a/test/source/xmltesttools.cxx
+++ b/test/source/xmltesttools.cxx
@@ -455,6 +455,8 @@ void 
XmlTestTools::registerOOXMLNamespaces(xmlXPathContextPtr& pXmlXpathCtx)
                        
BAD_CAST("http://schemas.microsoft.com/office/drawing/2012/chart";));
     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xr2"),
                        
BAD_CAST("http://schemas.microsoft.com/office/spreadsheetml/2015/revision2";));
+    xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("asvg"),
+                       
BAD_CAST("http://schemas.microsoft.com/office/drawing/2016/SVG/main";));
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to