import os
os.environ.setdefault('SOURCE_DATE_EPOCH', '0')
from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.t2CharStringPen import T2CharStringPen
from fontTools.cffLib import (
FDArrayIndex,
FDSelect,
FontDict,
PrivateDict,
)
UPM = 1000
LETTERS = [chr(ord('A') + i) for i in range(10)] ADVANCES = {'.notdef': 500, 'space': 250}
ADVANCES.update({ch: 300 + 50 * i for i, ch in enumerate(LETTERS)})
GLYPH_ORDER = ['.notdef', 'space'] + LETTERS
CIDS = [0, 700, 901, 811, 821, 831, 841, 851, 861, 871, 881, 891]
OUT_DIR = os.path.join(os.path.dirname(__file__), '..', 'tests', 'assets', 'fonts', 'mock')
def draw_rect(pen, width):
pen.moveTo((0, 0))
pen.lineTo((width, 0))
pen.lineTo((width, 700))
pen.lineTo((0, 700))
pen.closePath()
def cmap():
m = {0x20: 'space'}
m.update({ord(ch): ch for ch in LETTERS})
return m
def common_setup(fb, glyph_order):
fb.setupGlyphOrder(glyph_order)
fb.setupCharacterMap({cp: n for cp, n in cmap().items()
if n in glyph_order or n in ('space',)})
fb.setupHorizontalMetrics({n: (ADVANCES[base_name(n)], 0) for n in glyph_order})
fb.setupHorizontalHeader(ascent=800, descent=-200)
fb.setupNameTable({'familyName': 'MockFont', 'styleName': 'Regular'})
fb.setupOS2(sTypoAscender=800, sTypoDescender=-200, usWinAscent=800, usWinDescent=200)
fb.setupPost()
def base_name(glyph_name):
return glyph_name
def build_ttf(path):
fb = FontBuilder(UPM, isTTF=True)
glyphs = {}
for name in GLYPH_ORDER:
pen = TTGlyphPen(None)
if name not in ('.notdef', 'space'):
draw_rect(pen, ADVANCES[name])
glyphs[name] = pen.glyph()
common_setup(fb, GLYPH_ORDER)
fb.setupGlyf(glyphs)
fb.font.save(path)
def build_cff_named(path):
fb = FontBuilder(UPM, isTTF=False)
charstrings = {}
for name in GLYPH_ORDER:
pen = T2CharStringPen(ADVANCES[name], None)
if name not in ('.notdef', 'space'):
draw_rect(pen, ADVANCES[name])
charstrings[name] = pen.getCharString()
common_setup(fb, GLYPH_ORDER)
fb.setupCFF('MockFont-Regular', {'FullName': 'MockFont Regular'}, charstrings, {})
fb.font.save(path)
def build_cff_cid(path):
fb = FontBuilder(UPM, isTTF=False)
charstrings = {}
for name in GLYPH_ORDER:
pen = T2CharStringPen(ADVANCES[name], None)
if name not in ('.notdef', 'space'):
draw_rect(pen, ADVANCES[name])
charstrings[name] = pen.getCharString()
common_setup(fb, GLYPH_ORDER)
fb.setupCFF('MockFont-CID', {'FullName': 'MockFont CID'}, charstrings, {})
font = fb.font
cff = font['CFF '].cff
td = cff[cff.fontNames[0]]
rename = {GLYPH_ORDER[gid]: ('.notdef' if gid == 0 else 'cid%05d' % CIDS[gid])
for gid in range(len(GLYPH_ORDER))}
new_order = [rename[n] for n in GLYPH_ORDER]
cs = td.CharStrings
for old, new in rename.items():
if old == new:
continue
cs.charStrings[new] = cs.charStrings.pop(old)
td.charset = new_order
font.setGlyphOrder(new_order)
if hasattr(font, '_reverseGlyphOrderDict'):
del font._reverseGlyphOrderDict
for table in font['cmap'].tables:
table.cmap = {cp: rename.get(n, n) for cp, n in table.cmap.items()}
hmtx = font['hmtx']
hmtx.metrics = {rename.get(n, n): v for n, v in hmtx.metrics.items()}
td.ROS = ('Adobe', 'Identity', 0)
td.rawDict['ROS'] = td.ROS
td.CIDCount = max(CIDS) + 1
fd = FontDict()
fd.Private = td.Private
fd_array = FDArrayIndex()
fd_array.append(fd)
td.FDArray = fd_array
fd_select = FDSelect()
fd_select.format = 3
fd_select.gidArray = [0] * len(new_order)
td.FDSelect = fd_select
td.rawDict.pop('Private', None)
if hasattr(td, 'Private'):
del td.Private
td.rawDict.pop('Encoding', None)
if hasattr(td, 'Encoding'):
td.Encoding = None
font.save(path)
def main():
os.makedirs(OUT_DIR, exist_ok=True)
build_ttf(os.path.join(OUT_DIR, 'mock_ttf.ttf'))
build_cff_named(os.path.join(OUT_DIR, 'mock_cff_named.otf'))
build_cff_cid(os.path.join(OUT_DIR, 'mock_cff_cid.otf'))
from fontTools.ttLib import TTFont
for fname in ('mock_ttf.ttf', 'mock_cff_named.otf', 'mock_cff_cid.otf'):
p = os.path.join(OUT_DIR, fname)
f = TTFont(p)
assert f['head'].unitsPerEm == UPM
order = f.getGlyphOrder()
assert len(order) == len(GLYPH_ORDER), (fname, order)
hmtx = f['hmtx']
for gid, name in enumerate(order):
expected = ADVANCES[GLYPH_ORDER[gid]]
assert hmtx[name][0] == expected, (fname, name, hmtx[name][0], expected)
if fname == 'mock_cff_cid.otf':
cff = f['CFF '].cff
td = cff[cff.fontNames[0]]
assert hasattr(td, 'ROS'), 'must be CID-keyed'
got = [0 if n == '.notdef' else int(n[3:]) for n in td.charset]
assert got == CIDS, (got, CIDS)
print(f'{fname}: OK ({os.path.getsize(p)} bytes)')
if __name__ == '__main__':
main()