SVG export: fix lost semi-transparent line shapes
The line shape itself didn't really have a height, rather it had a stroke. For some reason, the SVG mask then decides that nothing has to be painted there, so unless the line is entirely opaque, the line shape gets lost on export. Fix the problem by handling transparency similar to the PDF export, which detects if the whole purpose of the transparency gradient is to pass around a transparency percentage. We don't need a mask in that case, we can just use opacity as described at e.g. <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/opacity>. Change-Id: I0355b9b09b6dd48bbacc5b7cc54fb71866304ef1 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/91932 Reviewed-by: Miklos Vajna <vmiklos@collabora.com> Tested-by: Jenkins
This commit is contained in:
BIN
filter/qa/unit/data/semi-transparent-line.odg
Normal file
BIN
filter/qa/unit/data/semi-transparent-line.odg
Normal file
Binary file not shown.
@@ -20,9 +20,7 @@
|
|||||||
|
|
||||||
using namespace ::com::sun::star;
|
using namespace ::com::sun::star;
|
||||||
|
|
||||||
#if !defined MACOSX
|
|
||||||
char const DATA_DIRECTORY[] = "/filter/qa/unit/data/";
|
char const DATA_DIRECTORY[] = "/filter/qa/unit/data/";
|
||||||
#endif
|
|
||||||
|
|
||||||
/// SVG filter tests.
|
/// SVG filter tests.
|
||||||
class SvgFilterTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
|
class SvgFilterTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
|
||||||
@@ -34,10 +32,8 @@ public:
|
|||||||
void setUp() override;
|
void setUp() override;
|
||||||
void tearDown() override;
|
void tearDown() override;
|
||||||
void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override;
|
void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override;
|
||||||
#if !defined MACOSX
|
|
||||||
uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
|
uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
|
||||||
void load(const OUString& rURL);
|
void load(const OUString& rURL);
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void SvgFilterTest::setUp()
|
void SvgFilterTest::setUp()
|
||||||
@@ -55,13 +51,11 @@ void SvgFilterTest::tearDown()
|
|||||||
test::BootstrapFixture::tearDown();
|
test::BootstrapFixture::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined MACOSX
|
|
||||||
void SvgFilterTest::load(const OUString& rFileName)
|
void SvgFilterTest::load(const OUString& rFileName)
|
||||||
{
|
{
|
||||||
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFileName;
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFileName;
|
||||||
mxComponent = loadFromDesktop(aURL);
|
mxComponent = loadFromDesktop(aURL);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
void SvgFilterTest::registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx)
|
void SvgFilterTest::registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx)
|
||||||
{
|
{
|
||||||
@@ -100,6 +94,34 @@ CPPUNIT_TEST_FIXTURE(SvgFilterTest, testPreserveJpg)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CPPUNIT_TEST_FIXTURE(SvgFilterTest, testSemiTransparentLine)
|
||||||
|
{
|
||||||
|
// Load a document with a semi-transparent line shape.
|
||||||
|
load("semi-transparent-line.odg");
|
||||||
|
|
||||||
|
// Export it 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);
|
||||||
|
|
||||||
|
// Get the style of the group around the actual <path> element.
|
||||||
|
xmlDocPtr pXmlDoc = parseXmlStream(&aStream);
|
||||||
|
OUString aStyle = getXPath(
|
||||||
|
pXmlDoc, "//svg:g[@class='com.sun.star.drawing.LineShape']/svg:g/svg:g", "style");
|
||||||
|
OUString aPrefix("opacity: ");
|
||||||
|
// Without the accompanying fix in place, this test would have failed, as the style was
|
||||||
|
// "mask:url(#mask1)", not "opacity: <value>".
|
||||||
|
CPPUNIT_ASSERT(aStyle.startsWith(aPrefix));
|
||||||
|
int nPercent = std::round(aStyle.copy(aPrefix.getLength()).toDouble() * 100);
|
||||||
|
// Make sure that the line is still 30% opaque, rather than completely invisible.
|
||||||
|
CPPUNIT_ASSERT_EQUAL(30, nPercent);
|
||||||
|
}
|
||||||
|
|
||||||
CPPUNIT_PLUGIN_IMPLEMENT();
|
CPPUNIT_PLUGIN_IMPLEMENT();
|
||||||
|
|
||||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||||
|
@@ -2382,32 +2382,43 @@ void SVGActionWriter::ImplWriteMask( GDIMetaFile& rMtf,
|
|||||||
if( nMoveX || nMoveY )
|
if( nMoveX || nMoveY )
|
||||||
rMtf.Move( nMoveX, nMoveY );
|
rMtf.Move( nMoveX, nMoveY );
|
||||||
|
|
||||||
OUString aMaskId = "mask" + OUString::number( mnCurMaskId++ );
|
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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OUString aMaskId = "mask" + OUString::number(mnCurMaskId++);
|
||||||
|
|
||||||
{
|
{
|
||||||
SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
|
SvXMLElementExport aElemDefs(mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true);
|
||||||
|
|
||||||
mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aMaskId );
|
mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrId, aMaskId);
|
||||||
{
|
{
|
||||||
SvXMLElementExport aElemMask( mrExport, XML_NAMESPACE_NONE, "mask", true, true );
|
SvXMLElementExport aElemMask(mrExport, XML_NAMESPACE_NONE, "mask", true, true);
|
||||||
|
|
||||||
const tools::PolyPolygon aPolyPolygon( tools::PolyPolygon( tools::Rectangle( rDestPt, rDestSize ) ) );
|
const tools::PolyPolygon aPolyPolygon(tools::PolyPolygon(tools::Rectangle(rDestPt, rDestSize)));
|
||||||
Gradient aGradient( rGradient );
|
Gradient aGradient(rGradient);
|
||||||
|
|
||||||
// swap gradient stops to adopt SVG mask
|
// swap gradient stops to adopt SVG mask
|
||||||
Color aTmpColor( aGradient.GetStartColor() );
|
Color aTmpColor(aGradient.GetStartColor());
|
||||||
sal_uInt16 nTmpIntensity( aGradient.GetStartIntensity() );
|
sal_uInt16 nTmpIntensity(aGradient.GetStartIntensity());
|
||||||
aGradient.SetStartColor( aGradient.GetEndColor() );
|
aGradient.SetStartColor(aGradient.GetEndColor());
|
||||||
aGradient.SetStartIntensity( aGradient.GetEndIntensity() ) ;
|
aGradient.SetStartIntensity(aGradient.GetEndIntensity());
|
||||||
aGradient.SetEndColor( aTmpColor );
|
aGradient.SetEndColor(aTmpColor);
|
||||||
aGradient.SetEndIntensity( nTmpIntensity );
|
aGradient.SetEndIntensity(nTmpIntensity);
|
||||||
|
|
||||||
ImplWriteGradientEx( aPolyPolygon, aGradient, nWriteFlags );
|
ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OUString aMaskStyle = "mask:url(#" + aMaskId + ")";
|
aStyle = "mask:url(#" + aMaskId + ")";
|
||||||
mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aMaskStyle );
|
}
|
||||||
|
mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStyle, aStyle);
|
||||||
|
|
||||||
{
|
{
|
||||||
SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
|
SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <vcl/mtfxmldump.hxx>
|
#include <vcl/mtfxmldump.hxx>
|
||||||
|
#include <sal/log.hxx>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -53,7 +54,9 @@ xmlDocPtr XmlTestTools::parseXmlStream(SvStream* pStream)
|
|||||||
std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nSize + 1]);
|
std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nSize + 1]);
|
||||||
pStream->ReadBytes(pBuffer.get(), nSize);
|
pStream->ReadBytes(pBuffer.get(), nSize);
|
||||||
pBuffer[nSize] = 0;
|
pBuffer[nSize] = 0;
|
||||||
return xmlParseDoc(reinterpret_cast<xmlChar*>(pBuffer.get()));
|
auto pCharBuffer = reinterpret_cast<xmlChar*>(pBuffer.get());
|
||||||
|
SAL_INFO("test", "XmlTestTools::parseXmlStream: pBuffer is '" << pCharBuffer << "'");
|
||||||
|
return xmlParseDoc(pCharBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
xmlDocPtr XmlTestTools::dumpAndParse(MetafileXmlDump& rDumper, const GDIMetaFile& rGDIMetaFile)
|
xmlDocPtr XmlTestTools::dumpAndParse(MetafileXmlDump& rDumper, const GDIMetaFile& rGDIMetaFile)
|
||||||
|
Reference in New Issue
Block a user