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 <[email protected]> AuthorDate: Wed Sep 2 17:44:19 2020 +0200 Commit: Miklos Vajna <[email protected]> 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 <[email protected]> AuthorDate: Tue Sep 1 13:36:48 2020 +0200 Commit: Miklos Vajna <[email protected]> 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 <[email protected]> AuthorDate: Fri Aug 28 12:00:11 2020 +0300 Commit: Miklos Vajna <[email protected]> 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 <[email protected]> AuthorDate: Tue Aug 25 13:48:32 2020 +0300 Commit: Miklos Vajna <[email protected]> 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 <[email protected]> AuthorDate: Thu Aug 20 22:58:24 2020 +0300 Commit: Miklos Vajna <[email protected]> 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 <[email protected]> AuthorDate: Wed Jul 29 10:23:06 2020 +0200 Commit: Miklos Vajna <[email protected]> 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 <[email protected]> AuthorDate: Fri Jul 17 09:23:16 2020 +0200 Commit: Miklos Vajna <[email protected]> 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 <[email protected]> AuthorDate: Mon Jul 13 12:16:48 2020 +0200 Commit: Miklos Vajna <[email protected]> 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: */ _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
