Return true if the GSFont contains a manually written 'kern' feature.
760738
def _has_manual_kern_feature(font):
    """"""
    return any(f for f in font.features if f.name == "kern" and not f.automatic)
167403
def com_google_fonts_check_aat(ttFont):
  """"""
  UNWANTED_TABLES = {
    'EBSC', 'Zaph', 'acnt', 'ankr', 'bdat', 'bhed', 'bloc',
    'bmap', 'bsln', 'fdsc', 'feat', 'fond', 'gcid', 'just',
    'kerx', 'lcar', 'ltag', 'mort', 'morx', 'opbd', 'prop',
    'trak', 'xref'
  }
  unwanted_tables_found = []
  for table in ttFont.keys():
    if table in UNWANTED_TABLES:
      unwanted_tables_found.append(table)

  if len(unwanted_tables_found) > 0:
    yield FAIL, ("Unwanted AAT tables were found"
                 " in the font and should be removed, either by"
                 " fonttools/ttx or by editing them using the tool"
                 " they built with:"
                 " {}").format(", ".join(unwanted_tables_found))
  else:
    yield PASS, "There are no unwanted AAT tables."
1155260
def com_google_fonts_check_name_trailing_spaces(ttFont):
  """"""
  failed = False
  for name_record in ttFont['name'].names:
    name_string = name_record.toUnicode()
    if name_string != name_string.strip():
      failed = True
      name_key = tuple([name_record.platformID, name_record.platEncID,
                       name_record.langID, name_record.nameID])
      shortened_str = name_record.toUnicode()
      if len(shortened_str) > 20:
        shortened_str = shortened_str[:10] + "[...]" + shortened_str[-10:]
      yield FAIL, (f"Name table record with key = {name_key} has"
                    " trailing spaces that must be removed:"
                   f" '{shortened_str}'")
  if not failed:
    yield PASS, ("No trailing spaces on name table entries.")
654423
def com_google_fonts_check_fontvalidator(font):
  """"""

  # In some cases we want to override the severity level of
  # certain checks in FontValidator:
  downgrade_to_warn = [
    # There are reports that this fontval check has an out-of-date
    # understanding of valid bits in fsSelection.
    # More info at:
    # https://github.com/googlei18n/fontmake/issues/414#issuecomment-379408127
    "There are undefined bits set in fsSelection field",

    # FIX-ME: Why did we downgrade this one to WARN?
    "Misoriented contour"
  ]

  # Some other checks we want to completely disable:
  disabled_fval_checks = [
    # FontVal E4012 thinks that
    # "Versions 0x00010000 and 0x0001002 are currently
    #  the only defined versions of the GDEF table."
    # but the GDEF chapter of the OpenType specification at
    # https://docs.microsoft.com/en-us/typography/opentype/spec/gdef
    # describes GDEF header version 1.3, which is not yet recognized
    # by FontVal, thus resulting in this spurious false-FAIL:
    "The version number is neither 0x00010000 nor 0x0001002",

    # These messages below are simply fontval given user feedback
    # on the progress of runnint it. It has nothing to do with
    # actual issues on the font files:
    "Validating glyph with index",
    "Table Test:",

    # No software is affected by Mac strings nowadays.
    # More info at: googlei18n/fontmake#414
    "The table doesn't contain strings for Mac platform",
    "The PostScript string is not present for both required platforms",

    # Font Bakery has got a native check for the xAvgCharWidth field
    # which is: com.google.fonts/check/xavgcharwidth
    "The xAvgCharWidth field does not equal the calculated value",

    # The optimal ordering suggested by FVal check W0020 seems to only be
    # relevant to performance optimizations on old versions of Windows
    # running on old hardware. Since such performance considerations
    # are most likely negligible, we're not going to bother users with
    # this check's table ordering requirements.
    # More info at:
    # https://github.com/googlefonts/fontbakery/issues/2105
    "Tables are not in optimal order",

    # Font Bakery has its own check for required/optional tables:
    # com.google.fonts/check/required_tables
    "Recommended table is missing"
  ]

  # There are also some checks that do not make
  # sense when we're dealing with variable fonts:
  VARFONT_disabled_fval_checks = [
    # Variable fonts typically do have lots of self-intersecting
    # contours because they are used to draw each portion
    # of variable glyph features.
    "Intersecting contours",
    "Intersecting components of composite glyph",

    # DeltaFormat = 32768 (same as 0x8000) means VARIATION_INDEX,
    # according to https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2
    # The FontVal problem description for this check (E5200) only mentions
    # the other values as possible valid ones. So apparently this means FontVal
    # implementation is not up-to-date with more recent versions of the OpenType spec
    # and that's why these spurious FAILs are being emitted.
    # That's good enough reason to mute it.
    # More info at:
    # https://github.com/googlefonts/fontbakery/issues/2109
    "The device table's DeltaFormat value is invalid"
  ]

  from fontTools.ttLib import TTFont
  if is_variable_font(TTFont(font)):
    disabled_fval_checks.extend(VARFONT_disabled_fval_checks)

  try:
    import subprocess
    fval_cmd = [
        "FontValidator", "-file", font, "-all-tables",
        "-report-in-font-dir", "-no-raster-tests"
    ]
    subprocess.check_output(fval_cmd, stderr=subprocess.STDOUT)
  except subprocess.CalledProcessError as e:
    filtered_msgs = ""
    for line in e.output.decode().split("\n"):
      disable_it = False
      for substring in disabled_fval_checks:
        if substring in line:
          disable_it = True
      if not disable_it:
        filtered_msgs += line + "\n"
    yield INFO, ("Microsoft Font Validator returned an error code."
                 " Output follows :\n\n{}\n").format(filtered_msgs)
  except (OSError, IOError) as error:
    yield ERROR, ("Mono runtime and/or "
                  "Microsoft Font Validator are not available!")
    raise error

  def report_message(msg, details):
    if details:
      if isinstance(details, list) and len(details) > 1:
        # We'll print lists with one item per line for
        # improved readability.
        if None in details:
          details.remove(None)

        # A designer will likely not need the full list
        # in order to fix a problem.
        # Showing only the 10 first ones is more than enough
        # and helps avoid flooding the report.
        if len(details) > 25:
          num_similar = len(details) - 10
          details = details[:10]
          details.append(f"NOTE: {num_similar} other similar"
                          " results were hidden!")
        details = '\n\t- ' + '\n\t- '.join(details)
      return f"MS-FonVal: {msg} DETAILS: {details}"
    else:
      return f"MS-FonVal: {msg}"

  xml_report_file = f"{font}.report.xml"
  html_report_file = f"{font}.report.html"
  fval_file = os.path.join(os.path.dirname(font), 'fval.xsl')

  grouped_msgs = {}
  with open(xml_report_file, "rb") as xml_report:
    import defusedxml.lxml
    doc = defusedxml.lxml.parse(xml_report)

    for report in doc.iter('Report'):
      msg = report.get("Message")
      details = report.get("Details")

      disable_it = False
      for substring in disabled_fval_checks:
        if substring in msg:
          disable_it = True
      if disable_it:
        continue

      if msg not in grouped_msgs:
        grouped_msgs[msg] = {"errortype": report.get("ErrorType"),
                             "details": [details]}
      else:
        if details not in grouped_msgs[msg]["details"]:
          # avoid cluttering the output with tons of identical reports
          # yield INFO, 'grouped_msgs[msg]["details"]: {}'.format(grouped_msgs[msg]["details"])
          grouped_msgs[msg]["details"].append(details)

  # ---------------------------
  # Clean-up generated files...
  os.remove(xml_report_file)
  # FontVal internal detail: HTML report generated only on non-Windows due to
  # Mono or the used HTML renderer not being able to render XML with a
  # stylesheet directly. https://github.com/googlefonts/fontbakery/issues/1747
  if os.path.exists(html_report_file):
    os.remove(html_report_file)
  os.remove(fval_file)

  # ---------------------------
  # Here we start emitting the grouped log messages
  for msg, data in grouped_msgs.items():
    # But before printing we try to make the "details" more
    # readable. Otherwise the user would get the text terminal
    # flooded with messy data.

    # No need to print is as a list if wereally only
    # got one log message of this kind:
    if len(data["details"]) == 1:
      data["details"] = data["details"][0]

    # Simplify the list of glyph indices by only displaying
    # their numerical values in a list:
    for glyph_index in ["Glyph index ", "glyph# "]:
      if data["details"] and \
         data["details"][0] and \
         glyph_index in data["details"][0]:
        try:
          data["details"] = {'Glyph index': [int(x.split(glyph_index)[1])
                                             for x in data["details"]]}
          break
        except ValueError:
          pass

    # And, finally, the log messages are emitted:
    if data["errortype"] == "P":
      yield PASS, report_message(msg, data["details"])

    elif data["errortype"] == "E":
      status = FAIL
      for substring in downgrade_to_warn:
        if substring in msg:
          status = WARN
      yield status, report_message(msg, data["details"])

    elif data["errortype"] == "W":
      yield WARN, report_message(msg, data["details"])

    else:
      yield INFO, report_message(msg, data["details"])
711398
public boolean setKerning(int char1, int char2, int kern) {
        String first = GlyphList.unicodeToName(char1);
        if (first == null)
            return false;
        String second = GlyphList.unicodeToName(char2);
        if (second == null)
            return false;
        Object obj[] = (Object[])KernPairs.get(first);
        if (obj == null) {
            obj = new Object[]{second, Integer.valueOf(kern)};
            KernPairs.put(first, obj);
            return true;
        }
        for (int k = 0; k < obj.length; k += 2) {
            if (second.equals(obj[k])) {
                obj[k + 1] = Integer.valueOf(kern);
                return true;
            }
        }
        int size = obj.length;
        Object obj2[] = new Object[size + 2];
        System.arraycopy(obj, 0, obj2, 0, size);
        obj2[size] = second;
        obj2[size + 1] = Integer.valueOf(kern);
        KernPairs.put(first, obj2);
        return true;
    }
449960
def com_google_fonts_check_metadata_italic_style(ttFont, font_metadata):
  """"""
  from fontbakery.utils import get_name_entry_strings
  from fontbakery.constants import MacStyle

  if font_metadata.style != "italic":
    yield SKIP, "This check only applies to italic fonts."
  else:
    font_fullname = get_name_entry_strings(ttFont, NameID.FULL_FONT_NAME)
    if len(font_fullname) == 0:
      yield SKIP, "Font lacks fullname entries in name table."
      # this fail scenario was already checked above
      # (passing those previous checks is a prerequisite for this one)
      # FIXME: Could we pack this into a condition ?
    else:
      # FIXME: here we only check the first name entry.
      #        Should we iterate over them all ? Or should we check
      #        if they're all the same?
      font_fullname = font_fullname[0]

      if not bool(ttFont["head"].macStyle & MacStyle.ITALIC):
        yield FAIL, Message("bad-macstyle",
                            "METADATA.pb style has been set to italic"
                            " but font macStyle is improperly set.")
      elif not font_fullname.split("-")[-1].endswith("Italic"):
        yield FAIL, Message("bad-fullfont-name",
                            ("Font macStyle Italic bit is set"
                             " but nameID {} (\"{}\") is not ended with"
                             " \"Italic\"").format(NameID.FULL_FONT_NAME,
                                                   font_fullname))
      else:
        yield PASS, ("OK: METADATA.pb font.style \"italic\""
                     " matches font internals.")
938636
def com_google_fonts_check_version_bump(ttFont,
                                        api_gfonts_ttFont,
                                        github_gfonts_ttFont):
  """"""
  v_number = ttFont["head"].fontRevision
  api_gfonts_v_number = api_gfonts_ttFont["head"].fontRevision
  github_gfonts_v_number = github_gfonts_ttFont["head"].fontRevision
  failed = False

  if v_number == api_gfonts_v_number:
    failed = True
    yield FAIL, ("Version number {} is equal to"
                 " version on Google Fonts.").format(v_number)

  if v_number < api_gfonts_v_number:
    failed = True
    yield FAIL, ("Version number {} is less than"
                 " version on Google Fonts ({})."
                 "").format(v_number,
                            api_gfonts_v_number)

  if v_number == github_gfonts_v_number:
    failed = True
    yield FAIL, ("Version number {} is equal to"
                 " version on Google Fonts GitHub repo."
                 "").format(v_number)

  if v_number < github_gfonts_v_number:
    failed = True
    yield FAIL, ("Version number {} is less than"
                 " version on Google Fonts GitHub repo ({})."
                 "").format(v_number,
                            github_gfonts_v_number)

  if not failed:
    yield PASS, ("Version number {} is greater than"
                 " version on Google Fonts GitHub ({})"
                 " and production servers ({})."
                 "").format(v_number,
                            github_gfonts_v_number,
                            api_gfonts_v_number)
810205
def com_google_fonts_check_font_copyright(ttFont):
  """"""
  import re
  from fontbakery.utils import get_name_entry_strings

  failed = False
  for string in get_name_entry_strings(ttFont, NameID.COPYRIGHT_NOTICE):
    does_match = re.search(r'Copyright [0-9]{4} The .* Project Authors \([^\@]*\)',
                           string)
    if does_match:
      yield PASS, ("Name Table entry: Copyright field '{}'"
                   " matches canonical pattern.").format(string)
    else:
      failed = True
      yield FAIL, ("Name Table entry: Copyright notices should match"
                   " a pattern similar to:"
                   " 'Copyright 2017 The Familyname"
                   " Project Authors (git url)'\n"
                   "But instead we have got:"
                   " '{}'").format(string)
  if not failed:
    yield PASS, "Name table copyright entries are good"
997342
def __isValidTGZ(self, suffix):
        """"""
        if suffix and isinstance(suffix, string_types):
            if suffix.endswith(".tar.gz") or suffix.endswith(".tgz"):
                return True
        return False
1268480
def is_letter(uni_char):
    """"""
    category = Category.get(uni_char)
    return (category == Category.UPPERCASE_LETTER or
            category == Category.LOWERCASE_LETTER or
            category == Category.TITLECASE_LETTER or
            category == Category.MODIFIER_LETTER or
            category == Category.OTHER_LETTER)
382135
def com_google_fonts_check_post_table_version(ttFont, is_ttf):
  """"""
  formatType = ttFont['post'].formatType
  if is_ttf:
    expected = 2
  else:
    expected = 3
  if formatType != expected:
    yield FAIL, ("Post table should be version {} instead of {}."
                 " More info at https://github.com/google/fonts/"
                 "issues/215").format(expected, formatType)
  else:
    yield PASS, f"Font has post table version {expected}."
199577
private final void readKerning(FontFileReader in) throws IOException {
        // Read kerning
        kerningTab = new java.util.HashMap();
        ansiKerningTab = new java.util.HashMap();
        TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("kern");
        if (dirTab != null) {
            seekTab(in, "kern", 2);
            for (int n = in.readTTFUShort(); n > 0; n--) {
                in.skip(2 * 2);
                int k = in.readTTFUShort();
                if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) {
                    return;
                }
                if ((k >> 8) != 0) {
                    continue;
                }

                k = in.readTTFUShort();
                in.skip(3 * 2);
                while (k-- > 0) {
                    int i = in.readTTFUShort();
                    int j = in.readTTFUShort();
                    int kpx = in.readTTFShort();
                    if (kpx != 0) {
                        // CID kerning table entry, using unicode indexes
                        final Integer iObj = glyphToUnicode(i);
                        final Integer u2 = glyphToUnicode(j);
                        if(iObj==null) {
                            // happens for many fonts (Ubuntu font set),
                            // stray entries in the kerning table?? 
                            log.warn("Unicode index (1) not found for glyph " + i);
                        } else if(u2==null) {
                            log.warn("Unicode index (2) not found for glyph " + i);
                        } else {
                            Map adjTab = (Map)kerningTab.get(iObj);
                            if (adjTab == null) {
                                adjTab = new java.util.HashMap();
                            }
                            adjTab.put(u2,new Integer(kpx));
                            kerningTab.put(iObj, adjTab);
                        }
                    }
                }
            }

            // Create winAnsiEncoded kerning table from kerningTab
            // (could probably be simplified, for now we remap back to CID indexes and then to winAnsi)
            Iterator ae = kerningTab.keySet().iterator();
            while (ae.hasNext()) {
                Integer unicodeKey1 = (Integer)ae.next();
                Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue());
                Map akpx = new java.util.HashMap();
                Map ckpx = (Map)kerningTab.get(unicodeKey1);

                Iterator aee = ckpx.keySet().iterator();
                while (aee.hasNext()) {
                    Integer unicodeKey2 = (Integer)aee.next();
                    Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue());
                    Integer kern = (Integer)ckpx.get(unicodeKey2);

                    Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator();
                    while (uniMap.hasNext()) {
                        Integer unicodeKey = (Integer)uniMap.next();
                        Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue());
                        for (int u = 0; u < ansiKeys.length; u++) {
                            akpx.put(ansiKeys[u], kern);
                        }
                    }
                }

                if (akpx.size() > 0) {
                    Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator();
                    while (uniMap.hasNext()) {
                        Integer unicodeKey = (Integer)uniMap.next();
                        Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue());
                        for (int u = 0; u < ansiKeys.length; u++) {
                            ansiKerningTab.put(ansiKeys[u], akpx);
                        }
                    }
                }
            }
        }
    }
489081
public boolean isOtherFont(Font font) {
        try {
            Font cFont = (Font) currentfont.peek();
            if (cFont.compareTo(font) == 0) return false;
            return true;
        }
        catch(EmptyStackException ese) {
            if (standardfont.compareTo(font) == 0) return false;
            return true;
        }
    }
1247629
def com_google_fonts_check_fvar_name_entries(ttFont):
  """"""

  failed = False
  for instance in ttFont["fvar"].instances:

    entries = [entry for entry in ttFont["name"].names if entry.nameID == instance.subfamilyNameID]
    if len(entries) == 0:
      failed = True
      yield FAIL, (f"Named instance with coordinates {instance.coordinates}"
                   f" lacks an entry on the name table (nameID={instance.subfamilyNameID}).")

  if not failed:
    yield PASS, "OK"
611664
function isPlainText($s) {
        $arrfailAt = array("*", "fonttbl", "colortbl", "datastore", "themedata");
        for ($i = 0; $i < count($arrfailAt); $i++) {
            if (!empty($s[$arrfailAt[$i]])) {
                return false;
            }
        }
        return true;
    }
1312698
def com_google_fonts_check_name_unwanted_chars(ttFont):
  """"""
  failed = False
  replacement_map = [("\u00a9", '(c)'),
                     ("\u00ae", '(r)'),
                     ("\u2122", '(tm)')]
  for name in ttFont['name'].names:
    string = str(name.string, encoding=name.getEncoding())
    for mark, ascii_repl in replacement_map:
      new_string = string.replace(mark, ascii_repl)
      if string != new_string:
        yield FAIL, ("NAMEID #{} contains symbol that should be"
                     " replaced by '{}'.").format(name.nameID,
                                                  ascii_repl)
        failed = True
  if not failed:
    yield PASS, ("No need to substitute copyright, registered and"
                 " trademark symbols in name table entries of this font.")
552145
static boolean hasFunkyChars(String s) {
        int length = s.length();

        if (length == 0)
            return false;

        for (int i = 0; i < length; ++i) {
            char c = s.charAt(i);

            if (Character.isLetterOrDigit(c) || c == '-' || c == '_')
                continue;
            else
                return true;
        }
        return false;
    }