diff options
-rw-r--r-- | vcl/headless/svpgdi.cxx | 401 |
1 files changed, 289 insertions, 112 deletions
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx index e47eb402e40f..73b9a1fe24bf 100644 --- a/vcl/headless/svpgdi.cxx +++ b/vcl/headless/svpgdi.cxx @@ -37,6 +37,7 @@ #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> #include <basegfx/utils/systemdependentdata.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> #if ENABLE_CAIRO_CANVAS # if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0) @@ -52,7 +53,13 @@ namespace cairo_clip_extents(cr, &x1, &y1, &x2, &y2); - return basegfx::B2DRange(x1, y1, x2, y2); + // support B2DRange::isEmpty() + if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2) + { + return basegfx::B2DRange(x1, y1, x2, y2); + } + + return basegfx::B2DRange(); } basegfx::B2DRange getFillDamage(cairo_t* cr) @@ -61,7 +68,13 @@ namespace cairo_fill_extents(cr, &x1, &y1, &x2, &y2); - return basegfx::B2DRange(x1, y1, x2, y2); + // support B2DRange::isEmpty() + if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2) + { + return basegfx::B2DRange(x1, y1, x2, y2); + } + + return basegfx::B2DRange(); } basegfx::B2DRange getClippedFillDamage(cairo_t* cr) @@ -77,7 +90,13 @@ namespace cairo_stroke_extents(cr, &x1, &y1, &x2, &y2); - return basegfx::B2DRange(x1, y1, x2, y2); + // support B2DRange::isEmpty() + if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2) + { + return basegfx::B2DRange(x1, y1, x2, y2); + } + + return basegfx::B2DRange(); } basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr) @@ -514,36 +533,52 @@ void SvpSalGraphics::clipRegion(cairo_t* cr) bool SvpSalGraphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency) { + const bool bHasFill(m_aFillColor != SALCOLOR_NONE); + const bool bHasLine(m_aLineColor != SALCOLOR_NONE); + + if(!(bHasFill || bHasLine)) + { + return true; + } + cairo_t* cr = getCairoContext(false); clipRegion(cr); const double fTransparency = (100 - nTransparency) * (1.0/100); - basegfx::B2DRange extents(0, 0, 0, 0); + // To make releaseCairoContext work, use empty extents + basegfx::B2DRange extents; cairo_rectangle(cr, nX, nY, nWidth, nHeight); - if (m_aFillColor != SALCOLOR_NONE) + if (bHasFill) { cairo_set_source_rgba(cr, m_aFillColor.GetRed()/255.0, m_aFillColor.GetGreen()/255.0, m_aFillColor.GetBlue()/255.0, fTransparency); - if (m_aLineColor == SALCOLOR_NONE) - extents = getClippedFillDamage(cr); + // set FillDamage + extents = getClippedFillDamage(cr); cairo_fill_preserve(cr); } - if (m_aLineColor != SALCOLOR_NONE) + if (bHasLine) { + // PixelOffset used: Set PixelOffset as linear transformation + // Note: Was missing here - probably not by purpose (?) + cairo_matrix_t aMatrix; + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + cairo_set_matrix(cr, &aMatrix); + cairo_set_source_rgba(cr, m_aLineColor.GetRed()/255.0, m_aLineColor.GetGreen()/255.0, m_aLineColor.GetBlue()/255.0, fTransparency); - extents = getClippedStrokeDamage(cr); + // expand with possible StrokeDamage + extents.expand(getClippedStrokeDamage(cr)); cairo_stroke_preserve(cr); } @@ -766,22 +801,82 @@ void SvpSalGraphics::drawPolyPolygon(sal_uInt32 nPoly, drawPolyPolygon(aPolyPoly); } -static const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5); +basegfx::B2DPoint impPixelSnap( + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if(bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint( + bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + if(rObjectToDeviceInv.isIdentity()) + { + rObjectToDeviceInv = rObjectToDevice; + rObjectToDeviceInv.invert(); + } + + aSnappedPoint *= rObjectToDeviceInv; + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} -static void AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, bool bClosePath, - bool bPixelSnap, bool bLineDraw) +// Remove bClosePath: Checked that the already used mechanism for Win using +// Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace +// this. +// For PixelSnap we need the ObjectToDevice transformation here now. Tis is a +// special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in +// DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we +// need the ObjectToDevice transformation *without* that offset here to do the +// same. The LineDraw-Offset will be appied by the callers using a linear +// transformation for Cairo now +// For support of PixelSnapHairline we also need the ObjectToDevice transformation +// and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g. +// for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!) +static void AddPolygonToPath( + cairo_t* cr, + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + bool bPixelSnap, + bool bPixelSnapHairline) { // short circuit if there is nothing to do - const int nPointCount = rPolygon.count(); - if( nPointCount <= 0 ) + const sal_uInt32 nPointCount(rPolygon.count()); + + if(0 == nPointCount) { return; } - const bool bHasCurves = rPolygon.areControlPointsUsed(); + const bool bHasCurves(rPolygon.areControlPointsUsed()); + const bool bClosePath(rPolygon.isClosed()); + const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity()); + basegfx::B2DHomMatrix aObjectToDeviceInv; basegfx::B2DPoint aLast; - for( int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++ ) + for( sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++ ) { int nClosedIdx = nPointIdx; if( nPointIdx >= nPointCount ) @@ -797,18 +892,39 @@ static void AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, b } } - basegfx::B2DPoint aPoint = rPolygon.getB2DPoint( nClosedIdx ); + basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx)); - if( bPixelSnap) + if(bPixelSnap) { // snap device coordinates to full pixels + if(bObjectToDeviceUsed) + { + // go to DeviceCoordinates + aPoint *= rObjectToDevice; + } + + // snap by rounding aPoint.setX( basegfx::fround( aPoint.getX() ) ); aPoint.setY( basegfx::fround( aPoint.getY() ) ); + + if(bObjectToDeviceUsed) + { + if(aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } + + // go back to ObjectCoordinates + aPoint *= aObjectToDeviceInv; + } } - if( bLineDraw ) + if(bPixelSnapHairline) { - aPoint += aHalfPointOfs; + // snap horizontal and vertical lines (mainly used in Chart for + // 'nicer' AAing) + aPoint = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx); } if( !nPointIdx ) @@ -819,7 +935,8 @@ static void AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, b continue; } - bool bPendingCurve = false; + bool bPendingCurve(false); + if( bHasCurves ) { bPendingCurve = rPolygon.isNextControlPointUsed( nPrevIdx ); @@ -834,11 +951,6 @@ static void AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, b { basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint( nPrevIdx ); basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint( nClosedIdx ); - if( bLineDraw ) - { - aCP1 += aHalfPointOfs; - aCP2 += aHalfPointOfs; - } // tdf#99165 if the control points are 'empty', create the mathematical // correct replacement ones to avoid problems with the graphical sub-system @@ -870,14 +982,28 @@ static void AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, b void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 ) { basegfx::B2DPolygon aPoly; - aPoly.append(basegfx::B2DPoint(nX1, nY1), 2); - aPoly.setB2DPoint(1, basegfx::B2DPoint(nX2, nY2)); - aPoly.setClosed(false); + + // PixelOffset used: To not mix with possible PixelSnap, cannot do + // directly on coordinates as truied before - despite being already 'snapped' + // due to being integer. If it would be directly added here, it would be + // 'snapped' again when !getAntiAliasB2DDraw(), losing the (0.5, 0.5) offset + aPoly.append(basegfx::B2DPoint(nX1, nY1)); + aPoly.append(basegfx::B2DPoint(nX2, nY2)); cairo_t* cr = getCairoContext(false); clipRegion(cr); - AddPolygonToPath(cr, aPoly, aPoly.isClosed(), !getAntiAliasB2DDraw(), true); + // PixelOffset used: Set PixelOffset as linear transformation + cairo_matrix_t aMatrix; + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + cairo_set_matrix(cr, &aMatrix); + + AddPolygonToPath( + cr, + aPoly, + basegfx::B2DHomMatrix(), + !getAntiAliasB2DDraw(), + false); applyColor(cr, m_aLineColor); @@ -892,6 +1018,7 @@ class SystemDependentData_CairoPath : public basegfx::SystemDependentData { private: cairo_path_t* mpCairoPath; + bool mbPixelSnapHairline; public: SystemDependentData_CairoPath( @@ -900,13 +1027,17 @@ public: virtual ~SystemDependentData_CairoPath() override; cairo_path_t* getCairoPath() { return mpCairoPath; } + + bool getPixelSnapHairline() const { return mbPixelSnapHairline; } + void setPixelSnapHairline(bool bNew) { mbPixelSnapHairline = bNew; } }; SystemDependentData_CairoPath::SystemDependentData_CairoPath( basegfx::SystemDependentDataManager& rSystemDependentDataManager, cairo_path_t* pCairoPath) : basegfx::SystemDependentData(rSystemDependentDataManager), - mpCairoPath(pCairoPath) + mpCairoPath(pCairoPath), + mbPixelSnapHairline(false) { } @@ -1006,22 +1137,33 @@ bool SvpSalGraphics::drawPolyLine( aLineWidths = aObjectToDeviceInv * basegfx::B2DVector(1.0, 1.0); } - if(!bObjectToDeviceIsIdentity) - { - // set ObjectToDevice transformation - cairo_matrix_t aMatrix; + // PixelOffset used: Need to reflect in linear transformation + cairo_matrix_t aMatrix; + if(bObjectToDeviceIsIdentity) + { + // Set PixelOffset as requested + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + } + else + { + // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into + // account: Multiply from left to act in DeviceCoordinates + const basegfx::B2DHomMatrix aCombined( + basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) * rObjectToDevice); cairo_matrix_init( &aMatrix, - rObjectToDevice.get( 0, 0 ), - rObjectToDevice.get( 1, 0 ), - rObjectToDevice.get( 0, 1 ), - rObjectToDevice.get( 1, 1 ), - rObjectToDevice.get( 0, 2 ), - rObjectToDevice.get( 1, 2 )); - cairo_set_matrix(cr, &aMatrix); + aCombined.get( 0, 0 ), + aCombined.get( 1, 0 ), + aCombined.get( 0, 1 ), + aCombined.get( 1, 1 ), + aCombined.get( 0, 2 ), + aCombined.get( 1, 2 )); } + // set linear transformation + cairo_set_matrix(cr, &aMatrix); + const bool bNoJoin((basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(aLineWidths.getX(), 0.0))); // setup line attributes @@ -1084,7 +1226,8 @@ bool SvpSalGraphics::drawPolyLine( if(pSystemDependentData_CairoPath) { // check data validity - if(nullptr == pSystemDependentData_CairoPath->getCairoPath()) + if(nullptr == pSystemDependentData_CairoPath->getCairoPath() + || pSystemDependentData_CairoPath->getPixelSnapHairline() != bPixelSnapHairline) { // data invalid, forget pSystemDependentData_CairoPath.reset(); @@ -1099,44 +1242,15 @@ bool SvpSalGraphics::drawPolyLine( else { // create data - basegfx::B2DPolygon aPolyLine(rPolyLine); - - if(bPixelSnapHairline) - { - // Need to take care of PixelSnapHairline now. The 'short' version - // will manipulate the Polygon by using the known tooling at - // basegfx. To do this correct, this needs to be done in device - // coordinates, so when the transformation is used, transform - // to device first, execute, transform back using the inverse. - // The important part for buffering the result and not need to - // do this at each repaint (for now) is to change a copy of the - // Polygon to create the CairoData, but to buffer it at the original - // unmodified Polygon. - // The 'long' version would be to add this to AddPolygonToPath - // equal as done in Win version (see impPixelSnap), should be done - // later - if(!bObjectToDeviceIsIdentity) - { - aPolyLine.transform(rObjectToDevice); - } - - aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); - - if(!bObjectToDeviceIsIdentity) - { - if(aObjectToDeviceInv.isIdentity()) - { - aObjectToDeviceInv = rObjectToDevice; - aObjectToDeviceInv.invert(); - } - - aPolyLine.transform(aObjectToDeviceInv); - } - } - if (!bNoJoin) { - AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !bAntiAliasB2DDraw, true); + // PixelOffset now reflected in linear transformation used + AddPolygonToPath( + cr, + rPolyLine, + rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset + !bAntiAliasB2DDraw, + bPixelSnapHairline); } else { @@ -1154,7 +1268,13 @@ bool SvpSalGraphics::drawPolyLine( aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i)); aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex)); - AddPolygonToPath(cr, aEdge, false, !bAntiAliasB2DDraw, true); + // PixelOffset now reflected in linear transformation used + AddPolygonToPath( + cr, + aEdge, + rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset + !bAntiAliasB2DDraw, + bPixelSnapHairline); // prepare next step aEdge.setB2DPoint(0, aEdge.getB2DPoint(1)); @@ -1162,35 +1282,33 @@ bool SvpSalGraphics::drawPolyLine( } // copy and add to buffering mechanism - rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>( + pSystemDependentData_CairoPath = rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>( SalGraphics::getSystemDependentDataManager(), cairo_copy_path(cr)); + + // fill data of buffered data + pSystemDependentData_CairoPath->setPixelSnapHairline(bPixelSnapHairline); } // extract extents if(nullptr != pExtents) { + // This uses cairo_stroke_extents and combines with cairo_clip_extents, so + // referring to Cairo-documentation: + // "Computes a bounding box in user coordinates covering the area that would + // be affected, (the "inked" area), by a cairo_stroke() operation given the + // current path and stroke parameters." + // It *should* use the current set cairo_matrix_t. *pExtents = getClippedStrokeDamage(cr); + + // If not - the following code needs to be used to correct that: + // if(!pExtents->isEmpty() && !bObjectToDeviceIsIdentity) + // pExtents->transform(rObjectToDevice); } // draw and consume cairo_stroke(cr); - if(!bObjectToDeviceIsIdentity) - { - // reset ObjectToDevice transformation if was set (safe, may - // be better suited at ::getCairoContext) - cairo_identity_matrix(cr); - } - - if(nullptr != pExtents && !pExtents->isEmpty() && !bObjectToDeviceIsIdentity) - { - // transform extents to DeviceCoordinates if used. These - // were calculated with ObjectToDevice transformation actively set, - // but use DeviceCoordinates locally - pExtents->transform(rObjectToDevice); - } - return true; } @@ -1224,38 +1342,64 @@ void SvpSalGraphics::setupPolyPolygon(cairo_t* cr, const basegfx::B2DPolyPolygon clipRegion(cr); for (const auto & rPoly : rPolyPoly) - AddPolygonToPath(cr, rPoly, true, !getAntiAliasB2DDraw(), m_aLineColor != SALCOLOR_NONE); + { + // PixelOffset used: Was dependent of 'm_aLineColor != SALCOLOR_NONE' + // Adapt setupPolyPolygon-users to set a linear transformation to achieve PixelOffset + AddPolygonToPath( + cr, + rPoly, + basegfx::B2DHomMatrix(), + !getAntiAliasB2DDraw(), + false); + } } bool SvpSalGraphics::drawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPoly, double fTransparency) { + const bool bHasFill(m_aFillColor != SALCOLOR_NONE); + const bool bHasLine(m_aLineColor != SALCOLOR_NONE); + + if(0 == rPolyPoly.count() || !(bHasFill || bHasLine)) + { + return true; + } + cairo_t* cr = getCairoContext(true); setupPolyPolygon(cr, rPolyPoly); - basegfx::B2DRange extents(0, 0, 0, 0); + // To make releaseCairoContext work, use empty extents + basegfx::B2DRange extents; - if (m_aFillColor != SALCOLOR_NONE) + if (bHasFill) { cairo_set_source_rgba(cr, m_aFillColor.GetRed()/255.0, m_aFillColor.GetGreen()/255.0, m_aFillColor.GetBlue()/255.0, 1.0-fTransparency); - if (m_aLineColor == SALCOLOR_NONE) - extents = getClippedFillDamage(cr); + // Get FillDamage (will be extended for LineDamage below) + extents = getClippedFillDamage(cr); cairo_fill_preserve(cr); } - if (m_aLineColor != SALCOLOR_NONE) + if (bHasLine) { + // PixelOffset used: Set PixelOffset as linear transformation + cairo_matrix_t aMatrix; + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + cairo_set_matrix(cr, &aMatrix); + + // Note: Other methods use applyColor(...) to set the Color. Thst + // seems to do some more. Maybe it should be used here, too (?) cairo_set_source_rgba(cr, m_aLineColor.GetRed()/255.0, m_aLineColor.GetGreen()/255.0, m_aLineColor.GetBlue()/255.0, 1.0-fTransparency); - extents = getClippedStrokeDamage(cr); + // expand with possible StrokeDamage + extents.expand(getClippedStrokeDamage(cr)); cairo_stroke_preserve(cr); } @@ -1284,24 +1428,43 @@ void SvpSalGraphics::applyColor(cairo_t *cr, Color aColor) void SvpSalGraphics::drawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPoly) { + const bool bHasFill(m_aFillColor != SALCOLOR_NONE); + const bool bHasLine(m_aLineColor != SALCOLOR_NONE); + + if(0 == rPolyPoly.count() || !(bHasFill || bHasLine)) + { + return; + } + cairo_t* cr = getCairoContext(true); setupPolyPolygon(cr, rPolyPoly); - basegfx::B2DRange extents(0, 0, 0, 0); + // To make releaseCairoContext work, use empty extents + basegfx::B2DRange extents; - if (m_aFillColor != SALCOLOR_NONE) + if (bHasFill) { applyColor(cr, m_aFillColor); - if (m_aLineColor == SALCOLOR_NONE) - extents = getClippedFillDamage(cr); + + // Get FillDamage (will be extended for LineDamage below) + extents = getClippedFillDamage(cr); + cairo_fill_preserve(cr); } - if (m_aLineColor != SALCOLOR_NONE) + if (bHasLine) { + // PixelOffset used: Set PixelOffset as linear transformation + cairo_matrix_t aMatrix; + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + cairo_set_matrix(cr, &aMatrix); + applyColor(cr, m_aLineColor); - extents = getClippedStrokeDamage(cr); + + // expand with possible StrokeDamage + extents.expand(getClippedStrokeDamage(cr)); + cairo_stroke_preserve(cr); } @@ -1597,9 +1760,15 @@ void SvpSalGraphics::invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags) cairo_t* cr = getCairoContext(false); clipRegion(cr); - basegfx::B2DRange extents(0, 0, 0, 0); + // To make releaseCairoContext work, use empty extents + basegfx::B2DRange extents; - AddPolygonToPath(cr, rPoly, true, !getAntiAliasB2DDraw(), false); + AddPolygonToPath( + cr, + rPoly, + basegfx::B2DHomMatrix(), + !getAntiAliasB2DDraw(), + false); cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); @@ -1622,7 +1791,10 @@ void SvpSalGraphics::invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags) //see tdf#106577 under wayland, some pixel droppings seen, maybe we're //out by one somewhere, or cairo_stroke_extents is confused by //dashes/line width - extents.grow(1); + if(!extents.isEmpty()) + { + extents.grow(1); + } cairo_stroke(cr); } @@ -1751,6 +1923,11 @@ cairo_t* SvpSalGraphics::getCairoContext(bool bXorModeAllowed) const cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); cairo_set_antialias(cr, getAntiAliasB2DDraw() ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + // ensure no linear transformation and no PathInfo in local cairo_path_t + cairo_identity_matrix(cr); + cairo_new_path(cr); + return cr; } |