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 ++- 4 files changed, 127 insertions(+), 35 deletions(-)
New commits: commit a16d6e2130f5a4915fd1aca629805f2682e86803 Author: Miklos Vajna <[email protected]> AuthorDate: Fri Jul 17 09:23:16 2020 +0200 Commit: Miklos Vajna <[email protected]> CommitDate: Fri Jul 17 17:31:03 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) Conflicts: filter/source/svg/svgwriter.cxx filter/source/svg/svgwriter.hxx Change-Id: If43b0ab3446015299acc4b37590358867c5fac5f diff --git a/filter/qa/unit/svg.cxx b/filter/qa/unit/svg.cxx index f856cf01d257..2f52377e92bc 100644 --- a/filter/qa/unit/svg.cxx +++ b/filter/qa/unit/svg.cxx @@ -114,16 +114,21 @@ CPPUNIT_TEST_FIXTURE(SvgFilterTest, testSemiTransparentText) aStream.Seek(STREAM_SEEK_TO_BEGIN); xmlDocPtr pXmlDoc = parseXmlStream(&aStream); - (void)pXmlDoc; // 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 148b1ca42bb2..abb6877e8902 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -440,9 +440,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(), @@ -581,7 +583,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(); @@ -617,6 +620,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 ); @@ -1258,6 +1277,7 @@ void SVGTextWriter::endTextShape() delete mpTextShapeElem; mpTextShapeElem = nullptr; } + maTextOpacity.clear(); mbIsTextShapeStarted = false; // these need to be invoked after the <text> element has been closed implExportHyperlinkIds(); @@ -1344,6 +1364,7 @@ void SVGTextWriter::endTextPosition() } } +bool SVGTextWriter::hasTextOpacity() { return !maTextOpacity.isEmpty(); } void SVGTextWriter::implExportHyperlinkIds() { @@ -1692,6 +1713,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> @@ -1727,7 +1753,7 @@ SVGActionWriter::SVGActionWriter( SVGExport& rExport, SVGFontExport& rFontExport maContextHandler(), mrCurrentState( maContextHandler.getCurrentState() ), maAttributeWriter( rExport, rFontExport, mrCurrentState ), - maTextWriter( rExport, maAttributeWriter ), + maTextWriter( rExport, maAttributeWriter, *this ), mbClipAttrChanged( false ), mbIsPlaceholderShape( false ) { @@ -2382,39 +2408,26 @@ Color SVGActionWriter::ImplGetGradientColor( const Color& rStartColor, return Color( (sal_uInt8)nNewRed, (sal_uInt8)nNewGreen, (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() ? (double) rDestSize.Width() / aSrcSize.Width() : 1.0; - const double fScaleY = aSrcSize.Height() ? (double) rDestSize.Height() / aSrcSize.Height() : 1.0; - long nMoveX, nMoveY; - - if( fScaleX != 1.0 || fScaleY != 1.0 ) - { - rMtf.Scale( fScaleX, fScaleY ); - aSrcPt.X() = FRound( aSrcPt.X() * fScaleX ); - aSrcPt.Y() = 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 { @@ -2445,9 +2458,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 ); @@ -3372,7 +3416,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 ) @@ -3407,7 +3452,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. @@ -3440,7 +3486,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 46fced788f72..197c2ff98b93 100644 --- a/filter/source/svg/svgwriter.hxx +++ b/filter/source/svg/svgwriter.hxx @@ -222,6 +222,7 @@ class SVGTextWriter final private: SVGExport& mrExport; SVGAttributeWriter& mrAttributeWriter; + SVGActionWriter& mrActionWriter; VclPtr<VirtualDevice> mpVDev; bool mbIsTextShapeStarted; Reference<XText> mrTextShape; @@ -235,6 +236,7 @@ class SVGTextWriter final SvXMLElementExport* mpTextShapeElem; SvXMLElementExport* mpTextParagraphElem; SvXMLElementExport* mpTextPositionElem; + OUString maTextOpacity; sal_Int32 mnLeftTextPortionLength; Point maTextPos; long int mnTextWidth; @@ -254,10 +256,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 ); @@ -272,6 +276,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 > @@ -386,6 +391,8 @@ public: const OUString* pElementId = nullptr, const Reference< 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 9a35a05b23251f86267eb73b5e67fd05a0d75925 Author: Tomaž Vajngerl <[email protected]> AuthorDate: Mon Jul 13 12:16:48 2020 +0200 Commit: Miklos Vajna <[email protected]> CommitDate: Fri Jul 17 10:35:02 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 ee582d52e982..f856cf01d257 100644 --- a/filter/qa/unit/svg.cxx +++ b/filter/qa/unit/svg.cxx @@ -93,6 +93,39 @@ 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); + + xmlDocPtr pXmlDoc = parseXmlStream(&aStream); + (void)pXmlDoc; + + // 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
