1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 
23 #include <basegfx/polygon/b2dpolygon.hxx>
24 #include <basegfx/matrix/b2dhommatrix.hxx>
25 
26 #include <vcl/settings.hxx>
27 
28 
29 #include <quartz/ctfonts.hxx>
30 #include <impfont.hxx>
31 #ifdef MACOSX
32 #include <osx/saldata.hxx>
33 #include <osx/salinst.h>
34 #endif
35 #include <fontinstance.hxx>
36 #include <fontattributes.hxx>
37 #include <impglyphitem.hxx>
38 #include <PhysicalFontCollection.hxx>
39 #include <quartz/salgdi.h>
40 #include <quartz/utils.h>
41 #include <sallayout.hxx>
42 #include <hb-coretext.h>
43 
toRadian(int nDegree)44 static double toRadian(int nDegree)
45 {
46     return nDegree * (M_PI / 1800.0);
47 }
48 
CoreTextStyle(const PhysicalFontFace & rPFF,const FontSelectPattern & rFSP)49 CoreTextStyle::CoreTextStyle(const PhysicalFontFace& rPFF, const FontSelectPattern& rFSP)
50     : LogicalFontInstance(rPFF, rFSP)
51     , mfFontStretch( 1.0 )
52     , mfFontRotation( 0.0 )
53     , mbFauxBold(false)
54     , mpStyleDict( nullptr )
55 {
56     double fScaledFontHeight = rFSP.mfExactHeight;
57 
58     // convert font rotation to radian
59     mfFontRotation = toRadian(rFSP.mnOrientation);
60 
61     // dummy matrix so we can use CGAffineTransformConcat() below
62     CGAffineTransform aMatrix = CGAffineTransformMakeTranslation(0, 0);
63 
64     // handle font stretching if any
65     if( (rFSP.mnWidth != 0) && (rFSP.mnWidth != rFSP.mnHeight) )
66     {
67         mfFontStretch = float(rFSP.mnWidth) / rFSP.mnHeight;
68         aMatrix = CGAffineTransformConcat(aMatrix, CGAffineTransformMakeScale(mfFontStretch, 1.0F));
69     }
70 
71     // create the style object for CoreText font attributes
72     static const CFIndex nMaxDictSize = 16; // TODO: does this really suffice?
73     mpStyleDict = CFDictionaryCreateMutable( nullptr, nMaxDictSize,
74                                              &kCFTypeDictionaryKeyCallBacks,
75                                              &kCFTypeDictionaryValueCallBacks );
76 
77     CFBooleanRef pCFVertBool = rFSP.mbVertical ? kCFBooleanTrue : kCFBooleanFalse;
78     CFDictionarySetValue( mpStyleDict, kCTVerticalFormsAttributeName, pCFVertBool );
79 
80     // fake bold
81     if ( (rFSP.GetWeight() >= WEIGHT_BOLD) &&
82          ((rPFF.GetWeight() < WEIGHT_SEMIBOLD) &&
83           (rPFF.GetWeight() != WEIGHT_DONTKNOW)) )
84     {
85         mbFauxBold = true;
86     }
87 
88     // fake italic
89     if (((rFSP.GetItalic() == ITALIC_NORMAL) ||
90          (rFSP.GetItalic() == ITALIC_OBLIQUE)) &&
91         (rPFF.GetItalic() == ITALIC_NONE))
92     {
93         aMatrix = CGAffineTransformConcat(aMatrix, CGAffineTransformMake(1, 0, toRadian(120), 1, 0, 0));
94     }
95 
96     CTFontDescriptorRef pFontDesc = reinterpret_cast<CTFontDescriptorRef>(rPFF.GetFontId());
97     CTFontRef pNewCTFont = CTFontCreateWithFontDescriptor( pFontDesc, fScaledFontHeight, &aMatrix );
98     CFDictionarySetValue( mpStyleDict, kCTFontAttributeName, pNewCTFont );
99     CFRelease( pNewCTFont);
100 }
101 
~CoreTextStyle()102 CoreTextStyle::~CoreTextStyle()
103 {
104     if( mpStyleDict )
105         CFRelease( mpStyleDict );
106 }
107 
GetFontMetric(ImplFontMetricDataRef const & rxFontMetric)108 void CoreTextStyle::GetFontMetric( ImplFontMetricDataRef const & rxFontMetric )
109 {
110     // get the matching CoreText font handle
111     // TODO: is it worth it to cache the CTFontRef in SetFont() and reuse it here?
112     CTFontRef aCTFontRef = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
113 
114     rxFontMetric->ImplCalcLineSpacing(this);
115 
116     // since ImplFontMetricData::mnWidth is only used for stretching/squeezing fonts
117     // setting this width to the pixel height of the fontsize is good enough
118     // it also makes the calculation of the stretch factor simple
119     rxFontMetric->SetWidth( lrint( CTFontGetSize( aCTFontRef ) * mfFontStretch) );
120 
121     rxFontMetric->SetMinKashida(GetKashidaWidth());
122 }
123 
ImplGetGlyphBoundRect(sal_GlyphId nId,tools::Rectangle & rRect,bool bVertical) const124 bool CoreTextStyle::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool bVertical) const
125 {
126     CGGlyph nCGGlyph = nId;
127     CTFontRef aCTFontRef = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
128 
129     SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.11 kCTFontDefaultOrientation
130     const CTFontOrientation aFontOrientation = kCTFontDefaultOrientation; // TODO: horz/vert
131     SAL_WNODEPRECATED_DECLARATIONS_POP
132     CGRect aCGRect = CTFontGetBoundingRectsForGlyphs(aCTFontRef, aFontOrientation, &nCGGlyph, nullptr, 1);
133 
134     // Apply font rotation to non-vertical glyphs.
135     if (mfFontRotation && !bVertical)
136         aCGRect = CGRectApplyAffineTransform(aCGRect, CGAffineTransformMakeRotation(mfFontRotation));
137 
138     long xMin = floor(aCGRect.origin.x);
139     long yMin = floor(aCGRect.origin.y);
140     long xMax = ceil(aCGRect.origin.x + aCGRect.size.width);
141     long yMax = ceil(aCGRect.origin.y + aCGRect.size.height);
142     rRect = tools::Rectangle(xMin, -yMax, xMax, -yMin);
143     return true;
144 }
145 
146 // callbacks from CTFontCreatePathForGlyph+CGPathApply for GetGlyphOutline()
147 struct GgoData { basegfx::B2DPolygon maPolygon; basegfx::B2DPolyPolygon* mpPolyPoly; };
148 
MyCGPathApplierFunc(void * pData,const CGPathElement * pElement)149 static void MyCGPathApplierFunc( void* pData, const CGPathElement* pElement )
150 {
151     basegfx::B2DPolygon& rPolygon = static_cast<GgoData*>(pData)->maPolygon;
152     const int nPointCount = rPolygon.count();
153 
154     switch( pElement->type )
155     {
156     case kCGPathElementCloseSubpath:
157     case kCGPathElementMoveToPoint:
158         if( nPointCount > 0 )
159         {
160             static_cast<GgoData*>(pData)->mpPolyPoly->append( rPolygon );
161             rPolygon.clear();
162         }
163         // fall through for kCGPathElementMoveToPoint:
164         if( pElement->type != kCGPathElementMoveToPoint )
165         {
166             break;
167         }
168         [[fallthrough]];
169     case kCGPathElementAddLineToPoint:
170         rPolygon.append( basegfx::B2DPoint( +pElement->points[0].x, -pElement->points[0].y ) );
171         break;
172 
173     case kCGPathElementAddCurveToPoint:
174         rPolygon.append( basegfx::B2DPoint( +pElement->points[2].x, -pElement->points[2].y ) );
175         rPolygon.setNextControlPoint( nPointCount - 1,
176                                       basegfx::B2DPoint( pElement->points[0].x,
177                                                          -pElement->points[0].y ) );
178         rPolygon.setPrevControlPoint( nPointCount + 0,
179                                       basegfx::B2DPoint( pElement->points[1].x,
180                                                          -pElement->points[1].y ) );
181         break;
182 
183     case kCGPathElementAddQuadCurveToPoint:
184         {
185             const basegfx::B2DPoint aStartPt = rPolygon.getB2DPoint( nPointCount-1 );
186             const basegfx::B2DPoint aCtrPt1( (aStartPt.getX() + 2 * pElement->points[0].x) / 3.0,
187                                              (aStartPt.getY() - 2 * pElement->points[0].y) / 3.0 );
188             const basegfx::B2DPoint aCtrPt2( (+2 * pElement->points[0].x + pElement->points[1].x) / 3.0,
189                                              (-2 * pElement->points[0].y - pElement->points[1].y) / 3.0 );
190             rPolygon.append( basegfx::B2DPoint( +pElement->points[1].x, -pElement->points[1].y ) );
191             rPolygon.setNextControlPoint( nPointCount-1, aCtrPt1 );
192             rPolygon.setPrevControlPoint( nPointCount+0, aCtrPt2 );
193         }
194         break;
195     }
196 }
197 
GetGlyphOutline(sal_GlyphId nId,basegfx::B2DPolyPolygon & rResult,bool) const198 bool CoreTextStyle::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rResult, bool) const
199 {
200     rResult.clear();
201 
202     CGGlyph nCGGlyph = nId;
203     CTFontRef pCTFont = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
204 
205     SAL_WNODEPRECATED_DECLARATIONS_PUSH
206     const CTFontOrientation aFontOrientation = kCTFontDefaultOrientation;
207     SAL_WNODEPRECATED_DECLARATIONS_POP
208     CGRect aCGRect = CTFontGetBoundingRectsForGlyphs(pCTFont, aFontOrientation, &nCGGlyph, nullptr, 1);
209 
210     if (!CGRectIsNull(aCGRect) && CGRectIsEmpty(aCGRect))
211     {
212         // CTFontCreatePathForGlyph returns NULL for blank glyphs, but we want
213         // to return true for them.
214         return true;
215     }
216 
217     CGPathRef xPath = CTFontCreatePathForGlyph( pCTFont, nCGGlyph, nullptr );
218     if (!xPath)
219     {
220         return false;
221     }
222 
223     GgoData aGgoData;
224     aGgoData.mpPolyPoly = &rResult;
225     CGPathApply( xPath, static_cast<void*>(&aGgoData), MyCGPathApplierFunc );
226 #if 0 // TODO: does OSX ensure that the last polygon is always closed?
227     const CGPathElement aClosingElement = { kCGPathElementCloseSubpath, NULL };
228     MyCGPathApplierFunc( (void*)&aGgoData, &aClosingElement );
229 #endif
230     CFRelease( xPath );
231 
232     return true;
233 }
234 
getFontTable(hb_face_t *,hb_tag_t nTableTag,void * pUserData)235 static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
236 {
237     sal_uLong nLength = 0;
238     unsigned char* pBuffer = nullptr;
239     CoreTextFontFace* pFont = static_cast<CoreTextFontFace*>(pUserData);
240     nLength = pFont->GetFontTable(nTableTag, nullptr);
241     if (nLength > 0)
242     {
243         pBuffer = new unsigned char[nLength];
244         pFont->GetFontTable(nTableTag, pBuffer);
245     }
246 
247     hb_blob_t* pBlob = nullptr;
248     if (pBuffer != nullptr)
249         pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
250                                pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); });
251     return pBlob;
252 }
253 
ImplInitHbFont()254 hb_font_t* CoreTextStyle::ImplInitHbFont()
255 {
256     hb_face_t* pHbFace = hb_face_create_for_tables(getFontTable, const_cast<PhysicalFontFace*>(GetFontFace()), nullptr);
257 
258     return InitHbFont(pHbFace);
259 }
260 
CreateFontInstance(const FontSelectPattern & rFSD) const261 rtl::Reference<LogicalFontInstance> CoreTextFontFace::CreateFontInstance(const FontSelectPattern& rFSD) const
262 {
263     return new CoreTextStyle(*this, rFSD);
264 }
265 
GetFontTable(const char pTagName[5],unsigned char * pResultBuf) const266 int CoreTextFontFace::GetFontTable( const char pTagName[5], unsigned char* pResultBuf ) const
267 {
268     SAL_WARN_IF( pTagName[4]!='\0', "vcl", "CoreTextFontFace::GetFontTable with invalid tagname!" );
269 
270     const CTFontTableTag nTagCode = (pTagName[0]<<24) + (pTagName[1]<<16) + (pTagName[2]<<8) + (pTagName[3]<<0);
271 
272     return GetFontTable(nTagCode, pResultBuf);
273 }
274 
GetFontTable(uint32_t nTagCode,unsigned char * pResultBuf) const275 int CoreTextFontFace::GetFontTable(uint32_t nTagCode, unsigned char* pResultBuf ) const
276 {
277     // get the raw table length
278     CTFontDescriptorRef pFontDesc = reinterpret_cast<CTFontDescriptorRef>( GetFontId());
279     CTFontRef rCTFont = CTFontCreateWithFontDescriptor( pFontDesc, 0.0, nullptr);
280     const uint32_t opts( kCTFontTableOptionNoOptions );
281     CFDataRef pDataRef = CTFontCopyTable( rCTFont, nTagCode, opts);
282     CFRelease( rCTFont);
283     if( !pDataRef)
284         return 0;
285 
286     const CFIndex nByteLength = CFDataGetLength( pDataRef);
287 
288     // get the raw table data if requested
289     if( pResultBuf && (nByteLength > 0))
290     {
291         const CFRange aFullRange = CFRangeMake( 0, nByteLength);
292         CFDataGetBytes( pDataRef, aFullRange, reinterpret_cast<UInt8*>(pResultBuf));
293     }
294 
295     CFRelease( pDataRef);
296 
297     return static_cast<int>(nByteLength);
298 }
299 
DevFontFromCTFontDescriptor(CTFontDescriptorRef pFD,bool * bFontEnabled)300 FontAttributes DevFontFromCTFontDescriptor( CTFontDescriptorRef pFD, bool* bFontEnabled )
301 {
302     // all CoreText fonts are device fonts that can rotate just fine
303     FontAttributes rDFA;
304     rDFA.SetQuality( 0 );
305 
306     // reset the font attributes
307     rDFA.SetFamilyType( FAMILY_DONTKNOW );
308     rDFA.SetPitch( PITCH_VARIABLE );
309     rDFA.SetWidthType( WIDTH_NORMAL );
310     rDFA.SetWeight( WEIGHT_NORMAL );
311     rDFA.SetItalic( ITALIC_NONE );
312     rDFA.SetSymbolFlag( false );
313 
314     // get font name
315 #ifdef MACOSX
316     const OUString aUILang = Application::GetSettings().GetUILanguageTag().getLanguage();
317     CFStringRef pUILang = CFStringCreateWithCharacters( kCFAllocatorDefault,
318                                                         reinterpret_cast<UniChar const *>(aUILang.getStr()), aUILang.getLength() );
319     CFStringRef pLang = nullptr;
320     CFStringRef pFamilyName = static_cast<CFStringRef>(
321             CTFontDescriptorCopyLocalizedAttribute( pFD, kCTFontFamilyNameAttribute, &pLang ));
322 
323     if ( !pLang || ( CFStringCompare( pUILang, pLang, 0 ) != kCFCompareEqualTo ))
324     {
325         if(pFamilyName)
326         {
327             CFRelease( pFamilyName );
328         }
329         pFamilyName = static_cast<CFStringRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontFamilyNameAttribute ));
330     }
331 #else
332     // No "Application" on iOS. And it is unclear whether this code
333     // snippet will actually ever get invoked on iOS anyway. So just
334     // use the old code that uses a non-localized font name.
335     CFStringRef pFamilyName = (CFStringRef)CTFontDescriptorCopyAttribute( pFD, kCTFontFamilyNameAttribute );
336 #endif
337 
338     rDFA.SetFamilyName( GetOUString( pFamilyName ) );
339 
340     // get font style
341     CFStringRef pStyleName = static_cast<CFStringRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontStyleNameAttribute ));
342     rDFA.SetStyleName( GetOUString( pStyleName ) );
343 
344     // get font-enabled status
345     if( bFontEnabled )
346     {
347         int bEnabled = TRUE; // by default (and when we're on macOS < 10.6) it's "enabled"
348         CFNumberRef pEnabled = static_cast<CFNumberRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontEnabledAttribute ));
349         CFNumberGetValue( pEnabled, kCFNumberIntType, &bEnabled );
350         *bFontEnabled = bEnabled;
351     }
352 
353     // get font attributes
354     CFDictionaryRef pAttrDict = static_cast<CFDictionaryRef>(CTFontDescriptorCopyAttribute( pFD, kCTFontTraitsAttribute ));
355 
356     if (bFontEnabled && *bFontEnabled)
357     {
358         // Ignore font formats not supported.
359         int nFormat;
360         CFNumberRef pFormat = static_cast<CFNumberRef>(CTFontDescriptorCopyAttribute(pFD, kCTFontFormatAttribute));
361         CFNumberGetValue(pFormat, kCFNumberIntType, &nFormat);
362         if (nFormat == kCTFontFormatUnrecognized || nFormat == kCTFontFormatPostScript || nFormat == kCTFontFormatBitmap)
363         {
364             SAL_INFO("vcl.fonts", "Ignoring font with unsupported format: " << rDFA.GetFamilyName());
365             *bFontEnabled = false;
366         }
367         CFRelease(pFormat);
368     }
369 
370     // get symbolic trait
371     // TODO: use other traits such as MonoSpace/Condensed/Expanded or Vertical too
372     SInt64 nSymbolTrait = 0;
373     CFNumberRef pSymbolNum = nullptr;
374     if( CFDictionaryGetValueIfPresent( pAttrDict, kCTFontSymbolicTrait, reinterpret_cast<const void**>(&pSymbolNum) ) )
375     {
376         CFNumberGetValue( pSymbolNum, kCFNumberSInt64Type, &nSymbolTrait );
377         rDFA.SetSymbolFlag( (nSymbolTrait & kCTFontClassMaskTrait) == kCTFontSymbolicClass );
378     }
379 
380     // get the font weight
381     double fWeight = 0;
382     CFNumberRef pWeightNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontWeightTrait ));
383     CFNumberGetValue( pWeightNum, kCFNumberDoubleType, &fWeight );
384     int nInt = WEIGHT_NORMAL;
385 
386     // Special case fixes
387 
388     // tdf#67744: Courier Std Medium is always bold. We get a kCTFontWeightTrait of 0.23 which
389     // surely must be wrong.
390     if (rDFA.GetFamilyName() == "Courier Std" &&
391         (rDFA.GetStyleName() == "Medium" || rDFA.GetStyleName() == "Medium Oblique") &&
392         fWeight > 0.2)
393     {
394         fWeight = 0;
395     }
396 
397     // tdf#68889: Ditto for Gill Sans MT Pro. Here I can kinda understand it, maybe the
398     // kCTFontWeightTrait is intended to give a subjective "optical" impression of how the font
399     // looks, and Gill Sans MT Pro Medium is kinda heavy. But with the way LibreOffice uses fonts,
400     // we still should think of it as being "medium" weight.
401     if (rDFA.GetFamilyName() == "Gill Sans MT Pro" &&
402         (rDFA.GetStyleName() == "Medium" || rDFA.GetStyleName() == "Medium Italic") &&
403         fWeight > 0.2)
404     {
405         fWeight = 0;
406     }
407 
408     if( fWeight > 0 )
409     {
410         nInt = rint(WEIGHT_NORMAL + fWeight * ((WEIGHT_BLACK - WEIGHT_NORMAL)/0.68));
411         if( nInt > WEIGHT_BLACK )
412         {
413             nInt = WEIGHT_BLACK;
414         }
415     }
416     else if( fWeight < 0 )
417     {
418         nInt = rint(WEIGHT_NORMAL + fWeight * ((WEIGHT_NORMAL - WEIGHT_THIN)/0.8));
419         if( nInt < WEIGHT_THIN )
420         {
421             nInt = WEIGHT_THIN;
422         }
423     }
424     rDFA.SetWeight( static_cast<FontWeight>(nInt) );
425 
426     // get the font slant
427     double fSlant = 0;
428     CFNumberRef pSlantNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontSlantTrait ));
429     CFNumberGetValue( pSlantNum, kCFNumberDoubleType, &fSlant );
430     if( fSlant >= 0.035 )
431     {
432         rDFA.SetItalic( ITALIC_NORMAL );
433     }
434     // get width trait
435     double fWidth = 0;
436     CFNumberRef pWidthNum = static_cast<CFNumberRef>(CFDictionaryGetValue( pAttrDict, kCTFontWidthTrait ));
437     CFNumberGetValue( pWidthNum, kCFNumberDoubleType, &fWidth );
438     nInt = WIDTH_NORMAL;
439 
440     if( fWidth > 0 )
441     {
442         nInt = rint( WIDTH_NORMAL + fWidth * ((WIDTH_ULTRA_EXPANDED - WIDTH_NORMAL)/0.4));
443         if( nInt > WIDTH_ULTRA_EXPANDED )
444         {
445             nInt = WIDTH_ULTRA_EXPANDED;
446         }
447     }
448     else if( fWidth < 0 )
449     {
450         nInt = rint( WIDTH_NORMAL + fWidth * ((WIDTH_NORMAL - WIDTH_ULTRA_CONDENSED)/0.5));
451         if( nInt < WIDTH_ULTRA_CONDENSED )
452         {
453             nInt = WIDTH_ULTRA_CONDENSED;
454         }
455     }
456     rDFA.SetWidthType( static_cast<FontWidth>(nInt) );
457 
458     // release the attribute dict that we had copied
459     CFRelease( pAttrDict );
460 
461     // TODO? also use the HEAD table if available to get more attributes
462 //  CFDataRef CTFontCopyTable( CTFontRef, kCTFontTableHead, /*kCTFontTableOptionNoOptions*/kCTFontTableOptionExcludeSynthetic );
463 
464     return rDFA;
465 }
466 
fontEnumCallBack(const void * pValue,void * pContext)467 static void fontEnumCallBack( const void* pValue, void* pContext )
468 {
469     CTFontDescriptorRef pFD = static_cast<CTFontDescriptorRef>(pValue);
470 
471     bool bFontEnabled;
472     FontAttributes rDFA = DevFontFromCTFontDescriptor( pFD, &bFontEnabled );
473 
474     if( bFontEnabled)
475     {
476         const sal_IntPtr nFontId = reinterpret_cast<sal_IntPtr>(pValue);
477         rtl::Reference<CoreTextFontFace> pFontData = new CoreTextFontFace( rDFA, nFontId );
478         SystemFontList* pFontList = static_cast<SystemFontList*>(pContext);
479         pFontList->AddFont( pFontData.get() );
480     }
481 }
482 
SystemFontList()483 SystemFontList::SystemFontList()
484   : mpCTFontCollection( nullptr )
485   , mpCTFontArray( nullptr )
486 {}
487 
~SystemFontList()488 SystemFontList::~SystemFontList()
489 {
490     maFontContainer.clear();
491 
492     if( mpCTFontArray )
493     {
494         CFRelease( mpCTFontArray );
495     }
496     if( mpCTFontCollection )
497     {
498         CFRelease( mpCTFontCollection );
499     }
500 }
501 
AddFont(CoreTextFontFace * pFontData)502 void SystemFontList::AddFont( CoreTextFontFace* pFontData )
503 {
504     sal_IntPtr nFontId = pFontData->GetFontId();
505     maFontContainer[ nFontId ] = pFontData;
506 }
507 
AnnounceFonts(PhysicalFontCollection & rFontCollection) const508 void SystemFontList::AnnounceFonts( PhysicalFontCollection& rFontCollection ) const
509 {
510     for(const auto& rEntry : maFontContainer )
511     {
512         rFontCollection.Add( rEntry.second.get() );
513     }
514 }
515 
GetFontDataFromId(sal_IntPtr nFontId) const516 CoreTextFontFace* SystemFontList::GetFontDataFromId( sal_IntPtr nFontId ) const
517 {
518     auto it = maFontContainer.find( nFontId );
519     if( it == maFontContainer.end() )
520     {
521         return nullptr;
522     }
523     return (*it).second.get();
524 }
525 
Init()526 bool SystemFontList::Init()
527 {
528     // enumerate available system fonts
529     static const int nMaxDictEntries = 8;
530     CFMutableDictionaryRef pCFDict = CFDictionaryCreateMutable( nullptr,
531                                                                 nMaxDictEntries,
532                                                                 &kCFTypeDictionaryKeyCallBacks,
533                                                                 &kCFTypeDictionaryValueCallBacks );
534 
535     CFDictionaryAddValue( pCFDict, kCTFontCollectionRemoveDuplicatesOption, kCFBooleanTrue );
536     mpCTFontCollection = CTFontCollectionCreateFromAvailableFonts( pCFDict );
537     CFRelease( pCFDict );
538     mpCTFontArray = CTFontCollectionCreateMatchingFontDescriptors( mpCTFontCollection );
539 
540     const int nFontCount = CFArrayGetCount( mpCTFontArray );
541     const CFRange aFullRange = CFRangeMake( 0, nFontCount );
542     CFArrayApplyFunction( mpCTFontArray, aFullRange, fontEnumCallBack, this );
543 
544     return true;
545 }
546 
GetCoretextFontList()547 SystemFontList* GetCoretextFontList()
548 {
549     SystemFontList* pList = new SystemFontList();
550     if( !pList->Init() )
551     {
552         delete pList;
553         return nullptr;
554     }
555 
556     return pList;
557 }
558 
559 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
560