11import re
2+ from collections import defaultdict
3+ from itertools import product
24
3- from fontbakery .constants import (
4- RIBBI_STYLE_NAMES ,
5- NameID ,
6- )
7- from fontbakery .prelude import check , Message , FAIL , WARN
5+ from fontbakery .constants import RIBBI_STYLE_NAMES , NameID
6+ from fontbakery .prelude import FAIL , Message , check
87from fontbakery .utils import get_name_entry_strings
98
109
1312 rationale = """
1413 This check ensures that the length of name table entries is not
1514 too long, as this causes problems in some environments.
15+
16+ Background on length limit (credit to Aaron Bell at google/fonts/issues/9185):
17+
18+ The font dropdown in Microsoft Office on PC is driven by the older
19+ GDI font enumeration and rendering technology. GDI uses LOGFONTA
20+ (https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfonta)
21+ to define the attributes of a font, and CHAR lfFaceName[LF_FACESIZE];
22+ to define the font name. The problem, though, is that the string lfFaceName
23+ is restricted to 32 characters, including the terminating NULL.
24+
25+ In a variable font where the master location is at a location with a
26+ significant number of characters, say, “ExtraLight”, name ID 1 can become
27+ quite long (eg: “Chiron Hei HK ExtraLight”). However, in determining the
28+ font name, Microsoft will prioritize name ID 16 over name ID 1. So this
29+ lets one reduce the character count back to the standard font name
30+ (eg: “Chiron Hei HK”).
1631 """ ,
1732 proposal = [
1833 "https://github.com/fonttools/fontbakery/issues/1488" ,
1934 "https://github.com/fonttools/fontbakery/issues/2179" ,
2035 ],
2136)
2237def check_name_family_and_style_max_length (ttFont ):
23- """Combined length of family and style must not exceed 32 characters."""
38+ """Combined length of family and style must not exceed 31 characters."""
2439
2540 def strip_ribbi (x ):
2641 ribbi_re = " (" + "|" .join (RIBBI_STYLE_NAMES ) + ")$"
2742 return re .sub (ribbi_re , "" , x )
2843
44+ # constants for name length limits
45+ NAME_LENGTH_LIMIT = 31
46+ PSNAME_LENGTH_LIMIT = 27
47+ ELIDABLE_FLAG = 2
48+
2949 checks = [
3050 [
3151 FAIL ,
32- NameID .FULL_FONT_NAME ,
33- 32 ,
34- (
35- "with the dropdown menu in old versions of Microsoft Word"
36- " as well as shaping issues for some accented letters in"
37- " Microsoft Word on Windows 10 and 11"
38- ),
52+ NameID .FONT_FAMILY_NAME ,
53+ NAME_LENGTH_LIMIT ,
54+ "cause a fallback font to appear for some accented letters, as well"
55+ " as in some scripts such as Thai, in"
56+ " Microsoft Word on Windows 10 and 11. It can also lead to names"
57+ " which are truncated in the Microsoft Word font menu.\n \n " ,
3958 strip_ribbi ,
4059 ],
41- [
42- WARN ,
43- NameID .POSTSCRIPT_NAME ,
44- 27 ,
45- "with PostScript printers, especially on Mac platforms" ,
46- lambda x : x ,
47- ],
4860 ]
4961 for loglevel , nameid , maxlen , reason , transform in checks :
5062 for the_name in get_name_entry_strings (ttFont , nameid ):
@@ -57,36 +69,92 @@ def strip_ribbi(x):
5769 f" cause problems { reason } ." ,
5870 )
5971
60- # name ID 1/16 + fvar instance name > 32 : FAIL : problems with Windows
61- if "fvar" in ttFont :
72+ # check variable font name lengths
73+ if "fvar" in ttFont and "STAT" in ttFont :
74+ # Get the family name from the name table (prefer ID 16)
75+ if ttFont ["name" ].getName (NameID .TYPOGRAPHIC_FAMILY_NAME , 3 , 1 , 0x409 ):
76+ family_name = (
77+ ttFont ["name" ]
78+ .getName (NameID .TYPOGRAPHIC_FAMILY_NAME , 3 , 1 , 0x409 )
79+ .toUnicode ()
80+ )
81+ family_name_id = NameID .TYPOGRAPHIC_FAMILY_NAME
82+ else :
83+ family_name = (
84+ ttFont ["name" ].getName (NameID .FONT_FAMILY_NAME , 3 , 1 , 0x409 ).toUnicode ()
85+ )
86+ family_name_id = NameID .FONT_FAMILY_NAME
87+
88+ styles_per_axis = defaultdict (list )
89+ for value in ttFont ["STAT" ].table .AxisValueArray .AxisValue :
90+ # if the value is marked as elidable, don’t count it
91+ if value .Flags & ELIDABLE_FLAG :
92+ continue
93+ # skip "Regular" and "Italic" style names, which do not count towards MS Word limit
94+ if ttFont ["name" ].getName (value .ValueNameID , 3 , 1 , 0x409 ).toUnicode () in [
95+ "Regular" ,
96+ "Italic" ,
97+ ]:
98+ continue
99+ # otherwise, get the STAT style particle name and add it to the list
100+ styles_per_axis [value .AxisIndex ].append (
101+ ttFont ["name" ].getName (value .ValueNameID , 3 , 1 , 0x409 ).toUnicode ()
102+ )
103+
104+ # make list of combined family & STAT style names
105+ names = [
106+ f'{ family_name } { " " .join (combination )} '
107+ for combination in product (* styles_per_axis .values ())
108+ ]
109+
110+ for name in names :
111+ if len (name ) > NAME_LENGTH_LIMIT :
112+ stat_style_combination = name .replace (f"{ family_name } " , "" )
113+ yield FAIL , Message (
114+ "familyname-plus-stat-entries-too-long" ,
115+ f"Name ID { family_name_id } '{ family_name } ' plus"
116+ f" STAT table style combination '{ stat_style_combination } '"
117+ f" exceeds 31 characters (the combination is { len (name )} characters).\n \n "
118+ f" This has been found to"
119+ f" cause a fallback font to appear for some accented letters, as well"
120+ f" as in some scripts such as Thai, in"
121+ f" Microsoft Word on Windows 10 and 11. It can also lead to names"
122+ f" which are truncated in the Microsoft Word font menu.\n \n " ,
123+ )
124+
125+ # if STAT not in font, assume that "fvar" instance names are used
126+ if "fvar" in ttFont and "STAT" not in ttFont :
62127 for instance in ttFont ["fvar" ].instances :
63128 for instance_name in get_name_entry_strings (
64129 ttFont , instance .subfamilyNameID
65130 ):
66131 typo_family_names = {
67132 (r .platformID , r .platEncID , r .langID ): r
68133 for r in ttFont ["name" ].names
69- if r .nameID == 16
134+ if r .nameID == NameID . TYPOGRAPHIC_FAMILY_NAME
70135 }
71136 family_names = {
72137 (r .platformID , r .platEncID , r .langID ): r
73138 for r in ttFont ["name" ].names
74- if r .nameID == 1
139+ if r .nameID == NameID . FONT_FAMILY_NAME
75140 }
76141 for platform in family_names :
77142 if platform in typo_family_names :
78143 family_name = typo_family_names [platform ].toUnicode ()
79144 else :
80145 family_name = family_names [platform ].toUnicode ()
81146 full_instance_name = family_name + " " + instance_name
82- if len (full_instance_name ) > 32 :
147+ if len (full_instance_name ) > NAME_LENGTH_LIMIT :
83148 yield FAIL , Message (
84- "instance-too-long" ,
149+ "fvar- instance-too-long" ,
85150 f"Variable font instance name '{ full_instance_name } '"
86151 f" formed by space-separated concatenation of"
87152 f" font family name (nameID { NameID .FONT_FAMILY_NAME } )"
88153 f" and instance subfamily nameID { instance .subfamilyNameID } "
89- f" exceeds 32 characters.\n \n "
90- f"This has been found to cause shaping issues for some"
91- f" accented letters in Microsoft Word on Windows 10 and 11." ,
154+ f" exceeds { NAME_LENGTH_LIMIT } characters.\n \n "
155+ f" This has been found to"
156+ f" cause a fallback font to appear for some accented letters, as well"
157+ f" as in some scripts such as Thai, in"
158+ f" Microsoft Word on Windows 10 and 11. It can also lead to names"
159+ f" which are truncated in the Microsoft Word font menu.\n \n " ,
92160 )
0 commit comments