Beispiel #1
0
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.')
Beispiel #2
0
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")
Beispiel #3
0
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.')
Beispiel #4
0
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))
Beispiel #5
0
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.')
Beispiel #6
0
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.')
Beispiel #7
0
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.')
Beispiel #8
0
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.")
Beispiel #9
0
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."
Beispiel #10
0
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."
Beispiel #11
0
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"
Beispiel #12
0
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!"
Beispiel #13
0
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.'
Beispiel #14
0
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!"
Beispiel #15
0
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.")
Beispiel #16
0
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"
Beispiel #17
0
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"
Beispiel #18
0
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."
Beispiel #19
0
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}.")
Beispiel #20
0
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))
Beispiel #21
0
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.")
Beispiel #22
0
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'
Beispiel #23
0
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})")
Beispiel #24
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!"
Beispiel #25
0
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."
Beispiel #26
0
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".')
Beispiel #27
0
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).")
Beispiel #28
0
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.")
Beispiel #29
0
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!"
Beispiel #30
0
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."