Use CTFontCopyDefaultCascadeListForLanguages for font fallback if available

On Mac OS 10.8 and iOS 6.0 we can use CTFontCopyDefaultCascadeListForLanguages
to get the list of fallback fonts, which is preferable to reading the plist
file from the filesystem. The latter doesn't work (is not allowed) on iOS
in any case, so for iOS < 6.0 we use a static list of fallback fonts.

Change-Id: Ibb5e1b4dedd6bfb936f66b53a20f7ced83536ed7
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@digia.com>
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
bb10
Tor Arne Vestbø 2013-02-11 17:31:12 +01:00 committed by The Qt Project
parent 183e04a439
commit 74f9664f95
2 changed files with 130 additions and 69 deletions

View File

@ -91,22 +91,6 @@ static const char *languageForWritingSystem[] = {
};
enum { LanguageCount = sizeof(languageForWritingSystem) / sizeof(const char *) };
QFont::StyleHint styleHintFromNSString(NSString *style)
{
if ([style isEqual: @"sans-serif"])
return QFont::SansSerif;
else if ([style isEqual: @"monospace"])
return QFont::Monospace;
else if ([style isEqual: @"cursive"])
return QFont::Cursive;
else if ([style isEqual: @"serif"])
return QFont::Serif;
else if ([style isEqual: @"fantasy"])
return QFont::Fantasy;
else // if ([style isEqual: @"default"])
return QFont::AnyStyle;
}
static NSInteger languageMapSort(id obj1, id obj2, void *context)
{
NSArray *map1 = (NSArray *) obj1;
@ -188,27 +172,6 @@ QCoreTextFontDatabase::~QCoreTextFontDatabase()
{
}
static QString familyNameFromPostScriptName(QHash<QString, QString> &psNameToFamily,
NSString *psName)
{
QString name = QCFString::toQString(psName);
if (psNameToFamily.contains(name))
return psNameToFamily[name];
else {
// Some of the font name in DefaultFontFallbacks.plist are hidden fonts like AquaHiraKaku,
// their family name begins with a dot, like ".AquaHiraKaku" or ".Apple Symbols Fallback",
// the only way (I've found) to get it are actually creating a CTFont with them. We only
// need to do it once though.
QCFType<CTFontRef> font = CTFontCreateWithName((CFStringRef) psName, 12.0, NULL);
if (font) {
QCFString family = CTFontCopyFamilyName(font);
psNameToFamily[name] = family;
return family;
}
return name;
}
}
void QCoreTextFontDatabase::populateFontDatabase()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
@ -223,7 +186,6 @@ void QCoreTextFontDatabase::populateFontDatabase()
QString foundryName = QLatin1String("CoreText");
const int numFonts = CFArrayGetCount(fonts);
QHash<QString, QString> psNameToFamily;
for (int i = 0; i < numFonts; ++i) {
CTFontDescriptorRef font = (CTFontDescriptorRef) CFArrayGetValueAtIndex(fonts, i);
QCFString familyName = (CFStringRef) CTFontDescriptorCopyLocalizedAttribute(font, kCTFontFamilyNameAttribute, NULL);
@ -289,35 +251,14 @@ void QCoreTextFontDatabase::populateFontDatabase()
QPlatformFontDatabase::registerFont(familyName, styleName, foundryName, weight, style, stretch,
true /* antialiased */, true /* scalable */,
pixelSize, fixedPitch, writingSystems, (void *) font);
// We need to map back and forth between PostScript-names and family-names for fallback list construction
CFStringRef psName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontNameAttribute);
// we need PostScript Name to family name mapping for fallback list construction
psNameToFamily[QCFString::toQString((NSString *) psName)] = familyName;
familyNameToPsName[familyName] = QCFString::toQString((NSString *) psName);
CFRelease(psName);
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *languages = [defaults stringArrayForKey: @"AppleLanguages"];
NSDictionary *fallbackDict = [NSDictionary dictionaryWithContentsOfFile: @"/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreText.framework/Resources/DefaultFontFallbacks.plist"];
for (NSString *style in [fallbackDict allKeys]) {
NSArray *list = [fallbackDict valueForKey: style];
QFont::StyleHint styleHint = styleHintFromNSString(style);
QStringList fallbackList;
for (id item in list) {
// sort the array based on system language preferences
if ([item isKindOfClass: [NSArray class]]) {
NSArray *langs = [(NSArray *) item sortedArrayUsingFunction: languageMapSort
context: languages];
for (NSArray *map in langs)
fallbackList.append(familyNameFromPostScriptName(psNameToFamily, [map objectAtIndex: 1]));
}
else if ([item isKindOfClass: [NSString class]])
fallbackList.append(familyNameFromPostScriptName(psNameToFamily, item));
}
fallbackLists[styleHint] = fallbackList;
}
[pool release];
}
@ -376,18 +317,137 @@ QFontEngine *QCoreTextFontDatabase::fontEngine(const QByteArray &fontData, qreal
return fontEngine;
}
QFont::StyleHint styleHintFromNSString(NSString *style)
{
if ([style isEqual: @"sans-serif"])
return QFont::SansSerif;
else if ([style isEqual: @"monospace"])
return QFont::Monospace;
else if ([style isEqual: @"cursive"])
return QFont::Cursive;
else if ([style isEqual: @"serif"])
return QFont::Serif;
else if ([style isEqual: @"fantasy"])
return QFont::Fantasy;
else // if ([style isEqual: @"default"])
return QFont::AnyStyle;
}
static QString familyNameFromPostScriptName(QHash<QString, QString> &psNameToFamily,
NSString *psName)
{
QString name = QCFString::toQString(psName);
if (psNameToFamily.contains(name))
return psNameToFamily[name];
else {
// Some of the font name in DefaultFontFallbacks.plist are hidden fonts like AquaHiraKaku,
// their family name begins with a dot, like ".AquaHiraKaku" or ".Apple Symbols Fallback",
// the only way (I've found) to get it are actually creating a CTFont with them. We only
// need to do it once though.
QCFType<CTFontRef> font = CTFontCreateWithName((CFStringRef) psName, 12.0, NULL);
if (font) {
QCFString family = CTFontCopyFamilyName(font);
psNameToFamily[name] = family;
return family;
}
return name;
}
}
QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
{
Q_UNUSED(family);
Q_UNUSED(style);
Q_UNUSED(script);
static bool didPopulateFallbackList = false;
if (!didPopulateFallbackList) {
const_cast<QCoreTextFontDatabase*>(this)->populateFontDatabase();
didPopulateFallbackList = true;
static QHash<QString, QStringList> fallbackLists;
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000
// CTFontCopyDefaultCascadeListForLanguages is available in the SDK
#if (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 1080) \
|| (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 60000)
// But we have to feature check at runtime
if (&CTFontCopyDefaultCascadeListForLanguages)
#endif
{
if (fallbackLists.contains(family))
return fallbackLists.value(family);
if (!familyNameToPsName.contains(family))
const_cast<QCoreTextFontDatabase*>(this)->populateFontDatabase();
QCFType<CTFontRef> font = CTFontCreateWithName(QCFString(familyNameToPsName[family]), 12.0, NULL);
if (font) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *languages = [defaults stringArrayForKey: @"AppleLanguages"];
QCFType<CFArrayRef> cascadeList = (CFArrayRef) CTFontCopyDefaultCascadeListForLanguages(font, (CFArrayRef) languages);
if (cascadeList) {
QStringList fallbackList;
const int numCascades = CFArrayGetCount(cascadeList);
for (int i = 0; i < numCascades; ++i) {
CTFontDescriptorRef fontFallback = (CTFontDescriptorRef) CFArrayGetValueAtIndex(cascadeList, i);
QCFString fallbackFamilyName = (CFStringRef) CTFontDescriptorCopyLocalizedAttribute(fontFallback, kCTFontFamilyNameAttribute, NULL);
fallbackList.append(QCFString::toQString(fallbackFamilyName));
}
fallbackLists[family] = fallbackList;
}
}
if (fallbackLists.contains(family))
return fallbackLists.value(family);
}
return fallbackLists[styleHint];
#endif
// We were not able to find a fallback for the specific family,
// so we fall back to the stylehint.
static const QString styleLookupKey = QString::fromLatin1(".QFontStyleHint_%1");
static bool didPopulateStyleFallbacks = false;
if (!didPopulateStyleFallbacks) {
#if !defined(Q_OS_IOS)
// Ensure we have the psNameToFamily mapping set up
const_cast<QCoreTextFontDatabase*>(this)->populateFontDatabase();
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *languages = [defaults stringArrayForKey: @"AppleLanguages"];
NSDictionary *fallbackDict = [NSDictionary dictionaryWithContentsOfFile: @"/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreText.framework/Resources/DefaultFontFallbacks.plist"];
for (NSString *style in [fallbackDict allKeys]) {
NSArray *list = [fallbackDict valueForKey: style];
QFont::StyleHint fallbackStyleHint = styleHintFromNSString(style);
QStringList fallbackList;
for (id item in list) {
// sort the array based on system language preferences
if ([item isKindOfClass: [NSArray class]]) {
NSArray *langs = [(NSArray *) item sortedArrayUsingFunction: languageMapSort
context: languages];
for (NSArray *map in langs)
fallbackList.append(familyNameFromPostScriptName(psNameToFamily, [map objectAtIndex: 1]));
}
else if ([item isKindOfClass: [NSString class]])
fallbackList.append(familyNameFromPostScriptName(psNameToFamily, item));
}
fallbackLists[styleLookupKey.arg(fallbackStyleHint)] = fallbackList;
}
#else
QStringList staticFallbackList;
staticFallbackList << QString::fromLatin1("Helvetica,Apple Color Emoji,Geeza Pro,Arial Hebrew,Thonburi,Kailasa"
"Hiragino Kaku Gothic ProN,.Heiti J,Apple SD Gothic Neo,.Heiti K,Heiti SC,Heiti TC"
"Bangla Sangam MN,Devanagari Sangam MN,Gujarati Sangam MN,Gurmukhi MN,Kannada Sangam MN"
"Malayalam Sangam MN,Oriya Sangam MN,Sinhala Sangam MN,Tamil Sangam MN,Telugu Sangam MN"
"Euphemia UCAS,.PhoneFallback").split(QLatin1String(","));
for (int i = QFont::Helvetica; i <= QFont::Fantasy; ++i)
fallbackLists[styleLookupKey.arg(i)] = staticFallbackList;
#endif
didPopulateStyleFallbacks = true;
}
Q_ASSERT(!fallbackLists.isEmpty());
return fallbackLists[styleLookupKey.arg(styleHint)];
}
#ifndef Q_OS_IOS

View File

@ -64,7 +64,8 @@ public:
private:
mutable QString defaultFontName;
QHash<QFont::StyleHint, QStringList> fallbackLists;
mutable QHash<QString, QString> psNameToFamily;
mutable QHash<QString, QString> familyNameToPsName;
};
QT_END_NAMESPACE