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>
27 #include <basegfx/polygon/b2dpolygon.hxx>
28 #include <basegfx/polygon/b2dpolygontools.hxx>
29 #include <basegfx/polygon/b2dpolypolygontools.hxx>
30 #include <osl/endian.h>
31 #include <osl/file.hxx>
32 #include <sal/types.h>
33 #include <tools/long.hxx>
34 #include <vcl/sysdata.hxx>
36 #include <fontsubset.hxx>
37 #include <quartz/salbmp.h>
39 #include <quartz/salgdi.h>
41 #include <quartz/utils.h>
43 #include "saldatabasic.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
)
197 AquaGraphicsBackend::~AquaGraphicsBackend() {}
199 void AquaGraphicsBackend::Init() {}
200 void AquaGraphicsBackend::freeResources() {}
202 bool 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())
234 void AquaGraphicsBackend::ResetClipRegion()
236 // release old path and indicate no clipping
237 mrShared
.unsetClipPath();
239 if (mrShared
.checkContext())
245 sal_uInt16
AquaGraphicsBackend::GetBitCount() const
247 sal_uInt16 nBits
= mrShared
.mnBitmapDepth
? mrShared
.mnBitmapDepth
: 32; //24;
251 tools::Long
AquaGraphicsBackend::GetGraphicsWidth() const
253 tools::Long width
= 0;
254 if (mrShared
.maContextHolder
.isSet()
261 width
= mrShared
.mnWidth
;
267 if (mrShared
.mbWindow
&& mrShared
.mpFrame
)
269 width
= mrShared
.mpFrame
->maGeometry
.nWidth
;
276 void AquaGraphicsBackend::SetLineColor()
278 mrShared
.maLineColor
.SetAlpha(0.0); // transparent
279 if (mrShared
.checkContext())
281 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), mrShared
.maLineColor
.GetRed(),
282 mrShared
.maLineColor
.GetGreen(), mrShared
.maLineColor
.GetBlue(),
283 mrShared
.maLineColor
.GetAlpha());
287 void AquaGraphicsBackend::SetLineColor(Color nColor
)
289 mrShared
.maLineColor
= RGBAColor(nColor
);
290 if (mrShared
.checkContext())
292 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), mrShared
.maLineColor
.GetRed(),
293 mrShared
.maLineColor
.GetGreen(), mrShared
.maLineColor
.GetBlue(),
294 mrShared
.maLineColor
.GetAlpha());
298 void AquaGraphicsBackend::SetFillColor()
300 mrShared
.maFillColor
.SetAlpha(0.0); // transparent
301 if (mrShared
.checkContext())
303 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.GetRed(),
304 mrShared
.maFillColor
.GetGreen(), mrShared
.maFillColor
.GetBlue(),
305 mrShared
.maFillColor
.GetAlpha());
309 void AquaGraphicsBackend::SetFillColor(Color nColor
)
311 mrShared
.maFillColor
= RGBAColor(nColor
);
312 if (mrShared
.checkContext())
314 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.GetRed(),
315 mrShared
.maFillColor
.GetGreen(), mrShared
.maFillColor
.GetBlue(),
316 mrShared
.maFillColor
.GetAlpha());
320 void AquaGraphicsBackend::SetXORMode(bool bSet
, bool bInvertOnly
)
322 // return early if XOR mode remains unchanged
323 if (mrShared
.mbPrinter
)
327 if (!bSet
&& mrShared
.mnXorMode
== 2)
329 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeNormal
);
330 mrShared
.mnXorMode
= 0;
333 else if (bSet
&& bInvertOnly
&& mrShared
.mnXorMode
== 0)
335 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
336 mrShared
.mnXorMode
= 2;
340 if (!mrShared
.mpXorEmulation
&& !bSet
)
344 if (mrShared
.mpXorEmulation
&& bSet
== mrShared
.mpXorEmulation
->IsEnabled())
348 if (!mrShared
.checkContext())
352 // prepare XOR emulation
353 if (!mrShared
.mpXorEmulation
)
355 mrShared
.mpXorEmulation
= std::make_unique
<XorEmulation
>();
356 mrShared
.mpXorEmulation
->SetTarget(mrShared
.mnWidth
, mrShared
.mnHeight
,
357 mrShared
.mnBitmapDepth
, mrShared
.maContextHolder
.get(),
358 mrShared
.maLayer
.get());
361 // change the XOR mode
364 mrShared
.mpXorEmulation
->Enable();
365 mrShared
.maContextHolder
.set(mrShared
.mpXorEmulation
->GetMaskContext());
366 mrShared
.mnXorMode
= 1;
370 mrShared
.mpXorEmulation
->UpdateTarget();
371 mrShared
.mpXorEmulation
->Disable();
372 mrShared
.maContextHolder
.set(mrShared
.mpXorEmulation
->GetTargetContext());
373 mrShared
.mnXorMode
= 0;
377 void AquaGraphicsBackend::SetROPFillColor(SalROPColor nROPColor
)
379 if (!mrShared
.mbPrinter
)
381 SetFillColor(ImplGetROPColor(nROPColor
));
385 void AquaGraphicsBackend::SetROPLineColor(SalROPColor nROPColor
)
387 if (!mrShared
.mbPrinter
)
389 SetLineColor(ImplGetROPColor(nROPColor
));
393 void AquaGraphicsBackend::drawPixelImpl(tools::Long nX
, tools::Long nY
, const RGBAColor
& rColor
)
395 if (!mrShared
.checkContext())
398 // overwrite the fill color
399 CGContextSetFillColor(mrShared
.maContextHolder
.get(), rColor
.AsArray());
400 // draw 1x1 rect, there is no pixel drawing in Quartz
401 const CGRect aDstRect
= CGRectMake(nX
, nY
, 1, 1);
402 CGContextFillRect(mrShared
.maContextHolder
.get(), aDstRect
);
404 refreshRect(aDstRect
);
406 // reset the fill color
407 CGContextSetFillColor(mrShared
.maContextHolder
.get(), mrShared
.maFillColor
.AsArray());
410 void AquaGraphicsBackend::drawPixel(tools::Long nX
, tools::Long nY
)
412 // draw pixel with current line color
413 drawPixelImpl(nX
, nY
, mrShared
.maLineColor
);
416 void AquaGraphicsBackend::drawPixel(tools::Long nX
, tools::Long nY
, Color nColor
)
418 const RGBAColor
aPixelColor(nColor
);
419 drawPixelImpl(nX
, nY
, aPixelColor
);
422 void AquaGraphicsBackend::drawLine(tools::Long nX1
, tools::Long nY1
, tools::Long nX2
,
425 if (nX1
== nX2
&& nY1
== nY2
)
427 // #i109453# platform independent code expects at least one pixel to be drawn
432 if (!mrShared
.checkContext())
435 CGContextBeginPath(mrShared
.maContextHolder
.get());
436 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), float(nX1
) + 0.5, float(nY1
) + 0.5);
437 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), float(nX2
) + 0.5, float(nY2
) + 0.5);
438 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathStroke
);
440 tools::Rectangle
aRefreshRect(nX1
, nY1
, nX2
, nY2
);
442 // Is a call to RefreshRect( aRefreshRect ) missing here?
445 void AquaGraphicsBackend::drawRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
448 if (!mrShared
.checkContext())
451 CGRect aRect
= CGRectMake(nX
, nY
, nWidth
, nHeight
);
452 if (mrShared
.isPenVisible())
454 aRect
.origin
.x
+= 0.5;
455 aRect
.origin
.y
+= 0.5;
456 aRect
.size
.width
-= 1;
457 aRect
.size
.height
-= 1;
460 if (mrShared
.isBrushVisible())
462 CGContextFillRect(mrShared
.maContextHolder
.get(), aRect
);
464 if (mrShared
.isPenVisible())
466 CGContextStrokeRect(mrShared
.maContextHolder
.get(), aRect
);
468 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
471 void AquaGraphicsBackend::drawPolyLine(sal_uInt32 nPoints
, const Point
* pPointArray
)
476 if (!mrShared
.checkContext())
479 tools::Long nX
= 0, nY
= 0, nWidth
= 0, nHeight
= 0;
480 getBoundRect(nPoints
, pPointArray
, nX
, nY
, nWidth
, nHeight
);
483 CGContextBeginPath(mrShared
.maContextHolder
.get());
484 alignLinePoint(pPointArray
, fX
, fY
);
485 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
488 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
490 alignLinePoint(pPointArray
, fX
, fY
);
491 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
493 CGContextStrokePath(mrShared
.maContextHolder
.get());
495 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
498 void AquaGraphicsBackend::drawPolygon(sal_uInt32 nPoints
, const Point
* pPointArray
)
503 if (!mrShared
.checkContext())
506 tools::Long nX
= 0, nY
= 0, nWidth
= 0, nHeight
= 0;
507 getBoundRect(nPoints
, pPointArray
, nX
, nY
, nWidth
, nHeight
);
509 CGPathDrawingMode eMode
;
510 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
512 eMode
= kCGPathEOFillStroke
;
514 else if (mrShared
.isPenVisible())
516 eMode
= kCGPathStroke
;
518 else if (mrShared
.isBrushVisible())
520 eMode
= kCGPathEOFill
;
524 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
528 CGContextBeginPath(mrShared
.maContextHolder
.get());
530 if (mrShared
.isPenVisible())
533 alignLinePoint(pPointArray
, fX
, fY
);
534 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
536 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
538 alignLinePoint(pPointArray
, fX
, fY
);
539 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
544 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), pPointArray
->getX(),
545 pPointArray
->getY());
547 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPointArray
++)
549 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), pPointArray
->getX(),
550 pPointArray
->getY());
554 CGContextClosePath(mrShared
.maContextHolder
.get());
555 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
557 mrShared
.refreshRect(nX
, nY
, nWidth
, nHeight
);
560 void AquaGraphicsBackend::drawPolyPolygon(sal_uInt32 nPolyCount
, const sal_uInt32
* pPoints
,
561 const Point
** ppPtAry
)
566 if (!mrShared
.checkContext())
570 tools::Long leftX
= 0, topY
= 0, maxWidth
= 0, maxHeight
= 0;
572 getBoundRect(pPoints
[0], ppPtAry
[0], leftX
, topY
, maxWidth
, maxHeight
);
574 for (sal_uInt32 n
= 1; n
< nPolyCount
; n
++)
576 tools::Long nX
= leftX
, nY
= topY
, nW
= maxWidth
, nH
= maxHeight
;
577 getBoundRect(pPoints
[n
], ppPtAry
[n
], nX
, nY
, nW
, nH
);
580 maxWidth
+= leftX
- nX
;
585 maxHeight
+= topY
- nY
;
588 if (nX
+ nW
> leftX
+ maxWidth
)
590 maxWidth
= nX
+ nW
- leftX
;
592 if (nY
+ nH
> topY
+ maxHeight
)
594 maxHeight
= nY
+ nH
- topY
;
598 // prepare drawing mode
599 CGPathDrawingMode eMode
;
600 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
602 eMode
= kCGPathEOFillStroke
;
604 else if (mrShared
.isPenVisible())
606 eMode
= kCGPathStroke
;
608 else if (mrShared
.isBrushVisible())
610 eMode
= kCGPathEOFill
;
614 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
619 CGContextBeginPath(mrShared
.maContextHolder
.get());
620 if (mrShared
.isPenVisible())
622 for (sal_uInt32 nPoly
= 0; nPoly
< nPolyCount
; nPoly
++)
624 const sal_uInt32 nPoints
= pPoints
[nPoly
];
627 const Point
* pPtAry
= ppPtAry
[nPoly
];
630 alignLinePoint(pPtAry
, fX
, fY
);
631 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
634 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPtAry
++)
636 alignLinePoint(pPtAry
, fX
, fY
);
637 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), fX
, fY
);
639 CGContextClosePath(mrShared
.maContextHolder
.get());
645 for (sal_uInt32 nPoly
= 0; nPoly
< nPolyCount
; nPoly
++)
647 const sal_uInt32 nPoints
= pPoints
[nPoly
];
650 const Point
* pPtAry
= ppPtAry
[nPoly
];
651 CGContextMoveToPoint(mrShared
.maContextHolder
.get(), pPtAry
->getX(),
654 for (sal_uInt32 nPoint
= 1; nPoint
< nPoints
; nPoint
++, pPtAry
++)
656 CGContextAddLineToPoint(mrShared
.maContextHolder
.get(), pPtAry
->getX(),
659 CGContextClosePath(mrShared
.maContextHolder
.get());
664 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
666 mrShared
.refreshRect(leftX
, topY
, maxWidth
, maxHeight
);
669 bool AquaGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix
& rObjectToDevice
,
670 const basegfx::B2DPolyPolygon
& rPolyPolygon
,
671 double fTransparency
)
674 if (!mrShared
.maContextHolder
.isSet())
678 // short circuit if there is nothing to do
679 if (rPolyPolygon
.count() == 0)
682 // ignore invisible polygons
683 if ((fTransparency
>= 1.0) || (fTransparency
< 0))
686 // Fallback: Transform to DeviceCoordinates
687 basegfx::B2DPolyPolygon
aPolyPolygon(rPolyPolygon
);
688 aPolyPolygon
.transform(rObjectToDevice
);
690 // setup poly-polygon path
691 CGMutablePathRef xPath
= CGPathCreateMutable();
692 // tdf#120252 Use the correct, already transformed PolyPolygon (as long as
693 // the transformation is not used here...)
694 for (auto const& rPolygon
: aPolyPolygon
)
696 AddPolygonToPath(xPath
, rPolygon
, true, !getAntiAlias(), mrShared
.isPenVisible());
699 const CGRect aRefreshRect
= CGPathGetBoundingBox(xPath
);
700 // #i97317# workaround for Quartz having problems with drawing small polygons
701 if (aRefreshRect
.size
.width
> 0.125 || aRefreshRect
.size
.height
> 0.125)
703 // prepare drawing mode
704 CGPathDrawingMode eMode
;
705 if (mrShared
.isBrushVisible() && mrShared
.isPenVisible())
707 eMode
= kCGPathEOFillStroke
;
709 else if (mrShared
.isPenVisible())
711 eMode
= kCGPathStroke
;
713 else if (mrShared
.isBrushVisible())
715 eMode
= kCGPathEOFill
;
719 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
720 CGPathRelease(xPath
);
724 // use the path to prepare the graphics context
725 mrShared
.maContextHolder
.saveState();
726 CGContextBeginPath(mrShared
.maContextHolder
.get());
727 CGContextAddPath(mrShared
.maContextHolder
.get(), xPath
);
729 // draw path with antialiased polygon
730 CGContextSetShouldAntialias(mrShared
.maContextHolder
.get(), getAntiAlias());
731 CGContextSetAlpha(mrShared
.maContextHolder
.get(), 1.0 - fTransparency
);
732 CGContextDrawPath(mrShared
.maContextHolder
.get(), eMode
);
733 mrShared
.maContextHolder
.restoreState();
735 // mark modified rectangle as updated
736 refreshRect(aRefreshRect
);
739 CGPathRelease(xPath
);
744 bool AquaGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix
& rObjectToDevice
,
745 const basegfx::B2DPolygon
& rPolyLine
, double fTransparency
,
747 const std::vector
<double>* pStroke
, // MM01
748 basegfx::B2DLineJoin eLineJoin
,
749 css::drawing::LineCap eLineCap
, double fMiterMinimumAngle
,
750 bool bPixelSnapHairline
)
752 // MM01 check done for simple reasons
753 if (!rPolyLine
.count() || fTransparency
< 0.0 || fTransparency
> 1.0)
759 if (!mrShared
.checkContext())
763 // tdf#124848 get correct LineWidth in discrete coordinates,
764 if (fLineWidth
== 0) // hairline
766 else // Adjust line width for object-to-device scale.
767 fLineWidth
= (rObjectToDevice
* basegfx::B2DVector(fLineWidth
, 0)).getLength();
769 // #i101491# Aqua does not support B2DLineJoin::NONE; return false to use
770 // the fallback (own geometry preparation)
771 // #i104886# linejoin-mode and thus the above only applies to "fat" lines
772 if ((basegfx::B2DLineJoin::NONE
== eLineJoin
) && (fLineWidth
> 1.3))
775 // MM01 need to do line dashing as fallback stuff here now
776 const double fDotDashLength(
777 nullptr != pStroke
? std::accumulate(pStroke
->begin(), pStroke
->end(), 0.0) : 0.0);
778 const bool bStrokeUsed(0.0 != fDotDashLength
);
779 assert(!bStrokeUsed
|| (bStrokeUsed
&& pStroke
));
780 basegfx::B2DPolyPolygon aPolyPolygonLine
;
785 basegfx::utils::applyLineDashing(rPolyLine
, // source
787 &aPolyPolygonLine
, // target for lines
788 nullptr, // target for gaps
789 fDotDashLength
); // full length if available
793 // no line dashing, just copy
794 aPolyPolygonLine
.append(rPolyLine
);
797 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
798 aPolyPolygonLine
.transform(rObjectToDevice
);
799 if (bPixelSnapHairline
)
801 aPolyPolygonLine
= basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine
);
804 // setup line attributes
805 CGLineJoin aCGLineJoin
= kCGLineJoinMiter
;
808 case basegfx::B2DLineJoin::NONE
:
809 aCGLineJoin
= /*TODO?*/ kCGLineJoinMiter
;
811 case basegfx::B2DLineJoin::Bevel
:
812 aCGLineJoin
= kCGLineJoinBevel
;
814 case basegfx::B2DLineJoin::Miter
:
815 aCGLineJoin
= kCGLineJoinMiter
;
817 case basegfx::B2DLineJoin::Round
:
818 aCGLineJoin
= kCGLineJoinRound
;
821 // convert miter minimum angle to miter limit
822 CGFloat fCGMiterLimit
= 1.0 / sin(fMiterMinimumAngle
/ 2.0);
823 // setup cap attribute
824 CGLineCap
aCGLineCap(kCGLineCapButt
);
828 default: // css::drawing::LineCap_BUTT:
830 aCGLineCap
= kCGLineCapButt
;
833 case css::drawing::LineCap_ROUND
:
835 aCGLineCap
= kCGLineCapRound
;
838 case css::drawing::LineCap_SQUARE
:
840 aCGLineCap
= kCGLineCapSquare
;
845 // setup poly-polygon path
846 CGMutablePathRef xPath
= CGPathCreateMutable();
848 // MM01 todo - I assume that this is OKAY to be done in one run for quartz
849 // but this NEEDS to be checked/verified
850 for (sal_uInt32
a(0); a
< aPolyPolygonLine
.count(); a
++)
852 const basegfx::B2DPolygon
aPolyLine(aPolyPolygonLine
.getB2DPolygon(a
));
853 AddPolygonToPath(xPath
, aPolyLine
, aPolyLine
.isClosed(), !getAntiAlias(), true);
856 const CGRect aRefreshRect
= CGPathGetBoundingBox(xPath
);
857 // #i97317# workaround for Quartz having problems with drawing small polygons
858 if ((aRefreshRect
.size
.width
> 0.125) || (aRefreshRect
.size
.height
> 0.125))
860 // use the path to prepare the graphics context
861 mrShared
.maContextHolder
.saveState();
862 CGContextBeginPath(mrShared
.maContextHolder
.get());
863 CGContextAddPath(mrShared
.maContextHolder
.get(), xPath
);
864 // draw path with antialiased line
865 CGContextSetShouldAntialias(mrShared
.maContextHolder
.get(), getAntiAlias());
866 CGContextSetAlpha(mrShared
.maContextHolder
.get(), 1.0 - fTransparency
);
867 CGContextSetLineJoin(mrShared
.maContextHolder
.get(), aCGLineJoin
);
868 CGContextSetLineCap(mrShared
.maContextHolder
.get(), aCGLineCap
);
869 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), fLineWidth
);
870 CGContextSetMiterLimit(mrShared
.maContextHolder
.get(), fCGMiterLimit
);
871 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathStroke
);
872 mrShared
.maContextHolder
.restoreState();
874 // mark modified rectangle as updated
875 refreshRect(aRefreshRect
);
878 CGPathRelease(xPath
);
883 bool AquaGraphicsBackend::drawPolyLineBezier(sal_uInt32
/*nPoints*/, const Point
* /*pPointArray*/,
884 const PolyFlags
* /*pFlagArray*/)
889 bool AquaGraphicsBackend::drawPolygonBezier(sal_uInt32
/*nPoints*/, const Point
* /*pPointArray*/,
890 const PolyFlags
* /*pFlagArray*/)
895 bool AquaGraphicsBackend::drawPolyPolygonBezier(sal_uInt32
/*nPoly*/, const sal_uInt32
* /*pPoints*/,
896 const Point
* const* /*pPointArray*/,
897 const PolyFlags
* const* /*pFlagArray*/)
902 void AquaGraphicsBackend::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
)
904 if (!mrShared
.checkContext())
907 const QuartzSalBitmap
& rBitmap
= static_cast<const QuartzSalBitmap
&>(rSalBitmap
);
908 CGImageRef xImage
= rBitmap
.CreateCroppedImage(
909 static_cast<int>(rPosAry
.mnSrcX
), static_cast<int>(rPosAry
.mnSrcY
),
910 static_cast<int>(rPosAry
.mnSrcWidth
), static_cast<int>(rPosAry
.mnSrcHeight
));
914 const CGRect aDstRect
915 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
916 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xImage
);
918 CGImageRelease(xImage
);
919 refreshRect(aDstRect
);
922 void AquaGraphicsBackend::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
,
923 const SalBitmap
& rTransparentBitmap
)
925 if (!mrShared
.checkContext())
928 const QuartzSalBitmap
& rBitmap
= static_cast<const QuartzSalBitmap
&>(rSalBitmap
);
929 const QuartzSalBitmap
& rMask
= static_cast<const QuartzSalBitmap
&>(rTransparentBitmap
);
931 CGImageRef
xMaskedImage(rBitmap
.CreateWithMask(rMask
, rPosAry
.mnSrcX
, rPosAry
.mnSrcY
,
932 rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
));
936 const CGRect aDstRect
937 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
938 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xMaskedImage
);
939 CGImageRelease(xMaskedImage
);
940 refreshRect(aDstRect
);
943 void AquaGraphicsBackend::drawMask(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
,
946 if (!mrShared
.checkContext())
949 const QuartzSalBitmap
& rBitmap
= static_cast<const QuartzSalBitmap
&>(rSalBitmap
);
950 CGImageRef xImage
= rBitmap
.CreateColorMask(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
,
951 rPosAry
.mnSrcHeight
, nMaskColor
);
955 const CGRect aDstRect
956 = CGRectMake(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
957 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xImage
);
958 CGImageRelease(xImage
);
959 refreshRect(aDstRect
);
962 std::shared_ptr
<SalBitmap
> AquaGraphicsBackend::getBitmap(tools::Long nX
, tools::Long nY
,
963 tools::Long nDX
, tools::Long nDY
)
965 SAL_WARN_IF(!mrShared
.maLayer
.isSet(), "vcl.quartz",
966 "AquaSalGraphics::getBitmap() with no layer this=" << this);
968 mrShared
.applyXorContext();
970 std::shared_ptr
<QuartzSalBitmap
> pBitmap
= std::make_shared
<QuartzSalBitmap
>();
971 if (!pBitmap
->Create(mrShared
.maLayer
, mrShared
.mnBitmapDepth
, nX
, nY
, nDX
, nDY
,
972 mrShared
.isFlipped()))
979 Color
AquaGraphicsBackend::getPixel(tools::Long nX
, tools::Long nY
)
981 // return default value on printers or when out of bounds
982 if (!mrShared
.maLayer
.isSet() || (nX
< 0) || (nX
>= mrShared
.mnWidth
) || (nY
< 0)
983 || (nY
>= mrShared
.mnHeight
))
988 // prepare creation of matching a CGBitmapContext
989 #if defined OSL_BIGENDIAN
992 unsigned char b
, g
, r
, a
;
997 unsigned char a
, r
, g
, b
;
1001 // create a one-pixel bitmap context
1002 // TODO: is it worth to cache it?
1003 CGContextRef xOnePixelContext
= CGBitmapContextCreate(
1004 &aPixel
, 1, 1, 8, 32, GetSalData()->mxRGBSpace
,
1005 uint32_t(kCGImageAlphaNoneSkipFirst
) | uint32_t(kCGBitmapByteOrder32Big
));
1007 // update this graphics layer
1008 mrShared
.applyXorContext();
1010 // copy the requested pixel into the bitmap context
1011 if (mrShared
.isFlipped())
1013 nY
= mrShared
.mnHeight
- nY
;
1015 const CGPoint aCGPoint
= CGPointMake(-nX
, -nY
);
1016 CGContextDrawLayerAtPoint(xOnePixelContext
, aCGPoint
, mrShared
.maLayer
.get());
1018 CGContextRelease(xOnePixelContext
);
1020 Color
nColor(aPixel
.r
, aPixel
.g
, aPixel
.b
);
1024 void AquaSalGraphics::GetResolution(sal_Int32
& rDPIX
, sal_Int32
& rDPIY
)
1029 initResolution((maShared
.mbWindow
&& maShared
.mpFrame
) ? maShared
.mpFrame
->getNSWindow()
1036 // This *must* be 96 or else the iOS app will behave very badly (tiles are scaled wrongly and
1037 // don't match each others at their boundaries, and other issues). But *why* it must be 96 I
1038 // have no idea. The commit that changed it to 96 from (the arbitrary) 200 did not say. If you
1039 // know where else 96 is explicitly or implicitly hard-coded, please modify this comment.
1041 // Follow-up: It might be this: in 'online', loleaflet/src/map/Map.js:
1042 // 15 = 1440 twips-per-inch / 96 dpi.
1043 // Chosen to match previous hardcoded value of 3840 for
1044 // the current tile pixel size of 256.
1049 void AquaGraphicsBackend::pattern50Fill()
1051 static const CGFloat aFillCol
[4] = { 1, 1, 1, 1 };
1052 static const CGPatternCallbacks aCallback
= { 0, &drawPattern50
, nullptr };
1053 static const CGColorSpaceRef mxP50Space
= CGColorSpaceCreatePattern(GetSalData()->mxRGBSpace
);
1054 static const CGPatternRef mxP50Pattern
1055 = CGPatternCreate(nullptr, CGRectMake(0, 0, 4, 4), CGAffineTransformIdentity
, 4, 4,
1056 kCGPatternTilingConstantSpacing
, false, &aCallback
);
1057 SAL_WARN_IF(!mrShared
.maContextHolder
.get(), "vcl.quartz", "maContextHolder.get() is NULL");
1058 CGContextSetFillColorSpace(mrShared
.maContextHolder
.get(), mxP50Space
);
1059 CGContextSetFillPattern(mrShared
.maContextHolder
.get(), mxP50Pattern
, aFillCol
);
1060 CGContextFillPath(mrShared
.maContextHolder
.get());
1063 void AquaGraphicsBackend::invert(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1064 tools::Long nHeight
, SalInvert nFlags
)
1066 if (mrShared
.checkContext())
1068 CGRect aCGRect
= CGRectMake(nX
, nY
, nWidth
, nHeight
);
1069 mrShared
.maContextHolder
.saveState();
1070 if (nFlags
& SalInvert::TrackFrame
)
1072 const CGFloat dashLengths
[2] = { 4.0, 4.0 }; // for drawing dashed line
1073 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1074 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1075 CGContextSetLineDash(mrShared
.maContextHolder
.get(), 0, dashLengths
, 2);
1076 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), 2.0);
1077 CGContextStrokeRect(mrShared
.maContextHolder
.get(), aCGRect
);
1079 else if (nFlags
& SalInvert::N50
)
1081 //CGContextSetAllowsAntialiasing( maContextHolder.get(), false );
1082 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1083 CGContextAddRect(mrShared
.maContextHolder
.get(), aCGRect
);
1088 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1089 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1090 CGContextFillRect(mrShared
.maContextHolder
.get(), aCGRect
);
1092 mrShared
.maContextHolder
.restoreState();
1093 refreshRect(aCGRect
);
1099 CGPoint
* makeCGptArray(sal_uInt32 nPoints
, const Point
* pPtAry
)
1101 CGPoint
* CGpoints
= new CGPoint
[nPoints
];
1102 for (sal_uLong i
= 0; i
< nPoints
; i
++)
1104 CGpoints
[i
].x
= pPtAry
[i
].getX();
1105 CGpoints
[i
].y
= pPtAry
[i
].getY();
1110 } // end anonymous ns
1112 void AquaGraphicsBackend::invert(sal_uInt32 nPoints
, const Point
* pPtAry
, SalInvert nSalFlags
)
1114 if (mrShared
.checkContext())
1116 mrShared
.maContextHolder
.saveState();
1117 CGPoint
* CGpoints
= makeCGptArray(nPoints
, pPtAry
);
1118 CGContextAddLines(mrShared
.maContextHolder
.get(), CGpoints
, nPoints
);
1119 if (nSalFlags
& SalInvert::TrackFrame
)
1121 const CGFloat dashLengths
[2] = { 4.0, 4.0 }; // for drawing dashed line
1122 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1123 CGContextSetRGBStrokeColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1124 CGContextSetLineDash(mrShared
.maContextHolder
.get(), 0, dashLengths
, 2);
1125 CGContextSetLineWidth(mrShared
.maContextHolder
.get(), 2.0);
1126 CGContextStrokePath(mrShared
.maContextHolder
.get());
1128 else if (nSalFlags
& SalInvert::N50
)
1130 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1135 CGContextSetBlendMode(mrShared
.maContextHolder
.get(), kCGBlendModeDifference
);
1136 CGContextSetRGBFillColor(mrShared
.maContextHolder
.get(), 1.0, 1.0, 1.0, 1.0);
1137 CGContextFillPath(mrShared
.maContextHolder
.get());
1139 const CGRect aRefreshRect
= CGContextGetClipBoundingBox(mrShared
.maContextHolder
.get());
1140 mrShared
.maContextHolder
.restoreState();
1142 refreshRect(aRefreshRect
);
1147 bool AquaGraphicsBackend::drawEPS(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1148 tools::Long nHeight
, void* pEpsData
, sal_uInt32 nByteCount
)
1150 // convert the raw data to an NSImageRef
1151 NSData
* xNSData
= [NSData dataWithBytes
:pEpsData length
:static_cast<int>(nByteCount
)];
1152 NSImageRep
* xEpsImage
= [NSEPSImageRep imageRepWithData
:xNSData
];
1157 // get the target context
1158 if (!mrShared
.checkContext())
1162 // NOTE: flip drawing, else the nsimage would be drawn upside down
1163 mrShared
.maContextHolder
.saveState();
1164 // CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight );
1165 CGContextScaleCTM(mrShared
.maContextHolder
.get(), +1, -1);
1166 nY
= /*mnHeight*/ -(nY
+ nHeight
);
1168 // prepare the target context
1169 NSGraphicsContext
* pOrigNSCtx
= [NSGraphicsContext currentContext
];
1170 [pOrigNSCtx retain
];
1172 // create new context
1173 NSGraphicsContext
* pDrawNSCtx
=
1174 [NSGraphicsContext graphicsContextWithCGContext
:mrShared
.maContextHolder
.get()
1175 flipped
:mrShared
.isFlipped()];
1176 // set it, setCurrentContext also releases the previously set one
1177 [NSGraphicsContext setCurrentContext
:pDrawNSCtx
];
1180 const NSRect aDstRect
= NSMakeRect(nX
, nY
, nWidth
, nHeight
);
1181 const bool bOK
= [xEpsImage drawInRect
:aDstRect
];
1183 // restore the NSGraphicsContext
1184 [NSGraphicsContext setCurrentContext
:pOrigNSCtx
];
1185 [pOrigNSCtx release
]; // restore the original retain count
1187 mrShared
.maContextHolder
.restoreState();
1188 // mark the destination rectangle as updated
1189 refreshRect(aDstRect
);
1194 bool AquaGraphicsBackend::drawEPS(tools::Long
/*nX*/, tools::Long
/*nY*/, tools::Long
/*nWidth*/,
1195 tools::Long
/*nHeight*/, void* /*pEpsData*/,
1196 sal_uInt32
/*nByteCount*/)
1202 bool AquaGraphicsBackend::blendBitmap(const SalTwoRect
& /*rPosAry*/, const SalBitmap
& /*rBitmap*/)
1207 bool AquaGraphicsBackend::blendAlphaBitmap(const SalTwoRect
& /*rPosAry*/,
1208 const SalBitmap
& /*rSrcBitmap*/,
1209 const SalBitmap
& /*rMaskBitmap*/,
1210 const SalBitmap
& /*rAlphaBitmap*/)
1215 bool AquaGraphicsBackend::drawAlphaBitmap(const SalTwoRect
& rTR
, const SalBitmap
& rSrcBitmap
,
1216 const SalBitmap
& rAlphaBmp
)
1218 // An image mask can't have a depth > 8 bits (should be 1 to 8 bits)
1219 if (rAlphaBmp
.GetBitCount() > 8)
1222 // are these two tests really necessary? (see vcl/unx/source/gdi/salgdi2.cxx)
1223 // horizontal/vertical mirroring not implemented yet
1224 if (rTR
.mnDestWidth
< 0 || rTR
.mnDestHeight
< 0)
1227 const QuartzSalBitmap
& rSrcSalBmp
= static_cast<const QuartzSalBitmap
&>(rSrcBitmap
);
1228 const QuartzSalBitmap
& rMaskSalBmp
= static_cast<const QuartzSalBitmap
&>(rAlphaBmp
);
1229 CGImageRef xMaskedImage
= rSrcSalBmp
.CreateWithMask(rMaskSalBmp
, rTR
.mnSrcX
, rTR
.mnSrcY
,
1230 rTR
.mnSrcWidth
, rTR
.mnSrcHeight
);
1234 if (mrShared
.checkContext())
1236 const CGRect aDstRect
1237 = CGRectMake(rTR
.mnDestX
, rTR
.mnDestY
, rTR
.mnDestWidth
, rTR
.mnDestHeight
);
1238 CGContextDrawImage(mrShared
.maContextHolder
.get(), aDstRect
, xMaskedImage
);
1239 refreshRect(aDstRect
);
1242 CGImageRelease(xMaskedImage
);
1247 bool AquaGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint
& rNull
,
1248 const basegfx::B2DPoint
& rX
,
1249 const basegfx::B2DPoint
& rY
,
1250 const SalBitmap
& rSrcBitmap
,
1251 const SalBitmap
* pAlphaBmp
, double fAlpha
)
1253 if (!mrShared
.checkContext())
1259 // get the Quartz image
1260 CGImageRef xImage
= nullptr;
1261 const Size aSize
= rSrcBitmap
.GetSize();
1262 const QuartzSalBitmap
& rSrcSalBmp
= static_cast<const QuartzSalBitmap
&>(rSrcBitmap
);
1263 const QuartzSalBitmap
* pMaskSalBmp
= static_cast<const QuartzSalBitmap
*>(pAlphaBmp
);
1266 xImage
= rSrcSalBmp
.CreateCroppedImage(0, 0, int(aSize
.Width()), int(aSize
.Height()));
1268 xImage
= rSrcSalBmp
.CreateWithMask(*pMaskSalBmp
, 0, 0, int(aSize
.Width()),
1269 int(aSize
.Height()));
1274 // setup the image transformation
1275 // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
1276 mrShared
.maContextHolder
.saveState();
1277 const basegfx::B2DVector aXRel
= rX
- rNull
;
1278 const basegfx::B2DVector aYRel
= rY
- rNull
;
1279 const CGAffineTransform aCGMat
= CGAffineTransformMake(
1280 aXRel
.getX() / aSize
.Width(), aXRel
.getY() / aSize
.Width(), aYRel
.getX() / aSize
.Height(),
1281 aYRel
.getY() / aSize
.Height(), rNull
.getX(), rNull
.getY());
1283 CGContextConcatCTM(mrShared
.maContextHolder
.get(), aCGMat
);
1285 // draw the transformed image
1286 const CGRect aSrcRect
= CGRectMake(0, 0, aSize
.Width(), aSize
.Height());
1287 CGContextDrawImage(mrShared
.maContextHolder
.get(), aSrcRect
, xImage
);
1289 CGImageRelease(xImage
);
1291 // restore the Quartz graphics state
1292 mrShared
.maContextHolder
.restoreState();
1294 // mark the destination as painted
1295 const CGRect aDstRect
= CGRectApplyAffineTransform(aSrcRect
, aCGMat
);
1296 refreshRect(aDstRect
);
1301 bool AquaGraphicsBackend::hasFastDrawTransformedBitmap() const { return false; }
1303 bool AquaGraphicsBackend::drawAlphaRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1304 tools::Long nHeight
, sal_uInt8 nTransparency
)
1306 if (!mrShared
.checkContext())
1309 // save the current state
1310 mrShared
.maContextHolder
.saveState();
1311 CGContextSetAlpha(mrShared
.maContextHolder
.get(), (100 - nTransparency
) * (1.0 / 100));
1313 CGRect aRect
= CGRectMake(nX
, nY
, nWidth
- 1, nHeight
- 1);
1314 if (mrShared
.isPenVisible())
1316 aRect
.origin
.x
+= 0.5;
1317 aRect
.origin
.y
+= 0.5;
1320 CGContextBeginPath(mrShared
.maContextHolder
.get());
1321 CGContextAddRect(mrShared
.maContextHolder
.get(), aRect
);
1322 CGContextDrawPath(mrShared
.maContextHolder
.get(), kCGPathFill
);
1324 mrShared
.maContextHolder
.restoreState();
1330 bool AquaGraphicsBackend::drawGradient(const tools::PolyPolygon
& /*rPolygon*/,
1331 const Gradient
& /*rGradient*/)
1336 bool AquaGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon
const& /*rPolyPolygon*/,
1337 SalGradient
const& /*rGradient*/)
1342 bool AquaGraphicsBackend::supportsOperation(OutDevSupportType eType
) const
1346 case OutDevSupportType::TransparentRect
:
1347 case OutDevSupportType::B2DDraw
:
1355 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */