summaryrefslogtreecommitdiffstats
path: root/vcl/quartz/salgdicommon.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/quartz/salgdicommon.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--vcl/quartz/salgdicommon.cxx2047
1 files changed, 2047 insertions, 0 deletions
diff --git a/vcl/quartz/salgdicommon.cxx b/vcl/quartz/salgdicommon.cxx
new file mode 100644
index 000000000..8b51e91c6
--- /dev/null
+++ b/vcl/quartz/salgdicommon.cxx
@@ -0,0 +1,2047 @@
+/* -*- 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 <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 <vcl/sysdata.hxx>
+
+#include <fontsubset.hxx>
+#include <quartz/salbmp.h>
+#ifdef MACOSX
+#include <quartz/salgdi.h>
+#endif
+#include <quartz/utils.h>
+#ifdef IOS
+#include "saldatabasic.hxx"
+#endif
+#include <sft.hxx>
+
+using namespace vcl;
+
+static const basegfx::B2DPoint aHalfPointOfs ( 0.5, 0.5 );
+
+static 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 );
+ }
+}
+
+static void AddPolyPolygonToPath( CGMutablePathRef xPath,
+ const basegfx::B2DPolyPolygon& rPolyPoly,
+ bool bPixelSnap, bool bLineDraw )
+{
+ // short circuit if there is nothing to do
+ if( rPolyPoly.count() == 0 )
+ {
+ return;
+ }
+ for(auto const& rPolygon : rPolyPoly)
+ {
+ AddPolygonToPath( xPath, rPolygon, true, bPixelSnap, bLineDraw );
+ }
+}
+
+bool AquaSalGraphics::CreateFontSubset( const OUString& rToFile,
+ const PhysicalFontFace* pFontData,
+ const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding,
+ sal_Int32* pGlyphWidths, int nGlyphCount,
+ FontSubsetInfo& rInfo )
+{
+ // TODO: move more of the functionality here into the generic subsetter code
+
+ // prepare the requested file name for writing the font-subset file
+ OUString aSysPath;
+ if( osl_File_E_None != osl_getSystemPathFromFileURL( rToFile.pData, &aSysPath.pData ) )
+ {
+ return false;
+ }
+
+ // get the raw-bytes from the font to be subset
+ std::vector<unsigned char> aBuffer;
+ bool bCffOnly = false;
+ if( !GetRawFontData( pFontData, aBuffer, &bCffOnly ) )
+ {
+ return false;
+ }
+ const OString aToFile( OUStringToOString( aSysPath,
+ osl_getThreadTextEncoding()));
+
+ // handle CFF-subsetting
+ if( bCffOnly )
+ {
+ // provide the raw-CFF data to the subsetter
+ ByteCount nCffLen = aBuffer.size();
+ rInfo.LoadFont( FontType::CFF_FONT, aBuffer.data(), nCffLen );
+
+ // NOTE: assuming that all glyphids requested on Aqua are fully translated
+
+ // make the subsetter provide the requested subset
+ FILE* pOutFile = fopen( aToFile.getStr(), "wb" );
+ bool bRC = rInfo.CreateFontSubset( FontType::TYPE1_PFB, pOutFile, nullptr,
+ pGlyphIds, pEncoding, nGlyphCount, pGlyphWidths );
+ fclose( pOutFile );
+ return bRC;
+ }
+
+ // TODO: modernize psprint's horrible fontsubset C-API
+ // this probably only makes sense after the switch to another SCM
+ // that can preserve change history after file renames
+
+ // prepare data for psprint's font subsetter
+ TrueTypeFont* pSftFont = nullptr;
+ SFErrCodes nRC = ::OpenTTFontBuffer( static_cast<void*>(aBuffer.data()), aBuffer.size(), 0, &pSftFont);
+ if( nRC != SFErrCodes::Ok )
+ {
+ return false;
+ }
+ // get details about the subsetted font
+ TTGlobalFontInfo aTTInfo;
+ ::GetTTGlobalFontInfo( pSftFont, &aTTInfo );
+ rInfo.m_nFontType = FontType::SFNT_TTF;
+ rInfo.m_aPSName = OUString( aTTInfo.psname, std::strlen(aTTInfo.psname),
+ RTL_TEXTENCODING_UTF8 );
+ rInfo.m_aFontBBox = tools::Rectangle( Point( aTTInfo.xMin, aTTInfo.yMin ),
+ Point( aTTInfo.xMax, aTTInfo.yMax ) );
+ rInfo.m_nCapHeight = aTTInfo.yMax; // Well ...
+ rInfo.m_nAscent = aTTInfo.winAscent;
+ rInfo.m_nDescent = aTTInfo.winDescent;
+ // mac fonts usually do not have an OS2-table
+ // => get valid ascent/descent values from other tables
+ if( !rInfo.m_nAscent )
+ {
+ rInfo.m_nAscent = +aTTInfo.typoAscender;
+ }
+ if( !rInfo.m_nAscent )
+ {
+ rInfo.m_nAscent = +aTTInfo.ascender;
+ }
+ if( !rInfo.m_nDescent )
+ {
+ rInfo.m_nDescent = +aTTInfo.typoDescender;
+ }
+ if( !rInfo.m_nDescent )
+ {
+ rInfo.m_nDescent = -aTTInfo.descender;
+ }
+
+ // subset glyphs and get their properties
+ // take care that subset fonts require the NotDef glyph in pos 0
+ int nOrigCount = nGlyphCount;
+ sal_uInt16 aShortIDs[ 257 ];
+ sal_uInt8 aTempEncs[ 257 ];
+ int nNotDef = -1;
+
+ assert( (nGlyphCount <= 256 && "too many glyphs for subsetting" ));
+
+ for( int i = 0; i < nGlyphCount; ++i )
+ {
+ aTempEncs[i] = pEncoding[i];
+
+ sal_GlyphId aGlyphId(pGlyphIds[i]);
+ aShortIDs[i] = static_cast<sal_uInt16>( aGlyphId );
+ if( !aGlyphId && nNotDef < 0 )
+ {
+ nNotDef = i; // first NotDef glyph found
+ }
+ }
+
+ if( nNotDef != 0 )
+ {
+ // add fake NotDef glyph if needed
+ if( nNotDef < 0 )
+ {
+ nNotDef = nGlyphCount++;
+ }
+ // NotDef glyph must be in pos 0 => swap glyphids
+ aShortIDs[ nNotDef ] = aShortIDs[0];
+ aTempEncs[ nNotDef ] = aTempEncs[0];
+ aShortIDs[0] = 0;
+ aTempEncs[0] = 0;
+ }
+
+ // TODO: where to get bVertical?
+ const bool bVertical = false;
+
+ // fill the pGlyphWidths array
+ // while making sure that the NotDef glyph is at index==0
+ std::unique_ptr<sal_uInt16[]> pGlyphMetrics = ::GetTTSimpleGlyphMetrics( pSftFont, aShortIDs,
+ nGlyphCount, bVertical );
+ if( !pGlyphMetrics )
+ {
+ return false;
+ }
+
+ sal_uInt16 nNotDefAdv = pGlyphMetrics[0];
+ pGlyphMetrics[0] = pGlyphMetrics[nNotDef];
+ pGlyphMetrics[nNotDef] = nNotDefAdv;
+ for( int i = 0; i < nOrigCount; ++i )
+ {
+ pGlyphWidths[i] = pGlyphMetrics[i];
+ }
+ pGlyphMetrics.reset();
+
+ // write subset into destination file
+ nRC = ::CreateTTFromTTGlyphs( pSftFont, aToFile.getStr(), aShortIDs,
+ aTempEncs, nGlyphCount );
+ ::CloseTTFont(pSftFont);
+ return (nRC == SFErrCodes::Ok);
+}
+
+static void alignLinePoint( const SalPoint* i_pIn, float& o_fX, float& o_fY )
+{
+ o_fX = static_cast<float>(i_pIn->mnX ) + 0.5;
+ o_fY = static_cast<float>(i_pIn->mnY ) + 0.5;
+}
+
+void AquaSalGraphics::copyBits( const SalTwoRect& rPosAry, SalGraphics *pSrcGraphics )
+{
+
+ if( !pSrcGraphics )
+ {
+ pSrcGraphics = this;
+ }
+ //from unix salgdi2.cxx
+ //[FIXME] find a better way to prevent calc from crashing when width and height are negative
+ if( rPosAry.mnSrcWidth <= 0 ||
+ rPosAry.mnSrcHeight <= 0 ||
+ rPosAry.mnDestWidth <= 0 ||
+ rPosAry.mnDestHeight <= 0 )
+ {
+ return;
+ }
+
+#ifdef IOS
+ // If called from idle layout, maContextHolder.get() is NULL, no idea what to do
+ if (!maContextHolder.isSet())
+ return;
+#endif
+
+ // accelerate trivial operations
+ /*const*/ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
+ const bool bSameGraphics = (this == pSrc)
+#ifdef MACOSX
+ || (mbWindow && mpFrame && pSrc->mbWindow && (mpFrame == pSrc->mpFrame))
+#endif
+ ;
+
+ if( bSameGraphics &&
+ (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
+ (rPosAry.mnSrcHeight == rPosAry.mnDestHeight))
+ {
+ // short circuit if there is nothing to do
+ if( (rPosAry.mnSrcX == rPosAry.mnDestX) &&
+ (rPosAry.mnSrcY == rPosAry.mnDestY))
+ {
+ return;
+ }
+ // use copyArea() if source and destination context are identical
+ copyArea( rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, false/*bWindowInvalidate*/ );
+ return;
+ }
+
+ ApplyXorContext();
+ pSrc->ApplyXorContext();
+
+ SAL_WARN_IF (!pSrc->maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
+
+ const CGPoint aDstPoint = CGPointMake(+rPosAry.mnDestX - rPosAry.mnSrcX, rPosAry.mnDestY - rPosAry.mnSrcY);
+ if ((rPosAry.mnSrcWidth == rPosAry.mnDestWidth &&
+ rPosAry.mnSrcHeight == rPosAry.mnDestHeight) &&
+ (!mnBitmapDepth || (aDstPoint.x + pSrc->mnWidth) <= mnWidth)
+ && pSrc->maLayer.isSet()) // workaround for a Quartz crash
+ {
+ // in XOR mode the drawing context is redirected to the XOR mask
+ // if source and target are identical then copyBits() paints onto the target context though
+ CGContextHolder aCopyContext = maContextHolder;
+ if( mpXorEmulation && mpXorEmulation->IsEnabled() )
+ {
+ if( pSrcGraphics == this )
+ {
+ aCopyContext.set(mpXorEmulation->GetTargetContext());
+ }
+ }
+ aCopyContext.saveState();
+
+ const CGRect aDstRect = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ CGContextClipToRect(aCopyContext.get(), aDstRect);
+
+ // draw at new destination
+ // NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down
+ if( pSrc->IsFlipped() )
+ {
+ CGContextTranslateCTM( aCopyContext.get(), 0, +mnHeight );
+ CGContextScaleCTM( aCopyContext.get(), +1, -1 );
+ }
+
+ // TODO: pSrc->size() != this->size()
+ CGContextDrawLayerAtPoint(aCopyContext.get(), aDstPoint, pSrc->maLayer.get());
+
+ aCopyContext.restoreState();
+ // mark the destination rectangle as updated
+ RefreshRect( aDstRect );
+ }
+ else
+ {
+ std::shared_ptr<SalBitmap> pBitmap = pSrc->getBitmap( rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight );
+ if( pBitmap )
+ {
+ SalTwoRect aPosAry( rPosAry );
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap( aPosAry, *pBitmap );
+ }
+ }
+}
+
+static void DrawPattern50( void*, CGContextRef rContext )
+{
+ static const CGRect aRects[2] = { { {0,0}, { 2, 2 } }, { { 2, 2 }, { 2, 2 } } };
+ CGContextAddRects( rContext, aRects, 2 );
+ CGContextFillPath( rContext );
+}
+
+static void getBoundRect( sal_uInt32 nPoints, const SalPoint *pPtAry,
+ long &rX, long& rY, long& rWidth, long& rHeight )
+{
+ long nX1 = pPtAry->mnX;
+ long nX2 = nX1;
+ long nY1 = pPtAry->mnY;
+ long nY2 = nY1;
+
+ for( sal_uInt32 n = 1; n < nPoints; n++ )
+ {
+ if( pPtAry[n].mnX < nX1 )
+ {
+ nX1 = pPtAry[n].mnX;
+ }
+ else if( pPtAry[n].mnX > nX2 )
+ {
+ nX2 = pPtAry[n].mnX;
+ }
+ if( pPtAry[n].mnY < nY1 )
+ {
+ nY1 = pPtAry[n].mnY;
+ }
+ else if( pPtAry[n].mnY > nY2 )
+ {
+ nY2 = pPtAry[n].mnY;
+ }
+ }
+ rX = nX1;
+ rY = nY1;
+ rWidth = nX2 - nX1 + 1;
+ rHeight = nY2 - nY1 + 1;
+}
+
+static Color ImplGetROPColor( SalROPColor nROPColor )
+{
+ Color nColor;
+ if ( nROPColor == SalROPColor::N0 )
+ {
+ nColor = Color( 0, 0, 0 );
+ }
+ else
+ {
+ nColor = Color( 255, 255, 255 );
+ }
+ return nColor;
+}
+
+// apply the XOR mask to the target context if active and dirty
+void AquaSalGraphics::ApplyXorContext()
+{
+ if( !mpXorEmulation )
+ {
+ return;
+ }
+ if( mpXorEmulation->UpdateTarget() )
+ {
+ RefreshRect( 0, 0, mnWidth, mnHeight ); // TODO: refresh minimal changerect
+ }
+}
+
+void AquaSalGraphics::copyArea( long nDstX, long nDstY,long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight, bool /*bWindowInvalidate*/ )
+{
+ SAL_WARN_IF (!maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyArea() for non-layered graphics this=" << this);
+
+#ifdef IOS
+ if (!maLayer.isSet())
+ return;
+#endif
+ float fScale = maLayer.getScale();
+
+ long nScaledSourceX = nSrcX * fScale;
+ long nScaledSourceY = nSrcY * fScale;
+
+ long nScaledTargetX = nDstX * fScale;
+ long nScaledTargetY = nDstY * fScale;
+
+ long nScaledSourceWidth = nSrcWidth * fScale;
+ long nScaledSourceHeight = nSrcHeight * fScale;
+
+ ApplyXorContext();
+
+ maContextHolder.saveState();
+
+ // in XOR mode the drawing context is redirected to the XOR mask
+ // copyArea() always works on the target context though
+ CGContextRef xCopyContext = maContextHolder.get();
+
+ if( mpXorEmulation && mpXorEmulation->IsEnabled() )
+ {
+ xCopyContext = mpXorEmulation->GetTargetContext();
+ }
+
+ // If we have a scaled layer, we need to revert the scaling or else
+ // it will interfere with the coordinate calculation
+ CGContextScaleCTM(xCopyContext, 1.0 / fScale, 1.0 / fScale);
+
+ // drawing a layer onto its own context causes trouble on OSX => copy it first
+ // TODO: is it possible to get rid of this unneeded copy more often?
+ // e.g. on OSX>=10.5 only this situation causes problems:
+ // mnBitmapDepth && (aDstPoint.x + pSrc->mnWidth) > mnWidth
+
+ CGLayerHolder sSourceLayerHolder(maLayer);
+ {
+ const CGSize aSrcSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
+ sSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSrcSize, nullptr));
+
+ const CGContextRef xSrcContext = CGLayerGetContext(sSourceLayerHolder.get());
+
+ CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
+ if( IsFlipped() )
+ {
+ CGContextTranslateCTM( xSrcContext, 0, +nScaledSourceHeight );
+ CGContextScaleCTM( xSrcContext, +1, -1 );
+ aSrcPoint.y = (nScaledSourceY + nScaledSourceHeight) - (mnHeight * fScale);
+ }
+ CGContextSetBlendMode(xSrcContext, kCGBlendModeCopy);
+
+ CGContextDrawLayerAtPoint(xSrcContext, aSrcPoint, maLayer.get());
+ }
+
+ // draw at new destination
+ const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
+ CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
+ CGContextDrawLayerInRect(xCopyContext, aTargetRect, sSourceLayerHolder.get());
+
+ maContextHolder.restoreState();
+
+ // cleanup
+ if (sSourceLayerHolder.get() != maLayer.get())
+ {
+ CGLayerRelease(sSourceLayerHolder.get());
+ }
+ // mark the destination rectangle as updated
+ RefreshRect( nDstX, nDstY, nSrcWidth, nSrcHeight );
+}
+
+#ifndef IOS
+
+void AquaSalGraphics::copyResolution( AquaSalGraphics& rGraphics )
+{
+ if( !rGraphics.mnRealDPIY && rGraphics.mbWindow && rGraphics.mpFrame )
+ {
+ rGraphics.initResolution( rGraphics.mpFrame->getNSWindow() );
+ }
+ mnRealDPIX = rGraphics.mnRealDPIX;
+ mnRealDPIY = rGraphics.mnRealDPIY;
+}
+
+#endif
+
+bool AquaSalGraphics::blendBitmap( const SalTwoRect&,
+ const SalBitmap& )
+{
+ return false;
+}
+
+bool AquaSalGraphics::blendAlphaBitmap( const SalTwoRect&,
+ const SalBitmap&,
+ const SalBitmap&,
+ const SalBitmap& )
+{
+ return false;
+}
+
+bool AquaSalGraphics::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;
+
+ const QuartzSalBitmap& rSrcSalBmp = static_cast<const QuartzSalBitmap&>(rSrcBitmap);
+ const QuartzSalBitmap& rMaskSalBmp = static_cast<const QuartzSalBitmap&>(rAlphaBmp);
+ CGImageRef xMaskedImage = rSrcSalBmp.CreateWithMask( rMaskSalBmp, rTR.mnSrcX,
+ rTR.mnSrcY, rTR.mnSrcWidth,
+ rTR.mnSrcHeight );
+ if( !xMaskedImage )
+ return false;
+
+ if ( CheckContext() )
+ {
+ const CGRect aDstRect = CGRectMake( rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
+ CGContextDrawImage( maContextHolder.get(), aDstRect, xMaskedImage );
+ RefreshRect( aDstRect );
+ }
+
+ CGImageRelease(xMaskedImage);
+
+ return true;
+}
+
+bool AquaSalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX, const basegfx::B2DPoint& rY,
+ const SalBitmap& rSrcBitmap, const SalBitmap* pAlphaBmp )
+{
+ if( !CheckContext() )
+ return true;
+
+ // get the Quartz image
+ CGImageRef xImage = nullptr;
+ const Size aSize = rSrcBitmap.GetSize();
+ const QuartzSalBitmap& rSrcSalBmp = static_cast<const QuartzSalBitmap&>(rSrcBitmap);
+ const QuartzSalBitmap* pMaskSalBmp = static_cast<const QuartzSalBitmap*>(pAlphaBmp);
+
+ if( !pMaskSalBmp)
+ xImage = rSrcSalBmp.CreateCroppedImage( 0, 0, static_cast<int>(aSize.Width()), static_cast<int>(aSize.Height()) );
+ else
+ xImage = rSrcSalBmp.CreateWithMask( *pMaskSalBmp, 0, 0, static_cast<int>(aSize.Width()), static_cast<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
+ 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( maContextHolder.get(), aCGMat );
+
+ // draw the transformed image
+ const CGRect aSrcRect = CGRectMake(0, 0, aSize.Width(), aSize.Height());
+ CGContextDrawImage( maContextHolder.get(), aSrcRect, xImage );
+
+ CGImageRelease( xImage );
+ // restore the Quartz graphics state
+ maContextHolder.restoreState();
+
+ // mark the destination as painted
+ const CGRect aDstRect = CGRectApplyAffineTransform( aSrcRect, aCGMat );
+ RefreshRect( aDstRect );
+
+ return true;
+}
+
+bool AquaSalGraphics::drawAlphaRect( long nX, long nY, long nWidth,
+ long nHeight, sal_uInt8 nTransparency )
+{
+ if( !CheckContext() )
+ return true;
+
+ // save the current state
+ maContextHolder.saveState();
+ CGContextSetAlpha( maContextHolder.get(), (100-nTransparency) * (1.0/100) );
+
+ CGRect aRect = CGRectMake(nX, nY, nWidth-1, nHeight-1);
+ if( IsPenVisible() )
+ {
+ aRect.origin.x += 0.5;
+ aRect.origin.y += 0.5;
+ }
+
+ CGContextBeginPath( maContextHolder.get() );
+ CGContextAddRect( maContextHolder.get(), aRect );
+ CGContextDrawPath( maContextHolder.get(), kCGPathFill );
+
+ maContextHolder.restoreState();
+ RefreshRect( aRect );
+
+ return true;
+}
+
+void AquaSalGraphics::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap )
+{
+ if( !CheckContext() )
+ return;
+
+ const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap);
+ CGImageRef xImage = rBitmap.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( maContextHolder.get(), aDstRect, xImage );
+
+ CGImageRelease( xImage );
+ RefreshRect( aDstRect );
+}
+
+void AquaSalGraphics::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap )
+{
+ if( !CheckContext() )
+ return;
+
+ const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap);
+ const QuartzSalBitmap& rMask = static_cast<const QuartzSalBitmap&>(rTransparentBitmap);
+ CGImageRef xMaskedImage( rBitmap.CreateWithMask( rMask, rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight ) );
+ if( !xMaskedImage )
+ return;
+
+ const CGRect aDstRect = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ CGContextDrawImage( maContextHolder.get(), aDstRect, xMaskedImage );
+ CGImageRelease( xMaskedImage );
+ RefreshRect( aDstRect );
+}
+
+#ifndef IOS
+
+bool AquaSalGraphics::drawEPS( long nX, long nY, long nWidth, 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( !CheckContext() )
+ {
+ return false;
+ }
+ // NOTE: flip drawing, else the nsimage would be drawn upside down
+ maContextHolder.saveState();
+// CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight );
+ CGContextScaleCTM( 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: maContextHolder.get() flipped: 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
+
+ maContextHolder.restoreState();
+ // mark the destination rectangle as updated
+ RefreshRect( aDstRect );
+
+ return bOK;
+}
+
+#endif
+
+void AquaSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
+{
+ if( nX1 == nX2 && nY1 == nY2 )
+ {
+ // #i109453# platform independent code expects at least one pixel to be drawn
+ drawPixel( nX1, nY1 );
+
+ return;
+ }
+
+ if( !CheckContext() )
+ return;
+
+ CGContextBeginPath( maContextHolder.get() );
+ CGContextMoveToPoint( maContextHolder.get(), static_cast<float>(nX1)+0.5, static_cast<float>(nY1)+0.5 );
+ CGContextAddLineToPoint( maContextHolder.get(), static_cast<float>(nX2)+0.5, static_cast<float>(nY2)+0.5 );
+ CGContextDrawPath( maContextHolder.get(), kCGPathStroke );
+
+ tools::Rectangle aRefreshRect( nX1, nY1, nX2, nY2 );
+ (void) aRefreshRect;
+ // Is a call to RefreshRect( aRefreshRect ) missing here?
+}
+
+void AquaSalGraphics::drawMask( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, Color nMaskColor )
+{
+ if( !CheckContext() )
+ return;
+
+ const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap);
+ CGImageRef xImage = rBitmap.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( maContextHolder.get(), aDstRect, xImage );
+ CGImageRelease( xImage );
+ RefreshRect( aDstRect );
+}
+
+void AquaSalGraphics::drawPixel( long nX, long nY )
+{
+ // draw pixel with current line color
+ ImplDrawPixel( nX, nY, maLineColor );
+}
+
+void AquaSalGraphics::drawPixel( long nX, long nY, Color nColor )
+{
+ const RGBAColor aPixelColor( nColor );
+ ImplDrawPixel( nX, nY, aPixelColor );
+}
+
+bool AquaSalGraphics::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( !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(fMiterMinimumAngle / 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(),
+ !getAntiAliasB2DDraw(),
+ 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
+ maContextHolder.saveState();
+ CGContextBeginPath( maContextHolder.get() );
+ CGContextAddPath( maContextHolder.get(), xPath );
+ // draw path with antialiased line
+ CGContextSetShouldAntialias( maContextHolder.get(), true );
+ CGContextSetAlpha( maContextHolder.get(), 1.0 - fTransparency );
+ CGContextSetLineJoin( maContextHolder.get(), aCGLineJoin );
+ CGContextSetLineCap( maContextHolder.get(), aCGLineCap );
+ CGContextSetLineWidth( maContextHolder.get(), fLineWidth );
+ CGContextSetMiterLimit(maContextHolder.get(), fCGMiterLimit);
+ CGContextDrawPath( maContextHolder.get(), kCGPathStroke );
+ maContextHolder.restoreState();
+
+ // mark modified rectangle as updated
+ RefreshRect( aRefreshRect );
+ }
+
+ CGPathRelease( xPath );
+
+ return true;
+}
+
+bool AquaSalGraphics::drawPolyLineBezier( sal_uInt32, const SalPoint*, const PolyFlags* )
+{
+ return false;
+}
+
+bool AquaSalGraphics::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+#ifdef IOS
+ if (!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 : aPolyPolygon)
+ {
+ AddPolygonToPath( xPath, rPolygon, true, !getAntiAliasB2DDraw(), 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( IsBrushVisible() && IsPenVisible() )
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if( IsPenVisible() )
+ {
+ eMode = kCGPathStroke;
+ }
+ else if( 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
+ maContextHolder.saveState();
+ CGContextBeginPath( maContextHolder.get() );
+ CGContextAddPath( maContextHolder.get(), xPath );
+
+ // draw path with antialiased polygon
+ CGContextSetShouldAntialias( maContextHolder.get(), true );
+ CGContextSetAlpha( maContextHolder.get(), 1.0 - fTransparency );
+ CGContextDrawPath( maContextHolder.get(), eMode );
+ maContextHolder.restoreState();
+
+ // mark modified rectangle as updated
+ RefreshRect( aRefreshRect );
+ }
+
+ CGPathRelease( xPath );
+
+ return true;
+}
+
+void AquaSalGraphics::drawPolyPolygon( sal_uInt32 nPolyCount, const sal_uInt32 *pPoints, PCONSTSALPOINT *ppPtAry )
+{
+ if( nPolyCount <= 0 )
+ return;
+
+ if( !CheckContext() )
+ return;
+
+ // find bound rect
+ 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++ )
+ {
+ 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( IsBrushVisible() && IsPenVisible() )
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if( IsPenVisible() )
+ {
+ eMode = kCGPathStroke;
+ }
+ else if( IsBrushVisible() )
+ {
+ eMode = kCGPathEOFill;
+ }
+ else
+ {
+ SAL_WARN( "vcl.quartz", "Neither pen nor brush visible" );
+ return;
+ }
+
+ // convert to CGPath
+ CGContextBeginPath( maContextHolder.get() );
+ if( IsPenVisible() )
+ {
+ for( sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++ )
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if( nPoints > 1 )
+ {
+ const SalPoint *pPtAry = ppPtAry[nPoly];
+ float fX, fY;
+
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextMoveToPoint( maContextHolder.get(), fX, fY );
+ pPtAry++;
+
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextAddLineToPoint( maContextHolder.get(), fX, fY );
+ }
+ CGContextClosePath(maContextHolder.get());
+ }
+ }
+ }
+ else
+ {
+ for( sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++ )
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if( nPoints > 1 )
+ {
+ const SalPoint *pPtAry = ppPtAry[nPoly];
+ CGContextMoveToPoint( maContextHolder.get(), pPtAry->mnX, pPtAry->mnY );
+ pPtAry++;
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ CGContextAddLineToPoint( maContextHolder.get(), pPtAry->mnX, pPtAry->mnY );
+ }
+ CGContextClosePath(maContextHolder.get());
+ }
+ }
+ }
+
+ CGContextDrawPath( maContextHolder.get(), eMode );
+
+ RefreshRect( leftX, topY, maxWidth, maxHeight );
+}
+
+void AquaSalGraphics::drawPolygon( sal_uInt32 nPoints, const SalPoint *pPtAry )
+{
+ if( nPoints <= 1 )
+ return;
+
+ if( !CheckContext() )
+ return;
+
+ long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
+ getBoundRect( nPoints, pPtAry, nX, nY, nWidth, nHeight );
+
+ CGPathDrawingMode eMode;
+ if( IsBrushVisible() && IsPenVisible() )
+ {
+ eMode = kCGPathEOFillStroke;
+ }
+ else if( IsPenVisible() )
+ {
+ eMode = kCGPathStroke;
+ }
+ else if( IsBrushVisible() )
+ {
+ eMode = kCGPathEOFill;
+ }
+ else
+ {
+ SAL_WARN( "vcl.quartz", "Neither pen nor brush visible" );
+ return;
+ }
+
+ CGContextBeginPath( maContextHolder.get() );
+
+ if( IsPenVisible() )
+ {
+ float fX, fY;
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextMoveToPoint( maContextHolder.get(), fX, fY );
+ pPtAry++;
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextAddLineToPoint( maContextHolder.get(), fX, fY );
+ }
+ }
+ else
+ {
+ CGContextMoveToPoint( maContextHolder.get(), pPtAry->mnX, pPtAry->mnY );
+ pPtAry++;
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ CGContextAddLineToPoint( maContextHolder.get(), pPtAry->mnX, pPtAry->mnY );
+ }
+ }
+
+ CGContextClosePath( maContextHolder.get() );
+ CGContextDrawPath( maContextHolder.get(), eMode );
+ RefreshRect( nX, nY, nWidth, nHeight );
+}
+
+bool AquaSalGraphics::drawPolygonBezier( sal_uInt32, const SalPoint*, const PolyFlags* )
+{
+ return false;
+}
+
+bool AquaSalGraphics::drawPolyPolygonBezier( sal_uInt32, const sal_uInt32*,
+ const SalPoint* const*, const PolyFlags* const* )
+{
+ return false;
+}
+
+void AquaSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight )
+{
+ if( !CheckContext() )
+ return;
+
+ CGRect aRect( CGRectMake(nX, nY, nWidth, nHeight) );
+ if( IsPenVisible() )
+ {
+ aRect.origin.x += 0.5;
+ aRect.origin.y += 0.5;
+ aRect.size.width -= 1;
+ aRect.size.height -= 1;
+ }
+
+ if( IsBrushVisible() )
+ {
+ CGContextFillRect( maContextHolder.get(), aRect );
+ }
+ if( IsPenVisible() )
+ {
+ CGContextStrokeRect( maContextHolder.get(), aRect );
+ }
+ RefreshRect( nX, nY, nWidth, nHeight );
+}
+
+void AquaSalGraphics::drawPolyLine( sal_uInt32 nPoints, const SalPoint *pPtAry )
+{
+ if( nPoints < 1 )
+ return;
+
+ if( !CheckContext() )
+ return;
+
+ long nX = 0, nY = 0, nWidth = 0, nHeight = 0;
+ getBoundRect( nPoints, pPtAry, nX, nY, nWidth, nHeight );
+
+ float fX, fY;
+ CGContextBeginPath( maContextHolder.get() );
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextMoveToPoint( maContextHolder.get(), fX, fY );
+ pPtAry++;
+
+ for( sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ )
+ {
+ alignLinePoint( pPtAry, fX, fY );
+ CGContextAddLineToPoint( maContextHolder.get(), fX, fY );
+ }
+ CGContextStrokePath(maContextHolder.get());
+
+ RefreshRect( nX, nY, nWidth, nHeight );
+}
+
+sal_uInt16 AquaSalGraphics::GetBitCount() const
+{
+ sal_uInt16 nBits = mnBitmapDepth ? mnBitmapDepth : 32;//24;
+ return nBits;
+}
+
+std::shared_ptr<SalBitmap> AquaSalGraphics::getBitmap( long nX, long nY, long nDX, long nDY )
+{
+ SAL_WARN_IF(!maLayer.isSet(), "vcl.quartz", "AquaSalGraphics::getBitmap() with no layer this=" << this);
+
+ ApplyXorContext();
+
+ std::shared_ptr<QuartzSalBitmap> pBitmap = std::make_shared<QuartzSalBitmap>();
+ if (!pBitmap->Create(maLayer, mnBitmapDepth, nX, nY, nDX, nDY, IsFlipped()))
+ {
+ pBitmap = nullptr;
+ }
+ return pBitmap;
+}
+
+SystemGraphicsData AquaSalGraphics::GetGraphicsData() const
+{
+ SystemGraphicsData aRes;
+ aRes.nSize = sizeof(aRes);
+ aRes.rCGContext = maContextHolder.get();
+ return aRes;
+}
+
+long AquaSalGraphics::GetGraphicsWidth() const
+{
+ long w = 0;
+ if( maContextHolder.isSet() && (
+#ifndef IOS
+ mbWindow ||
+#endif
+ mbVirDev) )
+ {
+ w = mnWidth;
+ }
+
+#ifndef IOS
+ if( w == 0 )
+ {
+ if( mbWindow && mpFrame )
+ {
+ w = mpFrame->maGeometry.nWidth;
+ }
+ }
+#endif
+ return w;
+}
+
+Color AquaSalGraphics::getPixel( long nX, long nY )
+{
+ // return default value on printers or when out of bounds
+ if (!maLayer.isSet() || (nX < 0) || (nX >= mnWidth) ||
+ (nY < 0) || (nY >= mnHeight))
+ {
+ return sal_uInt32(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
+ ApplyXorContext();
+
+ // copy the requested pixel into the bitmap context
+ if( IsFlipped() )
+ {
+ nY = mnHeight - nY;
+ }
+ const CGPoint aCGPoint = CGPointMake(-nX, -nY);
+ CGContextDrawLayerAtPoint(xOnePixelContext, aCGPoint, 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( (mbWindow && mpFrame) ? 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 AquaSalGraphics::ImplDrawPixel( long nX, long nY, const RGBAColor& rColor )
+{
+ if( !CheckContext() )
+ {
+ return;
+ }
+ // overwrite the fill color
+ CGContextSetFillColor( maContextHolder.get(), rColor.AsArray() );
+ // draw 1x1 rect, there is no pixel drawing in Quartz
+ const CGRect aDstRect = CGRectMake(nX, nY, 1, 1);
+ CGContextFillRect( maContextHolder.get(), aDstRect );
+ RefreshRect( aDstRect );
+ // reset the fill color
+ CGContextSetFillColor( maContextHolder.get(), maFillColor.AsArray() );
+}
+
+#ifndef IOS
+
+void AquaSalGraphics::initResolution(NSWindow* nsWindow)
+{
+ if (!nsWindow)
+ {
+ if (Application::IsBitmapRendering())
+ mnRealDPIX = mnRealDPIY = 96;
+ return;
+ }
+
+ // #i100617# read DPI only once; there is some kind of weird caching going on
+ // if the main screen changes
+ // FIXME: this is really unfortunate and needs to be investigated
+
+ SalData* pSalData = GetSalData();
+ if( pSalData->mnDPIX == 0 || pSalData->mnDPIY == 0 )
+ {
+ NSScreen* pScreen = nil;
+
+ /* #i91301#
+ many woes went into the try to have different resolutions
+ on different screens. The result of these trials is that OOo is not ready
+ for that yet, vcl and applications would need to be adapted.
+
+ Unfortunately this is not possible in the 3.0 timeframe.
+ So let's stay with one resolution for all Windows and VirtualDevices
+ which is the resolution of the main screen
+
+ This of course also means that measurements are exact only on the main screen.
+ For activating different resolutions again just comment out the two lines below.
+
+ if( pWin )
+ pScreen = [pWin screen];
+ */
+ if( pScreen == nil )
+ {
+ NSArray* pScreens = [NSScreen screens];
+ if( pScreens && [pScreens count] > 0)
+ {
+ pScreen = [pScreens objectAtIndex: 0];
+ }
+ }
+
+ mnRealDPIX = mnRealDPIY = 96;
+ if( pScreen )
+ {
+ NSDictionary* pDev = [pScreen deviceDescription];
+ if( pDev )
+ {
+ NSNumber* pVal = [pDev objectForKey: @"NSScreenNumber"];
+ if( pVal )
+ {
+ // FIXME: casting a long to CGDirectDisplayID is evil, but
+ // Apple suggest to do it this way
+ const CGDirectDisplayID nDisplayID = static_cast<CGDirectDisplayID>([pVal longValue]);
+ const CGSize aSize = CGDisplayScreenSize( nDisplayID ); // => result is in millimeters
+ mnRealDPIX = static_cast<long>((CGDisplayPixelsWide( nDisplayID ) * 25.4) / aSize.width);
+ mnRealDPIY = static_cast<long>((CGDisplayPixelsHigh( nDisplayID ) * 25.4) / aSize.height);
+ }
+ else
+ {
+ OSL_FAIL( "no resolution found in device description" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "no device description" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "no screen found" );
+ }
+
+ // #i107076# maintaining size-WYSIWYG-ness causes many problems for
+ // low-DPI, high-DPI or for mis-reporting devices
+ // => it is better to limit the calculation result then
+ static const int nMinDPI = 72;
+ if( (mnRealDPIX < nMinDPI) || (mnRealDPIY < nMinDPI) )
+ {
+ mnRealDPIX = mnRealDPIY = nMinDPI;
+ }
+ // Note that on a Retina display, the "mnRealDPIX" as
+ // calculated above is not the true resolution of the display,
+ // but the "logical" one, or whatever the correct terminology
+ // is. (For instance on a 5K 27in iMac, it's 108.) So at
+ // least currently, it won't be over 200. I don't know whether
+ // this test is a "sanity check", or whether there is some
+ // real reason to limit this to 200.
+ static const int nMaxDPI = 200;
+ if( (mnRealDPIX > nMaxDPI) || (mnRealDPIY > nMaxDPI) )
+ {
+ mnRealDPIX = mnRealDPIY = nMaxDPI;
+ }
+ // for OSX any anisotropy reported for the display resolution is best ignored (e.g. TripleHead2Go)
+ mnRealDPIX = mnRealDPIY = (mnRealDPIX + mnRealDPIY + 1) / 2;
+
+ pSalData->mnDPIX = mnRealDPIX;
+ pSalData->mnDPIY = mnRealDPIY;
+ }
+ else
+ {
+ mnRealDPIX = pSalData->mnDPIX;
+ mnRealDPIY = pSalData->mnDPIY;
+ }
+}
+
+#endif
+
+void AquaSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags )
+{
+ if ( CheckContext() )
+ {
+ CGRect aCGRect = CGRectMake( nX, nY, nWidth, nHeight);
+ maContextHolder.saveState();
+ if ( nFlags & SalInvert::TrackFrame )
+ {
+ const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ CGContextSetRGBStrokeColor ( maContextHolder.get(), 1.0, 1.0, 1.0, 1.0 );
+ CGContextSetLineDash ( maContextHolder.get(), 0, dashLengths, 2 );
+ CGContextSetLineWidth( maContextHolder.get(), 2.0);
+ CGContextStrokeRect ( maContextHolder.get(), aCGRect );
+ }
+ else if ( nFlags & SalInvert::N50 )
+ {
+ //CGContextSetAllowsAntialiasing( maContextHolder.get(), false );
+ CGContextSetBlendMode(maContextHolder.get(), kCGBlendModeDifference);
+ CGContextAddRect( maContextHolder.get(), aCGRect );
+ Pattern50Fill();
+ }
+ else // just invert
+ {
+ CGContextSetBlendMode(maContextHolder.get(), kCGBlendModeDifference);
+ CGContextSetRGBFillColor ( maContextHolder.get(),1.0, 1.0, 1.0 , 1.0 );
+ CGContextFillRect ( maContextHolder.get(), aCGRect );
+ }
+ maContextHolder.restoreState();
+ RefreshRect( aCGRect );
+ }
+}
+
+namespace {
+
+CGPoint* makeCGptArray(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ CGPoint *CGpoints = new CGPoint[nPoints];
+ for(sal_uLong i=0;i<nPoints;i++)
+ {
+ CGpoints[i].x = pPtAry[i].mnX;
+ CGpoints[i].y = pPtAry[i].mnY;
+ }
+ return CGpoints;
+}
+
+}
+
+void AquaSalGraphics::invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nSalFlags )
+{
+ if ( CheckContext() )
+ {
+ maContextHolder.saveState();
+ CGPoint* CGpoints = makeCGptArray(nPoints,pPtAry);
+ CGContextAddLines ( maContextHolder.get(), CGpoints, nPoints );
+ if ( nSalFlags & SalInvert::TrackFrame )
+ {
+ const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ CGContextSetRGBStrokeColor ( maContextHolder.get(), 1.0, 1.0, 1.0, 1.0 );
+ CGContextSetLineDash ( maContextHolder.get(), 0, dashLengths, 2 );
+ CGContextSetLineWidth( maContextHolder.get(), 2.0);
+ CGContextStrokePath ( maContextHolder.get() );
+ }
+ else if ( nSalFlags & SalInvert::N50 )
+ {
+ CGContextSetBlendMode(maContextHolder.get(), kCGBlendModeDifference);
+ Pattern50Fill();
+ }
+ else // just invert
+ {
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ CGContextSetRGBFillColor( maContextHolder.get(), 1.0, 1.0, 1.0, 1.0 );
+ CGContextFillPath( maContextHolder.get() );
+ }
+ const CGRect aRefreshRect = CGContextGetClipBoundingBox(maContextHolder.get());
+ maContextHolder.restoreState();
+ delete [] CGpoints;
+ RefreshRect( aRefreshRect );
+ }
+}
+
+void AquaSalGraphics::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( !maContextHolder.get(), "vcl.quartz", "maContextHolder.get() is NULL" );
+ CGContextSetFillColorSpace( maContextHolder.get(), mxP50Space );
+ CGContextSetFillPattern( maContextHolder.get(), mxP50Pattern, aFillCol );
+ CGContextFillPath( maContextHolder.get() );
+}
+
+void AquaSalGraphics::ResetClipRegion()
+{
+ // release old path and indicate no clipping
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ mxClipPath = nullptr;
+ }
+ if( CheckContext() )
+ {
+ SetState();
+ }
+}
+
+void AquaSalGraphics::SetState()
+{
+ maContextHolder.restoreState();
+ maContextHolder.saveState();
+
+ // setup clipping
+ if( mxClipPath )
+ {
+ CGContextBeginPath( maContextHolder.get() ); // discard any existing path
+ CGContextAddPath( maContextHolder.get(), mxClipPath ); // set the current path to the clipping path
+ CGContextClip( maContextHolder.get() ); // use it for clipping
+ }
+
+ // set RGB colorspace and line and fill colors
+ CGContextSetFillColor( maContextHolder.get(), maFillColor.AsArray() );
+
+ CGContextSetStrokeColor( maContextHolder.get(), maLineColor.AsArray() );
+ CGContextSetShouldAntialias( maContextHolder.get(), false );
+ if( mnXorMode == 2 )
+ {
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ }
+}
+
+void AquaSalGraphics::SetLineColor()
+{
+ maLineColor.SetAlpha( 0.0 ); // transparent
+ if( CheckContext() )
+ {
+ CGContextSetRGBStrokeColor( maContextHolder.get(), maLineColor.GetRed(), maLineColor.GetGreen(),
+ maLineColor.GetBlue(), maLineColor.GetAlpha() );
+ }
+}
+
+void AquaSalGraphics::SetLineColor( Color nColor )
+{
+ maLineColor = RGBAColor( nColor );
+ if( CheckContext() )
+ {
+ CGContextSetRGBStrokeColor( maContextHolder.get(), maLineColor.GetRed(), maLineColor.GetGreen(),
+ maLineColor.GetBlue(), maLineColor.GetAlpha() );
+ }
+}
+
+void AquaSalGraphics::SetFillColor()
+{
+ maFillColor.SetAlpha( 0.0 ); // transparent
+ if( CheckContext() )
+ {
+ CGContextSetRGBFillColor( maContextHolder.get(), maFillColor.GetRed(), maFillColor.GetGreen(),
+ maFillColor.GetBlue(), maFillColor.GetAlpha() );
+ }
+}
+
+void AquaSalGraphics::SetFillColor( Color nColor )
+{
+ maFillColor = RGBAColor( nColor );
+ if( CheckContext() )
+ {
+ CGContextSetRGBFillColor( maContextHolder.get(), maFillColor.GetRed(), maFillColor.GetGreen(),
+ maFillColor.GetBlue(), maFillColor.GetAlpha() );
+ }
+}
+
+bool AquaSalGraphics::supportsOperation( OutDevSupportType eType ) const
+{
+ bool bRet = false;
+ switch( eType )
+ {
+ case OutDevSupportType::TransparentRect:
+ case OutDevSupportType::B2DDraw:
+ bRet = true;
+ break;
+ default:
+ break;
+ }
+ return bRet;
+}
+
+bool AquaSalGraphics::setClipRegion( const vcl::Region& i_rClip )
+{
+ // release old clip path
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ mxClipPath = nullptr;
+ }
+ mxClipPath = CGPathCreateMutable();
+
+ // set current path, either as polypolgon or sequence of rectangles
+ if(i_rClip.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ const basegfx::B2DPolyPolygon aClip(i_rClip.GetAsB2DPolyPolygon());
+
+ AddPolyPolygonToPath( mxClipPath, aClip, !getAntiAliasB2DDraw(), false );
+ }
+ else
+ {
+ RectangleVector aRectangles;
+ i_rClip.GetRegionRectangles(aRectangles);
+
+ for(const auto& rRect : aRectangles)
+ {
+ const long nW(rRect.Right() - rRect.Left() + 1); // uses +1 logic in original
+
+ if(nW)
+ {
+ const 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( mxClipPath, nullptr, aRect );
+ }
+ }
+ }
+ }
+ // set the current path as clip region
+ if( CheckContext() )
+ {
+ SetState();
+ }
+ return true;
+}
+
+void AquaSalGraphics::SetROPFillColor( SalROPColor nROPColor )
+{
+ if( ! mbPrinter )
+ {
+ SetFillColor( ImplGetROPColor( nROPColor ) );
+ }
+}
+
+void AquaSalGraphics::SetROPLineColor( SalROPColor nROPColor )
+{
+ if( ! mbPrinter )
+ {
+ SetLineColor( ImplGetROPColor( nROPColor ) );
+ }
+}
+
+void AquaSalGraphics::SetXORMode( bool bSet, bool bInvertOnly )
+{
+ // return early if XOR mode remains unchanged
+ if( mbPrinter )
+ {
+ return;
+ }
+ if( ! bSet && mnXorMode == 2 )
+ {
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeNormal );
+ mnXorMode = 0;
+ return;
+ }
+ else if( bSet && bInvertOnly && mnXorMode == 0)
+ {
+ CGContextSetBlendMode( maContextHolder.get(), kCGBlendModeDifference );
+ mnXorMode = 2;
+ return;
+ }
+
+ if( (mpXorEmulation == nullptr) && !bSet )
+ {
+ return;
+ }
+ if( (mpXorEmulation != nullptr) && (bSet == mpXorEmulation->IsEnabled()) )
+ {
+ return;
+ }
+ if( !CheckContext() )
+ {
+ return;
+ }
+ // prepare XOR emulation
+ if( !mpXorEmulation )
+ {
+ mpXorEmulation = new XorEmulation();
+ mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
+ }
+
+ // change the XOR mode
+ if( bSet )
+ {
+ mpXorEmulation->Enable();
+ maContextHolder.set(mpXorEmulation->GetMaskContext());
+ mnXorMode = 1;
+ }
+ else
+ {
+ mpXorEmulation->UpdateTarget();
+ mpXorEmulation->Disable();
+ maContextHolder.set(mpXorEmulation->GetTargetContext());
+ mnXorMode = 0;
+ }
+}
+
+#ifndef IOS
+
+void AquaSalGraphics::updateResolution()
+{
+ SAL_WARN_IF( !mbWindow, "vcl", "updateResolution on inappropriate graphics" );
+
+ initResolution( (mbWindow && mpFrame) ? mpFrame->getNSWindow() : nil );
+}
+
+#endif
+
+XorEmulation::XorEmulation()
+ : m_xTargetLayer( nullptr )
+ , m_xTargetContext( nullptr )
+ , m_xMaskContext( nullptr )
+ , m_xTempContext( nullptr )
+ , m_pMaskBuffer( nullptr )
+ , m_pTempBuffer( nullptr )
+ , m_nBufferLongs( 0 )
+ , m_bIsEnabled( false )
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::XorEmulation() this=" << this );
+}
+
+XorEmulation::~XorEmulation()
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::~XorEmulation() this=" << this );
+ Disable();
+ SetTarget( 0, 0, 0, nullptr, nullptr );
+}
+
+void XorEmulation::SetTarget( int nWidth, int nHeight, int nTargetDepth,
+ CGContextRef xTargetContext, CGLayerRef xTargetLayer )
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
+ " (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
+ " context=" << xTargetContext << " layer=" << xTargetLayer );
+
+ // prepare to replace old mask+temp context
+ if( m_xMaskContext )
+ {
+ // cleanup the mask context
+ CGContextRelease( m_xMaskContext );
+ delete[] m_pMaskBuffer;
+ m_xMaskContext = nullptr;
+ m_pMaskBuffer = nullptr;
+
+ // cleanup the temp context if needed
+ if( m_xTempContext )
+ {
+ CGContextRelease( m_xTempContext );
+ delete[] m_pTempBuffer;
+ m_xTempContext = nullptr;
+ m_pTempBuffer = nullptr;
+ }
+ }
+
+ // return early if there is nothing more to do
+ if( !xTargetContext )
+ {
+ return;
+ }
+ // retarget drawing operations to the XOR mask
+ m_xTargetLayer = xTargetLayer;
+ m_xTargetContext = xTargetContext;
+
+ // prepare creation of matching CGBitmaps
+ CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
+ int nBitDepth = nTargetDepth;
+ if( !nBitDepth )
+ {
+ nBitDepth = 32;
+ }
+ int nBytesPerRow = 4;
+ const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
+ if( nBitDepth <= 8 )
+ {
+ aCGColorSpace = GetSalData()->mxGraySpace;
+ aCGBmpInfo = kCGImageAlphaNone;
+ nBytesPerRow = 1;
+ }
+ nBytesPerRow *= nWidth;
+ m_nBufferLongs = (nHeight * nBytesPerRow + sizeof(sal_uLong)-1) / sizeof(sal_uLong);
+
+ // create a XorMask context
+ m_pMaskBuffer = new sal_uLong[ m_nBufferLongs ];
+ m_xMaskContext = CGBitmapContextCreate( m_pMaskBuffer,
+ nWidth, nHeight,
+ nBitsPerComponent, nBytesPerRow,
+ aCGColorSpace, aCGBmpInfo );
+ SAL_WARN_IF( !m_xMaskContext, "vcl.quartz", "mask context creation failed" );
+
+ // reset the XOR mask to black
+ memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) );
+
+ // a bitmap context will be needed for manual XORing
+ // create one unless the target context is a bitmap context
+ if( nTargetDepth )
+ {
+ m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData( m_xTargetContext ));
+ }
+ if( !m_pTempBuffer )
+ {
+ // create a bitmap context matching to the target context
+ m_pTempBuffer = new sal_uLong[ m_nBufferLongs ];
+ m_xTempContext = CGBitmapContextCreate( m_pTempBuffer,
+ nWidth, nHeight,
+ nBitsPerComponent, nBytesPerRow,
+ aCGColorSpace, aCGBmpInfo );
+ SAL_WARN_IF( !m_xTempContext, "vcl.quartz", "temp context creation failed" );
+ }
+
+ // initialize XOR mask context for drawing
+ CGContextSetFillColorSpace( m_xMaskContext, aCGColorSpace );
+ CGContextSetStrokeColorSpace( m_xMaskContext, aCGColorSpace );
+ CGContextSetShouldAntialias( m_xMaskContext, false );
+
+ // improve the XorMask's XOR emulation a little
+ // NOTE: currently only enabled for monochrome contexts
+ if( aCGColorSpace == GetSalData()->mxGraySpace )
+ {
+ CGContextSetBlendMode( m_xMaskContext, kCGBlendModeDifference );
+ }
+ // initialize the transformation matrix to the drawing target
+ const CGAffineTransform aCTM = CGContextGetCTM( xTargetContext );
+ CGContextConcatCTM( m_xMaskContext, aCTM );
+ if( m_xTempContext )
+ {
+ CGContextConcatCTM( m_xTempContext, aCTM );
+ }
+ // initialize the default XorMask graphics state
+ CGContextSaveGState( m_xMaskContext );
+}
+
+bool XorEmulation::UpdateTarget()
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::UpdateTarget() this=" << this );
+
+ if( !IsEnabled() )
+ {
+ return false;
+ }
+ // update the temp bitmap buffer if needed
+ if( m_xTempContext )
+ {
+ SAL_WARN_IF( m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
+ CGContextDrawLayerAtPoint( m_xTempContext, CGPointZero, m_xTargetLayer );
+ }
+ // do a manual XOR with the XorMask
+ // this approach suffices for simple color manipulations
+ // and also the complex-clipping-XOR-trick used in metafiles
+ const sal_uLong* pSrc = m_pMaskBuffer;
+ sal_uLong* pDst = m_pTempBuffer;
+ for( int i = m_nBufferLongs; --i >= 0;)
+ {
+ *(pDst++) ^= *(pSrc++);
+ }
+ // write back the XOR results to the target context
+ if( m_xTempContext )
+ {
+ CGImageRef xXorImage = CGBitmapContextCreateImage( m_xTempContext );
+ const int nWidth = static_cast<int>(CGImageGetWidth( xXorImage ));
+ const int nHeight = static_cast<int>(CGImageGetHeight( xXorImage ));
+ // TODO: update minimal changerect
+ const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
+ CGContextDrawImage( m_xTargetContext, aFullRect, xXorImage );
+ CGImageRelease( xXorImage );
+ }
+
+ // reset the XorMask to black again
+ // TODO: not needed for last update
+ memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) );
+
+ // TODO: return FALSE if target was not changed
+ return true;
+}
+
+void AquaSalGraphics::SetVirDevGraphics(CGLayerHolder const & rLayer, CGContextRef xContext,
+ int nBitmapDepth)
+{
+ SAL_INFO( "vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext );
+
+#ifndef IOS
+ mbWindow = false;
+#endif
+ mbPrinter = false;
+ mbVirDev = true;
+
+ // set graphics properties
+ maLayer = rLayer;
+ maContextHolder.set(xContext);
+
+ mnBitmapDepth = nBitmapDepth;
+
+#ifdef IOS
+ mbForeignContext = xContext != NULL;
+#endif
+
+ // return early if the virdev is being destroyed
+ if( !xContext )
+ return;
+
+ // get new graphics properties
+ if (!maLayer.isSet())
+ {
+ mnWidth = CGBitmapContextGetWidth( maContextHolder.get() );
+ mnHeight = CGBitmapContextGetHeight( maContextHolder.get() );
+ }
+ else
+ {
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ mnWidth = static_cast<int>(aSize.width);
+ mnHeight = static_cast<int>(aSize.height);
+ }
+
+ // prepare graphics for drawing
+ const CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGContextSetFillColorSpace( maContextHolder.get(), aCGColorSpace );
+ CGContextSetStrokeColorSpace( maContextHolder.get(), aCGColorSpace );
+
+ // re-enable XorEmulation for the new context
+ if( mpXorEmulation )
+ {
+ mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
+ if( mpXorEmulation->IsEnabled() )
+ {
+ maContextHolder.set(mpXorEmulation->GetMaskContext());
+ }
+ }
+
+ // initialize stack of CGContext states
+ maContextHolder.saveState();
+ SetState();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */