-
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.
splitting out a bit more of googlefonts checks
- Loading branch information
1 parent
81528d5
commit da87474
Showing
59 changed files
with
2,169 additions
and
2,117 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
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
Empty file.
File renamed without changes.
91 changes: 91 additions & 0 deletions
91
Lib/fontbakery/checks/vendorspecific/googlefonts/cjk_vertical_metrics.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,91 @@ | ||
import os | ||
|
||
from fontbakery.prelude import check, Message, FAIL, WARN | ||
from fontbakery.utils import typo_metrics_enabled | ||
|
||
|
||
@check( | ||
id="googlefonts/cjk_vertical_metrics", | ||
conditions=["is_cjk_font", "not listed_on_gfonts_api"], | ||
rationale=""" | ||
CJK fonts have different vertical metrics when compared to Latin fonts. | ||
We follow the schema developed by dr Ken Lunde for Source Han Sans and | ||
the Noto CJK fonts. | ||
Our documentation includes further information: | ||
https://github.com/googlefonts/gf-docs/tree/main/Spec#cjk-vertical-metrics | ||
""", | ||
proposal="https://github.com/fonttools/fontbakery/pull/2797", | ||
) | ||
def check_cjk_vertical_metrics(ttFont): | ||
"""Check font follows the Google Fonts CJK vertical metric schema""" | ||
|
||
filename = os.path.basename(ttFont.reader.file.name) | ||
|
||
# Check necessary tables are present. | ||
missing_tables = False | ||
required = ["OS/2", "hhea", "head"] | ||
for key in required: | ||
if key not in ttFont: | ||
missing_tables = True | ||
yield FAIL, Message(f"lacks-{key}", f"{filename} lacks a '{key}' table.") | ||
|
||
if missing_tables: | ||
return | ||
|
||
font_upm = ttFont["head"].unitsPerEm | ||
font_metrics = { | ||
"OS/2.sTypoAscender": ttFont["OS/2"].sTypoAscender, | ||
"OS/2.sTypoDescender": ttFont["OS/2"].sTypoDescender, | ||
"OS/2.sTypoLineGap": ttFont["OS/2"].sTypoLineGap, | ||
"hhea.ascent": ttFont["hhea"].ascent, | ||
"hhea.descent": ttFont["hhea"].descent, | ||
"hhea.lineGap": ttFont["hhea"].lineGap, | ||
"OS/2.usWinAscent": ttFont["OS/2"].usWinAscent, | ||
"OS/2.usWinDescent": ttFont["OS/2"].usWinDescent, | ||
} | ||
expected_metrics = { | ||
"OS/2.sTypoAscender": round(font_upm * 0.88), | ||
"OS/2.sTypoDescender": round(font_upm * -0.12), | ||
"OS/2.sTypoLineGap": 0, | ||
"hhea.lineGap": 0, | ||
} | ||
|
||
# Check fsSelection bit 7 is not enabled | ||
if typo_metrics_enabled(ttFont): | ||
yield FAIL, Message( | ||
"bad-fselection-bit7", "OS/2 fsSelection bit 7 must be disabled" | ||
) | ||
|
||
# Check typo metrics and hhea lineGap match our expected values | ||
for k in expected_metrics: | ||
if font_metrics[k] != expected_metrics[k]: | ||
yield FAIL, Message( | ||
f"bad-{k}", | ||
f'{k} is "{font_metrics[k]}" it should be {expected_metrics[k]}', | ||
) | ||
|
||
# Check hhea and win values match | ||
if font_metrics["hhea.ascent"] != font_metrics["OS/2.usWinAscent"]: | ||
yield FAIL, Message( | ||
"ascent-mismatch", "hhea.ascent must match OS/2.usWinAscent" | ||
) | ||
|
||
if abs(font_metrics["hhea.descent"]) != font_metrics["OS/2.usWinDescent"]: | ||
yield FAIL, Message( | ||
"descent-mismatch", | ||
"hhea.descent must match absolute value of OS/2.usWinDescent", | ||
) | ||
|
||
# Check the sum of the hhea metrics is between 1.1-1.5x of the font's upm | ||
hhea_sum = ( | ||
font_metrics["hhea.ascent"] | ||
+ abs(font_metrics["hhea.descent"]) | ||
+ font_metrics["hhea.lineGap"] | ||
) / font_upm | ||
if not 1.1 < hhea_sum <= 1.5: | ||
yield WARN, Message( | ||
"bad-hhea-range", | ||
f"We recommend the absolute sum of the hhea metrics should be" | ||
f" between 1.1-1.4x of the font's upm. This font has {hhea_sum}x", | ||
) |
52 changes: 52 additions & 0 deletions
52
Lib/fontbakery/checks/vendorspecific/googlefonts/cjk_vertical_metrics_regressions.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,52 @@ | ||
from fontbakery.prelude import check, Message, FAIL | ||
|
||
|
||
@check( | ||
id="googlefonts/cjk_vertical_metrics_regressions", | ||
conditions=["is_cjk_font", "regular_remote_style", "regular_ttFont"], | ||
rationale=""" | ||
Check CJK family has the same vertical metrics as the same family | ||
hosted on Google Fonts. | ||
""", | ||
proposal="https://github.com/fonttools/fontbakery/pull/3244", | ||
) | ||
def check_cjk_vertical_metrics_regressions(regular_ttFont, regular_remote_style): | ||
"""Check if the vertical metrics of a CJK family are similar to the same | ||
family hosted on Google Fonts.""" | ||
import math | ||
|
||
gf_ttFont = regular_remote_style | ||
ttFont = regular_ttFont | ||
|
||
if not ttFont: | ||
yield FAIL, Message( | ||
"couldnt-find-local-regular", | ||
"Could not identify a local Regular style font", | ||
) | ||
return | ||
if not gf_ttFont: | ||
yield FAIL, Message( | ||
"couldnt-find-remote-regular", | ||
"Could not identify a Regular style font hosted on Google Fonts", | ||
) | ||
return | ||
|
||
upm_scale = ttFont["head"].unitsPerEm / gf_ttFont["head"].unitsPerEm | ||
|
||
for tbl, attrib in [ | ||
("OS/2", "sTypoAscender"), | ||
("OS/2", "sTypoDescender"), | ||
("OS/2", "sTypoLineGap"), | ||
("OS/2", "usWinAscent"), | ||
("OS/2", "usWinDescent"), | ||
("hhea", "ascent"), | ||
("hhea", "descent"), | ||
("hhea", "lineGap"), | ||
]: | ||
gf_val = math.ceil(getattr(gf_ttFont[tbl], attrib) * upm_scale) | ||
f_val = math.ceil(getattr(ttFont[tbl], attrib)) | ||
if gf_val != f_val: | ||
yield FAIL, Message( | ||
"cjk-metric-regression", | ||
f" {tbl} {attrib} is {f_val}" f" when it should be {gf_val}", | ||
) |
36 changes: 36 additions & 0 deletions
36
Lib/fontbakery/checks/vendorspecific/googlefonts/family/has_license.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,36 @@ | ||
import os | ||
|
||
from fontbakery.prelude import check, Message, FAIL | ||
from fontbakery.utils import pretty_print_list | ||
|
||
|
||
@check( | ||
id="googlefonts/family/has_license", | ||
conditions=["gfonts_repo_structure"], | ||
rationale=""" | ||
A license file is required for all fonts in the Google Fonts collection. | ||
This checks that the font's directory contains a file named OFL.txt or | ||
LICENSE.txt. | ||
""", | ||
proposal="https://github.com/fonttools/fontbakery/issues/4829", # legacy check | ||
) | ||
def check_family_has_license(licenses, config): | ||
"""Check font has a license.""" | ||
|
||
if len(licenses) > 1: | ||
filenames = pretty_print_list( | ||
config, [os.path.basename(license) for license in licenses] | ||
) | ||
yield FAIL, Message( | ||
"multiple", | ||
f"More than a single license file found: {filenames}", | ||
) | ||
elif not licenses: | ||
yield FAIL, Message( | ||
"no-license", | ||
"No license file was found." | ||
" Please add an OFL.txt or a LICENSE.txt file." | ||
" If you are running fontbakery on a Google Fonts" | ||
" upstream repo, which is fine, just make sure" | ||
" there is a temporary license file in the same folder.", | ||
) |
109 changes: 109 additions & 0 deletions
109
Lib/fontbakery/checks/vendorspecific/googlefonts/family_name_compliance.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,109 @@ | ||
from fontbakery.prelude import check, Message, PASS, FAIL | ||
from fontbakery.constants import NameID | ||
|
||
|
||
@check( | ||
id="googlefonts/family_name_compliance", | ||
rationale=""" | ||
Checks the family name for compliance with the Google Fonts Guide. | ||
https://googlefonts.github.io/gf-guide/onboarding.html#new-fonts | ||
If you want to have your family name added to the CamelCase | ||
exceptions list, please submit a pull request to the | ||
camelcased_familyname_exceptions.txt file. | ||
Similarly, abbreviations can be submitted to the | ||
abbreviations_familyname_exceptions.txt file. | ||
These are located in the Lib/fontbakery/data/googlefonts/ directory | ||
of the FontBakery source code currently hosted at | ||
https://github.com/fonttools/fontbakery/ | ||
""", | ||
conditions=[], | ||
proposal="https://github.com/fonttools/fontbakery/issues/4049", | ||
) | ||
def check_family_name_compliance(ttFont): | ||
"""Check family name for GF Guide compliance.""" | ||
import re | ||
from pkg_resources import resource_filename | ||
from fontbakery.utils import get_name_entries | ||
|
||
camelcase_exceptions_txt = "data/googlefonts/camelcased_familyname_exceptions.txt" | ||
abbreviations_exceptions_txt = ( | ||
"data/googlefonts/abbreviations_familyname_exceptions.txt" | ||
) | ||
|
||
if get_name_entries(ttFont, NameID.TYPOGRAPHIC_FAMILY_NAME): | ||
family_name = get_name_entries(ttFont, NameID.TYPOGRAPHIC_FAMILY_NAME)[ | ||
0 | ||
].toUnicode() | ||
else: | ||
family_name = get_name_entries(ttFont, NameID.FONT_FAMILY_NAME)[0].toUnicode() | ||
|
||
# CamelCase | ||
if bool(re.match(r"([A-Z][a-z]+){2,}", family_name)): | ||
known_exception = False | ||
|
||
# Process exceptions | ||
filename = resource_filename("fontbakery", camelcase_exceptions_txt) | ||
for exception in open(filename, "r", encoding="utf-8").readlines(): | ||
exception = exception.split("#")[0].strip() | ||
if exception == "": | ||
continue | ||
if exception in family_name: | ||
known_exception = True | ||
yield PASS, Message( | ||
"known-camelcase-exception", | ||
"Family name is a known exception to the CamelCase rule.", | ||
) | ||
break | ||
|
||
if not known_exception: | ||
yield FAIL, Message( | ||
"camelcase", | ||
f'"{family_name}" is a CamelCased name.' | ||
f" To solve this, simply use spaces" | ||
f" instead in the font name.", | ||
) | ||
|
||
# Abbreviations | ||
if bool(re.match(r"([A-Z]){2,}", family_name)): | ||
known_exception = False | ||
|
||
# Process exceptions | ||
filename = resource_filename("fontbakery", abbreviations_exceptions_txt) | ||
for exception in open(filename, "r", encoding="utf-8").readlines(): | ||
exception = exception.split("#")[0].strip() | ||
if exception == "": | ||
continue | ||
if exception in family_name: | ||
known_exception = True | ||
yield PASS, Message( | ||
"known-abbreviation-exception", | ||
"Family name is a known exception to the abbreviation rule.", | ||
) | ||
break | ||
|
||
if not known_exception: | ||
# Allow SC ending | ||
if not family_name.endswith("SC"): | ||
yield FAIL, Message( | ||
"abbreviation", f'"{family_name}" contains an abbreviation.' | ||
) | ||
|
||
# Allowed characters | ||
forbidden_characters = re.findall(r"[^a-zA-Z0-9 ]", family_name) | ||
if forbidden_characters: | ||
forbidden_characters = "".join(sorted(list(set(forbidden_characters)))) | ||
yield FAIL, Message( | ||
"forbidden-characters", | ||
f'"{family_name}" contains the following characters' | ||
f' which are not allowed: "{forbidden_characters}".', | ||
) | ||
|
||
# Starts with uppercase | ||
if not bool(re.match(r"^[A-Z]", family_name)): | ||
yield FAIL, Message( | ||
"starts-with-not-uppercase", | ||
f'"{family_name}" doesn\'t start with an uppercase letter.', | ||
) |
Oops, something went wrong.