diff --git a/Lib/fontbakery/callable.py b/Lib/fontbakery/callable.py index f73b6a4981..f3090f27a1 100644 --- a/Lib/fontbakery/callable.py +++ b/Lib/fontbakery/callable.py @@ -150,7 +150,14 @@ def __init__( name = None, # very short text conditions=None, # arguments_setup=None, - documentation=None, # long text, markdown? + documentation=None, # markdown? + rationale=None, # long text explaining why this test is needed. Using markdown, perhaps? + affects=None, # A list of tuples each indicating Browser/OS/Application + # and the affected versions range. + request=None, # An URL to the original request for implementation of this test. + # This is typically a github issue tracker URL. + example_failures=None, # A reference to some font or family that originally failed due to + # the problems that this test tries to detect and report. advancedMessageSetup=None, priority=None ): diff --git a/Lib/fontbakery/specifications/googlefonts.py b/Lib/fontbakery/specifications/googlefonts.py index a02eb4942e..514837e00f 100644 --- a/Lib/fontbakery/specifications/googlefonts.py +++ b/Lib/fontbakery/specifications/googlefonts.py @@ -4570,3 +4570,39 @@ def check_name_table_TYPOGRAPHIC_SUBFAMILY_NAME(ttFont, style): unidecode(string)) if not failed: yield PASS, "TYPOGRAPHIC_SUBFAMILY_NAME entries are all good." + + +@register_test +@test( + id='com.google.fonts/test/163' + , rationale = """According to a Glyphs tutorial (available at https://glyphsapp.com/tutorials/multiple-masters-part-3-setting-up-instances), in order to make sure all versions of Windows recognize it as a valid font file, we must make sure that the concatenated length of the familyname (NAMEID_FONT_FAMILY_NAME) and style (NAMEID_FONT_SUBFAMILY_NAME) strings in the name table do not exceed 20 characters.""" # Somebody with access to Windows should make some tests and confirm that this is really the case. + , affects = [('Windows', 'unspecified')] + , request = 'https://github.com/googlefonts/fontbakery/issues/1488' + , example_failures = None +) +def com_google_fonts_test_163(ttFont): + """ Combined length of family and style must not exceed 20 characters. """ + from unidecode import unidecode + from fontbakery.utils import (get_name_entries, + get_name_entry_strings) + from fontbakery.constants import (NAMEID_FONT_FAMILY_NAME, + NAMEID_FONT_SUBFAMILY_NAME, + PLATID_STR) + failed = False + for familyname in get_name_entries(ttFont, NAMEID_FONT_FAMILY_NAME): + # we'll only match family/style name entries with the same platform ID: + plat = familyname.platformID + familyname_str = familyname.string.decode(familyname.getEncoding()) + for stylename_str in get_name_entry_strings(ttFont, + NAMEID_FONT_SUBFAMILY_NAME, + platformID=plat): + if len(familyname_str + stylename_str) > 20: + failed = True + yield FAIL, ("The combined length of family and style" + " exceeds 20 chars in the following '{}' entries:" + " FONT_FAMILY_NAME = '{}' / SUBFAMILY_NAME = '{}'" + "").format(PLATID_STR[plat], + unidecode(familyname_str), + unidecode(stylename_str)) + if not failed: + yield PASS, "All name entries are good." diff --git a/Lib/fontbakery/specifications/googlefonts_test.py b/Lib/fontbakery/specifications/googlefonts_test.py index b35a64311a..5268ba68bf 100644 --- a/Lib/fontbakery/specifications/googlefonts_test.py +++ b/Lib/fontbakery/specifications/googlefonts_test.py @@ -1832,3 +1832,37 @@ def test_id_159(): assert status == FAIL # restore it: ttFont["name"].names[index].string = backup + +# TODO: test_id_160 +# TODO: test_id_161 +# TODO: test_id_162 + +def test_id_163(): + """ Check font name is the same as family name. """ + from fontbakery.specifications.googlefonts import com_google_fonts_test_163 as test + from fontbakery.constants import (NAMEID_FONT_FAMILY_NAME, + NAMEID_FONT_SUBFAMILY_NAME) + # Our reference Cabin Regular is known to be good + ttFont = TTFont("data/test/cabin/Cabin-Regular.ttf") + + # So it must PASS the test: + print ("Test PASS with a good font...") + status, message = list(test(ttFont))[-1] + assert status == PASS + + # Then we FAIL with the long family/style names + # that were used as an example on the glyphs tutorial + # (at https://glyphsapp.com/tutorials/multiple-masters-part-3-setting-up-instances): + for index, name in enumerate(ttFont["name"].names): + if name.nameID == NAMEID_FONT_FAMILY_NAME: + ttFont["name"].names[index].string = "ImpossibleFamilyNameFont".encode(name.getEncoding()) + break + + for index, name in enumerate(ttFont["name"].names): + if name.nameID == NAMEID_FONT_SUBFAMILY_NAME: + ttFont["name"].names[index].string = "WithAVeryLongStyleName".encode(name.getEncoding()) + break + + print ("Test FAIL with a bad font...") + status, message = list(test(ttFont))[-1] + assert status == FAIL diff --git a/Lib/fontbakery/utils.py b/Lib/fontbakery/utils.py index e68056ab06..6f208f586f 100644 --- a/Lib/fontbakery/utils.py +++ b/Lib/fontbakery/utils.py @@ -49,21 +49,30 @@ def get_bounding_box(font): return ymin, ymax -def get_name_entry_strings(font, - nameID, - platformID=None, - encodingID=None, - langID=None): +def get_name_entries(font, + nameID, + platformID=None, + encodingID=None, + langID=None): results = [] for entry in font['name'].names: if entry.nameID == nameID and \ (platformID is None or entry.platformID == platformID) and \ (encodingID is None or entry.platEncID == encodingID) and \ (langID is None or entry.langID == langID): - results.append(entry.string.decode(entry.getEncoding())) + results.append(entry) return results +def get_name_entry_strings(font, + nameID, + platformID=None, + encodingID=None, + langID=None): + entries = get_name_entries(font, nameID, platformID, encodingID, langID) + return map(lambda e: e.string.decode(e.getEncoding()), entries) + + def name_entry_id(name): from fontbakery.constants import (NAMEID_STR, PLATID_STR)