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 0a89d65e6bb7be293c1a7b4615a08292701694dc
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Sep 4 11:05:56 2024 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Sep 4 16:07:34 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>

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 ac179f78372f..92825412250a 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() )
@@ -2546,6 +2548,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())
@@ -2559,6 +2570,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 >

Reply via email to