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 24c428e9b6af6c1823e118606eacd2eac89f4207 Author: Miklos Vajna <[email protected]> AuthorDate: Wed Sep 4 11:05:56 2024 +0200 Commit: Caolán McNamara <[email protected]> CommitDate: Thu Sep 5 10:14:35 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. (cherry picked from commit 0a89d65e6bb7be293c1a7b4615a08292701694dc) Change-Id: I8cb0ca2e839fba911a75e1925cf79145f69af151 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172882 Reviewed-by: Caolán McNamara <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> 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 308fe75f8a86..8e1aff2c96b0 100644 --- a/filter/qa/unit/svg.cxx +++ b/filter/qa/unit/svg.cxx @@ -319,6 +319,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 e2079304abbc..d659ee3305ee 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -1390,6 +1390,8 @@ void SVGTextWriter::endTextPosition() bool SVGTextWriter::hasTextOpacity() const { return !maTextOpacity.isEmpty(); } +OUString& SVGTextWriter::getTextOpacity() { return maTextOpacity; } + void SVGTextWriter::implExportHyperlinkIds() { if( !msHyperlinkIdList.isEmpty() ) @@ -2544,6 +2546,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()) @@ -2557,6 +2568,11 @@ void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, con ImplWriteActions( rMtf, nWriteFlags, "" ); mpVDev->Pop(); } + + if (oTextOpacity) + { + maTextWriter.getTextOpacity() = *oTextOpacity; + } } diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx index 85c931fb1763..6da0f5e85354 100644 --- a/filter/source/svg/svgwriter.hxx +++ b/filter/source/svg/svgwriter.hxx @@ -259,6 +259,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 >
