From b290d4ad764a414d05763d16035d3b3e42cddb46 Mon Sep 17 00:00:00 2001 From: Martin von Gagern Date: Thu, 9 Jul 2015 17:33:50 +0200 Subject: [PATCH] Switch from fontforge to fonttools The dependencies of fonttools are much lighter than fontforge, and since all we need are some metrics, fonttools is very much up to that task. This addresses issue #288. --- metrics/README.md | 8 ++--- metrics/extract_ttfs.py | 68 ++++++++++++++++++++++++---------------- src/fontMetricsData.json | 6 ++-- 3 files changed, 48 insertions(+), 34 deletions(-) diff --git a/metrics/README.md b/metrics/README.md index 8f8ed4587..4531461ef 100644 --- a/metrics/README.md +++ b/metrics/README.md @@ -7,11 +7,11 @@ There are several requirements for generating the metrics used by KaTeX. this by running `tex --version`, and seeing if it has a line that looks like > kpathsea version 6.2.0 -- You need the JSON module for perl. You can install this either from CPAN or with - your package manager. +- You need the JSON module for perl. You can install this either from CPAN + (possibly using the `cpan` command line tool) or with your package manager. -- You need the python fontforge module. This is probably either installed with - fontforge or can be installed from your package manager. +- You need the python module fonttools. You can install this either from PyPi + (using `easy_install` or `pip`) or with your package manager. Once you have these things, run diff --git a/metrics/extract_ttfs.py b/metrics/extract_ttfs.py index edcf0da5a..05b70bfab 100755 --- a/metrics/extract_ttfs.py +++ b/metrics/extract_ttfs.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import fontforge +from fontTools.ttLib import TTFont import sys import json @@ -60,36 +60,50 @@ def main(): start_json = json.load(sys.stdin) for font, chars in metrics_to_extract.iteritems(): - fontInfo = fontforge.open("../static/fonts/KaTeX_" + font + ".ttf") + fontInfo = TTFont("../static/fonts/KaTeX_" + font + ".ttf") + glyf = fontInfo["glyf"] + unitsPerEm = float(fontInfo["head"].unitsPerEm) - for glyph in fontInfo.glyphs(): - try: - char = unichr(glyph.unicode) - except ValueError: + # We keep ALL Unicode cmaps, not just fontInfo["cmap"].getcmap(3, 1). + # This is playing it extra safe, since it reports inconsistencies. + # Platform 0 is Unicode, platform 3 is Windows. For platform 3, + # encoding 1 is UCS-2 and encoding 10 is UCS-4. + cmap = [t.cmap for t in fontInfo["cmap"].tables + if (t.platformID == 0) + or (t.platformID == 3 and t.platEncID in (1, 10))] + + for char, base_char in chars.iteritems(): + code = ord(char) + names = set(t.get(code) for t in cmap) + if not names: + sys.stderr.write( + "Codepoint {} of font {} maps to no name\n" + .format(code, font)) continue + if len(names) != 1: + sys.stderr.write( + "Codepoint {} of font {} maps to multiple names: {}\n" + .format(code, font, ", ".join(sorted(names)))) + continue + name = names.pop() - if char in chars: - _, depth, _, height = glyph.boundingBox() + height = depth = italic = skew = 0 + glyph = glyf[name] + if glyph.numberOfContours: + height = glyph.yMax + depth = -glyph.yMin + if base_char: + base_char_str = str(ord(base_char)) + base_metrics = start_json[font][base_char_str] + italic = base_metrics["italic"] + skew = base_metrics["skew"] - depth = -depth - - base_char = chars[char] - if base_char: - base_char_str = str(ord(base_char)) - base_metrics = start_json[font][base_char_str] - - italic = base_metrics["italic"] - skew = base_metrics["skew"] - else: - italic = 0 - skew = 0 - - start_json[font][str(ord(char))] = { - "height": height / fontInfo.em, - "depth": depth / fontInfo.em, - "italic": italic, - "skew": skew, - } + start_json[font][str(code)] = { + "height": height / unitsPerEm, + "depth": depth / unitsPerEm, + "italic": italic, + "skew": skew, + } sys.stdout.write( json.dumps(start_json, separators=(',', ':'), sort_keys=True)) diff --git a/src/fontMetricsData.json b/src/fontMetricsData.json index caa5ec522..35188677a 100644 --- a/src/fontMetricsData.json +++ b/src/fontMetricsData.json @@ -632,7 +632,7 @@ "8463": {"depth": 0.0, "height": 0.68889, "italic": 0.0, "skew": 0.0} }, "Main-Regular": { - "32": {"depth": -0.0, "height": 0.0, "italic": 0, "skew": 0}, + "32": {"depth": 0.0, "height": 0.0, "italic": 0, "skew": 0}, "33": {"depth": 0.0, "height": 0.69444, "italic": 0.0, "skew": 0.0}, "34": {"depth": 0.0, "height": 0.69444, "italic": 0.0, "skew": 0.0}, "35": {"depth": 0.19444, "height": 0.69444, "italic": 0.0, "skew": 0.0}, @@ -727,7 +727,7 @@ "124": {"depth": 0.25, "height": 0.75, "italic": 0.0, "skew": 0.0}, "125": {"depth": 0.25, "height": 0.75, "italic": 0.0, "skew": 0.0}, "126": {"depth": 0.35, "height": 0.31786, "italic": 0.0, "skew": 0.0}, - "160": {"depth": -0.0, "height": 0.0, "italic": 0, "skew": 0}, + "160": {"depth": 0.0, "height": 0.0, "italic": 0, "skew": 0}, "168": {"depth": 0.0, "height": 0.66786, "italic": 0.0, "skew": 0.0}, "172": {"depth": 0.0, "height": 0.43056, "italic": 0.0, "skew": 0.0}, "175": {"depth": 0.0, "height": 0.56778, "italic": 0.0, "skew": 0.0}, @@ -778,7 +778,7 @@ "8221": {"depth": 0.0, "height": 0.69444, "italic": 0.0, "skew": 0.0}, "8224": {"depth": 0.19444, "height": 0.69444, "italic": 0.0, "skew": 0.0}, "8225": {"depth": 0.19444, "height": 0.69444, "italic": 0.0, "skew": 0.0}, - "8230": {"depth": -0.0, "height": 0.12, "italic": 0, "skew": 0}, + "8230": {"depth": 0.0, "height": 0.12, "italic": 0, "skew": 0}, "8242": {"depth": 0.0, "height": 0.55556, "italic": 0.0, "skew": 0.0}, "8407": {"depth": 0.0, "height": 0.71444, "italic": 0.15382, "skew": 0.0}, "8463": {"depth": 0.0, "height": 0.68889, "italic": 0.0, "skew": 0.0},