forked from fonttools/fontbakery
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added to the OpenType profile. Replaces the old checks (also from OpenType profile): - opentype/italic_axis_in_stat - opentype/italic_axis_in_stat_is_boolean - opentype/italic_axis_last Large chunk of code back-ported from FontSpector. (issue fonttools#4865)
- Loading branch information
1 parent
9a33924
commit 5050a74
Showing
8 changed files
with
159 additions
and
184 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,185 +1,155 @@ | ||
import os | ||
|
||
from fontbakery.prelude import check, Message, FAIL, PASS, WARN, SKIP | ||
from fontTools.ttLib import TTFont | ||
|
||
|
||
@check( | ||
id="opentype/italic_axis_in_STAT", | ||
rationale=""" | ||
Check that related Upright and Italic VFs have a | ||
'ital' axis in STAT table. | ||
""", | ||
proposal="https://github.com/fonttools/fontbakery/issues/2934", | ||
) | ||
def check_italic_axis_in_STAT(fonts, config): | ||
"""Ensure VFs have 'ital' STAT axis.""" | ||
from fontTools.ttLib import TTFont | ||
def get_STAT_axis(font, tag): | ||
for axis in font["STAT"].table.DesignAxisRecord.Axis: | ||
if axis.AxisTag == tag: | ||
return axis | ||
return None | ||
|
||
font_filenames = [f.file for f in fonts] | ||
italics = [f for f in font_filenames if "Italic" in f] | ||
missing_roman = [] | ||
italic_to_roman_mapping = {} | ||
for italic in italics: | ||
style_from_filename = os.path.basename(italic).split("-")[-1].split(".")[0] | ||
is_varfont = "[" in style_from_filename | ||
|
||
# to remove the axes syntax used on variable-font filenames: | ||
if is_varfont: | ||
style_from_filename = style_from_filename.split("[")[0] | ||
|
||
if style_from_filename == "Italic": | ||
if is_varfont: | ||
# "Familyname-Italic[wght,wdth].ttf" => "Familyname[wght,wdth].ttf" | ||
roman_counterpart = italic.replace("-Italic", "") | ||
else: | ||
# "Familyname-Italic.ttf" => "Familyname-Regular.ttf" | ||
roman_counterpart = italic.replace("Italic", "Regular") | ||
else: | ||
# "Familyname-BoldItalic[wght,wdth].ttf" => "Familyname-Bold[wght,wdth].ttf" | ||
roman_counterpart = italic.replace("Italic", "") | ||
|
||
if is_varfont: | ||
if roman_counterpart not in font_filenames: | ||
missing_roman.append(italic) | ||
else: | ||
italic_to_roman_mapping[italic] = roman_counterpart | ||
def get_STAT_axis_value(font, tag): | ||
for i, axis in enumerate(font["STAT"].table.DesignAxisRecord.Axis): | ||
if axis.AxisTag == tag: | ||
for axisValue in font["STAT"].table.AxisValueArray.AxisValue: | ||
if axisValue.AxisIndex == i: | ||
linkedValue = None | ||
if hasattr(axisValue, "LinkedValue"): | ||
linkedValue = axisValue.LinkedValue | ||
return axisValue.Value, axisValue.Flags, linkedValue | ||
return None, None, None | ||
|
||
if missing_roman: | ||
from fontbakery.utils import pretty_print_list | ||
|
||
missing_roman = pretty_print_list(config, missing_roman) | ||
def check_has_ital(ttFont): | ||
font = os.path.basename(ttFont.reader.name) | ||
|
||
if "STAT" not in ttFont: | ||
yield FAIL, Message( | ||
"missing-roman", | ||
f"Italics missing a Roman counterpart, so couldn't check" | ||
f" both Roman and Italic for 'ital' axis: {missing_roman}", | ||
"no-stat", | ||
f"Font {font} has no STAT table", | ||
) | ||
return | ||
|
||
# Actual check starts here | ||
for italic_filename in italic_to_roman_mapping: | ||
italic = italic_filename | ||
upright = italic_to_roman_mapping[italic_filename] | ||
if "ital" not in [ | ||
axis.AxisTag for axis in ttFont["STAT"].table.DesignAxisRecord.Axis | ||
]: | ||
yield FAIL, Message( | ||
"missing-ital-axis", | ||
f"Font {font} lacks an 'ital' axis in the STAT table.", | ||
) | ||
|
||
for filepath in (upright, italic): | ||
ttFont = TTFont(filepath) | ||
if "ital" not in [ | ||
axis.AxisTag for axis in ttFont["STAT"].table.DesignAxisRecord.Axis | ||
]: | ||
yield FAIL, Message( | ||
"missing-ital-axis", | ||
f"Font {os.path.basename(filepath)}" f" is missing an 'ital' axis.", | ||
) | ||
|
||
def check_ital_is_binary_and_last(ttFont, is_italic): | ||
font = os.path.basename(ttFont.reader.name) | ||
|
||
@check( | ||
id="opentype/italic_axis_in_STAT_is_boolean", | ||
conditions=["style", "has_STAT_table"], | ||
rationale=""" | ||
Check that the value of the 'ital' STAT axis is boolean (either 0 or 1), | ||
and elided for the Upright and not elided for the Italic, | ||
and that the Upright is linked to the Italic. | ||
""", | ||
proposal="https://github.com/fonttools/fontbakery/issues/3668", | ||
) | ||
def check_italic_axis_in_STAT_is_boolean(ttFont, style): | ||
"""Ensure 'ital' STAT axis is boolean value""" | ||
|
||
def get_STAT_axis(font, tag): | ||
for axis in font["STAT"].table.DesignAxisRecord.Axis: | ||
if axis.AxisTag == tag: | ||
return axis | ||
return None | ||
|
||
def get_STAT_axis_value(font, tag): | ||
for i, axis in enumerate(font["STAT"].table.DesignAxisRecord.Axis): | ||
if axis.AxisTag == tag: | ||
for axisValue in font["STAT"].table.AxisValueArray.AxisValue: | ||
if axisValue.AxisIndex == i: | ||
linkedValue = None | ||
if hasattr(axisValue, "LinkedValue"): | ||
linkedValue = axisValue.LinkedValue | ||
return axisValue.Value, axisValue.Flags, linkedValue | ||
return None, None, None | ||
if "STAT" not in ttFont: | ||
return | ||
|
||
if not get_STAT_axis(ttFont, "ital"): | ||
yield SKIP, "Font doesn't have an ital axis" | ||
yield SKIP, "Font {font} doesn't have an ital axis" | ||
return | ||
|
||
tags = [axis.AxisTag in ttFont["STAT"].table.DesignAxisRecord.Axis] | ||
ital_pos = tags.index("ital") | ||
if ital_pos != len(tags) - 1: | ||
yield WARN, Message( | ||
"ital-axis-not-last", | ||
f"Font {font} has 'ital' axis in position" | ||
f" {ital_pos + 1} of {len(tags)}.", | ||
) | ||
|
||
value, flags, linkedValue = get_STAT_axis_value(ttFont, "ital") | ||
if (value, flags, linkedValue) == (None, None, None): | ||
yield SKIP, "No 'ital' axis in STAT." | ||
return | ||
|
||
passed = True | ||
# Font has an 'ital' axis in STAT | ||
if "Italic" in style: | ||
if value != 1: | ||
passed = False | ||
yield WARN, Message( | ||
"wrong-ital-axis-value", | ||
f"STAT table 'ital' axis has wrong value." | ||
f" Expected: 1, got '{value}'.", | ||
) | ||
if flags != 0: | ||
passed = False | ||
yield WARN, Message( | ||
"wrong-ital-axis-flag", | ||
f"STAT table 'ital' axis flag is wrong." | ||
f" Expected: 0 (not elided), got '{flags}'.", | ||
) | ||
if is_italic: | ||
expected_value = 1.0 | ||
expected_flags = 0x0000 # AxisValueTableFlags empty | ||
else: | ||
if value != 0: | ||
passed = False | ||
yield WARN, Message( | ||
"wrong-ital-axis-value", | ||
f"STAT table 'ital' axis has wrong value." | ||
f" Expected: 0, got '{value}'.", | ||
) | ||
if flags != 2: | ||
passed = False | ||
yield WARN, Message( | ||
"wrong-ital-axis-flag", | ||
f"STAT table 'ital' axis flag is wrong.\n" | ||
f"Expected: 2 (elided)\n" | ||
f"Got: '{flags}'", | ||
) | ||
if linkedValue != 1: | ||
passed = False | ||
expected_value = 0.0 | ||
expected_flags = 0x0002 # ElidableAxisValueName | ||
|
||
if value != expected_value: | ||
yield WARN, Message( | ||
"wrong-ital-axis-value", | ||
f"{font} has STAT table 'ital' axis with wrong value." | ||
f" Expected: {expected_value}, got '{value}'", | ||
) | ||
|
||
if flags != expected_flags: | ||
yield WARN, Message( | ||
"wrong-ital-axis-flag", | ||
f"{font} has STAT table 'ital' axis with wrong flags." | ||
f" Expected: {expected_flags}, got '{flags}'", | ||
) | ||
|
||
# If we are Roman, check for the linked value | ||
if not is_italic: | ||
if linked_value != 1.0: | ||
yield WARN, Message( | ||
"wrong-ital-axis-linkedvalue", | ||
"STAT table 'ital' axis is not linked to Italic.", | ||
f"{font} has STAT table 'ital' axis with wrong linked value." | ||
f" Expected: 1.0, got '{linked_value}'", | ||
) | ||
|
||
if passed: | ||
yield PASS, "STAT table ital axis values are good." | ||
|
||
def segment_vf_collection(fonts): | ||
roman_italic = [] | ||
italics = [] | ||
non_italics = [] | ||
for font in fonts: | ||
if "-Italic[" in font: | ||
italics.append(font) | ||
else: | ||
non_italics.append(font) | ||
|
||
for italic in italics: | ||
# Find a matching roman | ||
suspected_roman = italic.replace("-Italic[", "[") | ||
index = non_italics.index(suspected_roman) | ||
roman = None | ||
if index: | ||
roman = non_italics.pop(index) | ||
roman_italic.append((roman, italic)) | ||
|
||
# Now add all the remaining non-italic fonts | ||
for roman in non_italics: | ||
roman_italic.append((roman, None)) | ||
|
||
return roman_italic | ||
|
||
|
||
@check( | ||
id="opentype/italic_axis_last", | ||
conditions=["style", "has_STAT_table"], | ||
id="opentype/STAT/ital_axis", | ||
rationale=""" | ||
Check that the 'ital' STAT axis is last in axis order. | ||
Check that related Upright and Italic VFs have an | ||
'ital' axis in the STAT table; that if there is a stat axis, | ||
it is the last one, and that it is boolean and set correctly. | ||
""", | ||
proposal="https://github.com/fonttools/fontbakery/issues/3669", | ||
proposal=[ | ||
"https://github.com/fonttools/fontbakery/issues/2934", | ||
"https://github.com/fonttools/fontbakery/issues/3668", | ||
"https://github.com/fonttools/fontbakery/issues/3669", | ||
], | ||
) | ||
def check_italic_axis_last(ttFont, style): | ||
"""Ensure 'ital' STAT axis is last.""" | ||
|
||
def get_STAT_axis(font, tag): | ||
for axis in font["STAT"].table.DesignAxisRecord.Axis: | ||
if axis.AxisTag == tag: | ||
return axis | ||
return None | ||
|
||
axis = get_STAT_axis(ttFont, "ital") | ||
if not axis: | ||
yield SKIP, "No 'ital' axis in STAT." | ||
return | ||
def check_STAT_ital_axis(fonts, config): | ||
"""Ensure VFs have 'ital' STAT axis.""" | ||
|
||
if ttFont["STAT"].table.DesignAxisRecord.Axis[-1].AxisTag != "ital": | ||
yield WARN, Message( | ||
"ital-axis-not-last", | ||
"STAT table 'ital' axis is not the last in the axis order.", | ||
) | ||
else: | ||
yield PASS, "STAT table ital axis order is good." | ||
for roman, italic in segment_vf_collection(fonts): | ||
if roman and italic: | ||
# These should definitely both have an ital axis | ||
yield from check_has_ital(roman) | ||
yield from check_has_ital(italic) | ||
yield from check_ital_is_binary_and_last(roman, False) | ||
yield from check_ital_is_binary_and_last(italic, True) | ||
elif italic: | ||
yield FAIL, Message( | ||
"missing-roman", | ||
f"Italic font {italic} has no matching Roman font.", | ||
) | ||
elif roman: | ||
yield from check_ital_is_binary_and_last(roman, False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.