diff options
author | Miklos Vajna <vmiklos@collabora.com> | 2020-07-17 09:23:16 +0200 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2020-09-10 12:21:37 +0200 |
commit | e6d433e2e58c14f116ee6434ca74f29d14ab4b21 (patch) | |
tree | a893bb846cca7a1f246c55ed749b59dca7f389fb | |
parent | 1fdd21015a1c98162fadc8d10549e7520c85cd0f (diff) |
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
-rw-r--r-- | filter/qa/unit/svg.cxx | 12 | ||||
-rw-r--r-- | filter/source/svg/svgwriter.cxx | 113 | ||||
-rw-r--r-- | filter/source/svg/svgwriter.hxx | 11 |
3 files changed, 98 insertions, 38 deletions
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); }; |