1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
import argparse
from dataclasses import replace
import sys
import glyphsLib
from fontTools.ttLib import TTFont
from fontTools.pens.transformPen import TransformPen
from fontTools.pens.ttGlyphPen import TTGlyphPen
def transform(ttfont, x, y):
glyphset = ttfont.getGlyphSet()
for glyphname in glyphset.keys():
glyph = glyphset[glyphname]
if glyph._getGlyphAndOffset()[0].numberOfContours < 1:
continue
pen = TTGlyphPen(glyphset)
transformpen = TransformPen(pen, (1, 0, 0, 1, x, y))
glyph.draw(transformpen)
transformed = pen.glyph()
ttfont['glyf'].glyphs[glyphname] = transformed
def reencode(ttfont, glyph, cp):
for subtable in ttfont["cmap"].tables:
subtable.cmap[cp] = glyph
def grovel_substitutions(font, lookup, glyphmap):
if lookup.LookupType == 7:
raise NotImplementedError
gmap = lambda g: glyphmap.get(g,g)
go = font.getGlyphOrder()
def do_coverage(c):
c.glyphs = list(sorted([gmap(g) for g in c.glyphs], key=lambda g:go.index(g)))
return c
for st in lookup.SubTable:
if lookup.LookupType == 1:
newmap = {}
for inglyph, outglyph in st.mapping.items():
newmap[gmap(inglyph)] = gmap(outglyph)
st.mapping = newmap
elif lookup.LookupType == 2:
newmap = {}
for inglyph, outglyphs in st.mapping.items():
newmap[gmap(inglyph)] = [gmap(g) for g in outglyphs]
st.mapping = newmap
elif lookup.LookupType == 4:
newligatures = {}
for outglyph, inglyphs in st.ligatures.items():
for ig in inglyphs:
ig.LigGlyph = gmap(ig.LigGlyph)
ig.Component = [gmap(c) for c in ig.Component]
newligatures[gmap(outglyph)] = inglyphs
st.ligatures = newligatures
elif lookup.LookupType == 5:
if st.Format == 1:
do_coverage(st.Coverage)
for srs in st.SubRuleSet:
for subrule in srs.SubRule:
subrule.Input = [gmap(c) for c in subrule.Input]
elif st.Format == 2:
do_coverage(st.Coverage)
st.ClassDef.classDefs = {gmap(k):v for k,v in st.ClassDef.classDefs.items()}
else:
st.Coverage = [do_coverage(c) for c in st.Coverage]
elif lookup.LookupType == 6:
if st.Format == 1:
do_coverage(st.Coverage)
for srs in st.ChainSubRuleSet:
for subrule in srs.ChainSubRule:
subrule.Backtrack = [gmap(c) for c in subrule.Backtrack]
subrule.Input = [gmap(c) for c in subrule.Input]
subrule.LookAhead = [gmap(c) for c in subrule.LookAhead]
elif st.Format == 2:
do_coverage(st.Coverage)
st.BacktrackClassDef.classDefs = {gmap(k):v for k,v in st.BacktrackClassDef.classDefs.items()}
st.InputClassDef.classDefs = {gmap(k):v for k,v in st.InputClassDef.classDefs.items()}
st.LookAheadClassDef.classDefs = {gmap(k):v for k,v in st.LookAheadClassDef.classDefs.items()}
elif st.Format == 3:
st.BacktrackCoverage = [ do_coverage(c) for c in st.BacktrackCoverage]
st.InputCoverage = [ do_coverage(c) for c in st.InputCoverage]
st.LookAheadCoverage = [ do_coverage(c) for c in st.LookAheadCoverage]
if __name__ == "__main__":
# Parse command line arguments
parser = argparse.ArgumentParser(description='Generate a UI font from existing binary and Glyphs file')
parser.add_argument('--output', '-o', help='Output font file')
parser.add_argument('binary', help='Binary font file')
parser.add_argument('glyphs', help='Glyphs file')
args = parser.parse_args()
if not args.output:
if "-VF.ttf" not in args.binary:
print("Error: Output file not specified")
sys.exit(1)
args.output = args.binary.replace('-VF.ttf', 'UI-VF.ttf')
gsfont = glyphsLib.GSFont(args.glyphs)
ttfont = TTFont(args.binary)
# Find UI instances
cp = None
for instance in gsfont.instances:
if "UI" in instance.familyName:
cp = instance.customParameters
break
if cp is None:
print("No UI instance found")
sys.exit(1)
cp = { p.name: p.value for p in cp }
print("Creating UI font with custom parameters %s" % cp)
if "typoAscender" in cp:
ttfont['OS/2'].sTypoAscender = int(cp["typoAscender"])
if "typoDescender" in cp:
ttfont['OS/2'].sTypoDescender = int(cp["typoDescender"])
if "typoLineGap" in cp:
ttfont['OS/2'].sTypoLineGap = int(cp["typoLineGap"])
if "winAscent" in cp:
ttfont['OS/2'].usWinAscent = int(cp["winAscent"])
if "winDescent" in cp:
ttfont['OS/2'].usWinDescent = int(cp["winDescent"])
if "hheaAscender" in cp:
ttfont['hhea'].ascent = int(cp["hheaAscender"])
if "hheaDescender" in cp:
ttfont['hhea'].descent = int(cp["hheaDescender"])
if "hheaLineGap" in cp:
ttfont['hhea'].lineGap = int(cp["hheaLineGap"])
if "xHeight" in cp:
ttfont['OS/2'].sxHeight = int(cp["xHeight"])
if "Filter" in cp:
assert cp["Filter"].startswith("Transformations;")
filter_args = cp["Filter"].split(";")
filter_args = filter_args[1:-1]
filter_args = { arg.split(":")[0]: arg.split(":")[1] for arg in filter_args }
if "ScaleX" in filter_args or "ScaleY" in filter_args:
print("ScaleX and ScaleY are not supported")
sys.exit(1)
offset_x = int(filter_args.get("OffsetX", "0"))
offset_y = int(filter_args.get("OffsetY", "0"))
if offset_x or offset_y:
print("Transforming glyphs by %d, %d" % (offset_x, offset_y))
transform(ttfont, offset_x, offset_y)
if "Reencode Glyphs" in cp:
for parm in cp["Reencode Glyphs"]:
glyph, codepoint = parm.split("=")
reencode(ttfont, glyph, int(codepoint, 16))
# Mash the GSUB table
uis = [ x for x in ttfont.getGlyphOrder() if "UI" in x and x.replace("UI", "") in ttfont.getGlyphOrder() ]
glyphmap = { g.replace("UI", ""): g for g in uis}
for lookup in ttfont["GSUB"].table.LookupList.Lookup:
grovel_substitutions(ttfont, lookup, glyphmap)
# Mash the name table
name = ttfont["name"]
name.setName(name.getName(1,3,1).toUnicode()+" UI", 1, 3, 1, 0x409)
name.setName(name.getName(3,3,1).toUnicode().replace("-","UI-"), 3, 3, 1, 0x409)
name.setName(name.getName(4,3,1).toUnicode().replace(" Regular","UI Regular"), 4, 3, 1, 0x409)
name.setName(name.getName(6,3,1).toUnicode().replace("-Regular","UI-Regular"), 6, 3, 1, 0x409)
ttfont.save(args.output)
|