def com_google_fonts_check_varfont_bold_wght_coord(ttFont, bold_wght_coord): """The variable font 'wght' (Weight) axis coordinate must be 700 on the 'Bold' instance.""" if bold_wght_coord == 700: yield PASS, "Bold:wght is 700." else: yield FAIL,\ Message("not-700", f'The "wght" axis coordinate of' f' the "Bold" instance must be 700.' f' Got {bold_wght_coord} instead.')
def com_google_fonts_check_iso15008_proportions(ttFont): """Check if 0.65 => (H width / H height) => 0.80""" glyphset = ttFont.getGlyphSet() if "H" not in glyphset: yield FAIL,\ Message('glyph-not-present', "There was no 'H' glyph in the font," " so the proportions could not be tested") h_glyph = glyphset["H"] pen = BoundsPen(glyphset) h_glyph._glyph.draw(pen, ttFont.get("glyf")) (xMin, yMin, xMax, yMax) = pen.bounds proportion = (xMax - xMin) / (yMax - yMin) if 0.65 <= proportion <= 0.80: yield PASS, "the letter H is not too narrow or too wide" else: yield FAIL,\ Message('invalid-proportion', f"The proportion of H width to H height ({proportion})" f"does not conform to the expected range of 0.65-0.80")
def com_google_fonts_check_varfont_regular_opsz_coord(ttFont, regular_opsz_coord): """The variable font 'opsz' (Optical Size) axis coordinate should be between 9 and 13 on the 'Regular' instance.""" if regular_opsz_coord >= 9 and regular_opsz_coord <= 13: yield PASS, ("Regular:opsz coordinate ({regular_opsz_coord}) looks good.") else: yield WARN,\ Message("out-of-range", f'The "opsz" (Optical Size) coordinate' f' on the "Regular" instance is recommended' f' to be a value in the range 9 to 13.' f' Got {regular_opsz_coord} instead.')
def forbidden_glyph_test_results(vharfbuzz, shaping_file, failed_shaping_tests): report_items = [] msg = f"{shaping_file}: Forbidden glyphs found while shaping" report_items.append(create_report_item(vharfbuzz, msg, type="header")) for shaping_text, buf, forbidden in failed_shaping_tests: msg = f"{shaping_text} produced '{forbidden}'" report_items.append( create_report_item(vharfbuzz, msg, text=shaping_text, buf1=buf)) yield FAIL, Message("shaping-forbidden", msg + ".\n" + "\n".join(report_items))
def com_google_fonts_check_varfont_regular_wdth_coord(ttFont, regular_wdth_coord): """The variable font 'wdth' (Width) axis coordinate must be 100 on the 'Regular' instance.""" if regular_wdth_coord == 100: yield PASS, "Regular:wdth is 100." else: yield FAIL,\ Message("not-100", f'The "wdth" coordinate of' f' the "Regular" instance must be 100.' f' Got {regular_wdth_coord} as a default value instead.')
def com_google_fonts_check_varfont_regular_slnt_coord(ttFont, regular_slnt_coord): """The variable font 'slnt' (Slant) axis coordinate must be zero on the 'Regular' instance.""" if regular_slnt_coord == 0: yield PASS, "Regular:slnt is zero." else: yield FAIL,\ Message("non-zero", f'The "slnt" coordinate of' f' the "Regular" instance must be zero.' f' Got {regular_slnt_coord} as a default value instead.')
def com_google_fonts_check_varfont_regular_wght_coord(ttFont, regular_wght_coord): """The variable font 'wght' (Weight) axis coordinate must be 400 on the 'Regular' instance.""" if regular_wght_coord == 400: yield PASS, "Regular:wght is 400." else: yield FAIL,\ Message("not-400", f'The "wght" axis coordinate of' f' the "Regular" instance must be 400.' f' Got {regular_wght_coord} instead.')
def com_google_fonts_check_dsig(ttFont): """Does the font have a DSIG table?""" if "DSIG" in ttFont: yield PASS, "Digital Signature (DSIG) exists." else: yield FAIL,\ Message("lacks-signature", "This font lacks a digital signature (DSIG table)." " Some applications may require one (even if only a" " dummy placeholder) in order to work properly. You" " can add a DSIG table by running the" " `gftools fix-dsig` script.")
def com_google_fonts_check_font_version(ttFont): """Checking font version fields (head and name table).""" from decimal import Decimal head_version_str = str( Decimal(ttFont["head"].fontRevision).quantize(Decimal('1.000'))) head_version = parse_version_string(head_version_str) # Compare the head version against the name ID 5 strings in all name records. name_id_5_records = [ record for record in ttFont["name"].names if record.nameID == NameID.VERSION_STRING ] failed = False if name_id_5_records: for record in name_id_5_records: try: name_version = parse_version_string(record.toUnicode()) if name_version != head_version: failed = True yield FAIL, Message( "mismatch", "head version is '{}' while name version string" " (for platform {}, encoding {}) is '{}'." "".format(head_version_str, record.platformID, record.platEncID, record.toUnicode())) except ValueError: failed = True yield FAIL, Message( "parse", "name version string for platform {}," " encoding {} ('{}'), could not be parsed." "".format(record.platformID, record.platEncID, record.toUnicode())) else: failed = True yield FAIL, Message( "missing", "There is no name ID 5 (version string) in the font.") if not failed: yield PASS, "All font version fields match."
def org_sil_software_version_format(ttFont): "Version format is correct in 'name' table?" from fontbakery.utils import get_name_entry_strings import re def is_valid_version_format(value): return re.match(r'Version [0-9]+\.\d{3}( .+)*$', value) failed = False warned = False version_entries = get_name_entry_strings(ttFont, NameID.VERSION_STRING) if len(version_entries) == 0: failed = True yield FAIL,\ Message("no-version-string", f"Font lacks a NameID.VERSION_STRING" f" (nameID={NameID.VERSION_STRING}) entry") for ventry in version_entries: if not re.match(r'Version [0-9]+\.\d{3}( .+)*$', ventry): failed = True yield FAIL,\ Message("bad-version-strings", f'The NameID.VERSION_STRING' f' (nameID={NameID.VERSION_STRING}) value must' f' follow the pattern "Version X.nnn devstring" with X.nnn' f' greater than or equal to 0.000.' f' Current version string is: "{ventry}"') elif not re.match(r'Version [1-9][0-9]*\.\d{3}$', ventry): warned = True yield WARN, \ Message("nonproduction-version-strings", f'For production fonts, the NameID.VERSION_STRING' f' (nameID={NameID.VERSION_STRING}) value must' f' follow the pattern "Version X.nnn" with X.nnn' f' greater than or equal to 1.000.' f' Current version string is: "{ventry}"') if not failed and not warned: yield PASS, "Version format in NAME table entries is correct."
def com_google_fonts_check_ots(font): """Checking with ots-sanitize.""" import ots try: process = ots.sanitize(font, check=True, capture_output=True) except ots.CalledProcessError as e: yield FAIL, \ Message("ots-sanitize-error", ("ots-sanitize returned an error code ({}). Output follows:\n\n{}{}" ).format(e.returncode, e.stderr.decode(), e.stdout.decode()) ) else: if process.stderr: yield WARN, \ Message("ots-sanitize-warn", ("ots-sanitize passed this file, " "however warnings were printed:\n\n{}" ).format(process.stderr.decode()) ) else: yield PASS, "ots-sanitize passed this file"
def com_google_fonts_check_040(ttFont, vmetrics): """Checking OS/2 usWinAscent & usWinDescent. A font's winAscent and winDescent values should be greater than the head table's yMax, abs(yMin) values. If they are less than these values, clipping can occur on Windows platforms, https://github.com/RedHatBrand/Overpass/issues/33 If the font includes tall/deep writing systems such as Arabic or Devanagari, the winAscent and winDescent can be greater than the yMax and abs(yMin) to accommodate vowel marks. When the win Metrics are significantly greater than the upm, the linespacing can appear too loose. To counteract this, enabling the OS/2 fsSelection bit 7 (Use_Typo_Metrics), will force Windows to use the OS/2 typo values instead. This means the font developer can control the linespacing with the typo values, whilst avoiding clipping by setting the win values to values greater than the yMax and abs(yMin). """ failed = False # OS/2 usWinAscent: if ttFont['OS/2'].usWinAscent < vmetrics['ymax']: failed = True yield FAIL, Message("ascent", ("OS/2.usWinAscent value" " should be equal or greater than {}, but got" " {} instead").format(vmetrics['ymax'], ttFont['OS/2'].usWinAscent)) # OS/2 usWinDescent: if ttFont['OS/2'].usWinDescent < abs(vmetrics['ymin']): failed = True yield FAIL, Message( "descent", ("OS/2.usWinDescent value" " should be equal or greater than {}, but got" " {} instead").format( abs(vmetrics['ymin']), ttFont['OS/2'].usWinDescent)) if not failed: yield PASS, "OS/2 usWinAscent & usWinDescent values look good!"
def com_google_fonts_check_kern_table(ttFont): """Is there a "kern" table declared in the font?""" if "kern" in ttFont: yield INFO,\ Message("kern-found", 'Only a few programs may require the kerning' ' info that this font provides on its "kern" table.') # TODO: perhaps we should add code here to detect and emit an ERROR # if the kern table and subtable version and format are not zero, # as mentioned in the rationale above. else: yield PASS, 'Font does not declare an optional "kern" table.'
def com_google_fonts_check_family_win_ascent_and_descent(ttFont, vmetrics): """Checking OS/2 usWinAscent & usWinDescent.""" failed = False # OS/2 usWinAscent: if ttFont['OS/2'].usWinAscent < vmetrics['ymax']: failed = True yield FAIL, Message("ascent", ("OS/2.usWinAscent value" " should be equal or greater than {}, but got" " {} instead").format(vmetrics['ymax'], ttFont['OS/2'].usWinAscent)) if ttFont['OS/2'].usWinAscent > vmetrics['ymax'] * 2: failed = True yield FAIL, Message("ascent", ("OS/2.usWinAscent value {} is too large." " It should be less than double the yMax." " Current yMax value is {}").format( ttFont['OS/2'].usWinDescent, vmetrics['ymax'])) # OS/2 usWinDescent: if ttFont['OS/2'].usWinDescent < abs(vmetrics['ymin']): failed = True yield FAIL, Message( "descent", ("OS/2.usWinDescent value" " should be equal or greater than {}, but got" " {} instead").format(abs(vmetrics['ymin']), ttFont['OS/2'].usWinDescent)) if ttFont['OS/2'].usWinDescent > abs(vmetrics['ymin']) * 2: failed = True yield FAIL, Message("descent", ("OS/2.usWinDescent value {} is too large." " It should be less than double the yMin." " Current absolute yMin value is {}").format( ttFont['OS/2'].usWinDescent, abs(vmetrics['ymin']))) if not failed: yield PASS, "OS/2 usWinAscent & usWinDescent values look good!"
def com_google_fonts_check_079(ttFont): """Monospace font has hhea.advanceWidthMax equal to each glyph's advanceWidth?""" # hhea:advanceWidthMax is treated as source of truth here. max_advw = ttFont['hhea'].advanceWidthMax outliers = [] zero_or_double_width_outliers = [] glyphs = [ g for g in ttFont['glyf'].glyphs if g not in ['.notdef', '.null', 'NULL'] ] for glyph_id in glyphs: width = ttFont['hmtx'].metrics[glyph_id][0] if width != max_advw: outliers.append(glyph_id) if width == 0 or width == 2 * max_advw: zero_or_double_width_outliers.append(glyph_id) if outliers: outliers_percentage = float(len(outliers)) / len(ttFont['glyf'].glyphs) yield WARN, Message( "should-be-monospaced", "This seems to be a monospaced font," " so advanceWidth value should be the same" " across all glyphs, but {}% of them" " have a different value: {}" "".format(round(100 * outliers_percentage, 2), ", ".join(outliers))) if zero_or_double_width_outliers: yield WARN, Message( "variable-monospaced", "Double-width and/or zero-width glyphs" " were detected. These glyphs should be set" " to the same width as all others" " and then add GPOS single pos lookups" " that zeros/doubles the widths as needed: {}".format( ", ".join(zero_or_double_width_outliers))) else: yield PASS, ("hhea.advanceWidthMax is equal" " to all glyphs' advanceWidth in this monospaced font.")
def com_google_fonts_check_varfont_stat_axis_record_for_each_axis(ttFont): """ All fvar axes have a correspondent Axis Record on STAT table? """ fvar_axes = set(a.axisTag for a in ttFont['fvar'].axes) stat_axes = set(a.AxisTag for a in ttFont['STAT'].table.DesignAxisRecord.Axis) missing_axes = fvar_axes - stat_axes if len(missing_axes) > 0: yield FAIL,\ Message("missing-axis-records", f"STAT table is missing Axis Records for" f" the following axes: {missing_axes}") else: yield PASS, "STAT table has all necessary Axis Records"
def com_google_fonts_check_iso15008_interline_spacing(ttFont): """Check if spacing between lines is adequate for display use""" glyphset = ttFont.getGlyphSet() if "h" not in glyphset or "g" not in glyphset: yield FAIL,\ Message('glyph-not-present', "There was no 'g'/'h' glyph in the font," " so the spacing could not be tested") return h_glyph = glyphset["h"] pen = BoundsPen(glyphset) h_glyph._glyph.draw(pen, ttFont.get("glyf")) (_, _, _, h_yMax) = pen.bounds g_glyph = glyphset["g"] pen = BoundsPen(glyphset) g_glyph._glyph.draw(pen, ttFont.get("glyf")) (_, g_yMin, _, _) = pen.bounds linegap = ( (g_yMin - ttFont["OS/2"].sTypoDescender) + ttFont["OS/2"].sTypoLineGap + (ttFont["OS/2"].sTypoAscender - h_yMax) ) width = stem_width(ttFont) if width is None: yield FAIL,\ Message('no-stem-width', "Could not determine stem width") return if linegap < width: yield FAIL,\ Message('bad-interline-spacing', f"The interline space {linegap} should" f" be more than the stem width {width}") return yield PASS, "Amount of interline space was adequate"
def com_google_fonts_check_044(ttFont): """Checking font version fields (head and name table).""" head_version = parse_version_string(str(ttFont["head"].fontRevision)) # Compare the head version against the name ID 5 strings in all name records. from fontbakery.constants import NAMEID_VERSION_STRING name_id_5_records = [ record for record in ttFont["name"].names if record.nameID == NAMEID_VERSION_STRING ] failed = False if name_id_5_records: for record in name_id_5_records: try: name_version = parse_version_string(record.toUnicode()) if name_version != head_version: failed = True yield FAIL, Message( "mismatch", "head version is {}, name version string for platform {}," " encoding {}, is {}".format(head_version, record.platformID, record.platEncID, name_version)) except ValueError: failed = True yield FAIL, Message( "parse", "name version string for platform {}," " encoding {}, could not be parsed".format( record.platformID, record.platEncID)) else: failed = True yield FAIL, Message( "missing", "There is no name ID 5 (version string) in the font.") if not failed: yield PASS, "All font version fields match."
def check_bit_entry(ttFont, table, attr, expected, bitmask, bitname): from fontbakery.message import Message from fontbakery.checkrunner import (PASS, FAIL) value = getattr(ttFont[table], attr) name_str = f"{table} {attr} {bitname} bit" if bool(value & bitmask) == expected: return PASS, f"{name_str} is properly set." else: if expected: expected_str = "set" else: expected_str = "reset" return FAIL, Message(f"bad-{bitname}", f"{name_str} should be {expected_str}.")
def check_bit_entry(ttFont, table, attr, expected, bitmask, bitname): from fontbakery.message import Message from fontbakery.checkrunner import (PASS, FAIL) value = getattr(ttFont[table], attr) name_str = "{} {} {} bit".format(table, attr, bitname) if bool(value & bitmask) == expected: return PASS, "{} is properly set.".format(name_str) else: if expected: expected_str = "set" else: expected_str = "reset" return FAIL, Message("bad-{}".format(bitname), "{} should be {}.".format(name_str, expected_str))
def com_adobe_fonts_check_fsselection_matches_macstyle(ttFont): """Check if OS/2 fsSelection matches head macStyle bold and italic bits.""" # Check both OS/2 and head are present. missing_tables = False required = ["OS/2", "head"] for key in required: if key not in ttFont: missing_tables = True yield FAIL,\ Message(f'lacks-{key}', f"The '{key}' table is missing.") if missing_tables: return from fontbakery.constants import FsSelection, MacStyle failed = False head_bold = (ttFont['head'].macStyle & MacStyle.BOLD) != 0 os2_bold = (ttFont['OS/2'].fsSelection & FsSelection.BOLD) != 0 if head_bold != os2_bold: failed = True yield FAIL, \ Message("fsselection-macstyle-bold", "The OS/2.fsSelection and head.macStyle " \ "bold settings do not match.") head_italic = (ttFont['head'].macStyle & MacStyle.ITALIC) != 0 os2_italic = (ttFont['OS/2'].fsSelection & FsSelection.ITALIC) != 0 if head_italic != os2_italic: failed = True yield FAIL, \ Message("fsselection-macstyle-italic", "The OS/2.fsSelection and head.macStyle " \ "italic settings do not match.") if not failed: yield PASS, ("The OS/2.fsSelection and head.macStyle " "bold and italic settings match.")
def io_github_abysstypeco_check_ytlc_sanity(ttFont): """Check if ytlc values are sane in vf""" passed = True for axis in ttFont['fvar'].axes: if not axis.axisTag == 'ytlc': continue if axis.minValue < 0 or axis.maxValue > 1000: passed = False yield FAIL,\ Message("invalid-range", f'The range of ytlc values ({axis.minValue} - {axis.maxValue})' f'does not conform to the expected range of ytlc which should be min value 0 to max value 1000') if passed: yield PASS, 'ytlc is sane'
def com_google_fonts_check_iso15008_interword_spacing(font, ttFont): """Check if spacing between words is adequate for display use""" l_intersections = xheight_intersections(ttFont, "l") if len(l_intersections) < 2: yield FAIL,\ Message('glyph-not-present', "There was no 'l' glyph in the font," " so the spacing could not be tested") return l_advance = ttFont["hmtx"]["l"][0] l_rsb = l_advance - l_intersections[-1].point.x glyphset = ttFont.getGlyphSet() h_glyph = glyphset["m"] pen = BoundsPen(glyphset) h_glyph._glyph.draw(pen, ttFont.get("glyf")) (xMin, yMin, xMax, yMax) = pen.bounds m_advance = ttFont["hmtx"]["m"][0] m_lsb = xMin m_rsb = m_advance - (m_lsb + xMax - xMin) n_lsb = ttFont["hmtx"]["n"][1] l_m = l_rsb + pair_kerning(font, "l", "m") + m_lsb space_width = ttFont["hmtx"]["space"][0] # Add spacing caused by normal sidebearings space_width += m_rsb + n_lsb if 2.50 <= space_width / l_m <= 3.0: yield PASS, "Advance width of interword space was adequate" else: yield FAIL,\ Message('bad-interword-spacing', f"The interword space ({space_width}) was" f" outside the recommended range ({l_m*2.5}-{l_m*3.0})")
def com_google_fonts_check_064(ttFont, ligatures): """Is there a caret position declared for every ligature?""" if ligatures == -1: yield FAIL, Message( "malformed", "Failed to lookup ligatures." " This font file seems to be malformed." " For more info, read:" " https://github.com" "/googlefonts/fontbakery/issues/1596") elif "GDEF" not in ttFont: yield WARN, Message("GDEF-missing", ("GDEF table is missing, but it is mandatory" " to declare it on fonts that provide ligature" " glyphs because the caret (text cursor)" " positioning for each ligature must be" " provided in this table.")) else: # TODO: After getting a sample of a good font, # resume the implementation of this routine: lig_caret_list = ttFont["GDEF"].table.LigCaretList if lig_caret_list is None or lig_caret_list.LigGlyphCount == 0: yield WARN, Message("lacks-caret-pos", ("This font lacks caret position values for" " ligature glyphs on its GDEF table.")) elif lig_caret_list.LigGlyphCount != len(ligatures): yield WARN, Message( "incomplete-caret-pos-data", ("It seems that this font lacks caret positioning" " values for some of its ligature glyphs on the" " GDEF table. There's a total of {} ligatures," " but only {} sets of caret positioning" " values.").format(len(ligatures), lig_caret_list.LigGlyphCount)) else: # Should we also actually check each individual entry here? yield PASS, "Looks good!"
def com_google_fonts_check_all_glyphs_have_codepoints(ttFont): """Check all glyphs have codepoints assigned.""" failed = False for subtable in ttFont['cmap'].tables: if subtable.isUnicode(): for item in subtable.cmap.items(): codepoint = item[0] if codepoint is None: failed = True yield FAIL,\ Message("glyph-lacks-codepoint", f"Glyph {codepoint} lacks a unicode" f" codepoint assignment.") if not failed: yield PASS, "All glyphs have a codepoint value assigned."
def com_google_fonts_check_name_rfn(ttFont): """Name table strings must not contain the string 'Reserved Font Name'.""" failed = False for entry in ttFont["name"].names: string = entry.toUnicode() if "reserved font name" in string.lower(): yield WARN,\ Message("rfn", f'Name table entry ("{string}")' f' contains "Reserved Font Name".' f' This is an error except in a few specific rare cases.') failed = True if not failed: yield PASS, ('None of the name table strings' ' contain "Reserved Font Name".')
def com_google_fonts_check_name_line_breaks(ttFont): """Name table entries should not contain line-breaks.""" failed = False for name in ttFont["name"].names: string = name.string.decode(name.getEncoding()) if "\n" in string: failed = True yield FAIL,\ Message("line-break", f"Name entry {NameID(name.nameID).name}" f" on platform {PlatformID(name.platformID).name}" f" contains a line-break.") if not failed: yield PASS, ("Name table entries are all single-line" " (no line-breaks found).")
def com_adobe_fonts_check_name_empty_records(ttFont): """Check name table for empty records.""" failed = False for name_record in ttFont['name'].names: name_string = name_record.toUnicode().strip() if len(name_string) == 0: failed = True name_key = tuple([name_record.platformID, name_record.platEncID, name_record.langID, name_record.nameID]) yield FAIL,\ Message("empty-record", f'"name" table record with key={name_key} is' f' empty and should be removed.') if not failed: yield PASS, ("No empty name table records found.")
def com_google_fonts_check_cmap_unexpected_subtables(ttFont): """Ensure all cmap subtables are the typical types expected in a font.""" from fontbakery.profiles.shared_conditions import is_cjk_font passed = True # Note: # Format 0 = Byte encoding table # Format 4 = Segment mapping to delta values # Format 6 = Trimmed table mapping # Format 12 = Segmented coverage # Format 14 = Unicode Variation Sequences EXPECTED_SUBTABLES = [ ( 0, PlatformID.MACINTOSH, MacintoshEncodingID.ROMAN), # 13.7% of GFonts TTFs (389 files) #( 4, PlatformID.MACINTOSH, MacintoshEncodingID.ROMAN), # only the Sansation family has this on GFonts ( 6, PlatformID.MACINTOSH, MacintoshEncodingID.ROMAN), # 38.1% of GFonts TTFs (1.082 files) #( 4, PlatformID.UNICODE, UnicodeEncodingID.UNICODE_1_0), # only the Gentium family has this on GFonts #(12, PlatformID.UNICODE, 10), # INVALID? - only the Overpass family and SawarabiGothic-Regular has this on GFonts # ----------------------------------------------------------------------- ( 4, PlatformID.WINDOWS, WindowsEncodingID.UNICODE_BMP), # Absolutely all GFonts TTFs have this table :-) (12, PlatformID.WINDOWS, WindowsEncodingID.UNICODE_FULL_REPERTOIRE), # 5.7% of GFonts TTFs (162 files) (14, PlatformID.UNICODE, UnicodeEncodingID.UNICODE_VARIATION_SEQUENCES), # 1.1% - Only 4 families (30 TTFs), # including SourceCodePro, have this on GFonts ( 4, PlatformID.UNICODE, UnicodeEncodingID.UNICODE_2_0_BMP_ONLY), # 97.0% of GFonts TTFs (only 84 files lack it) (12, PlatformID.UNICODE, UnicodeEncodingID.UNICODE_2_0_FULL) # 2.9% of GFonts TTFs (82 files) ] if is_cjk_font(ttFont): EXPECTED_SUBTABLES.extend([ # Adobe says historically some programs used these to identify # the script in the font. The encodingID is the quickdraw # script manager code. These are dummy tables. (6, PlatformID.MACINTOSH, MacintoshEncodingID.JAPANESE), (6, PlatformID.MACINTOSH, MacintoshEncodingID.CHINESE_TRADITIONAL), (6, PlatformID.MACINTOSH, MacintoshEncodingID.KOREAN), (6, PlatformID.MACINTOSH, MacintoshEncodingID.CHINESE_SIMPLIFIED) ]) for subtable in ttFont['cmap'].tables: if (subtable.format, subtable.platformID, subtable.platEncID) not in EXPECTED_SUBTABLES: passed = False yield WARN,\ Message("unexpected-subtable", f"'cmap' has a subtable of" f" (format={subtable.format}, platform={subtable.platformID}," f" encoding={subtable.platEncID}), which it shouldn't have.") if passed: yield PASS, "All cmap subtables look good!"
def com_google_fonts_check_superfamily_vertical_metrics(superfamily_ttFonts): """Each font in set of sibling families must have the same set of vertical metrics values.""" if len(superfamily_ttFonts) < 2: yield SKIP, "Sibling families were not detected." return warn = [] vmetrics = { "sTypoAscender": {}, "sTypoDescender": {}, "sTypoLineGap": {}, "usWinAscent": {}, "usWinDescent": {}, "ascent": {}, "descent": {} } for family_ttFonts in superfamily_ttFonts: for ttFont in family_ttFonts: full_font_name = ttFont['name'].getName(4, 3, 1, 1033).toUnicode() vmetrics['sTypoAscender'][full_font_name] = ttFont[ 'OS/2'].sTypoAscender vmetrics['sTypoDescender'][full_font_name] = ttFont[ 'OS/2'].sTypoDescender vmetrics['sTypoLineGap'][full_font_name] = ttFont[ 'OS/2'].sTypoLineGap vmetrics['usWinAscent'][full_font_name] = ttFont[ 'OS/2'].usWinAscent vmetrics['usWinDescent'][full_font_name] = ttFont[ 'OS/2'].usWinDescent vmetrics['ascent'][full_font_name] = ttFont['hhea'].ascent vmetrics['descent'][full_font_name] = ttFont['hhea'].descent for k, v in vmetrics.items(): metric_vals = set(vmetrics[k].values()) if len(metric_vals) != 1: warn.append(k) if warn: for k in warn: s = ["{}: {}".format(k, v) for k, v in vmetrics[k].items()] s = "\n".join(s) yield WARN, \ Message("superfamily-vertical-metrics", f"{k} is not the same across the super-family:\n" f"{s}") else: yield PASS, "Vertical metrics are the same across the super-family."