tdf#55058 tdf#143875 EMF+ Fix display of dashed lines and line joints
With previous implementation, empty spaces between dashes
were too long.
Additionally line joints were not working correctly, after
EMF+ reworking: tdf#111486
This commit fixes all these issues and additionally it is
covering it with tests.
Change-Id: I9404e566d2d7d3405ab817268ad9b1f538c200eb
Change-Id: I523f92a928ab592ff175d0d01c1ad1a3bc22e324
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133207
Tested-by: Jenkins
Reviewed-by: Bartosz Kosiorek <gang65@poczta.onet.pl>
diff --git a/compilerplugins/clang/redundantfcast.cxx b/compilerplugins/clang/redundantfcast.cxx
index be9565c..1378d9e 100644
--- a/compilerplugins/clang/redundantfcast.cxx
+++ b/compilerplugins/clang/redundantfcast.cxx
@@ -335,6 +335,9 @@ public:
// tdf#145203: FIREBIRD cannot create a table
if (fn == SRCDIR "/connectivity/source/drivers/firebird/DatabaseMetaData.cxx")
return false;
// false positive during using contructor drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw })
if (fn == SRCDIR "/drawinglayer/source/tools/emfppen.cxx")
return false;
return true;
}
diff --git a/drawinglayer/source/tools/emfpcustomlinecap.cxx b/drawinglayer/source/tools/emfpcustomlinecap.cxx
index e24cbcc..49cc912 100644
--- a/drawinglayer/source/tools/emfpcustomlinecap.cxx
+++ b/drawinglayer/source/tools/emfpcustomlinecap.cxx
@@ -43,15 +43,6 @@ namespace emfplushelper
{
}
void EMFPCustomLineCap::SetAttributes(rendering::StrokeAttributes& aAttributes)
{
aAttributes.StartCapType = EMFPPen::lcl_convertStrokeCap(strokeStartCap);
aAttributes.EndCapType = EMFPPen::lcl_convertStrokeCap(strokeEndCap);
aAttributes.JoinType = EMFPPen::lcl_convertLineJoinType(strokeJoin);
aAttributes.MiterLimit = miterLimit;
}
void EMFPCustomLineCap::ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill)
{
sal_Int32 pathLength;
diff --git a/drawinglayer/source/tools/emfpcustomlinecap.hxx b/drawinglayer/source/tools/emfpcustomlinecap.hxx
index a42e0ab..e6202ae 100644
--- a/drawinglayer/source/tools/emfpcustomlinecap.hxx
+++ b/drawinglayer/source/tools/emfpcustomlinecap.hxx
@@ -19,7 +19,6 @@
#pragma once
#include <com/sun/star/rendering/StrokeAttributes.hpp>
#include "emfphelperdata.hxx"
namespace emfplushelper
@@ -34,7 +33,6 @@ namespace emfplushelper
EMFPCustomLineCap();
void SetAttributes(com::sun::star::rendering::StrokeAttributes& aAttributes);
void ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill);
void Read(SvStream& s, EmfPlusHelperData const & rR);
};
diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx
index e60f881..84d848e 100644
--- a/drawinglayer/source/tools/emfphelperdata.cxx
+++ b/drawinglayer/source/tools/emfphelperdata.cxx
@@ -523,13 +523,6 @@ namespace emfplushelper
if (!(pen && polygon.count()))
return;
// we need a line join attribute
basegfx::B2DLineJoin lineJoin = basegfx::B2DLineJoin::Round;
if (pen->penDataFlags & EmfPlusPenDataJoin) // additional line join information
{
lineJoin = static_cast<basegfx::B2DLineJoin>(EMFPPen::lcl_convertLineJoinType(pen->lineJoin));
}
// we need a line cap attribute
css::drawing::LineCap lineCap = css::drawing::LineCap_BUTT;
if (pen->penDataFlags & EmfPlusPenDataStartCap) // additional line cap information
@@ -541,57 +534,16 @@ namespace emfplushelper
const double transformedPenWidth = maMapTransform.get(0, 0) * pen->penWidth;
drawinglayer::attribute::LineAttribute lineAttribute(pen->GetColor().getBColor(),
transformedPenWidth,
lineJoin,
lineCap);
drawinglayer::attribute::StrokeAttribute aStrokeAttribute;
if (pen->penDataFlags & EmfPlusPenDataLineStyle && pen->dashStyle != EmfPlusLineStyleCustom) // pen has a predefined line style
{
// short writing
const double pw = maMapTransform.get(1, 1) * pen->penWidth;
// taken from the old cppcanvas implementation and multiplied with pen width
const std::vector<double> dash = { 3*pw, 3*pw };
const std::vector<double> dot = { pw, 3*pw };
const std::vector<double> dashdot = { 3*pw, 3*pw, pw, 3*pw };
const std::vector<double> dashdotdot = { 3*pw, 3*pw, pw, 3*pw, pw, 3*pw };
switch (pen->dashStyle)
{
case EmfPlusLineStyleSolid: // do nothing special, use default stroke attribute
break;
case EmfPlusLineStyleDash:
aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dash));
break;
case EmfPlusLineStyleDot:
aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dot));
break;
case EmfPlusLineStyleDashDot:
aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dashdot));
break;
case EmfPlusLineStyleDashDotDot:
aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dashdotdot));
break;
}
}
else if (pen->penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line
{
// StrokeAttribute needs a double vector while the pen provides a float vector
std::vector<double> aPattern(pen->dashPattern.size());
for (size_t i=0; i<aPattern.size(); i++)
{
// convert from float to double and multiply with the adjusted pen width
aPattern[i] = maMapTransform.get(1, 1) * pen->penWidth * pen->dashPattern[i];
}
aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::move(aPattern));
}
pen->GetLineJoinType(),
lineCap,
basegfx::deg2rad(15.0)); // TODO Add MiterLimit support
if (!pen->GetColor().IsTransparent())
{
mrTargetHolders.Current().append(
new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
polygon,
lineAttribute,
aStrokeAttribute));
pen->GetStrokeAttribute(maMapTransform.get(1, 1))));
}
else
{
@@ -599,7 +551,7 @@ namespace emfplushelper
new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
polygon,
lineAttribute,
aStrokeAttribute));
pen->GetStrokeAttribute(maMapTransform.get(1, 1))));
mrTargetHolders.Current().append(
new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
@@ -648,7 +600,7 @@ namespace emfplushelper
new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
startCapPolygon,
lineAttribute,
aStrokeAttribute));
pen->GetStrokeAttribute(maMapTransform.get(1, 1))));
}
}
@@ -693,7 +645,7 @@ namespace emfplushelper
new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
endCapPolygon,
lineAttribute,
aStrokeAttribute));
pen->GetStrokeAttribute(maMapTransform.get(1, 1))));
}
}
diff --git a/drawinglayer/source/tools/emfppen.cxx b/drawinglayer/source/tools/emfppen.cxx
index d41dca6..f348391 100644
--- a/drawinglayer/source/tools/emfppen.cxx
+++ b/drawinglayer/source/tools/emfppen.cxx
@@ -18,7 +18,6 @@
*/
#include <com/sun/star/rendering/PathCapType.hpp>
#include <com/sun/star/rendering/PathJoinType.hpp>
#include <o3tl/safeint.hxx>
#include <sal/log.hxx>
#include <rtl/ustrbuf.hxx>
@@ -179,27 +178,70 @@ namespace emfplushelper
{
switch (nEmfStroke)
{
case EmfPlusLineCapTypeSquare: return rendering::PathCapType::SQUARE;
case EmfPlusLineCapTypeRound: return rendering::PathCapType::ROUND;
case EmfPlusLineCapTypeSquare:
return rendering::PathCapType::SQUARE;
// we have no mapping for EmfPlusLineCapTypeTriangle,
// but it is similar to Round
case EmfPlusLineCapTypeTriangle: // fall-through
case EmfPlusLineCapTypeRound:
return rendering::PathCapType::ROUND;
}
// we have no mapping for EmfPlusLineCapTypeTriangle = 0x00000003,
// so return BUTT always
return rendering::PathCapType::BUTT;
}
sal_Int8 EMFPPen::lcl_convertLineJoinType(sal_uInt32 nEmfLineJoin)
basegfx::B2DLineJoin EMFPPen::GetLineJoinType() const
{
switch (nEmfLineJoin)
if (penDataFlags & EmfPlusPenDataJoin) // additional line join information
{
case EmfPlusLineJoinTypeMiter: // fall-through
case EmfPlusLineJoinTypeMiterClipped: return rendering::PathJoinType::MITER;
case EmfPlusLineJoinTypeBevel: return rendering::PathJoinType::BEVEL;
case EmfPlusLineJoinTypeRound: return rendering::PathJoinType::ROUND;
switch (lineJoin)
{
case EmfPlusLineJoinTypeMiter: // fall-through
case EmfPlusLineJoinTypeMiterClipped:
return basegfx::B2DLineJoin::Miter;
case EmfPlusLineJoinTypeBevel:
return basegfx::B2DLineJoin::Bevel;
case EmfPlusLineJoinTypeRound:
return basegfx::B2DLineJoin::Round;
}
}
// If nothing set, then miter applied with no limit
return basegfx::B2DLineJoin::Miter;
}
assert(false); // Line Join type isn't in specification.
return 0;
drawinglayer::attribute::StrokeAttribute
EMFPPen::GetStrokeAttribute(const double aTransformation) const
{
if (penDataFlags & EmfPlusPenDataLineStyle // pen has a predefined line style
&& dashStyle != EmfPlusLineStyleCustom)
{
const double pw = aTransformation * penWidth;
switch (dashStyle)
{
case EmfPlusLineStyleDash:
return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw });
case EmfPlusLineStyleDot:
return drawinglayer::attribute::StrokeAttribute({ pw, pw });
case EmfPlusLineStyleDashDot:
return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw });
case EmfPlusLineStyleDashDotDot:
return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw, pw, pw });
}
}
else if (penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line
{
const double pw = aTransformation * penWidth;
// StrokeAttribute needs a double vector while the pen provides a float vector
std::vector<double> aPattern(dashPattern.size());
for (size_t i = 0; i < aPattern.size(); i++)
{
// convert from float to double and multiply with the adjusted pen width
aPattern[i] = pw * dashPattern[i];
}
return drawinglayer::attribute::StrokeAttribute(std::move(aPattern));
}
// EmfPlusLineStyleSolid: - do nothing special, use default stroke attribute
return drawinglayer::attribute::StrokeAttribute();
}
void EMFPPen::Read(SvStream& s, EmfPlusHelperData const & rR)
@@ -249,7 +291,7 @@ namespace emfplushelper
if (penDataFlags & PenDataJoin)
{
s.ReadInt32(lineJoin);
SAL_WARN("drawinglayer.emf", "EMF+\t\tTODO PenDataJoin: " << LineJoinTypeToString(lineJoin) << " (0x" << std::hex << lineJoin << ")");
SAL_WARN("drawinglayer.emf", "EMF+\t\t LineJoin: " << LineJoinTypeToString(lineJoin) << " (0x" << std::hex << lineJoin << ")");
}
else
{
diff --git a/drawinglayer/source/tools/emfppen.hxx b/drawinglayer/source/tools/emfppen.hxx
index 05b2fc3..29ece63 100644
--- a/drawinglayer/source/tools/emfppen.hxx
+++ b/drawinglayer/source/tools/emfppen.hxx
@@ -19,6 +19,7 @@
#pragma once
#include <drawinglayer/attribute/strokeattribute.hxx>
#include "emfpbrush.hxx"
#include <vector>
@@ -26,6 +27,7 @@ namespace emfplushelper
{
const sal_uInt32 EmfPlusLineCapTypeSquare = 0x00000001;
const sal_uInt32 EmfPlusLineCapTypeRound = 0x00000002;
const sal_uInt32 EmfPlusLineCapTypeTriangle = 0x00000003;
const sal_uInt32 EmfPlusLineJoinTypeMiter = 0x00000000;
const sal_uInt32 EmfPlusLineJoinTypeBevel = 0x00000001;
@@ -122,7 +124,8 @@ namespace emfplushelper
void Read(SvStream& s, EmfPlusHelperData const & rR);
static sal_Int8 lcl_convertStrokeCap(sal_uInt32 nEmfStroke);
static sal_Int8 lcl_convertLineJoinType(sal_uInt32 nEmfLineJoin);
drawinglayer::attribute::StrokeAttribute GetStrokeAttribute(const double aTransformation) const;
basegfx::B2DLineJoin GetLineJoinType() const;
};
}
diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx b/drawinglayer/source/tools/primitive2dxmldump.cxx
index 26ee634..6126449 100644
--- a/drawinglayer/source/tools/primitive2dxmldump.cxx
+++ b/drawinglayer/source/tools/primitive2dxmldump.cxx
@@ -13,6 +13,7 @@
#include <tools/stream.hxx>
#include <tools/XmlWriter.hxx>
#include <math.h>
#include <memory>
#include <sal/log.hxx>
@@ -132,6 +133,24 @@ void writePolyPolygon(::tools::XmlWriter& rWriter, const basegfx::B2DPolyPolygon
rWriter.endElement();
}
void writeStrokeAttribute(::tools::XmlWriter& rWriter,
const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute)
{
if (!rStrokeAttribute.getDotDashArray().empty())
{
rWriter.startElement("stroke");
OUString sDotDash;
for (double fDotDash : rStrokeAttribute.getDotDashArray())
{
sDotDash += OUString::number(round(100.0 * fDotDash)) + " ";
}
rWriter.attribute("dotDashArray", sDotDash);
rWriter.attribute("fullDotDashLength", rStrokeAttribute.getFullDotDashLen());
rWriter.endElement();
}
}
void writeLineAttribute(::tools::XmlWriter& rWriter,
const drawinglayer::attribute::LineAttribute& rLineAttribute)
{
@@ -715,14 +734,7 @@ void Primitive2dXmlDump::decomposeAndWrite(
rWriter.endElement();
writeLineAttribute(rWriter, rPolygonStrokePrimitive2D.getLineAttribute());
rWriter.startElement("stroke");
const drawinglayer::attribute::StrokeAttribute& aStrokeAttribute
= rPolygonStrokePrimitive2D.getStrokeAttribute();
rWriter.attribute("fulldotdashlen", aStrokeAttribute.getFullDotDashLen());
//rWriter.attribute("dotdasharray", aStrokeAttribute.getDotDashArray());
rWriter.endElement();
writeStrokeAttribute(rWriter, rPolygonStrokePrimitive2D.getStrokeAttribute());
rWriter.endElement();
}
break;
@@ -733,9 +745,7 @@ void Primitive2dXmlDump::decomposeAndWrite(
rWriter.startElement("polypolygonstroke");
writeLineAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getLineAttribute());
//getStrokeAttribute()
writeStrokeAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getStrokeAttribute());
writePolyPolygon(rWriter, rPolyPolygonStrokePrimitive2D.getB2DPolyPolygon());
rWriter.endElement();
diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index 0f28169..6c4785b 100644
--- a/emfio/qa/cppunit/emf/EmfImportTest.cxx
+++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx
@@ -51,6 +51,7 @@ class Test : public test::BootstrapFixture, public XmlTestTools, public unotest:
void TestDrawStringTransparent();
void TestDrawStringWithBrush();
void TestDrawLine();
void TestDrawLineWithDash();
void TestLinearGradient();
void TestTextMapMode();
void TestEnglishMapMode();
@@ -96,6 +97,7 @@ public:
CPPUNIT_TEST(TestDrawStringTransparent);
CPPUNIT_TEST(TestDrawStringWithBrush);
CPPUNIT_TEST(TestDrawLine);
CPPUNIT_TEST(TestDrawLineWithDash);
CPPUNIT_TEST(TestLinearGradient);
CPPUNIT_TEST(TestTextMapMode);
CPPUNIT_TEST(TestEnglishMapMode);
@@ -386,6 +388,37 @@ void Test::TestDrawLine()
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke/line", "width", "33");
}
void Test::TestDrawLineWithDash()
{
// EMF+ with records: DrawLine
// The lines with different dash styles
Primitive2DSequence aSequence
= parseEmf(u"/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf");
CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
drawinglayer::Primitive2dXmlDump dumper;
xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
CPPUNIT_ASSERT(pDocument);
// check correct import of the DrawLine: color and width of the line
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke", 10);
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "color", "#000000");
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "width", "132");
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/stroke", 0);
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "width", "132");
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/stroke", "dotDashArray",
"13225 13225 ");
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[3]/stroke", "dotDashArray",
"39674 13225 ");
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[4]/stroke", "dotDashArray",
"39674 13225 13225 13225 ");
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[5]/stroke", "dotDashArray",
"39674 13225 13225 13225 13225 13225 ");
//TODO polypolygonstroke[6-9]/stroke add support for PenDataDashedLineOffset
assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[10]/stroke", "dotDashArray",
"66124 26450 198372 52899 ");
}
void Test::TestLinearGradient()
{
// EMF+ file with LinearGradient brush
diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf
new file mode 100644
index 0000000..dc5af59
--- /dev/null
+++ b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf
Binary files differ
diff --git a/svx/qa/unit/svdraw.cxx b/svx/qa/unit/svdraw.cxx
index a063ef7..a730bb3 100644
--- a/svx/qa/unit/svdraw.cxx
+++ b/svx/qa/unit/svdraw.cxx
@@ -338,7 +338,8 @@ CPPUNIT_TEST_FIXTURE(SvdrawTest, testRectangleObject)
assertXPathContent(pXmlDoc, aBasePath + "/polygon", "49.5,99 0,99 0,0 99,0 99,99");
assertXPath(pXmlDoc, aBasePath + "/stroke", "fulldotdashlen", "0");
// If solid line, then there is no line stroke information
assertXPath(pXmlDoc, aBasePath + "/stroke", 0);
pPage->RemoveObject(0);