1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
28 #include <basegfx/polygon/b2dpolygon.hxx>
29 #include <basegfx/polygon/b2dpolygontools.hxx>
30 #include <basegfx/polygon/b2dpolypolygontools.hxx>
31 #include <osl/endian.h>
32 #include <osl/file.hxx>
33 #include <sal/types.h>
34 #include <tools/long.hxx>
35 #include <vcl/sysdata.hxx>
37 #include <fontsubset.hxx>
38 #include <quartz/salbmp.h>
40 #include <quartz/salgdi.h>
42 #include <quartz/utils.h>
44 #include <ios/iosinst.hxx>
51 const basegfx::B2DPoint
aHalfPointOfs(0.5, 0.5);
53 void AddPolygonToPath(CGMutablePathRef xPath
, const basegfx::B2DPolygon
& rPolygon
, bool bClosePath
,
54 bool bPixelSnap
, bool bLineDraw
)
56 // short circuit if there is nothing to do
57 const int nPointCount
= rPolygon
.count();
63 const bool bHasCurves
= rPolygon
.areControlPointsUsed();
64 for (int nPointIdx
= 0, nPrevIdx
= 0;; nPrevIdx
= nPointIdx
++)
66 int nClosedIdx
= nPointIdx
;
67 if (nPointIdx
>= nPointCount
)
69 // prepare to close last curve segment if needed
70 if (bClosePath
&& (nPointIdx
== nPointCount
))
80 basegfx::B2DPoint aPoint
= rPolygon
.getB2DPoint(nClosedIdx
);
84 // snap device coordinates to full pixels
85 aPoint
.setX(basegfx::fround(aPoint
.getX()));
86 aPoint
.setY(basegfx::fround(aPoint
.getY()));
91 aPoint
+= aHalfPointOfs
;
95 // first point => just move there
96 CGPathMoveToPoint(xPath
, nullptr, aPoint
.getX(), aPoint
.getY());
100 bool bPendingCurve
= false;
103 bPendingCurve
= rPolygon
.isNextControlPointUsed(nPrevIdx
);
104 bPendingCurve
|= rPolygon
.isPrevControlPointUsed(nClosedIdx
);
107 if (!bPendingCurve
) // line segment
109 CGPathAddLineToPoint(xPath
, nullptr, aPoint
.getX(), aPoint
.getY());
111 else // cubic bezier segment
113 basegfx::B2DPoint aCP1
= rPolygon
.getNextControlPoint(nPrevIdx
);
114 basegfx::B2DPoint aCP2
= rPolygon
.getPrevControlPoint(nClosedIdx
);
117 aCP1
+= aHalfPointOfs
;
118 aCP2
+= aHalfPointOfs
;
120 CGPathAddCurveToPoint(xPath
, nullptr, aCP1
.getX(), aCP1
.getY(), aCP2
.getX(),
121 aCP2
.getY(), aPoint
.getX(), aPoint
.getY());
127 CGPathCloseSubpath(xPath
);
131 void alignLinePoint(const Point
* i_pIn
, float& o_fX
, float& o_fY
)
133 o_fX
= static_cast<float>(i_pIn
->getX()) + 0.5;
134 o_fY
= static_cast<float>(i_pIn
->getY()) + 0.5;
137 void getBoundRect(sal_uInt32 nPoints
, const Point
* pPtAry
, tools::Long
& rX
, tools::Long
& rY
,
138 tools::Long
& rWidth
, tools::Long
& rHeight
)
140 tools::Long nX1
= pPtAry
->getX();
141 tools::Long nX2
= nX1
;
142 tools::Long nY1
= pPtAry
->getY();
143 tools::Long nY2
= nY1
;
145 for (sal_uInt32 n
= 1; n
< nPoints
; n
++)
147 if (pPtAry
[n
].getX() < nX1
)
149 nX1
= pPtAry
[n
].getX();
151 else if (pPtAry
[n
].getX() > nX2
)
153 nX2
= pPtAry
[n
].getX();
155 if (pPtAry
[n
].getY() < nY1
)
157 nY1
= pPtAry
[n
].getY();
159 else if (pPtAry
[n
].getY() > nY2
)
161 nY2
= pPtAry
[n
].getY();
166 rWidth
= nX2
- nX1
+ 1;
167 rHeight
= nY2
- nY1
+ 1;
170 Color
ImplGetROPColor(SalROPColor nROPColor
)
173 if (nROPColor
== SalROPColor::N0
)
175 nColor
= Color(0, 0, 0);
179 nColor
= Color(255, 255, 255);
184 void drawPattern50(void*, CGContextRef rContext
)
186 static const CGRect aRects
[2] = { { { 0, 0 }, { 2, 2 } }, { { 2, 2 }, { 2, 2 } } };
187 CGContextAddRects(rContext
, aRects
, 2);
188 CGContextFillPath(rContext
);
192 AquaGraphicsBackend::AquaGraphicsBackend(AquaSharedAttributes
& rShared
)
193 : AquaGraphicsBackendBase(rShared
, this)
197 AquaGraphicsBackend::~AquaGraphicsBackend() {}
199 void AquaGraphicsBackend::setClipRegion(vcl::Region
const& rRegion
)
201 // release old clip path
202 mrShared
.unsetClipPath();
203 mrShared
.mxClipPath
= CGPathCreateMutable();
205 // set current path, either as polypolgon or sequence of rectangles
206 RectangleVector aRectangles
;
207 rRegion
.GetRegionRectangles(aRectangles
);
209 for (const auto& rRect
: aRectangles
)
211 const tools::Long
nW(rRect
.Right() - rRect
.Left() + 1); // uses +1 logic in original
215 const tools::Long
nH(rRect
.Bottom() - rRect
.Top() + 1); // uses +1 logic in original
219 const CGRect aRect
= CGRectMake(rRect
.Left(), rRect
.Top(), nW
, nH
);
220 CGPathAddRect(mrShared
.mxClipPath
, nullptr, aRect
);
224 // set the current path as clip region
225 if (mrShared
.checkContext())
229 void AquaGraphicsBackend::ResetClipRegion()
231 // release old path and indicate no clipping
232 mrShared
.unsetClipPath();
234 if (mrShared
.checkContext())
240 sal_uInt16
AquaGraphicsBackend::GetBitCount() const
242 sal_uInt16 nBits
= mrShared
.mnBitmapDepth
? mrShared
.mnBitmapDepth
: 32; //24;
246 tools::Long
AquaGraphicsBackend::GetGraphicsWidth() const
248 tools::Long width
= 0;
249 if (mrShared
.maContextHolder
.isSet()
256 width
= mrShared
.mnWidth
;
262 if (mrShared
.mbWindow
&& mrShared
.mpFrame
)
264 width
= mrShared
.mpFrame
->GetWidth();
271 void AquaGraphicsBackend::SetLineColor()
273 mrShared
.maLineColor
.SetAlpha(0.0); // transparent
274 if (mrShared
.checkContext())
276 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), mrShared
.maLineColor
.GetRed(),
277 mrShared
.maLineColor
.GetGreen(), mrShared
.maLineColor
.GetBlue(),
278 mrShared
.maLineColor
.GetAlpha());
282 void AquaGraphicsBackend::SetLineColor(Color nColor
)
284 mrShared
.maLineColor
= RGBAColor(nColor
);
285 if (mrShared
.checkContext())
287 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), mrShared
.maLineColor
.GetRed(),
288 mrShared
.maLineColor
.GetGreen(), mrShared
.maLineColor
.GetBlue(),
289 mrShared
.maLineColor
.GetAlpha());
293 void AquaGraphicsBackend::SetFillColor()
295 mrShared
.maFillColor
.SetAlpha(0.0); // transparent
296 if (mrShared
.checkContext())
298 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.GetRed(),
299 mrShared
.maFillColor
.GetGreen(), mrShared
.maFillColor
.GetBlue(),
300 mrShared
.maFillColor
.GetAlpha());
304 void AquaGraphicsBackend::SetFillColor(Color nColor
)
306 mrShared
.maFillColor
= RGBAColor(nColor
);
307 if (mrShared
.checkContext())
309 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.GetRed(),
310 mrShared
.maFillColor
.GetGreen(), mrShared
.maFillColor
.GetBlue(),
311 mrShared
.maFillColor
.GetAlpha());
315 void AquaGraphicsBackend::SetXORMode(bool bSet
, bool bInvertOnly
)
317 // return early if XOR mode remains unchanged
318 if (mrShared
.mbPrinter
)
322 if (!bSet
&& mrShared
.mnXorMode
== 2)
324 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeNormal
);
325 mrShared
.mnXorMode
= 0;
328 else if (bSet
&& bInvertOnly
&& mrShared
.mnXorMode
== 0)
330 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
331 mrShared
.mnXorMode
= 2;
335 if (!mrShared
.mpXorEmulation
&& !bSet
)
339 if (mrShared
.mpXorEmulation
&& bSet
== mrShared
.mpXorEmulation
->IsEnabled())
343 if (!mrShared
.checkContext())
347 // prepare XOR emulation
348 if (!mrShared
.mpXorEmulation
)
350 mrShared
.mpXorEmulation
= std::make_unique
<XorEmulation
>();
351 mrShared
.mpXorEmulation
->SetTarget(mrShared
.mnWidth
, mrShared
.mnHeight
,
352 mrShared
.mnBitmapDepth
, mrShared
.maContextHolder
.get(),
353 mrShared
.maLayer
.get());
356 // change the XOR mode
359 mrShared
.mpXorEmulation
->Enable();
360 mrShared
.maContextHolder
.set(mrShared
.mpXorEmulation
->GetMaskContext());
361 mrShared
.mnXorMode
= 1;
365 mrShared
.mpXorEmulation
->UpdateTarget();
366 mrShared
.mpXorEmulation
->Disable();
367 mrShared
.maContextHolder
.set(mrShared
.mpXorEmulation
->GetTargetContext());
368 mrShared
.mnXorMode
= 0;
372 void AquaGraphicsBackend::SetROPFillColor(SalROPColor nROPColor
)
374 if (!mrShared
.mbPrinter
)
376 SetFillColor(ImplGetROPColor(nROPColor
));
380 void AquaGraphicsBackend::SetROPLineColor(SalROPColor nROPColor
)
382 if (!mrShared
.mbPrinter
)
384 SetLineColor(ImplGetROPColor(nROPColor
));
388 void AquaGraphicsBackend::drawPixelImpl(tools::Long nX
, tools::Long nY
, const RGBAColor
& rColor
)
390 if (!mrShared
.checkContext())
393 // overwrite the fill color
394 CGContextSetFillColor(mrShared
.maContextHolder
.get(), rColor
.AsArray());
395 // draw 1x1 rect, there is no pixel drawing in Quartz
396 const CGRect aDstRect
= CGRectMake(nX
, nY
, 1, 1);
397 CGContextFillRect(mrShared
.maContextHolder
.get(), aDstRect
);
399 refreshRect(aDstRect
);
401 // reset the fill color
402 CGContextSetFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.AsArray());
405 void AquaGraphicsBackend::drawPixel(tools::Long nX
, tools::Long nY
)
407 // draw pixel with current line color
408 drawPixelImpl(nX
, nY
, mrShared
.maLineColor
);
411 void AquaGraphicsBackend::drawPixel(tools::Long nX
, tools::Long nY
, Color nColor
)
413 const RGBAColor
aPixelColor(nColor
);
414 drawPixelImpl(nX
, nY
, aPixelColor
);
417 void AquaGraphicsBackend::drawLine(tools::Long nX1
, tools::Long nY1
, tools::Long nX2
,
420 if (nX1
== nX2
&& nY1
== nY2
)
422 // #i109453# platform independent code expects at least one pixel to be drawn
427 if (!mrShared
.checkContext())
430 CGContextBeginPath(mrShared
.maContextHolder
.get());
431 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), float(nX1
) + 0.5, float(nY1
) + 0.5);
432 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), float(nX2
) + 0.5, float(nY2
) + 0.5);
433 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathStroke
);
435 tools::Rectangle
aRefreshRect(nX1
, nY1
, nX2
, nY2
);
437 // Is a call to RefreshRect( aRefreshRect ) missing here?
440 void AquaGraphicsBackend::drawRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
443 if (!mrShared
.checkContext())
446 CGRect aRect
= CGRectMake(nX
, nY
, nWidth
, nHeight
);
447 if (mrShared
.isPenVisible())
449 aRect
.origin
.x
+= 0.5;
450 aRect
.origin
.y
+= 0.5;
451 aRect
.size
.width
-= 1;
452 aRect
.size
.height
-= 1;
455 if (mrShared
.isBrushVisible())
457 CGContextFillRect(mrShared
.maContextHolder
.get(), aRect
);
459 if (mrShared
.isPenVisible())
461 CGContextStrokeRect(mrShared
.maContextHolder
.get(), aRect
);
463 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
466 void AquaGraphicsBackend::drawPolyLine(sal_uInt32 nPoints
, const Point
* pPointArray
)
471 if (!mrShared
.checkContext())
474 tools::Long nX
= 0, nY
= 0, nWidth
= 0, nHeight
= 0;
475 getBoundRect(nPoints
, pPointArray
, nX
, nY
, nWidth
, nHeight
);
478 CGContextBeginPath(mrShared
.maContextHolder
.get());
479 alignLinePoint(pPointArray
, fX
, fY
);
480 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
483 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
485 alignLinePoint(pPointArray
, fX
, fY
);
486 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
488 CGContextStrokePath(mrShared
.maContextHolder
.get());
490 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
493 void AquaGraphicsBackend::drawPolygon(sal_uInt32 nPoints
, const Point
* pPointArray
)
498 if (!mrShared
.checkContext())
501 tools::Long nX
= 0, nY
= 0, nWidth
= 0, nHeight
= 0;
502 getBoundRect(nPoints
, pPointArray
, nX
, nY
, nWidth
, nHeight
);
504 CGPathDrawingMode eMode
;
505 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
507 eMode
= kCGPathEOFillStroke
;
509 else if (mrShared
.isPenVisible())
511 eMode
= kCGPathStroke
;
513 else if (mrShared
.isBrushVisible())
515 eMode
= kCGPathEOFill
;
519 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
523 CGContextBeginPath(mrShared
.maContextHolder
.get());
525 if (mrShared
.isPenVisible())
528 alignLinePoint(pPointArray
, fX
, fY
);
529 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
531 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
533 alignLinePoint(pPointArray
, fX
, fY
);
534 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
539 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), pPointArray
->getX(),
540 pPointArray
->getY());
542 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
544 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), pPointArray
->getX(),
545 pPointArray
->getY());
549 CGContextClosePath(mrShared
.maContextHolder
.get());
550 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
552 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
555 void AquaGraphicsBackend::drawPolyPolygon(sal_uInt32 nPolyCount
, const sal_uInt32
* pPoints
,
556 const Point
** ppPtAry
)
561 if (!mrShared
.checkContext())
565 tools::Long leftX
= 0, topY
= 0, maxWidth
= 0, maxHeight
= 0;
567 getBoundRect(pPoints
[0], ppPtAry
[0], leftX
, topY
, maxWidth
, maxHeight
);
569 for (sal_uInt32 n
= 1; n
< nPolyCount
; n
++)
571 tools::Long nX
= leftX
, nY
= topY
, nW
= maxWidth
, nH
= maxHeight
;
572 getBoundRect(pPoints
[n
], ppPtAry
[n
], nX
, nY
, nW
, nH
);
575 maxWidth
+= leftX
- nX
;
580 maxHeight
+= topY
- nY
;
583 if (nX
+ nW
> leftX
+ maxWidth
)
585 maxWidth
= nX
+ nW
- leftX
;
587 if (nY
+ nH
> topY
+ maxHeight
)
589 maxHeight
= nY
+ nH
- topY
;
593 // prepare drawing mode
594 CGPathDrawingMode eMode
;
595 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
597 eMode
= kCGPathEOFillStroke
;
599 else if (mrShared
.isPenVisible())
601 eMode
= kCGPathStroke
;
603 else if (mrShared
.isBrushVisible())
605 eMode
= kCGPathEOFill
;
609 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
614 CGContextBeginPath(mrShared
.maContextHolder
.get());
615 if (mrShared
.isPenVisible())
617 for (sal_uInt32 nPoly
= 0; nPoly
< nPolyCount
; nPoly
++)
619 const sal_uInt32 nPoints
= pPoints
[nPoly
];
622 const Point
* pPtAry
= ppPtAry
[nPoly
];
625 alignLinePoint(pPtAry
, fX
, fY
);
626 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
629 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPtAry
++)
631 alignLinePoint(pPtAry
, fX
, fY
);
632 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
634 CGContextClosePath(mrShared
.maContextHolder
.get());
640 for (sal_uInt32 nPoly
= 0; nPoly
< nPolyCount
; nPoly
++)
642 const sal_uInt32 nPoints
= pPoints
[nPoly
];
645 const Point
* pPtAry
= ppPtAry
[nPoly
];
646 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), pPtAry
->getX(),
649 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPtAry
++)
651 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), pPtAry
->getX(),
654 CGContextClosePath(mrShared
.maContextHolder
.get());
659 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
661 mrShared
.refreshRect(leftX
, topY
, maxWidth
, maxHeight
);
664 void AquaGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix
& rObjectToDevice
,
665 const basegfx::B2DPolyPolygon
& rPolyPolygon
,
666 double fTransparency
)
669 if (!mrShared
.maContextHolder
.isSet())
673 // short circuit if there is nothing to do
674 if (rPolyPolygon
.count() == 0)
677 // ignore invisible polygons
678 if ((fTransparency
>= 1.0) || (fTransparency
< 0))
681 // Fallback: Transform to DeviceCoordinates
682 basegfx::B2DPolyPolygon
aPolyPolygon(rPolyPolygon
);
683 aPolyPolygon
.transform(rObjectToDevice
);
685 // setup poly-polygon path
686 CGMutablePathRef xPath
= CGPathCreateMutable();
687 // tdf#120252 Use the correct, already transformed PolyPolygon (as long as
688 // the transformation is not used here...)
689 for (auto const& rPolygon
: std::as_const(aPolyPolygon
))
691 AddPolygonToPath(xPath
, rPolygon
, true, !getAntiAlias(), mrShared
.isPenVisible());
694 const CGRect aRefreshRect
= CGPathGetBoundingBox(xPath
);
695 // #i97317# workaround for Quartz having problems with drawing small polygons
696 if (aRefreshRect
.size
.width
> 0.125 || aRefreshRect
.size
.height
> 0.125)
698 // prepare drawing mode
699 CGPathDrawingMode eMode
;
700 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
702 eMode
= kCGPathEOFillStroke
;
704 else if (mrShared
.isPenVisible())
706 eMode
= kCGPathStroke
;
708 else if (mrShared
.isBrushVisible())
710 eMode
= kCGPathEOFill
;
714 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
715 CGPathRelease(xPath
);
719 // use the path to prepare the graphics context
720 mrShared
.maContextHolder
.saveState();
721 CGContextBeginPath(mrShared
.maContextHolder
.get());
722 CGContextAddPath(mrShared
.maContextHolder
.get(), xPath
);
724 // draw path with antialiased polygon
725 CGContextSetShouldAntialias(mrShared
.maContextHolder
.get(), getAntiAlias());
726 CGContextSetAlpha(mrShared
.maContextHolder
.get(), 1.0 - fTransparency
);
727 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
728 mrShared
.maContextHolder
.restoreState();
730 // mark modified rectangle as updated
731 refreshRect(aRefreshRect
);
734 CGPathRelease(xPath
);
737 bool AquaGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix
& rObjectToDevice
,
738 const basegfx::B2DPolygon
& rPolyLine
, double fTransparency
,
740 const std::vector
<double>* pStroke
, // MM01
741 basegfx::B2DLineJoin eLineJoin
,
742 css::drawing::LineCap eLineCap
, double fMiterMinimumAngle
,
743 bool bPixelSnapHairline
)
745 // MM01 check done for simple reasons
746 if (!rPolyLine
.count() || fTransparency
< 0.0 || fTransparency
> 1.0)
752 if (!mrShared
.checkContext())
756 // tdf#124848 get correct LineWidth in discrete coordinates,
757 if (fLineWidth
== 0) // hairline
759 else // Adjust line width for object-to-device scale.
760 fLineWidth
= (rObjectToDevice
* basegfx::B2DVector(fLineWidth
, 0)).getLength();
762 // #i101491# Aqua does not support B2DLineJoin::NONE; return false to use
763 // the fallback (own geometry preparation)
764 // #i104886# linejoin-mode and thus the above only applies to "fat" lines
765 if ((basegfx::B2DLineJoin::NONE
== eLineJoin
) && (fLineWidth
> 1.3))
768 // MM01 need to do line dashing as fallback stuff here now
769 const double fDotDashLength(
770 nullptr != pStroke
? std::accumulate(pStroke
->begin(), pStroke
->end(), 0.0) : 0.0);
771 const bool bStrokeUsed(0.0 != fDotDashLength
);
772 assert(!bStrokeUsed
|| (bStrokeUsed
&& pStroke
));
773 basegfx::B2DPolyPolygon aPolyPolygonLine
;
778 basegfx::utils::applyLineDashing(rPolyLine
, // source
780 &aPolyPolygonLine
, // target for lines
781 nullptr, // target for gaps
782 fDotDashLength
); // full length if available
786 // no line dashing, just copy
787 aPolyPolygonLine
.append(rPolyLine
);
790 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
791 aPolyPolygonLine
.transform(rObjectToDevice
);
792 if (bPixelSnapHairline
)
794 aPolyPolygonLine
= basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine
);
797 // setup line attributes
798 CGLineJoin aCGLineJoin
= kCGLineJoinMiter
;
801 case basegfx::B2DLineJoin::NONE
:
802 aCGLineJoin
= /*TODO?*/ kCGLineJoinMiter
;
804 case basegfx::B2DLineJoin::Bevel
:
805 aCGLineJoin
= kCGLineJoinBevel
;
807 case basegfx::B2DLineJoin::Miter
:
808 aCGLineJoin
= kCGLineJoinMiter
;
810 case basegfx::B2DLineJoin::Round
:
811 aCGLineJoin
= kCGLineJoinRound
;
814 // convert miter minimum angle to miter limit
815 CGFloat fCGMiterLimit
= 1.0 / sin(std::max(fMiterMinimumAngle
, 0.01 * M_PI
) / 2.0);
816 // setup cap attribute
817 CGLineCap
aCGLineCap(kCGLineCapButt
);
821 default: // css::drawing::LineCap_BUTT:
823 aCGLineCap
= kCGLineCapButt
;
826 case css::drawing::LineCap_ROUND
:
828 aCGLineCap
= kCGLineCapRound
;
831 case css::drawing::LineCap_SQUARE
:
833 aCGLineCap
= kCGLineCapSquare
;
838 // setup poly-polygon path
839 CGMutablePathRef xPath
= CGPathCreateMutable();
841 // MM01 todo - I assume that this is OKAY to be done in one run for quartz
842 // but this NEEDS to be checked/verified
843 for (sal_uInt32
a(0); a
< aPolyPolygonLine
.count(); a
++)
845 const basegfx::B2DPolygon
aPolyLine(aPolyPolygonLine
.getB2DPolygon(a
));
846 AddPolygonToPath(xPath
, aPolyLine
, aPolyLine
.isClosed(), !getAntiAlias(), true);
849 const CGRect aRefreshRect
= CGPathGetBoundingBox(xPath
);
850 // #i97317# workaround for Quartz having problems with drawing small polygons
851 if ((aRefreshRect
.size
.width
> 0.125) || (aRefreshRect
.size
.height
> 0.125))
853 // use the path to prepare the graphics context
854 mrShared
.maContextHolder
.saveState();
855 CGContextBeginPath(mrShared
.maContextHolder
.get());
856 CGContextAddPath(mrShared
.maContextHolder
.get(), xPath
);
857 // draw path with antialiased line
858 CGContextSetShouldAntialias(mrShared
.maContextHolder
.get(), getAntiAlias());
859 CGContextSetAlpha(mrShared
.maContextHolder
.get(), 1.0 - fTransparency
);
860 CGContextSetLineJoin(mrShared
.maContextHolder
.get(), aCGLineJoin
);
861 CGContextSetLineCap(mrShared
.maContextHolder
.get(), aCGLineCap
);
862 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), fLineWidth
);
863 CGContextSetMiterLimit(mrShared
.maContextHolder
.get(), fCGMiterLimit
);
864 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathStroke
);
865 mrShared
.maContextHolder
.restoreState();
867 // mark modified rectangle as updated
868 refreshRect(aRefreshRect
);
871 CGPathRelease(xPath
);
876 bool AquaGraphicsBackend::drawPolyLineBezier(sal_uInt32
/*nPoints*/, const Point
* /*pPointArray*/,
877 const PolyFlags
* /*pFlagArray*/)
882 bool AquaGraphicsBackend::drawPolygonBezier(sal_uInt32
/*nPoints*/, const Point
* /*pPointArray*/,
883 const PolyFlags
* /*pFlagArray*/)
888 bool AquaGraphicsBackend::drawPolyPolygonBezier(sal_uInt32
/*nPoly*/, const sal_uInt32
* /*pPoints*/,
889 const Point
* const* /*pPointArray*/,
890 const PolyFlags
* const* /*pFlagArray*/)
895 void AquaGraphicsBackend::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
)
897 if (!mrShared
.checkContext())
900 CGImageRef xImage
= rSalBitmap
.CreateCroppedImage(
901 static_cast<int>(rPosAry
.mnSrcX
), static_cast<int>(rPosAry
.mnSrcY
),
902 static_cast<int>(rPosAry
.mnSrcWidth
), static_cast<int>(rPosAry
.mnSrcHeight
));
906 const CGRect aDstRect
907 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
908 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xImage
);
910 CGImageRelease(xImage
);
911 refreshRect(aDstRect
);
914 void AquaGraphicsBackend::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
,
915 const SalBitmap
& rTransparentBitmap
)
917 if (!mrShared
.checkContext())
920 CGImageRef
xMaskedImage(rSalBitmap
.CreateWithMask(rTransparentBitmap
, rPosAry
.mnSrcX
,
921 rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
,
922 rPosAry
.mnSrcHeight
));
926 const CGRect aDstRect
927 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
928 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xMaskedImage
);
929 CGImageRelease(xMaskedImage
);
930 refreshRect(aDstRect
);
933 void AquaGraphicsBackend::drawMask(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
,
936 if (!mrShared
.checkContext())
939 CGImageRef xImage
= rSalBitmap
.CreateColorMask(
940 rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
, nMaskColor
);
944 const CGRect aDstRect
945 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
946 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xImage
);
947 CGImageRelease(xImage
);
948 refreshRect(aDstRect
);
951 std::shared_ptr
<SalBitmap
> AquaGraphicsBackend::getBitmap(tools::Long nX
, tools::Long nY
,
952 tools::Long nDX
, tools::Long nDY
)
954 SAL_WARN_IF(!mrShared
.maLayer
.isSet(), "vcl.quartz",
955 "AquaSalGraphics::getBitmap() with no layer this=" << this);
957 mrShared
.applyXorContext();
959 std::shared_ptr
<QuartzSalBitmap
> pBitmap
= std::make_shared
<QuartzSalBitmap
>();
960 if (!pBitmap
->Create(mrShared
.maLayer
, mrShared
.mnBitmapDepth
, nX
, nY
, nDX
, nDY
,
961 mrShared
.isFlipped()))
968 Color
AquaGraphicsBackend::getPixel(tools::Long nX
, tools::Long nY
)
970 // return default value on printers or when out of bounds
971 if (!mrShared
.maLayer
.isSet() || (nX
< 0) || (nX
>= mrShared
.mnWidth
) || (nY
< 0)
972 || (nY
>= mrShared
.mnHeight
))
977 // prepare creation of matching a CGBitmapContext
978 #if defined OSL_BIGENDIAN
981 unsigned char b
, g
, r
, a
;
986 unsigned char a
, r
, g
, b
;
990 // create a one-pixel bitmap context
991 // TODO: is it worth to cache it?
992 CGContextRef xOnePixelContext
= CGBitmapContextCreate(
993 &aPixel
, 1, 1, 8, 32, GetSalData()->mxRGBSpace
,
994 uint32_t(kCGImageAlphaNoneSkipFirst
) | uint32_t(kCGBitmapByteOrder32Big
));
996 // update this graphics layer
997 mrShared
.applyXorContext();
999 // copy the requested pixel into the bitmap context
1000 if (mrShared
.isFlipped())
1002 nY
= mrShared
.mnHeight
- nY
;
1004 const CGPoint aCGPoint
= CGPointMake(-nX
, -nY
);
1005 CGContextDrawLayerAtPoint(xOnePixelContext
, aCGPoint
, mrShared
.maLayer
.get());
1007 CGContextRelease(xOnePixelContext
);
1009 Color
nColor(aPixel
.r
, aPixel
.g
, aPixel
.b
);
1013 void AquaSalGraphics::GetResolution(sal_Int32
& rDPIX
, sal_Int32
& rDPIY
)
1018 initResolution((maShared
.mbWindow
&& maShared
.mpFrame
) ? maShared
.mpFrame
->getNSWindow()
1025 // This *must* be 96 or else the iOS app will behave very badly (tiles are scaled wrongly and
1026 // don't match each others at their boundaries, and other issues). But *why* it must be 96 I
1027 // have no idea. The commit that changed it to 96 from (the arbitrary) 200 did not say. If you
1028 // know where else 96 is explicitly or implicitly hard-coded, please modify this comment.
1030 // Follow-up: It might be this: in 'online', loleaflet/src/map/Map.js:
1031 // 15 = 1440 twips-per-inch / 96 dpi.
1032 // Chosen to match previous hardcoded value of 3840 for
1033 // the current tile pixel size of 256.
1038 void AquaGraphicsBackend::pattern50Fill()
1040 static const CGFloat aFillCol
[4] = { 1, 1, 1, 1 };
1041 static const CGPatternCallbacks aCallback
= { 0, &drawPattern50
, nullptr };
1042 static const CGColorSpaceRef mxP50Space
= CGColorSpaceCreatePattern(GetSalData()->mxRGBSpace
);
1043 static const CGPatternRef mxP50Pattern
1044 = CGPatternCreate(nullptr, CGRectMake(0, 0, 4, 4), CGAffineTransformIdentity
, 4, 4,
1045 kCGPatternTilingConstantSpacing
, false, &aCallback
);
1046 SAL_WARN_IF(!mrShared
.maContextHolder
.get(), "vcl.quartz", "maContextHolder.get() is NULL");
1047 CGContextSetFillColorSpace(mrShared
.maContextHolder
.get(), mxP50Space
);
1048 CGContextSetFillPattern(mrShared
.maContextHolder
.get(), mxP50Pattern
, aFillCol
);
1049 CGContextFillPath(mrShared
.maContextHolder
.get());
1052 void AquaGraphicsBackend::invert(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1053 tools::Long nHeight
, SalInvert nFlags
)
1055 if (mrShared
.checkContext())
1057 CGRect aCGRect
= CGRectMake(nX
, nY
, nWidth
, nHeight
);
1058 mrShared
.maContextHolder
.saveState();
1059 if (nFlags
& SalInvert::TrackFrame
)
1061 const CGFloat dashLengths
[2] = { 4.0, 4.0 }; // for drawing dashed line
1062 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1063 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1064 CGContextSetLineDash(mrShared
.maContextHolder
.get(), 0, dashLengths
, 2);
1065 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), 2.0);
1066 CGContextStrokeRect(mrShared
.maContextHolder
.get(), aCGRect
);
1068 else if (nFlags
& SalInvert::N50
)
1070 //CGContextSetAllowsAntialiasing( maContextHolder.get(), false );
1071 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1072 CGContextAddRect(mrShared
.maContextHolder
.get(), aCGRect
);
1077 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1078 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1079 CGContextFillRect(mrShared
.maContextHolder
.get(), aCGRect
);
1081 mrShared
.maContextHolder
.restoreState();
1082 refreshRect(aCGRect
);
1088 CGPoint
* makeCGptArray(sal_uInt32 nPoints
, const Point
* pPtAry
)
1090 CGPoint
* CGpoints
= new CGPoint
[nPoints
];
1091 for (sal_uLong i
= 0; i
< nPoints
; i
++)
1093 CGpoints
[i
].x
= pPtAry
[i
].getX();
1094 CGpoints
[i
].y
= pPtAry
[i
].getY();
1099 } // end anonymous ns
1101 void AquaGraphicsBackend::invert(sal_uInt32 nPoints
, const Point
* pPtAry
, SalInvert nSalFlags
)
1103 if (mrShared
.checkContext())
1105 mrShared
.maContextHolder
.saveState();
1106 CGPoint
* CGpoints
= makeCGptArray(nPoints
, pPtAry
);
1107 CGContextAddLines(mrShared
.maContextHolder
.get(), CGpoints
, nPoints
);
1108 if (nSalFlags
& SalInvert::TrackFrame
)
1110 const CGFloat dashLengths
[2] = { 4.0, 4.0 }; // for drawing dashed line
1111 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1112 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1113 CGContextSetLineDash(mrShared
.maContextHolder
.get(), 0, dashLengths
, 2);
1114 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), 2.0);
1115 CGContextStrokePath(mrShared
.maContextHolder
.get());
1117 else if (nSalFlags
& SalInvert::N50
)
1119 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1124 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1125 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1126 CGContextFillPath(mrShared
.maContextHolder
.get());
1128 const CGRect aRefreshRect
= CGContextGetClipBoundingBox(mrShared
.maContextHolder
.get());
1129 mrShared
.maContextHolder
.restoreState();
1131 refreshRect(aRefreshRect
);
1136 bool AquaGraphicsBackend::drawEPS(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1137 tools::Long nHeight
, void* pEpsData
, sal_uInt32 nByteCount
)
1139 // convert the raw data to an NSImageRef
1140 NSData
* xNSData
= [NSData dataWithBytes
:pEpsData length
:static_cast<int>(nByteCount
)];
1141 SAL_WNODEPRECATED_DECLARATIONS_PUSH
1142 // 'NSEPSImageRep' is deprecated: first deprecated in macOS 14.0 - `NSEPSImageRep` instances
1143 // cannot be created on macOS 14.0 and later
1144 NSImageRep
* xEpsImage
= [NSEPSImageRep imageRepWithData
:xNSData
];
1145 SAL_WNODEPRECATED_DECLARATIONS_POP
1150 // get the target context
1151 if (!mrShared
.checkContext())
1155 // NOTE: flip drawing, else the nsimage would be drawn upside down
1156 mrShared
.maContextHolder
.saveState();
1157 // CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight );
1158 CGContextScaleCTM(mrShared
.maContextHolder
.get(), +1, -1);
1159 nY
= /*mnHeight*/ -(nY
+ nHeight
);
1161 // prepare the target context
1162 NSGraphicsContext
* pOrigNSCtx
= [NSGraphicsContext currentContext
];
1163 [pOrigNSCtx retain
];
1165 // create new context
1166 NSGraphicsContext
* pDrawNSCtx
=
1167 [NSGraphicsContext graphicsContextWithCGContext
:mrShared
.maContextHolder
.get()
1168 flipped
:mrShared
.isFlipped()];
1169 // set it, setCurrentContext also releases the previously set one
1170 [NSGraphicsContext setCurrentContext
:pDrawNSCtx
];
1173 const NSRect aDstRect
= NSMakeRect(nX
, nY
, nWidth
, nHeight
);
1174 const bool bOK
= [xEpsImage drawInRect
:aDstRect
];
1176 // restore the NSGraphicsContext
1177 [NSGraphicsContext setCurrentContext
:pOrigNSCtx
];
1178 [pOrigNSCtx release
]; // restore the original retain count
1180 mrShared
.maContextHolder
.restoreState();
1181 // mark the destination rectangle as updated
1182 refreshRect(aDstRect
);
1187 bool AquaGraphicsBackend::drawEPS(tools::Long
/*nX*/, tools::Long
/*nY*/, tools::Long
/*nWidth*/,
1188 tools::Long
/*nHeight*/, void* /*pEpsData*/,
1189 sal_uInt32
/*nByteCount*/)
1195 bool AquaGraphicsBackend::blendBitmap(const SalTwoRect
& /*rPosAry*/, const SalBitmap
& /*rBitmap*/)
1200 bool AquaGraphicsBackend::blendAlphaBitmap(const SalTwoRect
& /*rPosAry*/,
1201 const SalBitmap
& /*rSrcBitmap*/,
1202 const SalBitmap
& /*rMaskBitmap*/,
1203 const SalBitmap
& /*rAlphaBitmap*/)
1208 bool AquaGraphicsBackend::drawAlphaBitmap(const SalTwoRect
& rTR
, const SalBitmap
& rSrcBitmap
,
1209 const SalBitmap
& rAlphaBmp
)
1211 // An image mask can't have a depth > 8 bits (should be 1 to 8 bits)
1212 if (rAlphaBmp
.GetBitCount() > 8)
1215 // are these two tests really necessary? (see vcl/unx/source/gdi/salgdi2.cxx)
1216 // horizontal/vertical mirroring not implemented yet
1217 if (rTR
.mnDestWidth
< 0 || rTR
.mnDestHeight
< 0)
1220 CGImageRef xMaskedImage
= rSrcBitmap
.CreateWithMask(rAlphaBmp
, rTR
.mnSrcX
, rTR
.mnSrcY
,
1221 rTR
.mnSrcWidth
, rTR
.mnSrcHeight
);
1225 if (mrShared
.checkContext())
1227 const CGRect aDstRect
1228 = CGRectMake(rTR
.mnDestX
, rTR
.mnDestY
, rTR
.mnDestWidth
, rTR
.mnDestHeight
);
1229 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xMaskedImage
);
1230 refreshRect(aDstRect
);
1233 CGImageRelease(xMaskedImage
);
1238 bool AquaGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint
& rNull
,
1239 const basegfx::B2DPoint
& rX
,
1240 const basegfx::B2DPoint
& rY
,
1241 const SalBitmap
& rSrcBitmap
,
1242 const SalBitmap
* pAlphaBmp
, double fAlpha
)
1244 if (!mrShared
.checkContext())
1250 // get the Quartz image
1251 CGImageRef xImage
= nullptr;
1252 const Size aSize
= rSrcBitmap
.GetSize();
1255 xImage
= rSrcBitmap
.CreateCroppedImage(0, 0, int(aSize
.Width()), int(aSize
.Height()));
1258 = rSrcBitmap
.CreateWithMask(*pAlphaBmp
, 0, 0, int(aSize
.Width()), int(aSize
.Height()));
1263 // setup the image transformation
1264 // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
1265 mrShared
.maContextHolder
.saveState();
1266 const basegfx::B2DVector aXRel
= rX
- rNull
;
1267 const basegfx::B2DVector aYRel
= rY
- rNull
;
1268 const CGAffineTransform aCGMat
= CGAffineTransformMake(
1269 aXRel
.getX() / aSize
.Width(), aXRel
.getY() / aSize
.Width(), aYRel
.getX() / aSize
.Height(),
1270 aYRel
.getY() / aSize
.Height(), rNull
.getX(), rNull
.getY());
1272 CGContextConcatCTM(mrShared
.maContextHolder
.get(), aCGMat
);
1274 // draw the transformed image
1275 const CGRect aSrcRect
= CGRectMake(0, 0, aSize
.Width(), aSize
.Height());
1276 CGContextDrawImage(mrShared
.maContextHolder
.get(), aSrcRect
, xImage
);
1278 CGImageRelease(xImage
);
1280 // restore the Quartz graphics state
1281 mrShared
.maContextHolder
.restoreState();
1283 // mark the destination as painted
1284 const CGRect aDstRect
= CGRectApplyAffineTransform(aSrcRect
, aCGMat
);
1285 refreshRect(aDstRect
);
1290 bool AquaGraphicsBackend::drawAlphaRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1291 tools::Long nHeight
, sal_uInt8 nTransparency
)
1293 if (!mrShared
.checkContext())
1296 // save the current state
1297 mrShared
.maContextHolder
.saveState();
1298 CGContextSetAlpha(mrShared
.maContextHolder
.get(), (100 - nTransparency
) * (1.0 / 100));
1300 CGRect aRect
= CGRectMake(nX
, nY
, nWidth
- 1, nHeight
- 1);
1301 if (mrShared
.isPenVisible())
1303 aRect
.origin
.x
+= 0.5;
1304 aRect
.origin
.y
+= 0.5;
1307 CGContextBeginPath(mrShared
.maContextHolder
.get());
1308 CGContextAddRect(mrShared
.maContextHolder
.get(), aRect
);
1309 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathFill
);
1311 mrShared
.maContextHolder
.restoreState();
1317 bool AquaGraphicsBackend::drawGradient(const tools::PolyPolygon
& /*rPolygon*/,
1318 const Gradient
& /*rGradient*/)
1323 bool AquaGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon
const& /*rPolyPolygon*/,
1324 SalGradient
const& /*rGradient*/)
1329 bool AquaGraphicsBackend::supportsOperation(OutDevSupportType
/*eType*/) const { return false; }
1331 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */