[Libreoffice-commits] core.git: Branch 'distro/vector/vector-7.0' - 8 commits - filter/qa filter/source sw/CppunitTest_sw_updateall_object_replacements.mk sw/Module_sw.mk sw/qa sw/source

Miklos Vajna (via logerrit) logerrit at kemper.freedesktop.org
Thu Sep 10 15:38:23 UTC 2020


 filter/qa/unit/data/TransparentText.odg            |binary
 filter/qa/unit/svg.cxx                             |   38 ++++++
 filter/source/svg/svgwriter.cxx                    |  113 ++++++++++++++------
 filter/source/svg/svgwriter.hxx                    |   11 +
 sw/CppunitTest_sw_updateall_object_replacements.mk |   73 +++++++++++++
 sw/Module_sw.mk                                    |    1 
 sw/qa/core/data/updateall-objectreplacements.odt   |binary
 sw/qa/core/updateall_objectreplacements.cxx        |   92 ++++++++++++++++
 sw/qa/extras/htmlexport/data/no-ole2-pres-data.odt |binary
 sw/qa/extras/htmlexport/htmlexport.cxx             |  102 +++++++++++++++++-
 sw/source/filter/html/css1atr.cxx                  |   15 +-
 sw/source/filter/html/htmlatr.cxx                  |    4 
 sw/source/filter/html/htmlreqifreader.cxx          |  117 +++++++++++++--------
 sw/source/filter/html/wrthtml.hxx                  |    4 
 sw/source/uibase/uiview/view.cxx                   |    4 
 15 files changed, 483 insertions(+), 91 deletions(-)

New commits:
commit 63d93f1b92f16ad5b90507d1f349abcbed1994d5
Author:     Miklos Vajna <vmiklos at collabora.com>
AuthorDate: Wed Sep 2 17:44:19 2020 +0200
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Sep 10 17:29:16 2020 +0200

    sw reqif-xhtml export, embedded objects: take OLE1 pres data from rtf if needed
    
    Next to the native data of an embedded object, the presentation data /
    replacement is included at several layers:
    
    - the OLE2 container may have it
    - the OLE1 container may have it
    - the RTF container may have it
    - the PNG file next to the RTF container may have it
    
    Given that various consumers pick one of the above, we try to provide
    presentation data in all layers.
    
    We already had code to generate the OLE1 presentation data from the OLE2
    container, but we gave up for OLE1 in case the OLE2 container didn't
    have it. This means that in case the RTF container is wrapped in a
    proper RTF file, Word refuses the edit the embedded object.
    
    Fix the problem by taking the presentation data from RTF for OLE1
    purposes, in case it's missing from the OLE2 container.
    
    (cherry picked from commit 0d027abbc5609b096d2a954e77aa7354a55928ab)
    
    Change-Id: I158db1c87044a3895d0c64a6e5a5384686627d96

diff --git a/sw/qa/extras/htmlexport/data/no-ole2-pres-data.odt b/sw/qa/extras/htmlexport/data/no-ole2-pres-data.odt
new file mode 100644
index 000000000000..cd65a1755746
Binary files /dev/null and b/sw/qa/extras/htmlexport/data/no-ole2-pres-data.odt differ
diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx b/sw/qa/extras/htmlexport/htmlexport.cxx
index ec08d491e64f..a0bb2b073070 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -64,6 +64,16 @@ public:
         rStream.Seek(0);
     }
 
+    /// Wraps an RTF fragment into a complete RTF file, so an RTF parser can handle it.
+    static void wrapRtfFragment(const OUString& rURL, SvMemoryStream& rStream)
+    {
+        SvFileStream aRtfStream(rURL, StreamMode::READ);
+        rStream.WriteOString("{\\rtf1");
+        rStream.WriteStream(aRtfStream);
+        rStream.WriteOString("}");
+        rStream.Seek(0);
+    }
+
 private:
     bool mustCalcLayoutOf(const char* filename) override
     {
@@ -994,12 +1004,8 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifOle1PDF)
     OUString aRtfUrl = aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE);
 
     // Parse the ole1 data out of that.
-    SvFileStream aRtfStream(aRtfUrl, StreamMode::READ);
     SvMemoryStream aRtf;
-    aRtf.WriteOString("{\\rtf1");
-    aRtf.WriteStream(aRtfStream);
-    aRtf.WriteOString("}");
-    aRtf.Seek(0);
+    HtmlExportTest::wrapRtfFragment(aRtfUrl, aRtf);
     tools::SvRef<TestReqIfRtfReader> xReader(new TestReqIfRtfReader(aRtf));
     CPPUNIT_ASSERT(xReader->CallParser() != SvParserState::Error);
     SvMemoryStream aOle1;
@@ -1134,6 +1140,64 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testUnderlineNone)
     assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:div/reqif-xhtml:p", "style");
 }
 
+CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifOle1PresDataNoOle2)
+{
+    // Save to reqif-xhtml.
+    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "no-ole2-pres-data.odt";
+    mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument", {});
+    uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aStoreProperties = {
+        comphelper::makePropertyValue("FilterName", OUString("HTML (StarWriter)")),
+        comphelper::makePropertyValue("FilterOptions", OUString("xhtmlns=reqif-xhtml")),
+    };
+    xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties);
+
+    // Get the .ole path.
+    SvMemoryStream aStream;
+    HtmlExportTest::wrapFragment(maTempFile, aStream);
+    xmlDocUniquePtr pDoc = parseXmlStream(&aStream);
+    CPPUNIT_ASSERT(pDoc);
+    OUString aOlePath = getXPath(
+        pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p/reqif-xhtml:object", "data");
+    OUString aOleSuffix(".ole");
+    CPPUNIT_ASSERT(aOlePath.endsWith(aOleSuffix));
+    INetURLObject aUrl(maTempFile.GetURL());
+    aUrl.setBase(aOlePath.copy(0, aOlePath.getLength() - aOleSuffix.getLength()));
+    aUrl.setExtension("ole");
+    OUString aRtfUrl = aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+    // Parse the ole1 data out of the RTF fragment.
+    SvMemoryStream aRtf;
+    HtmlExportTest::wrapRtfFragment(aRtfUrl, aRtf);
+    tools::SvRef<TestReqIfRtfReader> xReader(new TestReqIfRtfReader(aRtf));
+    CPPUNIT_ASSERT(xReader->CallParser() != SvParserState::Error);
+    SvMemoryStream aOle1;
+    CPPUNIT_ASSERT(xReader->WriteObjectData(aOle1));
+    CPPUNIT_ASSERT(aOle1.Tell());
+
+    // Check the content of the ole1 data.
+    // Skip ObjectHeader, see [MS-OLEDS] 2.2.4.
+    aOle1.Seek(0);
+    sal_uInt32 nData;
+    aOle1.ReadUInt32(nData); // OLEVersion
+    aOle1.ReadUInt32(nData); // FormatID
+    aOle1.ReadUInt32(nData); // ClassName
+    aOle1.SeekRel(nData);
+    aOle1.ReadUInt32(nData); // TopicName
+    aOle1.SeekRel(nData);
+    aOle1.ReadUInt32(nData); // ItemName
+    aOle1.SeekRel(nData);
+    aOle1.ReadUInt32(nData); // NativeDataSize
+    aOle1.SeekRel(nData);
+
+    aOle1.ReadUInt32(nData); // OLEVersion for presentation data
+
+    // Without the accompanying fix in place, this test would have failed as there was no
+    // presentation data after the native data in the OLE1 container. The result was not editable in
+    // Word.
+    CPPUNIT_ASSERT(aOle1.good());
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/htmlreqifreader.cxx b/sw/source/filter/html/htmlreqifreader.cxx
index 87368da014bd..26119adfed46 100644
--- a/sw/source/filter/html/htmlreqifreader.cxx
+++ b/sw/source/filter/html/htmlreqifreader.cxx
@@ -229,10 +229,15 @@ OString InsertOLE1HeaderFromOle10NativeStream(tools::SvRef<SotStorage>& xStorage
     return aClassName;
 }
 
-/// Inserts an OLE1 header before an OLE2 storage.
+/**
+ * Writes an OLE1 header and data from rOle2 to rOle1.
+ *
+ * In case rOle2 has presentation data, then its size is written to nWidth/nHeight.  Otherwise
+ * nWidth/nHeight/pPresentationData/nPresentationData is used for the presentation data.
+ */
 OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, sal_uInt32& nHeight,
-                         SwOLENode& rOLENode, const sal_uInt8* /*pPresentationData*/,
-                         sal_uInt64 /*nPresentationData*/)
+                         SwOLENode& rOLENode, const sal_uInt8* pPresentationData,
+                         sal_uInt64 nPresentationData)
 {
     rOle2.Seek(0);
     tools::SvRef<SotStorage> xStorage(new SotStorage(rOle2));
@@ -278,33 +283,41 @@ OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, s
 
     // Write Presentation.
     SvMemoryStream aPresentationData;
+    // OLEVersion.
+    rOle1.WriteUInt32(0x00000501);
+    // FormatID: constant means the ClassName field is present.
+    rOle1.WriteUInt32(0x00000005);
+    // ClassName: null terminated pascal string.
+    OString aPresentationClassName("METAFILEPICT");
+    rOle1.WriteUInt32(aPresentationClassName.getLength() + 1);
+    rOle1.WriteOString(aPresentationClassName);
+    rOle1.WriteChar(0);
+    const sal_uInt8* pBytes = nullptr;
+    sal_uInt64 nBytes = 0;
     if (ParseOLE2Presentation(rOle2, nWidth, nHeight, aPresentationData))
     {
         // Take presentation data for OLE1 from OLE2.
-        // OLEVersion.
-        rOle1.WriteUInt32(0x00000501);
-        // FormatID: constant means the ClassName field is present.
-        rOle1.WriteUInt32(0x00000005);
-        // ClassName: null terminated pascal string.
-        OString aPresentationClassName("METAFILEPICT");
-        rOle1.WriteUInt32(aPresentationClassName.getLength() + 1);
-        rOle1.WriteOString(aPresentationClassName);
-        rOle1.WriteChar(0);
-        // Width.
-        rOle1.WriteUInt32(nWidth);
-        // Height.
-        rOle1.WriteUInt32(nHeight * -1);
-        // PresentationDataSize
-        sal_uInt32 nPresentationData = aPresentationData.Tell();
-        rOle1.WriteUInt32(8 + nPresentationData);
-        // Reserved1-4.
-        rOle1.WriteUInt16(0x0008);
-        rOle1.WriteUInt16(0x31b1);
-        rOle1.WriteUInt16(0x1dd9);
-        rOle1.WriteUInt16(0x0000);
-        aPresentationData.Seek(0);
-        rOle1.WriteStream(aPresentationData, nPresentationData);
+        pBytes = static_cast<const sal_uInt8*>(aPresentationData.GetData());
+        nBytes = aPresentationData.Tell();
+    }
+    else
+    {
+        // Take presentation data for OLE1 from RTF.
+        pBytes = pPresentationData;
+        nBytes = nPresentationData;
     }
+    // Width.
+    rOle1.WriteUInt32(nWidth);
+    // Height.
+    rOle1.WriteUInt32(nHeight * -1);
+    // PresentationDataSize
+    rOle1.WriteUInt32(8 + nPresentationData);
+    // Reserved1-4.
+    rOle1.WriteUInt16(0x0008);
+    rOle1.WriteUInt16(0x31b1);
+    rOle1.WriteUInt16(0x1dd9);
+    rOle1.WriteUInt16(0x0000);
+    rOle1.WriteBytes(pBytes, nBytes);
 
     return aClassName;
 }
commit 51c1b256daaf33f89419e662b51096a2baafdce7
Author:     Miklos Vajna <vmiklos at collabora.com>
AuthorDate: Tue Sep 1 13:36:48 2020 +0200
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Sep 10 12:43:44 2020 +0200

    sw reqif-xhtml export, embedded objects: prepare pres data earlier
    
    If an embedded object has some native data, we provide presentation data
    (replacement graphic) for it in the OLE1 container. We usually take this
    from the OLE2 container, but it's OK to omit the presentation data
    there.
    
    So refactor to have the presentation data available from the OLE node
    (already used for RTF purposes) earlier, that'll allow taking the OLE1
    presentation data from RTF if it's missing from OLE2.
    
    (cherry picked from commit 4d33262b1b652b57f222c9f1cce7d976725399d4)
    
    Change-Id: Ib6b1b5e843308b0f7af04499de5a1ef5461f7b00

diff --git a/sw/source/filter/html/htmlreqifreader.cxx b/sw/source/filter/html/htmlreqifreader.cxx
index 523bc973c587..87368da014bd 100644
--- a/sw/source/filter/html/htmlreqifreader.cxx
+++ b/sw/source/filter/html/htmlreqifreader.cxx
@@ -231,7 +231,8 @@ OString InsertOLE1HeaderFromOle10NativeStream(tools::SvRef<SotStorage>& xStorage
 
 /// Inserts an OLE1 header before an OLE2 storage.
 OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, sal_uInt32& nHeight,
-                         SwOLENode& rOLENode)
+                         SwOLENode& rOLENode, const sal_uInt8* /*pPresentationData*/,
+                         sal_uInt64 /*nPresentationData*/)
 {
     rOle2.Seek(0);
     tools::SvRef<SotStorage> xStorage(new SotStorage(rOle2));
@@ -279,6 +280,7 @@ OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, s
     SvMemoryStream aPresentationData;
     if (ParseOLE2Presentation(rOle2, nWidth, nHeight, aPresentationData))
     {
+        // Take presentation data for OLE1 from OLE2.
         // OLEVersion.
         rOle1.WriteUInt32(0x00000501);
         // FormatID: constant means the ClassName field is present.
@@ -307,8 +309,9 @@ OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, s
     return aClassName;
 }
 
-/// Writes rGraphic with size from rOLENode to rRtf as an RTF hexdump.
-void WrapOleGraphicInRtf(SvStream& rRtf, const SwOLENode& rOLENode, const Graphic& rGraphic)
+/// Writes presentation data with the specified size to rRtf as an RTF hexdump.
+void WrapOleGraphicInRtf(SvStream& rRtf, sal_uInt32 nWidth, sal_uInt32 nHeight,
+                         const sal_uInt8* pPresentationData, sal_uInt64 nPresentationData)
 {
     // Start result.
     rRtf.WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_RESULT);
@@ -317,23 +320,18 @@ void WrapOleGraphicInRtf(SvStream& rRtf, const SwOLENode& rOLENode, const Graphi
     rRtf.WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_PICT);
 
     rRtf.WriteCharPtr(OOO_STRING_SVTOOLS_RTF_WMETAFILE "8");
-    Size aSize(rOLENode.GetTwipSize());
     rRtf.WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PICW);
-    rRtf.WriteOString(OString::number(aSize.getWidth()));
+    rRtf.WriteOString(OString::number(nWidth));
     rRtf.WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PICH);
-    rRtf.WriteOString(OString::number(aSize.getHeight()));
+    rRtf.WriteOString(OString::number(nHeight));
     rRtf.WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PICWGOAL);
-    rRtf.WriteOString(OString::number(aSize.getWidth()));
+    rRtf.WriteOString(OString::number(nWidth));
     rRtf.WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PICHGOAL);
-    rRtf.WriteOString(OString::number(aSize.getHeight()));
-    SvMemoryStream aGraphicStream;
-    if (GraphicConverter::Export(aGraphicStream, rGraphic, ConvertDataFormat::WMF) == ERRCODE_NONE)
+    rRtf.WriteOString(OString::number(nHeight));
+    if (pPresentationData)
     {
-        auto pGraphicAry = static_cast<const sal_uInt8*>(aGraphicStream.GetData());
-        sal_uInt64 nSize = aGraphicStream.TellEnd();
-        msfilter::rtfutil::StripMetafileHeader(pGraphicAry, nSize);
         rRtf.WriteCharPtr(SAL_NEWLINE_STRING);
-        msfilter::rtfutil::WriteHex(pGraphicAry, nSize, &rRtf);
+        msfilter::rtfutil::WriteHex(pPresentationData, nPresentationData, &rRtf);
     }
 
     // End pict.
@@ -394,9 +392,27 @@ bool WrapOleInRtf(SvStream& rOle2, SvStream& rRtf, SwOLENode& rOLENode)
 
     // Write OLE1 header, then the RTF wrapper.
     SvMemoryStream aOLE1;
-    sal_uInt32 nWidth = 0;
-    sal_uInt32 nHeight = 0;
-    OString aClassName = InsertOLE1Header(rOle2, aOLE1, nWidth, nHeight, rOLENode);
+
+    // Prepare presentation data early, so it's available to both OLE1 and RTF.
+    Size aSize(rOLENode.GetTwipSize());
+    sal_uInt32 nWidth = aSize.getWidth();
+    sal_uInt32 nHeight = aSize.getHeight();
+    const Graphic* pGraphic = rOLENode.GetGraphic();
+    const sal_uInt8* pPresentationData = nullptr;
+    sal_uInt64 nPresentationData = 0;
+    SvMemoryStream aGraphicStream;
+    if (pGraphic)
+    {
+        if (GraphicConverter::Export(aGraphicStream, *pGraphic, ConvertDataFormat::WMF)
+            == ERRCODE_NONE)
+        {
+            pPresentationData = static_cast<const sal_uInt8*>(aGraphicStream.GetData());
+            nPresentationData = aGraphicStream.TellEnd();
+            msfilter::rtfutil::StripMetafileHeader(pPresentationData, nPresentationData);
+        }
+    }
+    OString aClassName = InsertOLE1Header(rOle2, aOLE1, nWidth, nHeight, rOLENode,
+                                          pPresentationData, nPresentationData);
 
     // Start object.
     rRtf.WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_OBJECT);
@@ -422,8 +438,10 @@ bool WrapOleInRtf(SvStream& rOle2, SvStream& rRtf, SwOLENode& rOLENode)
     // End objdata.
     rRtf.WriteCharPtr("}");
 
-    if (const Graphic* pGraphic = rOLENode.GetGraphic())
-        WrapOleGraphicInRtf(rRtf, rOLENode, *pGraphic);
+    if (pPresentationData)
+    {
+        WrapOleGraphicInRtf(rRtf, nWidth, nHeight, pPresentationData, nPresentationData);
+    }
 
     // End object.
     rRtf.WriteCharPtr("}");
commit c893e018b485313e905e0c4a05aa36cda09a8695
Author:     Tor Lillqvist <tml at collabora.com>
AuthorDate: Fri Aug 28 12:00:11 2020 +0300
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Sep 10 12:34:45 2020 +0200

    Fix ooo38104-1.sxw crash after c123bfff501229f398a1b679fc7434b82d53685c
    
    Unlike the c123bf commit, this commit does not cause the crash that
    was caught by the crash-testing system.
    
    (The crash could be reproduced by:
        wget -O ooo38104-1.sxw https://bz.apache.org/ooo/attachment.cgi?id=19889
        ./instdir/program/soffice.bin --headless --convert-to docx ./ooo38104-1.sxw
    )
    
    In this commit, I reinstate the "early return" in
    SwView::ReadUserDataSequence() that I dropped in the c123bf commit,
    but instead move the SelectShell() call earlier, so that it will be
    executed before the potential early return.
    
    The problem that we try to fix here is the one that the fresh
    CppunitTest_sw_updateall_object_replacements checks, so to reproduce
    that problem, revert both this commit and c123bf, and then run that
    unit test.
    
    (cherry picked from commit 6e0bb3fc4e89ddb85ddf40889b11a0c0bd4ab607)
    
    Conflicts:
            sw/source/uibase/uiview/view.cxx
    
    Change-Id: I6c728b75a2f172b75fbf2ad00c019c32aecc19f8

diff --git a/sw/source/uibase/uiview/view.cxx b/sw/source/uibase/uiview/view.cxx
index 79b6b7c761e8..04daaadd73ca 100644
--- a/sw/source/uibase/uiview/view.cxx
+++ b/sw/source/uibase/uiview/view.cxx
@@ -1380,10 +1380,14 @@ void SwView::ReadUserDataSequence ( const uno::Sequence < beans::PropertyValue >
         // delegate further
         GetViewImpl()->GetUNOObject_Impl()->getViewSettings()->setPropertyValue("ShowOnlineLayout", uno::Any(bBrowseMode));
     }
+
+    SelectShell();
+
     if (bGotVisibleBottom)
     {
         Point aCursorPos( nX, nY );
-
+        const long nAdd = m_pWrtShell->GetViewOptions()->getBrowseMode() ? DOCUMENTBORDER : DOCUMENTBORDER*2;
+        if (nBottom <= (m_pWrtShell->GetDocSize().Height()+nAdd) )
         {
             m_pWrtShell->EnableSmooth( false );
             const tools::Rectangle aVis( nLeft, nTop, nRight, nBottom );
@@ -1440,7 +1444,6 @@ void SwView::ReadUserDataSequence ( const uno::Sequence < beans::PropertyValue >
                 // reset flag value
                 m_pWrtShell->SetMacroExecAllowed( bSavedFlagValue );
             }
-            SelectShell();
 
             // Set ViewLayoutSettings
             const bool bSetViewLayoutSettings = bGotViewLayoutColumns && bGotViewLayoutBookMode &&
commit 34e34912455eca65777a1ab707ffac475a8fa65d
Author:     Tor Lillqvist <tml at collabora.com>
AuthorDate: Tue Aug 25 13:48:32 2020 +0300
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Sep 10 12:32:10 2020 +0200

    Add unit test for c123bfff501229f398a1b679fc7434b82d53685c
    
    (cherry picked from commit 3f291bb285335efbc2f21a08bdcb23d92911940c)
    
    Conflicts:
            sw/Module_sw.mk
    
    Change-Id: Ic616ec9f39b65f8e8ec840a48e3b5801b31cf5da

diff --git a/sw/CppunitTest_sw_updateall_object_replacements.mk b/sw/CppunitTest_sw_updateall_object_replacements.mk
new file mode 100644
index 000000000000..e6bd9ca09425
--- /dev/null
+++ b/sw/CppunitTest_sw_updateall_object_replacements.mk
@@ -0,0 +1,73 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,sw_updateall_object_replacements))
+
+$(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_updateall_object_replacements))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,sw_updateall_object_replacements, \
+    sw/qa/core/updateall_objectreplacements \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sw_updateall_object_replacements, \
+    comphelper \
+    cppu \
+    cppuhelper \
+    sal \
+    sfx \
+    svxcore \
+    sw \
+    test \
+    unotest \
+    utl \
+    vcl \
+    svt \
+    tl \
+    svl \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,sw_updateall_object_replacements,\
+    boost_headers \
+    libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_set_include,sw_updateall_object_replacements,\
+    -I$(SRCDIR)/sw/inc \
+    -I$(SRCDIR)/sw/source/core/inc \
+    -I$(SRCDIR)/sw/source/uibase/inc \
+    -I$(SRCDIR)/sw/qa/inc \
+    $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_api,sw_updateall_object_replacements,\
+    udkapi \
+    offapi \
+    oovbaapi \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,sw_updateall_object_replacements))
+$(eval $(call gb_CppunitTest_use_vcl,sw_updateall_object_replacements))
+
+$(eval $(call gb_CppunitTest_use_rdb,sw_updateall_object_replacements,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,sw_updateall_object_replacements,\
+    officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,sw_updateall_object_replacements))
+
+$(eval $(call gb_CppunitTest_use_uiconfigs,sw_updateall_object_replacements, \
+    modules/swriter \
+))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,sw_updateall_object_replacements))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sw/Module_sw.mk b/sw/Module_sw.mk
index 1dd4f34cc61f..7050954e74fb 100644
--- a/sw/Module_sw.mk
+++ b/sw/Module_sw.mk
@@ -118,6 +118,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sw,\
     CppunitTest_sw_core_objectpositioning \
     CppunitTest_sw_core_unocore \
     CppunitTest_sw_core_crsr \
+    CppunitTest_sw_updateall_object_replacements \
 ))
 
 ifneq ($(DISABLE_GUI),TRUE)
diff --git a/sw/qa/core/data/updateall-objectreplacements.odt b/sw/qa/core/data/updateall-objectreplacements.odt
new file mode 100644
index 000000000000..35decf73f895
Binary files /dev/null and b/sw/qa/core/data/updateall-objectreplacements.odt differ
diff --git a/sw/qa/core/updateall_objectreplacements.cxx b/sw/qa/core/updateall_objectreplacements.cxx
new file mode 100644
index 000000000000..92997d498e59
--- /dev/null
+++ b/sw/qa/core/updateall_objectreplacements.cxx
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <swmodeltestbase.hxx>
+
+#include <unotools/mediadescriptor.hxx>
+#include <comphelper/processfactory.hxx>
+#include <osl/file.hxx>
+
+#include <com/sun/star/frame/DispatchHelper.hpp>
+#include <com/sun/star/frame/XComponentLoader.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
+
+#include <unotxdoc.hxx>
+#include <docsh.hxx>
+#include <wrtsh.hxx>
+#include <swdtflvr.hxx>
+
+char const DATA_DIRECTORY[] = "/sw/qa/core/data/";
+
+/// Covers sw/source/core/undo/ fixes.
+class SwCoreUpdateAllObjectReplacementsTest : public SwModelTestBase
+{
+};
+
+CPPUNIT_TEST_FIXTURE(SwCoreUpdateAllObjectReplacementsTest, testDoIt)
+{
+    // Make a temporary copy of the test document
+    utl::TempFile tmp;
+    tmp.EnableKillingFile();
+    OUString sTempCopy = tmp.GetURL();
+    CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None,
+                         osl::File::copy(m_directories.getURLFromSrc(DATA_DIRECTORY)
+                                             + "updateall-objectreplacements.odt",
+                                         sTempCopy));
+
+    /* BASIC code that exhibits the problem:
+
+    desktop = CreateUnoService("com.sun.star.frame.Desktop")
+    Dim props(0) as new com.sun.star.beans.PropertyValue
+    props(0).Name = "Hidden"
+    props(0).Value = true
+    component = desktop.loadComponentFromURL("file://.../test.odt", "_default", 0, props)
+    Wait 1000 ' workaround
+    dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
+    frame = component.CurrentController.Frame
+    dispatcher.executeDispatch(frame, ".uno:UpdateAll", "", 0, Array())
+    component.storeSelf(Array())
+    component.dispose()
+    */
+
+    uno::Reference<lang::XMultiServiceFactory> xFactory(comphelper::getProcessServiceFactory());
+
+    // Load the copy
+    uno::Reference<uno::XInterface> xInterface
+        = xFactory->createInstance("com.sun.star.frame.Desktop");
+    uno::Reference<frame::XComponentLoader> xComponentLoader(xInterface, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aLoadArgs(1);
+    aLoadArgs[0].Name = "Hidden";
+    aLoadArgs[0].Value <<= true;
+    mxComponent = xComponentLoader->loadComponentFromURL(sTempCopy, "_default", 0, aLoadArgs);
+
+    // Perform the .uno:UpdateAll call and save
+    xInterface = xFactory->createInstance("com.sun.star.frame.DispatchHelper");
+    uno::Reference<frame::XDispatchHelper> xDispatchHelper(xInterface, uno::UNO_QUERY);
+    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
+    uno::Reference<frame::XDispatchProvider> xDispatchProvider(
+        xModel->getCurrentController()->getFrame(), uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aNoArgs;
+    xDispatchHelper->executeDispatch(xDispatchProvider, ".uno:UpdateAll", OUString(), 0, aNoArgs);
+    uno::Reference<frame::XStorable2> xStorable(mxComponent, uno::UNO_QUERY);
+    xStorable->storeSelf(aNoArgs);
+
+    // Check the contents of the updated copy and verify that ObjectReplacements are there
+    uno::Reference<packages::zip::XZipFileAccess2> xNameAccess
+        = packages::zip::ZipFileAccess::createWithURL(comphelper::getComponentContext(xFactory),
+                                                      sTempCopy);
+
+    CPPUNIT_ASSERT(xNameAccess->hasByName("ObjectReplacements/Components"));
+    CPPUNIT_ASSERT(xNameAccess->hasByName("ObjectReplacements/Components_1"));
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 98b18e9ac48c2a89f4ed4897db2952d07d6c852b
Author:     Tor Lillqvist <tml at collabora.com>
AuthorDate: Thu Aug 20 22:58:24 2020 +0300
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Sep 10 12:29:07 2020 +0200

    Bin overly eager early return that stops replacement image creation
    
    The return causes a customer's Java code that handles some .odt
    document to unexpectedly not create object replacement images in the
    document.
    
    The problem is that in the bad case, the SwTextShell is created later
    than .uno:UpdateAll is dispatched, so the dispatch does nothing.
    SwBaseShell::Execute() for FN_UPDATE_ALL is not called. And this is
    required, becase that calls setUserAllowsLinkUpdate(true), so that on
    save, when EmbeddedObjectContainer::StoreAsChildren() in comphelper/
    hits the 'if ( !xStream.is() && getUserAllowsLinkUpdate() )'
    condition, then we create a replacement image.
    
    This is a Basic script that demonstrates the same issue:
    
    desktop = CreateUnoService("com.sun.star.frame.Desktop")
    Dim props(0) as new com.sun.star.beans.PropertyValue
    props(0).Name = "Hidden"
    props(0).Value = true
    component = desktop.loadComponentFromURL("file:///.../test.odt", "_default", 0, props)
    dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
    frame = component.CurrentController.Frame
    dispatcher.executeDispatch(frame, ".uno:UpdateAll", "", 0, Array())
    component.storeSelf(Array())
    component.dispose()
    
    (Will also try to add a unit test that demonstrates the problem and
    verifies this fix.)
    
    (cherry picked from commit c123bfff501229f398a1b679fc7434b82d53685c)
    
    Conflicts:
            sw/source/uibase/uiview/view.cxx
    
    Change-Id: I4a5f78a2118a387f062c2d5213a0012a7df2dd9f

diff --git a/sw/source/uibase/uiview/view.cxx b/sw/source/uibase/uiview/view.cxx
index f2d10b466dce..79b6b7c761e8 100644
--- a/sw/source/uibase/uiview/view.cxx
+++ b/sw/source/uibase/uiview/view.cxx
@@ -1383,8 +1383,7 @@ void SwView::ReadUserDataSequence ( const uno::Sequence < beans::PropertyValue >
     if (bGotVisibleBottom)
     {
         Point aCursorPos( nX, nY );
-        const long nAdd = m_pWrtShell->GetViewOptions()->getBrowseMode() ? DOCUMENTBORDER : DOCUMENTBORDER*2;
-        if (nBottom <= (m_pWrtShell->GetDocSize().Height()+nAdd) )
+
         {
             m_pWrtShell->EnableSmooth( false );
             const tools::Rectangle aVis( nLeft, nTop, nRight, nBottom );
commit 45e6a810a8f286431f9ebd1b2563f521262139a8
Author:     Miklos Vajna <vmiklos at collabora.com>
AuthorDate: Wed Jul 29 10:23:06 2020 +0200
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Sep 10 12:22:46 2020 +0200

    sw reqif-xhtml export: avoid writing text-decoration:none
    
    This CSS key is allowed, but only the underline and line-through values
    are allowed in reqif mode, according to the top of page 66 of
    "01_OMG_Requirements Interchange Format (ReqIF)_Version
    1.2_formal-16-07-01.pdf".
    
    (cherry picked from commit d19a21a81bea24cdcfc8618ed3d37b825e638f65)
    
    Change-Id: Ide64344f58bde4569fe499d8514dab36a055bda9

diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx b/sw/qa/extras/htmlexport/htmlexport.cxx
index aa92fd90ddd6..ec08d491e64f 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -17,6 +17,7 @@
 #include <com/sun/star/frame/XDispatchHelper.hpp>
 #include <com/sun/star/frame/DispatchHelper.hpp>
 #include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
 
 #include <svtools/htmlcfg.hxx>
 #include <swmodule.hxx>
@@ -1106,6 +1107,33 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testMultiParaListItem)
     assertXPathContent(pXmlDoc, "//reqif-xhtml:ol/reqif-xhtml:li[3]/reqif-xhtml:p", "D");
 }
 
+CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testUnderlineNone)
+{
+    // Create a document with a single paragraph: its underlying is set to an explicit 'none' value.
+    loadURL("private:factory/swriter", nullptr);
+    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
+    uno::Reference<text::XText> xText = xTextDocument->getText();
+    xText->insertString(xText->getEnd(), "x", /*bAbsorb=*/false);
+    uno::Reference<beans::XPropertySet> xParagraph(getParagraph(1), uno::UNO_QUERY);
+    xParagraph->setPropertyValue("CharUnderline", uno::makeAny(sal_Int16(awt::FontUnderline::NONE)));
+
+    // Export to reqif-xhtml.
+    uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aStoreProperties = {
+        comphelper::makePropertyValue("FilterName", OUString("HTML (StarWriter)")),
+        comphelper::makePropertyValue("FilterOptions", OUString("xhtmlns=reqif-xhtml")),
+    };
+    xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties);
+
+    // Make sure that the paragraph has no explicit style, because "text-decoration: none" is
+    // filtered out.
+    SvMemoryStream aStream;
+    HtmlExportTest::wrapFragment(maTempFile, aStream);
+    xmlDocUniquePtr pXmlDoc = parseXmlStream(&aStream);
+    CPPUNIT_ASSERT(pXmlDoc);
+    assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:div/reqif-xhtml:p", "style");
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/css1atr.cxx b/sw/source/filter/html/css1atr.cxx
index 5445115a6d0d..9d773603a1ab 100644
--- a/sw/source/filter/html/css1atr.cxx
+++ b/sw/source/filter/html/css1atr.cxx
@@ -185,15 +185,22 @@ OString lclConvToHex(sal_uInt16 nHex)
 }
 }
 
-/// Determines if rProperty has to be suppressed due to ReqIF mode.
-bool IgnorePropertyForReqIF(bool bReqIF, const OString& rProperty)
+bool IgnorePropertyForReqIF(bool bReqIF, const OString& rProperty, const OString& rValue)
 {
     if (!bReqIF)
         return false;
 
     // Only allow these two keys, nothing else in ReqIF mode.
     if (rProperty == sCSS1_P_text_decoration)
-        return false;
+    {
+        // Deny other text-decoration values (e.g. "none").
+        if (rValue == "underline" || rValue == "line-through")
+        {
+            return false;
+        }
+
+        return true;
+    }
 
     if (rProperty == sCSS1_P_color)
         return false;
@@ -238,7 +245,7 @@ void SwHTMLWriter::OutCSS1_Property( const char *pProp,
                                      const char *pVal,
                                      const OUString *pSVal )
 {
-    if (IgnorePropertyForReqIF(mbReqIF, pProp))
+    if (IgnorePropertyForReqIF(mbReqIF, pProp, pVal))
         return;
 
     OStringBuffer sOut;
diff --git a/sw/source/filter/html/htmlatr.cxx b/sw/source/filter/html/htmlatr.cxx
index 83a02160b6ef..e7997b56f5e5 100644
--- a/sw/source/filter/html/htmlatr.cxx
+++ b/sw/source/filter/html/htmlatr.cxx
@@ -2687,7 +2687,7 @@ static Writer& OutHTML_SvxFont( Writer& rWrt, const SfxPoolItem& rHt )
     if( rHTMLWrt.m_bOutOpts )
         return rWrt;
 
-    if (IgnorePropertyForReqIF(rHTMLWrt.mbReqIF, "font-family"))
+    if (IgnorePropertyForReqIF(rHTMLWrt.mbReqIF, "font-family", OString()))
     {
         return rWrt;
     }
@@ -2733,7 +2733,7 @@ static Writer& OutHTML_SvxFontHeight( Writer& rWrt, const SfxPoolItem& rHt )
     if( rHTMLWrt.m_bOutOpts )
         return rWrt;
 
-    if (IgnorePropertyForReqIF(rHTMLWrt.mbReqIF, "font-size"))
+    if (IgnorePropertyForReqIF(rHTMLWrt.mbReqIF, "font-size", OString()))
     {
         return rWrt;
     }
diff --git a/sw/source/filter/html/wrthtml.hxx b/sw/source/filter/html/wrthtml.hxx
index fe50f51498d3..ffc416f22662 100644
--- a/sw/source/filter/html/wrthtml.hxx
+++ b/sw/source/filter/html/wrthtml.hxx
@@ -696,7 +696,9 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt,
 Writer& OutCSS1_SvxBox( Writer& rWrt, const SfxPoolItem& rHt );
 
 OString GetCSS1_Color(const Color& rColor);
-bool IgnorePropertyForReqIF(bool bReqIF, const OString& rProperty);
+
+/// Determines if rProperty with a given rValue has to be suppressed due to ReqIF mode.
+bool IgnorePropertyForReqIF(bool bReqIF, const OString& rProperty, const OString& rValue);
 
 #endif // INCLUDED_SW_SOURCE_FILTER_HTML_WRTHTML_HXX
 
commit e6d433e2e58c14f116ee6434ca74f29d14ab4b21
Author:     Miklos Vajna <vmiklos at collabora.com>
AuthorDate: Fri Jul 17 09:23:16 2020 +0200
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Sep 10 12:21:37 2020 +0200

    SVG export: fix lost semi-transparent text on shapes
    
    Extend SVGTextWriter::setTextPosition(), so when it looks for a text
    action in a metafile, it recurses into transparency groups, so the text
    is not lost.
    
    Extract part of SVGActionWriter::ImplWriteMask() into a new StartMask(),
    so we can detect the case when the transparency group has a constant
    alpha, i.e. no complex mask is needed, just an opacity value.
    
    When looking for text, remember if we saw a request for text opacity and
    make the transparency group writing in SVGActionWriter::ImplWriteMask()
    conditional to avoid duplication. This is needed because once we're
    inside <text>, we don't want to write an invalid transparency group via
    <g>, rather we want a fill-opacity on the existing <tspan>.
    
    With this, the SVG export is on par with PDF export for semi-transparent
    shape text.
    
    (cherry picked from commit 666f252457bdb4371d15380a0289e107b2dfbe84)
    
    Change-Id: If43b0ab3446015299acc4b37590358867c5fac5f

diff --git a/filter/qa/unit/svg.cxx b/filter/qa/unit/svg.cxx
index 4589b417a0c2..5dcb1af0eb90 100644
--- a/filter/qa/unit/svg.cxx
+++ b/filter/qa/unit/svg.cxx
@@ -146,12 +146,18 @@ CPPUNIT_TEST_FIXTURE(SvgFilterTest, testSemiTransparentText)
 
     // We expect 2 groups of class "com.sun.star.drawing.TextShape" that
     // have some svg:text node inside.
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 2
+    // - Actual  : 1
+    // i.e. the 2nd shape lots its text.
 
-    // TODO: fix the bug
+    assertXPath(pXmlDoc, "//svg:g[@class='com.sun.star.drawing.TextShape']//svg:text", 2);
 
-    // assertXPath(pXmlDoc, "//svg:g[@class='com.sun.star.drawing.TextShape']//svg:text", 2);
+    // First shape has semi-transparent text.
+    assertXPath(pXmlDoc, "//svg:text[1]/svg:tspan/svg:tspan/svg:tspan[@fill-opacity='0.8']");
 
-    // TODO: assert we the text has correctly transparent text (20%)
+    // Second shape has normal text.
+    assertXPath(pXmlDoc, "//svg:text[2]/svg:tspan/svg:tspan/svg:tspan[@fill-opacity]", 0);
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx
index c4337927846e..3e54df31ba49 100644
--- a/filter/source/svg/svgwriter.cxx
+++ b/filter/source/svg/svgwriter.cxx
@@ -453,9 +453,11 @@ void SVGAttributeWriter::setFontFamily()
     }
 }
 
-SVGTextWriter::SVGTextWriter( SVGExport& rExport,  SVGAttributeWriter& rAttributeWriter  )
+SVGTextWriter::SVGTextWriter(SVGExport& rExport, SVGAttributeWriter& rAttributeWriter,
+                             SVGActionWriter& rActionWriter)
 : mrExport( rExport ),
   mrAttributeWriter( rAttributeWriter ),
+  mrActionWriter(rActionWriter),
   mpVDev( nullptr ),
   mbIsTextShapeStarted( false ),
   mrTextShape(),
