emfio: implement EMR_SETPOLYFILLMODE and W_META_SETPOLYFILLMODE
The polygon fill mode (ALTERNATE vs WINDING) was ignored in both EMF and WMF readers. ALTERNATE (even-odd) is the default; WINDING (non-zero winding) appears in 2541/6939 EMF files and 135/467 WMF files that set the mode. Self-intersecting polygons like pentagram stars render differently under the two rules. Store the fill mode state in MtfTools with save/restore in Push/Pop, wire up the record handlers, and fix the hardcoded fillNonZero in DrawPolygon pattern fills to respect the current mode. Add a unit test with a pentagram star EMF using WINDING mode. Change-Id: I1d9127b79cc6c3b67f39bebe2c82e806c9db4ce8 Reviewed-on: https://gerrit.collaboraoffice.com/c/core/+/324 Tested-by: Andras Timar <andras.timar@collabora.com> Reviewed-by: Miklos Vajna <vmiklos@collabora.com> Signed-off-by: Xisco Fauli <xiscofauli@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/204388 Tested-by: Jenkins
This commit is contained in:
committed by
Xisco Fauli
parent
d8f87cb61f
commit
857fd268e8
+1
-1
@@ -63,7 +63,7 @@ Currently, these records are not implemented for WMF (specified in
|
||||
`wmfreader.cxx`):
|
||||
|
||||
```
|
||||
W_META_SETRELABS W_META_SETPOLYFILLMODE W_META_SETSTRETCHBLTMODE
|
||||
W_META_SETRELABS W_META_SETSTRETCHBLTMODE
|
||||
W_META_SETTEXTCHAREXTRA W_META_SETTEXTJUSTIFICATION W_META_FLOODFILL
|
||||
W_META_FILLREGION W_META_FRAMEREGION W_META_INVERTREGION
|
||||
W_META_PAINTREGION W_META_DRAWTEXT W_META_SETMAPPERFLAGS
|
||||
|
||||
@@ -593,6 +593,7 @@ namespace emfio
|
||||
|
||||
bool bClockWiseArcDirection;
|
||||
bool bFillStyleSelected;
|
||||
sal_uInt32 nPolyFillMode;
|
||||
};
|
||||
|
||||
struct BSaveStruct
|
||||
@@ -699,6 +700,7 @@ namespace emfio
|
||||
bool mbNopMode : 1;
|
||||
bool mbClockWiseArcDirection : 1;
|
||||
bool mbFillStyleSelected : 1;
|
||||
sal_uInt32 mnPolyFillMode; // ALTERNATE(1) or WINDING(2)
|
||||
bool mbClipNeedsUpdate : 1;
|
||||
bool mbComplexClip : 1;
|
||||
bool mbIsMapWinSet : 1;
|
||||
@@ -756,6 +758,7 @@ namespace emfio
|
||||
void SetGfxMode(GraphicsMode nGfxMode) { meGfxMode = nGfxMode; };
|
||||
GraphicsMode GetGfxMode() const { return meGfxMode; };
|
||||
void SetBkMode(BackgroundMode nMode);
|
||||
void SetPolyFillMode(sal_uInt32 nPolyFillMode);
|
||||
void SetBkColor(const Color& rColor);
|
||||
void SetTextColor(const Color& rColor);
|
||||
void SetTextAlign(sal_uInt32 nAlign);
|
||||
|
||||
@@ -683,13 +683,16 @@ CPPUNIT_TEST_FIXTURE(Test, testEnglishMapMode)
|
||||
assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "width", u"424");
|
||||
assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "height", u"424");
|
||||
|
||||
assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline", 5);
|
||||
// WINDING fill mode: fill and stroke are separated; strokes come from
|
||||
// MetaPolyLineAction instead of MetaPolygonAction, one per shape.
|
||||
assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline", 3);
|
||||
assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[1]/polygon",
|
||||
u"-1,-1 29699,-1 29699,21000 -1,21000");
|
||||
assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[2]/polygon",
|
||||
u"1058,7937 1058,13230 4233,13230 4233,12171 2115,12171 2115,7937");
|
||||
assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[3]/polygon",
|
||||
u"1058,7937 1058,13230 4233,13230 4233,12171 2115,12171 2115,7937");
|
||||
assertXPathContent(
|
||||
pDocument, aXPathPrefix + "mask/polygonhairline[3]/polygon",
|
||||
u"12699,1058 16933,1058 16933,2118 15346,2118 15346,6349 14287,6349 14287,2118 12699,2118");
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testRectangleWithModifyWorldTransform)
|
||||
@@ -2004,6 +2007,32 @@ CPPUNIT_TEST_FIXTURE(Test, testExcludeClipRect)
|
||||
assertXPath(pDocument, aXPathPrefix + "group/mask/polypolygoncolor", "color", u"#ff0000");
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testPolyFillModeWinding)
|
||||
{
|
||||
// EMR_SETPOLYFILLMODE sets WINDING mode, then draws a self-intersecting
|
||||
// pentagram (5-pointed star) filled with red. With WINDING the entire
|
||||
// star including the center is filled; with ALTERNATE the center pentagon
|
||||
// would be a hole. The geometry is converted by CreateWindingFillPolyPolygon()
|
||||
// so that even-odd rendering fills the star completely.
|
||||
Primitive2DSequence aSequence
|
||||
= parseEmf(u"/emfio/qa/cppunit/emf/data/TestPolyFillModeWinding.emf");
|
||||
CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
|
||||
drawinglayer::Primitive2dXmlDump dumper;
|
||||
xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
|
||||
CPPUNIT_ASSERT(pDocument);
|
||||
|
||||
// The pentagram is filled with red. The self-intersecting polygon is
|
||||
// resolved into a non-self-intersecting outer boundary via union of
|
||||
// sub-regions, so even-odd fill covers the entire star including the center.
|
||||
assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", "color", u"#ff0000");
|
||||
assertXPath(pDocument, aXPathPrefix + "polypolygoncolor/polypolygon/polygon", 1);
|
||||
|
||||
// The stroke preserves the original 5-vertex star shape.
|
||||
assertXPath(pDocument, aXPathPrefix + "polygonhairline", 1);
|
||||
assertXPathContent(pDocument, aXPathPrefix + "polygonhairline/polygon",
|
||||
u"2646,264 4048,4565 370,1900 4921,1900 1244,4565");
|
||||
}
|
||||
|
||||
CPPUNIT_PLUGIN_IMPLEMENT();
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
||||
Binary file not shown.
@@ -1146,6 +1146,11 @@ namespace emfio
|
||||
break;
|
||||
|
||||
case EMR_SETPOLYFILLMODE :
|
||||
{
|
||||
mpInputStream->ReadUInt32( nDat32 );
|
||||
SAL_INFO("emfio", "\t\tPolyFillMode: 0x" << std::hex << nDat32 << std::dec);
|
||||
SetPolyFillMode( nDat32 );
|
||||
}
|
||||
break;
|
||||
|
||||
case EMR_SETROP2 :
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <basegfx/matrix/b2dhommatrix.hxx>
|
||||
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
|
||||
#include <basegfx/polygon/b2dpolypolygontools.hxx>
|
||||
#include <tools/mapunit.hxx>
|
||||
#include <vcl/metric.hxx>
|
||||
@@ -883,6 +884,11 @@ namespace emfio
|
||||
mnBkMode = nMode;
|
||||
}
|
||||
|
||||
void MtfTools::SetPolyFillMode( sal_uInt32 nPolyFillMode )
|
||||
{
|
||||
mnPolyFillMode = nPolyFillMode;
|
||||
}
|
||||
|
||||
void MtfTools::SetBkColor( const Color& rColor )
|
||||
{
|
||||
maBkColor = rColor;
|
||||
@@ -903,12 +909,60 @@ namespace emfio
|
||||
mvGDIObj.resize(nNewEntrys);
|
||||
}
|
||||
|
||||
// Convert a PolyPolygon so that nonzero/winding fill rule gives the same visual
|
||||
// result when rendered with even-odd fill rule (which is all VCL supports).
|
||||
static tools::PolyPolygon CreateWindingFillPolyPolygon(const tools::PolyPolygon& rPolyPoly)
|
||||
{
|
||||
basegfx::B2DPolyPolygon aB2D = rPolyPoly.getB2DPolyPolygon();
|
||||
|
||||
if (aB2D.count() == 1)
|
||||
{
|
||||
// Single self-intersecting polygon (e.g. pentagram star): resolve
|
||||
// self-intersections, then union (OR) all resulting sub-regions.
|
||||
// This gives the outer boundary of the filled area, which even-odd
|
||||
// fill renders correctly. createNonzeroConform() fails here because
|
||||
// solveCrossovers() produces overlapping loops for odd-crossing
|
||||
// polygons, and the center ends up inside an even number of loops.
|
||||
basegfx::B2DPolyPolygon aResolved
|
||||
= basegfx::utils::solveCrossovers(aB2D.getB2DPolygon(0));
|
||||
aResolved = basegfx::utils::stripNeutralPolygons(aResolved);
|
||||
|
||||
if (aResolved.count() > 1)
|
||||
{
|
||||
basegfx::B2DPolyPolygonVector aInput;
|
||||
for (sal_uInt32 i = 0; i < aResolved.count(); ++i)
|
||||
aInput.push_back(basegfx::B2DPolyPolygon(aResolved.getB2DPolygon(i)));
|
||||
aB2D = basegfx::utils::mergeToSinglePolyPolygon(aInput);
|
||||
}
|
||||
else
|
||||
{
|
||||
aB2D = aResolved;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multiple polygons: use createNonzeroConform (handles nested
|
||||
// same-orientation polygons by removing inner duplicates).
|
||||
aB2D = basegfx::utils::createNonzeroConform(aB2D);
|
||||
}
|
||||
|
||||
return tools::PolyPolygon(aB2D);
|
||||
}
|
||||
|
||||
void MtfTools::ImplDrawClippedPolyPolygon( const tools::PolyPolygon& rPolyPoly )
|
||||
{
|
||||
if ( !rPolyPoly.Count() )
|
||||
return;
|
||||
|
||||
ImplSetNonPersistentLineColorTransparenz();
|
||||
|
||||
if (mnPolyFillMode == 2) // WINDING
|
||||
{
|
||||
tools::PolyPolygon aFill = CreateWindingFillPolyPolygon(rPolyPoly);
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( aFill ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( rPolyPoly.Count() == 1 )
|
||||
{
|
||||
if ( rPolyPoly.IsRect() )
|
||||
@@ -1113,6 +1167,7 @@ namespace emfio
|
||||
mbNopMode(false),
|
||||
mbClockWiseArcDirection(false),
|
||||
mbFillStyleSelected(false),
|
||||
mnPolyFillMode(1), // ALTERNATE
|
||||
mbClipNeedsUpdate(true),
|
||||
mbComplexClip(false),
|
||||
mbIsMapWinSet(false),
|
||||
@@ -1292,26 +1347,39 @@ namespace emfio
|
||||
UpdateClipRegion();
|
||||
UpdateLineStyle();
|
||||
UpdateFillStyle();
|
||||
bool bWindingFill = (mnPolyFillMode == 2);
|
||||
if ( bFill )
|
||||
{
|
||||
if ( !bStroke )
|
||||
// When using WINDING fill mode, we convert the geometry so it renders
|
||||
// correctly with even-odd fill. We must also disable line color to avoid
|
||||
// stroking internal edges of the converted fill geometry.
|
||||
if ( !bStroke || bWindingFill )
|
||||
{
|
||||
mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
|
||||
mpGDIMetaFile->AddAction( new MetaLineColorAction( Color(), false ) );
|
||||
}
|
||||
if ( maPathObj.Count() == 1 )
|
||||
mpGDIMetaFile->AddAction( new MetaPolygonAction( maPathObj.GetObject( 0 ) ) );
|
||||
if (bWindingFill)
|
||||
{
|
||||
tools::PolyPolygon aFill = CreateWindingFillPolyPolygon(maPathObj);
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( aFill ) );
|
||||
}
|
||||
else
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( maPathObj ) );
|
||||
{
|
||||
if ( maPathObj.Count() == 1 )
|
||||
mpGDIMetaFile->AddAction( new MetaPolygonAction( maPathObj.GetObject( 0 ) ) );
|
||||
else
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( maPathObj ) );
|
||||
}
|
||||
|
||||
if ( !bStroke )
|
||||
if ( !bStroke || bWindingFill )
|
||||
mpGDIMetaFile->AddAction( new MetaPopAction() );
|
||||
}
|
||||
// tdf#142014 By default the stroke is made with hairline. If width is bigger, we need to use PolyLineAction
|
||||
if ( bStroke )
|
||||
{
|
||||
// bFill is drawing hairstyle line. So we need to draw it only when the width is different than 0
|
||||
if ( !bFill || maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
|
||||
// When using WINDING fill mode, line was disabled for the fill, so always draw stroke
|
||||
if ( !bFill || bWindingFill || maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
|
||||
{
|
||||
sal_uInt16 i, nCount = maPathObj.Count();
|
||||
for ( i = 0; i < nCount; i++ )
|
||||
@@ -1552,21 +1620,47 @@ namespace emfio
|
||||
}
|
||||
}
|
||||
ImplSetNonPersistentLineColorTransparenz();
|
||||
mpGDIMetaFile->AddAction( new MetaPolygonAction( rPolygon ) );
|
||||
if (mnPolyFillMode == 2) // WINDING
|
||||
{
|
||||
tools::PolyPolygon aFill = CreateWindingFillPolyPolygon(tools::PolyPolygon(rPolygon));
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( aFill ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
mpGDIMetaFile->AddAction( new MetaPolygonAction( rPolygon ) );
|
||||
}
|
||||
UpdateLineStyle();
|
||||
mpGDIMetaFile->AddAction( new MetaPolyLineAction( std::move(rPolygon), maLineStyle.aLineInfo ) );
|
||||
mpGDIMetaFile->AddAction( new MetaPolyLineAction( rPolygon, maLineStyle.aLineInfo ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateLineStyle();
|
||||
|
||||
if (maLatestFillStyle.aType != WinMtfFillStyleType::Pattern)
|
||||
mpGDIMetaFile->AddAction( new MetaPolygonAction( std::move(rPolygon) ) );
|
||||
{
|
||||
if (mnPolyFillMode == 2) // WINDING
|
||||
{
|
||||
tools::PolyPolygon aFill = CreateWindingFillPolyPolygon(tools::PolyPolygon(rPolygon));
|
||||
ImplSetNonPersistentLineColorTransparenz();
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( aFill ) );
|
||||
UpdateLineStyle();
|
||||
// Stroke with original polygon, closing it for the polyline
|
||||
sal_uInt16 nCount = rPolygon.GetSize();
|
||||
if (nCount && rPolygon[nCount - 1] != rPolygon[0])
|
||||
{
|
||||
Point aPoint(rPolygon[0]);
|
||||
rPolygon.Insert(nCount, aPoint);
|
||||
}
|
||||
mpGDIMetaFile->AddAction(new MetaPolyLineAction(rPolygon, maLineStyle.aLineInfo));
|
||||
}
|
||||
else
|
||||
mpGDIMetaFile->AddAction( new MetaPolygonAction( std::move(rPolygon) ) );
|
||||
}
|
||||
else {
|
||||
SvtGraphicFill aFill( tools::PolyPolygon( rPolygon ),
|
||||
Color(),
|
||||
0.0,
|
||||
SvtGraphicFill::fillNonZero,
|
||||
mnPolyFillMode == 2 ? SvtGraphicFill::fillNonZero : SvtGraphicFill::fillEvenOdd,
|
||||
SvtGraphicFill::fillTexture,
|
||||
SvtGraphicFill::Transform(),
|
||||
true,
|
||||
@@ -1614,14 +1708,32 @@ namespace emfio
|
||||
else
|
||||
{
|
||||
UpdateLineStyle();
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPolygon ) );
|
||||
if (maLineStyle.aLineInfo.GetWidth() > 0 || maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash)
|
||||
if (mnPolyFillMode == 2) // WINDING
|
||||
{
|
||||
// Convert geometry so even-odd rendering matches winding fill.
|
||||
// Disable line to avoid stroking internal resolved edges.
|
||||
tools::PolyPolygon aFill = CreateWindingFillPolyPolygon(rPolyPolygon);
|
||||
mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
|
||||
mpGDIMetaFile->AddAction( new MetaLineColorAction( Color(), false ) );
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( aFill ) );
|
||||
mpGDIMetaFile->AddAction( new MetaPopAction() );
|
||||
// Stroke with original geometry
|
||||
for (sal_uInt16 nPoly = 0; nPoly < rPolyPolygon.Count(); ++nPoly)
|
||||
{
|
||||
mpGDIMetaFile->AddAction(new MetaPolyLineAction(rPolyPolygon[nPoly], maLineStyle.aLineInfo));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPolygon ) );
|
||||
if (maLineStyle.aLineInfo.GetWidth() > 0 || maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash)
|
||||
{
|
||||
for (sal_uInt16 nPoly = 0; nPoly < rPolyPolygon.Count(); ++nPoly)
|
||||
{
|
||||
mpGDIMetaFile->AddAction(new MetaPolyLineAction(rPolyPolygon[nPoly], maLineStyle.aLineInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2782,6 +2894,7 @@ namespace emfio
|
||||
pSave->aBkColor = maBkColor;
|
||||
pSave->bClockWiseArcDirection = mbClockWiseArcDirection;
|
||||
pSave->bFillStyleSelected = mbFillStyleSelected;
|
||||
pSave->nPolyFillMode = mnPolyFillMode;
|
||||
|
||||
pSave->aActPos = maActPos;
|
||||
pSave->aXForm = maXForm;
|
||||
@@ -2844,6 +2957,7 @@ namespace emfio
|
||||
maBkColor = pSave->aBkColor;
|
||||
mbClockWiseArcDirection = pSave->bClockWiseArcDirection;
|
||||
mbFillStyleSelected = pSave->bFillStyleSelected;
|
||||
mnPolyFillMode = pSave->nPolyFillMode;
|
||||
|
||||
maActPos = pSave->aActPos;
|
||||
maXForm = pSave->aXForm;
|
||||
|
||||
@@ -1390,8 +1390,17 @@ namespace emfio
|
||||
}
|
||||
break;
|
||||
|
||||
case W_META_SETRELABS:
|
||||
case W_META_SETPOLYFILLMODE:
|
||||
{
|
||||
if ((nRecordSize != 4) && (nRecordSize != 5))
|
||||
bRecordOk = false;
|
||||
sal_uInt16 nDat = 0;
|
||||
mpInputStream->ReadUInt16( nDat );
|
||||
SetPolyFillMode( nDat );
|
||||
}
|
||||
break;
|
||||
|
||||
case W_META_SETRELABS:
|
||||
case W_META_SETSTRETCHBLTMODE:
|
||||
case W_META_SETTEXTCHAREXTRA:
|
||||
case W_META_SETTEXTJUSTIFICATION:
|
||||
|
||||
Reference in New Issue
Block a user