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::Init() {}
200 void AquaGraphicsBackend::freeResources() {}
202 void AquaGraphicsBackend::setClipRegion(vcl::Region
const& rRegion
)
204 // release old clip path
205 mrShared
.unsetClipPath();
206 mrShared
.mxClipPath
= CGPathCreateMutable();
208 // set current path, either as polypolgon or sequence of rectangles
209 RectangleVector aRectangles
;
210 rRegion
.GetRegionRectangles(aRectangles
);
212 for (const auto& rRect
: aRectangles
)
214 const tools::Long
nW(rRect
.Right() - rRect
.Left() + 1); // uses +1 logic in original
218 const tools::Long
nH(rRect
.Bottom() - rRect
.Top() + 1); // uses +1 logic in original
222 const CGRect aRect
= CGRectMake(rRect
.Left(), rRect
.Top(), nW
, nH
);
223 CGPathAddRect(mrShared
.mxClipPath
, nullptr, aRect
);
227 // set the current path as clip region
228 if (mrShared
.checkContext())
232 void AquaGraphicsBackend::ResetClipRegion()
234 // release old path and indicate no clipping
235 mrShared
.unsetClipPath();
237 if (mrShared
.checkContext())
243 sal_uInt16
AquaGraphicsBackend::GetBitCount() const
245 sal_uInt16 nBits
= mrShared
.mnBitmapDepth
? mrShared
.mnBitmapDepth
: 32; //24;
249 tools::Long
AquaGraphicsBackend::GetGraphicsWidth() const
251 tools::Long width
= 0;
252 if (mrShared
.maContextHolder
.isSet()
259 width
= mrShared
.mnWidth
;
265 if (mrShared
.mbWindow
&& mrShared
.mpFrame
)
267 width
= mrShared
.mpFrame
->maGeometry
.width();
274 void AquaGraphicsBackend::SetLineColor()
276 mrShared
.maLineColor
.SetAlpha(0.0); // transparent
277 if (mrShared
.checkContext())
279 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), mrShared
.maLineColor
.GetRed(),
280 mrShared
.maLineColor
.GetGreen(), mrShared
.maLineColor
.GetBlue(),
281 mrShared
.maLineColor
.GetAlpha());
285 void AquaGraphicsBackend::SetLineColor(Color nColor
)
287 mrShared
.maLineColor
= RGBAColor(nColor
);
288 if (mrShared
.checkContext())
290 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), mrShared
.maLineColor
.GetRed(),
291 mrShared
.maLineColor
.GetGreen(), mrShared
.maLineColor
.GetBlue(),
292 mrShared
.maLineColor
.GetAlpha());
296 void AquaGraphicsBackend::SetFillColor()
298 mrShared
.maFillColor
.SetAlpha(0.0); // transparent
299 if (mrShared
.checkContext())
301 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.GetRed(),
302 mrShared
.maFillColor
.GetGreen(), mrShared
.maFillColor
.GetBlue(),
303 mrShared
.maFillColor
.GetAlpha());
307 void AquaGraphicsBackend::SetFillColor(Color nColor
)
309 mrShared
.maFillColor
= RGBAColor(nColor
);
310 if (mrShared
.checkContext())
312 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.GetRed(),
313 mrShared
.maFillColor
.GetGreen(), mrShared
.maFillColor
.GetBlue(),
314 mrShared
.maFillColor
.GetAlpha());
318 void AquaGraphicsBackend::SetXORMode(bool bSet
, bool bInvertOnly
)
320 // return early if XOR mode remains unchanged
321 if (mrShared
.mbPrinter
)
325 if (!bSet
&& mrShared
.mnXorMode
== 2)
327 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeNormal
);
328 mrShared
.mnXorMode
= 0;
331 else if (bSet
&& bInvertOnly
&& mrShared
.mnXorMode
== 0)
333 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
334 mrShared
.mnXorMode
= 2;
338 if (!mrShared
.mpXorEmulation
&& !bSet
)
342 if (mrShared
.mpXorEmulation
&& bSet
== mrShared
.mpXorEmulation
->IsEnabled())
346 if (!mrShared
.checkContext())
350 // prepare XOR emulation
351 if (!mrShared
.mpXorEmulation
)
353 mrShared
.mpXorEmulation
= std::make_unique
<XorEmulation
>();
354 mrShared
.mpXorEmulation
->SetTarget(mrShared
.mnWidth
, mrShared
.mnHeight
,
355 mrShared
.mnBitmapDepth
, mrShared
.maContextHolder
.get(),
356 mrShared
.maLayer
.get());
359 // change the XOR mode
362 mrShared
.mpXorEmulation
->Enable();
363 mrShared
.maContextHolder
.set(mrShared
.mpXorEmulation
->GetMaskContext());
364 mrShared
.mnXorMode
= 1;
368 mrShared
.mpXorEmulation
->UpdateTarget();
369 mrShared
.mpXorEmulation
->Disable();
370 mrShared
.maContextHolder
.set(mrShared
.mpXorEmulation
->GetTargetContext());
371 mrShared
.mnXorMode
= 0;
375 void AquaGraphicsBackend::SetROPFillColor(SalROPColor nROPColor
)
377 if (!mrShared
.mbPrinter
)
379 SetFillColor(ImplGetROPColor(nROPColor
));
383 void AquaGraphicsBackend::SetROPLineColor(SalROPColor nROPColor
)
385 if (!mrShared
.mbPrinter
)
387 SetLineColor(ImplGetROPColor(nROPColor
));
391 void AquaGraphicsBackend::drawPixelImpl(tools::Long nX
, tools::Long nY
, const RGBAColor
& rColor
)
393 if (!mrShared
.checkContext())
396 // overwrite the fill color
397 CGContextSetFillColor(mrShared
.maContextHolder
.get(), rColor
.AsArray());
398 // draw 1x1 rect, there is no pixel drawing in Quartz
399 const CGRect aDstRect
= CGRectMake(nX
, nY
, 1, 1);
400 CGContextFillRect(mrShared
.maContextHolder
.get(), aDstRect
);
402 refreshRect(aDstRect
);
404 // reset the fill color
405 CGContextSetFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.AsArray());
408 void AquaGraphicsBackend::drawPixel(tools::Long nX
, tools::Long nY
)
410 // draw pixel with current line color
411 drawPixelImpl(nX
, nY
, mrShared
.maLineColor
);
414 void AquaGraphicsBackend::drawPixel(tools::Long nX
, tools::Long nY
, Color nColor
)
416 const RGBAColor
aPixelColor(nColor
);
417 drawPixelImpl(nX
, nY
, aPixelColor
);
420 void AquaGraphicsBackend::drawLine(tools::Long nX1
, tools::Long nY1
, tools::Long nX2
,
423 if (nX1
== nX2
&& nY1
== nY2
)
425 // #i109453# platform independent code expects at least one pixel to be drawn
430 if (!mrShared
.checkContext())
433 CGContextBeginPath(mrShared
.maContextHolder
.get());
434 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), float(nX1
) + 0.5, float(nY1
) + 0.5);
435 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), float(nX2
) + 0.5, float(nY2
) + 0.5);
436 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathStroke
);
438 tools::Rectangle
aRefreshRect(nX1
, nY1
, nX2
, nY2
);
440 // Is a call to RefreshRect( aRefreshRect ) missing here?
443 void AquaGraphicsBackend::drawRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
446 if (!mrShared
.checkContext())
449 CGRect aRect
= CGRectMake(nX
, nY
, nWidth
, nHeight
);
450 if (mrShared
.isPenVisible())
452 aRect
.origin
.x
+= 0.5;
453 aRect
.origin
.y
+= 0.5;
454 aRect
.size
.width
-= 1;
455 aRect
.size
.height
-= 1;
458 if (mrShared
.isBrushVisible())
460 CGContextFillRect(mrShared
.maContextHolder
.get(), aRect
);
462 if (mrShared
.isPenVisible())
464 CGContextStrokeRect(mrShared
.maContextHolder
.get(), aRect
);
466 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
469 void AquaGraphicsBackend::drawPolyLine(sal_uInt32 nPoints
, const Point
* pPointArray
)
474 if (!mrShared
.checkContext())
477 tools::Long nX
= 0, nY
= 0, nWidth
= 0, nHeight
= 0;
478 getBoundRect(nPoints
, pPointArray
, nX
, nY
, nWidth
, nHeight
);
481 CGContextBeginPath(mrShared
.maContextHolder
.get());
482 alignLinePoint(pPointArray
, fX
, fY
);
483 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
486 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
488 alignLinePoint(pPointArray
, fX
, fY
);
489 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
491 CGContextStrokePath(mrShared
.maContextHolder
.get());
493 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
496 void AquaGraphicsBackend::drawPolygon(sal_uInt32 nPoints
, const Point
* pPointArray
)
501 if (!mrShared
.checkContext())
504 tools::Long nX
= 0, nY
= 0, nWidth
= 0, nHeight
= 0;
505 getBoundRect(nPoints
, pPointArray
, nX
, nY
, nWidth
, nHeight
);
507 CGPathDrawingMode eMode
;
508 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
510 eMode
= kCGPathEOFillStroke
;
512 else if (mrShared
.isPenVisible())
514 eMode
= kCGPathStroke
;
516 else if (mrShared
.isBrushVisible())
518 eMode
= kCGPathEOFill
;
522 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
526 CGContextBeginPath(mrShared
.maContextHolder
.get());
528 if (mrShared
.isPenVisible())
531 alignLinePoint(pPointArray
, fX
, fY
);
532 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
534 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
536 alignLinePoint(pPointArray
, fX
, fY
);
537 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
542 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), pPointArray
->getX(),
543 pPointArray
->getY());
545 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
547 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), pPointArray
->getX(),
548 pPointArray
->getY());
552 CGContextClosePath(mrShared
.maContextHolder
.get());
553 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
555 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
558 void AquaGraphicsBackend::drawPolyPolygon(sal_uInt32 nPolyCount
, const sal_uInt32
* pPoints
,
559 const Point
** ppPtAry
)
564 if (!mrShared
.checkContext())
568 tools::Long leftX
= 0, topY
= 0, maxWidth
= 0, maxHeight
= 0;
570 getBoundRect(pPoints
[0], ppPtAry
[0], leftX
, topY
, maxWidth
, maxHeight
);
572 for (sal_uInt32 n
= 1; n
< nPolyCount
; n
++)
574 tools::Long nX
= leftX
, nY
= topY
, nW
= maxWidth
, nH
= maxHeight
;
575 getBoundRect(pPoints
[n
], ppPtAry
[n
], nX
, nY
, nW
, nH
);
578 maxWidth
+= leftX
- nX
;
583 maxHeight
+= topY
- nY
;
586 if (nX
+ nW
> leftX
+ maxWidth
)
588 maxWidth
= nX
+ nW
- leftX
;
590 if (nY
+ nH
> topY
+ maxHeight
)
592 maxHeight
= nY
+ nH
- topY
;
596 // prepare drawing mode
597 CGPathDrawingMode eMode
;
598 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
600 eMode
= kCGPathEOFillStroke
;
602 else if (mrShared
.isPenVisible())
604 eMode
= kCGPathStroke
;
606 else if (mrShared
.isBrushVisible())
608 eMode
= kCGPathEOFill
;
612 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
617 CGContextBeginPath(mrShared
.maContextHolder
.get());
618 if (mrShared
.isPenVisible())
620 for (sal_uInt32 nPoly
= 0; nPoly
< nPolyCount
; nPoly
++)
622 const sal_uInt32 nPoints
= pPoints
[nPoly
];
625 const Point
* pPtAry
= ppPtAry
[nPoly
];
628 alignLinePoint(pPtAry
, fX
, fY
);
629 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
632 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPtAry
++)
634 alignLinePoint(pPtAry
, fX
, fY
);
635 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
637 CGContextClosePath(mrShared
.maContextHolder
.get());
643 for (sal_uInt32 nPoly
= 0; nPoly
< nPolyCount
; nPoly
++)
645 const sal_uInt32 nPoints
= pPoints
[nPoly
];
648 const Point
* pPtAry
= ppPtAry
[nPoly
];
649 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), pPtAry
->getX(),
652 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPtAry
++)
654 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), pPtAry
->getX(),
657 CGContextClosePath(mrShared
.maContextHolder
.get());
662 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
664 mrShared
.refreshRect(leftX
, topY
, maxWidth
, maxHeight
);
667 void AquaGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix
& rObjectToDevice
,
668 const basegfx::B2DPolyPolygon
& rPolyPolygon
,
669 double fTransparency
)
672 if (!mrShared
.maContextHolder
.isSet())
676 // short circuit if there is nothing to do
677 if (rPolyPolygon
.count() == 0)
680 // ignore invisible polygons
681 if ((fTransparency
>= 1.0) || (fTransparency
< 0))
684 // Fallback: Transform to DeviceCoordinates
685 basegfx::B2DPolyPolygon
aPolyPolygon(rPolyPolygon
);
686 aPolyPolygon
.transform(rObjectToDevice
);
688 // setup poly-polygon path
689 CGMutablePathRef xPath
= CGPathCreateMutable();
690 // tdf#120252 Use the correct, already transformed PolyPolygon (as long as
691 // the transformation is not used here...)
692 for (auto const& rPolygon
: std::as_const(aPolyPolygon
))
694 AddPolygonToPath(xPath
, rPolygon
, true, !getAntiAlias(), mrShared
.isPenVisible());
697 const CGRect aRefreshRect
= CGPathGetBoundingBox(xPath
);
698 // #i97317# workaround for Quartz having problems with drawing small polygons
699 if (aRefreshRect
.size
.width
> 0.125 || aRefreshRect
.size
.height
> 0.125)
701 // prepare drawing mode
702 CGPathDrawingMode eMode
;
703 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
705 eMode
= kCGPathEOFillStroke
;
707 else if (mrShared
.isPenVisible())
709 eMode
= kCGPathStroke
;
711 else if (mrShared
.isBrushVisible())
713 eMode
= kCGPathEOFill
;
717 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
718 CGPathRelease(xPath
);
722 // use the path to prepare the graphics context
723 mrShared
.maContextHolder
.saveState();
724 CGContextBeginPath(mrShared
.maContextHolder
.get());
725 CGContextAddPath(mrShared
.maContextHolder
.get(), xPath
);
727 // draw path with antialiased polygon
728 CGContextSetShouldAntialias(mrShared
.maContextHolder
.get(), getAntiAlias());
729 CGContextSetAlpha(mrShared
.maContextHolder
.get(), 1.0 - fTransparency
);
730 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
731 mrShared
.maContextHolder
.restoreState();
733 // mark modified rectangle as updated
734 refreshRect(aRefreshRect
);
737 CGPathRelease(xPath
);
740 bool AquaGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix
& rObjectToDevice
,
741 const basegfx::B2DPolygon
& rPolyLine
, double fTransparency
,
743 const std::vector
<double>* pStroke
, // MM01
744 basegfx::B2DLineJoin eLineJoin
,
745 css::drawing::LineCap eLineCap
, double fMiterMinimumAngle
,
746 bool bPixelSnapHairline
)
748 // MM01 check done for simple reasons
749 if (!rPolyLine
.count() || fTransparency
< 0.0 || fTransparency
> 1.0)
755 if (!mrShared
.checkContext())
759 // tdf#124848 get correct LineWidth in discrete coordinates,
760 if (fLineWidth
== 0) // hairline
762 else // Adjust line width for object-to-device scale.
763 fLineWidth
= (rObjectToDevice
* basegfx::B2DVector(fLineWidth
, 0)).getLength();
765 // #i101491# Aqua does not support B2DLineJoin::NONE; return false to use
766 // the fallback (own geometry preparation)
767 // #i104886# linejoin-mode and thus the above only applies to "fat" lines
768 if ((basegfx::B2DLineJoin::NONE
== eLineJoin
) && (fLineWidth
> 1.3))
771 // MM01 need to do line dashing as fallback stuff here now
772 const double fDotDashLength(
773 nullptr != pStroke
? std::accumulate(pStroke
->begin(), pStroke
->end(), 0.0) : 0.0);
774 const bool bStrokeUsed(0.0 != fDotDashLength
);
775 assert(!bStrokeUsed
|| (bStrokeUsed
&& pStroke
));
776 basegfx::B2DPolyPolygon aPolyPolygonLine
;
781 basegfx::utils::applyLineDashing(rPolyLine
, // source
783 &aPolyPolygonLine
, // target for lines
784 nullptr, // target for gaps
785 fDotDashLength
); // full length if available
789 // no line dashing, just copy
790 aPolyPolygonLine
.append(rPolyLine
);
793 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
794 aPolyPolygonLine
.transform(rObjectToDevice
);
795 if (bPixelSnapHairline
)
797 aPolyPolygonLine
= basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine
);
800 // setup line attributes
801 CGLineJoin aCGLineJoin
= kCGLineJoinMiter
;
804 case basegfx::B2DLineJoin::NONE
:
805 aCGLineJoin
= /*TODO?*/ kCGLineJoinMiter
;
807 case basegfx::B2DLineJoin::Bevel
:
808 aCGLineJoin
= kCGLineJoinBevel
;
810 case basegfx::B2DLineJoin::Miter
:
811 aCGLineJoin
= kCGLineJoinMiter
;
813 case basegfx::B2DLineJoin::Round
:
814 aCGLineJoin
= kCGLineJoinRound
;
817 // convert miter minimum angle to miter limit
818 CGFloat fCGMiterLimit
= 1.0 / sin(std::max(fMiterMinimumAngle
, 0.01 * M_PI
) / 2.0);
819 // setup cap attribute
820 CGLineCap
aCGLineCap(kCGLineCapButt
);
824 default: // css::drawing::LineCap_BUTT:
826 aCGLineCap
= kCGLineCapButt
;
829 case css::drawing::LineCap_ROUND
:
831 aCGLineCap
= kCGLineCapRound
;
834 case css::drawing::LineCap_SQUARE
:
836 aCGLineCap
= kCGLineCapSquare
;
841 // setup poly-polygon path
842 CGMutablePathRef xPath
= CGPathCreateMutable();
844 // MM01 todo - I assume that this is OKAY to be done in one run for quartz
845 // but this NEEDS to be checked/verified
846 for (sal_uInt32
a(0); a
< aPolyPolygonLine
.count(); a
++)
848 const basegfx::B2DPolygon
aPolyLine(aPolyPolygonLine
.getB2DPolygon(a
));
849 AddPolygonToPath(xPath
, aPolyLine
, aPolyLine
.isClosed(), !getAntiAlias(), true);
852 const CGRect aRefreshRect
= CGPathGetBoundingBox(xPath
);
853 // #i97317# workaround for Quartz having problems with drawing small polygons
854 if ((aRefreshRect
.size
.width
> 0.125) || (aRefreshRect
.size
.height
> 0.125))
856 // use the path to prepare the graphics context
857 mrShared
.maContextHolder
.saveState();
858 CGContextBeginPath(mrShared
.maContextHolder
.get());
859 CGContextAddPath(mrShared
.maContextHolder
.get(), xPath
);
860 // draw path with antialiased line
861 CGContextSetShouldAntialias(mrShared
.maContextHolder
.get(), getAntiAlias());
862 CGContextSetAlpha(mrShared
.maContextHolder
.get(), 1.0 - fTransparency
);
863 CGContextSetLineJoin(mrShared
.maContextHolder
.get(), aCGLineJoin
);
864 CGContextSetLineCap(mrShared
.maContextHolder
.get(), aCGLineCap
);
865 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), fLineWidth
);
866 CGContextSetMiterLimit(mrShared
.maContextHolder
.get(), fCGMiterLimit
);
867 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathStroke
);
868 mrShared
.maContextHolder
.restoreState();
870 // mark modified rectangle as updated
871 refreshRect(aRefreshRect
);
874 CGPathRelease(xPath
);
879 bool AquaGraphicsBackend::drawPolyLineBezier(sal_uInt32
/*nPoints*/, const Point
* /*pPointArray*/,
880 const PolyFlags
* /*pFlagArray*/)
885 bool AquaGraphicsBackend::drawPolygonBezier(sal_uInt32
/*nPoints*/, const Point
* /*pPointArray*/,
886 const PolyFlags
* /*pFlagArray*/)
891 bool AquaGraphicsBackend::drawPolyPolygonBezier(sal_uInt32
/*nPoly*/, const sal_uInt32
* /*pPoints*/,
892 const Point
* const* /*pPointArray*/,
893 const PolyFlags
* const* /*pFlagArray*/)
898 void AquaGraphicsBackend::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
)
900 if (!mrShared
.checkContext())
903 CGImageRef xImage
= rSalBitmap
.CreateCroppedImage(
904 static_cast<int>(rPosAry
.mnSrcX
), static_cast<int>(rPosAry
.mnSrcY
),
905 static_cast<int>(rPosAry
.mnSrcWidth
), static_cast<int>(rPosAry
.mnSrcHeight
));
909 const CGRect aDstRect
910 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
911 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xImage
);
913 CGImageRelease(xImage
);
914 refreshRect(aDstRect
);
917 void AquaGraphicsBackend::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
,
918 const SalBitmap
& rTransparentBitmap
)
920 if (!mrShared
.checkContext())
923 CGImageRef
xMaskedImage(rSalBitmap
.CreateWithMask(rTransparentBitmap
, rPosAry
.mnSrcX
,
924 rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
,
925 rPosAry
.mnSrcHeight
));
929 const CGRect aDstRect
930 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
931 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xMaskedImage
);
932 CGImageRelease(xMaskedImage
);
933 refreshRect(aDstRect
);
936 void AquaGraphicsBackend::drawMask(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
,
939 if (!mrShared
.checkContext())
942 CGImageRef xImage
= rSalBitmap
.CreateColorMask(
943 rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
, nMaskColor
);
947 const CGRect aDstRect
948 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
949 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xImage
);
950 CGImageRelease(xImage
);
951 refreshRect(aDstRect
);
954 std::shared_ptr
<SalBitmap
> AquaGraphicsBackend::getBitmap(tools::Long nX
, tools::Long nY
,
955 tools::Long nDX
, tools::Long nDY
)
957 SAL_WARN_IF(!mrShared
.maLayer
.isSet(), "vcl.quartz",
958 "AquaSalGraphics::getBitmap() with no layer this=" << this);
960 mrShared
.applyXorContext();
962 std::shared_ptr
<QuartzSalBitmap
> pBitmap
= std::make_shared
<QuartzSalBitmap
>();
963 if (!pBitmap
->Create(mrShared
.maLayer
, mrShared
.mnBitmapDepth
, nX
, nY
, nDX
, nDY
,
964 mrShared
.isFlipped()))
971 Color
AquaGraphicsBackend::getPixel(tools::Long nX
, tools::Long nY
)
973 // return default value on printers or when out of bounds
974 if (!mrShared
.maLayer
.isSet() || (nX
< 0) || (nX
>= mrShared
.mnWidth
) || (nY
< 0)
975 || (nY
>= mrShared
.mnHeight
))
980 // prepare creation of matching a CGBitmapContext
981 #if defined OSL_BIGENDIAN
984 unsigned char b
, g
, r
, a
;
989 unsigned char a
, r
, g
, b
;
993 // create a one-pixel bitmap context
994 // TODO: is it worth to cache it?
995 CGContextRef xOnePixelContext
= CGBitmapContextCreate(
996 &aPixel
, 1, 1, 8, 32, GetSalData()->mxRGBSpace
,
997 uint32_t(kCGImageAlphaNoneSkipFirst
) | uint32_t(kCGBitmapByteOrder32Big
));
999 // update this graphics layer
1000 mrShared
.applyXorContext();
1002 // copy the requested pixel into the bitmap context
1003 if (mrShared
.isFlipped())
1005 nY
= mrShared
.mnHeight
- nY
;
1007 const CGPoint aCGPoint
= CGPointMake(-nX
, -nY
);
1008 CGContextDrawLayerAtPoint(xOnePixelContext
, aCGPoint
, mrShared
.maLayer
.get());
1010 CGContextRelease(xOnePixelContext
);
1012 Color
nColor(aPixel
.r
, aPixel
.g
, aPixel
.b
);
1016 void AquaSalGraphics::GetResolution(sal_Int32
& rDPIX
, sal_Int32
& rDPIY
)
1021 initResolution((maShared
.mbWindow
&& maShared
.mpFrame
) ? maShared
.mpFrame
->getNSWindow()
1028 // This *must* be 96 or else the iOS app will behave very badly (tiles are scaled wrongly and
1029 // don't match each others at their boundaries, and other issues). But *why* it must be 96 I
1030 // have no idea. The commit that changed it to 96 from (the arbitrary) 200 did not say. If you
1031 // know where else 96 is explicitly or implicitly hard-coded, please modify this comment.
1033 // Follow-up: It might be this: in 'online', loleaflet/src/map/Map.js:
1034 // 15 = 1440 twips-per-inch / 96 dpi.
1035 // Chosen to match previous hardcoded value of 3840 for
1036 // the current tile pixel size of 256.
1041 void AquaGraphicsBackend::pattern50Fill()
1043 static const CGFloat aFillCol
[4] = { 1, 1, 1, 1 };
1044 static const CGPatternCallbacks aCallback
= { 0, &drawPattern50
, nullptr };
1045 static const CGColorSpaceRef mxP50Space
= CGColorSpaceCreatePattern(GetSalData()->mxRGBSpace
);
1046 static const CGPatternRef mxP50Pattern
1047 = CGPatternCreate(nullptr, CGRectMake(0, 0, 4, 4), CGAffineTransformIdentity
, 4, 4,
1048 kCGPatternTilingConstantSpacing
, false, &aCallback
);
1049 SAL_WARN_IF(!mrShared
.maContextHolder
.get(), "vcl.quartz", "maContextHolder.get() is NULL");
1050 CGContextSetFillColorSpace(mrShared
.maContextHolder
.get(), mxP50Space
);
1051 CGContextSetFillPattern(mrShared
.maContextHolder
.get(), mxP50Pattern
, aFillCol
);
1052 CGContextFillPath(mrShared
.maContextHolder
.get());
1055 void AquaGraphicsBackend::invert(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1056 tools::Long nHeight
, SalInvert nFlags
)
1058 if (mrShared
.checkContext())
1060 CGRect aCGRect
= CGRectMake(nX
, nY
, nWidth
, nHeight
);
1061 mrShared
.maContextHolder
.saveState();
1062 if (nFlags
& SalInvert::TrackFrame
)
1064 const CGFloat dashLengths
[2] = { 4.0, 4.0 }; // for drawing dashed line
1065 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1066 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1067 CGContextSetLineDash(mrShared
.maContextHolder
.get(), 0, dashLengths
, 2);
1068 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), 2.0);
1069 CGContextStrokeRect(mrShared
.maContextHolder
.get(), aCGRect
);
1071 else if (nFlags
& SalInvert::N50
)
1073 //CGContextSetAllowsAntialiasing( maContextHolder.get(), false );
1074 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1075 CGContextAddRect(mrShared
.maContextHolder
.get(), aCGRect
);
1080 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1081 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1082 CGContextFillRect(mrShared
.maContextHolder
.get(), aCGRect
);
1084 mrShared
.maContextHolder
.restoreState();
1085 refreshRect(aCGRect
);
1091 CGPoint
* makeCGptArray(sal_uInt32 nPoints
, const Point
* pPtAry
)
1093 CGPoint
* CGpoints
= new CGPoint
[nPoints
];
1094 for (sal_uLong i
= 0; i
< nPoints
; i
++)
1096 CGpoints
[i
].x
= pPtAry
[i
].getX();
1097 CGpoints
[i
].y
= pPtAry
[i
].getY();
1102 } // end anonymous ns
1104 void AquaGraphicsBackend::invert(sal_uInt32 nPoints
, const Point
* pPtAry
, SalInvert nSalFlags
)
1106 if (mrShared
.checkContext())
1108 mrShared
.maContextHolder
.saveState();
1109 CGPoint
* CGpoints
= makeCGptArray(nPoints
, pPtAry
);
1110 CGContextAddLines(mrShared
.maContextHolder
.get(), CGpoints
, nPoints
);
1111 if (nSalFlags
& SalInvert::TrackFrame
)
1113 const CGFloat dashLengths
[2] = { 4.0, 4.0 }; // for drawing dashed line
1114 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1115 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1116 CGContextSetLineDash(mrShared
.maContextHolder
.get(), 0, dashLengths
, 2);
1117 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), 2.0);
1118 CGContextStrokePath(mrShared
.maContextHolder
.get());
1120 else if (nSalFlags
& SalInvert::N50
)
1122 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1127 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1128 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1129 CGContextFillPath(mrShared
.maContextHolder
.get());
1131 const CGRect aRefreshRect
= CGContextGetClipBoundingBox(mrShared
.maContextHolder
.get());
1132 mrShared
.maContextHolder
.restoreState();
1134 refreshRect(aRefreshRect
);
1139 bool AquaGraphicsBackend::drawEPS(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1140 tools::Long nHeight
, void* pEpsData
, sal_uInt32 nByteCount
)
1142 // convert the raw data to an NSImageRef
1143 NSData
* xNSData
= [NSData dataWithBytes
:pEpsData length
:static_cast<int>(nByteCount
)];
1144 SAL_WNODEPRECATED_DECLARATIONS_PUSH
1145 // 'NSEPSImageRep' is deprecated: first deprecated in macOS 14.0 - `NSEPSImageRep` instances
1146 // cannot be created on macOS 14.0 and later
1147 NSImageRep
* xEpsImage
= [NSEPSImageRep imageRepWithData
:xNSData
];
1148 SAL_WNODEPRECATED_DECLARATIONS_POP
1153 // get the target context
1154 if (!mrShared
.checkContext())
1158 // NOTE: flip drawing, else the nsimage would be drawn upside down
1159 mrShared
.maContextHolder
.saveState();
1160 // CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight );
1161 CGContextScaleCTM(mrShared
.maContextHolder
.get(), +1, -1);
1162 nY
= /*mnHeight*/ -(nY
+ nHeight
);
1164 // prepare the target context
1165 NSGraphicsContext
* pOrigNSCtx
= [NSGraphicsContext currentContext
];
1166 [pOrigNSCtx retain
];
1168 // create new context
1169 NSGraphicsContext
* pDrawNSCtx
=
1170 [NSGraphicsContext graphicsContextWithCGContext
:mrShared
.maContextHolder
.get()
1171 flipped
:mrShared
.isFlipped()];
1172 // set it, setCurrentContext also releases the previously set one
1173 [NSGraphicsContext setCurrentContext
:pDrawNSCtx
];
1176 const NSRect aDstRect
= NSMakeRect(nX
, nY
, nWidth
, nHeight
);
1177 const bool bOK
= [xEpsImage drawInRect
:aDstRect
];
1179 // restore the NSGraphicsContext
1180 [NSGraphicsContext setCurrentContext
:pOrigNSCtx
];
1181 [pOrigNSCtx release
]; // restore the original retain count
1183 mrShared
.maContextHolder
.restoreState();
1184 // mark the destination rectangle as updated
1185 refreshRect(aDstRect
);
1190 bool AquaGraphicsBackend::drawEPS(tools::Long
/*nX*/, tools::Long
/*nY*/, tools::Long
/*nWidth*/,
1191 tools::Long
/*nHeight*/, void* /*pEpsData*/,
1192 sal_uInt32
/*nByteCount*/)
1198 bool AquaGraphicsBackend::blendBitmap(const SalTwoRect
& /*rPosAry*/, const SalBitmap
& /*rBitmap*/)
1203 bool AquaGraphicsBackend::blendAlphaBitmap(const SalTwoRect
& /*rPosAry*/,
1204 const SalBitmap
& /*rSrcBitmap*/,
1205 const SalBitmap
& /*rMaskBitmap*/,
1206 const SalBitmap
& /*rAlphaBitmap*/)
1211 bool AquaGraphicsBackend::drawAlphaBitmap(const SalTwoRect
& rTR
, const SalBitmap
& rSrcBitmap
,
1212 const SalBitmap
& rAlphaBmp
)
1214 // An image mask can't have a depth > 8 bits (should be 1 to 8 bits)
1215 if (rAlphaBmp
.GetBitCount() > 8)
1218 // are these two tests really necessary? (see vcl/unx/source/gdi/salgdi2.cxx)
1219 // horizontal/vertical mirroring not implemented yet
1220 if (rTR
.mnDestWidth
< 0 || rTR
.mnDestHeight
< 0)
1223 CGImageRef xMaskedImage
= rSrcBitmap
.CreateWithMask(rAlphaBmp
, rTR
.mnSrcX
, rTR
.mnSrcY
,
1224 rTR
.mnSrcWidth
, rTR
.mnSrcHeight
);
1228 if (mrShared
.checkContext())
1230 const CGRect aDstRect
1231 = CGRectMake(rTR
.mnDestX
, rTR
.mnDestY
, rTR
.mnDestWidth
, rTR
.mnDestHeight
);
1232 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xMaskedImage
);
1233 refreshRect(aDstRect
);
1236 CGImageRelease(xMaskedImage
);
1241 bool AquaGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint
& rNull
,
1242 const basegfx::B2DPoint
& rX
,
1243 const basegfx::B2DPoint
& rY
,
1244 const SalBitmap
& rSrcBitmap
,
1245 const SalBitmap
* pAlphaBmp
, double fAlpha
)
1247 if (!mrShared
.checkContext())
1253 // get the Quartz image
1254 CGImageRef xImage
= nullptr;
1255 const Size aSize
= rSrcBitmap
.GetSize();
1258 xImage
= rSrcBitmap
.CreateCroppedImage(0, 0, int(aSize
.Width()), int(aSize
.Height()));
1261 = rSrcBitmap
.CreateWithMask(*pAlphaBmp
, 0, 0, int(aSize
.Width()), int(aSize
.Height()));
1266 // setup the image transformation
1267 // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
1268 mrShared
.maContextHolder
.saveState();
1269 const basegfx::B2DVector aXRel
= rX
- rNull
;
1270 const basegfx::B2DVector aYRel
= rY
- rNull
;
1271 const CGAffineTransform aCGMat
= CGAffineTransformMake(
1272 aXRel
.getX() / aSize
.Width(), aXRel
.getY() / aSize
.Width(), aYRel
.getX() / aSize
.Height(),
1273 aYRel
.getY() / aSize
.Height(), rNull
.getX(), rNull
.getY());
1275 CGContextConcatCTM(mrShared
.maContextHolder
.get(), aCGMat
);
1277 // draw the transformed image
1278 const CGRect aSrcRect
= CGRectMake(0, 0, aSize
.Width(), aSize
.Height());
1279 CGContextDrawImage(mrShared
.maContextHolder
.get(), aSrcRect
, xImage
);
1281 CGImageRelease(xImage
);
1283 // restore the Quartz graphics state
1284 mrShared
.maContextHolder
.restoreState();
1286 // mark the destination as painted
1287 const CGRect aDstRect
= CGRectApplyAffineTransform(aSrcRect
, aCGMat
);
1288 refreshRect(aDstRect
);
1293 bool AquaGraphicsBackend::hasFastDrawTransformedBitmap() const { return false; }
1295 bool AquaGraphicsBackend::drawAlphaRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1296 tools::Long nHeight
, sal_uInt8 nTransparency
)
1298 if (!mrShared
.checkContext())
1301 // save the current state
1302 mrShared
.maContextHolder
.saveState();
1303 CGContextSetAlpha(mrShared
.maContextHolder
.get(), (100 - nTransparency
) * (1.0 / 100));
1305 CGRect aRect
= CGRectMake(nX
, nY
, nWidth
- 1, nHeight
- 1);
1306 if (mrShared
.isPenVisible())
1308 aRect
.origin
.x
+= 0.5;
1309 aRect
.origin
.y
+= 0.5;
1312 CGContextBeginPath(mrShared
.maContextHolder
.get());
1313 CGContextAddRect(mrShared
.maContextHolder
.get(), aRect
);
1314 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathFill
);
1316 mrShared
.maContextHolder
.restoreState();
1322 bool AquaGraphicsBackend::drawGradient(const tools::PolyPolygon
& /*rPolygon*/,
1323 const Gradient
& /*rGradient*/)
1328 bool AquaGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon
const& /*rPolyPolygon*/,
1329 SalGradient
const& /*rGradient*/)
1334 bool AquaGraphicsBackend::supportsOperation(OutDevSupportType eType
) const
1338 case OutDevSupportType::TransparentRect
:
1346 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */