summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>2023-12-03 13:21:35 +0900
committerAndras Timar <andras.timar@collabora.com>2023-12-10 12:37:25 +0100
commit1372b6e33d0ce3e831ca553c5148655e97615d84 (patch)
tree31b978ce0dce18b5f5464477fd28a5b700f54b9a
parentc86a81192e6a39ca82850156fccb78b2a4ccc3c6 (diff)
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 <quikee@gmail.com> (cherry picked from commit bfbbf06bcea4d58117c14fd3f3b8743a3714f97e) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160383 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Andras Timar <andras.timar@collabora.com>
-rw-r--r--include/oox/export/drawingml.hxx14
-rw-r--r--oox/source/export/drawingml.cxx103
-rw-r--r--oox/source/token/namespaces-strict.txt1
-rw-r--r--oox/source/token/namespaces.txt1
-rw-r--r--oox/source/token/tokens.txt2
-rw-r--r--sw/qa/extras/ooxmlexport/data/SvgImageTest.odtbin0 -> 13608 bytes
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport18.cxx27
-rw-r--r--sw/source/filter/ww8/docxattributeoutput.cxx16
-rw-r--r--test/source/xmltesttools.cxx2
9 files changed, 157 insertions, 9 deletions
diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx
index 70827d256b8d..0b76af607fb3 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
@@ -352,7 +361,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 );
@@ -514,6 +523,7 @@ public:
const char* sRelationshipType,
OUString* pRelationshipId );
+ std::shared_ptr<GraphicExport> createGraphicExport();
};
}
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index 0bb2272d6ecf..e876f545c4ff 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -1266,12 +1266,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)
@@ -1497,19 +1519,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 = "image/svg";
+ OUString aExtension = "svg";
+
+ 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 + "/media/image" + sImageCountString + "." + 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 = "../";
+ else
+ sRelationCompPrefix = getRelationCompPrefix(meDocumentType);
+
+ OUString sPath = sRelationCompPrefix + "media/image" + sImageCountString + "." + 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);
@@ -1517,10 +1592,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)
@@ -1684,10 +1764,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 59631432eb2f..f537ddbd3a05 100644
--- a/oox/source/token/namespaces-strict.txt
+++ b/oox/source/token/namespaces-strict.txt
@@ -93,6 +93,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 0790c65d8817..94eb86870b6b 100644
--- a/oox/source/token/namespaces.txt
+++ b/oox/source/token/namespaces.txt
@@ -93,6 +93,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 eb5239d8a8ac..2f62737f1c70 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
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/SvgImageTest.odt
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
index 4f812c643f5c..58dcc17bc3c2 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
@@ -745,6 +745,33 @@ DECLARE_OOXMLEXPORT_TEST(testTdf155736, "tdf155736_PageNumbers_footer.docx")
CPPUNIT_ASSERT_EQUAL(OUString("Page * of *"), parseDump("/root/page[2]/footer/txt/text()"));
}
+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']", "Id",
+ "rId2");
+
+ assertXPath(pXmlDocRels,
+ "/rels:Relationships/rels:Relationship[@Target='media/image2.svg']", "Id",
+ "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");
+ assertXPath(pXmlDocContent, aPath, "embed", "rId2");
+
+ assertXPath(pXmlDocContent, aPath + "/a:extLst/a:ext", "uri",
+ "{96DAC541-7B7A-43D3-8B79-37D633B846F1}");
+ assertXPath(pXmlDocContent, aPath + "/a:extLst/a:ext/asvg:svgBlip", "embed", "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 af1a940dad71..3ae94d3c6e69 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>
@@ -5074,6 +5075,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() )
{
@@ -5109,9 +5111,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;
}
@@ -5261,6 +5268,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 db743e5688aa..1c462c94a089 100644
--- a/test/source/xmltesttools.cxx
+++ b/test/source/xmltesttools.cxx
@@ -447,6 +447,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: */