Allow selecting fonts with irregular style names
authorJiang Jiang <jiang.jiang@nokia.com>
Sat, 4 Jun 2011 12:47:22 +0000 (14:47 +0200)
committerQt Continuous Integration System <qt-info@nokia.com>
Thu, 9 Jun 2011 13:07:13 +0000 (15:07 +0200)
Fonts like "Helvetica Neue UltraLight" or "Skia Regular Black
Condensed" can't be selected in Qt because either they don't
report correct numeric values for weight/stretch/etc. or these
values are not mapped from QFont enums in a linear way. Thus
we provide a shortcut to select these fonts with PostScript
name or full name without resorting to family name matching in
QFontDatabase (these fonts are not registered in font database
anyway). After this, we can simply use:

    QFont font("Helvetica Neue");
    font.setStyleName("UltraLight");

to select these fonts. QCoreTextFontEngineMulti matched like
this can be created directly from the CTFontRef instance
instead of creating from the font name, making this process
faster.

The commit also cleaned up the font loading process in Mac
font database a bit, moving the code for family matching into
a separate function.

Add QFontInfo::styleName() and QRawFont::styleName() to access
the resolved style name for a font.

Task-number: QTBUG-19366
Change-Id: Iad07768c02ed06cc8d6b7395dec554384f410506
Reviewed-on: http://codereview.qt.nokia.com/333
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Jiang Jiang <jiang.jiang@nokia.com>
12 files changed:
src/gui/text/qfont.cpp
src/gui/text/qfont.h
src/gui/text/qfont_p.h
src/gui/text/qfontdatabase_mac.cpp
src/gui/text/qfontdatabase_x11.cpp
src/gui/text/qfontengine_coretext.mm
src/gui/text/qfontengine_coretext_p.h
src/gui/text/qfontengine_ft.cpp
src/gui/text/qfontinfo.h
src/gui/text/qrawfont.cpp
src/gui/text/qrawfont.h
tests/auto/qfont/tst_qfont.cpp

index 90dd029..d4c81b9 100644 (file)
@@ -348,6 +348,9 @@ void QFontPrivate::resolve(uint mask, const QFontPrivate *other)
     if (! (mask & QFont::FamilyResolved))
         request.family = other->request.family;
 
