summaryrefslogtreecommitdiffstats
path: root/vcl/quartz
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/quartz')
-rw-r--r--vcl/quartz/ctfonts.cxx560
-rw-r--r--vcl/quartz/salbmp.cxx963
-rw-r--r--vcl/quartz/salgdi.cxx888
-rw-r--r--vcl/quartz/salgdicommon.cxx2047
-rw-r--r--vcl/quartz/salgdiutils.cxx265
-rw-r--r--vcl/quartz/salvd.cxx303
-rw-r--r--vcl/quartz/utils.cxx161
7 files changed, 5187 insertions, 0 deletions
diff --git a/vcl/quartz/ctfonts.cxx b/vcl/quartz/ctfonts.cxx
new file mode 100644
index 000000000..eb67d981f
--- /dev/null
+++ b/vcl/quartz/ctfonts.cxx
@@ -0,0 +1,560 @@
+/* -*- 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 <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <vcl/settings.hxx>
+
+
+#include <quartz/ctfonts.hxx>
+#include <impfont.hxx>
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#endif
+#include <fontinstance.hxx>
+#include <fontattributes.hxx>
+#include <impglyphitem.hxx>
+#include <PhysicalFontCollection.hxx>
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+#include <sallayout.hxx>
+#include <hb-coretext.h>
+
+static double toRadian(int nDegree)
+{
+ return nDegree * (M_PI / 1800.0);
+}
+
+CoreTextStyle::CoreTextStyle(const PhysicalFontFace& rPFF, const FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , mfFontStretch( 1.0 )
+ , mfFontRotation( 0.0 )
+ , mbFauxBold(false)
+ , mpStyleDict( nullptr )
+{
+ double fScaledFontHeight = rFSP.mfExactHeight;
+
+ // convert font rotation to radian
+ mfFontRotation = toRadian(rFSP.mnOrientation);
+
+ // dummy matrix so we can use CGAffineTransformConcat() below
+ CGAffineTransform aMatrix = CGAffineTransformMakeTranslation(0, 0);
+
+ // handle font stretching if any
+ if( (rFSP.mnWidth != 0) && (rFSP.mnWidth != rFSP.mnHeight) )
+ {
+ mfFontStretch = float(rFSP.mnWidth) / rFSP.mnHeight;
+ aMatrix = CGAffineTransformConcat(aMatrix, CGAffineTransformMakeScale(mfFontStretch, 1.0F));
+ }
+
+ // create the style object for CoreText font attributes
+ static const CFIndex nMaxDictSize = 16; // TODO: does this really suffice?
+ mpStyleDict = CFDictionaryCreateMutable( nullptr, nMaxDictSize,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks );
+
+ CFBooleanRef pCFVertBool = rFSP.mbVertical ? kCFBooleanTrue : kCFBooleanFalse;
+ CFDictionarySetValue( mpStyleDict, kCTVerticalFormsAttributeName, pCFVertBool );
+
+ // fake bold
+ if ( (rFSP.GetWeight() >= WEIGHT_BOLD) &&
+ ((rPFF.GetWeight() < WEIGHT_SEMIBOLD) &&
+ (rPFF.GetWeight() != WEIGHT_DONTKNOW)) )
+ {
+ mbFauxBold = true;
+ }
+
+ // fake italic
+ if (((rFSP.GetItalic() == ITALIC_NORMAL) ||
+ (rFSP.GetItalic() == ITALIC_OBLIQUE)) &&
+ (rPFF.GetItalic() == ITALIC_NONE))
+ {
+ aMatrix = CGAffineTransformConcat(aMatrix, CGAffineTransformMake(1, 0, toRadian(120), 1, 0, 0));
+ }
+
+ CTFontDescriptorRef pFontDesc = reinterpret_cast<CTFontDescriptorRef>(rPFF.GetFontId());
+ CTFontRef pNewCTFont = CTFontCreateWithFontDescriptor( pFontDesc, fScaledFontHeight, &aMatrix );
+ CFDictionarySetValue( mpStyleDict, kCTFontAttributeName, pNewCTFont );
+ CFRelease( pNewCTFont);
+}
+
+CoreTextStyle::~CoreTextStyle()
+{
+ if( mpStyleDict )
+ CFRelease( mpStyleDict );
+}
+
+void CoreTextStyle::GetFontMetric( ImplFontMetricDataRef const & rxFontMetric )
+{
+ // get the matching CoreText font handle
+ // TODO: is it worth it to cache the CTFontRef in SetFont() and reuse it here?
+ CTFontRef aCTFontRef = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
+
+ rxFontMetric->ImplCalcLineSpacing(this);
+
+ // since ImplFontMetricData::mnWidth is only used for stretching/squeezing fonts
+ // setting this width to the pixel height of the fontsize is good enough
+ // it also makes the calculation of the stretch factor simple
+ rxFontMetric->SetWidth( lrint( CTFontGetSize( aCTFontRef ) * mfFontStretch) );
+
+ rxFontMetric->SetMinKashida(GetKashidaWidth());
+}
+
+bool CoreTextStyle::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool bVertical) const
+{
+ CGGlyph nCGGlyph = nId;
+ CTFontRef aCTFontRef = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.11 kCTFontDefaultOrientation
+ const CTFontOrientation aFontOrientation = kCTFontDefaultOrientation; // TODO: horz/vert
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ CGRect aCGRect = CTFontGetBoundingRectsForGlyphs(aCTFontRef, aFontOrientation, &nCGGlyph, nullptr, 1);
+
+ // Apply font rotation to non-vertical glyphs.
+ if (mfFontRotation && !bVertical)
+ aCGRect = CGRectApplyAffineTransform(aCGRect, CGAffineTransformMakeRotation(mfFontRotation));
+
+ long xMin = floor(aCGRect.origin.x);
+ long yMin = floor(aCGRect.origin.y);
+ long xMax = ceil(aCGRect.origin.x + aCGRect.size.width);
+ long yMax = ceil(aCGRect.origin.y + aCGRect.size.height);
+ rRect = tools::Rectangle(xMin, -yMax, xMax, -yMin);
+ return true;
+}
+
+namespace {
+
+// callbacks from CTFontCreatePathForGlyph+CGPathApply for GetGlyphOutline()
+struct GgoData { basegfx::B2DPolygon maPolygon; basegfx::B2DPolyPolygon* mpPolyPoly; };
+
+}
+
+static void MyCGPathApplierFunc( void* pData, const CGPathElement* pElement )
+{
+ basegfx::B2DPolygon& rPolygon = static_cast<GgoData*>(pData)->maPolygon;
+ const int nPointCount = rPolygon.count();
+
+ switch( pElement->type )
+ {
+ case kCGPathElementCloseSubpath:
+ case kCGPathElementMoveToPoint:
+ if( nPointCount > 0 )
+ {
+ static_cast<GgoData*>(pData)->mpPolyPoly->append( rPolygon );
+ rPolygon.clear();
+ }
+ // fall through for kCGPathElementMoveToPoint:
+ if( pElement->type != kCGPathElementMoveToPoint )
+ {
+ break;
+ }
+ [[fallthrough]];
+ case kCGPathElementAddLineToPoint:
+ rPolygon.append( basegfx::B2DPoint( +pElement->points[0].x, -pElement->points[0].y ) );
+ break;
+
+ case kCGPathElementAddCurveToPoint:
+ rPolygon.append( basegfx::B2DPoint( +pElement->points[2].x, -pElement->points[2].y ) );
+ rPolygon.setNextControlPoint( nPointCount - 1,
+ basegfx::B2DPoint( pElement->points[0].x,
+ -pElement->points[0].y ) );
+ rPolygon.setPrevControlPoint( nPointCount + 0,
+ basegfx::B2DPoint( pElement->points[1].x,
+ -pElement->points[1].y ) );
+ break;
+
+ case kCGPathElementAddQuadCurveToPoint:
+ {
+ const basegfx::B2DPoint aStartPt = rPolygon.getB2DPoint( nPointCount-1 );
+ const basegfx::B2DPoint aCtrPt1( (aStartPt.getX() + 2 * pElement->points[0].x) / 3.0,
+ (aStartPt.getY() - 2 * pElement->points[0].y) / 3.0 );
+ const basegfx::B2DPoint aCtrPt2( (+2 * pElement->points[0].x + pElement->points[1].x) / 3.0,
+ (-2 * pElement->points[0].y - pElement->points[1].y) / 3.0 );
+ rPolygon.append( basegfx::B2DPoint( +pElement->points[1].x, -pElement->points[1].y ) );
+ rPolygon.setNextControlPoint( nPointCount-1, aCtrPt1 );
+ rPolygon.setPrevControlPoint( nPointCount+0, aCtrPt2 );
+ }
+ break;
+ }
+}
+
+bool CoreTextStyle::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rResult, bool) const
+{
+ rResult.clear();
+
+ CGGlyph nCGGlyph = nId;
+ CTFontRef pCTFont = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ const CTFontOrientation aFontOrientation = kCTFontDefaultOrientation;
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ CGRect aCGRect = CTFontGetBoundingRectsForGlyphs(pCTFont, aFontOrientation, &nCGGlyph, nullptr, 1);
+
+ if (!CGRectIsNull(aCGRect) && CGRectIsEmpty(aCGRect))
+ {
+ // CTFontCreatePathForGlyph returns NULL for blank glyphs, but we want
+ // to return true for them.
+ return true;
+ }
+
+ CGPathRef xPath = CTFontCreatePathForGlyph( pCTFont, nCGGlyph, nullptr );
+ if (!xPath)
+ {
+ return false;
+ }
+
+ GgoData aGgoData;
+ aGgoData.mpPolyPoly = &rResult;
+ CGPathApply( xPath, static_cast<void*>(&aGgoData), MyCGPathApplierFunc );
+#if 0 // TODO: does OSX ensure that the last polygon is always closed?
+ const CGPathElement aClosingElement = { kCGPathElementCloseSubpath, NULL };
+ MyCGPathApplierFunc( (void*)&aGgoData, &aClosingElement );
+#endif
+ CFRelease( xPath );
+
+ return true;
+}
+
+static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
+{
+ sal_uLong nLength = 0;
+ unsigned char* pBuffer = nullptr;
+ CoreTextFontFace* pFont = static_cast<CoreTextFontFace*>(pUserData);
+ nLength = pFont->GetFontTable(nTableTag, nullptr);
+ if (nLength > 0)
+ {
+ pBuffer = new unsigned char[nLength];
+ pFont->GetFontTable(nTableTag, pBuffer);
+ }
+
+ hb_blob_t* pBlob = nullptr;
+ if (pBuffer != nullptr)
+ pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
+ pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); });
+ return pBlob;
+}
+
+hb_font_t* CoreTextStyle::ImplInitHbFont()
+{
+ hb_face_t* pHbFace = hb_face_create_for_tables(getFontTable, GetFontFace(), nullptr);
+
+ return InitHbFont(pHbFace);
+}
+
+rtl::Reference<LogicalFontInstance> CoreTextFontFace::CreateFontInstance(const FontSelectPattern& rFSD) const
+{
+ return new CoreTextStyle(*this, rFSD);
+}
+
+int CoreTextFontFace::GetFontTable( const char pTagName[5], unsigned char* pResultBuf ) const
+{
+ SAL_WARN_IF( pTagName[4]!='\0', "vcl", "CoreTextFontFace::GetFontTable with invalid tagname!" );
+
+ const CTFontTableTag nTagCode = (pTagName[0]<<24) + (pTagName[1]<<16) + (pTagName[2]<<8) + (pTagName[3]<<0);
+
+ return GetFontTable(nTagCode, pResultBuf);
+}
+
+int CoreTextFontFace::GetFontTable(uint32_t nTagCode, unsigned char* pResultBuf ) const
+{
+ // get the raw table length
+ CTFontDescriptorRef pFontDesc = reinterpret_cast<CTFontDescriptorRef>( GetFontId());
+ CTFontRef rCTFont = CTFontCreateWithFontDescriptor( pFontDesc, 0.0, nullptr);
+ const uint32_t opts( kCTFontTableOptionNoOptions );
+ CFDataRef pDataRef = CTFontCopyTable( rCTFont, nTagCode, opts);
+ CFRelease( rCTFont);
+ if( !pDataRef)
+ return 0;
+
+ const CFIndex nByteLength = CFDataGetLength( pDataRef);
+
+ // get the raw table data if requested
+ if( pResultBuf && (nByteLength > 0))
+ {
+ const CFRange aFullRange = CFRangeMake( 0, nByteLength);
+ CFDataGetBytes( pDataRef, aFullRange, reinterpret_cast<UInt8*>(pResultBuf));
+ }
+
+ CFRelease( pDataRef);
+
+ return static_cast<int>(nByteLength);
+}
+
+FontAttributes DevFontFromCTFontDescriptor( CTFontDescriptorRef pFD, bool* bFontEnabled )
+{
+ // all CoreText fonts are device fonts that can rotate just fine
+ FontAttributes rDFA;
+ rDFA.SetQuality( 0 );
+
+ // reset the font attributes
+ rDFA.SetFamilyType( FAMILY_DONTKNOW );
+ rDFA.SetPitch( PITCH_VARIABLE );
+ rDFA.SetWidthType( WIDTH_NORMAL );
+ rDFA.SetWeight( WEIGHT_NORMAL );
+ rDFA.SetItalic( ITALIC_NONE );
+ rDFA.SetSymbolFlag( false );
+
+ // get font name
+#ifdef MACOSX
+ CFStringRef pLang = nullptr;
+ CFStringRef pFamilyName = static_cast<CFStringRef>(
+ CTFontDescriptorCopyLocalizedAttribute( pFD, kCTFontFamilyNameAttribute, &pLang ));
+
+ if ( !pLang )
+ {
+ if( pFamilyName )
+ {
+ CFRelease( pFamilyName );
+ }
+ pFamilyName = static_cast<CFStringRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontFamilyNameAttribute ));
+ }
+#else
+ // No "Application" on iOS. And it is unclear whether this code
+ // snippet will actually ever get invoked on iOS anyway. So just
+ // use the old code that uses a non-localized font name.
+ CFStringRef pFamilyName = (CFStringRef)CTFontDescriptorCopyAttribute( pFD, kCTFontFamilyNameAttribute );
+#endif
+
+ rDFA.SetFamilyName( GetOUString( pFamilyName ) );
+
+ // get font style
+ CFStringRef pStyleName = static_cast<CFStringRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontStyleNameAttribute ));
+ rDFA.SetStyleName( GetOUString( pStyleName ) );
+
+ // get font-enabled status
+ if( bFontEnabled )
+ {
+ int bEnabled = TRUE; // by default (and when we're on macOS < 10.6) it's "enabled"
+ CFNumberRef pEnabled = static_cast<CFNumberRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontEnabledAttribute ));
+ CFNumberGetValue( pEnabled, kCFNumberIntType, &bEnabled );
+ *bFontEnabled = bEnabled;
+ }
+
+ // get font attributes
+ CFDictionaryRef pAttrDict = static_cast<CFDictionaryRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontTraitsAttribute ));
+
+ if (bFontEnabled && *bFontEnabled)
+ {
+ // Ignore font formats not supported.
+ int nFormat;
+ CFNumberRef pFormat = static_cast<CFNumberRef>(CTFontDescriptorCopyAttribute(pFD, kCTFontFormatAttribute));
+ CFNumberGetValue(pFormat, kCFNumberIntType, &nFormat);
+ if (nFormat == kCTFontFormatUnrecognized || nFormat == kCTFontFormatPostScript || nFormat == kCTFontFormatBitmap)
+ {
+ SAL_INFO("vcl.fonts", "Ignoring font with unsupported format: " << rDFA.GetFamilyName());
+ *bFontEnabled = false;
+ }
+ CFRelease(pFormat);
+ }
+
+ // get symbolic trait
+ // TODO: use other traits such as MonoSpace/Condensed/Expanded or Vertical too
+ SInt64 nSymbolTrait = 0;
+ CFNumberRef pSymbolNum = nullptr;
+ if( CFDictionaryGetValueIfPresent( pAttrDict, kCTFontSymbolicTrait, reinterpret_cast<const void**>(&pSymbolNum) ) )
+ {
+ CFNumberGetValue( pSymbolNum, kCFNumberSInt64Type, &nSymbolTrait );
+ rDFA.SetSymbolFlag( (nSymbolTrait & kCTFontClassMaskTrait) == kCTFontSymbolicClass );
+ }
+
+ // get the font weight
+ double fWeight = 0;
+ CFNumberRef pWeightNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontWeightTrait ));
+ CFNumberGetValue( pWeightNum, kCFNumberDoubleType, &fWeight );
+ int nInt = WEIGHT_NORMAL;
+
+ // Special case fixes
+
+ // tdf#67744: Courier Std Medium is always bold. We get a kCTFontWeightTrait of 0.23 which
+ // surely must be wrong.
+ if (rDFA.GetFamilyName() == "Courier Std" &&
+ (rDFA.GetStyleName() == "Medium" || rDFA.GetStyleName() == "Medium Oblique") &&
+ fWeight > 0.2)
+ {
+ fWeight = 0;
+ }
+
+ // tdf#68889: Ditto for Gill Sans MT Pro. Here I can kinda understand it, maybe the
+ // kCTFontWeightTrait is intended to give a subjective "optical" impression of how the font
+ // looks, and Gill Sans MT Pro Medium is kinda heavy. But with the way LibreOffice uses fonts,
+ // we still should think of it as being "medium" weight.
+ if (rDFA.GetFamilyName() == "Gill Sans MT Pro" &&
+ (rDFA.GetStyleName() == "Medium" || rDFA.GetStyleName() == "Medium Italic") &&
+ fWeight > 0.2)
+ {
+ fWeight = 0;
+ }
+
+ if( fWeight > 0 )
+ {
+ nInt = rint(int(WEIGHT_NORMAL) + fWeight * ((WEIGHT_BLACK - WEIGHT_NORMAL)/0.68));
+ if( nInt > WEIGHT_BLACK )
+ {
+ nInt = WEIGHT_BLACK;
+ }
+ }
+ else if( fWeight < 0 )
+ {
+ nInt = rint(int(WEIGHT_NORMAL) + fWeight * ((WEIGHT_NORMAL - WEIGHT_THIN)/0.8));
+ if( nInt < WEIGHT_THIN )
+ {
+ nInt = WEIGHT_THIN;
+ }
+ }
+ rDFA.SetWeight( static_cast<FontWeight>(nInt) );
+
+ // get the font slant
+ double fSlant = 0;
+ CFNumberRef pSlantNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontSlantTrait ));
+ CFNumberGetValue( pSlantNum, kCFNumberDoubleType, &fSlant );
+ if( fSlant >= 0.035 )
+ {
+ rDFA.SetItalic( ITALIC_NORMAL );
+ }
+ // get width trait
+ double fWidth = 0;
+ CFNumberRef pWidthNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontWidthTrait ));
+ CFNumberGetValue( pWidthNum, kCFNumberDoubleType, &fWidth );
+ nInt = WIDTH_NORMAL;
+
+ if( fWidth > 0 )
+ {
+ nInt = rint( int(WIDTH_NORMAL) + fWidth * ((WIDTH_ULTRA_EXPANDED - WIDTH_NORMAL)/0.4));
+ if( nInt > WIDTH_ULTRA_EXPANDED )
+ {
+ nInt = WIDTH_ULTRA_EXPANDED;
+ }
+ }
+ else if( fWidth < 0 )
+ {
+ nInt = rint( int(WIDTH_NORMAL) + fWidth * ((WIDTH_NORMAL - WIDTH_ULTRA_CONDENSED)/0.5));
+ if( nInt < WIDTH_ULTRA_CONDENSED )
+ {
+ nInt = WIDTH_ULTRA_CONDENSED;
+ }
+ }
+ rDFA.SetWidthType( static_cast<FontWidth>(nInt) );
+
+ // release the attribute dict that we had copied
+ CFRelease( pAttrDict );
+
+ // TODO? also use the HEAD table if available to get more attributes
+// CFDataRef CTFontCopyTable( CTFontRef, kCTFontTableHead, /*kCTFontTableOptionNoOptions*/kCTFontTableOptionExcludeSynthetic );
+
+ return rDFA;
+}
+
+static void fontEnumCallBack( const void* pValue, void* pContext )
+{
+ CTFontDescriptorRef pFD = static_cast<CTFontDescriptorRef>(pValue);
+
+ bool bFontEnabled;
+ FontAttributes rDFA = DevFontFromCTFontDescriptor( pFD, &bFontEnabled );
+
+ if( bFontEnabled)
+ {
+ const sal_IntPtr nFontId = reinterpret_cast<sal_IntPtr>(pValue);
+ rtl::Reference<CoreTextFontFace> pFontData = new CoreTextFontFace( rDFA, nFontId );
+ SystemFontList* pFontList = static_cast<SystemFontList*>(pContext);
+ pFontList->AddFont( pFontData.get() );
+ }
+}
+
+SystemFontList::SystemFontList()
+ : mpCTFontCollection( nullptr )
+ , mpCTFontArray( nullptr )
+{}
+
+SystemFontList::~SystemFontList()
+{
+ maFontContainer.clear();
+
+ if( mpCTFontArray )
+ {
+ CFRelease( mpCTFontArray );
+ }
+ if( mpCTFontCollection )
+ {
+ CFRelease( mpCTFontCollection );
+ }
+}
+
+void SystemFontList::AddFont( CoreTextFontFace* pFontData )
+{
+ sal_IntPtr nFontId = pFontData->GetFontId();
+ maFontContainer[ nFontId ] = pFontData;
+}
+
+void SystemFontList::AnnounceFonts( PhysicalFontCollection& rFontCollection ) const
+{
+ for(const auto& rEntry : maFontContainer )
+ {
+ rFontCollection.Add( rEntry.second.get() );
+ }
+}
+
+CoreTextFontFace* SystemFontList::GetFontDataFromId( sal_IntPtr nFontId ) const
+{
+ auto it = maFontContainer.find( nFontId );
+ if( it == maFontContainer.end() )
+ {
+ return nullptr;
+ }
+ return (*it).second.get();
+}
+
+bool SystemFontList::Init()
+{
+ // enumerate available system fonts
+ static const int nMaxDictEntries = 8;
+ CFMutableDictionaryRef pCFDict = CFDictionaryCreateMutable( nullptr,
+ nMaxDictEntries,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks );
+
+ CFDictionaryAddValue( pCFDict, kCTFontCollectionRemoveDuplicatesOption, kCFBooleanTrue );
+ mpCTFontCollection = CTFontCollectionCreateFromAvailableFonts( pCFDict );
+ CFRelease( pCFDict );
+ mpCTFontArray = CTFontCollectionCreateMatchingFontDescriptors( mpCTFontCollection );
+
+ const int nFontCount = CFArrayGetCount( mpCTFontArray );
+ const CFRange aFullRange = CFRangeMake( 0, nFontCount );
+ CFArrayApplyFunction( mpCTFontArray, aFullRange, fontEnumCallBack, this );
+
+ return true;
+}
+
+SystemFontList* GetCoretextFontList()
+{
+ SystemFontList* pList = new SystemFontList();
+ if( !pList->Init() )
+ {
+ delete pList;
+ return nullptr;
+ }
+
+ return pList;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salbmp.cxx b/vcl/quartz/salbmp.cxx
new file mode 100644
index 000000000..8dd09d176
--- /dev/null
+++ b/vcl/quartz/salbmp.cxx
@@ -0,0 +1,963 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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 <osl/diagnose.h>
+
+#include <cstddef>
+#include <limits>
+
+#include <o3tl/make_shared.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <tools/color.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/BitmapAccessMode.hxx>
+#include <vcl/BitmapBuffer.hxx>
+#include <vcl/BitmapColor.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/ColorMask.hxx>
+#include <vcl/Scanline.hxx>
+
+#include <bmpfast.hxx>
+#include <quartz/salbmp.h>
+#include <quartz/utils.h>
+
+#ifdef MACOSX
+#include <osx/saldata.hxx>
+#else
+#include "saldatabasic.hxx"
+#endif
+
+static const unsigned long k32BitRedColorMask = 0x00ff0000;
+static const unsigned long k32BitGreenColorMask = 0x0000ff00;
+static const unsigned long k32BitBlueColorMask = 0x000000ff;
+
+static bool isValidBitCount( sal_uInt16 nBitCount )
+{
+ return (nBitCount == 1) || (nBitCount == 4) || (nBitCount == 8) ||
+ (nBitCount == 24) || (nBitCount == 32);
+}
+
+QuartzSalBitmap::QuartzSalBitmap()
+ : mxCachedImage( nullptr )
+ , mnBits(0)
+ , mnWidth(0)
+ , mnHeight(0)
+ , mnBytesPerRow(0)
+{
+}
+
+QuartzSalBitmap::~QuartzSalBitmap()
+{
+ doDestroy();
+}
+
+bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits, int nX, int nY, int nWidth, int nHeight, bool bFlipped)
+{
+ SAL_WARN_IF(!rLayerHolder.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
+
+ // sanitize input parameters
+ if( nX < 0 ) {
+ nWidth += nX;
+ nX = 0;
+ }
+
+ if( nY < 0 ) {
+ nHeight += nY;
+ nY = 0;
+ }
+
+ const CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
+
+ if( nWidth >= static_cast<int>(aLayerSize.width) - nX )
+ nWidth = static_cast<int>(aLayerSize.width) - nX;
+
+ if( nHeight >= static_cast<int>(aLayerSize.height) - nY )
+ nHeight = static_cast<int>(aLayerSize.height) - nY;
+
+ if( (nWidth < 0) || (nHeight < 0) )
+ nWidth = nHeight = 0;
+
+ // initialize properties
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ mnBits = nBitmapBits ? nBitmapBits : 32;
+
+ // initialize drawing context
+ CreateContext();
+
+ // copy layer content into the bitmap buffer
+ const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX), static_cast<CGFloat>(-nY) };
+ if (maGraphicContext.isSet()) // remove warning
+ {
+ if( bFlipped )
+ {
+ CGContextTranslateCTM( maGraphicContext.get(), 0, +mnHeight );
+
+ CGContextScaleCTM( maGraphicContext.get(), +1, -1 );
+ }
+
+ CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
+ }
+ return true;
+}
+
+bool QuartzSalBitmap::Create( const Size& rSize, sal_uInt16 nBits, const BitmapPalette& rBitmapPalette )
+{
+ if( !isValidBitCount( nBits ) )
+ return false;
+
+ maPalette = rBitmapPalette;
+ mnBits = nBits;
+ mnWidth = rSize.Width();
+ mnHeight = rSize.Height();
+ return AllocateUserData();
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp )
+{
+ return Create( rSalBmp, rSalBmp.GetBitCount() );
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics )
+{
+ return Create( rSalBmp, pGraphics ? pGraphics->GetBitCount() : rSalBmp.GetBitCount() );
+}
+
+bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount )
+{
+ const QuartzSalBitmap& rSourceBitmap = static_cast<const QuartzSalBitmap&>(rSalBmp);
+
+ if (isValidBitCount(nNewBitCount) && rSourceBitmap.m_pUserBuffer.get())
+ {
+ mnBits = nNewBitCount;
+ mnWidth = rSourceBitmap.mnWidth;
+ mnHeight = rSourceBitmap.mnHeight;
+ maPalette = rSourceBitmap.maPalette;
+
+ if( AllocateUserData() )
+ {
+ ConvertBitmapData( mnWidth, mnHeight, mnBits, mnBytesPerRow, maPalette,
+ m_pUserBuffer.get(), rSourceBitmap.mnBits,
+ rSourceBitmap.mnBytesPerRow, rSourceBitmap.maPalette,
+ rSourceBitmap.m_pUserBuffer.get() );
+ return true;
+ }
+ }
+ return false;
+}
+
+bool QuartzSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/,
+ Size& /*rSize*/, bool /*bMask*/ )
+{
+ return false;
+}
+
+void QuartzSalBitmap::Destroy()
+{
+ doDestroy();
+}
+
+void QuartzSalBitmap::doDestroy()
+{
+ DestroyContext();
+ m_pUserBuffer.reset();
+}
+
+void QuartzSalBitmap::DestroyContext()
+{
+ if( mxCachedImage )
+ {
+ CGImageRelease( mxCachedImage );
+ mxCachedImage = nullptr;
+ }
+
+ if (maGraphicContext.isSet())
+ {
+ CGContextRelease(maGraphicContext.get());
+ maGraphicContext.set(nullptr);
+ m_pContextBuffer.reset();
+ }
+}
+
+bool QuartzSalBitmap::CreateContext()
+{
+ DestroyContext();
+
+ // prepare graphics context
+ // convert image from user input if available
+ const bool bSkipConversion = !m_pUserBuffer;
+ if( bSkipConversion )
+ AllocateUserData();
+
+ // default to RGBA color space
+ CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
+
+ // convert data into something accepted by CGBitmapContextCreate()
+ size_t bitsPerComponent = 8;
+ sal_uInt32 nContextBytesPerRow = mnBytesPerRow;
+ if( mnBits == 32 )
+ {
+ // no conversion needed for truecolor
+ m_pContextBuffer = m_pUserBuffer;
+ }
+ else if( mnBits == 8 && maPalette.IsGreyPalette8Bit() )
+ {
+ // no conversion needed for grayscale
+ m_pContextBuffer = m_pUserBuffer;
+ aCGColorSpace = GetSalData()->mxGraySpace;
+ aCGBmpInfo = kCGImageAlphaNone;
+ bitsPerComponent = mnBits;
+ }
+ // TODO: is special handling for 1bit input buffers worth it?
+ else
+ {
+ // convert user data to 32 bit
+ nContextBytesPerRow = mnWidth << 2;
+ try
+ {
+ m_pContextBuffer = o3tl::make_shared_array<sal_uInt8>(mnHeight * nContextBytesPerRow);
+
+ if( !bSkipConversion )
+ {
+ ConvertBitmapData( mnWidth, mnHeight,
+ 32, nContextBytesPerRow, maPalette, m_pContextBuffer.get(),
+ mnBits, mnBytesPerRow, maPalette, m_pUserBuffer.get() );
+ }
+ }
+ catch( const std::bad_alloc& )
+ {
+ maGraphicContext.set(nullptr);
+ }
+ }
+
+ if (m_pContextBuffer.get())
+ {
+ maGraphicContext.set(CGBitmapContextCreate(m_pContextBuffer.get(), mnWidth, mnHeight,
+ bitsPerComponent, nContextBytesPerRow,
+ aCGColorSpace, aCGBmpInfo));
+ }
+
+ if (!maGraphicContext.isSet())
+ m_pContextBuffer.reset();
+
+ return maGraphicContext.isSet();
+}
+
+bool QuartzSalBitmap::AllocateUserData()
+{
+ Destroy();
+
+ if( mnWidth && mnHeight )
+ {
+ mnBytesPerRow = 0;
+
+ switch( mnBits )
+ {
+ case 1: mnBytesPerRow = (mnWidth + 7) >> 3; break;
+ case 4: mnBytesPerRow = (mnWidth + 1) >> 1; break;
+ case 8: mnBytesPerRow = mnWidth; break;
+ case 24: mnBytesPerRow = (mnWidth << 1) + mnWidth; break;
+ case 32: mnBytesPerRow = mnWidth << 2; break;
+ default:
+ assert(false && "vcl::QuartzSalBitmap::AllocateUserData(), illegal bitcount!");
+ }
+ }
+
+ bool alloc = false;
+ if (mnBytesPerRow != 0 &&
+ mnBytesPerRow <= std::numeric_limits<sal_uInt32>::max() / mnHeight)
+ {
+ try
+ {
+ m_pUserBuffer = o3tl::make_shared_array<sal_uInt8>(mnBytesPerRow * mnHeight);
+ alloc = true;
+ }
+ catch (std::bad_alloc &) {}
+ }
+ if (!alloc)
+ {
+ SAL_WARN( "vcl.quartz", "bad_alloc: " << mnWidth << "x" << mnHeight << " (" << mnBytesPerRow * mnHeight << " bytes)");
+ m_pUserBuffer.reset();
+ mnBytesPerRow = 0;
+ }
+
+ return m_pUserBuffer.get() != nullptr;
+}
+
+namespace {
+
+class ImplPixelFormat
+{
+public:
+ static std::unique_ptr<ImplPixelFormat> GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette );
+
+ virtual void StartLine( sal_uInt8* pLine ) = 0;
+ virtual void SkipPixel( sal_uInt32 nPixel ) = 0;
+ virtual Color ReadPixel() = 0;
+ virtual void WritePixel( Color nColor ) = 0;
+ virtual ~ImplPixelFormat() { }
+};
+
+class ImplPixelFormat32 : public ImplPixelFormat
+// currently ARGB-format for 32bit depth
+{
+ sal_uInt8* pData;
+public:
+ virtual void StartLine( sal_uInt8* pLine ) override { pData = pLine; }
+ virtual void SkipPixel( sal_uInt32 nPixel ) override
+ {
+ pData += nPixel << 2;
+ }
+ virtual Color ReadPixel() override
+ {
+ const Color c( pData[1], pData[2], pData[3] );
+ pData += 4;
+ return c;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ *pData++ = 0;
+ *pData++ = nColor.GetRed();
+ *pData++ = nColor.GetGreen();
+ *pData++ = nColor.GetBlue();
+ }
+};
+
+class ImplPixelFormat24 : public ImplPixelFormat
+// currently BGR-format for 24bit depth
+{
+ sal_uInt8* pData;
+public:
+ virtual void StartLine( sal_uInt8* pLine ) override { pData = pLine; }
+ virtual void SkipPixel( sal_uInt32 nPixel ) override
+ {
+ pData += (nPixel << 1) + nPixel;
+ }
+ virtual Color ReadPixel() override
+ {
+ const Color c( pData[2], pData[1], pData[0] );
+ pData += 3;
+ return c;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ *pData++ = nColor.GetBlue();
+ *pData++ = nColor.GetGreen();
+ *pData++ = nColor.GetRed();
+ }
+};
+
+class ImplPixelFormat8 : public ImplPixelFormat
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+ const sal_uInt16 mnPaletteCount;
+
+public:
+ explicit ImplPixelFormat8( const BitmapPalette& rPalette )
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ , mnPaletteCount(rPalette.GetEntryCount())
+ {
+ }
+ virtual void StartLine( sal_uInt8* pLine ) override { pData = pLine; }
+ virtual void SkipPixel( sal_uInt32 nPixel ) override
+ {
+ pData += nPixel;
+ }
+ virtual Color ReadPixel() override
+ {
+ const sal_uInt8 nIndex(*pData++);
+
+ // Caution(!) rPalette.GetEntryCount() may be != (depth^^2)-1 (!)
+ if(nIndex < mnPaletteCount)
+ return mrPalette[nIndex];
+ else
+ return COL_BLACK;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ *pData++ = static_cast< sal_uInt8 >( mrPalette.GetBestIndex( nColor ) );
+ }
+};
+
+class ImplPixelFormat4 : public ImplPixelFormat
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+ const sal_uInt16 mnPaletteCount;
+ sal_uInt32 mnX;
+ sal_uInt32 mnShift;
+
+public:
+ explicit ImplPixelFormat4( const BitmapPalette& rPalette )
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ , mnPaletteCount(rPalette.GetEntryCount())
+ , mnX(0)
+ , mnShift(0)
+ {
+ }
+ virtual void SkipPixel( sal_uInt32 nPixel ) override
+ {
+ mnX += nPixel;
+ if( nPixel & 1 )
+ {
+ mnShift ^= 4;
+ }
+ }
+ virtual void StartLine( sal_uInt8* pLine ) override
+ {
+ pData = pLine;
+ mnX = 0;
+ mnShift = 4;
+ }
+ virtual Color ReadPixel() override
+ {
+ // Caution(!) rPalette.GetEntryCount() may be != (depth^^2)-1 (!)
+ const sal_uInt8 nIndex(( pData[mnX >> 1] >> mnShift) & 0x0f);
+ mnX++;
+ mnShift ^= 4;
+
+ if(nIndex < mnPaletteCount)
+ return mrPalette[nIndex];
+ else
+ return COL_BLACK;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ pData[mnX>>1] &= (0xf0 >> mnShift);
+ pData[mnX>>1] |= (static_cast< sal_uInt8 >( mrPalette.GetBestIndex( nColor ) ) & 0x0f);
+ mnX++;
+ mnShift ^= 4;
+ }
+};
+
+class ImplPixelFormat1 : public ImplPixelFormat
+{
+private:
+ sal_uInt8* pData;
+ const BitmapPalette& mrPalette;
+ const sal_uInt16 mnPaletteCount;
+ sal_uInt32 mnX;
+
+public:
+ explicit ImplPixelFormat1( const BitmapPalette& rPalette )
+ : pData(nullptr)
+ , mrPalette(rPalette)
+ , mnPaletteCount(rPalette.GetEntryCount())
+ , mnX(0)
+ {
+ }
+ virtual void SkipPixel( sal_uInt32 nPixel ) override
+ {
+ mnX += nPixel;
+ }
+ virtual void StartLine( sal_uInt8* pLine ) override
+ {
+ pData = pLine;
+ mnX = 0;
+ }
+ virtual Color ReadPixel() override
+ {
+ // Caution(!) rPalette.GetEntryCount() may be != (depth^^2)-1 (!)
+ const sal_uInt8 nIndex( (pData[mnX >> 3 ] >> ( 7 - ( mnX & 7 ) )) & 1);
+ mnX++;
+
+ if(nIndex < mnPaletteCount)
+ return mrPalette[nIndex];
+ else
+ return COL_BLACK;
+ }
+ virtual void WritePixel( Color nColor ) override
+ {
+ if( mrPalette.GetBestIndex( nColor ) & 1 )
+ {
+ pData[ mnX >> 3 ] |= 1 << ( 7 - ( mnX & 7 ) );
+ }
+ else
+ {
+ pData[ mnX >> 3 ] &= ~( 1 << ( 7 - ( mnX & 7 ) ) );
+ }
+ mnX++;
+ }
+};
+
+std::unique_ptr<ImplPixelFormat> ImplPixelFormat::GetFormat( sal_uInt16 nBits, const BitmapPalette& rPalette )
+{
+ switch( nBits )
+ {
+ case 1: return std::make_unique<ImplPixelFormat1>( rPalette );
+ case 4: return std::make_unique<ImplPixelFormat4>( rPalette );
+ case 8: return std::make_unique<ImplPixelFormat8>( rPalette );
+ case 24: return std::make_unique<ImplPixelFormat24>();
+ case 32: return std::make_unique<ImplPixelFormat32>();
+ default:
+ assert(false);
+ return nullptr;
+ }
+
+ return nullptr;
+}
+
+} // namespace
+
+void QuartzSalBitmap::ConvertBitmapData( sal_uInt32 nWidth, sal_uInt32 nHeight,
+ sal_uInt16 nDestBits, sal_uInt32 nDestBytesPerRow,
+ const BitmapPalette& rDestPalette, sal_uInt8* pDestData,
+ sal_uInt16 nSrcBits, sal_uInt32 nSrcBytesPerRow,
+ const BitmapPalette& rSrcPalette, sal_uInt8* pSrcData )
+
+{
+ if( (nDestBytesPerRow == nSrcBytesPerRow) &&
+ (nDestBits == nSrcBits) && ((nSrcBits != 8) || (rDestPalette.operator==( rSrcPalette ))) )
+ {
+ // simple case, same format, so just copy
+ memcpy( pDestData, pSrcData, nHeight * nDestBytesPerRow );
+ return;
+ }
+
+ // try accelerated conversion if possible
+ // TODO: are other truecolor conversions except BGR->ARGB worth it?
+ bool bConverted = false;
+ if( (nSrcBits == 24) && (nDestBits == 32) )
+ {
+ // TODO: extend bmpfast.cxx with a method that can be directly used here
+ BitmapBuffer aSrcBuf;
+ aSrcBuf.mnFormat = ScanlineFormat::N24BitTcBgr;
+ aSrcBuf.mpBits = pSrcData;
+ aSrcBuf.mnBitCount = nSrcBits;
+ aSrcBuf.mnScanlineSize = nSrcBytesPerRow;
+ BitmapBuffer aDstBuf;
+ aDstBuf.mnFormat = ScanlineFormat::N32BitTcArgb;
+ aDstBuf.mpBits = pDestData;
+ aDstBuf.mnBitCount = nDestBits;
+ aDstBuf.mnScanlineSize = nDestBytesPerRow;
+
+ aSrcBuf.mnWidth = aDstBuf.mnWidth = nWidth;
+ aSrcBuf.mnHeight = aDstBuf.mnHeight = nHeight;
+
+ SalTwoRect aTwoRects(0, 0, mnWidth, mnHeight, 0, 0, mnWidth, mnHeight);
+ bConverted = ::ImplFastBitmapConversion( aDstBuf, aSrcBuf, aTwoRects );
+ }
+
+ if( !bConverted )
+ {
+ // TODO: this implementation is for clarity, not for speed
+
+ std::unique_ptr<ImplPixelFormat> pD = ImplPixelFormat::GetFormat( nDestBits, rDestPalette );
+ std::unique_ptr<ImplPixelFormat> pS = ImplPixelFormat::GetFormat( nSrcBits, rSrcPalette );
+
+ if( pD && pS )
+ {
+ sal_uInt32 nY = nHeight;
+ while( nY-- )
+ {
+ pD->StartLine( pDestData );
+ pS->StartLine( pSrcData );
+
+ sal_uInt32 nX = nWidth;
+ while( nX-- )
+ {
+ pD->WritePixel( pS->ReadPixel() );
+ }
+ pSrcData += nSrcBytesPerRow;
+ pDestData += nDestBytesPerRow;
+ }
+ }
+ }
+}
+
+Size QuartzSalBitmap::GetSize() const
+{
+ return Size( mnWidth, mnHeight );
+}
+
+sal_uInt16 QuartzSalBitmap::GetBitCount() const
+{
+ return mnBits;
+}
+
+namespace {
+
+struct pal_entry
+{
+ sal_uInt8 mnRed;
+ sal_uInt8 mnGreen;
+ sal_uInt8 mnBlue;
+};
+
+}
+
+static pal_entry const aImplSalSysPalEntryAry[ 16 ] =
+{
+{ 0, 0, 0 },
+{ 0, 0, 0x80 },
+{ 0, 0x80, 0 },
+{ 0, 0x80, 0x80 },
+{ 0x80, 0, 0 },
+{ 0x80, 0, 0x80 },
+{ 0x80, 0x80, 0 },
+{ 0x80, 0x80, 0x80 },
+{ 0xC0, 0xC0, 0xC0 },
+{ 0, 0, 0xFF },
+{ 0, 0xFF, 0 },
+{ 0, 0xFF, 0xFF },
+{ 0xFF, 0, 0 },
+{ 0xFF, 0, 0xFF },
+{ 0xFF, 0xFF, 0 },
+{ 0xFF, 0xFF, 0xFF }
+};
+
+static const BitmapPalette& GetDefaultPalette( int mnBits, bool bMonochrome )
+{
+ if( bMonochrome )
+ return Bitmap::GetGreyPalette( 1U << mnBits );
+
+ // at this point we should provide some kind of default palette
+ // since all other platforms do so, too.
+ static bool bDefPalInit = false;
+ static BitmapPalette aDefPalette256;
+ static BitmapPalette aDefPalette16;
+ static BitmapPalette aDefPalette2;
+ if( ! bDefPalInit )
+ {
+ bDefPalInit = true;
+ aDefPalette256.SetEntryCount( 256 );
+ aDefPalette16.SetEntryCount( 16 );
+ aDefPalette2.SetEntryCount( 2 );
+
+ // Standard colors
+ unsigned int i;
+ for( i = 0; i < 16; i++ )
+ {
+ aDefPalette16[i] =
+ aDefPalette256[i] = BitmapColor( aImplSalSysPalEntryAry[i].mnRed,
+ aImplSalSysPalEntryAry[i].mnGreen,
+ aImplSalSysPalEntryAry[i].mnBlue );
+ }
+
+ aDefPalette2[0] = BitmapColor( 0, 0, 0 );
+ aDefPalette2[1] = BitmapColor( 0xff, 0xff, 0xff );
+
+ // own palette (6/6/6)
+ const int DITHER_PAL_STEPS = 6;
+ const sal_uInt8 DITHER_PAL_DELTA = 51;
+ int nB, nG, nR;
+ sal_uInt8 nRed, nGreen, nBlue;
+ for( nB=0, nBlue=0; nB < DITHER_PAL_STEPS; nB++, nBlue += DITHER_PAL_DELTA )
+ {
+ for( nG=0, nGreen=0; nG < DITHER_PAL_STEPS; nG++, nGreen += DITHER_PAL_DELTA )
+ {
+ for( nR=0, nRed=0; nR < DITHER_PAL_STEPS; nR++, nRed += DITHER_PAL_DELTA )
+ {
+ aDefPalette256[ i ] = BitmapColor( nRed, nGreen, nBlue );
+ i++;
+ }
+ }
+ }
+ }
+
+ // now fill in appropriate palette
+ switch( mnBits )
+ {
+ case 1: return aDefPalette2;
+ case 4: return aDefPalette16;
+ case 8: return aDefPalette256;
+ default: break;
+ }
+
+ const static BitmapPalette aEmptyPalette;
+ return aEmptyPalette;
+}
+
+BitmapBuffer* QuartzSalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ )
+{
+ // TODO: AllocateUserData();
+ if (!m_pUserBuffer.get())
+ return nullptr;
+
+ BitmapBuffer* pBuffer = new BitmapBuffer;
+ pBuffer->mnWidth = mnWidth;
+ pBuffer->mnHeight = mnHeight;
+ pBuffer->maPalette = maPalette;
+ pBuffer->mnScanlineSize = mnBytesPerRow;
+ pBuffer->mpBits = m_pUserBuffer.get();
+ pBuffer->mnBitCount = mnBits;
+ switch( mnBits )
+ {
+ case 1:
+ pBuffer->mnFormat = ScanlineFormat::N1BitMsbPal;
+ break;
+ case 4:
+ pBuffer->mnFormat = ScanlineFormat::N4BitMsnPal;
+ break;
+ case 8:
+ pBuffer->mnFormat = ScanlineFormat::N8BitPal;
+ break;
+ case 24:
+ pBuffer->mnFormat = ScanlineFormat::N24BitTcBgr;
+ break;
+ case 32:
+ {
+ pBuffer->mnFormat = ScanlineFormat::N32BitTcArgb;
+ ColorMaskElement aRedMask(k32BitRedColorMask);
+ aRedMask.CalcMaskShift();
+ ColorMaskElement aGreenMask(k32BitGreenColorMask);
+ aGreenMask.CalcMaskShift();
+ ColorMaskElement aBlueMask(k32BitBlueColorMask);
+ aBlueMask.CalcMaskShift();
+ pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask);
+ break;
+ }
+ default: assert(false);
+ }
+
+ // some BitmapBuffer users depend on a complete palette
+ if( (mnBits <= 8) && !maPalette )
+ pBuffer->maPalette = GetDefaultPalette( mnBits, true );
+
+ return pBuffer;
+}
+
+void QuartzSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode )
+{
+ // invalidate graphic context if we have different data
+ if( nMode == BitmapAccessMode::Write )
+ {
+ maPalette = pBuffer->maPalette;
+ if (maGraphicContext.isSet())
+ {
+ DestroyContext();
+ }
+ InvalidateChecksum();
+ }
+
+ delete pBuffer;
+}
+
+CGImageRef QuartzSalBitmap::CreateCroppedImage( int nX, int nY, int nNewWidth, int nNewHeight ) const
+{
+ if( !mxCachedImage )
+ {
+ if (!maGraphicContext.isSet())
+ {
+ if( !const_cast<QuartzSalBitmap*>(this)->CreateContext() )
+ {
+ return nullptr;
+ }
+ }
+ mxCachedImage = CGBitmapContextCreateImage(maGraphicContext.get());
+ }
+
+ CGImageRef xCroppedImage = nullptr;
+ // short circuit if there is nothing to crop
+ if( !nX && !nY && (mnWidth == nNewWidth) && (mnHeight == nNewHeight) )
+ {
+ xCroppedImage = mxCachedImage;
+ CFRetain( xCroppedImage );
+ }
+ else
+ {
+ nY = mnHeight - (nY + nNewHeight); // adjust for y-mirrored context
+ const CGRect aCropRect = { { static_cast<CGFloat>(nX), static_cast<CGFloat>(nY) }, { static_cast<CGFloat>(nNewWidth), static_cast<CGFloat>(nNewHeight) } };
+ xCroppedImage = CGImageCreateWithImageInRect( mxCachedImage, aCropRect );
+ }
+
+ return xCroppedImage;
+}
+
+static void CFRTLFree(void* /*info*/, const void* data, size_t /*size*/)
+{
+ std::free( const_cast<void*>(data) );
+}
+
+CGImageRef QuartzSalBitmap::CreateWithMask( const QuartzSalBitmap& rMask,
+ int nX, int nY, int nWidth, int nHeight ) const
+{
+ CGImageRef xImage( CreateCroppedImage( nX, nY, nWidth, nHeight ) );
+ if( !xImage )
+ return nullptr;
+
+ CGImageRef xMask = rMask.CreateCroppedImage( nX, nY, nWidth, nHeight );
+ if( !xMask )
+ return xImage;
+
+ // CGImageCreateWithMask() only likes masks or greyscale images => convert if needed
+ // TODO: isolate in an extra method?
+ if( !CGImageIsMask(xMask) || rMask.GetBitCount() != 8)//(CGImageGetColorSpace(xMask) != GetSalData()->mxGraySpace) )
+ {
+ const CGRect xImageRect=CGRectMake( 0, 0, nWidth, nHeight );//the rect has no offset
+
+ // create the alpha mask image fitting our image
+ // TODO: is caching the full mask or the subimage mask worth it?
+ int nMaskBytesPerRow = ((nWidth + 3) & ~3);
+ void* pMaskMem = std::malloc( nMaskBytesPerRow * nHeight );
+ CGContextRef xMaskContext = CGBitmapContextCreate( pMaskMem,
+ nWidth, nHeight, 8, nMaskBytesPerRow, GetSalData()->mxGraySpace, kCGImageAlphaNone );
+ CGContextDrawImage( xMaskContext, xImageRect, xMask );
+ CFRelease( xMask );
+ CGDataProviderRef xDataProvider( CGDataProviderCreateWithData( nullptr,
+ pMaskMem, nHeight * nMaskBytesPerRow, &CFRTLFree ) );
+
+ static const CGFloat* pDecode = nullptr;
+ xMask = CGImageMaskCreate( nWidth, nHeight, 8, 8, nMaskBytesPerRow, xDataProvider, pDecode, false );
+ CFRelease( xDataProvider );
+ CFRelease( xMaskContext );
+ }
+
+ if( !xMask )
+ return xImage;
+
+ // combine image and alpha mask
+ CGImageRef xMaskedImage = CGImageCreateWithMask( xImage, xMask );
+ CFRelease( xMask );
+ CFRelease( xImage );
+ return xMaskedImage;
+}
+
+/** creates an image from the given rectangle, replacing all black pixels
+ with nMaskColor and make all other full transparent */
+CGImageRef QuartzSalBitmap::CreateColorMask( int nX, int nY, int nWidth,
+ int nHeight, Color nMaskColor ) const
+{
+ CGImageRef xMask = nullptr;
+ if (m_pUserBuffer.get() && (nX + nWidth <= mnWidth) && (nY + nHeight <= mnHeight))
+ {
+ const sal_uInt32 nDestBytesPerRow = nWidth << 2;
+ std::unique_ptr<sal_uInt32[]> pMaskBuffer(new (std::nothrow) sal_uInt32[ nHeight * nDestBytesPerRow / 4] );
+ sal_uInt32* pDest = pMaskBuffer.get();
+
+ std::unique_ptr<ImplPixelFormat> pSourcePixels = ImplPixelFormat::GetFormat( mnBits, maPalette );
+
+ if( pMaskBuffer && pSourcePixels )
+ {
+ sal_uInt32 nColor;
+ reinterpret_cast<sal_uInt8*>(&nColor)[0] = 0xff;
+ reinterpret_cast<sal_uInt8*>(&nColor)[1] = nMaskColor.GetRed();
+ reinterpret_cast<sal_uInt8*>(&nColor)[2] = nMaskColor.GetGreen();
+ reinterpret_cast<sal_uInt8*>(&nColor)[3] = nMaskColor.GetBlue();
+
+ sal_uInt8* pSource = m_pUserBuffer.get();
+ // First to nY on y-axis, as that is our starting point (sub-image)
+ if( nY )
+ pSource += nY * mnBytesPerRow;
+
+ int y = nHeight;
+ while( y-- )
+ {
+ pSourcePixels->StartLine( pSource );
+ pSourcePixels->SkipPixel(nX); // Skip on x axis to nX
+ sal_uInt32 x = nWidth;
+ while( x-- )
+ {
+ *pDest++ = ( pSourcePixels->ReadPixel() == 0 ) ? nColor : 0;
+ }
+ pSource += mnBytesPerRow;
+ }
+
+ CGDataProviderRef xDataProvider( CGDataProviderCreateWithData(nullptr, pMaskBuffer.release(), nHeight * nDestBytesPerRow, &CFRTLFree) );
+ xMask = CGImageCreate(nWidth, nHeight, 8, 32, nDestBytesPerRow, GetSalData()->mxRGBSpace, kCGImageAlphaPremultipliedFirst, xDataProvider, nullptr, true, kCGRenderingIntentDefault);
+ CFRelease(xDataProvider);
+ }
+ }
+ return xMask;
+}
+
+/** QuartzSalBitmap::GetSystemData Get platform native image data from existing image
+ *
+ * @param rData struct BitmapSystemData, defined in vcl/inc/bitmap.hxx
+ * @return true if successful
+**/
+bool QuartzSalBitmap::GetSystemData( BitmapSystemData& rData )
+{
+ bool bRet = false;
+
+ if (!maGraphicContext.isSet())
+ CreateContext();
+
+ if (maGraphicContext.isSet())
+ {
+ bRet = true;
+
+ if ((CGBitmapContextGetBitsPerPixel(maGraphicContext.get()) == 32) &&
+ (CGBitmapContextGetBitmapInfo(maGraphicContext.get()) & kCGBitmapByteOrderMask) != kCGBitmapByteOrder32Host)
+ {
+ /**
+ * We need to hack things because VCL does not use kCGBitmapByteOrder32Host, while Cairo requires it.
+ *
+ * Not sure what the above comment means. We don't use Cairo on macOS or iOS.
+ *
+ * This whole if statement was originally (before 2011) inside #ifdef CAIRO. Did we use Cairo on Mac back then?
+ * Anyway, nowadays (since many years, I think) we don't, so should this if statement be dropped? Fun.
+ */
+
+ CGImageRef xImage = CGBitmapContextCreateImage(maGraphicContext.get());
+
+ // re-create the context with single change: include kCGBitmapByteOrder32Host flag.
+ CGContextHolder maGraphicContextNew(CGBitmapContextCreate(CGBitmapContextGetData(maGraphicContext.get()),
+ CGBitmapContextGetWidth(maGraphicContext.get()),
+ CGBitmapContextGetHeight(maGraphicContext.get()),
+ CGBitmapContextGetBitsPerComponent(maGraphicContext.get()),
+ CGBitmapContextGetBytesPerRow(maGraphicContext.get()),
+ CGBitmapContextGetColorSpace(maGraphicContext.get()),
+ CGBitmapContextGetBitmapInfo(maGraphicContext.get()) | kCGBitmapByteOrder32Host));
+ CFRelease(maGraphicContext.get());
+
+ // Needs to be flipped
+ maGraphicContextNew.saveState();
+ CGContextTranslateCTM (maGraphicContextNew.get(), 0, CGBitmapContextGetHeight(maGraphicContextNew.get()));
+ CGContextScaleCTM (maGraphicContextNew.get(), 1.0, -1.0);
+
+ CGContextDrawImage(maGraphicContextNew.get(), CGRectMake( 0, 0, CGImageGetWidth(xImage), CGImageGetHeight(xImage)), xImage);
+
+ // Flip back
+ CGContextRestoreGState( maGraphicContextNew.get() );
+ CGImageRelease( xImage );
+ maGraphicContext = maGraphicContextNew;
+ }
+
+ rData.mnWidth = mnWidth;
+ rData.mnHeight = mnHeight;
+ }
+
+ return bRet;
+}
+
+bool QuartzSalBitmap::ScalingSupported() const
+{
+ return false;
+}
+
+bool QuartzSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ )
+{
+ return false;
+}
+
+bool QuartzSalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ )
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salgdi.cxx b/vcl/quartz/salgdi.cxx
new file mode 100644
index 000000000..83aebe2ab
--- /dev/null
+++ b/vcl/quartz/salgdi.cxx
@@ -0,0 +1,888 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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 <config_folders.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <rtl/bootstrap.h>
+#include <rtl/strbuf.hxx>
+#include <comphelper/lok.hxx>
+
+#include <vcl/metric.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <quartz/ctfonts.hxx>
+#include <fontsubset.hxx>
+#include <impfont.hxx>
+#include <impfontcharmap.hxx>
+#include <impfontmetricdata.hxx>
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+
+#ifdef MACOSX
+#include <osx/salframe.h>
+#endif
+#include <quartz/utils.h>
+#ifdef IOS
+#include "saldatabasic.hxx"
+#endif
+#include <sallayout.hxx>
+#include <sft.hxx>
+
+using namespace vcl;
+
+namespace {
+
+class CoreTextGlyphFallbackSubstititution
+: public ImplGlyphFallbackFontSubstitution
+{
+public:
+ bool FindFontSubstitute(FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString&) const override;
+};
+
+}
+
+bool CoreTextGlyphFallbackSubstititution::FindFontSubstitute(FontSelectPattern& rPattern, LogicalFontInstance* pLogicalFont,
+ OUString& rMissingChars) const
+{
+ bool bFound = false;
+ CoreTextStyle* pStyle = static_cast<CoreTextStyle*>(pLogicalFont);
+ CTFontRef pFont = static_cast<CTFontRef>(CFDictionaryGetValue(pStyle->GetStyleDict(), kCTFontAttributeName));
+ CFStringRef pStr = CreateCFString(rMissingChars);
+ if (pStr)
+ {
+ CTFontRef pFallback = CTFontCreateForString(pFont, pStr, CFRangeMake(0, CFStringGetLength(pStr)));
+ if (pFallback)
+ {
+ bFound = true;
+
+ CTFontDescriptorRef pDesc = CTFontCopyFontDescriptor(pFallback);
+ FontAttributes rAttr = DevFontFromCTFontDescriptor(pDesc, nullptr);
+
+ rPattern.maSearchName = rAttr.GetFamilyName();
+
+ rPattern.SetWeight(rAttr.GetWeight());
+ rPattern.SetItalic(rAttr.GetItalic());
+ rPattern.SetPitch(rAttr.GetPitch());
+ rPattern.SetWidthType(rAttr.GetWidthType());
+
+ CFRelease(pFallback);
+ CFRelease(pDesc);
+ }
+ CFRelease(pStr);
+ }
+
+ return bFound;
+}
+
+CoreTextFontFace::CoreTextFontFace( const FontAttributes& rDFA, sal_IntPtr nFontId )
+ : PhysicalFontFace( rDFA )
+ , mnFontId( nFontId )
+ , mbFontCapabilitiesRead( false )
+{
+}
+
+CoreTextFontFace::~CoreTextFontFace()
+{
+}
+
+sal_IntPtr CoreTextFontFace::GetFontId() const
+{
+ return mnFontId;
+}
+
+FontCharMapRef CoreTextFontFace::GetFontCharMap() const
+{
+ // return the cached charmap
+ if( mxCharMap.is() )
+ return mxCharMap;
+
+ // set the default charmap
+ FontCharMapRef pCharMap( new FontCharMap() );
+ mxCharMap = pCharMap;
+
+ // get the CMAP byte size
+ // allocate a buffer for the CMAP raw data
+ const int nBufSize = GetFontTable( "cmap", nullptr );
+ SAL_WARN_IF( (nBufSize <= 0), "vcl", "CoreTextFontFace::GetFontCharMap : GetFontTable1 failed!");
+ if( nBufSize <= 0 )
+ return mxCharMap;
+
+ // get the CMAP raw data
+ std::vector<unsigned char> aBuffer( nBufSize );
+ const int nRawLength = GetFontTable( "cmap", aBuffer.data() );
+ SAL_WARN_IF( (nRawLength <= 0), "vcl", "CoreTextFontFace::GetFontCharMap : GetFontTable2 failed!");
+ if( nRawLength <= 0 )
+ return mxCharMap;
+
+ SAL_WARN_IF( (nBufSize!=nRawLength), "vcl", "CoreTextFontFace::GetFontCharMap : ByteCount mismatch!");
+
+ // parse the CMAP
+ CmapResult aCmapResult;
+ if( ParseCMAP( aBuffer.data(), nRawLength, aCmapResult ) )
+ {
+ FontCharMapRef xDefFontCharMap( new FontCharMap(aCmapResult) );
+ // create the matching charmap
+ mxCharMap = xDefFontCharMap;
+ }
+
+ return mxCharMap;
+}
+
+bool CoreTextFontFace::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ // read this only once per font
+ if( mbFontCapabilitiesRead )
+ {
+ rFontCapabilities = maFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+ }
+ mbFontCapabilitiesRead = true;
+
+ int nBufSize = GetFontTable( "OS/2", nullptr );
+ if( nBufSize > 0 )
+ {
+ // allocate a buffer for the OS/2 raw data
+ std::vector<unsigned char> aBuffer( nBufSize );
+ // get the OS/2 raw data
+ const int nRawLength = GetFontTable( "OS/2", aBuffer.data() );
+ if( nRawLength > 0 )
+ {
+ const unsigned char* pOS2Table = aBuffer.data();
+ vcl::getTTCoverage( maFontCapabilities.oUnicodeRange,
+ maFontCapabilities.oCodePageRange,
+ pOS2Table, nRawLength);
+ }
+ }
+ rFontCapabilities = maFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+}
+
+AquaSalGraphics::AquaSalGraphics()
+ : mpXorEmulation( nullptr )
+ , mnXorMode( 0 )
+ , mnWidth( 0 )
+ , mnHeight( 0 )
+ , mnBitmapDepth( 0 )
+ , mnRealDPIX( 0 )
+ , mnRealDPIY( 0 )
+ , mxClipPath( nullptr )
+ , maLineColor( COL_WHITE )
+ , maFillColor( COL_BLACK )
+ , maTextColor( COL_BLACK )
+ , mbNonAntialiasedText( false )
+#ifdef MACOSX
+ , mpFrame( nullptr )
+#endif
+ , mbPrinter( false )
+ , mbVirDev( false )
+#ifdef MACOSX
+ , mbWindow( false )
+#else
+ , mbForeignContext( false )
+#endif
+{
+ SAL_INFO( "vcl.quartz", "AquaSalGraphics::AquaSalGraphics() this=" << this );
+
+ for (int i = 0; i < MAX_FALLBACK; ++i)
+ mpTextStyle[i] = nullptr;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ initWidgetDrawBackends(true);
+}
+
+AquaSalGraphics::~AquaSalGraphics()
+{
+ SAL_INFO( "vcl.quartz", "AquaSalGraphics::~AquaSalGraphics() this=" << this );
+
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ }
+
+ ReleaseFonts();
+
+ if( mpXorEmulation )
+ delete mpXorEmulation;
+
+#ifdef IOS
+ if (mbForeignContext)
+ return;
+#endif
+ if (maLayer.isSet())
+ {
+ CGLayerRelease(maLayer.get());
+ }
+ else if (maContextHolder.isSet()
+#ifdef MACOSX
+ && mbWindow
+#endif
+ )
+ {
+ // destroy backbuffer bitmap context that we created ourself
+ CGContextRelease(maContextHolder.get());
+ maContextHolder.set(nullptr);
+ }
+}
+
+SalGraphicsImpl* AquaSalGraphics::GetImpl() const
+{
+ return nullptr;
+}
+
+void AquaSalGraphics::SetTextColor( Color nColor )
+{
+ maTextColor = RGBAColor( nColor );
+ // SAL_ DEBUG(std::hex << nColor << std::dec << "={" << maTextColor.GetRed() << ", " << maTextColor.GetGreen() << ", " << maTextColor.GetBlue() << ", " << maTextColor.GetAlpha() << "}");
+}
+
+void AquaSalGraphics::GetFontMetric(ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel)
+{
+ if (nFallbackLevel < MAX_FALLBACK && mpTextStyle[nFallbackLevel])
+ {
+ mpTextStyle[nFallbackLevel]->GetFontMetric(rxFontMetric);
+ }
+}
+
+static bool AddTempDevFont(const OUString& rFontFileURL)
+{
+ OUString aUSytemPath;
+ OSL_VERIFY( !osl::FileBase::getSystemPathFromFileURL( rFontFileURL, aUSytemPath ) );
+ OString aCFileName = OUStringToOString( aUSytemPath, RTL_TEXTENCODING_UTF8 );
+
+ CFStringRef rFontPath = CFStringCreateWithCString(nullptr, aCFileName.getStr(), kCFStringEncodingUTF8);
+ CFURLRef rFontURL = CFURLCreateWithFileSystemPath(nullptr, rFontPath, kCFURLPOSIXPathStyle, true);
+
+ CFErrorRef error;
+ bool success = CTFontManagerRegisterFontsForURL(rFontURL, kCTFontManagerScopeProcess, &error);
+ if (!success)
+ {
+ CFRelease(error);
+ }
+ CFRelease(rFontPath);
+ CFRelease(rFontURL);
+
+ return success;
+}
+
+static void AddTempFontDir( const OUString &rFontDirUrl )
+{
+ osl::Directory aFontDir( rFontDirUrl );
+ osl::FileBase::RC rcOSL = aFontDir.open();
+ if( rcOSL == osl::FileBase::E_None )
+ {
+ osl::DirectoryItem aDirItem;
+
+ while( aFontDir.getNextItem( aDirItem, 10 ) == osl::FileBase::E_None )
+ {
+ osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileURL );
+ rcOSL = aDirItem.getFileStatus( aFileStatus );
+ if ( rcOSL == osl::FileBase::E_None )
+ {
+ AddTempDevFont(aFileStatus.getFileURL());
+ }
+ }
+ }
+}
+
+static void AddLocalTempFontDirs()
+{
+ static bool bFirst = true;
+ if( !bFirst )
+ return;
+
+ bFirst = false;
+
+ // add private font files
+
+ OUString aBrandStr( "$BRAND_BASE_DIR" );
+ rtl_bootstrap_expandMacros( &aBrandStr.pData );
+
+ // internal font resources, required for normal operation, like OpenSymbol
+ AddTempFontDir( aBrandStr + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts/" );
+
+ AddTempFontDir( aBrandStr + "/" LIBO_SHARE_FOLDER "/fonts/truetype/" );
+}
+
+void AquaSalGraphics::GetDevFontList( PhysicalFontCollection* pFontCollection )
+{
+ SAL_WARN_IF( !pFontCollection, "vcl", "AquaSalGraphics::GetDevFontList(NULL) !");
+
+ AddLocalTempFontDirs();
+
+ // The idea is to cache the list of system fonts once it has been generated.
+ // SalData seems to be a good place for this caching. However we have to
+ // carefully make the access to the font list thread-safe. If we register
+ // a font-change event handler to update the font list in case fonts have
+ // changed on the system we have to lock access to the list. The right
+ // way to do that is the solar mutex since GetDevFontList is protected
+ // through it as should be all event handlers
+
+ SalData* pSalData = GetSalData();
+ if( !pSalData->mpFontList )
+ pSalData->mpFontList = GetCoretextFontList();
+
+ // Copy all PhysicalFontFace objects contained in the SystemFontList
+ pSalData->mpFontList->AnnounceFonts( *pFontCollection );
+
+ static CoreTextGlyphFallbackSubstititution aSubstFallback;
+ pFontCollection->SetFallbackHook(&aSubstFallback);
+}
+
+void AquaSalGraphics::ClearDevFontCache()
+{
+ SalData* pSalData = GetSalData();
+ delete pSalData->mpFontList;
+ pSalData->mpFontList = nullptr;
+}
+
+bool AquaSalGraphics::AddTempDevFont( PhysicalFontCollection*,
+ const OUString& rFontFileURL, const OUString& /*rFontName*/ )
+{
+ return ::AddTempDevFont(rFontFileURL);
+}
+
+void AquaSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+#ifdef IOS
+ if (!CheckContext())
+ {
+ SAL_WARN("vcl.quartz", "AquaSalGraphics::DrawTextLayout() without context");
+ return;
+ }
+#endif
+
+ const CoreTextStyle& rStyle = *static_cast<const CoreTextStyle*>(&rLayout.GetFont());
+ const FontSelectPattern& rFontSelect = rStyle.GetFontSelectPattern();
+ if (rFontSelect.mnHeight == 0)
+ {
+ SAL_WARN("vcl.quartz", "AquaSalGraphics::DrawTextLayout(): rFontSelect.mnHeight is zero!?");
+ return;
+ }
+
+ CTFontRef pFont = static_cast<CTFontRef>(CFDictionaryGetValue(rStyle.GetStyleDict(), kCTFontAttributeName));
+ CGAffineTransform aRotMatrix = CGAffineTransformMakeRotation(-rStyle.mfFontRotation);
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ std::vector<CGGlyph> aGlyphIds;
+ std::vector<CGPoint> aGlyphPos;
+ std::vector<bool> aGlyphOrientation;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ CGPoint aGCPos = CGPointMake(aPos.X(), -aPos.Y());
+
+ // Whether the glyph should be upright in vertical mode or not
+ bool bUprightGlyph = false;
+
+ if (rStyle.mfFontRotation)
+ {
+ if (pGlyph->IsVertical())
+ {
+ bUprightGlyph = true;
+ // Adjust the position of upright (vertical) glyphs.
+ aGCPos.y -= CTFontGetAscent(pFont) - CTFontGetDescent(pFont);
+ }
+ else
+ {
+ // Transform the position of rotated glyphs.
+ aGCPos = CGPointApplyAffineTransform(aGCPos, aRotMatrix);
+ }
+ }
+
+ aGlyphIds.push_back(pGlyph->glyphId());
+ aGlyphPos.push_back(aGCPos);
+ aGlyphOrientation.push_back(bUprightGlyph);
+ }
+
+ if (aGlyphIds.empty())
+ return;
+
+ assert(aGlyphIds.size() == aGlyphPos.size());
+#if 0
+ std::cerr << "aGlyphIds:[";
+ for (unsigned i = 0; i < aGlyphIds.size(); i++)
+ {
+ if (i > 0)
+ std::cerr << ",";
+ std::cerr << aGlyphIds[i];
+ }
+ std::cerr << "]\n";
+ std::cerr << "aGlyphPos:[";
+ for (unsigned i = 0; i < aGlyphPos.size(); i++)
+ {
+ if (i > 0)
+ std::cerr << ",";
+ std::cerr << aGlyphPos[i];
+ }
+ std::cerr << "]\n";
+#endif
+
+ maContextHolder.saveState();
+
+ // The view is vertically flipped (no idea why), flip it back.
+ CGContextScaleCTM(maContextHolder.get(), 1.0, -1.0);
+ CGContextSetShouldAntialias(maContextHolder.get(), !mbNonAntialiasedText);
+ CGContextSetFillColor(maContextHolder.get(), maTextColor.AsArray());
+
+ if (rStyle.mbFauxBold)
+ {
+
+ float fSize = rFontSelect.mnHeight / 23.0f;
+ CGContextSetStrokeColor(maContextHolder.get(), maTextColor.AsArray());
+ CGContextSetLineWidth(maContextHolder.get(), fSize);
+ CGContextSetTextDrawingMode(maContextHolder.get(), kCGTextFillStroke);
+ }
+
+ auto aIt = aGlyphOrientation.cbegin();
+ while (aIt != aGlyphOrientation.cend())
+ {
+ bool bUprightGlyph = *aIt;
+ // Find the boundary of the run of glyphs with the same rotation, to be
+ // drawn together.
+ auto aNext = std::find(aIt, aGlyphOrientation.cend(), !bUprightGlyph);
+ size_t nStartIndex = std::distance(aGlyphOrientation.cbegin(), aIt);
+ size_t nLen = std::distance(aIt, aNext);
+
+ maContextHolder.saveState();
+ if (rStyle.mfFontRotation && !bUprightGlyph)
+ {
+ CGContextRotateCTM(maContextHolder.get(), rStyle.mfFontRotation);
+ }
+ CTFontDrawGlyphs(pFont, &aGlyphIds[nStartIndex], &aGlyphPos[nStartIndex], nLen, maContextHolder.get());
+ maContextHolder.restoreState();
+
+ aIt = aNext;
+ }
+
+ maContextHolder.restoreState();
+}
+
+void AquaSalGraphics::SetFont(LogicalFontInstance* pReqFont, int nFallbackLevel)
+{
+ // release the text style
+ for (int i = nFallbackLevel; i < MAX_FALLBACK; ++i)
+ {
+ if (!mpTextStyle[i])
+ break;
+ mpTextStyle[i].clear();
+ }
+
+ if (!pReqFont)
+ return;
+
+ // update the text style
+ mpTextStyle[nFallbackLevel] = static_cast<CoreTextStyle*>(pReqFont);
+}
+
+std::unique_ptr<GenericSalLayout> AquaSalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(mpTextStyle[nFallbackLevel]);
+ if (!mpTextStyle[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<GenericSalLayout>(*mpTextStyle[nFallbackLevel]);
+}
+
+FontCharMapRef AquaSalGraphics::GetFontCharMap() const
+{
+ if (!mpTextStyle[0])
+ {
+ return FontCharMapRef( new FontCharMap() );
+ }
+
+ return static_cast<const CoreTextFontFace*>(mpTextStyle[0]->GetFontFace())->GetFontCharMap();
+}
+
+bool AquaSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ if (!mpTextStyle[0])
+ return false;
+
+ return static_cast<const CoreTextFontFace*>(mpTextStyle[0]->GetFontFace())->GetFontCapabilities(rFontCapabilities);
+}
+
+// fake a SFNT font directory entry for a font table
+// see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html#Directory
+static void FakeDirEntry( const char aTag[5], ByteCount nOfs, ByteCount nLen,
+ const unsigned char* /*pData*/, unsigned char*& rpDest )
+{
+ // write entry tag
+ rpDest[ 0] = aTag[0];
+ rpDest[ 1] = aTag[1];
+ rpDest[ 2] = aTag[2];
+ rpDest[ 3] = aTag[3];
+ // TODO: get entry checksum and write it
+ // not too important since the subsetter doesn't care currently
+ // for( pData+nOfs ... pData+nOfs+nLen )
+ // write entry offset
+ rpDest[ 8] = static_cast<char>(nOfs >> 24);
+ rpDest[ 9] = static_cast<char>(nOfs >> 16);
+ rpDest[10] = static_cast<char>(nOfs >> 8);
+ rpDest[11] = static_cast<char>(nOfs >> 0);
+ // write entry length
+ rpDest[12] = static_cast<char>(nLen >> 24);
+ rpDest[13] = static_cast<char>(nLen >> 16);
+ rpDest[14] = static_cast<char>(nLen >> 8);
+ rpDest[15] = static_cast<char>(nLen >> 0);
+ // advance to next entry
+ rpDest += 16;
+}
+
+// fake a TTF or CFF font as directly accessing font file is not possible
+// when only the fontid is known. This approach also handles *.font fonts.
+bool AquaSalGraphics::GetRawFontData( const PhysicalFontFace* pFontData,
+ std::vector<unsigned char>& rBuffer, bool* pJustCFF )
+{
+ const CoreTextFontFace* pMacFont = static_cast<const CoreTextFontFace*>(pFontData);
+
+ // short circuit for CFF-only fonts
+ const int nCffSize = pMacFont->GetFontTable( "CFF ", nullptr);
+ if( pJustCFF != nullptr )
+ {
+ *pJustCFF = (nCffSize > 0);
+ if( *pJustCFF)
+ {
+ rBuffer.resize( nCffSize);
+ const int nCffRead = pMacFont->GetFontTable( "CFF ", rBuffer.data());
+ if( nCffRead != nCffSize)
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ // get font table availability and size in bytes
+ const int nHeadSize = pMacFont->GetFontTable( "head", nullptr);
+ if( nHeadSize <= 0)
+ return false;
+
+ const int nMaxpSize = pMacFont->GetFontTable( "maxp", nullptr);
+ if( nMaxpSize <= 0)
+ return false;
+
+ const int nCmapSize = pMacFont->GetFontTable( "cmap", nullptr);
+ if( nCmapSize <= 0)
+ return false;
+
+ const int nNameSize = pMacFont->GetFontTable( "name", nullptr);
+ if( nNameSize <= 0)
+ return false;
+
+ const int nHheaSize = pMacFont->GetFontTable( "hhea", nullptr);
+ if( nHheaSize <= 0)
+ return false;
+
+ const int nHmtxSize = pMacFont->GetFontTable( "hmtx", nullptr);
+ if( nHmtxSize <= 0)
+ return false;
+
+ // get the ttf-glyf outline tables
+ int nLocaSize = 0;
+ int nGlyfSize = 0;
+ if( nCffSize <= 0)
+ {
+ nLocaSize = pMacFont->GetFontTable( "loca", nullptr);
+ if( nLocaSize <= 0)
+ return false;
+
+ nGlyfSize = pMacFont->GetFontTable( "glyf", nullptr);
+ if( nGlyfSize <= 0)
+ return false;
+ }
+
+ int nPrepSize = 0, nCvtSize = 0, nFpgmSize = 0;
+ if( nGlyfSize) // TODO: reduce PDF size by making hint subsetting optional
+ {
+ nPrepSize = pMacFont->GetFontTable( "prep", nullptr);
+ nCvtSize = pMacFont->GetFontTable( "cvt ", nullptr);
+ nFpgmSize = pMacFont->GetFontTable( "fpgm", nullptr);
+ }
+
+ // prepare a byte buffer for a fake font
+ int nTableCount = 7;
+ nTableCount += (nPrepSize>0?1:0) + (nCvtSize>0?1:0) + (nFpgmSize>0?1:0) + (nGlyfSize>0?1:0);
+ const ByteCount nFdirSize = 12 + 16*nTableCount;
+ ByteCount nTotalSize = nFdirSize;
+ nTotalSize += nHeadSize + nMaxpSize + nNameSize + nCmapSize;
+
+ if( nGlyfSize )
+ {
+ nTotalSize += nLocaSize + nGlyfSize;
+ }
+ else
+ {
+ nTotalSize += nCffSize;
+ }
+ nTotalSize += nHheaSize + nHmtxSize;
+ nTotalSize += nPrepSize + nCvtSize + nFpgmSize;
+ rBuffer.resize( nTotalSize );
+
+ // fake a SFNT font directory header
+ if( nTableCount < 16 )
+ {
+ int nLog2 = 0;
+ while( (nTableCount >> nLog2) > 1 ) ++nLog2;
+ rBuffer[ 1] = 1; // Win-TTF style scaler
+ rBuffer[ 5] = nTableCount; // table count
+ rBuffer[ 7] = nLog2*16; // searchRange
+ rBuffer[ 9] = nLog2; // entrySelector
+ rBuffer[11] = (nTableCount-nLog2)*16; // rangeShift
+ }
+
+ // get font table raw data and update the fake directory entries
+ ByteCount nOfs = nFdirSize;
+ unsigned char* pFakeEntry = &rBuffer[12];
+ if( nCmapSize != pMacFont->GetFontTable( "cmap", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "cmap", nOfs, nCmapSize, rBuffer.data(), pFakeEntry );
+ nOfs += nCmapSize;
+ if( nCvtSize )
+ {
+ if( nCvtSize != pMacFont->GetFontTable( "cvt ", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "cvt ", nOfs, nCvtSize, rBuffer.data(), pFakeEntry );
+ nOfs += nCvtSize;
+ }
+ if( nFpgmSize )
+ {
+ if( nFpgmSize != pMacFont->GetFontTable( "fpgm", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "fpgm", nOfs, nFpgmSize, rBuffer.data(), pFakeEntry );
+ nOfs += nFpgmSize;
+ }
+ if( nCffSize )
+ {
+ if( nCffSize != pMacFont->GetFontTable( "CFF ", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "CFF ", nOfs, nCffSize, rBuffer.data(), pFakeEntry );
+ nOfs += nGlyfSize;
+ }
+ else
+ {
+ if( nGlyfSize != pMacFont->GetFontTable( "glyf", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "glyf", nOfs, nGlyfSize, rBuffer.data(), pFakeEntry );
+ nOfs += nGlyfSize;
+
+ if( nLocaSize != pMacFont->GetFontTable( "loca", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "loca", nOfs, nLocaSize, rBuffer.data(), pFakeEntry );
+ nOfs += nLocaSize;
+ }
+ if( nHeadSize != pMacFont->GetFontTable( "head", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "head", nOfs, nHeadSize, rBuffer.data(), pFakeEntry );
+ nOfs += nHeadSize;
+
+ if( nHheaSize != pMacFont->GetFontTable( "hhea", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "hhea", nOfs, nHheaSize, rBuffer.data(), pFakeEntry );
+ nOfs += nHheaSize;
+ if( nHmtxSize != pMacFont->GetFontTable( "hmtx", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "hmtx", nOfs, nHmtxSize, rBuffer.data(), pFakeEntry );
+ nOfs += nHmtxSize;
+ if( nMaxpSize != pMacFont->GetFontTable( "maxp", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "maxp", nOfs, nMaxpSize, rBuffer.data(), pFakeEntry );
+ nOfs += nMaxpSize;
+ if( nNameSize != pMacFont->GetFontTable( "name", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "name", nOfs, nNameSize, rBuffer.data(), pFakeEntry );
+ nOfs += nNameSize;
+ if( nPrepSize )
+ {
+ if( nPrepSize != pMacFont->GetFontTable( "prep", &rBuffer[nOfs]))
+ return false;
+
+ FakeDirEntry( "prep", nOfs, nPrepSize, rBuffer.data(), pFakeEntry );
+ nOfs += nPrepSize;
+ }
+
+ SAL_WARN_IF( (nOfs!=nTotalSize), "vcl", "AquaSalGraphics::GetRawFontData (nOfs!=nTotalSize)");
+
+ return true;
+}
+
+void AquaSalGraphics::GetGlyphWidths( const PhysicalFontFace* pFontData, bool bVertical,
+ std::vector< sal_Int32 >& rGlyphWidths, Ucs2UIntMap& rUnicodeEnc )
+{
+ rGlyphWidths.clear();
+ rUnicodeEnc.clear();
+
+ std::vector<unsigned char> aBuffer;
+ if( !GetRawFontData( pFontData, aBuffer, nullptr ) )
+ return;
+
+ // 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
+
+ // use the font subsetter to get the widths
+ TrueTypeFont* pSftFont = nullptr;
+ SFErrCodes nRC = ::OpenTTFontBuffer( static_cast<void*>(aBuffer.data()), aBuffer.size(), 0, &pSftFont);
+ if( nRC != SFErrCodes::Ok )
+ return;
+
+ const int nGlyphCount = ::GetTTGlyphCount( pSftFont );
+ if( nGlyphCount > 0 )
+ {
+ // get glyph metrics
+ rGlyphWidths.resize(nGlyphCount);
+ std::vector<sal_uInt16> aGlyphIds(nGlyphCount);
+ for( int i = 0; i < nGlyphCount; i++ )
+ {
+ aGlyphIds[i] = static_cast<sal_uInt16>(i);
+ }
+
+ std::unique_ptr<sal_uInt16[]> pGlyphMetrics = ::GetTTSimpleGlyphMetrics( pSftFont, aGlyphIds.data(),
+ nGlyphCount, bVertical );
+ if( pGlyphMetrics )
+ {
+ for( int i = 0; i < nGlyphCount; ++i )
+ {
+ rGlyphWidths[i] = pGlyphMetrics[i];
+ }
+ pGlyphMetrics.reset();
+ }
+
+ rtl::Reference<CoreTextFontFace> rCTFontData(new CoreTextFontFace(*pFontData, pFontData->GetFontId()));
+ FontCharMapRef xFCMap = rCTFontData->GetFontCharMap();
+ SAL_WARN_IF( !xFCMap.is() || !xFCMap->GetCharCount(), "vcl", "no charmap" );
+
+ // get unicode<->glyph encoding
+ // TODO? avoid sft mapping by using the xFCMap itself
+ int nCharCount = xFCMap->GetCharCount();
+ sal_uInt32 nChar = xFCMap->GetFirstChar();
+ for( ; --nCharCount >= 0; nChar = xFCMap->GetNextChar( nChar ) )
+ {
+ if( nChar > 0xFFFF ) // TODO: allow UTF-32 chars
+ break;
+
+ sal_Ucs nUcsChar = static_cast<sal_Ucs>(nChar);
+ sal_uInt32 nGlyph = ::MapChar( pSftFont, nUcsChar );
+ if( nGlyph > 0 )
+ {
+ rUnicodeEnc[ nUcsChar ] = nGlyph;
+ }
+ }
+
+ xFCMap = nullptr;
+ }
+
+ ::CloseTTFont( pSftFont );
+}
+
+const void* AquaSalGraphics::GetEmbedFontData(const PhysicalFontFace*, long* /*pDataLen*/)
+{
+ return nullptr;
+}
+
+void AquaSalGraphics::FreeEmbedFontData( const void* pData, long /*nDataLen*/ )
+{
+ // TODO: implementing this only makes sense when the implementation of
+ // AquaSalGraphics::GetEmbedFontData() returns non-NULL
+ SAL_WARN_IF( (pData==nullptr), "vcl", "AquaSalGraphics::FreeEmbedFontData() is not implemented");
+}
+
+bool AquaSalGraphics::IsFlipped() const
+{
+#ifdef MACOSX
+ return mbWindow;
+#else
+ return false;
+#endif
+}
+
+void AquaSalGraphics::RefreshRect(float lX, float lY, float lWidth, float lHeight)
+{
+#ifdef MACOSX
+ if( ! mbWindow ) // view only on Window graphics
+ return;
+
+ if( mpFrame )
+ {
+ // update a little more around the designated rectangle
+ // this helps with antialiased rendering
+ // Rounding down x and width can accumulate a rounding error of up to 2
+ // The decrementing of x, the rounding error and the antialiasing border
+ // require that the width and the height need to be increased by four
+ const tools::Rectangle aVclRect(Point(static_cast<long int>(lX-1),
+ static_cast<long int>(lY-1) ),
+ Size( static_cast<long int>(lWidth+4),
+ static_cast<long int>(lHeight+4) ) );
+ mpFrame->maInvalidRect.Union( aVclRect );
+ }
+#else
+ (void) lX;
+ (void) lY;
+ (void) lWidth;
+ (void) lHeight;
+ return;
+#endif
+}
+
+#ifdef IOS
+
+bool AquaSalGraphics::CheckContext()
+{
+ if (mbForeignContext)
+ {
+ SAL_INFO("vcl.ios", "CheckContext() this=" << this << ", mbForeignContext, return true");
+ return true;
+ }
+
+ SAL_INFO( "vcl.ios", "CheckContext() this=" << this << ", not foreign, return false");
+ return false;
+}
+
+CGContextRef AquaSalGraphics::GetContext()
+{
+ if (!maContextHolder.isSet())
+ CheckContext();
+
+ return maContextHolder.get();
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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: */
diff --git a/vcl/quartz/salgdiutils.cxx b/vcl/quartz/salgdiutils.cxx
new file mode 100644
index 000000000..57953e536
--- /dev/null
+++ b/vcl/quartz/salgdiutils.cxx
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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 <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <vcl/svapp.hxx>
+
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+#include <osx/salframe.h>
+#include <osx/saldata.hxx>
+
+void AquaSalGraphics::SetWindowGraphics( AquaSalFrame* pFrame )
+{
+ mpFrame = pFrame;
+ mbWindow = true;
+ mbPrinter = false;
+ mbVirDev = false;
+}
+
+void AquaSalGraphics::SetPrinterGraphics( CGContextRef xContext, long nDPIX, long nDPIY )
+{
+ mbWindow = false;
+ mbPrinter = true;
+ mbVirDev = false;
+
+ maContextHolder.set(xContext);
+ mnRealDPIX = nDPIX;
+ mnRealDPIY = nDPIY;
+
+ // a previously set clip path is now invalid
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ mxClipPath = nullptr;
+ }
+
+ if (maContextHolder.isSet())
+ {
+ CGContextSetFillColorSpace( maContextHolder.get(), GetSalData()->mxRGBSpace );
+ CGContextSetStrokeColorSpace( maContextHolder.get(), GetSalData()->mxRGBSpace );
+ CGContextSaveGState( maContextHolder.get() );
+ SetState();
+ }
+}
+
+void AquaSalGraphics::InvalidateContext()
+{
+ UnsetState();
+
+ CGContextRelease(maContextHolder.get());
+ CGContextRelease(maBGContextHolder.get());
+ CGContextRelease(maCSContextHolder.get());
+
+ maContextHolder.set(nullptr);
+ maCSContextHolder.set(nullptr);
+ maBGContextHolder.set(nullptr);
+}
+
+void AquaSalGraphics::UnsetState()
+{
+ if (maBGContextHolder.isSet())
+ {
+ CGContextRelease(maBGContextHolder.get());
+ maBGContextHolder.set(nullptr);
+ }
+ if (maCSContextHolder.isSet())
+ {
+ CGContextRelease(maCSContextHolder.get());
+ maBGContextHolder.set(nullptr);
+ }
+ if (maContextHolder.isSet())
+ {
+ maContextHolder.restoreState();
+ maContextHolder.set(nullptr);
+ }
+ if( mxClipPath )
+ {
+ CGPathRelease( mxClipPath );
+ mxClipPath = nullptr;
+ }
+}
+
+/**
+ * (re-)create the off-screen maLayer we render everything to if
+ * necessary: eg. not initialized yet, or it has an incorrect size.
+ */
+bool AquaSalGraphics::CheckContext()
+{
+ if (mbWindow && mpFrame && (mpFrame->getNSWindow() || Application::IsBitmapRendering()))
+ {
+ const unsigned int nWidth = mpFrame->maGeometry.nWidth;
+ const unsigned int nHeight = mpFrame->maGeometry.nHeight;
+
+ // Let's get the window scaling factor if possible, or use 1.0
+ // as the scaling factor.
+ float fScale = 1.0f;
+ if (mpFrame->getNSWindow())
+ fScale = [mpFrame->getNSWindow() backingScaleFactor];
+
+ CGLayerRef rReleaseLayer = nullptr;
+
+ // check if a new drawing context is needed (e.g. after a resize)
+ if( (unsigned(mnWidth) != nWidth) || (unsigned(mnHeight) != nHeight) )
+ {
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ // prepare to release the corresponding resources
+ if (maLayer.isSet())
+ {
+ rReleaseLayer = maLayer.get();
+ }
+ else if (maContextHolder.isSet())
+ {
+ CGContextRelease(maContextHolder.get());
+ }
+ CGContextRelease(maBGContextHolder.get());
+ CGContextRelease(maCSContextHolder.get());
+
+ maContextHolder.set(nullptr);
+ maBGContextHolder.set(nullptr);
+ maCSContextHolder.set(nullptr);
+ maLayer.set(nullptr);
+ }
+
+ if (!maContextHolder.isSet())
+ {
+ const int nBitmapDepth = 32;
+
+ float nScaledWidth = mnWidth * fScale;
+ float nScaledHeight = mnHeight * fScale;
+
+ const CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
+
+ const int nBytesPerRow = (nBitmapDepth * nScaledWidth) / 8;
+ int nFlags = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
+ maBGContextHolder.set(CGBitmapContextCreate(
+ NULL, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
+
+ maLayer.set(CGLayerCreateWithContext(maBGContextHolder.get(), aLayerSize, nullptr));
+ maLayer.setScale(fScale);
+
+ nFlags = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
+ maCSContextHolder.set(CGBitmapContextCreate(
+ NULL, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
+
+ CGContextRef xDrawContext = CGLayerGetContext(maLayer.get());
+ maContextHolder = xDrawContext;
+
+ if (rReleaseLayer)
+ {
+ // copy original layer to resized layer
+ if (maContextHolder.isSet())
+ {
+ CGContextDrawLayerAtPoint(maContextHolder.get(), CGPointZero, rReleaseLayer);
+ }
+ CGLayerRelease(rReleaseLayer);
+ }
+
+ if (maContextHolder.isSet())
+ {
+ CGContextTranslateCTM(maContextHolder.get(), 0, nScaledHeight);
+ CGContextScaleCTM(maContextHolder.get(), 1.0, -1.0);
+ CGContextSetFillColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
+ CGContextSetStrokeColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
+ // apply a scale matrix so everything is auto-magically scaled
+ CGContextScaleCTM(maContextHolder.get(), fScale, fScale);
+ maContextHolder.saveState();
+ SetState();
+
+ // re-enable XOR emulation for the new context
+ if (mpXorEmulation)
+ mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
+ }
+ }
+ }
+
+ SAL_WARN_IF(!maContextHolder.isSet() && !mbPrinter, "vcl", "<<<WARNING>>> AquaSalGraphics::CheckContext() FAILED!!!!");
+
+ return maContextHolder.isSet();
+}
+
+CGContextRef AquaSalGraphics::GetContext()
+{
+ if (!maContextHolder.isSet())
+ {
+ CheckContext();
+ }
+ return maContextHolder.get();
+}
+
+/**
+ * Blit the contents of our internal maLayer state to the
+ * associated window, if any; cf. drawRect event handling
+ * on the frame.
+ */
+void AquaSalGraphics::UpdateWindow( NSRect& )
+{
+ if( !mpFrame )
+ {
+ return;
+ }
+
+ NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
+ if (maLayer.isSet() && pContext != nullptr)
+ {
+ CGContextHolder rCGContextHolder([pContext CGContext]);
+
+ rCGContextHolder.saveState();
+
+ CGMutablePathRef rClip = mpFrame->getClipPath();
+ if (rClip)
+ {
+ CGContextBeginPath(rCGContextHolder.get());
+ CGContextAddPath(rCGContextHolder.get(), rClip );
+ CGContextClip(rCGContextHolder.get());
+ }
+
+ ApplyXorContext();
+
+ const CGSize aSize = maLayer.getSizePoints();
+ const CGRect aRect = CGRectMake(0, 0, aSize.width, aSize.height);
+ const CGRect aRectPoints = { CGPointZero, maLayer.getSizePixels() };
+ CGContextSetBlendMode(maCSContextHolder.get(), kCGBlendModeCopy);
+ CGContextDrawLayerInRect(maCSContextHolder.get(), aRectPoints, maLayer.get());
+
+ CGImageRef img = CGBitmapContextCreateImage(maCSContextHolder.get());
+ CGImageRef displayColorSpaceImage = CGImageCreateCopyWithColorSpace(img, [[mpFrame->getNSWindow() colorSpace] CGColorSpace]);
+ CGContextSetBlendMode(rCGContextHolder.get(), kCGBlendModeCopy);
+ CGContextDrawImage(rCGContextHolder.get(), aRect, displayColorSpaceImage);
+
+ CGImageRelease(img);
+ CGImageRelease(displayColorSpaceImage);
+
+ rCGContextHolder.restoreState();
+ }
+ else
+ {
+ SAL_WARN_IF( !mpFrame->mbInitShow, "vcl", "UpdateWindow called on uneligible graphics" );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/salvd.cxx b/vcl/quartz/salvd.cxx
new file mode 100644
index 000000000..c06ba33c5
--- /dev/null
+++ b/vcl/quartz/salvd.cxx
@@ -0,0 +1,303 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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 <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#ifdef MACOSX
+#include <osx/salinst.h>
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#else
+#include "headless/svpframe.hxx"
+#include "headless/svpinst.hxx"
+#include "headless/svpvd.hxx"
+#endif
+#include <quartz/salgdi.h>
+#include <quartz/salvd.h>
+#include <quartz/utils.h>
+
+std::unique_ptr<SalVirtualDevice> AquaSalInstance::CreateVirtualDevice( SalGraphics* pGraphics,
+ long &nDX, long &nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData *pData )
+{
+ // #i92075# can be called first in a thread
+ SalData::ensureThreadAutoreleasePool();
+
+#ifdef IOS
+ if( pData )
+ {
+ return std::unique_ptr<SalVirtualDevice>(new AquaSalVirtualDevice( static_cast< AquaSalGraphics* >( pGraphics ),
+ nDX, nDY, eFormat, pData ));
+ }
+ else
+ {
+ std::unique_ptr<SalVirtualDevice> pNew(new AquaSalVirtualDevice( NULL, nDX, nDY, eFormat, NULL ));
+ pNew->SetSize( nDX, nDY );
+ return pNew;
+ }
+#else
+ return std::unique_ptr<SalVirtualDevice>(new AquaSalVirtualDevice( static_cast< AquaSalGraphics* >( pGraphics ),
+ nDX, nDY, eFormat, pData ));
+#endif
+}
+
+AquaSalVirtualDevice::AquaSalVirtualDevice( AquaSalGraphics* pGraphic, long &nDX, long &nDY,
+ DeviceFormat eFormat, const SystemGraphicsData *pData )
+ : mbGraphicsUsed( false )
+ , mnBitmapDepth( 0 )
+ , mnWidth(0)
+ , mnHeight(0)
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::AquaSalVirtualDevice() this=" << this
+ << " size=(" << nDX << "x" << nDY << ") bitcount=" << static_cast<int>(eFormat) <<
+ " pData=" << pData << " context=" << (pData ? pData->rCGContext : nullptr) );
+
+ if( pGraphic && pData && pData->rCGContext )
+ {
+ // Create virtual device based on existing SystemGraphicsData
+ // We ignore nDx and nDY, as the desired size comes from the SystemGraphicsData.
+ // the mxContext is from pData (what "mxContext"? there is no such field anywhere in vcl;)
+ mbForeignContext = true;
+ mpGraphics = new AquaSalGraphics( /*pGraphic*/ );
+ if (nDX == 0)
+ {
+ nDX = 1;
+ }
+ if (nDY == 0)
+ {
+ nDY = 1;
+ }
+ maLayer.set(CGLayerCreateWithContext(pData->rCGContext, CGSizeMake(nDX, nDY), nullptr));
+ // Interrogate the context as to its real size
+ if (maLayer.isSet())
+ {
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ nDX = static_cast<long>(aSize.width);
+ nDY = static_cast<long>(aSize.height);
+ }
+ else
+ {
+ nDX = 0;
+ nDY = 0;
+ }
+
+ mpGraphics->SetVirDevGraphics(maLayer, pData->rCGContext);
+ }
+ else
+ {
+ // create empty new virtual device
+ mbForeignContext = false; // the mxContext is created within VCL
+ mpGraphics = new AquaSalGraphics(); // never fails
+ switch (eFormat)
+ {
+ case DeviceFormat::BITMASK:
+ mnBitmapDepth = 1;
+ break;
+#ifdef IOS
+ case DeviceFormat::GRAYSCALE:
+ mnBitmapDepth = 8;
+ break;
+#endif
+ default:
+ mnBitmapDepth = 0;
+ break;
+ }
+#ifdef MACOSX
+ // inherit resolution from reference device
+ if( pGraphic )
+ {
+ AquaSalFrame* pFrame = pGraphic->getGraphicsFrame();
+ if( pFrame && AquaSalFrame::isAlive( pFrame ) )
+ {
+ mpGraphics->setGraphicsFrame( pFrame );
+ mpGraphics->copyResolution( *pGraphic );
+ }
+ }
+#endif
+ if( nDX && nDY )
+ {
+ SetSize( nDX, nDY );
+ }
+ // NOTE: if SetSize does not succeed, we just ignore the nDX and nDY
+ }
+}
+
+AquaSalVirtualDevice::~AquaSalVirtualDevice()
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::~AquaSalVirtualDevice() this=" << this );
+
+ if( mpGraphics )
+ {
+ mpGraphics->SetVirDevGraphics( nullptr, nullptr );
+ delete mpGraphics;
+ mpGraphics = nullptr;
+ }
+ Destroy();
+}
+
+void AquaSalVirtualDevice::Destroy()
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
+
+ if( mbForeignContext )
+ {
+ // Do not delete mxContext that we have received from outside VCL
+ maLayer.set(nullptr);
+ return;
+ }
+
+ if (maLayer.isSet())
+ {
+ if( mpGraphics )
+ {
+ mpGraphics->SetVirDevGraphics(nullptr, nullptr);
+ }
+ CGLayerRelease(maLayer.get());
+ maLayer.set(nullptr);
+ }
+
+ if (maBitmapContext.isSet())
+ {
+ void* pRawData = CGBitmapContextGetData(maBitmapContext.get());
+ std::free(pRawData);
+ CGContextRelease(maBitmapContext.get());
+ maBitmapContext.set(nullptr);
+ }
+}
+
+SalGraphics* AquaSalVirtualDevice::AcquireGraphics()
+{
+ if( mbGraphicsUsed || !mpGraphics )
+ {
+ return nullptr;
+ }
+ mbGraphicsUsed = true;
+ return mpGraphics;
+}
+
+void AquaSalVirtualDevice::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphicsUsed = false;
+}
+
+bool AquaSalVirtualDevice::SetSize( long nDX, long nDY )
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
+
+ if( mbForeignContext )
+ {
+ // Do not delete/resize mxContext that we have received from outside VCL
+ return true;
+ }
+
+ if (maLayer.isSet())
+ {
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ if( (nDX == aSize.width) && (nDY == aSize.height) )
+ {
+ // Yay, we do not have to do anything :)
+ return true;
+ }
+ }
+
+ Destroy();
+
+ mnWidth = nDX;
+ mnHeight = nDY;
+
+ // create a Quartz layer matching to the intended virdev usage
+ CGContextHolder xCGContextHolder;
+ if( mnBitmapDepth && (mnBitmapDepth < 16) )
+ {
+ mnBitmapDepth = 8; // TODO: are 1bit vdevs worth it?
+ const int nBytesPerRow = (mnBitmapDepth * nDX + 7) / 8;
+
+ void* pRawData = std::malloc( nBytesPerRow * nDY );
+ maBitmapContext.set(CGBitmapContextCreate( pRawData, nDX, nDY,
+ mnBitmapDepth, nBytesPerRow,
+ GetSalData()->mxGraySpace, kCGImageAlphaNone));
+ xCGContextHolder = maBitmapContext;
+ }
+ else
+ {
+#ifdef MACOSX
+ // default to a NSView target context
+ AquaSalFrame* pSalFrame = mpGraphics->getGraphicsFrame();
+ if( !pSalFrame || !AquaSalFrame::isAlive( pSalFrame ))
+ {
+ pSalFrame = static_cast<AquaSalFrame*>( GetSalData()->mpInstance->anyFrame() );
+ if ( pSalFrame )
+ // update the frame reference
+ mpGraphics->setGraphicsFrame( pSalFrame );
+ }
+ if( pSalFrame )
+ {
+ // #i91990#
+ NSWindow* pNSWindow = pSalFrame->getNSWindow();
+ if ( pNSWindow )
+ {
+ NSGraphicsContext* pNSContext = [NSGraphicsContext graphicsContextWithWindow: pNSWindow];
+ if( pNSContext )
+ {
+ xCGContextHolder.set([pNSContext CGContext]);
+ }
+ }
+ }
+#endif
+
+ if (!xCGContextHolder.isSet())
+ {
+ // assert(Application::IsBitmapRendering());
+ mnBitmapDepth = 32;
+
+ const int nBytesPerRow = (mnBitmapDepth * nDX) / 8;
+ void* pRawData = std::malloc( nBytesPerRow * nDY );
+#ifdef MACOSX
+ const int nFlags = kCGImageAlphaNoneSkipFirst;
+#else
+ const int nFlags = kCGImageAlphaNoneSkipFirst | kCGImageByteOrder32Little;
+#endif
+ maBitmapContext.set(CGBitmapContextCreate(pRawData, nDX, nDY, 8, nBytesPerRow,
+ GetSalData()->mxRGBSpace, nFlags));
+ xCGContextHolder = maBitmapContext;
+ }
+ }
+
+ SAL_WARN_IF(!xCGContextHolder.isSet(), "vcl.quartz", "No context");
+
+ const CGSize aNewSize = { static_cast<CGFloat>(nDX), static_cast<CGFloat>(nDY) };
+ maLayer.set(CGLayerCreateWithContext(xCGContextHolder.get(), aNewSize, nullptr));
+
+ if (maLayer.isSet() && mpGraphics)
+ {
+ // get the matching Quartz context
+ CGContextRef xDrawContext = CGLayerGetContext( maLayer.get() );
+ mpGraphics->SetVirDevGraphics(maLayer.get(), xDrawContext, mnBitmapDepth);
+ }
+
+ return maLayer.isSet();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/quartz/utils.cxx b/vcl/quartz/utils.cxx
new file mode 100644
index 000000000..0e0ac8f4d
--- /dev/null
+++ b/vcl/quartz/utils.cxx
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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 <iostream>
+#include <iomanip>
+
+#include <rtl/alloc.h>
+#include <rtl/ustrbuf.hxx>
+
+#include <quartz/utils.h>
+
+OUString GetOUString( CFStringRef rStr )
+{
+ if( rStr == nullptr )
+ {
+ return OUString();
+ }
+
+ CFIndex nLength = CFStringGetLength( rStr );
+ if( nLength == 0 )
+ {
+ return OUString();
+ }
+
+ const UniChar* pConstStr = CFStringGetCharactersPtr( rStr );
+ if( pConstStr )
+ {
+ return OUString( reinterpret_cast<sal_Unicode const *>(pConstStr), nLength );
+ }
+
+ std::unique_ptr<UniChar[]> pStr(new UniChar[nLength]);
+ CFRange aRange = { 0, nLength };
+ CFStringGetCharacters( rStr, aRange, pStr.get() );
+
+ OUString aRet( reinterpret_cast<sal_Unicode *>(pStr.get()), nLength );
+ return aRet;
+}
+
+OUString GetOUString( const NSString* pStr )
+{
+ if( ! pStr )
+ {
+ return OUString();
+ }
+
+ int nLen = [pStr length];
+ if( nLen == 0 )
+ {
+ return OUString();
+ }
+
+ OUStringBuffer aBuf( nLen+1 );
+ aBuf.setLength( nLen );
+ [pStr getCharacters:
+ reinterpret_cast<unichar *>(const_cast<sal_Unicode*>(aBuf.getStr()))];
+
+ return aBuf.makeStringAndClear();
+}
+
+CFStringRef CreateCFString( const OUString& rStr )
+{
+ return CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<UniChar const *>(rStr.getStr()), rStr.getLength() );
+}
+
+NSString* CreateNSString( const OUString& rStr )
+{
+ return [[NSString alloc] initWithCharacters: reinterpret_cast<unichar const *>(rStr.getStr()) length: rStr.getLength()];
+}
+
+std::ostream &operator <<(std::ostream& s, const CGRect &rRect)
+{
+#ifndef SAL_LOG_INFO
+ (void) rRect;
+#else
+ if (CGRectIsNull(rRect))
+ {
+ s << "NULL";
+ }
+ else
+ {
+ s << rRect.size << "@" << rRect.origin;
+ }
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, const CGPoint &rPoint)
+{
+#ifndef SAL_LOG_INFO
+ (void) rPoint;
+#else
+ s << "(" << rPoint.x << "," << rPoint.y << ")";
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, const CGSize &rSize)
+{
+#ifndef SAL_LOG_INFO
+ (void) rSize;
+#else
+ s << rSize.width << "x" << rSize.height;
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, CGColorRef pColor)
+{
+#ifndef SAL_LOG_INFO
+ (void) pColor;
+#else
+ CFStringRef colorString = CFCopyDescription(pColor);
+ if (colorString)
+ {
+ s << GetOUString(colorString);
+ CFRelease(colorString);
+ }
+ else
+ {
+ s << "NULL";
+ }
+#endif
+ return s;
+}
+
+std::ostream &operator <<(std::ostream& s, const CGAffineTransform &aXform)
+{
+#ifndef SAL_LOG_INFO
+ (void) aXform;
+#else
+ if (CGAffineTransformIsIdentity(aXform))
+ {
+ s << "IDENT";
+ }
+ else
+ {
+ s << "[" << aXform.a << "," << aXform.b << "," << aXform.c << "," << aXform.d << "," << aXform.tx << "," << aXform.ty << "]";
+ }
+#endif
+ return s;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */