1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 "impfont.hxx"
21 #include "outfont.hxx"
22 #include "PhysicalFontCollection.hxx"
23 #include "sallayout.hxx"
26 #include "osx/salinst.h"
27 #include "osx/saldata.hxx"
28 // kCTForegroundColorFromContextAttributeName is available on 10.5, but it is "hidden"
29 #ifndef kCTForegroundColorFromContextAttributeName
30 extern const CFStringRef kCTForegroundColorFromContextAttributeName
;
33 #include "quartz/salgdi.h"
34 #include "quartz/utils.h"
35 #include "ctfonts.hxx"
37 #include <vcl/settings.hxx>
39 #include "basegfx/polygon/b2dpolygon.hxx"
40 #include "basegfx/matrix/b2dhommatrix.hxx"
42 inline double toRadian(int nDegree
)
44 return nDegree
* (M_PI
/ 1800.0);
47 CoreTextStyle::CoreTextStyle( const FontSelectPattern
& rFSD
)
48 : mpFontData( static_cast<CoreTextFontData
const *>(rFSD
.mpFontData
) )
49 , mfFontStretch( 1.0 )
50 , mfFontRotation( 0.0 )
53 const FontSelectPattern
* const pReqFont
= &rFSD
;
55 double fScaledFontHeight
= pReqFont
->mfExactHeight
;
57 // convert font rotation to radian
58 mfFontRotation
= toRadian(pReqFont
->mnOrientation
);
60 // dummy matrix so we can use CGAffineTransformConcat() below
61 CGAffineTransform aMatrix
= CGAffineTransformMakeTranslation(0, 0);
63 // handle font stretching if any
64 if( (pReqFont
->mnWidth
!= 0) && (pReqFont
->mnWidth
!= pReqFont
->mnHeight
) )
66 mfFontStretch
= (float)pReqFont
->mnWidth
/ pReqFont
->mnHeight
;
67 aMatrix
= CGAffineTransformConcat(aMatrix
, CGAffineTransformMakeScale(mfFontStretch
, 1.0F
));
70 // create the style object for CoreText font attributes
71 static const CFIndex nMaxDictSize
= 16; // TODO: does this really suffice?
72 mpStyleDict
= CFDictionaryCreateMutable( NULL
, nMaxDictSize
,
73 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
75 CFBooleanRef pCFVertBool
= pReqFont
->mbVertical
? kCFBooleanTrue
: kCFBooleanFalse
;
76 CFDictionarySetValue( mpStyleDict
, kCTVerticalFormsAttributeName
, pCFVertBool
);
79 if ( (pReqFont
->GetWeight() >= WEIGHT_BOLD
) &&
80 ((mpFontData
->GetWeight() < WEIGHT_SEMIBOLD
) &&
81 (mpFontData
->GetWeight() != WEIGHT_DONTKNOW
)) )
83 int nStroke
= -lrint((3.5F
* pReqFont
->GetWeight()) / mpFontData
->GetWeight());
84 CFNumberRef rStroke
= CFNumberCreate(NULL
, kCFNumberSInt32Type
, &nStroke
);
85 CFDictionarySetValue(mpStyleDict
, kCTStrokeWidthAttributeName
, rStroke
);
89 if (((pReqFont
->GetSlant() == ITALIC_NORMAL
) || (pReqFont
->GetSlant() == ITALIC_OBLIQUE
))
90 && (mpFontData
->GetSlant() == ITALIC_NONE
))
92 aMatrix
= CGAffineTransformConcat(aMatrix
, CGAffineTransformMake(1, 0, toRadian(120), 1, 0, 0));
95 CTFontDescriptorRef pFontDesc
= reinterpret_cast<CTFontDescriptorRef
>(mpFontData
->GetFontId());
96 CTFontRef pNewCTFont
= CTFontCreateWithFontDescriptor( pFontDesc
, fScaledFontHeight
, &aMatrix
);
97 CFDictionarySetValue( mpStyleDict
, kCTFontAttributeName
, pNewCTFont
);
98 CFRelease( pNewCTFont
);
100 // allow delayed setting the font color, i.e. after the text layout
101 CFDictionarySetValue( mpStyleDict
, kCTForegroundColorFromContextAttributeName
, kCFBooleanTrue
);
103 #if 0 // LastResort is implicit in CoreText's font cascading
104 const void* aGFBDescriptors
[] = { CTFontDescriptorCreateWithNameAndSize( CFSTR("LastResort"), 0) }; // TODO: use the full GFB list
105 const int nGfbCount
= sizeof(aGFBDescriptors
) / sizeof(*aGFBDescriptors
);
106 CFArrayRef pGfbList
= CFArrayCreate( NULL
, aGFBDescriptors
, nGfbCount
, &kCFTypeArrayCallBacks
);
107 CFDictionaryAddValue( mpStyleDict
, kCTFontCascadeListAttribute
, pGfbList
);
108 CFRelease( pGfbList
);
112 CoreTextStyle::~CoreTextStyle()
115 CFRelease( mpStyleDict
);
118 void CoreTextStyle::GetFontMetric( ImplFontMetricData
& rMetric
) const
120 // get the matching CoreText font handle
121 // TODO: is it worth it to cache the CTFontRef in SetFont() and reuse it here?
122 CTFontRef aCTFontRef
= static_cast<CTFontRef
>(CFDictionaryGetValue( mpStyleDict
, kCTFontAttributeName
));
124 const CGFloat fAscent
= CTFontGetAscent( aCTFontRef
);
125 const CGFloat fCapHeight
= CTFontGetCapHeight( aCTFontRef
);
126 rMetric
.mnAscent
= lrint( fAscent
);
127 rMetric
.mnDescent
= lrint( CTFontGetDescent( aCTFontRef
));
128 rMetric
.mnExtLeading
= lrint( CTFontGetLeading( aCTFontRef
));
129 rMetric
.mnIntLeading
= lrint( fAscent
- fCapHeight
);
131 // since ImplFontMetricData::mnWidth is only used for stretching/squeezing fonts
132 // setting this width to the pixel height of the fontsize is good enough
133 // it also makes the calculation of the stretch factor simple
134 rMetric
.mnWidth
= lrint( CTFontGetSize( aCTFontRef
) * mfFontStretch
);
136 // all CoreText fonts are scalable
137 rMetric
.mbScalableFont
= true;
138 rMetric
.mbKernableFont
= true;
141 bool CoreTextStyle::GetGlyphBoundRect( sal_GlyphId aGlyphId
, Rectangle
& rRect
) const
143 CGGlyph nCGGlyph
= aGlyphId
& GF_IDXMASK
;
144 // XXX: this is broken if the glyph came from fallback font
145 CTFontRef aCTFontRef
= static_cast<CTFontRef
>(CFDictionaryGetValue( mpStyleDict
, kCTFontAttributeName
));
147 const CTFontOrientation aFontOrientation
= kCTFontDefaultOrientation
; // TODO: horz/vert
148 const CGRect aCGRect
= CTFontGetBoundingRectsForGlyphs( aCTFontRef
, aFontOrientation
, &nCGGlyph
, NULL
, 1 );
150 rRect
.Left() = lrint( aCGRect
.origin
.x
);
151 rRect
.Top() = lrint( aCGRect
.origin
.y
);
152 rRect
.Right() = lrint( aCGRect
.origin
.x
+ aCGRect
.size
.width
);
153 rRect
.Bottom() = lrint( aCGRect
.origin
.y
+ aCGRect
.size
.height
);
157 // callbacks from CTFontCreatePathForGlyph+CGPathApply for GetGlyphOutline()
158 struct GgoData
{ basegfx::B2DPolygon maPolygon
; basegfx::B2DPolyPolygon
* mpPolyPoly
; };
160 static void MyCGPathApplierFunc( void* pData
, const CGPathElement
* pElement
)
162 basegfx::B2DPolygon
& rPolygon
= static_cast<GgoData
*>(pData
)->maPolygon
;
163 const int nPointCount
= rPolygon
.count();
165 switch( pElement
->type
)
167 case kCGPathElementCloseSubpath
:
168 case kCGPathElementMoveToPoint
:
169 if( nPointCount
> 0 ) {
170 static_cast<GgoData
*>(pData
)->mpPolyPoly
->append( rPolygon
);
173 // fall through for kCGPathElementMoveToPoint:
174 if( pElement
->type
!= kCGPathElementMoveToPoint
)
176 case kCGPathElementAddLineToPoint
:
177 rPolygon
.append( basegfx::B2DPoint( +pElement
->points
[0].x
, -pElement
->points
[0].y
) );
179 case kCGPathElementAddCurveToPoint
:
180 rPolygon
.append( basegfx::B2DPoint( +pElement
->points
[2].x
, -pElement
->points
[2].y
) );
181 rPolygon
.setNextControlPoint( nPointCount
-1, basegfx::B2DPoint( pElement
->points
[0].x
, -pElement
->points
[0].y
) );
182 rPolygon
.setPrevControlPoint( nPointCount
+0, basegfx::B2DPoint( pElement
->points
[1].x
, -pElement
->points
[1].y
) );
184 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
);
197 bool CoreTextStyle::GetGlyphOutline( sal_GlyphId aGlyphId
, basegfx::B2DPolyPolygon
& rResult
) const
201 CGGlyph nCGGlyph
= aGlyphId
& GF_IDXMASK
;
202 // XXX: this is broken if the glyph came from fallback font
203 CTFontRef pCTFont
= static_cast<CTFontRef
>(CFDictionaryGetValue( mpStyleDict
, kCTFontAttributeName
));
204 CGPathRef xPath
= CTFontCreatePathForGlyph( pCTFont
, nCGGlyph
, NULL
);
210 aGgoData
.mpPolyPoly
= &rResult
;
211 CGPathApply( xPath
, (void*)&aGgoData
, MyCGPathApplierFunc
);
212 #if 0 // TODO: does OSX ensure that the last polygon is always closed?
213 const CGPathElement aClosingElement
= { kCGPathElementCloseSubpath
, NULL
};
214 MyCGPathApplierFunc( (void*)&aGgoData
, &aClosingElement
);
221 PhysicalFontFace
* CoreTextFontData::Clone() const
223 return new CoreTextFontData( *this);
226 ImplFontEntry
* CoreTextFontData::CreateFontInstance( /*const*/ FontSelectPattern
& rFSD
) const
228 return new ImplFontEntry( rFSD
);
231 int CoreTextFontData::GetFontTable( const char pTagName
[5], unsigned char* pResultBuf
) const
233 DBG_ASSERT( pTagName
[4]=='\0', "CoreTextFontData::GetFontTable with invalid tagname!\n" );
235 const CTFontTableTag nTagCode
= (pTagName
[0]<<24) + (pTagName
[1]<<16) + (pTagName
[2]<<8) + (pTagName
[3]<<0);
237 // get the raw table length
238 CTFontDescriptorRef pFontDesc
= reinterpret_cast<CTFontDescriptorRef
>( GetFontId());
239 CTFontRef rCTFont
= CTFontCreateWithFontDescriptor( pFontDesc
, 0.0, NULL
);
240 const uint32_t opts( kCTFontTableOptionNoOptions
);
241 CFDataRef pDataRef
= CTFontCopyTable( rCTFont
, nTagCode
, opts
);
246 const CFIndex nByteLength
= CFDataGetLength( pDataRef
);
248 // get the raw table data if requested
249 if( pResultBuf
&& (nByteLength
> 0))
251 const CFRange aFullRange
= CFRangeMake( 0, nByteLength
);
252 CFDataGetBytes( pDataRef
, aFullRange
, (UInt8
*)pResultBuf
);
255 CFRelease( pDataRef
);
257 return (int)nByteLength
;
260 ImplDevFontAttributes
DevFontFromCTFontDescriptor( CTFontDescriptorRef pFD
, bool* bFontEnabled
)
262 // all CoreText fonts are device fonts that can rotate just fine
263 ImplDevFontAttributes rDFA
;
264 rDFA
.mbOrientation
= true;
265 rDFA
.mbDevice
= true;
268 // reset the font attributes
269 rDFA
.SetFamilyType( FAMILY_DONTKNOW
);
270 rDFA
.SetPitch( PITCH_VARIABLE
);
271 rDFA
.SetWidthType( WIDTH_NORMAL
);
272 rDFA
.SetWeight( WEIGHT_NORMAL
);
273 rDFA
.SetItalic( ITALIC_NONE
);
274 rDFA
.SetSymbolFlag( false );
276 // all scalable fonts on this platform are subsettable
277 rDFA
.mbEmbeddable
= false;
278 rDFA
.mbSubsettable
= true;
282 const OUString aUILang
= Application::GetSettings().GetUILanguageTag().getLanguage();
283 CFStringRef pUILang
= CFStringCreateWithCharacters( kCFAllocatorDefault
, aUILang
.getStr(), aUILang
.getLength() );
284 CFStringRef pLang
= NULL
;
285 CFStringRef pFamilyName
= static_cast<CFStringRef
>(CTFontDescriptorCopyLocalizedAttribute( pFD
, kCTFontFamilyNameAttribute
, &pLang
));
286 if ( !pLang
|| ( CFStringCompare( pUILang
, pLang
, 0 ) != kCFCompareEqualTo
))
290 CFRelease( pFamilyName
);
292 pFamilyName
= static_cast<CFStringRef
>(CTFontDescriptorCopyAttribute( pFD
, kCTFontFamilyNameAttribute
));
295 // No "Application" on iOS. And it is unclear whether this code
296 // snippet will actually ever get invoked on iOS anyway. So just
297 // use the old code that uses a non-localized font name.
298 CFStringRef pFamilyName
= (CFStringRef
)CTFontDescriptorCopyAttribute( pFD
, kCTFontFamilyNameAttribute
);
301 rDFA
.SetFamilyName( GetOUString( pFamilyName
) );
304 CFStringRef pStyleName
= static_cast<CFStringRef
>(CTFontDescriptorCopyAttribute( pFD
, kCTFontStyleNameAttribute
));
305 rDFA
.SetStyleName( GetOUString( pStyleName
) );
307 // get font-enabled status
309 int bEnabled
= TRUE
; // by default (and when we're on OS X < 10.6) it's "enabled"
310 CFNumberRef pEnabled
= static_cast<CFNumberRef
>(CTFontDescriptorCopyAttribute( pFD
, kCTFontEnabledAttribute
));
311 CFNumberGetValue( pEnabled
, kCFNumberIntType
, &bEnabled
);
312 *bFontEnabled
= bEnabled
;
315 // get font attributes
316 CFDictionaryRef pAttrDict
= static_cast<CFDictionaryRef
>(CTFontDescriptorCopyAttribute( pFD
, kCTFontTraitsAttribute
));
318 // get symbolic trait
319 // TODO: use other traits such as MonoSpace/Condensed/Expanded or Vertical too
320 SInt64 nSymbolTrait
= 0;
321 CFNumberRef pSymbolNum
= NULL
;
322 if( CFDictionaryGetValueIfPresent( pAttrDict
, kCTFontSymbolicTrait
, reinterpret_cast<const void**>(&pSymbolNum
) ) ) {
323 CFNumberGetValue( pSymbolNum
, kCFNumberSInt64Type
, &nSymbolTrait
);
324 rDFA
.SetSymbolFlag( ((nSymbolTrait
& kCTFontClassMaskTrait
) == kCTFontSymbolicClass
) );
327 // get the font weight
329 CFNumberRef pWeightNum
= static_cast<CFNumberRef
>(CFDictionaryGetValue( pAttrDict
, kCTFontWeightTrait
));
330 CFNumberGetValue( pWeightNum
, kCFNumberDoubleType
, &fWeight
);
331 int nInt
= WEIGHT_NORMAL
;
333 nInt
= rint(WEIGHT_NORMAL
+ fWeight
* ((WEIGHT_BLACK
- WEIGHT_NORMAL
)/0.68));
334 if( nInt
> WEIGHT_BLACK
)
336 } else if( fWeight
< 0 ) {
337 nInt
= rint(WEIGHT_NORMAL
+ fWeight
* ((WEIGHT_NORMAL
- WEIGHT_THIN
)/0.9));
338 if( nInt
< WEIGHT_THIN
)
341 rDFA
.SetWeight( (FontWeight
)nInt
);
343 // get the font slant
345 CFNumberRef pSlantNum
= static_cast<CFNumberRef
>(CFDictionaryGetValue( pAttrDict
, kCTFontSlantTrait
));
346 CFNumberGetValue( pSlantNum
, kCFNumberDoubleType
, &fSlant
);
347 if( fSlant
>= 0.035 )
348 rDFA
.SetItalic( ITALIC_NORMAL
);
352 CFNumberRef pWidthNum
= static_cast<CFNumberRef
>(CFDictionaryGetValue( pAttrDict
, kCTFontWidthTrait
));
353 CFNumberGetValue( pWidthNum
, kCFNumberDoubleType
, &fWidth
);
356 nInt
= rint( WIDTH_NORMAL
+ fWidth
* ((WIDTH_ULTRA_EXPANDED
- WIDTH_NORMAL
)/0.4));
357 if( nInt
> WIDTH_ULTRA_EXPANDED
)
358 nInt
= WIDTH_ULTRA_EXPANDED
;
359 } else if( fWidth
< 0 ) {
360 nInt
= rint( WIDTH_NORMAL
+ fWidth
* ((WIDTH_NORMAL
- WIDTH_ULTRA_CONDENSED
)/0.5));
361 if( nInt
< WIDTH_ULTRA_CONDENSED
)
362 nInt
= WIDTH_ULTRA_CONDENSED
;
364 rDFA
.SetWidthType( (FontWidth
)nInt
);
366 // release the attribute dict that we had copied
367 CFRelease( pAttrDict
);
369 // TODO? also use the HEAD table if available to get more attributes
370 // CFDataRef CTFontCopyTable( CTFontRef, kCTFontTableHead, /*kCTFontTableOptionNoOptions*/kCTFontTableOptionExcludeSynthetic );
375 static void CTFontEnumCallBack( const void* pValue
, void* pContext
)
377 CTFontDescriptorRef pFD
= static_cast<CTFontDescriptorRef
>(pValue
);
380 ImplDevFontAttributes rDFA
= DevFontFromCTFontDescriptor( pFD
, &bFontEnabled
);
384 const sal_IntPtr nFontId
= reinterpret_cast<sal_IntPtr
>(pValue
);
385 CoreTextFontData
* pFontData
= new CoreTextFontData( rDFA
, nFontId
);
386 SystemFontList
* pFontList
= static_cast<SystemFontList
*>(pContext
);
387 pFontList
->AddFont( pFontData
);
391 SystemFontList::SystemFontList()
392 : mpCTFontCollection( NULL
)
393 , mpCTFontArray( NULL
)
396 SystemFontList::~SystemFontList()
398 CTFontContainer::const_iterator it
= maFontContainer
.begin();
399 for(; it
!= maFontContainer
.end(); ++it
)
401 maFontContainer
.clear();
404 CFRelease( mpCTFontArray
);
405 if( mpCTFontCollection
)
406 CFRelease( mpCTFontCollection
);
409 void SystemFontList::AddFont( CoreTextFontData
* pFontData
)
411 sal_IntPtr nFontId
= pFontData
->GetFontId();
412 maFontContainer
[ nFontId
] = pFontData
;
415 void SystemFontList::AnnounceFonts( PhysicalFontCollection
& rFontCollection
) const
417 CTFontContainer::const_iterator it
= maFontContainer
.begin();
418 for(; it
!= maFontContainer
.end(); ++it
)
419 rFontCollection
.Add( (*it
).second
->Clone() );
422 CoreTextFontData
* SystemFontList::GetFontDataFromId( sal_IntPtr nFontId
) const
424 CTFontContainer::const_iterator it
= maFontContainer
.find( nFontId
);
425 if( it
== maFontContainer
.end() )
430 bool SystemFontList::Init()
432 // enumerate available system fonts
433 static const int nMaxDictEntries
= 8;
434 CFMutableDictionaryRef pCFDict
= CFDictionaryCreateMutable( NULL
,
435 nMaxDictEntries
, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
436 CFDictionaryAddValue( pCFDict
, kCTFontCollectionRemoveDuplicatesOption
, kCFBooleanTrue
);
437 mpCTFontCollection
= CTFontCollectionCreateFromAvailableFonts( pCFDict
);
438 CFRelease( pCFDict
);
440 mpCTFontArray
= CTFontCollectionCreateMatchingFontDescriptors( mpCTFontCollection
);
441 const int nFontCount
= CFArrayGetCount( mpCTFontArray
);
442 const CFRange aFullRange
= CFRangeMake( 0, nFontCount
);
443 CFArrayApplyFunction( mpCTFontArray
, aFullRange
, CTFontEnumCallBack
, this );
448 SystemFontList
* GetCoretextFontList()
450 SystemFontList
* pList
= new SystemFontList();
451 if( !pList
->Init() ) {
459 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */