"""
Checks for Fontwerk <https://fontwerk.com/>
"""
from fontbakery.callable import check
from fontbakery.section import Section
from fontbakery.status import PASS, FAIL
from fontbakery.fonts_profile import profile_factory
from fontbakery.message import Message
from fontbakery.profiles.googlefonts import GOOGLEFONTS_PROFILE_CHECKS
from fontbakery.constants import FsSelection, MacStyle
profile_imports = ("fontbakery.profiles.googlefonts",)
profile = profile_factory(default_section=Section("Fontwerk"))
# FIXME: It would be much better to refactor this as described at:
# https://github.com/fonttools/fontbakery/issues/3585
profile.configuration_defaults = {
"com.google.fonts/check/file_size": {
"WARN_SIZE": 1 * 1024 * 1024,
"FAIL_SIZE": 9 * 1024 * 1024,
}
}
# Note: I had to use a function here in order to display it
# in the auto-generated Sphinx docs due to this bug:
# https://stackoverflow.com/questions/31561895/literalinclude-how-to-include-only-a-variable-using-pyobject
[docs]def leave_this_one_out(checkid):
CHECKS_NOT_TO_INCLUDE = [
# don't run these checks on the Fontwerk profile:
"com.google.fonts/check/canonical_filename",
"com.google.fonts/check/vendor_id",
"com.google.fonts/check/fstype",
"com.google.fonts/check/gasp",
"com.google.fonts/check/name/description_max_length",
"com.google.fonts/check/metadata/includes_production_subsets",
"com.google.fonts/check/font_copyright",
"com.google.fonts/check/version_bump",
"com.google.fonts/check/production_glyphs_similarity",
"com.google.fonts/check/name/line_breaks",
"com.google.fonts/check/fontdata_namecheck",
"com.google.fonts/check/meta/script_lang_tags",
# The following check they may need some improvements
# before we decide to include it:
"com.google.fonts/check/family/italics_have_roman_counterparts",
]
if checkid in CHECKS_NOT_TO_INCLUDE:
return True
FONTWERK_PROFILE_CHECKS = [
checkid for checkid in GOOGLEFONTS_PROFILE_CHECKS if not leave_this_one_out(checkid)
] + [
"com.fontwerk/check/no_mac_entries",
"com.fontwerk/check/vendor_id",
"com.fontwerk/check/weight_class_fvar",
"com.fontwerk/check/inconsistencies_between_fvar_stat",
"com.fontwerk/check/style_linking",
]
[docs]@check(
id="com.fontwerk/check/no_mac_entries",
rationale="""
Mac name table entries are not needed anymore. Even Apple stopped producing
name tables with platform 1. Please see for example the following system font:
/System/Library/Fonts/SFCompact.ttf
Also, Dave Opstad, who developed Apple's TrueType specifications, told
Olli Meier a couple years ago (as of January/2022) that these entries are
outdated and should not be produced anymore.
""",
proposal="https://github.com/googlefonts/gftools/issues/469",
)
def com_fontwerk_check_name_no_mac_entries(ttFont):
"""Check if font has Mac name table entries (platform=1)"""
passed = True
for rec in ttFont["name"].names:
if rec.platformID == 1:
yield FAIL, Message("mac-names", f"Please remove name ID {rec.nameID}")
passed = False
if passed:
yield PASS, "No Mac name table entries."
[docs]@check(
id="com.fontwerk/check/vendor_id",
rationale="""
Vendor ID must be WERK for Fontwerk fonts.
""",
proposal="https://github.com/fonttools/fontbakery/pull/3579",
)
def com_fontwerk_check_vendor_id(ttFont):
"""Checking OS/2 achVendID."""
vendor_id = ttFont["OS/2"].achVendID
if vendor_id != "WERK":
yield FAIL, Message(
"bad-vendor-id", f"OS/2 VendorID is '{vendor_id}', but should be 'WERK'."
)
else:
yield PASS, f"OS/2 VendorID '{vendor_id}' is correct."
[docs]@check(
id="com.fontwerk/check/weight_class_fvar",
rationale="""
According to Microsoft's OT Spec the OS/2 usWeightClass
should match the fvar default value.
""",
conditions=["is_variable_font", "has_wght_axis"],
proposal="https://github.com/googlefonts/gftools/issues/477",
)
def com_fontwerk_check_weight_class_fvar(ttFont):
"""Checking if OS/2 usWeightClass matches fvar."""
fvar = ttFont["fvar"]
default_axis_values = {a.axisTag: a.defaultValue for a in fvar.axes}
fvar_value = default_axis_values.get("wght")
os2_value = ttFont["OS/2"].usWeightClass
if os2_value != int(fvar_value):
yield FAIL, Message(
"bad-weight-class",
f"OS/2 usWeightClass is '{os2_value}', "
f"but should match fvar default value '{fvar_value}'.",
)
else:
yield PASS, f"OS/2 usWeightClass '{os2_value}' matches fvar default value."
[docs]def is_covered_in_stat(ttFont, axis_tag, value):
if "STAT" not in ttFont:
return False
stat_table = ttFont["STAT"].table
if stat_table.AxisValueCount == 0:
return False
for ax_value in stat_table.AxisValueArray.AxisValue:
ax_value_format = ax_value.Format
stat_value = []
if ax_value_format in (1, 2, 3):
axis_tag_stat = stat_table.DesignAxisRecord.Axis[ax_value.AxisIndex].AxisTag
if axis_tag != axis_tag_stat:
continue
if ax_value_format in (1, 3):
stat_value.append(ax_value.Value)
if ax_value_format == 3:
stat_value.append(ax_value.LinkedValue)
if ax_value_format == 2:
stat_value.append(ax_value.NominalValue)
if ax_value_format == 4:
# TODO: Need to implement
# locations check as well
pass
if value in stat_value:
return True
return False
[docs]@check(
id="com.fontwerk/check/inconsistencies_between_fvar_stat",
rationale="""
Check for inconsistencies in names and values between the fvar instances
and STAT table. Inconsistencies may cause issues in apps like Adobe InDesign.
""",
conditions=["is_variable_font"],
proposal="https://github.com/fonttools/fontbakery/pull/3636",
)
def com_fontwerk_check_inconsistencies_between_fvar_stat(ttFont):
"""Checking if STAT entries matches fvar and vice versa."""
if "STAT" not in ttFont:
return FAIL, Message(
"missing-stat-table", "Missing STAT table in variable font."
)
passed = True
fvar = ttFont["fvar"]
name = ttFont["name"]
for ins in fvar.instances:
instance_name = name.getDebugName(ins.subfamilyNameID)
if instance_name is None:
yield FAIL, Message(
"missing-name-id",
f"The name ID {ins.subfamilyNameID} used in an"
f" fvar instance is missing in the name table.",
)
passed = False
continue
for axis_tag, value in ins.coordinates.items():
if not is_covered_in_stat(ttFont, axis_tag, value):
yield FAIL, Message(
"missing-fvar-instance-axis-value",
f"{instance_name}: '{axis_tag}' axis value '{value}'"
f" missing in STAT table.",
)
passed = False
# TODO: Compare fvar instance name with constructed STAT table name.
if passed:
yield PASS, "STAT and fvar axis records are consistent."
[docs]@check(
id="com.fontwerk/check/style_linking",
rationale="""
Look for possible style linking issues.
""",
proposal="https://github.com/googlefonts/noto-fonts/issues/2269",
)
def com_fontwerk_check_style_linking(ttFont):
"""Checking style linking entries"""
from .shared_conditions import is_italic, is_bold
errs = []
if is_bold(ttFont):
if not (ttFont["OS/2"].fsSelection & FsSelection.BOLD):
errs.append("OS/2 fsSelection flag should be (most likely) 'Bold'.")
if not (ttFont["head"].macStyle & MacStyle.BOLD):
errs.append("head macStyle flag should be (most likely) 'Bold'.")
if ttFont["name"].getDebugName(2) not in ("Bold", "Bold Italic"):
name_id_2_should_be = "Bold"
if is_italic(ttFont):
name_id_2_should_be = "Bold Italic"
errs.append(f"name ID should be (most likely) '{name_id_2_should_be}'.")
if is_italic(ttFont):
if "post" in ttFont and not ttFont["post"].italicAngle:
errs.append(
"post talbe italic angle should be (most likely) different to 0."
)
if not (ttFont["OS/2"].fsSelection & FsSelection.ITALIC):
errs.append("OS/2 fsSelection flag should be (most likely) 'Italic'.")
if not (ttFont["head"].macStyle & MacStyle.ITALIC):
errs.append("head macStyle flag should be (most likely) 'Italic'.")
if ttFont["name"].getDebugName(2) not in ("Italic", "Bold Italic"):
name_id_2_should_be = "Italic"
if is_bold(ttFont):
name_id_2_should_be = "Bold Italic"
errs.append(f"name ID should be (most likely) '{name_id_2_should_be}'.")
if not errs:
yield PASS, "Style linking looks good."
for err in errs:
yield FAIL, Message("style-linking-issue", err)
profile.auto_register(
globals(),
filter_func=lambda type, id, _: not (type == "check" and leave_this_one_out(id)),
)
profile.test_expected_checks(FONTWERK_PROFILE_CHECKS, exclusive=True)