[Development] font selection and weight/style support on OS X: (possible regression from Qt 4 to Qt 5

René J.V. Bertin rjvbertin at gmail.com
Sat Apr 18 12:34:53 CEST 2015


Hello,

The specific question: how/where is (QFontEngine *)fe->ctfont set that is read in QFontDialogPrivate::setFont (qfontdialog_mac.mm around line 514)?

I have been looking into an issue with the selection of fonts in weights/styles that don't follow the usual four suspects (Normal, Italic, Bold, Bold-Italic).
In stock Qt 4.8, selecting a Medium or Semi Bold weight (i.e. a weight between normal and bold) works during the initial selection (say in qtconfig's default font selection), but open the font dialog once more, or relaunch the application, and that medium weight will have been replaced by standard bold.

I think I have a fix for this in Qt 4.8, which removes the "easy shortcut" I found in 2 places ("there's Normal and anything heavier which becomes Bold") and extends the weight string parser to support most of the font weights you'll find on a typical OS X install.

It turns out that almost inevitably I came up with code that looks a lot like what had already been done for Qt 5.4 (I swear, I didn't peak :)) except that I didn't introduce additional const variables to complement the existing QFont::DemiBold etc. styles.

We're getting to the question.
When opening the font dialog with an initial font, Qt 4 calls QFontDialogPrivate::setFont() where Qt 5.4 uses QCocoaFontPanel::setCurrentFont() . Both functions (can) use [NSFontManager fontWithFamily:traits:weight:size], but the Qt4 version is actually called with fe->ctfont already set to the appropriate NSFont*. 

And that seems to work more reliably. I have several fonts on my system (including the Apple-provided Avenir family) where fontWithFamily:traits:weight:size does *not* return the requested typeface, as if the weight parameter is ignored. And indeed the documentation for that function suggests that the weight parameter is used as a hint only (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSFontManager_Class/index.html#//apple_ref/occ/instm/NSFontManager/fontWithFamily:traits:weight:size:) though I *hope* that it's used as more than a hint when the requested weight actually exists.

Hence my question: where is fe->ctfont initialised? Clearly this uses a different method for obtaining the NSFont (or CFFontRef).

Note that I've already tried to put all chances on my side for weight: to be used as more than a hint. I've analysed the weights in question for all fonts on my system (Apple's documentation doesn't bother to document the exact value mapping), and came up with the following

+        // RJVB
+        // Thin,Light -> 3, Book -> 4
+        // Normal/Regular -> 5
+        // Medium/SemiBold/Demibold -> 6,7,8
+        // Bold -> 9
+        // Ultra/Black/Heavy -> 10,11
+        QByteArray *weights = NULL;
+        switch (font.weight()) {
+            case QFont::Light:
+                weights = new QByteArray((const char[]){3,4});
+                break;
+            case QFont::Normal:
+                weights = new QByteArray((const char[]){5});
+                break;
+            case QFont::DemiBold:
+                weights = new QByteArray((const char[]){6,7,8});
+                break;
+            case QFont::Bold:
+                weights = new QByteArray((const char[]){9});
+                break;
+            case QFont::Black:
+                weights = new QByteArray((const char[]){10,11});
+                break;
         }

(evidently I did the same for Apple's other scale, that goes from -1.0 to 1.0). I then test each of the weights corresponding to font.weight():

+        if (weights) {
+            nsFont = NULL;
+            for (int i = 0 ; i < weights->size() && !nsFont ; ++i) {
+                weight = (int) (*weights)[i];
+                nsFont = [mgr fontWithFamily:qt_mac_QStringToNSString(fontInfo.family())
+                         traits:mask
+                         weight:weight
+                         size:fontInfo.pointSize()];
+            }
+            delete weights;
+        }

the only thing I haven't yet added is an additional check whether fontWithFamily did indeed return the requested weight, and not the closest lower match (which would let DemiBold downgrade to Normal, or Ultra to Bold).

Thanks for bearing with me, and (even more :)) for any feedback!

R.



More information about the Development mailing list