Skip to content

Commit 6104d00

Browse files
committed
refactored percent metrics calculations to take into account metrics approach used in the font, added support for Google and Adobe metrics approaches
1 parent e756e16 commit 6104d00

File tree

3 files changed

+285
-14
lines changed

3 files changed

+285
-14
lines changed

lib/fontline/app.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818
from fontline.utilities import file_exists, is_supported_filetype
1919

2020
# TODO: support integer addition and subtraction to the metrics values through new sub-commands
21-
# TODO: support .woff and .woff2 file types?
2221
# TODO: JSON formatted metrics output
23-
# TODO: autocorrect command?
22+
# TODO: add report line items for 'exceeds ymax/ymin values' across each of the three metrics
2423

2524

2625
def main():

lib/fontline/commands.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,58 @@ def modify_linegap_percent(fontpath, percent):
8181
# get observed start values from the font
8282
os2_typo_ascender = tt['OS/2'].__dict__['sTypoAscender']
8383
os2_typo_descender = tt['OS/2'].__dict__['sTypoDescender']
84+
os2_typo_linegap = tt['OS/2'].__dict__['sTypoLineGap']
85+
hhea_ascent = tt['hhea'].__dict__['ascent']
86+
hhea_descent = tt['hhea'].__dict__['descent']
87+
units_per_em = tt['head'].__dict__['unitsPerEm']
8488

85-
# redefine hhea linegap to 0
86-
hhea_linegap = 0
89+
# calculate necessary delta values
90+
os2_typo_ascdesc_delta = os2_typo_ascender + -(os2_typo_descender)
91+
hhea_ascdesc_delta = hhea_ascent + -(hhea_descent)
8792

93+
# define percent UPM from command line request
8894
factor = 1.0 * int(percent) / 100
8995

90-
os2_typo_linegap = int(factor * (os2_typo_ascender + -(os2_typo_descender)))
91-
total_height = os2_typo_ascender + -(os2_typo_descender) + os2_typo_linegap
96+
# define line spacing units
97+
line_spacing_units = int(factor * units_per_em)
98+
99+
# define total height as UPM + line spacing units
100+
total_height = line_spacing_units + units_per_em
92101

93-
hhea_ascent = int(os2_typo_ascender + 0.5 * os2_typo_linegap)
94-
hhea_descent = -(total_height - hhea_ascent)
102+
# height calculations for adjustments
103+
delta_height = total_height - hhea_ascdesc_delta
104+
upper_lower_add_units = int(0.5 * delta_height)
95105

96-
os2_win_ascent = hhea_ascent
97-
os2_win_descent = -hhea_descent
106+
# redefine hhea linegap to 0 in all cases
107+
hhea_linegap = 0
98108

99-
# define updated values
109+
# Define metrics based upon original design approach in the font
110+
# Google metrics approach
111+
if os2_typo_linegap == 0 and (os2_typo_ascdesc_delta > units_per_em):
112+
# define values
113+
os2_typo_ascender += upper_lower_add_units
114+
os2_typo_descender -= upper_lower_add_units
115+
hhea_ascent += upper_lower_add_units
116+
hhea_descent -= upper_lower_add_units
117+
os2_win_ascent = hhea_ascent
118+
os2_win_descent = -hhea_descent
119+
# Adobe metrics approach
120+
elif os2_typo_linegap == 0 and (os2_typo_ascdesc_delta == units_per_em):
121+
hhea_ascent += upper_lower_add_units
122+
hhea_descent -= upper_lower_add_units
123+
os2_win_ascent = hhea_ascent
124+
os2_win_descent = -hhea_descent
125+
else:
126+
os2_typo_linegap = line_spacing_units
127+
hhea_ascent = int(os2_typo_ascender + 0.5 * os2_typo_linegap)
128+
hhea_descent = -(total_height - hhea_ascent)
129+
os2_win_ascent = hhea_ascent
130+
os2_win_descent = -hhea_descent
131+
132+
# define updated values from above calculations
100133
tt['hhea'].__dict__['lineGap'] = hhea_linegap
134+
tt['OS/2'].__dict__['sTypoAscender'] = os2_typo_ascender
135+
tt['OS/2'].__dict__['sTypoDescender'] = os2_typo_descender
101136
tt['OS/2'].__dict__['sTypoLineGap'] = os2_typo_linegap
102137
tt['OS/2'].__dict__['usWinAscent'] = os2_win_ascent
103138
tt['OS/2'].__dict__['usWinDescent'] = os2_win_descent

tests/test_percent_cmd.py

Lines changed: 240 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ def test_percent_cmd_font_file_wrong_filetype(capsys):
103103
#
104104
# ///////////////////////////////////////////////////////
105105

106-
def test_percent_cmd_ttf_file_10_percent(capsys):
106+
# Default approach with TypoLinegap > 0 and TypoAscender + TypoDescender set to UPM height
107+
108+
def test_percent_cmd_ttf_file_10_percent_default_approach(capsys):
107109
try:
108110
from fontline.app import main
109111
fontpath = os.path.join('tests', 'testingfiles', 'Hack-Regular.ttf')
@@ -149,7 +151,7 @@ def test_percent_cmd_ttf_file_10_percent(capsys):
149151
raise e
150152

151153

152-
def test_percent_cmd_otf_file_10_percent(capsys):
154+
def test_percent_cmd_otf_file_10_percent_default_approach(capsys):
153155
try:
154156
from fontline.app import main
155157
fontpath = os.path.join('tests', 'testingfiles', 'Hack-Regular.otf')
@@ -192,4 +194,239 @@ def test_percent_cmd_otf_file_10_percent(capsys):
192194
erase_test_file(testpath)
193195
if file_exists(newfont_path):
194196
erase_test_file(newfont_path)
195-
raise e
197+
raise e
198+
199+
200+
def test_percent_cmd_ttf_file_30_percent_default_approach(capsys):
201+
try:
202+
from fontline.app import main
203+
fontpath = os.path.join('tests', 'testingfiles', 'Hack-Regular.ttf')
204+
testpath = os.path.join('tests', 'testingfiles', 'Hack-Regular-test.ttf')
205+
newfont_path = os.path.join('tests', 'testingfiles', 'Hack-Regular-test-linegap30.ttf')
206+
create_test_file(fontpath)
207+
assert file_exists(testpath) is True
208+
sys.argv = ['font-line', 'percent', '30', testpath]
209+
main()
210+
211+
assert file_exists(newfont_path)
212+
213+
tt = ttLib.TTFont(newfont_path)
214+
215+
os2_typo_ascender = tt['OS/2'].__dict__['sTypoAscender']
216+
os2_typo_descender = tt['OS/2'].__dict__['sTypoDescender']
217+
os2_win_ascent = tt['OS/2'].__dict__['usWinAscent']
218+
os2_win_descent = tt['OS/2'].__dict__['usWinDescent']
219+
os2_typo_linegap = tt['OS/2'].__dict__['sTypoLineGap']
220+
hhea_ascent = tt['hhea'].__dict__['ascent']
221+
hhea_descent = tt['hhea'].__dict__['descent']
222+
hhea_linegap = tt['hhea'].__dict__['lineGap']
223+
units_per_em = tt['head'].__dict__['unitsPerEm']
224+
225+
assert os2_typo_ascender == 1556
226+
assert os2_typo_descender == -492
227+
assert os2_win_ascent == 1863
228+
assert os2_win_descent == 799
229+
assert units_per_em == 2048
230+
assert os2_typo_linegap == 614
231+
assert hhea_ascent == 1863
232+
assert hhea_descent == -799
233+
assert hhea_linegap == 0
234+
235+
erase_test_file(testpath)
236+
erase_test_file(newfont_path)
237+
except Exception as e:
238+
# cleanup test files
239+
if file_exists(testpath):
240+
erase_test_file(testpath)
241+
if file_exists(newfont_path):
242+
erase_test_file(newfont_path)
243+
raise e
244+
245+
246+
# Google metrics approach with TypoLinegap & hhea linegap = 0, all other metrics set to same values of internal leading
247+
248+
def test_percent_cmd_ttf_file_10_percent_google_approach(capsys):
249+
try:
250+
from fontline.app import main
251+
fontpath = os.path.join('tests', 'testingfiles', 'FiraMono-Regular.ttf')
252+
testpath = os.path.join('tests', 'testingfiles', 'FiraMono-Regular-test.ttf')
253+
newfont_path = os.path.join('tests', 'testingfiles', 'FiraMono-Regular-test-linegap10.ttf')
254+
create_test_file(fontpath)
255+
assert file_exists(testpath) is True
256+
sys.argv = ['font-line', 'percent', '10', testpath]
257+
main()
258+
259+
assert file_exists(newfont_path)
260+
261+
tt = ttLib.TTFont(newfont_path)
262+
263+
os2_typo_ascender = tt['OS/2'].__dict__['sTypoAscender']
264+
os2_typo_descender = tt['OS/2'].__dict__['sTypoDescender']
265+
os2_win_ascent = tt['OS/2'].__dict__['usWinAscent']
266+
os2_win_descent = tt['OS/2'].__dict__['usWinDescent']
267+
os2_typo_linegap = tt['OS/2'].__dict__['sTypoLineGap']
268+
hhea_ascent = tt['hhea'].__dict__['ascent']
269+
hhea_descent = tt['hhea'].__dict__['descent']
270+
hhea_linegap = tt['hhea'].__dict__['lineGap']
271+
units_per_em = tt['head'].__dict__['unitsPerEm']
272+
273+
assert os2_typo_ascender == 885
274+
assert os2_typo_descender == -215
275+
assert os2_win_ascent == 885
276+
assert os2_win_descent == 215
277+
assert units_per_em == 1000
278+
assert os2_typo_linegap == 0
279+
assert hhea_ascent == 885
280+
assert hhea_descent == -215
281+
assert hhea_linegap == 0
282+
283+
erase_test_file(testpath)
284+
erase_test_file(newfont_path)
285+
except Exception as e:
286+
# cleanup test files
287+
if file_exists(testpath):
288+
erase_test_file(testpath)
289+
if file_exists(newfont_path):
290+
erase_test_file(newfont_path)
291+
raise e
292+
293+
294+
def test_percent_cmd_ttf_file_30_percent_google_approach(capsys):
295+
try:
296+
from fontline.app import main
297+
fontpath = os.path.join('tests', 'testingfiles', 'FiraMono-Regular.ttf')
298+
testpath = os.path.join('tests', 'testingfiles', 'FiraMono-Regular-test.ttf')
299+
newfont_path = os.path.join('tests', 'testingfiles', 'FiraMono-Regular-test-linegap30.ttf')
300+
create_test_file(fontpath)
301+
assert file_exists(testpath) is True
302+
sys.argv = ['font-line', 'percent', '30', testpath]
303+
main()
304+
305+
assert file_exists(newfont_path)
306+
307+
tt = ttLib.TTFont(newfont_path)
308+
309+
os2_typo_ascender = tt['OS/2'].__dict__['sTypoAscender']
310+
os2_typo_descender = tt['OS/2'].__dict__['sTypoDescender']
311+
os2_win_ascent = tt['OS/2'].__dict__['usWinAscent']
312+
os2_win_descent = tt['OS/2'].__dict__['usWinDescent']
313+
os2_typo_linegap = tt['OS/2'].__dict__['sTypoLineGap']
314+
hhea_ascent = tt['hhea'].__dict__['ascent']
315+
hhea_descent = tt['hhea'].__dict__['descent']
316+
hhea_linegap = tt['hhea'].__dict__['lineGap']
317+
units_per_em = tt['head'].__dict__['unitsPerEm']
318+
319+
assert os2_typo_ascender == 985
320+
assert os2_typo_descender == -315
321+
assert os2_win_ascent == 985
322+
assert os2_win_descent == 315
323+
assert units_per_em == 1000
324+
assert os2_typo_linegap == 0
325+
assert hhea_ascent == 985
326+
assert hhea_descent == -315
327+
assert hhea_linegap == 0
328+
329+
erase_test_file(testpath)
330+
erase_test_file(newfont_path)
331+
except Exception as e:
332+
# cleanup test files
333+
if file_exists(testpath):
334+
erase_test_file(testpath)
335+
if file_exists(newfont_path):
336+
erase_test_file(newfont_path)
337+
raise e
338+
339+
340+
# Adobe metrics approach with TypoLinegap and hhea linegap = 0, TypoAscender + TypoDescender = UPM &
341+
# internal leading added to winAsc/winDesc & hhea Asc/hhea Desc metrics
342+
343+
def test_percent_cmd_ttf_file_10_percent_adobe_approach(capsys):
344+
try:
345+
from fontline.app import main
346+
fontpath = os.path.join('tests', 'testingfiles', 'SourceCodePro-Regular.ttf')
347+
testpath = os.path.join('tests', 'testingfiles', 'SourceCodePro-Regular-test.ttf')
348+
newfont_path = os.path.join('tests', 'testingfiles', 'SourceCodePro-Regular-test-linegap10.ttf')
349+
create_test_file(fontpath)
350+
assert file_exists(testpath) is True
351+
sys.argv = ['font-line', 'percent', '10', testpath]
352+
main()
353+
354+
assert file_exists(newfont_path)
355+
356+
tt = ttLib.TTFont(newfont_path)
357+
358+
os2_typo_ascender = tt['OS/2'].__dict__['sTypoAscender']
359+
os2_typo_descender = tt['OS/2'].__dict__['sTypoDescender']
360+
os2_win_ascent = tt['OS/2'].__dict__['usWinAscent']
361+
os2_win_descent = tt['OS/2'].__dict__['usWinDescent']
362+
os2_typo_linegap = tt['OS/2'].__dict__['sTypoLineGap']
363+
hhea_ascent = tt['hhea'].__dict__['ascent']
364+
hhea_descent = tt['hhea'].__dict__['descent']
365+
hhea_linegap = tt['hhea'].__dict__['lineGap']
366+
units_per_em = tt['head'].__dict__['unitsPerEm']
367+
368+
assert os2_typo_ascender == 750
369+
assert os2_typo_descender == -250
370+
assert os2_win_ascent == 906
371+
assert os2_win_descent == 195
372+
assert units_per_em == 1000
373+
assert os2_typo_linegap == 0
374+
assert hhea_ascent == 906
375+
assert hhea_descent == -195
376+
assert hhea_linegap == 0
377+
378+
erase_test_file(testpath)
379+
erase_test_file(newfont_path)
380+
except Exception as e:
381+
# cleanup test files
382+
if file_exists(testpath):
383+
erase_test_file(testpath)
384+
if file_exists(newfont_path):
385+
erase_test_file(newfont_path)
386+
raise e
387+
388+
389+
def test_percent_cmd_ttf_file_30_percent_adobe_approach(capsys):
390+
try:
391+
from fontline.app import main
392+
fontpath = os.path.join('tests', 'testingfiles', 'SourceCodePro-Regular.ttf')
393+
testpath = os.path.join('tests', 'testingfiles', 'SourceCodePro-Regular-test.ttf')
394+
newfont_path = os.path.join('tests', 'testingfiles', 'SourceCodePro-Regular-test-linegap30.ttf')
395+
create_test_file(fontpath)
396+
assert file_exists(testpath) is True
397+
sys.argv = ['font-line', 'percent', '30', testpath]
398+
main()
399+
400+
assert file_exists(newfont_path)
401+
402+
tt = ttLib.TTFont(newfont_path)
403+
404+
os2_typo_ascender = tt['OS/2'].__dict__['sTypoAscender']
405+
os2_typo_descender = tt['OS/2'].__dict__['sTypoDescender']
406+
os2_win_ascent = tt['OS/2'].__dict__['usWinAscent']
407+
os2_win_descent = tt['OS/2'].__dict__['usWinDescent']
408+
os2_typo_linegap = tt['OS/2'].__dict__['sTypoLineGap']
409+
hhea_ascent = tt['hhea'].__dict__['ascent']
410+
hhea_descent = tt['hhea'].__dict__['descent']
411+
hhea_linegap = tt['hhea'].__dict__['lineGap']
412+
units_per_em = tt['head'].__dict__['unitsPerEm']
413+
414+
assert os2_typo_ascender == 750
415+
assert os2_typo_descender == -250
416+
assert os2_win_ascent == 1005
417+
assert os2_win_descent == 294
418+
assert units_per_em == 1000
419+
assert os2_typo_linegap == 0
420+
assert hhea_ascent == 1005
421+
assert hhea_descent == -294
422+
assert hhea_linegap == 0
423+
424+
erase_test_file(testpath)
425+
erase_test_file(newfont_path)
426+
except Exception as e:
427+
# cleanup test files
428+
if file_exists(testpath):
429+
erase_test_file(testpath)
430+
if file_exists(newfont_path):
431+
erase_test_file(newfont_path)
432+
raise e

0 commit comments

Comments
 (0)