5473f185ea
Recent code reorganization and macOS changes caused the iOS build to fail. This change fixes the build errors and updates the iOS code to use the same headless data structures as other headless builds. Change-Id: I9c5329eb6376120d6789447f991c93eb9839d595 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146265 Tested-by: Jenkins Reviewed-by: Tor Lillqvist <tml@collabora.com> Reviewed-by: Patrick Luby <plubius@neooffice.org>
1345 lines
43 KiB
C++
1345 lines
43 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <sal/config.h>
|
|
#include <sal/log.hxx>
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <numeric>
|
|
#include <utility>
|
|
|
|
#include <basegfx/polygon/b2dpolygon.hxx>
|
|
#include <basegfx/polygon/b2dpolygontools.hxx>
|
|
#include <basegfx/polygon/b2dpolypolygontools.hxx>
|
|
#include <osl/endian.h>
|
|
#include <osl/file.hxx>
|
|
#include <sal/types.h>
|
|
#include <tools/long.hxx>
|
|
#include <vcl/sysdata.hxx>
|
|
|
|
#include <fontsubset.hxx>
|
|
#include <quartz/salbmp.h>
|
|
#ifdef MACOSX
|
|
#include <quartz/salgdi.h>
|
|
#endif
|
|
#include <quartz/utils.h>
|
|
#ifdef IOS
|
|
#include <ios/iosinst.hxx>
|
|
#endif
|
|
|
|
using namespace vcl;
|
|
|
|
namespace
|
|
{
|
|
const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5);
|
|
|
|
void AddPolygonToPath(CGMutablePathRef xPath, const basegfx::B2DPolygon& rPolygon, bool bClosePath,
|
|
bool bPixelSnap, bool bLineDraw)
|
|
{
|
|
// short circuit if there is nothing to do
|
|
const int nPointCount = rPolygon.count();
|
|
if (nPointCount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bHasCurves = rPolygon.areControlPointsUsed();
|
|
for (int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
|
|
{
|
|
int nClosedIdx = nPointIdx;
|
|
if (nPointIdx >= nPointCount)
|
|
{
|
|
// prepare to close last curve segment if needed
|
|
if (bClosePath && (nPointIdx == nPointCount))
|
|
{
|
|
nClosedIdx = 0;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
basegfx::B2DPoint aPoint = rPolygon.getB2DPoint(nClosedIdx);
|
|
|
|
if (bPixelSnap)
|
|
{
|
|
// snap device coordinates to full pixels
|
|
aPoint.setX(basegfx::fround(aPoint.getX()));
|
|
aPoint.setY(basegfx::fround(aPoint.getY()));
|
|
}
|
|
|
|
if (bLineDraw)
|
|
{
|
|
aPoint += aHalfPointOfs;
|
|
}
|
|
if (!nPointIdx)
|
|
{
|
|
// first point => just move there
|
|
CGPathMoveToPoint(xPath, nullptr, aPoint.getX(), aPoint.getY());
|
|
continue;
|
|
}
|
|
|
|
bool bPendingCurve = false;
|
|
if (bHasCurves)
|
|
{
|
|
bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
|
|
bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
|
|
}
|
|
|
|
if (!bPendingCurve) // line segment
|
|
{
|
|
CGPathAddLineToPoint(xPath, nullptr, aPoint.getX(), aPoint.getY());
|
|
}
|
|
else // cubic bezier segment
|
|
{
|
|
basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
|
|
basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
|
|
if (bLineDraw)
|
|
{
|
|
aCP1 += aHalfPointOfs;
|
|
aCP2 += aHalfPointOfs;
|
|
}
|
|
CGPathAddCurveToPoint(xPath, nullptr, aCP1.getX(), aCP1.getY(), aCP2.getX(),
|
|
aCP2.getY(), aPoint.getX(), aPoint.getY());
|
|
}
|
|
}
|
|
|
|
if (bClosePath)
|
|
{
|
|
CGPathCloseSubpath(xPath);
|
|
}
|
|
}
|
|
|
|
void alignLinePoint(const Point* i_pIn, float& o_fX, float& o_fY)
|
|
{
|
|
o_fX = static_cast<float>(i_pIn->getX()) + 0.5;
|
|
o_fY = static_cast<float>(i_pIn->getY()) + 0.5;
|
|
}
|
|
|
|
void getBoundRect(sal_uInt32 nPoints, const Point* pPtAry, tools::Long& rX, tools::Long& rY,
|
|
tools::Long& rWidth, tools::Long& rHeight)
|
|
{
|
|
tools::Long nX1 = pPtAry->getX();
|
|
tools::Long nX2 = nX1;
|
|
tools::Long nY1 = pPtAry->getY();
|
|
tools::Long nY2 = nY1;
|
|
|
|
for (sal_uInt32 n = 1; n < nPoints; n++)
|
|
{
|
|
if (pPtAry[n].getX() < nX1)
|
|
{
|
|
nX1 = pPtAry[n].getX();
|
|
}
|
|
else if (pPtAry[n].getX() > nX2)
|
|
{
|
|
nX2 = pPtAry[n].getX();
|
|
}
|
|
if (pPtAry[n].getY() < nY1)
|
|
{
|
|
nY1 = pPtAry[n].getY();
|
|
}
|
|
else if (pPtAry[n].getY() > nY2)
|
|
{
|
|
nY2 = pPtAry[n].getY();
|
|
}
|
|
}
|
|
rX = nX1;
|
|
rY = nY1;
|
|
rWidth = nX2 - nX1 + 1;
|
|
rHeight = nY2 - nY1 + 1;
|
|
}
|
|
|
|
Color ImplGetROPColor(SalROPColor nROPColor)
|
|
{
|
|
Color nColor;
|
|
if (nROPColor == SalROPColor::N0)
|
|
{
|
|
nColor = Color(0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
nColor = Color(255, 255, 255);
|
|
}
|
|
return nColor;
|
|
}
|
|
|
|
void drawPattern50(void*, CGContextRef rContext)
|
|
{
|
|
static const CGRect aRects[2] = { { { 0, 0 }, { 2, 2 } }, { { 2, 2 }, { 2, 2 } } };
|
|
CGContextAddRects(rContext, aRects, 2);
|
|
CGContextFillPath(rContext);
|
|
}
|
|
}
|
|
|
|
AquaGraphicsBackend::AquaGraphicsBackend(AquaSharedAttributes& rShared)
|
|
: AquaGraphicsBackendBase(rShared, this)
|
|
{
|
|
}
|
|
|
|
AquaGraphicsBackend::~AquaGraphicsBackend() {}
|
|
|
|
void AquaGraphicsBackend::Init() {}
|
|
void AquaGraphicsBackend::freeResources() {}
|
|
|
|
void AquaGraphicsBackend::setClipRegion(vcl::Region const& rRegion)
|
|
{
|
|
// release old clip path
|
|
mrShared.unsetClipPath();
|
|
mrShared.mxClipPath = CGPathCreateMutable();
|
|
|
|
// set current path, either as polypolgon or sequence of rectangles
|
|
RectangleVector aRectangles;
|
|
rRegion.GetRegionRectangles(aRectangles);
|
|
|
|
for (const auto& rRect : aRectangles)
|
|
{
|
|
const tools::Long nW(rRect.Right() - rRect.Left() + 1); // uses +1 logic in original
|
|
|
|
if (nW)
|
|
{
|
|
const tools::Long nH(rRect.Bottom() - rRect.Top() + 1); // uses +1 logic in original
|
|
|
|
if (nH)
|
|
{
|
|
const CGRect aRect = CGRectMake(rRect.Left(), rRect.Top(), nW, nH);
|
|
CGPathAddRect(mrShared.mxClipPath, nullptr, aRect);
|
|
}
|
|
}
|
|
}
|
|
// set the current path as clip region
|
|
if (mrShared.checkContext())
|
|
mrShared.setState();
|
|
}
|
|
|
|
void AquaGraphicsBackend::ResetClipRegion()
|
|
{
|
|
// release old path and indicate no clipping
|
|
mrShared.unsetClipPath();
|
|
|
|
if (mrShared.checkContext())
|
|
{
|
|
mrShared.setState();
|
|
}
|
|
}
|
|
|
|
sal_uInt16 AquaGraphicsBackend::GetBitCount() const
|
|
{
|
|
sal_uInt16 nBits = mrShared.mnBitmapDepth ? mrShared.mnBitmapDepth : 32; //24;
|
|
return nBits;
|
|
}
|
|
|
|
tools::Long AquaGraphicsBackend::GetGraphicsWidth() const
|
|
{
|
|
tools::Long width = 0;
|
|
if (mrShared.maContextHolder.isSet()
|
|
&& (
|
|
#ifndef IOS
|
|
mrShared.mbWindow ||
|
|
#endif
|
|
mrShared.mbVirDev))
|
|
{
|
|
width = mrShared.mnWidth;
|
|
}
|
|
|
|
#ifndef IOS
|
|
if (width == 0)
|
|
{
|
|
if (mrShared.mbWindow && mrShared.mpFrame)
|
|
{
|
|
width = mrShared.mpFrame->maGeometry.width();
|
|
}
|
|
}
|
|
#endif
|
|
return width;
|
|
}
|
|
|
|
void AquaGraphicsBackend::SetLineColor()
|
|
{
|
|
mrShared.maLineColor.SetAlpha(0.0); // transparent
|
|
if (mrShared.checkContext())
|
|
{
|
|
CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), mrShared.maLineColor.GetRed(),
|
|
mrShared.maLineColor.GetGreen(), mrShared.maLineColor.GetBlue(),
|
|
mrShared.maLineColor.GetAlpha());
|
|
}
|
|
}
|
|
|
|
void AquaGraphicsBackend::SetLineColor(Color nColor)
|
|
{
|
|
mrShared.maLineColor = RGBAColor(nColor);
|
|
if (mrShared.checkContext())
|
|
{
|
|
CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), mrShared.maLineColor.GetRed(),
|
|
mrShared.maLineColor.GetGreen(), mrShared.maLineColor.GetBlue(),
|
|
mrShared.maLineColor.GetAlpha());
|
|
}
|
|
}
|
|
|
|
void AquaGraphicsBackend::SetFillColor()
|
|
{
|
|
mrShared.maFillColor.SetAlpha(0.0); // transparent
|
|
if (mrShared.checkContext())
|
|
{
|
|
CGContextSetRGBFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.GetRed(),
|
|
mrShared.maFillColor.GetGreen(), mrShared.maFillColor.GetBlue(),
|
|
mrShared.maFillColor.GetAlpha());
|
|
}
|
|
}
|
|
|
|
void AquaGraphicsBackend::SetFillColor(Color nColor)
|
|
{
|
|
mrShared.maFillColor = RGBAColor(nColor);
|
|
if (mrShared.checkContext())
|
|
{
|
|
CGContextSetRGBFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.GetRed(),
|
|
mrShared.maFillColor.GetGreen(), mrShared.maFillColor.GetBlue(),
|
|
mrShared.maFillColor.GetAlpha());
|
|
}
|
|
}
|
|
|
|
void AquaGraphicsBackend::SetXORMode(bool bSet, bool bInvertOnly)
|
|
{
|
|
// return early if XOR mode remains unchanged
|
|
if (mrShared.mbPrinter)
|
|
{
|
|
return;
|
|
}
|
|
if (!bSet && mrShared.mnXorMode == 2)
|
|
{
|
|
CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeNormal);
|
|
mrShared.mnXorMode = 0;
|
|
return;
|
|
}
|
|
else if (bSet && bInvertOnly && mrShared.mnXorMode == 0)
|
|
{
|
|
CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
|
|
mrShared.mnXorMode = 2;
|
|
return;
|
|
}
|
|
|
|
if (!mrShared.mpXorEmulation && !bSet)
|
|
{
|
|
return;
|
|
}
|
|
if (mrShared.mpXorEmulation && bSet == mrShared.mpXorEmulation->IsEnabled())
|
|
{
|
|
return;
|
|
}
|
|
if (!mrShared.checkContext())
|
|
{
|
|
return;
|
|
}
|
|
// prepare XOR emulation
|
|
if (!mrShared.mpXorEmulation)
|
|
{
|
|
mrShared.mpXorEmulation = std::make_unique<XorEmulation>();
|
|
mrShared.mpXorEmulation->SetTarget(mrShared.mnWidth, mrShared.mnHeight,
|
|
mrShared.mnBitmapDepth, mrShared.maContextHolder.get(),
|
|
mrShared.maLayer.get());
|
|
}
|
|
|
|
// change the XOR mode
|
|
if (bSet)
|
|
{
|
|
mrShared.mpXorEmulation->Enable();
|
|
mrShared.maContextHolder.set(mrShared.mpXorEmulation->GetMaskContext());
|
|
mrShared.mnXorMode = 1;
|
|
}
|
|
else
|
|
{
|
|
mrShared.mpXorEmulation->UpdateTarget();
|
|
mrShared.mpXorEmulation->Disable();
|
|
mrShared.maContextHolder.set(mrShared.mpXorEmulation->GetTargetContext());
|
|
mrShared.mnXorMode = 0;
|
|
}
|
|
}
|
|
|
|
void AquaGraphicsBackend::SetROPFillColor(SalROPColor nROPColor)
|
|
{
|
|
if (!mrShared.mbPrinter)
|
|
{
|
|
SetFillColor(ImplGetROPColor(nROPColor));
|
|
}
|
|
}
|
|
|
|
void AquaGraphicsBackend::SetROPLineColor(SalROPColor nROPColor)
|
|
{
|
|
if (!mrShared.mbPrinter)
|
|
{
|
|
SetLineColor(ImplGetROPColor(nROPColor));
|
|
}
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawPixelImpl(tools::Long nX, tools::Long nY, const RGBAColor& rColor)
|
|
{
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
// overwrite the fill color
|
|
CGContextSetFillColor(mrShared.maContextHolder.get(), rColor.AsArray());
|
|
// draw 1x1 rect, there is no pixel drawing in Quartz
|
|
const CGRect aDstRect = CGRectMake(nX, nY, 1, 1);
|
|
CGContextFillRect(mrShared.maContextHolder.get(), aDstRect);
|
|
|
|
refreshRect(aDstRect);
|
|
|
|
// reset the fill color
|
|
CGContextSetFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.AsArray());
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY)
|
|
{
|
|
// draw pixel with current line color
|
|
drawPixelImpl(nX, nY, mrShared.maLineColor);
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
|
|
{
|
|
const RGBAColor aPixelColor(nColor);
|
|
drawPixelImpl(nX, nY, aPixelColor);
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
|
|
tools::Long nY2)
|
|
{
|
|
if (nX1 == nX2 && nY1 == nY2)
|
|
{
|
|
// #i109453# platform independent code expects at least one pixel to be drawn
|
|
drawPixel(nX1, nY1);
|
|
return;
|
|
}
|
|
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
CGContextBeginPath(mrShared.maContextHolder.get());
|
|
CGContextMoveToPoint(mrShared.maContextHolder.get(), float(nX1) + 0.5, float(nY1) + 0.5);
|
|
CGContextAddLineToPoint(mrShared.maContextHolder.get(), float(nX2) + 0.5, float(nY2) + 0.5);
|
|
CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathStroke);
|
|
|
|
tools::Rectangle aRefreshRect(nX1, nY1, nX2, nY2);
|
|
(void)aRefreshRect;
|
|
// Is a call to RefreshRect( aRefreshRect ) missing here?
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
|
|
tools::Long nHeight)
|
|
{
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
CGRect aRect = CGRectMake(nX, nY, nWidth, nHeight);
|
|
if (mrShared.isPenVisible())
|
|
{
|
|
aRect.origin.x += 0.5;
|
|
aRect.origin.y += 0.5;
|
|
aRect.size.width -= 1;
|
|
aRect.size.height -= 1;
|
|
}
|
|
|
|
if (mrShared.isBrushVisible())
|
|
{
|
|
CGContextFillRect(mrShared.maContextHolder.get(), aRect);
|
|
}
|
|
if (mrShared.isPenVisible())
|
|
{
|
|
CGContextStrokeRect(mrShared.maContextHolder.get(), aRect);
|
|
}
|
|
mrShared.refreshRect(nX, nY, nWidth, nHeight);
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray)
|
|
{
|
|
if (nPoints < 1)
|
|
return;
|
|
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
tools::Long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
|
|
getBoundRect(nPoints, pPointArray, nX, nY, nWidth, nHeight);
|
|
|
|
float fX, fY;
|
|
CGContextBeginPath(mrShared.maContextHolder.get());
|
|
alignLinePoint(pPointArray, fX, fY);
|
|
CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
|
|
pPointArray++;
|
|
|
|
for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++)
|
|
{
|
|
alignLinePoint(pPointArray, fX, fY);
|
|
CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY);
|
|
}
|
|
CGContextStrokePath(mrShared.maContextHolder.get());
|
|
|
|
mrShared.refreshRect(nX, nY, nWidth, nHeight);
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPointArray)
|
|
{
|
|
if (nPoints <= 1)
|
|
return;
|
|
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
tools::Long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
|
|
getBoundRect(nPoints, pPointArray, nX, nY, nWidth, nHeight);
|
|
|
|
CGPathDrawingMode eMode;
|
|
if (mrShared.isBrushVisible() && mrShared.isPenVisible())
|
|
{
|
|
eMode = kCGPathEOFillStroke;
|
|
}
|
|
else if (mrShared.isPenVisible())
|
|
{
|
|
eMode = kCGPathStroke;
|
|
}
|
|
else if (mrShared.isBrushVisible())
|
|
{
|
|
eMode = kCGPathEOFill;
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
|
|
return;
|
|
}
|
|
|
|
CGContextBeginPath(mrShared.maContextHolder.get());
|
|
|
|
if (mrShared.isPenVisible())
|
|
{
|
|
float fX, fY;
|
|
alignLinePoint(pPointArray, fX, fY);
|
|
CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
|
|
pPointArray++;
|
|
for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++)
|
|
{
|
|
alignLinePoint(pPointArray, fX, fY);
|
|
CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CGContextMoveToPoint(mrShared.maContextHolder.get(), pPointArray->getX(),
|
|
pPointArray->getY());
|
|
pPointArray++;
|
|
for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++)
|
|
{
|
|
CGContextAddLineToPoint(mrShared.maContextHolder.get(), pPointArray->getX(),
|
|
pPointArray->getY());
|
|
}
|
|
}
|
|
|
|
CGContextClosePath(mrShared.maContextHolder.get());
|
|
CGContextDrawPath(mrShared.maContextHolder.get(), eMode);
|
|
|
|
mrShared.refreshRect(nX, nY, nWidth, nHeight);
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawPolyPolygon(sal_uInt32 nPolyCount, const sal_uInt32* pPoints,
|
|
const Point** ppPtAry)
|
|
{
|
|
if (nPolyCount <= 0)
|
|
return;
|
|
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
// find bound rect
|
|
tools::Long leftX = 0, topY = 0, maxWidth = 0, maxHeight = 0;
|
|
|
|
getBoundRect(pPoints[0], ppPtAry[0], leftX, topY, maxWidth, maxHeight);
|
|
|
|
for (sal_uInt32 n = 1; n < nPolyCount; n++)
|
|
{
|
|
tools::Long nX = leftX, nY = topY, nW = maxWidth, nH = maxHeight;
|
|
getBoundRect(pPoints[n], ppPtAry[n], nX, nY, nW, nH);
|
|
if (nX < leftX)
|
|
{
|
|
maxWidth += leftX - nX;
|
|
leftX = nX;
|
|
}
|
|
if (nY < topY)
|
|
{
|
|
maxHeight += topY - nY;
|
|
topY = nY;
|
|
}
|
|
if (nX + nW > leftX + maxWidth)
|
|
{
|
|
maxWidth = nX + nW - leftX;
|
|
}
|
|
if (nY + nH > topY + maxHeight)
|
|
{
|
|
maxHeight = nY + nH - topY;
|
|
}
|
|
}
|
|
|
|
// prepare drawing mode
|
|
CGPathDrawingMode eMode;
|
|
if (mrShared.isBrushVisible() && mrShared.isPenVisible())
|
|
{
|
|
eMode = kCGPathEOFillStroke;
|
|
}
|
|
else if (mrShared.isPenVisible())
|
|
{
|
|
eMode = kCGPathStroke;
|
|
}
|
|
else if (mrShared.isBrushVisible())
|
|
{
|
|
eMode = kCGPathEOFill;
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
|
|
return;
|
|
}
|
|
|
|
// convert to CGPath
|
|
CGContextBeginPath(mrShared.maContextHolder.get());
|
|
if (mrShared.isPenVisible())
|
|
{
|
|
for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
|
|
{
|
|
const sal_uInt32 nPoints = pPoints[nPoly];
|
|
if (nPoints > 1)
|
|
{
|
|
const Point* pPtAry = ppPtAry[nPoly];
|
|
float fX, fY;
|
|
|
|
alignLinePoint(pPtAry, fX, fY);
|
|
CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY);
|
|
pPtAry++;
|
|
|
|
for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
|
|
{
|
|
alignLinePoint(pPtAry, fX, fY);
|
|
CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY);
|
|
}
|
|
CGContextClosePath(mrShared.maContextHolder.get());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
|
|
{
|
|
const sal_uInt32 nPoints = pPoints[nPoly];
|
|
if (nPoints > 1)
|
|
{
|
|
const Point* pPtAry = ppPtAry[nPoly];
|
|
CGContextMoveToPoint(mrShared.maContextHolder.get(), pPtAry->getX(),
|
|
pPtAry->getY());
|
|
pPtAry++;
|
|
for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
|
|
{
|
|
CGContextAddLineToPoint(mrShared.maContextHolder.get(), pPtAry->getX(),
|
|
pPtAry->getY());
|
|
}
|
|
CGContextClosePath(mrShared.maContextHolder.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
CGContextDrawPath(mrShared.maContextHolder.get(), eMode);
|
|
|
|
mrShared.refreshRect(leftX, topY, maxWidth, maxHeight);
|
|
}
|
|
|
|
bool AquaGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
|
|
const basegfx::B2DPolyPolygon& rPolyPolygon,
|
|
double fTransparency)
|
|
{
|
|
#ifdef IOS
|
|
if (!mrShared.maContextHolder.isSet())
|
|
return true;
|
|
#endif
|
|
|
|
// short circuit if there is nothing to do
|
|
if (rPolyPolygon.count() == 0)
|
|
return true;
|
|
|
|
// ignore invisible polygons
|
|
if ((fTransparency >= 1.0) || (fTransparency < 0))
|
|
return true;
|
|
|
|
// Fallback: Transform to DeviceCoordinates
|
|
basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
|
|
aPolyPolygon.transform(rObjectToDevice);
|
|
|
|
// setup poly-polygon path
|
|
CGMutablePathRef xPath = CGPathCreateMutable();
|
|
// tdf#120252 Use the correct, already transformed PolyPolygon (as long as
|
|
// the transformation is not used here...)
|
|
for (auto const& rPolygon : std::as_const(aPolyPolygon))
|
|
{
|
|
AddPolygonToPath(xPath, rPolygon, true, !getAntiAlias(), mrShared.isPenVisible());
|
|
}
|
|
|
|
const CGRect aRefreshRect = CGPathGetBoundingBox(xPath);
|
|
// #i97317# workaround for Quartz having problems with drawing small polygons
|
|
if (aRefreshRect.size.width > 0.125 || aRefreshRect.size.height > 0.125)
|
|
{
|
|
// prepare drawing mode
|
|
CGPathDrawingMode eMode;
|
|
if (mrShared.isBrushVisible() && mrShared.isPenVisible())
|
|
{
|
|
eMode = kCGPathEOFillStroke;
|
|
}
|
|
else if (mrShared.isPenVisible())
|
|
{
|
|
eMode = kCGPathStroke;
|
|
}
|
|
else if (mrShared.isBrushVisible())
|
|
{
|
|
eMode = kCGPathEOFill;
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
|
|
CGPathRelease(xPath);
|
|
return true;
|
|
}
|
|
|
|
// use the path to prepare the graphics context
|
|
mrShared.maContextHolder.saveState();
|
|
CGContextBeginPath(mrShared.maContextHolder.get());
|
|
CGContextAddPath(mrShared.maContextHolder.get(), xPath);
|
|
|
|
// draw path with antialiased polygon
|
|
CGContextSetShouldAntialias(mrShared.maContextHolder.get(), getAntiAlias());
|
|
CGContextSetAlpha(mrShared.maContextHolder.get(), 1.0 - fTransparency);
|
|
CGContextDrawPath(mrShared.maContextHolder.get(), eMode);
|
|
mrShared.maContextHolder.restoreState();
|
|
|
|
// mark modified rectangle as updated
|
|
refreshRect(aRefreshRect);
|
|
}
|
|
|
|
CGPathRelease(xPath);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
|
|
const basegfx::B2DPolygon& rPolyLine, double fTransparency,
|
|
double fLineWidth,
|
|
const std::vector<double>* pStroke, // MM01
|
|
basegfx::B2DLineJoin eLineJoin,
|
|
css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
|
|
bool bPixelSnapHairline)
|
|
{
|
|
// MM01 check done for simple reasons
|
|
if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#ifdef IOS
|
|
if (!mrShared.checkContext())
|
|
return false;
|
|
#endif
|
|
|
|
// tdf#124848 get correct LineWidth in discrete coordinates,
|
|
if (fLineWidth == 0) // hairline
|
|
fLineWidth = 1.0;
|
|
else // Adjust line width for object-to-device scale.
|
|
fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
|
|
|
|
// #i101491# Aqua does not support B2DLineJoin::NONE; return false to use
|
|
// the fallback (own geometry preparation)
|
|
// #i104886# linejoin-mode and thus the above only applies to "fat" lines
|
|
if ((basegfx::B2DLineJoin::NONE == eLineJoin) && (fLineWidth > 1.3))
|
|
return false;
|
|
|
|
// MM01 need to do line dashing as fallback stuff here now
|
|
const double fDotDashLength(
|
|
nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
|
|
const bool bStrokeUsed(0.0 != fDotDashLength);
|
|
assert(!bStrokeUsed || (bStrokeUsed && pStroke));
|
|
basegfx::B2DPolyPolygon aPolyPolygonLine;
|
|
|
|
if (bStrokeUsed)
|
|
{
|
|
// apply LineStyle
|
|
basegfx::utils::applyLineDashing(rPolyLine, // source
|
|
*pStroke, // pattern
|
|
&aPolyPolygonLine, // target for lines
|
|
nullptr, // target for gaps
|
|
fDotDashLength); // full length if available
|
|
}
|
|
else
|
|
{
|
|
// no line dashing, just copy
|
|
aPolyPolygonLine.append(rPolyLine);
|
|
}
|
|
|
|
// Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
|
|
aPolyPolygonLine.transform(rObjectToDevice);
|
|
if (bPixelSnapHairline)
|
|
{
|
|
aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
|
|
}
|
|
|
|
// setup line attributes
|
|
CGLineJoin aCGLineJoin = kCGLineJoinMiter;
|
|
switch (eLineJoin)
|
|
{
|
|
case basegfx::B2DLineJoin::NONE:
|
|
aCGLineJoin = /*TODO?*/ kCGLineJoinMiter;
|
|
break;
|
|
case basegfx::B2DLineJoin::Bevel:
|
|
aCGLineJoin = kCGLineJoinBevel;
|
|
break;
|
|
case basegfx::B2DLineJoin::Miter:
|
|
aCGLineJoin = kCGLineJoinMiter;
|
|
break;
|
|
case basegfx::B2DLineJoin::Round:
|
|
aCGLineJoin = kCGLineJoinRound;
|
|
break;
|
|
}
|
|
// convert miter minimum angle to miter limit
|
|
CGFloat fCGMiterLimit = 1.0 / sin(std::max(fMiterMinimumAngle, 0.01 * M_PI) / 2.0);
|
|
// setup cap attribute
|
|
CGLineCap aCGLineCap(kCGLineCapButt);
|
|
|
|
switch (eLineCap)
|
|
{
|
|
default: // css::drawing::LineCap_BUTT:
|
|
{
|
|
aCGLineCap = kCGLineCapButt;
|
|
break;
|
|
}
|
|
case css::drawing::LineCap_ROUND:
|
|
{
|
|
aCGLineCap = kCGLineCapRound;
|
|
break;
|
|
}
|
|
case css::drawing::LineCap_SQUARE:
|
|
{
|
|
aCGLineCap = kCGLineCapSquare;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// setup poly-polygon path
|
|
CGMutablePathRef xPath = CGPathCreateMutable();
|
|
|
|
// MM01 todo - I assume that this is OKAY to be done in one run for quartz
|
|
// but this NEEDS to be checked/verified
|
|
for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
|
|
{
|
|
const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
|
|
AddPolygonToPath(xPath, aPolyLine, aPolyLine.isClosed(), !getAntiAlias(), true);
|
|
}
|
|
|
|
const CGRect aRefreshRect = CGPathGetBoundingBox(xPath);
|
|
// #i97317# workaround for Quartz having problems with drawing small polygons
|
|
if ((aRefreshRect.size.width > 0.125) || (aRefreshRect.size.height > 0.125))
|
|
{
|
|
// use the path to prepare the graphics context
|
|
mrShared.maContextHolder.saveState();
|
|
CGContextBeginPath(mrShared.maContextHolder.get());
|
|
CGContextAddPath(mrShared.maContextHolder.get(), xPath);
|
|
// draw path with antialiased line
|
|
CGContextSetShouldAntialias(mrShared.maContextHolder.get(), getAntiAlias());
|
|
CGContextSetAlpha(mrShared.maContextHolder.get(), 1.0 - fTransparency);
|
|
CGContextSetLineJoin(mrShared.maContextHolder.get(), aCGLineJoin);
|
|
CGContextSetLineCap(mrShared.maContextHolder.get(), aCGLineCap);
|
|
CGContextSetLineWidth(mrShared.maContextHolder.get(), fLineWidth);
|
|
CGContextSetMiterLimit(mrShared.maContextHolder.get(), fCGMiterLimit);
|
|
CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathStroke);
|
|
mrShared.maContextHolder.restoreState();
|
|
|
|
// mark modified rectangle as updated
|
|
refreshRect(aRefreshRect);
|
|
}
|
|
|
|
CGPathRelease(xPath);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const Point* /*pPointArray*/,
|
|
const PolyFlags* /*pFlagArray*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::drawPolygonBezier(sal_uInt32 /*nPoints*/, const Point* /*pPointArray*/,
|
|
const PolyFlags* /*pFlagArray*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/,
|
|
const Point* const* /*pPointArray*/,
|
|
const PolyFlags* const* /*pFlagArray*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
|
|
{
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
CGImageRef xImage = rSalBitmap.CreateCroppedImage(
|
|
static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
|
|
static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight));
|
|
if (!xImage)
|
|
return;
|
|
|
|
const CGRect aDstRect
|
|
= CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
|
|
CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xImage);
|
|
|
|
CGImageRelease(xImage);
|
|
refreshRect(aDstRect);
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
|
|
const SalBitmap& rTransparentBitmap)
|
|
{
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
CGImageRef xMaskedImage(rSalBitmap.CreateWithMask(rTransparentBitmap, rPosAry.mnSrcX,
|
|
rPosAry.mnSrcY, rPosAry.mnSrcWidth,
|
|
rPosAry.mnSrcHeight));
|
|
if (!xMaskedImage)
|
|
return;
|
|
|
|
const CGRect aDstRect
|
|
= CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
|
|
CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xMaskedImage);
|
|
CGImageRelease(xMaskedImage);
|
|
refreshRect(aDstRect);
|
|
}
|
|
|
|
void AquaGraphicsBackend::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
|
|
Color nMaskColor)
|
|
{
|
|
if (!mrShared.checkContext())
|
|
return;
|
|
|
|
CGImageRef xImage = rSalBitmap.CreateColorMask(
|
|
rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, nMaskColor);
|
|
if (!xImage)
|
|
return;
|
|
|
|
const CGRect aDstRect
|
|
= CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
|
|
CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xImage);
|
|
CGImageRelease(xImage);
|
|
refreshRect(aDstRect);
|
|
}
|
|
|
|
std::shared_ptr<SalBitmap> AquaGraphicsBackend::getBitmap(tools::Long nX, tools::Long nY,
|
|
tools::Long nDX, tools::Long nDY)
|
|
{
|
|
SAL_WARN_IF(!mrShared.maLayer.isSet(), "vcl.quartz",
|
|
"AquaSalGraphics::getBitmap() with no layer this=" << this);
|
|
|
|
mrShared.applyXorContext();
|
|
|
|
std::shared_ptr<QuartzSalBitmap> pBitmap = std::make_shared<QuartzSalBitmap>();
|
|
if (!pBitmap->Create(mrShared.maLayer, mrShared.mnBitmapDepth, nX, nY, nDX, nDY,
|
|
mrShared.isFlipped()))
|
|
{
|
|
pBitmap = nullptr;
|
|
}
|
|
return pBitmap;
|
|
}
|
|
|
|
Color AquaGraphicsBackend::getPixel(tools::Long nX, tools::Long nY)
|
|
{
|
|
// return default value on printers or when out of bounds
|
|
if (!mrShared.maLayer.isSet() || (nX < 0) || (nX >= mrShared.mnWidth) || (nY < 0)
|
|
|| (nY >= mrShared.mnHeight))
|
|
{
|
|
return COL_BLACK;
|
|
}
|
|
|
|
// prepare creation of matching a CGBitmapContext
|
|
#if defined OSL_BIGENDIAN
|
|
struct
|
|
{
|
|
unsigned char b, g, r, a;
|
|
} aPixel;
|
|
#else
|
|
struct
|
|
{
|
|
unsigned char a, r, g, b;
|
|
} aPixel;
|
|
#endif
|
|
|
|
// create a one-pixel bitmap context
|
|
// TODO: is it worth to cache it?
|
|
CGContextRef xOnePixelContext = CGBitmapContextCreate(
|
|
&aPixel, 1, 1, 8, 32, GetSalData()->mxRGBSpace,
|
|
uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Big));
|
|
|
|
// update this graphics layer
|
|
mrShared.applyXorContext();
|
|
|
|
// copy the requested pixel into the bitmap context
|
|
if (mrShared.isFlipped())
|
|
{
|
|
nY = mrShared.mnHeight - nY;
|
|
}
|
|
const CGPoint aCGPoint = CGPointMake(-nX, -nY);
|
|
CGContextDrawLayerAtPoint(xOnePixelContext, aCGPoint, mrShared.maLayer.get());
|
|
|
|
CGContextRelease(xOnePixelContext);
|
|
|
|
Color nColor(aPixel.r, aPixel.g, aPixel.b);
|
|
return nColor;
|
|
}
|
|
|
|
void AquaSalGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
|
|
{
|
|
#ifndef IOS
|
|
if (!mnRealDPIY)
|
|
{
|
|
initResolution((maShared.mbWindow && maShared.mpFrame) ? maShared.mpFrame->getNSWindow()
|
|
: nil);
|
|
}
|
|
|
|
rDPIX = mnRealDPIX;
|
|
rDPIY = mnRealDPIY;
|
|
#else
|
|
// This *must* be 96 or else the iOS app will behave very badly (tiles are scaled wrongly and
|
|
// don't match each others at their boundaries, and other issues). But *why* it must be 96 I
|
|
// have no idea. The commit that changed it to 96 from (the arbitrary) 200 did not say. If you
|
|
// know where else 96 is explicitly or implicitly hard-coded, please modify this comment.
|
|
|
|
// Follow-up: It might be this: in 'online', loleaflet/src/map/Map.js:
|
|
// 15 = 1440 twips-per-inch / 96 dpi.
|
|
// Chosen to match previous hardcoded value of 3840 for
|
|
// the current tile pixel size of 256.
|
|
rDPIX = rDPIY = 96;
|
|
#endif
|
|
}
|
|
|
|
void AquaGraphicsBackend::pattern50Fill()
|
|
{
|
|
static const CGFloat aFillCol[4] = { 1, 1, 1, 1 };
|
|
static const CGPatternCallbacks aCallback = { 0, &drawPattern50, nullptr };
|
|
static const CGColorSpaceRef mxP50Space = CGColorSpaceCreatePattern(GetSalData()->mxRGBSpace);
|
|
static const CGPatternRef mxP50Pattern
|
|
= CGPatternCreate(nullptr, CGRectMake(0, 0, 4, 4), CGAffineTransformIdentity, 4, 4,
|
|
kCGPatternTilingConstantSpacing, false, &aCallback);
|
|
SAL_WARN_IF(!mrShared.maContextHolder.get(), "vcl.quartz", "maContextHolder.get() is NULL");
|
|
CGContextSetFillColorSpace(mrShared.maContextHolder.get(), mxP50Space);
|
|
CGContextSetFillPattern(mrShared.maContextHolder.get(), mxP50Pattern, aFillCol);
|
|
CGContextFillPath(mrShared.maContextHolder.get());
|
|
}
|
|
|
|
void AquaGraphicsBackend::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
|
|
tools::Long nHeight, SalInvert nFlags)
|
|
{
|
|
if (mrShared.checkContext())
|
|
{
|
|
CGRect aCGRect = CGRectMake(nX, nY, nWidth, nHeight);
|
|
mrShared.maContextHolder.saveState();
|
|
if (nFlags & SalInvert::TrackFrame)
|
|
{
|
|
const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line
|
|
CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
|
|
CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0);
|
|
CGContextSetLineDash(mrShared.maContextHolder.get(), 0, dashLengths, 2);
|
|
CGContextSetLineWidth(mrShared.maContextHolder.get(), 2.0);
|
|
CGContextStrokeRect(mrShared.maContextHolder.get(), aCGRect);
|
|
}
|
|
else if (nFlags & SalInvert::N50)
|
|
{
|
|
//CGContextSetAllowsAntialiasing( maContextHolder.get(), false );
|
|
CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
|
|
CGContextAddRect(mrShared.maContextHolder.get(), aCGRect);
|
|
pattern50Fill();
|
|
}
|
|
else // just invert
|
|
{
|
|
CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
|
|
CGContextSetRGBFillColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0);
|
|
CGContextFillRect(mrShared.maContextHolder.get(), aCGRect);
|
|
}
|
|
mrShared.maContextHolder.restoreState();
|
|
refreshRect(aCGRect);
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
CGPoint* makeCGptArray(sal_uInt32 nPoints, const Point* pPtAry)
|
|
{
|
|
CGPoint* CGpoints = new CGPoint[nPoints];
|
|
for (sal_uLong i = 0; i < nPoints; i++)
|
|
{
|
|
CGpoints[i].x = pPtAry[i].getX();
|
|
CGpoints[i].y = pPtAry[i].getY();
|
|
}
|
|
return CGpoints;
|
|
}
|
|
|
|
} // end anonymous ns
|
|
|
|
void AquaGraphicsBackend::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags)
|
|
{
|
|
if (mrShared.checkContext())
|
|
{
|
|
mrShared.maContextHolder.saveState();
|
|
CGPoint* CGpoints = makeCGptArray(nPoints, pPtAry);
|
|
CGContextAddLines(mrShared.maContextHolder.get(), CGpoints, nPoints);
|
|
if (nSalFlags & SalInvert::TrackFrame)
|
|
{
|
|
const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line
|
|
CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
|
|
CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0);
|
|
CGContextSetLineDash(mrShared.maContextHolder.get(), 0, dashLengths, 2);
|
|
CGContextSetLineWidth(mrShared.maContextHolder.get(), 2.0);
|
|
CGContextStrokePath(mrShared.maContextHolder.get());
|
|
}
|
|
else if (nSalFlags & SalInvert::N50)
|
|
{
|
|
CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
|
|
pattern50Fill();
|
|
}
|
|
else // just invert
|
|
{
|
|
CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference);
|
|
CGContextSetRGBFillColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0);
|
|
CGContextFillPath(mrShared.maContextHolder.get());
|
|
}
|
|
const CGRect aRefreshRect = CGContextGetClipBoundingBox(mrShared.maContextHolder.get());
|
|
mrShared.maContextHolder.restoreState();
|
|
delete[] CGpoints;
|
|
refreshRect(aRefreshRect);
|
|
}
|
|
}
|
|
|
|
#ifndef IOS
|
|
bool AquaGraphicsBackend::drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth,
|
|
tools::Long nHeight, void* pEpsData, sal_uInt32 nByteCount)
|
|
{
|
|
// convert the raw data to an NSImageRef
|
|
NSData* xNSData = [NSData dataWithBytes:pEpsData length:static_cast<int>(nByteCount)];
|
|
NSImageRep* xEpsImage = [NSEPSImageRep imageRepWithData:xNSData];
|
|
if (!xEpsImage)
|
|
{
|
|
return false;
|
|
}
|
|
// get the target context
|
|
if (!mrShared.checkContext())
|
|
{
|
|
return false;
|
|
}
|
|
// NOTE: flip drawing, else the nsimage would be drawn upside down
|
|
mrShared.maContextHolder.saveState();
|
|
// CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight );
|
|
CGContextScaleCTM(mrShared.maContextHolder.get(), +1, -1);
|
|
nY = /*mnHeight*/ -(nY + nHeight);
|
|
|
|
// prepare the target context
|
|
NSGraphicsContext* pOrigNSCtx = [NSGraphicsContext currentContext];
|
|
[pOrigNSCtx retain];
|
|
|
|
// create new context
|
|
NSGraphicsContext* pDrawNSCtx =
|
|
[NSGraphicsContext graphicsContextWithCGContext:mrShared.maContextHolder.get()
|
|
flipped:mrShared.isFlipped()];
|
|
// set it, setCurrentContext also releases the previously set one
|
|
[NSGraphicsContext setCurrentContext:pDrawNSCtx];
|
|
|
|
// draw the EPS
|
|
const NSRect aDstRect = NSMakeRect(nX, nY, nWidth, nHeight);
|
|
const bool bOK = [xEpsImage drawInRect:aDstRect];
|
|
|
|
// restore the NSGraphicsContext
|
|
[NSGraphicsContext setCurrentContext:pOrigNSCtx];
|
|
[pOrigNSCtx release]; // restore the original retain count
|
|
|
|
mrShared.maContextHolder.restoreState();
|
|
// mark the destination rectangle as updated
|
|
refreshRect(aDstRect);
|
|
|
|
return bOK;
|
|
}
|
|
#else
|
|
bool AquaGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
|
|
tools::Long /*nHeight*/, void* /*pEpsData*/,
|
|
sal_uInt32 /*nByteCount*/)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool AquaGraphicsBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/,
|
|
const SalBitmap& /*rSrcBitmap*/,
|
|
const SalBitmap& /*rMaskBitmap*/,
|
|
const SalBitmap& /*rAlphaBitmap*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap,
|
|
const SalBitmap& rAlphaBmp)
|
|
{
|
|
// An image mask can't have a depth > 8 bits (should be 1 to 8 bits)
|
|
if (rAlphaBmp.GetBitCount() > 8)
|
|
return false;
|
|
|
|
// are these two tests really necessary? (see vcl/unx/source/gdi/salgdi2.cxx)
|
|
// horizontal/vertical mirroring not implemented yet
|
|
if (rTR.mnDestWidth < 0 || rTR.mnDestHeight < 0)
|
|
return false;
|
|
|
|
CGImageRef xMaskedImage = rSrcBitmap.CreateWithMask(rAlphaBmp, rTR.mnSrcX, rTR.mnSrcY,
|
|
rTR.mnSrcWidth, rTR.mnSrcHeight);
|
|
if (!xMaskedImage)
|
|
return false;
|
|
|
|
if (mrShared.checkContext())
|
|
{
|
|
const CGRect aDstRect
|
|
= CGRectMake(rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
|
|
CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xMaskedImage);
|
|
refreshRect(aDstRect);
|
|
}
|
|
|
|
CGImageRelease(xMaskedImage);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
|
|
const basegfx::B2DPoint& rX,
|
|
const basegfx::B2DPoint& rY,
|
|
const SalBitmap& rSrcBitmap,
|
|
const SalBitmap* pAlphaBmp, double fAlpha)
|
|
{
|
|
if (!mrShared.checkContext())
|
|
return true;
|
|
|
|
if (fAlpha != 1.0)
|
|
return false;
|
|
|
|
// get the Quartz image
|
|
CGImageRef xImage = nullptr;
|
|
const Size aSize = rSrcBitmap.GetSize();
|
|
|
|
if (!pAlphaBmp)
|
|
xImage = rSrcBitmap.CreateCroppedImage(0, 0, int(aSize.Width()), int(aSize.Height()));
|
|
else
|
|
xImage
|
|
= rSrcBitmap.CreateWithMask(*pAlphaBmp, 0, 0, int(aSize.Width()), int(aSize.Height()));
|
|
|
|
if (!xImage)
|
|
return false;
|
|
|
|
// setup the image transformation
|
|
// using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
|
|
mrShared.maContextHolder.saveState();
|
|
const basegfx::B2DVector aXRel = rX - rNull;
|
|
const basegfx::B2DVector aYRel = rY - rNull;
|
|
const CGAffineTransform aCGMat = CGAffineTransformMake(
|
|
aXRel.getX() / aSize.Width(), aXRel.getY() / aSize.Width(), aYRel.getX() / aSize.Height(),
|
|
aYRel.getY() / aSize.Height(), rNull.getX(), rNull.getY());
|
|
|
|
CGContextConcatCTM(mrShared.maContextHolder.get(), aCGMat);
|
|
|
|
// draw the transformed image
|
|
const CGRect aSrcRect = CGRectMake(0, 0, aSize.Width(), aSize.Height());
|
|
CGContextDrawImage(mrShared.maContextHolder.get(), aSrcRect, xImage);
|
|
|
|
CGImageRelease(xImage);
|
|
|
|
// restore the Quartz graphics state
|
|
mrShared.maContextHolder.restoreState();
|
|
|
|
// mark the destination as painted
|
|
const CGRect aDstRect = CGRectApplyAffineTransform(aSrcRect, aCGMat);
|
|
refreshRect(aDstRect);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::hasFastDrawTransformedBitmap() const { return false; }
|
|
|
|
bool AquaGraphicsBackend::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
|
|
tools::Long nHeight, sal_uInt8 nTransparency)
|
|
{
|
|
if (!mrShared.checkContext())
|
|
return true;
|
|
|
|
// save the current state
|
|
mrShared.maContextHolder.saveState();
|
|
CGContextSetAlpha(mrShared.maContextHolder.get(), (100 - nTransparency) * (1.0 / 100));
|
|
|
|
CGRect aRect = CGRectMake(nX, nY, nWidth - 1, nHeight - 1);
|
|
if (mrShared.isPenVisible())
|
|
{
|
|
aRect.origin.x += 0.5;
|
|
aRect.origin.y += 0.5;
|
|
}
|
|
|
|
CGContextBeginPath(mrShared.maContextHolder.get());
|
|
CGContextAddRect(mrShared.maContextHolder.get(), aRect);
|
|
CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathFill);
|
|
|
|
mrShared.maContextHolder.restoreState();
|
|
refreshRect(aRect);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
|
|
const Gradient& /*rGradient*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/,
|
|
SalGradient const& /*rGradient*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool AquaGraphicsBackend::supportsOperation(OutDevSupportType eType) const
|
|
{
|
|
switch (eType)
|
|
{
|
|
case OutDevSupportType::TransparentRect:
|
|
case OutDevSupportType::B2DDraw:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|