-
Notifications
You must be signed in to change notification settings - Fork 104
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bfb01c0
commit c74ead6
Showing
34 changed files
with
1,171 additions
and
1,190 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
106 changes: 106 additions & 0 deletions
106
Lib/fontbakery/checks/vendorspecific/googlefonts/STAT/axisregistry.py
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 |
---|---|---|
@@ -0,0 +1,106 @@ | ||
from fontbakery.prelude import check, Message, INFO, FAIL | ||
from fontbakery.constants import PlatformID, WindowsEncodingID, WindowsLanguageID | ||
from fontbakery.checks.vendorspecific.googlefonts.utils import GFAxisRegistry | ||
|
||
|
||
@check( | ||
id="googlefonts/STAT/axisregistry", | ||
rationale=""" | ||
Check that particle names and values on STAT table match the fallback names | ||
in each axis entry at the Google Fonts Axis Registry, available at | ||
https://github.com/google/fonts/tree/main/axisregistry | ||
""", | ||
conditions=["is_variable_font"], | ||
proposal="https://github.com/fonttools/fontbakery/issues/3022", | ||
) | ||
def check_STAT_axisregistry_names(ttFont): | ||
""" | ||
Validate STAT particle names and values match the fallback names in GFAxisRegistry. | ||
""" | ||
|
||
def normalize_name(name): | ||
return "".join(name.split(" ")) | ||
|
||
format4_entries = False | ||
if "STAT" not in ttFont: | ||
yield FAIL, "Font is missing STAT table." | ||
return | ||
axis_value_array = ttFont["STAT"].table.AxisValueArray | ||
if not axis_value_array: | ||
yield FAIL, Message( | ||
"missing-axis-values", "STAT table is missing Axis Value Records" | ||
) | ||
return | ||
|
||
for axis_value in axis_value_array.AxisValue: | ||
if axis_value.Format == 4: | ||
coords = [] | ||
for record in axis_value.AxisValueRecord: | ||
axis = ttFont["STAT"].table.DesignAxisRecord.Axis[record.AxisIndex] | ||
coords.append(f"{axis.AxisTag}:{record.Value}") | ||
coords = ", ".join(coords) | ||
|
||
name_entry = ttFont["name"].getName( | ||
axis_value.ValueNameID, | ||
PlatformID.WINDOWS, | ||
WindowsEncodingID.UNICODE_BMP, | ||
WindowsLanguageID.ENGLISH_USA, | ||
) | ||
format4_entries = True | ||
yield INFO, Message("format-4", f"'{name_entry.toUnicode()}' at ({coords})") | ||
continue | ||
|
||
axis = ttFont["STAT"].table.DesignAxisRecord.Axis[axis_value.AxisIndex] | ||
# If a family has a MORF axis, we allow users to define their own | ||
# axisValues for this axis. | ||
if axis.AxisTag == "MORF": | ||
continue | ||
if axis.AxisTag in GFAxisRegistry().keys(): | ||
fallbacks = GFAxisRegistry()[axis.AxisTag].fallback | ||
fallbacks = {f.name: f.value for f in fallbacks} | ||
|
||
# Here we assume that it is enough to check for only the Windows, | ||
# English USA entry corresponding to a given nameID. It is up to other | ||
# checks to ensure all different platform/encoding entries | ||
# with a given nameID are consistent in the name table. | ||
name_entry = ttFont["name"].getName( | ||
axis_value.ValueNameID, | ||
PlatformID.WINDOWS, | ||
WindowsEncodingID.UNICODE_BMP, | ||
WindowsLanguageID.ENGLISH_USA, | ||
) | ||
|
||
# Here "name_entry" has the user-friendly name of the current AxisValue | ||
# We want to ensure that this string shows up as a "fallback" name | ||
# on the GF Axis Registry for this specific variation axis tag. | ||
name = normalize_name(name_entry.toUnicode()) | ||
expected_names = [normalize_name(n) for n in fallbacks.keys()] | ||
if hasattr(axis_value, "Value"): # Format 1 & 3 | ||
is_value = axis_value.Value | ||
elif hasattr(axis_value, "NominalValue"): # Format 2 | ||
is_value = axis_value.NominalValue | ||
if name not in expected_names: | ||
expected_names = ", ".join(expected_names) | ||
yield FAIL, Message( | ||
"invalid-name", | ||
f"On the font variation axis '{axis.AxisTag}'," | ||
f" the name '{name_entry.toUnicode()}'" | ||
f" is not among the expected ones ({expected_names}) according" | ||
" to the Google Fonts Axis Registry.", | ||
) | ||
elif is_value != fallbacks[name_entry.toUnicode()]: | ||
yield FAIL, Message( | ||
"bad-coordinate", | ||
f"Axis Value for '{axis.AxisTag}':'{name_entry.toUnicode()}' is" | ||
f" expected to be '{fallbacks[name_entry.toUnicode()]}' but this" | ||
f" font has '{name_entry.toUnicode()}'='{axis_value.Value}'.", | ||
) | ||
|
||
if format4_entries: | ||
yield INFO, Message( | ||
"format-4", | ||
"The GF Axis Registry does not currently contain fallback names" | ||
" for the combination of values for more than a single axis," | ||
" which is what these 'format 4' entries are designed to describe," | ||
" so this check will ignore them for now.", | ||
) |
111 changes: 111 additions & 0 deletions
111
Lib/fontbakery/checks/vendorspecific/googlefonts/STAT/compulsory_axis_values.py
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 |
---|---|---|
@@ -0,0 +1,111 @@ | ||
from fontbakery.prelude import check, Message, FAIL | ||
from fontbakery.utils import markdown_table | ||
|
||
|
||
@check( | ||
id="googlefonts/STAT/compulsory_axis_values", | ||
conditions=["is_variable_font", "expected_font_names"], | ||
rationale=""" | ||
Check a font's STAT table contains compulsory Axis Values which exist | ||
in the Google Fonts Axis Registry. | ||
We cannot determine what Axis Values the user will set for axes such as | ||
opsz, GRAD since these axes are unique for each font so we'll skip them. | ||
""", | ||
proposal="https://github.com/fonttools/fontbakery/pull/3800", | ||
) | ||
def check_stat(ttFont, expected_font_names): | ||
"""Check a font's STAT table contains compulsory Axis Values.""" | ||
if "STAT" not in ttFont: | ||
yield FAIL, "Font is missing STAT table" | ||
return | ||
|
||
AXES_TO_CHECK = { | ||
"CASL", | ||
"CRSV", | ||
"FILL", | ||
"FLAR", | ||
"MONO", | ||
"SOFT", | ||
"VOLM", | ||
"wdth", | ||
"wght", | ||
"WONK", | ||
} | ||
|
||
def stat_axis_values(ttFont): | ||
name = ttFont["name"] | ||
stat = ttFont["STAT"].table | ||
axes = [a.AxisTag for a in stat.DesignAxisRecord.Axis] | ||
res = {} | ||
if ttFont["STAT"].table.AxisValueCount == 0: | ||
return res | ||
axis_values = stat.AxisValueArray.AxisValue | ||
for ax in axis_values: | ||
# Google Fonts axis registry cannot check format 4 Axis Values | ||
if ax.Format == 4: | ||
continue | ||
axis_tag = axes[ax.AxisIndex] | ||
if axis_tag not in AXES_TO_CHECK: | ||
continue | ||
ax_name = name.getName(ax.ValueNameID, 3, 1, 0x409).toUnicode() | ||
if ax.Format == 2: | ||
value = ax.NominalValue | ||
else: | ||
value = ax.Value | ||
res[(axis_tag, ax_name)] = { | ||
"Axis": axis_tag, | ||
"Name": ax_name, | ||
"Flags": ax.Flags, | ||
"Value": value, | ||
"LinkedValue": None | ||
if not hasattr(ax, "LinkedValue") | ||
else ax.LinkedValue, | ||
} | ||
return res | ||
|
||
font_axis_values = stat_axis_values(ttFont) | ||
expected_axis_values = stat_axis_values(expected_font_names) | ||
|
||
table = [] | ||
for axis, name in set(font_axis_values.keys()) | set(expected_axis_values.keys()): | ||
row = {} | ||
key = (axis, name) | ||
if key in font_axis_values: | ||
row["Name"] = name | ||
row["Axis"] = axis | ||
row["Current Value"] = font_axis_values[key]["Value"] | ||
row["Current Flags"] = font_axis_values[key]["Flags"] | ||
row["Current LinkedValue"] = font_axis_values[key]["LinkedValue"] | ||
else: | ||
row["Name"] = name | ||
row["Axis"] = axis | ||
row["Current Value"] = "N/A" | ||
row["Current Flags"] = "N/A" | ||
row["Current LinkedValue"] = "N/A" | ||
if key in expected_axis_values: | ||
row["Name"] = name | ||
row["Axis"] = axis | ||
row["Expected Value"] = expected_axis_values[key]["Value"] | ||
row["Expected Flags"] = expected_axis_values[key]["Flags"] | ||
row["Expected LinkedValue"] = expected_axis_values[key]["LinkedValue"] | ||
else: | ||
row["Name"] = name | ||
row["Axis"] = axis | ||
row["Expected Value"] = "N/A" | ||
row["Expected Flags"] = "N/A" | ||
row["Expected LinkedValue"] = "N/A" | ||
table.append(row) | ||
table.sort(key=lambda k: (k["Axis"], str(k["Expected Value"]))) | ||
md_table = markdown_table(table) | ||
|
||
is_italic = any(a.axisTag in ["ital", "slnt"] for a in ttFont["fvar"].axes) | ||
missing_ital_av = any("Italic" in r["Name"] for r in table) | ||
if is_italic and missing_ital_av: | ||
yield FAIL, Message("missing-ital-axis-values", "Italic Axis Value missing.") | ||
|
||
if font_axis_values != expected_axis_values: | ||
yield FAIL, Message( | ||
"bad-axis-values", | ||
f"Compulsory STAT Axis Values are incorrect:\n\n{md_table}\n\n", | ||
) |
44 changes: 44 additions & 0 deletions
44
Lib/fontbakery/checks/vendorspecific/googlefonts/axes_match.py
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 |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from fontbakery.prelude import FAIL, PASS, Message, check | ||
|
||
|
||
@check( | ||
id="googlefonts/axes_match", | ||
conditions=["is_variable_font", "remote_style"], | ||
rationale=""" | ||
An updated font family must include the same axes found in the Google " | ||
Fonts version, with the same axis ranges. | ||
""", | ||
) | ||
def check_axes_match(ttFont, remote_style): | ||
"""Check if the axes match between the font and the Google Fonts version.""" | ||
remote_axes = { | ||
a.axisTag: (a.minValue, a.maxValue) for a in remote_style["fvar"].axes | ||
} | ||
font_axes = {a.axisTag: (a.minValue, a.maxValue) for a in ttFont["fvar"].axes} | ||
|
||
missing_axes = [] | ||
for axis, remote_axis_range in remote_axes.items(): | ||
if axis not in font_axes: | ||
missing_axes.append(axis) | ||
continue | ||
axis_range = font_axes[axis] | ||
axis_min, axis_max = axis_range | ||
remote_axis_min, remote_axis_max = remote_axis_range | ||
if axis_min > remote_axis_min: | ||
yield FAIL, Message( | ||
"axis-min-out-of-range", | ||
f"Axis '{axis}' min value is out of range." | ||
f" Expected '{remote_axis_min}', got '{axis_min}'.", | ||
) | ||
if axis_max < remote_axis_max: | ||
yield FAIL, Message( | ||
"axis-max-out-of-range", | ||
f"Axis {axis} max value is out of range." | ||
f" Expected {remote_axis_max}, got {axis_max}.", | ||
) | ||
|
||
if missing_axes: | ||
missing_axes = ", ".join(missing_axes) | ||
yield FAIL, Message("missing-axes", f"Missing axes: {missing_axes}") | ||
else: | ||
yield PASS, "Axes match Google Fonts version." |
Oops, something went wrong.