Typefaces loaded from file have different names on macOS and Windows

Typefaces loaded from file on Windows have their name member set to the full font name (font family name and subfamily/style name joined together) with the style member left blank, whereas macOS typeface names are correctly separated. This seems incorrect, inconsistent and makes it difficult to search for the same custom fonts by name in a cross-platform way. After all, the required information must exist in the typeface file because the same one is parsed on both platforms.

Below is a quick example patch showing how the TTFNameExtractor functions could be modified to read both name and style. I found the name record IDs on this Introduction to TrueType Fonts page.

diff --git a/modules/juce_graphics/native/juce_win32_Fonts.cpp b/modules/juce_graphics/native/juce_win32_Fonts.cpp
index a21082499..bd1169eb7 100644
--- a/modules/juce_graphics/native/juce_win32_Fonts.cpp
+++ b/modules/juce_graphics/native/juce_win32_Fonts.cpp
@@ -93,32 +93,45 @@ namespace TTFNameExtractor
         return result;
     }
 
-    static String parseNameTable (MemoryInputStream& input, int64 directoryOffset)
+    static std::pair<String, String> parseNameTable (MemoryInputStream& input, int64 directoryOffset)
     {
         input.setPosition (directoryOffset);
 
         NamingTable namingTable = { 0 };
         input.read (&namingTable, sizeof (namingTable));
 
+        String name, style, fullname;
+
         for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (namingTable.numberOfNameRecords); ++i)
         {
             NameRecord nameRecord = { 0 };
             input.read (&nameRecord, sizeof (nameRecord));
 
-            if (ByteOrder::swapIfLittleEndian (nameRecord.nameID) == 4)
+            auto parseIfIDMatches = [&](String& Out, uint16 ID)
             {
-                const String result (parseNameRecord (input, nameRecord, directoryOffset,
+                if (ByteOrder::swapIfLittleEndian (nameRecord.nameID) == ID)
+                {
+                    String result = (parseNameRecord (input, nameRecord, directoryOffset,
                                                       ByteOrder::swapIfLittleEndian (namingTable.offsetStartOfStringStorage)));
+                    if (result.isNotEmpty())
+                        Out = std::move (result);
+                }
+            };
 
-                if (result.isNotEmpty())
-                    return result;
-            }
+            parseIfIDMatches (name, 1);
+            parseIfIDMatches (style, 2);
+            parseIfIDMatches (fullname, 4);
+
+            if (name.isNotEmpty() && style.isNotEmpty())
+                return { name, style };
+            else if (fullname.isNotEmpty())
+                return { fullname, "" };
         }
 
         return {};
     }
 
-    static String getTypefaceNameFromFile (MemoryInputStream& input)
+    static std::pair<String, String> getTypefaceNameAndStyleFromFile (MemoryInputStream& input)
     {
         OffsetTable offsetTable = { 0 };
         input.read (&offsetTable, sizeof (offsetTable));
@@ -331,7 +344,7 @@ public:
                                            nullptr, &numInstalled);
 
         MemoryInputStream m (data, dataSize, false);
-        name = TTFNameExtractor::getTypefaceNameFromFile (m);
+        std::tie (name, style) = TTFNameExtractor::getTypefaceNameAndStyleFromFile (m);
         loadFont();
     }

Is this a change the JUCE team are able to make or are we stuck with things as they are due to compatibility with older code?

1 Like