def test_check_fsselection_matches_macstyle(): """Check if OS/2 fsSelection matches head macStyle bold and italic bits.""" from fontbakery.profiles.os2 import \ com_adobe_fonts_check_fsselection_matches_macstyle as check from fontbakery.constants import FsSelection test_font_path = TEST_FILE("nunito/Nunito-Regular.ttf") # try a regular (not bold, not italic) font test_font = TTFont(test_font_path) assert_PASS(check(test_font)) # now turn on bold in OS/2.fsSelection, but not in head.macStyle test_font['OS/2'].fsSelection |= FsSelection.BOLD message = assert_results_contain( check(test_font), FAIL, None) # FIXME: This needs a message keyword! assert 'bold' in message # now turn off bold in OS/2.fsSelection so we can focus on italic test_font['OS/2'].fsSelection &= ~FsSelection.BOLD # now turn on italic in OS/2.fsSelection, but not in head.macStyle test_font['OS/2'].fsSelection |= FsSelection.ITALIC message = assert_results_contain( check(test_font), FAIL, None) # FIXME: This needs a message keyword! assert 'italic' in message
def test_check_name_postscript_vs_cff(): from fontbakery.profiles.name import com_adobe_fonts_check_name_postscript_vs_cff as check test_font = TTFont() test_font['CFF '] = fontTools.ttLib.newTable('CFF ') test_font['CFF '].cff.fontNames = ['SomeFontName'] test_font['name'] = fontTools.ttLib.newTable('name') test_font['name'].setName( 'SomeOtherFontName', NameID.POSTSCRIPT_NAME, PlatformID.WINDOWS, WindowsEncodingID.UNICODE_BMP, WindowsLanguageID.ENGLISH_USA ) assert_results_contain(check(test_font), FAIL, 'mismatch') test_font['name'].setName( 'SomeFontName', NameID.POSTSCRIPT_NAME, PlatformID.WINDOWS, WindowsEncodingID.UNICODE_BMP, WindowsLanguageID.ENGLISH_USA ) assert_PASS(check(test_font))
def test_check_name_postscript_name_consistency(): from fontbakery.profiles.name import \ com_adobe_fonts_check_name_postscript_name_consistency as check base_path = portable_path("data/test/source-sans-pro/TTF") font_path = os.path.join(base_path, 'SourceSansPro-Regular.ttf') test_font = TTFont(font_path) # SourceSansPro-Regular only has one name ID 6 entry (for Windows), # let's add another one for Mac that matches the Windows entry: test_font['name'].setName( 'SourceSansPro-Regular', NameID.POSTSCRIPT_NAME, PlatformID.MACINTOSH, MacintoshEncodingID.ROMAN, MacintoshLanguageID.ENGLISH ) assert_PASS(check(test_font)) # ...now let's change the Mac name ID 6 entry to something else: test_font['name'].setName( 'YetAnotherFontName', NameID.POSTSCRIPT_NAME, PlatformID.MACINTOSH, MacintoshEncodingID.ROMAN, MacintoshLanguageID.ENGLISH ) assert_results_contain(check(test_font), FAIL, 'inconsistency')
def test_check_varfont_regular_ital_coord(): """ The variable font 'ital' (Italic) axis coordinate must be zero on the 'Regular' instance. """ from fontbakery.profiles.fvar import com_google_fonts_check_varfont_regular_ital_coord as check from fontbakery.profiles.shared_conditions import regular_ital_coord from fontTools.ttLib.tables._f_v_a_r import Axis # Our reference varfont, CabinVFBeta.ttf, lacks an 'ital' variation axis. ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # So we add one: new_axis = Axis() new_axis.axisTag = "ital" ttFont["fvar"].axes.append(new_axis) # and specify a bad coordinate for the Regular: ttFont["fvar"].instances[0].coordinates["ital"] = 123 # Note: I know the correct instance index for this hotfix because # I inspected the our reference CabinVF using ttx # then we test the code of the regular_ital_coord condition: regular_italic_coord = regular_ital_coord(ttFont) # So it must FAIL the test assert_results_contain(check(ttFont, regular_italic_coord), FAIL, 'non-zero', 'with a bad Regular:ital coordinate (123)...') # We then fix the Regular:ital coordinate: regular_italic_coord = 0 # and now this should PASS the test: assert_PASS(check(ttFont, regular_italic_coord), 'with a good Regular:ital coordinate (zero)...')
def test_check_fsselection_matches_macstyle(): """Check if OS/2 fsSelection matches head macStyle bold and italic bits.""" check = CheckTester(opentype_profile, "com.adobe.fonts/check/fsselection_matches_macstyle") from fontbakery.constants import FsSelection test_font_path = TEST_FILE("nunito/Nunito-Regular.ttf") # try a regular (not bold, not italic) font test_font = TTFont(test_font_path) assert_PASS(check(test_font)) # now turn on bold in OS/2.fsSelection, but not in head.macStyle test_font['OS/2'].fsSelection |= FsSelection.BOLD message = assert_results_contain(check(test_font), FAIL, "fsselection-macstyle-bold") assert 'bold' in message # now turn off bold in OS/2.fsSelection so we can focus on italic test_font['OS/2'].fsSelection &= ~FsSelection.BOLD # now turn on italic in OS/2.fsSelection, but not in head.macStyle test_font['OS/2'].fsSelection |= FsSelection.ITALIC message = assert_results_contain(check(test_font), FAIL, "fsselection-macstyle-italic") assert 'italic' in message
def test_check_varfont_wdth_valid_range(): """ The variable font 'wdth' (Width) axis coordinate must be within spec range of 1 to 1000 on all instances. """ from fontbakery.profiles.fvar import com_google_fonts_check_varfont_wdth_valid_range as check # Our reference varfont, CabinVFBeta.ttf, has # all instances within the 1-1000 range ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # so it must PASS the test: assert_PASS(check(ttFont), 'with a good varfont...') # We then introduce a bad value: ttFont["fvar"].instances[0].coordinates["wdth"] = 0 # And it must FAIL the test assert_results_contain(check(ttFont), FAIL, 'out-of-range', 'with wght=0...') # And yet another bad value: ttFont["fvar"].instances[0].coordinates["wdth"] = 1001 # Should also FAIL: assert_results_contain(check(ttFont), FAIL, 'out-of-range', 'with wght=1001...')
def test_check_varfont_regular_wght_coord(): """ The variable font 'wght' (Weight) axis coordinate must be 400 on the 'Regular' instance. """ from fontbakery.profiles.fvar import com_google_fonts_check_varfont_regular_wght_coord as check from fontbakery.profiles.shared_conditions import regular_wght_coord # Our reference varfont, CabinVFBeta.ttf, has # a good Regular:wght coordinate ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") regular_weight_coord = regular_wght_coord(ttFont) # So it must PASS the test assert_PASS(check(ttFont, regular_weight_coord), 'with a good Regular:wght coordinate...') # We then change the value so it must FAIL: ttFont["fvar"].instances[0].coordinates["wght"] = 500 # Then re-read the coord: regular_weight_coord = regular_wght_coord(ttFont) # and now this should FAIL the test: assert_results_contain(check(ttFont, regular_weight_coord), FAIL, 'not-400', 'with a bad Regular:wght coordinate (500)...')
def test_check_unwanted_tables(): """ Are there unwanted tables ? """ from fontbakery.profiles.universal import com_google_fonts_check_unwanted_tables as check unwanted_tables = [ "FFTM", # FontForge "TTFA", # TTFAutohint "TSI0", # TSI* = VTT "TSI1", "TSI2", "TSI3", "TSI5", "prop", # FIXME: Why is this one unwanted? "MVAR", # Bugs in DirectWrite ] # Our reference Mada Regular font is good here: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) # So it must PASS the check: assert_PASS(check(ttFont), 'with a good font...') # We now add unwanted tables one-by-one to validate the FAIL code-path: for unwanted in unwanted_tables: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) ttFont.reader.tables[unwanted] = "foo" assert_results_contain( check(ttFont), FAIL, None, # FIXME: This needs a message keyword f'with unwanted table {unwanted} ...')
def test_check_whitespace_glyphs(): """ Font contains glyphs for whitespace characters ? """ from fontbakery.profiles.universal import com_google_fonts_check_whitespace_glyphs as check from fontbakery.profiles.shared_conditions import missing_whitespace_chars # Our reference Mada Regular font is good here: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) missing = missing_whitespace_chars(ttFont) # So it must PASS the check: assert_PASS(check(ttFont, missing), 'with a good font...') # Then we remove the nbsp char (0x00A0) so that we get a FAIL: for table in ttFont['cmap'].tables: if 0x00A0 in table.cmap: del table.cmap[0x00A0] missing = missing_whitespace_chars(ttFont) assert_results_contain(check(ttFont, missing), FAIL, 'missing-whitespace-glyph-0x00A0', 'with a font lacking a nbsp (0x00A0)...') # restore original Mada Regular font: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) # And finally remove the space character (0x0020) to get another FAIL: for table in ttFont['cmap'].tables: if 0x0020 in table.cmap: del table.cmap[0x0020] missing = missing_whitespace_chars(ttFont) assert_results_contain(check(ttFont, missing), FAIL, 'missing-whitespace-glyph-0x0020', 'with a font lacking a space (0x0020)...')
def test_check_unique_glyphnames(): """ Font contains unique glyph names? """ import io from fontbakery.profiles.universal import com_google_fonts_check_unique_glyphnames as check test_font_path = TEST_FILE("nunito/Nunito-Regular.ttf") test_font = TTFont(test_font_path) assert_PASS(check(test_font)) # Fonttools renames duplicate glyphs with #1, #2, ... on load. # Code snippet from https://github.com/fonttools/fonttools/issues/149. glyph_names = test_font.getGlyphOrder() glyph_names[2] = glyph_names[3] # Load again, we changed the font directly. test_font = TTFont(test_font_path) test_font.setGlyphOrder(glyph_names) test_font['post'] # Just access the data to make fonttools generate it. test_file = io.BytesIO() test_font.save(test_file) test_font = TTFont(test_file) message = assert_results_contain( check(test_font), FAIL, None) # FIXME: This needs a message keyword assert "space" in message # Upgrade to post format 3.0 and roundtrip data to update TTF object. test_font = TTFont(test_font_path) test_font.setGlyphOrder(glyph_names) test_font["post"].formatType = 3.0 test_file = io.BytesIO() test_font.save(test_file) test_font = TTFont(test_file) assert_SKIP(check(test_font))
def test_check_family_bold_italic_unique_for_nameid1(): """Check that OS/2.fsSelection bold/italic settings are unique within each Compatible Family group (i.e. group of up to 4 with same NameID1)""" from fontbakery.profiles.os2 import \ com_adobe_fonts_check_family_bold_italic_unique_for_nameid1 as check from fontbakery.constants import FsSelection base_path = portable_path("data/test/source-sans-pro/OTF") # these fonts have the same NameID1 font_names = [ 'SourceSansPro-Regular.otf', 'SourceSansPro-Bold.otf', 'SourceSansPro-It.otf', 'SourceSansPro-BoldIt.otf' ] font_paths = [os.path.join(base_path, n) for n in font_names] test_fonts = [TTFont(x) for x in font_paths] # the family should be correctly constructed assert_PASS(check(test_fonts)) # now hack the italic font to also have the bold bit set test_fonts[2]['OS/2'].fsSelection |= FsSelection.BOLD # we should get a failure due to two fonts with both bold & italic set message = assert_results_contain( check(test_fonts), FAIL, None) # FIXME: This needs a message keyword! assert message == ("Family 'Source Sans Pro' has 2 fonts (should be no" " more than 1) with the same OS/2.fsSelection" " bold & italic settings: Bold=True, Italic=True")
def test_check_varfont_regular_opsz_coord(): """ The variable font 'opsz' (Optical Size) axis coordinate should be between 9 and 13 on the 'Regular' instance. """ check = CheckTester(opentype_profile, "com.google.fonts/check/varfont/regular_opsz_coord") from fontTools.ttLib.tables._f_v_a_r import Axis # Our reference varfont, CabinVFBeta.ttf, lacks an 'opsz' variation axis. ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # So we add one: new_axis = Axis() new_axis.axisTag = "opsz" ttFont["fvar"].axes.append(new_axis) # and specify a bad coordinate for the Regular: ttFont["fvar"].instances[0].coordinates["opsz"] = 8 # Note: I know the correct instance index for this hotfix because # I inspected the our reference CabinVF using ttx # Then we ensure the problem is detected: assert_results_contain(check(ttFont), WARN, 'out-of-range', 'with a bad Regular:opsz coordinate (8)...') # We try yet another bad value # and the check should detect the problem: assert_results_contain(check(ttFont, {"regular_opsz_coord": 14}), WARN, 'out-of-range', 'with another bad Regular:opsz value (14)...') # We then test with good default opsz values: for value in [9, 10, 11, 12, 13]: assert_PASS(check(ttFont, {"regular_opsz_coord": value}), f'with a good Regular:opsz coordinate ({value})...')
def test_check_family_underline_thickness(mada_ttFonts): """ Fonts have consistent underline thickness ? """ check = CheckTester(opentype_profile, "com.google.fonts/check/family/underline_thickness") # We start with our reference Mada font family, # which we know has the same value of post.underlineThickness # across all of its font files, based on our inspection # of the file contents using TTX. # # So the check should PASS in this case: assert_PASS(check(mada_ttFonts), 'with a good family.') # Then we introduce the issue by setting a # different underlineThickness value in just # one of the font files: value = mada_ttFonts[0]['post'].underlineThickness incorrect_value = value + 1 mada_ttFonts[0]['post'].underlineThickness = incorrect_value # And now re-running the check on the modified # family should result in a FAIL: assert_results_contain(check(mada_ttFonts), FAIL, "inconsistent-underline-thickness", 'with an inconsistent family.')
def test_check_ttx_roundtrip(): """ Checking with fontTools.ttx """ check = CheckTester(universal_profile, "com.google.fonts/check/ttx-roundtrip") font = TEST_FILE("mada/Mada-Regular.ttf") assert_PASS(check(font))
def test_check_glyf_unused_data(): """ Is there any unused data at the end of the glyf table? """ check = CheckTester(opentype_profile, "com.google.fonts/check/glyf_unused_data") font = TEST_FILE("nunito/Nunito-Regular.ttf") ttFont = TTFont(font) assert_PASS(check(ttFont)) # Always start with a fresh copy, as fT works lazily. Accessing certain data # can prevent the test from working because we rely on uninitialized # behavior. ttFont = TTFont(font) ttFont["loca"].locations.pop() _file = io.BytesIO() ttFont.save(_file) ttFont = TTFont(_file) ttFont.reader.file.name = font assert_results_contain(check(ttFont), FAIL, 'unreachable-data') ttFont = TTFont(font) ttFont["loca"].locations.append(50000) _file = io.BytesIO() ttFont.save(_file) ttFont = TTFont(_file) ttFont.reader.file.name = font assert_results_contain(check(ttFont), FAIL, 'missing-data')
def test_check_family_max_4_fonts_per_family_name(): from fontbakery.profiles.name import \ com_adobe_fonts_check_family_max_4_fonts_per_family_name as check base_path = portable_path("data/test/source-sans-pro/OTF") font_names = [ 'SourceSansPro-Black.otf', 'SourceSansPro-BlackIt.otf', 'SourceSansPro-Bold.otf', 'SourceSansPro-BoldIt.otf', 'SourceSansPro-ExtraLight.otf', 'SourceSansPro-ExtraLightIt.otf', 'SourceSansPro-It.otf', 'SourceSansPro-Light.otf', 'SourceSansPro-LightIt.otf', 'SourceSansPro-Regular.otf', 'SourceSansPro-Semibold.otf', 'SourceSansPro-SemiboldIt.otf' ] font_paths = [os.path.join(base_path, n) for n in font_names] test_fonts = [TTFont(x) for x in font_paths] # try fonts with correct family name grouping assert_PASS(check(test_fonts)) # now set 5 of the fonts to have the same family name for font in test_fonts[:5]: name_records = font['name'].names for name_record in name_records: if name_record.nameID == 1: # print(repr(name_record.string)) name_record.string = 'foobar'.encode('utf-16be') assert_results_contain(check(test_fonts), FAIL, 'too-many')
def test_check_unique_glyphnames(): """ Font contains unique glyph names? """ check = CheckTester(universal_profile, "com.google.fonts/check/unique_glyphnames") ttFont = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf")) assert_PASS(check(ttFont)) # Fonttools renames duplicate glyphs with #1, #2, ... on load. # Code snippet from https://github.com/fonttools/fonttools/issues/149. glyph_names = ttFont.getGlyphOrder() glyph_names[2] = glyph_names[3] # Load again, we changed the font directly. ttFont = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf")) ttFont.setGlyphOrder(glyph_names) ttFont['post'] # Just access the data to make fonttools generate it. _file = io.BytesIO() _file.name = ttFont.reader.file.name ttFont.save(_file) ttFont = TTFont(_file) message = assert_results_contain(check(ttFont), FAIL, "duplicated-glyph-names") assert "space" in message # Upgrade to post format 3.0 and roundtrip data to update TTF object. ttFont = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf")) ttFont.setGlyphOrder(glyph_names) ttFont["post"].formatType = 3.0 _file = io.BytesIO() _file.name = ttFont.reader.file.name ttFont.save(_file) ttFont = TTFont(_file) assert_SKIP(check(ttFont))
def test_check_name_match_familyname_fullfont(): """ Does full font name begin with the font family name? """ from fontbakery.profiles.name import com_google_fonts_check_name_match_familyname_fullfont as check # Our reference Mada Regular is known to be good ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) # So it must PASS the check: assert_PASS(check(ttFont), 'with a good font...') # alter the full-font-name prepending a bad prefix: for i, name in enumerate(ttFont["name"].names): if name.nameID == NameID.FULL_FONT_NAME: ttFont["name"].names[i].string = "bad-prefix".encode( name.getEncoding()) # and make sure the check FAILs: assert_results_contain( check(ttFont), FAIL, 'does-not', 'with a font in which the family name' ' begins with a digit...') ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) for i, name in enumerate(ttFont["name"].names): if name.nameID == NameID.FULL_FONT_NAME: del ttFont["name"].names[i] assert_results_contain(check(ttFont), FAIL, 'no-full-font-name', 'with no FULL_FONT_NAME entries...') ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) for i, name in enumerate(ttFont["name"].names): if name.nameID == NameID.FONT_FAMILY_NAME: del ttFont["name"].names[i] assert_results_contain(check(ttFont), FAIL, 'no-font-family-name', 'with no FONT_FAMILY_NAME entries...')
def test_check_varfont_regular_ital_coord(): """ The variable font 'ital' (Italic) axis coordinate must be zero on the 'Regular' instance. """ check = CheckTester(opentype_profile, "com.google.fonts/check/varfont/regular_ital_coord") from fontTools.ttLib.tables._f_v_a_r import Axis # Our reference varfont, CabinVFBeta.ttf, lacks an 'ital' variation axis. ttFont = TTFont("data/test/cabinvfbeta/CabinVFBeta.ttf") # So we add one: new_axis = Axis() new_axis.axisTag = "ital" ttFont["fvar"].axes.append(new_axis) # and specify a bad coordinate for the Regular: ttFont["fvar"].instances[0].coordinates["ital"] = 123 # Note: I know the correct instance index for this hotfix because # I inspected the our reference CabinVF using ttx # And with this the check must detect the problem: assert_results_contain(check(ttFont), FAIL, 'non-zero', 'with a bad Regular:ital coordinate (123)...') # but with zero it must PASS the check: assert_PASS(check(ttFont, {"regular_ital_coord": 0}), 'with a good Regular:ital coordinate (zero)...')
def test_check_family_underline_thickness(mada_ttFonts): """ Fonts have consistent underline thickness ? """ from fontbakery.profiles.post import com_google_fonts_check_family_underline_thickness as check # We start with our reference Mada font family, # which we know has the same value of post.underlineThickness # across all of its font files, based on our inspection # of the file contents using TTX. # # So the check should PASS in this case: assert_PASS(check(mada_ttFonts), 'with a good family.') # Then we introduce the issue by setting a # different underlineThickness value in just # one of the font files: value = mada_ttFonts[0]['post'].underlineThickness incorrect_value = value + 1 mada_ttFonts[0]['post'].underlineThickness = incorrect_value # And now re-running the check on the modified # family should result in a FAIL: assert_results_contain( check(mada_ttFonts), FAIL, None, # FIXME: This needs a message keyword! 'with an inconsistent family.')
def test_check_whitespace_ink(): """ Whitespace glyphs have ink? """ check = CheckTester(universal_profile, "com.google.fonts/check/whitespace_ink") test_font = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf")) assert_PASS(check(test_font)) test_font["cmap"].tables[0].cmap[0x0020] = "uni1E17" assert_results_contain( check(test_font), FAIL, 'has-ink', 'for whitespace character having composites (with ink).') test_font["cmap"].tables[0].cmap[0x0020] = "scedilla" assert_results_contain( check(test_font), FAIL, 'has-ink', 'for whitespace character having outlines (with ink).') import fontTools.pens.ttGlyphPen pen = fontTools.pens.ttGlyphPen.TTGlyphPen(test_font.getGlyphSet()) pen.addComponent("space", (1, 0, 0, 1, 0, 0)) test_font["glyf"].glyphs["uni200B"] = pen.glyph() assert_results_contain( check(test_font), FAIL, 'has-ink', # should we give is a separate keyword? This looks wrong. 'for whitespace character having composites (without ink).')
def test_check_whitespace_glyphs(): """ Font contains glyphs for whitespace characters? """ check = CheckTester(universal_profile, "com.google.fonts/check/whitespace_glyphs") # Our reference Mada Regular font is good here: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) assert_PASS(check(ttFont), 'with a good font...') # We remove the nbsp char (0x00A0) for table in ttFont['cmap'].tables: if 0x00A0 in table.cmap: del table.cmap[0x00A0] # And make sure the problem is detected: assert_results_contain(check(ttFont), FAIL, 'missing-whitespace-glyph-0x00A0', 'with a font lacking a nbsp (0x00A0)...') # restore original Mada Regular font: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) # And finally do the same with the space character (0x0020): for table in ttFont['cmap'].tables: if 0x0020 in table.cmap: del table.cmap[0x0020] assert_results_contain(check(ttFont), FAIL, 'missing-whitespace-glyph-0x0020', 'with a font lacking a space (0x0020)...')
def test_check_glyf_unused_data(): """ Is there any unused data at the end of the glyf table? """ from fontbakery.profiles.glyf import com_google_fonts_check_glyf_unused_data as check test_font_path = TEST_FILE("nunito/Nunito-Regular.ttf") test_font = TTFont(test_font_path) assert_PASS(check(test_font)) # Always start with a fresh copy, as fT works lazily. Accessing certain data # can prevent the test from working because we rely on uninitialized # behavior. test_font = TTFont(test_font_path) test_font["loca"].locations.pop() test_file = io.BytesIO() test_font.save(test_file) test_font = TTFont(test_file) assert_results_contain(check(test_font), FAIL, 'unreachable-data') test_font = TTFont(test_font_path) test_font["loca"].locations.append(50000) test_file = io.BytesIO() test_font.save(test_file) test_font = TTFont(test_file) assert_results_contain(check(test_font), FAIL, 'missing-data')
def test_check_unitsperem(): """ Checking unitsPerEm value is reasonable. """ from fontbakery.profiles.head import com_google_fonts_check_unitsperem as check # In this test we'll forge several known-good and known-bad values. # We'll use Mada Regular to start with: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) for good_value in [16, 32, 64, 128, 256, 512, 1000, 1024, 2000, 2048, 4096, 8192, 16384]: ttFont['head'].unitsPerEm = good_value assert_PASS(check(ttFont), f'with a good value of unitsPerEm = {good_value} ...') for warn_value in [20, 50, 100, 500, 4000]: ttFont['head'].unitsPerEm = warn_value assert_results_contain(check(ttFont), WARN, 'suboptimal', f'with a value of unitsPerEm = {warn_value} ...') # These are arbitrarily chosen bad values: for bad_value in [0, 1, 2, 4, 8, 10, 15, 16385, 32768]: ttFont['head'].unitsPerEm = bad_value assert_results_contain(check(ttFont), FAIL, 'out-of-range', f'with a bad value of unitsPerEm = {bad_value} ...')
def test_check_rupee(): """ Ensure indic fonts have the Indian Rupee Sign glyph. """ check = CheckTester(universal_profile, "com.google.fonts/check/rupee") # FIXME: This should be possible: # The `assert_SKIP` helper should be able to detect when a # check skips due to unmet @conditions. # For now it only detects explicit SKIP messages instead. # # non_indic = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) # assert_SKIP(check(non_indic), # "with a non-indic font.") # # But for now we have to do this: # from fontbakery.profiles.shared_conditions import is_indic_font print("Ensure the check will SKIP when dealing with a non-indic font...") non_indic = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) assert is_indic_font(non_indic) == False # This one is good: ttFont = TTFont( TEST_FILE( "indic-font-with-rupee-sign/NotoSerifDevanagari-Regular.ttf")) assert_PASS(check(ttFont), "with a sample font that has the Indian Rupee Sign.") # But this one lacks the glyph: ttFont = TTFont( TEST_FILE("indic-font-without-rupee-sign/NotoSansOlChiki-Regular.ttf")) assert_results_contain(check(ttFont), FAIL, "missing-rupee", "with a sample font missing it.")
def test_check_family_win_ascent_and_descent(mada_ttFonts): """ Checking OS/2 usWinAscent & usWinDescent. """ check = CheckTester( universal_profile, "com.google.fonts/check/family/win_ascent_and_descent") from fontbakery.profiles.shared_conditions import vmetrics # Our reference Mada Regular is know to be bad here. ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) # But we fix it first to test the PASS code-path: vm = vmetrics(mada_ttFonts) ttFont['OS/2'].usWinAscent = vm['ymax'] ttFont['OS/2'].usWinDescent = abs(vm['ymin']) assert_PASS(check(ttFont), 'with a good font...') # Then we break it: ttFont[ 'OS/2'].usWinAscent = 0 # FIXME: this should be bad as well: vm['ymax'] - 1 ttFont['OS/2'].usWinDescent = abs(vm['ymin']) assert_results_contain(check(ttFont), FAIL, 'ascent', 'with a bad OS/2.usWinAscent...') # and also this other way of breaking it: ttFont['OS/2'].usWinAscent = vm['ymax'] ttFont[ 'OS/2'].usWinDescent = 0 # FIXME: this should be bad as well: abs(vm['ymin']) - 1 assert_results_contain(check(ttFont), FAIL, 'descent', 'with a bad OS/2.usWinDescent...')
def test_check_unwanted_tables(): """ Are there unwanted tables ? """ check = CheckTester(universal_profile, "com.google.fonts/check/unwanted_tables") unwanted_tables = [ "FFTM", # FontForge "TTFA", # TTFAutohint "TSI0", # TSI* = VTT "TSI1", "TSI2", "TSI3", "TSI5", "prop", # FIXME: Why is this one unwanted? "MVAR", # Bugs in DirectWrite ] # Our reference Mada Regular font is good here: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) # So it must PASS the check: assert_PASS(check(ttFont), 'with a good font...') # We now add unwanted tables one-by-one to validate the FAIL code-path: for unwanted in unwanted_tables: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) ttFont.reader.tables[unwanted] = "foo" assert_results_contain(check(ttFont), FAIL, "unwanted-tables", f'with unwanted table {unwanted} ...')
def test_check_required_tables(): """ Font contains all required tables ? """ check = CheckTester(universal_profile, "com.google.fonts/check/required_tables") required_tables = [ "cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "post" ] optional_tables = [ "cvt ", "fpgm", "loca", "prep", "VORG", "EBDT", "EBLC", "EBSC", "BASE", "GPOS", "GSUB", "JSTF", "DSIG", "gasp", "hdmx", "kern", "LTSH", "PCLT", "VDMX", "vhea", "vmtx" ] # Our reference Mada Regular font is good here ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) # So it must PASS the check: assert_PASS(check(ttFont), 'with a good font...') # Now we test the special cases for variable fonts: # # Note: A TTF with an fvar table but no STAT table # is probably a GX font. For now we're OK with # rejecting those by emitting a FAIL in this case. # # TODO: Maybe we could someday emit a more explicit # message to the users regarding that... ttFont.reader.tables["fvar"] = "foo" assert_results_contain(check(ttFont), FAIL, "required-tables", 'with fvar but no STAT...') del ttFont.reader.tables["fvar"] ttFont.reader.tables["STAT"] = "foo" assert_PASS(check(ttFont), 'with STAT on a non-variable font...') # and finally remove what we've just added: del ttFont.reader.tables["STAT"] # Now we remove required tables one-by-one to validate the FAIL code-path: for required in required_tables: ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf")) if required in ttFont.reader.tables: del ttFont.reader.tables[required] assert_results_contain(check(ttFont), FAIL, "required-tables", f'with missing mandatory table {required} ...') # Then, in preparation for the next step, we make sure # there's no optional table (by removing them all): for optional in optional_tables: if optional in ttFont.reader.tables: del ttFont.reader.tables[optional] # Then re-insert them one by one to validate the INFO code-path: for optional in optional_tables: ttFont.reader.tables[optional] = "foo" # and ensure that the second to last logged message is an # INFO status informing the user about it: assert_results_contain(check(ttFont), INFO, "required-tables", f'with optional table {required} ...') # remove the one we've just inserted before trying the next one: del ttFont.reader.tables[optional]
def test_check_xavgcharwidth(): """ Check if OS/2 xAvgCharWidth is correct. """ from fontbakery.profiles.os2 import com_google_fonts_check_xavgcharwidth as check test_font_path = TEST_FILE("nunito/Nunito-Regular.ttf") test_font = TTFont(test_font_path) assert_PASS(check(test_font)) test_font['OS/2'].xAvgCharWidth = 556 assert_results_contain(check(test_font), INFO, None) # FIXME: This needs a message keyword! test_font['OS/2'].xAvgCharWidth = 500 assert_results_contain(check(test_font), WARN, None) # FIXME: This needs a message keyword test_font = TTFont() test_font['OS/2'] = fontTools.ttLib.newTable('OS/2') test_font['OS/2'].version = 4 test_font['OS/2'].xAvgCharWidth = 1000 test_font['glyf'] = fontTools.ttLib.newTable('glyf') test_font['glyf'].glyphs = {} test_font['hmtx'] = fontTools.ttLib.newTable('hmtx') test_font['hmtx'].metrics = {} assert_results_contain(check(test_font), FAIL, 'missing-glyphs') test_font = TTFont(test_font_path) subsetter = fontTools.subset.Subsetter() subsetter.populate(glyphs=[ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'space' ]) subsetter.subset(test_font) test_font['OS/2'].xAvgCharWidth = 447 test_font['OS/2'].version = 2 temp_file = io.BytesIO() test_font.save(temp_file) test_font = TTFont(temp_file) assert_PASS(check(test_font)) test_font['OS/2'].xAvgCharWidth = 450 assert_results_contain(check(test_font), INFO, None) # FIXME: This needs a message keyword test_font['OS/2'].xAvgCharWidth = 500 assert_results_contain(check(test_font), WARN, None) # FIXME: This needs a message keyword test_font = TTFont(temp_file) subsetter = fontTools.subset.Subsetter() subsetter.populate(glyphs=[ 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'space' ]) subsetter.subset(test_font) assert_results_contain(check(test_font), FAIL, 'missing-glyphs')
def test_check_STAT_strings(): check = CheckTester(universal_profile, "com.google.fonts/check/STAT_strings") good = TTFont(TEST_FILE("ibmplexsans-vf/IBMPlexSansVar-Roman.ttf")) assert_PASS(check(good)) bad = TTFont(TEST_FILE("ibmplexsans-vf/IBMPlexSansVar-Italic.ttf")) assert_results_contain(check(bad), FAIL, "bad-italic")