diff options
Diffstat (limited to 'text/test-kern.py')
| -rw-r--r-- | text/test-kern.py | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/text/test-kern.py b/text/test-kern.py new file mode 100644 index 0000000..2d423f2 --- /dev/null +++ b/text/test-kern.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""The kern table's class subtables against HarfBuzz, where no font here has +them: + + python3 text/test-kern.py VEGA_SYMBOLS [FONT.ttf] + +Fonts made from DejaVu Sans without its GPOS, their kern table a format 3 +subtable and a format 2 one in every order (and two of either), with what +HarfBuzz's reading of them turns on: class value 1 (out of bounds), +glyphs outside a class table (a digest's false positive takes class 0), +offsets below the kerning array, odd ones, values past the subtable's end +(the last subtable may read on to the table's) and past the table's, class +and index values past their counts. Each is built with vega-symbols, which +refuses a font whose shaping differs from HarfBuzz's on random runs. +Needs fontTools; prints one JSON line, exits 1 on a difference. +""" +import json +import random +import struct +import subprocess +import sys +import tempfile +from pathlib import Path + +from fontTools.ttLib import TTFont, newTable + + +def subtable(format, body): + return struct.pack('>HHBB', 0, 6 + len(body), format, 1) + body # horizontal + + +def format3(rng, glyphs): + lefts, rights, values = 7, 9, 40 + body = struct.pack('>HBBBB', glyphs, values, lefts, rights, 0) + body += b''.join(struct.pack('>h', rng.randrange(-150, 150)) for _ in range(values)) + body += bytes(rng.randrange(lefts + 1) for _ in range(glyphs)) # some past the count + body += bytes(rng.randrange(rights + 1) for _ in range(glyphs)) + body += bytes(rng.randrange(values + 3) for _ in range(lefts * rights)) + return subtable(3, body) + + +def format2(rng, latin): + rows, columns = 6, 8 + width = 2 * columns + first_left, first_right, left_count, right_count = latin[5], latin[3], 60, 70 + left_at = 6 + 8 + right_at = left_at + 4 + 2 * left_count + array = right_at + 4 + 2 * right_count + + def left(): + k = rng.randrange(20) + return (1 if k == 0 else rng.randrange(array) if k == 1 else array + rows * width + 64 if k == 2 else + array + rng.randrange(rows) * width + (k == 3)) + + def right(): + k = rng.randrange(20) + return 1 if k == 0 else 60000 if k == 1 else 2 * rng.randrange(columns) + + body = struct.pack('>HHHH', width, left_at, right_at, array) + body += struct.pack('>HH', first_left, left_count) + b''.join(struct.pack('>H', left()) for _ in range(left_count)) + body += struct.pack('>HH', first_right, right_count) + b''.join(struct.pack('>H', right()) for _ in range(right_count)) + body += b''.join(struct.pack('>h', rng.randrange(-120, 120)) for _ in range(rows * columns)) + return subtable(2, body) + + +def main(): + tool = sys.argv[1] + source = sys.argv[2] if len(sys.argv) > 2 else '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf' + fonts = plans = differing = 0 + with tempfile.TemporaryDirectory(prefix='vega-kern-') as directory: + work = Path(directory) + for seed in range(1, 5): + for order in ('32', '23', '3', '2', '22', '33'): + rng = random.Random(seed * 100 + len(order)) + font = TTFont(source) + del font['GPOS'] + glyphs = len(font.getGlyphOrder()) + cmap = font.getBestCmap() + latin = sorted({font.getGlyphID(cmap[c]) for c in range(0x20, 0x7F) if c in cmap}) + tables = [format3(rng, glyphs) if c == '3' else format2(rng, latin) for c in order] + font['kern'] = newTable('kern') + font['kern'].decompile(struct.pack('>HH', 0, len(tables)) + b''.join(tables), font) + path = work / f'kern-{seed}-{order}.ttf' + font.save(path) + built = subprocess.run([tool, 'build', '--font', str(path), '--codes', '0x20-0x7e', '--shape', + 'Latn,Zyyy', '--runs', '3000', '-o', str(work / 'kern.vsym')], + capture_output=True, text=True) + results = [json.loads(line) for line in built.stdout.splitlines() if line.startswith('{"script"')] + fonts += 1 + plans += len(results) + if built.returncode or not results or any(r['differences'] for r in results): + differing += 1 + print(f'seed {seed}, subtables {order}:\n{built.stderr[-2000:]}', file=sys.stderr) + print(json.dumps({'fonts': fonts, 'plans': plans, 'differing_fonts': differing})) + return 1 if differing else 0 + + +if __name__ == '__main__': + sys.exit(main()) |