+    if (! (mask & QFont::StyleNameResolved))
+        request.styleName = other->request.styleName;
+
     if (! (mask & QFont::SizeResolved)) {
         request.pointSize = other->request.pointSize;
         request.pixelSize = other->request.pixelSize;
@@ -897,6 +900,38 @@ void QFont::setFamily(const QString &family)
 }
 
 /*!
+    \since 4.8
+
+    Returns the requested font style name, it will be used to match the
+    font with irregular styles (that can't be normalized in other style
+    properties). It depends on system font support, thus only works for
+    Mac OS X and X11 so far. On Windows irregular styles will be added
+    as separate font families so there is no need for this.
+
+    \sa setFamily() setStyle()
+*/
+QString QFont::styleName() const
+{
+    return d->request.styleName;
+}
+
+/*!
+    \since 4.8
+
+    Sets the style name of the font. When set, other style properties
+    like \a style() and \a weight() will be ignored for font matching.
+
+    \sa styleName()
+*/
+void QFont::setStyleName(const QString &styleName)
+{
+    detach();
+
+    d->request.styleName = styleName;
+    resolve_mask |= QFont::StyleNameResolved;
+}
+
+/*!
     Returns the point size of the font. Returns -1 if the font size
     was specified in pixels.
 
@@ -2509,6 +2544,21 @@ QString QFontInfo::family() const
 }
 
 /*!
+    \since 4.8
+
+    Returns the style name of the matched window system font on
+    system that supports it.
+
+    \sa QFont::styleName()
+*/
+QString QFontInfo::styleName() const
+{
+    QFontEngine *engine = d->engineForScript(QUnicodeTables::Common);
+    Q_ASSERT(engine != 0);
+    return engine->fontDef.styleName;
+}
+
+/*!
     Returns the point size of the matched window system font.
 
     \sa pointSizeF() QFont::pointSize()
index 7636ecf..14f290c 100644 (file)
@@ -156,7 +156,8 @@ public:
         LetterSpacingResolved       = 0x2000,
         WordSpacingResolved         = 0x4000,
         HintingPreferenceResolved   = 0x8000,
-        AllPropertiesResolved       = 0xffff
+        StyleNameResolved           = 0x10000,
+        AllPropertiesResolved       = 0x1ffff
     };
 
     QFont();
@@ -168,6 +169,9 @@ public:
     QString family() const;
     void setFamily(const QString &);
 
+    QString styleName() const;
+    void setStyleName(const QString &);
+
     int pointSize() const;
     void setPointSize(int);
     qreal pointSizeF() const;
index d36518e..8eeae6f 100644 (file)
@@ -80,6 +80,7 @@ struct QFontDef
     }
 
     QString family;
+    QString styleName;
 
 #ifdef Q_WS_X11
     QString addStyle;
index 6fdaf06..724dbf6 100644 (file)
@@ -249,6 +249,63 @@ static inline float weightToFloat(unsigned int weight)
     return (weight - 50) / 100.0;
 }
 
+static QFontEngine *loadFromDatabase(const QFontDef &req, const QFontPrivate *d)
+{
+#if defined(QT_MAC_USE_COCOA)
+    QCFString fontName = NULL;
+#else
+    ATSFontFamilyRef familyRef = 0;
+    ATSFontRef fontRef = 0;
+#endif
+
+    QStringList family_list = familyList(req);
+
+    const char *stylehint = styleHint(req);
+    if (stylehint)
+        family_list << QLatin1String(stylehint);
+
+    // add QFont::defaultFamily() to the list, for compatibility with previous versions
+    family_list << QApplication::font().defaultFamily();
+
+    QMutexLocker locker(fontDatabaseMutex());
+    QFontDatabasePrivate *db = privateDb();
+    if (!db->count)
+        initializeDb();
+    for (int i = 0; i < family_list.size(); ++i) {
+        for (int k = 0; k < db->count; ++k) {
+            if (db->families[k]->name.compare(family_list.at(i), Qt::CaseInsensitive) == 0) {
+                QByteArray family_name = db->families[k]->name.toUtf8();
+#if defined(QT_MAC_USE_COCOA)
+                QCFType<CTFontRef> ctFont = CTFontCreateWithName(QCFString(db->families[k]->name), 12, NULL);
+                if (ctFont) {
+                    fontName = CTFontCopyFullName(ctFont);
+                    goto found;
+                }
+#else
+                familyRef = ATSFontFamilyFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault);
+                if (familyRef) {
+                    fontRef = ATSFontFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault);
+                    goto found;
+                }
+#endif
+            }
+        }
+    }
+found:
+#ifdef QT_MAC_USE_COCOA
+    if (fontName)
+        return new QCoreTextFontEngineMulti(fontName, req, d->kerning);
+#else
+    if (familyRef) {
+        QCFString actualName;
+        if (ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &actualName) == noErr)
+            req.family = actualName;
+        return new QFontEngineMacMulti(familyRef, req, fontDef, d->kerning);
+    }
+#endif
+    return NULL;
+}
+
 void QFontDatabase::load(const QFontPrivate *d, int script)
 {
     // sanity checks
@@ -289,69 +346,38 @@ void QFontDatabase::load(const QFontPrivate *d, int script)
         return; // the font info and fontdef should already be filled
     }
 
-    //find the font
-    QStringList family_list = familyList(req);
-
-    const char *stylehint = styleHint(req);
-    if (stylehint)
-        family_list << QLatin1String(stylehint);
-
-    // add QFont::defaultFamily() to the list, for compatibility with
-    // previous versions
-    family_list << QApplication::font().defaultFamily();
-
+    QFontEngine *engine = NULL;
 #if defined(QT_MAC_USE_COCOA)
-    QCFString fontName = NULL, familyName = NULL;
-#else
-    ATSFontFamilyRef familyRef = 0;
-    ATSFontRef fontRef = 0;
-#endif
-
-    QMutexLocker locker(fontDatabaseMutex());
-    QFontDatabasePrivate *db = privateDb();
-    if (!db->count)
-        initializeDb();
-    for(int i = 0; i < family_list.size(); ++i) {
-        for (int k = 0; k < db->count; ++k) {
-            if (db->families[k]->name.compare(family_list.at(i), Qt::CaseInsensitive) == 0) {
-                QByteArray family_name = db->families[k]->name.toUtf8();
-#if defined(QT_MAC_USE_COCOA)
-                QCFType<CTFontRef> ctFont = CTFontCreateWithName(QCFString(db->families[k]->name), 12, NULL);
-                if (ctFont) {
-                    fontName = CTFontCopyFullName(ctFont);
-                    familyName = CTFontCopyFamilyName(ctFont);
-                    goto FamilyFound;
-                }
-#else
-                familyRef = ATSFontFamilyFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault);
-                if (familyRef) {
-                    fontRef = ATSFontFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault);
-                    goto FamilyFound;
-                }
-#endif
+    // Shortcut to get the font directly without going through the font database
+    if (!req.family.isEmpty() && !req.styleName.isEmpty()) {
+        QCFString expectedFamily = QCFString(req.family);
+        QCFString expectedStyle = QCFString(req.styleName);
+
+        QCFType<CFMutableDictionaryRef> attributes = CFDictionaryCreateMutable(NULL, 0,
+            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+        CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, expectedFamily);
+        CFDictionaryAddValue(attributes, kCTFontStyleNameAttribute, expectedStyle);
+
+        QCFType<CTFontDescriptorRef> descriptor = CTFontDescriptorCreateWithAttributes(attributes);
+        CGAffineTransform transform = qt_transform_from_fontdef(req);
+        QCFType<CTFontRef> ctFont = CTFontCreateWithFontDescriptor(descriptor, req.pixelSize, &transform);
+        if (ctFont) {
+            QCFString familyName = CTFontCopyFamilyName(ctFont);
+            // Only accept the font if the family name is exactly the same as we specified
+            if (CFEqual(expectedFamily, familyName)) {
+                engine = new QCoreTextFontEngineMulti(ctFont, req, d->kerning);
             }
         }
     }
-FamilyFound:
-    //fill in the engine's font definition
-    QFontDef fontDef = d->request; //copy..
-    if(fontDef.pointSize < 0)
-        fontDef.pointSize = qt_mac_pointsize(fontDef, d->dpi);
-    else
-        fontDef.pixelSize = qt_mac_pixelsize(fontDef, d->dpi);
-
-#ifdef QT_MAC_USE_COCOA
-    fontDef.family = familyName;
-    QFontEngine *engine = new QCoreTextFontEngineMulti(fontName, fontDef, d->kerning);
-#else
-    QCFString actualName;
-    if (ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &actualName) == noErr)
-        fontDef.family = actualName;
-    QFontEngine *engine = new QFontEngineMacMulti(familyRef, fontRef, fontDef, d->kerning);
 #endif
-    d->engineData->engine = engine;
-    engine->ref.ref(); //a ref for the engineData->engine
-    QFontCache::instance()->insertEngine(key, engine);
+    if (!engine)
+        engine = loadFromDatabase(req, d);
+
+    if (engine) {
+        d->engineData->engine = engine;
+        engine->ref.ref();
+        QFontCache::instance()->insertEngine(key, engine);
+    }
 }
 
 static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt)
index a5fdcb5..754334c 100644 (file)
@@ -1452,6 +1452,35 @@ static const char *styleHint(const QFontDef &request)
 
 void qt_addPatternProps(FcPattern *pattern, int screen, int script, const QFontDef &request)
 {
+    double size_value = qMax(qreal(1.), request.pixelSize);
+    FcPatternDel(pattern, FC_PIXEL_SIZE);
+    FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size_value);
+
+    if (X11->display && QX11Info::appDepth(screen) <= 8) {
+        FcPatternDel(pattern, FC_ANTIALIAS);
+        // can't do antialiasing on 8bpp
+        FcPatternAddBool(pattern, FC_ANTIALIAS, false);
+    } else if (request.styleStrategy & (QFont::PreferAntialias|QFont::NoAntialias)) {
+        FcPatternDel(pattern, FC_ANTIALIAS);
+        FcPatternAddBool(pattern, FC_ANTIALIAS,
+                         !(request.styleStrategy & QFont::NoAntialias));
+    }
+
+    if (script != QUnicodeTables::Common && *specialLanguages[script] != '\0') {
+        Q_ASSERT(script < QUnicodeTables::ScriptCount);
+        FcLangSet *ls = FcLangSetCreate();
+        FcLangSetAdd(ls, (const FcChar8*)specialLanguages[script]);
+        FcPatternDel(pattern, FC_LANG);
+        FcPatternAddLangSet(pattern, FC_LANG, ls);
+        FcLangSetDestroy(ls);
+    }
+
+    if (!request.styleName.isEmpty()) {
+        QByteArray cs = request.styleName.toUtf8();
+        FcPatternAddString(pattern, FC_STYLE, (const FcChar8 *) cs.constData());
+        return;
+    }
+
     int weight_value = FC_WEIGHT_BLACK;
     if (request.weight == 0)
         weight_value = FC_WEIGHT_MEDIUM;
@@ -1474,34 +1503,11 @@ void qt_addPatternProps(FcPattern *pattern, int screen, int script, const QFontD
     FcPatternDel(pattern, FC_SLANT);
     FcPatternAddInteger(pattern, FC_SLANT, slant_value);
 
-    double size_value = qMax(qreal(1.), request.pixelSize);
-    FcPatternDel(pattern, FC_PIXEL_SIZE);
-    FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size_value);
-
     int stretch = request.stretch;
     if (!stretch)
         stretch = 100;
     FcPatternDel(pattern, FC_WIDTH);
     FcPatternAddInteger(pattern, FC_WIDTH, stretch);
-
-    if (X11->display && QX11Info::appDepth(screen) <= 8) {
-        FcPatternDel(pattern, FC_ANTIALIAS);
-        // can't do antialiasing on 8bpp
-        FcPatternAddBool(pattern, FC_ANTIALIAS, false);
-    } else if (request.styleStrategy & (QFont::PreferAntialias|QFont::NoAntialias)) {
-        FcPatternDel(pattern, FC_ANTIALIAS);
-        FcPatternAddBool(pattern, FC_ANTIALIAS,
-                         !(request.styleStrategy & QFont::NoAntialias));
-    }
-
-    if (script != QUnicodeTables::Common && *specialLanguages[script] != '\0') {
-        Q_ASSERT(script < QUnicodeTables::ScriptCount);
-        FcLangSet *ls = FcLangSetCreate();
-        FcLangSetAdd(ls, (const FcChar8*)specialLanguages[script]);
-        FcPatternDel(pattern, FC_LANG);
-        FcPatternAddLangSet(pattern, FC_LANG, ls);
-        FcLangSetDestroy(ls);
-    }
 }
 
 static bool preferScalable(const QFontDef &request)
@@ -2049,7 +2055,8 @@ static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt)
             return;
 
         FcPatternDel(pattern, FC_FILE);
-        FcPatternAddString(pattern, FC_FILE, (const FcChar8 *)fnt->fileName.toUtf8().constData());
+        QByteArray cs = fnt->fileName.toUtf8();
+        FcPatternAddString(pattern, FC_FILE, (const FcChar8 *) cs.constData());
 
         FcChar8 *fam = 0, *familylang = 0;
         int i, n = 0;
@@ -2135,7 +2142,8 @@ QString QFontDatabase::resolveFontFamilyAlias(const QString &family)
     if (!pattern)
         return family;
 
-    FcPatternAddString(pattern, FC_FAMILY, (const FcChar8 *) family.toUtf8().data());
+    QByteArray cs = family.toUtf8();
+    FcPatternAddString(pattern, FC_FAMILY, (const FcChar8 *) cs.constData());
     FcConfigSubstitute(0, pattern, FcMatchPattern);
     FcDefaultSubstitute(pattern);
 
index eedcc69..a68a155 100644 (file)
@@ -116,17 +116,11 @@ QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(const QCFString &name, const
     init(kerning);
 }
 
-QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(CGFontRef cgFontRef, const QFontDef &fontDef, bool kerning)
+QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(CTFontRef ctFontRef, const QFontDef &fontDef, bool kerning)
     : QFontEngineMulti(0)
 {
     this->fontDef = fontDef;
-
-    transform = CGAffineTransformIdentity;
-    if (fontDef.stretch != 100) {
-        transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1);
-    }
-
-    ctfont = CTFontCreateWithGraphicsFont(cgFontRef, fontDef.pixelSize, &transform, NULL);
+    ctfont = (CTFontRef) CFRetain(ctFontRef);
     init(kerning);
 }
 
@@ -149,6 +143,9 @@ void QCoreTextFontEngineMulti::init(bool kerning)
     }
 
     QCoreTextFontEngine *fe = new QCoreTextFontEngine(ctfont, fontDef);
+    fontDef.family = fe->fontDef.family;
+    fontDef.styleName = fe->fontDef.styleName;
+    transform = fe->transform;
     fe->ref.ref();
     engines.append(fe);
 }
@@ -405,7 +402,7 @@ void QCoreTextFontEngineMulti::loadEngine(int)
 
 extern int qt_antialiasing_threshold; // from qapplication.cpp
 
-static inline CGAffineTransform transformFromFontDef(const QFontDef &fontDef)
+CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef)
 {
     CGAffineTransform transform = CGAffineTransformIdentity;
     if (fontDef.stretch != 100)
@@ -416,7 +413,7 @@ static inline CGAffineTransform transformFromFontDef(const QFontDef &fontDef)
 QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def)
 {
     fontDef = def;
-    transform = transformFromFontDef(fontDef);
+    transform = qt_transform_from_fontdef(fontDef);
     ctfont = font;
     CFRetain(ctfont);
     cgFont = CTFontCopyGraphicsFont(font, NULL);
@@ -426,7 +423,7 @@ QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def)
 QCoreTextFontEngine::QCoreTextFontEngine(CGFontRef font, const QFontDef &def)
 {
     fontDef = def;
-    transform = transformFromFontDef(fontDef);
+    transform = qt_transform_from_fontdef(fontDef);
     cgFont = font;
     // Keep reference count balanced
     CFRetain(cgFont);
@@ -464,6 +461,9 @@ void QCoreTextFontEngine::init()
     QCFString family = CTFontCopyFamilyName(ctfont);
     fontDef.family = family;
 
+    QCFString styleName = (CFStringRef) CTFontCopyAttribute(ctfont, kCTFontStyleNameAttribute);
+    fontDef.styleName = styleName;
+
     synthesisFlags = 0;
     CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont);
     if (traits & kCTFontItalicTrait)
index 98d3b50..3ca8a0a 100644 (file)
@@ -113,7 +113,7 @@ class QCoreTextFontEngineMulti : public QFontEngineMulti
 {
 public:
     QCoreTextFontEngineMulti(const QCFString &name, const QFontDef &fontDef, bool kerning);
-    QCoreTextFontEngineMulti(CGFontRef cgFontRef, const QFontDef &fontDef, bool kerning);
+    QCoreTextFontEngineMulti(CTFontRef ctFontRef, const QFontDef &fontDef, bool kerning);
     ~QCoreTextFontEngineMulti();
 
     virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs,
@@ -141,6 +141,8 @@ private:
     friend class QFontDialogPrivate;
 };
 
+CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef);
+
 #endif// !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
 
 #endif // QFONTENGINE_CORETEXT_P_H
index 39abbd6..9a5d9d6 100644 (file)
@@ -758,6 +758,8 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format,
     }
 #endif
 
+    fontDef.styleName = QString::fromUtf8(face->style_name);
+
     unlockFace();
 
     fsType = freetype->fsType();
index 1238cba..37a724e 100644 (file)
@@ -61,6 +61,7 @@ public:
     QFontInfo &operator=(const QFontInfo &);
 
     QString family() const;
+    QString styleName() const;
     int pixelSize() const;
     int pointSize() const;
     qreal pointSizeF() const;
index 481180e..71762df 100644 (file)
@@ -404,6 +404,19 @@ QString QRawFont::familyName() const
 }
 
 /*!
+   Returns the style name of this QRawFont.
+
+   \sa QFont::styleName()
+*/
+QString QRawFont::styleName() const
+{
+    if (!isValid())
+        return QString();
+
+    return d->fontEngine->fontDef.styleName;
+}
+
+/*!
    Returns the style of this QRawFont.
 
    \sa QFont::style()
index 3857da3..aca33af 100644 (file)
@@ -84,6 +84,7 @@ public:
     bool operator==(const QRawFont &other) const;
 
     QString familyName() const;
+    QString styleName() const;
 
     QFont::Style style() const;
     int weight() const;
index cfafa78..711ffc0 100644 (file)
@@ -77,6 +77,7 @@ private slots:
     void insertAndRemoveSubstitutions();
     void serializeSpacing();
     void lastResortFont();
+    void styleName();
 };
 
 // Testing get/set functions
@@ -612,5 +613,17 @@ void tst_QFont::lastResortFont()
     QVERIFY(!font.lastResortFont().isEmpty());
 }
 
+void tst_QFont::styleName()
+{
+#if !defined(Q_WS_MAC)
+    QSKIP("Only tested on Mac", SkipAll);
+#else
+    QFont font("Helvetica Neue");
+    font.setStyleName("UltraLight");
+
+    QCOMPARE(QFontInfo(font).styleName(), QString("UltraLight"));
+#endif
+}
+
 QTEST_MAIN(tst_QFont)
 #include "tst_qfont.moc"