1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <basegfx/polygon/b2dpolygon.hxx>
24 #include <basegfx/matrix/b2dhommatrix.hxx>
26 #include <vcl/settings.hxx>
29 #include <quartz/ctfonts.hxx>
30 #include <impfont.hxx>
32 #include <osx/saldata.hxx>
33 #include <osx/salinst.h>
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>
44 static double toRadian(int nDegree
)
46 return nDegree
* (M_PI
/ 1800.0);
49 CoreTextStyle::CoreTextStyle(const PhysicalFontFace
& rPFF
, const FontSelectPattern
& rFSP
)
50 : LogicalFontInstance(rPFF
, rFSP
)
51 , mfFontStretch( 1.0 )
52 , mfFontRotation( 0.0 )
54 , mpStyleDict( nullptr )
56 double fScaledFontHeight
= rFSP
.mfExactHeight
;
58 // convert font rotation to radian
59 mfFontRotation
= toRadian(rFSP
.mnOrientation
);
61 // dummy matrix so we can use CGAffineTransformConcat() below
62 CGAffineTransform aMatrix
= CGAffineTransformMakeTranslation(0, 0);
64 // handle font stretching if any
65 if( (rFSP
.mnWidth
!= 0) && (rFSP
.mnWidth
!= rFSP
.mnHeight
) )
67 mfFontStretch
= float(rFSP
.mnWidth
) / rFSP
.mnHeight
;
68 aMatrix
= CGAffineTransformConcat(aMatrix
, CGAffineTransformMakeScale(mfFontStretch
, 1.0F
));
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
);
77 CFBooleanRef pCFVertBool
= rFSP
.mbVertical
? kCFBooleanTrue
: kCFBooleanFalse
;
78 CFDictionarySetValue( mpStyleDict
, kCTVerticalFormsAttributeName
, pCFVertBool
);
81 if ( (rFSP
.GetWeight() >= WEIGHT_BOLD
) &&
82 ((rPFF
.GetWeight() < WEIGHT_SEMIBOLD
) &&
83 (rPFF
.GetWeight() != WEIGHT_DONTKNOW
)) )
89 if (((rFSP
.GetItalic() == ITALIC_NORMAL
) ||
90 (rFSP
.GetItalic() == ITALIC_OBLIQUE
)) &&
91 (rPFF
.GetItalic() == ITALIC_NONE
))
93 aMatrix
= CGAffineTransformConcat(aMatrix
, CGAffineTransformMake(1, 0, toRadian(120), 1, 0, 0));
96 CTFontDescriptorRef pFontDesc
= reinterpret_cast<CTFontDescriptorRef
>(rPFF
.GetFontId());
97 CTFontRef pNewCTFont
= CTFontCreateWithFontDescriptor( pFontDesc
, fScaledFontHeight
, &aMatrix
);
98 CFDictionarySetValue( mpStyleDict
, kCTFontAttributeName
, pNewCTFont
);
99 CFRelease( pNewCTFont
);
102 CoreTextStyle::~CoreTextStyle()
105 CFRelease( mpStyleDict
);
108 void CoreTextStyle::GetFontMetric( ImplFontMetricDataRef
const & rxFontMetric
)
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
));
114 rxFontMetric
->ImplCalcLineSpacing(this);
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
) );
121 rxFontMetric
->SetMinKashida(GetKashidaWidth());
124 bool CoreTextStyle::ImplGetGlyphBoundRect(sal_GlyphId nId
, tools::Rectangle
& rRect
, bool bVertical
) const
126 CGGlyph nCGGlyph
= nId
;
127 CTFontRef aCTFontRef
= static_cast<CTFontRef
>(CFDictionaryGetValue( mpStyleDict
, kCTFontAttributeName
));
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);
134 // Apply font rotation to non-vertical glyphs.
135 if (mfFontRotation
&& !bVertical
)
136 aCGRect
= CGRectApplyAffineTransform(aCGRect
, CGAffineTransformMakeRotation(mfFontRotation
));
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
);
146 // callbacks from CTFontCreatePathForGlyph+CGPathApply for GetGlyphOutline()
147 struct GgoData
{ basegfx::B2DPolygon maPolygon
; basegfx::B2DPolyPolygon
* mpPolyPoly
; };
149 static void MyCGPathApplierFunc( void* pData
, const CGPathElement
* pElement
)
151 basegfx::B2DPolygon
& rPolygon
= static_cast<GgoData
*>(pData
)->maPolygon
;
152 const int nPointCount
= rPolygon
.count();
154 switch( pElement
->type
)
156 case kCGPathElementCloseSubpath
:
157 case kCGPathElementMoveToPoint
:
158 if( nPointCount
> 0 )
160 static_cast<GgoData
*>(pData
)->mpPolyPoly
->append( rPolygon
);
163 // fall through for kCGPathElementMoveToPoint:
164 if( pElement
->type
!= kCGPathElementMoveToPoint
)
169 case kCGPathElementAddLineToPoint
:
170 rPolygon
.append( basegfx::B2DPoint( +pElement
->points
[0].x
, -pElement
->points
[0].y
) );
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
) );
183 case kCGPathElementAddQuadCurveToPoint
:
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
);
198 bool CoreTextStyle::GetGlyphOutline(sal_GlyphId nId
, basegfx::B2DPolyPolygon
& rResult
, bool) const
202 CGGlyph nCGGlyph
= nId
;
203 CTFontRef pCTFont
= static_cast<CTFontRef
>(CFDictionaryGetValue( mpStyleDict
, kCTFontAttributeName
));
205 SAL_WNODEPRECATED_DECLARATIONS_PUSH
206 const CTFontOrientation aFontOrientation
= kCTFontDefaultOrientation
;
207 SAL_WNODEPRECATED_DECLARATIONS_POP
208 CGRect aCGRect
= CTFontGetBoundingRectsForGlyphs(pCTFont
, aFontOrientation
, &nCGGlyph
, nullptr, 1);
210 if (!CGRectIsNull(aCGRect
) && CGRectIsEmpty(aCGRect
))
212 // CTFontCreatePathForGlyph returns NULL for blank glyphs, but we want
213 // to return true for them.
217 CGPathRef xPath
= CTFontCreatePathForGlyph( pCTFont
, nCGGlyph
, nullptr );
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
);
235 static hb_blob_t
* getFontTable(hb_face_t
* /*face*/, hb_tag_t nTableTag
, void* pUserData
)
237 sal_uLong nLength
= 0;
238 unsigned char* pBuffer
= nullptr;
239 CoreTextFontFace
* pFont
= static_cast<CoreTextFontFace
*>(pUserData
);
240 nLength
= pFont
->GetFontTable(nTableTag
, nullptr);
243 pBuffer
= new unsigned char[nLength
];
244 pFont
->GetFontTable(nTableTag
, pBuffer
);
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
); });
254 hb_font_t
* CoreTextStyle::ImplInitHbFont()
256 hb_face_t
* pHbFace
= hb_face_create_for_tables(getFontTable
, const_cast<PhysicalFontFace
*>(GetFontFace()), nullptr);
258 return InitHbFont(pHbFace
);
261 rtl::Reference
<LogicalFontInstance
> CoreTextFontFace::CreateFontInstance(const FontSelectPattern
& rFSD
) const
263 return new CoreTextStyle(*this, rFSD
);
266 int CoreTextFontFace::GetFontTable( const char pTagName
[5], unsigned char* pResultBuf
) const
268 SAL_WARN_IF( pTagName
[4]!='\0', "vcl", "CoreTextFontFace::GetFontTable with invalid tagname!" );
270 const CTFontTableTag nTagCode
= (pTagName
[0]<<24) + (pTagName
[1]<<16) + (pTagName
[2]<<8) + (pTagName
[3]<<0);
272 return GetFontTable(nTagCode
, pResultBuf
);
275 int CoreTextFontFace::GetFontTable(uint32_t nTagCode
, unsigned char* pResultBuf
) const
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
);
286 const CFIndex nByteLength
= CFDataGetLength( pDataRef
);
288 // get the raw table data if requested
289 if( pResultBuf
&& (nByteLength
> 0))
291 const CFRange aFullRange
= CFRangeMake( 0, nByteLength
);
292 CFDataGetBytes( pDataRef
, aFullRange
, reinterpret_cast<UInt8
*>(pResultBuf
));
295 CFRelease( pDataRef
);
297 return static_cast<int>(nByteLength
);
300 FontAttributes
DevFontFromCTFontDescriptor( CTFontDescriptorRef pFD
, bool* bFontEnabled
)
302 // all CoreText fonts are device fonts that can rotate just fine
304 rDFA
.SetQuality( 0 );
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 );
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
));
323 if ( !pLang
|| ( CFStringCompare( pUILang
, pLang
, 0 ) != kCFCompareEqualTo
))
327 CFRelease( pFamilyName
);
329 pFamilyName
= static_cast<CFStringRef
>(CTFontDescriptorCopyAttribute( pFD
, kCTFontFamilyNameAttribute
));
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
);
338 rDFA
.SetFamilyName( GetOUString( pFamilyName
) );
341 CFStringRef pStyleName
= static_cast<CFStringRef
>(CTFontDescriptorCopyAttribute( pFD
, kCTFontStyleNameAttribute
));
342 rDFA
.SetStyleName( GetOUString( pStyleName
) );
344 // get font-enabled status
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
;
353 // get font attributes
354 CFDictionaryRef pAttrDict
= static_cast<CFDictionaryRef
>(CTFontDescriptorCopyAttribute( pFD
, kCTFontTraitsAttribute
));
356 if (bFontEnabled
&& *bFontEnabled
)
358 // Ignore font formats not supported.
360 CFNumberRef pFormat
= static_cast<CFNumberRef
>(CTFontDescriptorCopyAttribute(pFD
, kCTFontFormatAttribute
));
361 CFNumberGetValue(pFormat
, kCFNumberIntType
, &nFormat
);
362 if (nFormat
== kCTFontFormatUnrecognized
|| nFormat
== kCTFontFormatPostScript
|| nFormat
== kCTFontFormatBitmap
)
364 SAL_INFO("vcl.fonts", "Ignoring font with unsupported format: " << rDFA
.GetFamilyName());
365 *bFontEnabled
= false;
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
) ) )
376 CFNumberGetValue( pSymbolNum
, kCFNumberSInt64Type
, &nSymbolTrait
);
377 rDFA
.SetSymbolFlag( (nSymbolTrait
& kCTFontClassMaskTrait
) == kCTFontSymbolicClass
);
380 // get the font weight
382 CFNumberRef pWeightNum
= static_cast<CFNumberRef
>(CFDictionaryGetValue( pAttrDict
, kCTFontWeightTrait
));
383 CFNumberGetValue( pWeightNum
, kCFNumberDoubleType
, &fWeight
);
384 int nInt
= WEIGHT_NORMAL
;
386 // Special case fixes
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") &&
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") &&
410 nInt
= rint(WEIGHT_NORMAL
+ fWeight
* ((WEIGHT_BLACK
- WEIGHT_NORMAL
)/0.68));
411 if( nInt
> WEIGHT_BLACK
)
416 else if( fWeight
< 0 )
418 nInt
= rint(WEIGHT_NORMAL
+ fWeight
* ((WEIGHT_NORMAL
- WEIGHT_THIN
)/0.8));
419 if( nInt
< WEIGHT_THIN
)
424 rDFA
.SetWeight( static_cast<FontWeight
>(nInt
) );
426 // get the font slant
428 CFNumberRef pSlantNum
= static_cast<CFNumberRef
>(CFDictionaryGetValue( pAttrDict
, kCTFontSlantTrait
));
429 CFNumberGetValue( pSlantNum
, kCFNumberDoubleType
, &fSlant
);
430 if( fSlant
>= 0.035 )
432 rDFA
.SetItalic( ITALIC_NORMAL
);
436 CFNumberRef pWidthNum
= static_cast<CFNumberRef
>(CFDictionaryGetValue( pAttrDict
, kCTFontWidthTrait
));
437 CFNumberGetValue( pWidthNum
, kCFNumberDoubleType
, &fWidth
);
442 nInt
= rint( WIDTH_NORMAL
+ fWidth
* ((WIDTH_ULTRA_EXPANDED
- WIDTH_NORMAL
)/0.4));
443 if( nInt
> WIDTH_ULTRA_EXPANDED
)
445 nInt
= WIDTH_ULTRA_EXPANDED
;
448 else if( fWidth
< 0 )
450 nInt
= rint( WIDTH_NORMAL
+ fWidth
* ((WIDTH_NORMAL
- WIDTH_ULTRA_CONDENSED
)/0.5));
451 if( nInt
< WIDTH_ULTRA_CONDENSED
)
453 nInt
= WIDTH_ULTRA_CONDENSED
;
456 rDFA
.SetWidthType( static_cast<FontWidth
>(nInt
) );
458 // release the attribute dict that we had copied
459 CFRelease( pAttrDict
);
461 // TODO? also use the HEAD table if available to get more attributes
462 // CFDataRef CTFontCopyTable( CTFontRef, kCTFontTableHead, /*kCTFontTableOptionNoOptions*/kCTFontTableOptionExcludeSynthetic );
467 static void fontEnumCallBack( const void* pValue
, void* pContext
)
469 CTFontDescriptorRef pFD
= static_cast<CTFontDescriptorRef
>(pValue
);
472 FontAttributes rDFA
= DevFontFromCTFontDescriptor( pFD
, &bFontEnabled
);
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() );
483 SystemFontList::SystemFontList()
484 : mpCTFontCollection( nullptr )
485 , mpCTFontArray( nullptr )
488 SystemFontList::~SystemFontList()
490 maFontContainer
.clear();
494 CFRelease( mpCTFontArray
);
496 if( mpCTFontCollection
)
498 CFRelease( mpCTFontCollection
);
502 void SystemFontList::AddFont( CoreTextFontFace
* pFontData
)
504 sal_IntPtr nFontId
= pFontData
->GetFontId();
505 maFontContainer
[ nFontId
] = pFontData
;
508 void SystemFontList::AnnounceFonts( PhysicalFontCollection
& rFontCollection
) const
510 for(const auto& rEntry
: maFontContainer
)
512 rFontCollection
.Add( rEntry
.second
.get() );
516 CoreTextFontFace
* SystemFontList::GetFontDataFromId( sal_IntPtr nFontId
) const
518 auto it
= maFontContainer
.find( nFontId
);
519 if( it
== maFontContainer
.end() )
523 return (*it
).second
.get();
526 bool SystemFontList::Init()
528 // enumerate available system fonts
529 static const int nMaxDictEntries
= 8;
530 CFMutableDictionaryRef pCFDict
= CFDictionaryCreateMutable( nullptr,
532 &kCFTypeDictionaryKeyCallBacks
,
533 &kCFTypeDictionaryValueCallBacks
);
535 CFDictionaryAddValue( pCFDict
, kCTFontCollectionRemoveDuplicatesOption
, kCFBooleanTrue
);
536 mpCTFontCollection
= CTFontCollectionCreateFromAvailableFonts( pCFDict
);
537 CFRelease( pCFDict
);
538 mpCTFontArray
= CTFontCollectionCreateMatchingFontDescriptors( mpCTFontCollection
);
540 const int nFontCount
= CFArrayGetCount( mpCTFontArray
);
541 const CFRange aFullRange
= CFRangeMake( 0, nFontCount
);
542 CFArrayApplyFunction( mpCTFontArray
, aFullRange
, fontEnumCallBack
, this );
547 SystemFontList
* GetCoretextFontList()
549 SystemFontList
* pList
= new SystemFontList();
559 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */