filter/qa/unit/data/semi-transparent-text-bullet.odg |binary filter/qa/unit/svg.cxx | 29 +++++++++++++++++++ filter/source/svg/svgwriter.cxx | 16 ++++++++++ filter/source/svg/svgwriter.hxx | 1 4 files changed, 46 insertions(+)
New commits: commit dde9a8b6c6841e89bec958dcfa1bb36d8728598f Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Wed Sep 4 11:05:56 2024 +0200 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Wed Sep 4 20:18:29 2024 +0200 tdf#162782 SVG export: fix handling of semi-transparent text inside a list Open the bugdoc, try to export as SVG, results in an assertion failure in debug builds, the produced XML would not be well-formed. Commit 666f252457bdb4371d15380a0289e107b2dfbe84 (SVG export: fix lost semi-transparent text on shapes, 2020-07-17) added support for text opacity on shapes, but this assumes that the entire shape has the same opacity, while this shape has 3 paragraphs and only the middle one has an opacity set, at a text span level. Additionally, it's a bullet, so the text (for the bullet, has no transparency set) starts before the transparency metafile action would start. This means that the existing logic won't realize that opacity should be exported using the fill-opacity attribute instead of a <g> element. Fix the problem by checking for the isTextShapeStarted() case in SVGActionWriter::ImplWriteMask(): if we're already inside text, then we always want to map a transparency mask to the fill-opacity attribute instead of a <g> element. Leave the shape-level code at SVGTextWriter::setTextPosition() unchanged, that continues to deal with per-shape text opacity. Change-Id: I8cb0ca2e839fba911a75e1925cf79145f69af151 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172856 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmik...@collabora.com> (cherry picked from commit 0a89d65e6bb7be293c1a7b4615a08292701694dc) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172823 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/filter/qa/unit/data/semi-transparent-text-bullet.odg b/filter/qa/unit/data/semi-transparent-text-bullet.odg new file mode 100644 index 000000000000..df54dbfb6857 Binary files /dev/null and b/filter/qa/unit/data/semi-transparent-text-bullet.odg differ diff --git a/filter/qa/unit/svg.cxx b/filter/qa/unit/svg.cxx index 3f1fde79374e..a7b055c8dbe8 100644 --- a/filter/qa/unit/svg.cxx +++ b/filter/qa/unit/svg.cxx @@ -312,6 +312,35 @@ CPPUNIT_TEST_FIXTURE(SvgFilterTest, textInImage) // - Actual : 0 } +CPPUNIT_TEST_FIXTURE(SvgFilterTest, testSemiTransparentTextBullet) +{ + // Given a document with 3 paragraphs, paragraph 2 is a bullet with 80% opacity. + loadFromFile(u"semi-transparent-text-bullet.odg"); + + // When exporting to SVG: + save(u"draw_svg_Export"_ustr); + + // Then make sure the result is correct: + xmlDocUniquePtr pXmlDoc = parseExportedFile(); + // We have 3 paragraphs. + assertXPath(pXmlDoc, "//svg:g[@class='TextShape']//svg:text/svg:tspan"_ostr, 3); + // Paragraph 1 has no spans with text opacity set. + assertXPath( + pXmlDoc, + "(//svg:g[@class='TextShape']//svg:text/svg:tspan)[1]/svg:tspan/svg:tspan[@fill-opacity='0.8']"_ostr, + 0); + // Paragraph 2 has a span with text opacity set to 80%. + assertXPath( + pXmlDoc, + "(//svg:g[@class='TextShape']//svg:text/svg:tspan)[2]/svg:tspan/svg:tspan[@fill-opacity='0.8']"_ostr, + 1); + // Paragraph 3 has no spans with text opacity set. + assertXPath( + pXmlDoc, + "(//svg:g[@class='TextShape']//svg:text/svg:tspan)[3]/svg:tspan/svg:tspan[@fill-opacity='0.8']"_ostr, + 0); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx index 57a039f32a18..a3e3e01542c9 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -1391,6 +1391,8 @@ void SVGTextWriter::endTextPosition() bool SVGTextWriter::hasTextOpacity() const { return !maTextOpacity.isEmpty(); } +OUString& SVGTextWriter::getTextOpacity() { return maTextOpacity; } + void SVGTextWriter::implExportHyperlinkIds() { if( !msHyperlinkIdList.isEmpty() ) @@ -2545,6 +2547,15 @@ void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, con if (nMoveX || nMoveY) rMtf.Move(nMoveX, nMoveY); + std::optional<OUString> oTextOpacity; + if (maTextWriter.isTextShapeStarted()) + { + // We're inside <text>, then try to use the fill-opacity attribute instead of a <g> element + // to express transparency to ensure well-formed output. + oTextOpacity = maTextWriter.getTextOpacity(); + StartMask(rDestPt, rDestSize, rGradient, nWriteFlags, pColorStops, &maTextWriter.getTextOpacity()); + } + { std::unique_ptr<SvXMLElementExport> pElemG; if (!maTextWriter.hasTextOpacity()) @@ -2558,6 +2569,11 @@ void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, con ImplWriteActions( rMtf, nWriteFlags, u""_ustr ); mpVDev->Pop(); } + + if (oTextOpacity) + { + maTextWriter.getTextOpacity() = *oTextOpacity; + } } diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx index 5a9266b0c5fd..d87e2e67d4df 100644 --- a/filter/source/svg/svgwriter.hxx +++ b/filter/source/svg/svgwriter.hxx @@ -258,6 +258,7 @@ class SVGTextWriter final void startTextPosition( bool bExportX = true, bool bExportY = true); void endTextPosition(); bool hasTextOpacity() const; + OUString& getTextOpacity(); void implExportHyperlinkIds(); void implWriteBulletChars(); template< typename MetaBitmapActionType >