summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiklos Vajna <vmiklos@collabora.com>2020-07-17 09:23:16 +0200
committerMiklos Vajna <vmiklos@collabora.com>2020-09-10 12:21:37 +0200
commite6d433e2e58c14f116ee6434ca74f29d14ab4b21 (patch)
treea893bb846cca7a1f246c55ed749b59dca7f389fb
parent1fdd21015a1c98162fadc8d10549e7520c85cd0f (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.cxx12
-rw-r--r--filter/source/svg/svgwriter.cxx113
-rw-r--r--filter/source/svg/svgwriter.hxx11
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);
};