@@ -591,7 +593,8 @@ bool SVGTextWriter::implGetTextPositionFromBitmap( const MetaAction* pAction, Po
  *     0 if no text found and end of text shape is reached
  *     1 if text found!
  */
-sal_Int32 SVGTextWriter::setTextPosition( const GDIMetaFile& rMtf, sal_uLong& nCurAction )
+sal_Int32 SVGTextWriter::setTextPosition(const GDIMetaFile& rMtf, sal_uLong& nCurAction,
+                                         sal_uInt32 nWriteFlags)
 {
     Point aPos;
     sal_uLong nCount = rMtf.GetActionSize();
@@ -627,6 +630,22 @@ sal_Int32 SVGTextWriter::setTextPosition( const GDIMetaFile& rMtf, sal_uLong& nC
             }
             break;
 
+            case MetaActionType::FLOATTRANSPARENT:
+            {
+                const MetaFloatTransparentAction* pA
+                    = static_cast<const MetaFloatTransparentAction*>(pAction);
+                GDIMetaFile aTmpMtf(pA->GetGDIMetaFile());
+                sal_uLong nTmpAction = 0;
+                if (setTextPosition(aTmpMtf, nTmpAction, nWriteFlags) == 1)
+                {
+                    // Text is found in the inner metafile.
+                    bConfigured = true;
+                    mrActionWriter.StartMask(pA->GetPoint(), pA->GetSize(), pA->GetGradient(),
+                                             nWriteFlags, &maTextOpacity);
+                }
+            }
+            break;
+
             case MetaActionType::STRETCHTEXT:
             {
                 bConfigured = implGetTextPosition<MetaStretchTextAction>( pAction, aPos, bEmpty );
@@ -1252,6 +1271,7 @@ void SVGTextWriter::endTextShape()
     mrParagraphEnumeration.clear();
     mrCurrentTextParagraph.clear();
     mpTextShapeElem.reset();
+    maTextOpacity.clear();
     mbIsTextShapeStarted = false;
     // these need to be invoked after the <text> element has been closed
     implExportHyperlinkIds();
@@ -1328,6 +1348,7 @@ void SVGTextWriter::endTextPosition()
     mpTextPositionElem.reset();
 }
 
+bool SVGTextWriter::hasTextOpacity() { return !maTextOpacity.isEmpty(); }
 
 void SVGTextWriter::implExportHyperlinkIds()
 {
@@ -1665,6 +1686,11 @@ void SVGTextWriter::implWriteTextPortion( const Point& rPos,
 
     addFontAttributes( /* isTexTContainer: */ false );
 
+    if (!maTextOpacity.isEmpty())
+    {
+        mrExport.AddAttribute(XML_NAMESPACE_NONE, "fill-opacity", maTextOpacity);
+    }
+
     mrAttributeWriter.AddPaintAttr( COL_TRANSPARENT, aTextColor );
 
     // <a> tag for link should be the innermost tag, inside <tspan>
@@ -1700,7 +1726,7 @@ SVGActionWriter::SVGActionWriter( SVGExport& rExport, SVGFontExport& rFontExport
     maContextHandler(),
     mrCurrentState( maContextHandler.getCurrentState() ),
     maAttributeWriter( rExport, rFontExport, mrCurrentState ),
-    maTextWriter( rExport, maAttributeWriter ),
+    maTextWriter(rExport, maAttributeWriter, *this),
     mpVDev(VclPtr<VirtualDevice>::Create()),
     mbClipAttrChanged( false ),
     mbIsPlaceholderShape( false )
@@ -2356,39 +2382,26 @@ Color SVGActionWriter::ImplGetGradientColor( const Color& rStartColor,
     return Color( static_cast<sal_uInt8>(nNewRed), static_cast<sal_uInt8>(nNewGreen), static_cast<sal_uInt8>(nNewBlue) );
 }
 
-
-void SVGActionWriter::ImplWriteMask( GDIMetaFile& rMtf,
-                                     const Point& rDestPt,
-                                     const Size& rDestSize,
-                                     const Gradient& rGradient,
-                                     sal_uInt32 nWriteFlags )
+void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize,
+                                const Gradient& rGradient, sal_uInt32 nWriteFlags,
+                                OUString* pTextFillOpacity)
 {
-    Point          aSrcPt( rMtf.GetPrefMapMode().GetOrigin() );
-    const Size     aSrcSize( rMtf.GetPrefSize() );
-    const double   fScaleX = aSrcSize.Width() ? static_cast<double>(rDestSize.Width()) / aSrcSize.Width() : 1.0;
-    const double   fScaleY = aSrcSize.Height() ? static_cast<double>(rDestSize.Height()) / aSrcSize.Height() : 1.0;
-    long           nMoveX, nMoveY;
-
-    if( fScaleX != 1.0 || fScaleY != 1.0 )
-    {
-        rMtf.Scale( fScaleX, fScaleY );
-        aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) );
-        aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) );
-    }
-
-    nMoveX = rDestPt.X() - aSrcPt.X();
-    nMoveY = rDestPt.Y() - aSrcPt.Y();
-
-    if( nMoveX || nMoveY )
-        rMtf.Move( nMoveX, nMoveY );
-
     OUString aStyle;
     if (rGradient.GetStartColor() == rGradient.GetEndColor())
     {
         // Special case: constant alpha value.
         const Color& rColor = rGradient.GetStartColor();
         const double fOpacity = 1.0 - static_cast<double>(rColor.GetLuminance()) / 255;
-        aStyle = "opacity: " + OUString::number(fOpacity);
+        if (pTextFillOpacity)
+        {
+            // Don't write anything, return what is a value suitable for <tspan fill-opacity="...">.
+            *pTextFillOpacity = OUString::number(fOpacity);
+            return;
+        }
+        else
+        {
+            aStyle = "opacity: " + OUString::number(fOpacity);
+        }
     }
     else
     {
@@ -2419,9 +2432,40 @@ void SVGActionWriter::ImplWriteMask( GDIMetaFile& rMtf,
         aStyle = "mask:url(#" + aMaskId + ")";
     }
     mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStyle, aStyle);
+}
+
+void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize,
+                                    const Gradient& rGradient, sal_uInt32 nWriteFlags)
+{
+    Point aSrcPt(rMtf.GetPrefMapMode().GetOrigin());
+    const Size aSrcSize(rMtf.GetPrefSize());
+    const double fScaleX
+        = aSrcSize.Width() ? static_cast<double>(rDestSize.Width()) / aSrcSize.Width() : 1.0;
+    const double fScaleY
+        = aSrcSize.Height() ? static_cast<double>(rDestSize.Height()) / aSrcSize.Height() : 1.0;
+    long nMoveX, nMoveY;
 
+    if (fScaleX != 1.0 || fScaleY != 1.0)
     {
-        SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+        rMtf.Scale(fScaleX, fScaleY);
+        aSrcPt.setX(FRound(aSrcPt.X() * fScaleX));
+        aSrcPt.setY(FRound(aSrcPt.Y() * fScaleY));
+    }
+
+    nMoveX = rDestPt.X() - aSrcPt.X();
+    nMoveY = rDestPt.Y() - aSrcPt.Y();
+
+    if (nMoveX || nMoveY)
+        rMtf.Move(nMoveX, nMoveY);
+
+    {
+        std::unique_ptr<SvXMLElementExport> pElemG;
+        if (!maTextWriter.hasTextOpacity())
+        {
+            StartMask(rDestPt, rDestSize, rGradient, nWriteFlags);
+            pElemG.reset(
+                new SvXMLElementExport(mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true));
+        }
 
         mpVDev->Push();
         ImplWriteActions( rMtf, nWriteFlags, nullptr );
@@ -3403,7 +3447,8 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf,
                             sal_Int32 nTextFound = -1;
                             while( ( nTextFound < 0 ) && ( nCurAction < nCount ) )
                             {
-                                nTextFound = maTextWriter.setTextPosition( rMtf, nCurAction );
+                                nTextFound
+                                    = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
                             }
                             // We found some text in the current text shape.
                             if( nTextFound > 0 )
@@ -3438,7 +3483,8 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf,
                             sal_Int32 nTextFound = -1;
                             while( ( nTextFound < 0 ) && ( nCurAction < nCount ) )
                             {
-                                nTextFound = maTextWriter.setTextPosition( rMtf, nCurAction );
+                                nTextFound
+                                    = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
                             }
                             // We found a paragraph with some text in the
                             // current text shape.
@@ -3471,7 +3517,8 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf,
                             sal_Int32 nTextFound = -2;
                             while( ( nTextFound < -1 ) && ( nCurAction < nCount ) )
                             {
-                                nTextFound = maTextWriter.setTextPosition( rMtf, nCurAction );
+                                nTextFound
+                                    = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
                             }
                             // We found a line with some text in the current
                             // paragraph.
diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx
index 065d0585bb88..adfb10bf55d7 100644
--- a/filter/source/svg/svgwriter.hxx
+++ b/filter/source/svg/svgwriter.hxx
@@ -202,6 +202,7 @@ class SVGTextWriter final
   private:
     SVGExport&                                  mrExport;
     SVGAttributeWriter&                         mrAttributeWriter;
+    SVGActionWriter& mrActionWriter;
     VclPtr<VirtualDevice>                       mpVDev;
     bool                                        mbIsTextShapeStarted;
     Reference<XText>                            mrTextShape;
@@ -215,6 +216,7 @@ class SVGTextWriter final
     std::unique_ptr<SvXMLElementExport>         mpTextShapeElem;
     std::unique_ptr<SvXMLElementExport>         mpTextParagraphElem;
     std::unique_ptr<SvXMLElementExport>         mpTextPositionElem;
+    OUString maTextOpacity;
     sal_Int32                                   mnLeftTextPortionLength;
     Point                                       maTextPos;
     long int                                    mnTextWidth;
@@ -234,10 +236,12 @@ class SVGTextWriter final
     vcl::Font                                   maParentFont;
 
   public:
-    explicit SVGTextWriter( SVGExport& rExport, SVGAttributeWriter& rAttributeWriter );
+    explicit SVGTextWriter(SVGExport& rExport, SVGAttributeWriter& rAttributeWriter,
+            SVGActionWriter& mrActionWriter);
     ~SVGTextWriter();
 
-    sal_Int32 setTextPosition( const GDIMetaFile& rMtf, sal_uLong& nCurAction );
+    sal_Int32 setTextPosition(const GDIMetaFile& rMtf, sal_uLong& nCurAction,
+                              sal_uInt32 nWriteFlags);
     void setTextProperties( const GDIMetaFile& rMtf, sal_uLong nCurAction );
     void addFontAttributes( bool bIsTextContainer );
 
@@ -252,6 +256,7 @@ class SVGTextWriter final
     void endTextParagraph();
     void startTextPosition( bool bExportX = true, bool bExportY = true);
     void endTextPosition();
+    bool hasTextOpacity();
     void implExportHyperlinkIds();
     void implWriteBulletChars();
     template< typename MetaBitmapActionType >
@@ -366,6 +371,8 @@ public:
                                            const OUString* pElementId = nullptr,
                                            const Reference< css::drawing::XShape >* pXShape = nullptr,
                                            const GDIMetaFile* pTextEmbeddedBitmapMtf = nullptr );
+    void StartMask(const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient,
+                   sal_uInt32 nWriteFlags, OUString* pTextStyle = nullptr);
 };
 
 
