Avoid potential negative array index access to cached text.
[LibreOffice.git] / vcl / quartz / AquaGraphicsBackend.cxx
blob4badefacf43555e23ae678845dc7b62742177f9c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
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>
23 #include <cassert>
24 #include <cstring>
25 #include <numeric>
26 #include <utility>
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>
39 #ifdef MACOSX
40 #include <quartz/salgdi.h>
41 #endif
42 #include <quartz/utils.h>
43 #ifdef IOS
44 #include <ios/iosinst.hxx>
45 #endif
47 using namespace vcl;
49 namespace
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();
58 if (nPointCount <= 0)
60 return;
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))
72 nClosedIdx = 0;
74 else
76 break;
80 basegfx::B2DPoint aPoint = rPolygon.getB2DPoint(nClosedIdx);
82 if (bPixelSnap)
84 // snap device coordinates to full pixels
85 aPoint.setX(basegfx::fround(aPoint.getX()));
86 aPoint.setY(basegfx::fround(aPoint.getY()));
89 if (bLineDraw)
91 aPoint += aHalfPointOfs;
93 if (!nPointIdx)
95 // first point => just move there
96 CGPathMoveToPoint(xPath, nullptr, aPoint.getX(), aPoint.getY());
97 continue;
100 bool bPendingCurve = false;
101 if (bHasCurves)
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);
115 if (bLineDraw)
117 aCP1 += aHalfPointOfs;
118 aCP2 += aHalfPointOfs;
120 CGPathAddCurveToPoint(xPath, nullptr, aCP1.getX(), aCP1.getY(), aCP2.getX(),
121 aCP2.getY(), aPoint.getX(), aPoint.getY());
125 if (bClosePath)
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();
164 rX = nX1;
165 rY = nY1;
166 rWidth = nX2 - nX1 + 1;
167 rHeight = nY2 - nY1 + 1;
170 Color ImplGetROPColor(SalROPColor nROPColor)
172 Color nColor;
173 if (nROPColor == SalROPColor::N0)
175 nColor = Color(0, 0, 0);
177 else
179 nColor = Color(255, 255, 255);
181 return nColor;
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
216 if (nW)
218 const tools::Long nH(rRect.Bottom() - rRect.Top() + 1); // uses +1 logic in original
220 if (nH)
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())
229 mrShared.setState();
232 void AquaGraphicsBackend::ResetClipRegion()
234 // release old path and indicate no clipping
235 mrShared.unsetClipPath();
237 if (mrShared.checkContext())
239 mrShared.setState();
243 sal_uInt16 AquaGraphicsBackend::GetBitCount() const
245 sal_uInt16 nBits = mrShared.mnBitmapDepth ? mrShared.mnBitmapDepth : 32; //24;
246 return nBits;
249 tools::Long AquaGraphicsBackend::GetGraphicsWidth() const
251 tools::Long width = 0;
252 if (mrShared.maContextHolder.isSet()
253 && (
254 #ifndef IOS
255 mrShared.mbWindow ||
256 #endif
257 mrShared.mbVirDev))
259 width = mrShared.mnWidth;
262 #ifndef IOS
263 if (width == 0)
265 if (mrShared.mbWindow && mrShared.mpFrame)
267 width = mrShared.mpFrame->maGeometry.width();
270 #endif
271 return 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)
323 return;
325 if (!bSet && mrShared.mnXorMode == 2)
327 CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeNormal);
328 mrShared.mnXorMode = 0;
329 return;
331 else if (bSet && bInvertOnly && mrShared.mnXorMode == 0)
333 CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
334 mrShared.mnXorMode = 2;
335 return;
338 if (!mrShared.mpXorEmulation && !bSet)
340 return;
342 if (mrShared.mpXorEmulation && bSet == mrShared.mpXorEmulation->IsEnabled())
344 return;
346 if (!mrShared.checkContext())
348 return;
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
360 if (bSet)
362 mrShared.mpXorEmulation->Enable();
363 mrShared.maContextHolder.set(mrShared.mpXorEmulation->GetMaskContext());
364 mrShared.mnXorMode = 1;
366 else
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())
394 return;
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,
421 tools::Long nY2)
423 if (nX1 == nX2 && nY1 == nY2)
425 // #i109453# platform independent code expects at least one pixel to be drawn
426 drawPixel(nX1, nY1);
427 return;
430 if (!mrShared.checkContext())
431 return;
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);
439 (void)aRefreshRect;
440 // Is a call to RefreshRect( aRefreshRect ) missing here?
443 void AquaGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
444 tools::Long nHeight)
446 if (!mrShared.checkContext())
447 return;
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)
471 if (nPoints < 1)
472 return;
474 if (!mrShared.checkContext())
475 return;
477 tools::Long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
478 getBoundRect(nPoints, pPointArray, nX, nY, nWidth, nHeight);
480 float fX, fY;
481 CGContextBeginPath(mrShared.maContextHolder.get());
482 alignLinePoint(pPointArray, fX, fY);
483 CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
484 pPointArray++;
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)
498 if (nPoints <= 1)
499 return;
501 if (!mrShared.checkContext())
502 return;
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;
520 else
522 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
523 return;
526 CGContextBeginPath(mrShared.maContextHolder.get());
528 if (mrShared.isPenVisible())
530 float fX, fY;
531 alignLinePoint(pPointArray, fX, fY);
532 CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
533 pPointArray++;
534 for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++)
536 alignLinePoint(pPointArray, fX, fY);
537 CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY);
540 else
542 CGContextMoveToPoint(mrShared.maContextHolder.get(), pPointArray->getX(),
543 pPointArray->getY());
544 pPointArray++;
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)
561 if (nPolyCount <= 0)
562 return;
564 if (!mrShared.checkContext())
565 return;
567 // find bound rect
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);
576 if (nX < leftX)
578 maxWidth += leftX - nX;
579 leftX = nX;
581 if (nY < topY)
583 maxHeight += topY - nY;
584 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;
610 else
612 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
613 return;
616 // convert to CGPath
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];
623 if (nPoints > 1)
625 const Point* pPtAry = ppPtAry[nPoly];
626 float fX, fY;
628 alignLinePoint(pPtAry, fX, fY);
629 CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
630 pPtAry++;
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());
641 else
643 for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
645 const sal_uInt32 nPoints = pPoints[nPoly];
646 if (nPoints > 1)
648 const Point* pPtAry = ppPtAry[nPoly];
649 CGContextMoveToPoint(mrShared.maContextHolder.get(), pPtAry->getX(),
650 pPtAry->getY());
651 pPtAry++;
652 for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
654 CGContextAddLineToPoint(mrShared.maContextHolder.get(), pPtAry->getX(),
655 pPtAry->getY());
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)
671 #ifdef IOS
672 if (!mrShared.maContextHolder.isSet())
673 return;
674 #endif
676 // short circuit if there is nothing to do
677 if (rPolyPolygon.count() == 0)
678 return;
680 // ignore invisible polygons
681 if ((fTransparency >= 1.0) || (fTransparency < 0))
682 return;
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;
715 else
717 SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
718 CGPathRelease(xPath);
719 return;
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,
742 double fLineWidth,
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)
751 return true;
754 #ifdef IOS
755 if (!mrShared.checkContext())
756 return false;
757 #endif
759 // tdf#124848 get correct LineWidth in discrete coordinates,
760 if (fLineWidth == 0) // hairline
761 fLineWidth = 1.0;
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))
769 return false;
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;
778 if (bStrokeUsed)
780 // apply LineStyle
781 basegfx::utils::applyLineDashing(rPolyLine, // source
782 *pStroke, // pattern
783 &aPolyPolygonLine, // target for lines
784 nullptr, // target for gaps
785 fDotDashLength); // full length if available
787 else
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;
802 switch (eLineJoin)
804 case basegfx::B2DLineJoin::NONE:
805 aCGLineJoin = /*TODO?*/ kCGLineJoinMiter;
806 break;
807 case basegfx::B2DLineJoin::Bevel:
808 aCGLineJoin = kCGLineJoinBevel;
809 break;
810 case basegfx::B2DLineJoin::Miter:
811 aCGLineJoin = kCGLineJoinMiter;
812 break;
813 case basegfx::B2DLineJoin::Round:
814 aCGLineJoin = kCGLineJoinRound;
815 break;
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);
822 switch (eLineCap)
824 default: // css::drawing::LineCap_BUTT:
826 aCGLineCap = kCGLineCapButt;
827 break;
829 case css::drawing::LineCap_ROUND:
831 aCGLineCap = kCGLineCapRound;
832 break;
834 case css::drawing::LineCap_SQUARE:
836 aCGLineCap = kCGLineCapSquare;
837 break;
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);
876 return true;
879 bool AquaGraphicsBackend::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const Point* /*pPointArray*/,
880 const PolyFlags* /*pFlagArray*/)
882 return false;
885 bool AquaGraphicsBackend::drawPolygonBezier(sal_uInt32 /*nPoints*/, const Point* /*pPointArray*/,
886 const PolyFlags* /*pFlagArray*/)
888 return false;
891 bool AquaGraphicsBackend::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/,
892 const Point* const* /*pPointArray*/,
893 const PolyFlags* const* /*pFlagArray*/)
895 return false;
898 void AquaGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
900 if (!mrShared.checkContext())
901 return;
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));
906 if (!xImage)
907 return;
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())
921 return;
923 CGImageRef xMaskedImage(rSalBitmap.CreateWithMask(rTransparentBitmap, rPosAry.mnSrcX,
924 rPosAry.mnSrcY, rPosAry.mnSrcWidth,
925 rPosAry.mnSrcHeight));
926 if (!xMaskedImage)
927 return;
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,
937 Color nMaskColor)
939 if (!mrShared.checkContext())
940 return;
942 CGImageRef xImage = rSalBitmap.CreateColorMask(
943 rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, nMaskColor);
944 if (!xImage)
945 return;
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()))
966 pBitmap = nullptr;
968 return pBitmap;
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))
977 return COL_BLACK;
980 // prepare creation of matching a CGBitmapContext
981 #if defined OSL_BIGENDIAN
982 struct
984 unsigned char b, g, r, a;
985 } aPixel;
986 #else
987 struct
989 unsigned char a, r, g, b;
990 } aPixel;
991 #endif
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);
1013 return nColor;
1016 void AquaSalGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
1018 #ifndef IOS
1019 if (!mnRealDPIY)
1021 initResolution((maShared.mbWindow && maShared.mpFrame) ? maShared.mpFrame->getNSWindow()
1022 : nil);
1025 rDPIX = mnRealDPIX;
1026 rDPIY = mnRealDPIY;
1027 #else
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.
1037 rDPIX = rDPIY = 96;
1038 #endif
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);
1076 pattern50Fill();
1078 else // just invert
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);
1089 namespace
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();
1099 return CGpoints;
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);
1123 pattern50Fill();
1125 else // just invert
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();
1133 delete[] CGpoints;
1134 refreshRect(aRefreshRect);
1138 #ifndef IOS
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
1149 if (!xEpsImage)
1151 return false;
1153 // get the target context
1154 if (!mrShared.checkContext())
1156 return false;
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];
1175 // draw the EPS
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);
1187 return bOK;
1189 #else
1190 bool AquaGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
1191 tools::Long /*nHeight*/, void* /*pEpsData*/,
1192 sal_uInt32 /*nByteCount*/)
1194 return false;
1196 #endif
1198 bool AquaGraphicsBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/)
1200 return false;
1203 bool AquaGraphicsBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/,
1204 const SalBitmap& /*rSrcBitmap*/,
1205 const SalBitmap& /*rMaskBitmap*/,
1206 const SalBitmap& /*rAlphaBitmap*/)
1208 return false;
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)
1216 return false;
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)
1221 return false;
1223 CGImageRef xMaskedImage = rSrcBitmap.CreateWithMask(rAlphaBmp, rTR.mnSrcX, rTR.mnSrcY,
1224 rTR.mnSrcWidth, rTR.mnSrcHeight);
1225 if (!xMaskedImage)
1226 return false;
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);
1238 return true;
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())
1248 return true;
1250 if (fAlpha != 1.0)
1251 return false;
1253 // get the Quartz image
1254 CGImageRef xImage = nullptr;
1255 const Size aSize = rSrcBitmap.GetSize();
1257 if (!pAlphaBmp)
1258 xImage = rSrcBitmap.CreateCroppedImage(0, 0, int(aSize.Width()), int(aSize.Height()));
1259 else
1260 xImage
1261 = rSrcBitmap.CreateWithMask(*pAlphaBmp, 0, 0, int(aSize.Width()), int(aSize.Height()));
1263 if (!xImage)
1264 return false;
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);
1290 return true;
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())
1299 return true;
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();
1317 refreshRect(aRect);
1319 return true;
1322 bool AquaGraphicsBackend::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
1323 const Gradient& /*rGradient*/)
1325 return false;
1328 bool AquaGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/,
1329 SalGradient const& /*rGradient*/)
1331 return false;
1334 bool AquaGraphicsBackend::supportsOperation(OutDevSupportType eType) const
1336 switch (eType)
1338 case OutDevSupportType::TransparentRect:
1339 return true;
1340 default:
1341 break;
1343 return false;
1346 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */