1- from fontbakery .prelude import check , FAIL
1+ from fontbakery .constants import NameID
2+ from fontbakery .prelude import check , FAIL , Message
23from fontbakery .utils import get_family_name , get_subfamily_name
34
45
6+ def _check_name_length_req (family_name , subfamily_name ):
7+ if family_name is None :
8+ yield FAIL , Message ("missing-name-id" , "Name ID 1 (family) missing" )
9+ if subfamily_name is None :
10+ yield FAIL , Message ("missing-name-id" , "Name ID 2 (sub family) missing" )
11+
12+ logfont = (
13+ family_name
14+ if subfamily_name in ("Regular" , "Bold" , "Italic" , "Bold Italic" )
15+ else " " .join ([family_name , subfamily_name ])
16+ )
17+
18+ if len (logfont ) > 31 :
19+ yield FAIL , Message (
20+ "long-name" ,
21+ f"Family + subfamily name, '{ logfont } ', is too long: "
22+ f"{ len (logfont )} characters; must be 31 or less" ,
23+ )
24+
25+
526@check (
627 id = "name_length_req" ,
728 rationale = """
@@ -14,19 +35,112 @@ def check_name_length_req(ttFont):
1435 """Maximum allowed length for family and subfamily names."""
1536 family_name = get_family_name (ttFont )
1637 subfamily_name = get_subfamily_name (ttFont )
17- if family_name is None :
18- yield FAIL , "Name ID 1 (family) missing"
19- if subfamily_name is None :
20- yield FAIL , "Name ID 2 (sub family) missing"
38+ yield from _check_name_length_req (family_name , subfamily_name )
2139
22- logfont = (
23- family_name
24- if subfamily_name in ("Regular" , "Bold" , "Italic" , "Bold Italic" )
25- else " " .join ([family_name , subfamily_name ])
40+
41+ def resolve_stat_names (vf , coordinates ):
42+ """
43+ Returns a dictionary of names & axis order, indexed by axis tag, for an
44+ instance in the given font at the given coordinates.
45+
46+ e.g. result['wght']: ('Regular', 0)
47+ """
48+ stat_table = vf ["STAT" ].table
49+ result = {}
50+
51+ for avr in stat_table .AxisValueArray .AxisValue :
52+ axis_tag = stat_table .DesignAxisRecord .Axis [avr .AxisIndex ].AxisTag
53+ if (
54+ axis_tag in coordinates
55+ and (
56+ (
57+ avr .Format == 2
58+ and avr .RangeMinValue <= coordinates [axis_tag ] <= avr .RangeMaxValue
59+ )
60+ or (avr .Format in (1 , 3 ) and avr .Value == coordinates [axis_tag ])
61+ )
62+ ) or (axis_tag not in coordinates ):
63+ if avr .Flags & 0x2 == 0 :
64+ name = vf ["name" ].getDebugName (avr .ValueNameID )
65+ axis_order = stat_table .DesignAxisRecord .Axis [
66+ avr .AxisIndex
67+ ].AxisOrdering
68+ result [axis_tag ] = (name , axis_order )
69+
70+ return result
71+
72+
73+ RBIZ_AXES = ["wght" , "ital" ]
74+
75+
76+ def compute_rbiz_names (family , stat_names , rbiz_axes ):
77+ # Name IDs 1 & 2 in a RBIZ model
78+ rbiz_family_names = [
79+ n
80+ for axis , (n , _ ) in sorted (stat_names .items (), key = lambda x : x [1 ][1 ])
81+ if axis not in rbiz_axes
82+ ]
83+ rbiz_family = " " .join ([f"{ family } " , * rbiz_family_names ])
84+ rbiz_subfamily_names = [
85+ stat_names [axis ] for axis in rbiz_axes if axis in stat_names
86+ ]
87+ rbiz_subfamily = " " .join (
88+ [n for n , _ in sorted (rbiz_subfamily_names , key = lambda x : x [1 ])]
2689 )
90+ if rbiz_subfamily == "" :
91+ rbiz_subfamily = "Regular"
92+ return (rbiz_family , rbiz_subfamily )
2793
28- if len (logfont ) > 31 :
29- yield FAIL , (
30- f"Family + subfamily name, '{ logfont } ', is too long: "
31- f"{ len (logfont )} characters; must be 31 or less"
32- )
94+
95+ def names_from_stat (vf , coordinates ):
96+ """
97+ Returns family and subfamily names generated from the STAT and name tables, for an instance
98+ at the given coordinates.
99+ """
100+ stat_names = resolve_stat_names (vf , coordinates )
101+
102+ # Find any implicit style names in ID 1 by removing ID 16 and any of the
103+ # default instance style names. E.g. if ID 1 is "Bahnschrift Rounded Light",
104+ # and ID 16 is "Bahnschrift", find "Rounded".
105+ id1 = vf ["name" ].getDebugName (NameID .FONT_FAMILY_NAME )
106+ id16 = vf ["name" ].getDebugName (NameID .TYPOGRAPHIC_FAMILY_NAME )
107+ if id1 and id16 and id1 .startswith (id16 ) and id1 != id16 :
108+ id1_names = id1 [len (id16 ) + 1 :].split (" " )
109+ default_inst_coords = {
110+ axis .axisTag : axis .defaultValue for axis in vf ["fvar" ].axes
111+ }
112+ default_stat_names = resolve_stat_names (vf , default_inst_coords )
113+ default_stat_names = [n for n , _ in sorted (default_stat_names .values ())]
114+ remaining_names = [n for n in id1_names if n not in default_stat_names ]
115+ # Update axis ordering in stat_names
116+ for axis_tag , (name , ordering ) in stat_names .items ():
117+ stat_names [axis_tag ] = (name , ordering + len (remaining_names ))
118+ # Add the names from ID 1
119+ stat_names .update ({f"X{ i :<3} " : (n , i ) for i , n in enumerate (remaining_names )})
120+
121+ family = vf ["name" ].getDebugName (NameID .TYPOGRAPHIC_FAMILY_NAME )
122+ if family is None :
123+ family = vf ["name" ].getDebugName (NameID .FONT_FAMILY_NAME )
124+
125+ rbiz_axes = list (RBIZ_AXES )
126+ if "wght" in coordinates and coordinates ["wght" ] not in (400 , 700 ):
127+ rbiz_axes = [x for x in rbiz_axes if x != "wght" ]
128+
129+ rbiz_family , rbiz_subfamily = compute_rbiz_names (family , stat_names , rbiz_axes )
130+ return rbiz_family , rbiz_subfamily
131+
132+
133+ @check (
134+ id = "instances_name_length_req" ,
135+ conditions = ["is_variable_font" ],
136+ rationale = """
137+ For Office, instance family and subfamily names must be 31 characters or less total
138+ to fit in a LOGFONT.
139+ """ ,
140+ )
141+ def instances_name_length_req (ttFont ):
142+ """Maximum allowed length for instance family and subfamily names."""
143+ fvar = ttFont ["fvar" ]
144+ for instance in fvar .instances :
145+ family_name , subfamily_name = names_from_stat (ttFont , instance .coordinates )
146+ yield from _check_name_length_req (family_name , subfamily_name )
0 commit comments