commit 1fdd21015a1c98162fadc8d10549e7520c85cd0f
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Mon Jul 13 12:16:48 2020 +0200
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Sep 10 12:21:27 2020 +0200

    Prepare test for SVG export of semi-transparent text, not enabled
    
    This prepares the test for semi-transparent text, but the assert
    is not yet enabled until the bug gets fixed.
    
    (cherry picked from commit ebb7cd91ec2bbbba3e4d2ce106b24933b23f4d14)
    
    Change-Id: I31a241910fd7bdf27579f291a497b76292eac775

diff --git a/filter/qa/unit/data/TransparentText.odg b/filter/qa/unit/data/TransparentText.odg
new file mode 100644
index 000000000000..d3027d17d657
Binary files /dev/null and b/filter/qa/unit/data/TransparentText.odg differ
diff --git a/filter/qa/unit/svg.cxx b/filter/qa/unit/svg.cxx
index 9651881826c9..4589b417a0c2 100644
--- a/filter/qa/unit/svg.cxx
+++ b/filter/qa/unit/svg.cxx
@@ -122,6 +122,38 @@ CPPUNIT_TEST_FIXTURE(SvgFilterTest, testSemiTransparentLine)
     CPPUNIT_ASSERT_EQUAL(30, nPercent);
 }
 
+CPPUNIT_TEST_FIXTURE(SvgFilterTest, testSemiTransparentText)
+{
+    // Two shapes, one with transparent text and the other one with
+    // opaque text. We expect both to be exported to the SVG with the
+    // correct transparency factor applied for the first shape.
+
+    // Load draw document with transparent text in one box
+    load("TransparentText.odg");
+
+    // Export to SVG.
+    uno::Reference<frame::XStorable> xStorable(getComponent(), uno::UNO_QUERY_THROW);
+
+    SvMemoryStream aStream;
+    uno::Reference<io::XOutputStream> xOut = new utl::OOutputStreamWrapper(aStream);
+    utl::MediaDescriptor aMediaDescriptor;
+    aMediaDescriptor["FilterName"] <<= OUString("draw_svg_Export");
+    aMediaDescriptor["OutputStream"] <<= xOut;
+    xStorable->storeToURL("private:stream", aMediaDescriptor.getAsConstPropertyValueList());
+    aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+    xmlDocUniquePtr pXmlDoc = parseXmlStream(&aStream);
+
+    // We expect 2 groups of class "com.sun.star.drawing.TextShape" that
+    // have some svg:text node inside.
+
+    // TODO: fix the bug
+
+    // assertXPath(pXmlDoc, "//svg:g[@class='com.sun.star.drawing.TextShape']//svg:text", 2);
+
+    // TODO: assert we the text has correctly transparent text (20%)
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list