mirror of
https://github.com/rsms/inter.git
synced 2024-09-11 10:55:24 +03:00
adds two debugging tools: lsfvar.py & lsstat.py
This commit is contained in:
parent
1920f75082
commit
1bf738cec6
184
misc/tools/lsfvar.py
Normal file
184
misc/tools/lsfvar.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import sys, os, os.path, re, argparse, pprint, shutil
|
||||||
|
import yaml
|
||||||
|
try:
|
||||||
|
from yaml import CLoader as YamlLoader, CDumper as YamlDumper
|
||||||
|
except ImportError:
|
||||||
|
from yaml import Loader as YamlLoader, Dumper as YamlDumper
|
||||||
|
from collections import OrderedDict
|
||||||
|
from multiprocessing import Pool
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
|
||||||
|
|
||||||
|
def read_fvar_table(filename: str) -> {str:dict}:
|
||||||
|
font = TTFont(filename, recalcBBoxes=False, recalcTimestamp=False)
|
||||||
|
nametab = font["name"]
|
||||||
|
|
||||||
|
names = {}
|
||||||
|
for rec in nametab.names:
|
||||||
|
names[rec.nameID] = rec.toUnicode()
|
||||||
|
|
||||||
|
fvartab = font["fvar"]
|
||||||
|
axes = []
|
||||||
|
for a in fvartab.axes:
|
||||||
|
# axisTag
|
||||||
|
# axisNameID
|
||||||
|
# flags
|
||||||
|
# minValue
|
||||||
|
# defaultValue
|
||||||
|
# maxValue
|
||||||
|
d = a.__dict__
|
||||||
|
for k in list(d.keys()):
|
||||||
|
# replace name-table IDs with their values
|
||||||
|
if k.endswith('NameID'):
|
||||||
|
d[k[:-2]] = names[d[k]]
|
||||||
|
del d[k]
|
||||||
|
axes.append(d)
|
||||||
|
|
||||||
|
instances = []
|
||||||
|
for inst in fvartab.instances:
|
||||||
|
d = inst.__dict__
|
||||||
|
for k in list(d.keys()):
|
||||||
|
# replace name-table IDs with their values
|
||||||
|
if k.endswith('NameID'):
|
||||||
|
d[k[:-2]] = names[d[k]]
|
||||||
|
del d[k]
|
||||||
|
instances.append(d)
|
||||||
|
|
||||||
|
return OrderedDict(axes=axes, instances=instances)
|
||||||
|
|
||||||
|
|
||||||
|
def fmtstructured(tables: [dict], files: [str], cl: dict) -> str:
|
||||||
|
doc = {}
|
||||||
|
for i, tab in enumerate(tables):
|
||||||
|
file = files[i]
|
||||||
|
doc[file] = dict(tab)
|
||||||
|
cols = shutil.get_terminal_size((80, 20)).columns
|
||||||
|
if cl.format == 'yaml':
|
||||||
|
return yaml.dump(doc, Dumper=YamlDumper, width=cols)
|
||||||
|
return pprint.PrettyPrinter(indent=2, width=cols).pformat(doc)
|
||||||
|
|
||||||
|
|
||||||
|
def fmtplaintext(out: [str], header: [str], rows: [[str]]) -> str:
|
||||||
|
# calculate width needed for columns
|
||||||
|
colw = [0] * len(header)
|
||||||
|
for i, s in enumerate(header):
|
||||||
|
colw[i] = max(colw[i], len(s))
|
||||||
|
for row in rows:
|
||||||
|
for i, s in enumerate(row):
|
||||||
|
colw[i] = max(colw[i], len(s))
|
||||||
|
|
||||||
|
colglue, divglue, divchar = ' │ ', '─┼─', '─'
|
||||||
|
row_prefix, row_suffix, div_prefix = '', '\n', ''
|
||||||
|
if cl.format == 'md':
|
||||||
|
colglue, divglue, divchar = ' | ', ' | ', '-'
|
||||||
|
row_prefix, row_suffix, div_prefix = '| ', ' |\n', ':'
|
||||||
|
|
||||||
|
def format_row(row):
|
||||||
|
out.append(row_prefix)
|
||||||
|
out.append(colglue.join(['%-*s' % (colw[i], s) for i, s in enumerate(row)]))
|
||||||
|
out.append(row_suffix)
|
||||||
|
|
||||||
|
def format_divider():
|
||||||
|
out.append(row_prefix)
|
||||||
|
xs = [div_prefix + (divchar * (colw[i] - len(div_prefix))) for i in range(len(row))]
|
||||||
|
out.append(divglue.join(xs))
|
||||||
|
out.append(row_suffix)
|
||||||
|
|
||||||
|
format_row(header)
|
||||||
|
format_divider()
|
||||||
|
for row in rows:
|
||||||
|
format_row(row)
|
||||||
|
|
||||||
|
|
||||||
|
def build_axes(tables: [dict], files: [str]) -> ([str], [[str]]):
|
||||||
|
uniqueAxes1 = OrderedDict()
|
||||||
|
for i, tab in enumerate(tables):
|
||||||
|
file = files[i]
|
||||||
|
for a in tab["axes"]:
|
||||||
|
vals = OrderedDict(sorted(a.items())).values()
|
||||||
|
key = ' '.join([str(v) for v in vals])
|
||||||
|
d = uniqueAxes1.get(key, {})
|
||||||
|
filev = d.get('files', [])
|
||||||
|
d.update(a)
|
||||||
|
filev.append(file)
|
||||||
|
d['files'] = filev
|
||||||
|
uniqueAxes1[key] = d
|
||||||
|
|
||||||
|
uniqueAxes = dict() # {tag:[{axis, ...}]}
|
||||||
|
for a in uniqueAxes1.values():
|
||||||
|
v = uniqueAxes.get(a['axisTag'], [])
|
||||||
|
v.append(a)
|
||||||
|
uniqueAxes[a['axisTag']] = v
|
||||||
|
|
||||||
|
header = ['tag', 'name', 'flags', 'min', 'default', 'max', 'files']
|
||||||
|
rows = []
|
||||||
|
for axesForTag in uniqueAxes.values():
|
||||||
|
for a in axesForTag:
|
||||||
|
rows.append([
|
||||||
|
a["axisTag"],
|
||||||
|
a["axisName"],
|
||||||
|
str(a["flags"]),
|
||||||
|
str(a["minValue"]),
|
||||||
|
str(a["defaultValue"]),
|
||||||
|
str(a["maxValue"]),
|
||||||
|
', '.join(a["files"]),
|
||||||
|
])
|
||||||
|
|
||||||
|
return header, rows
|
||||||
|
|
||||||
|
|
||||||
|
def build_instances(tables: [dict], files: [str]) -> ([str], [[str]]):
|
||||||
|
axisTags = []
|
||||||
|
for axis in tables[0]["axes"]:
|
||||||
|
axisTags.append(axis["axisTag"])
|
||||||
|
header = axisTags + ['flags', 'postscriptName', 'subfamilyName', 'file']
|
||||||
|
rows = []
|
||||||
|
for i, tab in enumerate(tables):
|
||||||
|
file = files[i]
|
||||||
|
axes = tab['axes']
|
||||||
|
for inst in tab['instances']:
|
||||||
|
row = []
|
||||||
|
for axisTag in axisTags:
|
||||||
|
row.append(str(inst['coordinates'][axisTag]))
|
||||||
|
row.append(str(inst['flags']))
|
||||||
|
row.append(inst['postscriptName'])
|
||||||
|
row.append(inst['subfamilyName'])
|
||||||
|
row.append(file)
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
return header, rows
|
||||||
|
|
||||||
|
|
||||||
|
def format_fvar(tables: [dict], files: [str], cl: dict) -> str:
|
||||||
|
if cl.format in ('yaml', 'py'):
|
||||||
|
return fmtstructured(tables, files, cl)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
|
||||||
|
# axes
|
||||||
|
header, rows = build_axes(tables, files)
|
||||||
|
fmtplaintext(out, header, rows)
|
||||||
|
out.append('\n')
|
||||||
|
|
||||||
|
# instances
|
||||||
|
header, rows = build_instances(tables, files)
|
||||||
|
fmtplaintext(out, header, rows)
|
||||||
|
|
||||||
|
return ''.join(out)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
argparser = argparse.ArgumentParser(description='Print fvar table entries')
|
||||||
|
a = lambda *args, **kwargs: argparser.add_argument(*args, **kwargs)
|
||||||
|
a('-f', '--format', metavar='<format>', help='One of: plain, yaml, py, md')
|
||||||
|
a('inputs', metavar='<file>', nargs='+', help='Input fonts (ttf or otf)')
|
||||||
|
cl = argparser.parse_args()
|
||||||
|
|
||||||
|
if cl.format not in (None, 'plain', 'yaml', 'py', 'md'):
|
||||||
|
raise Exception(f'unknown format: "{cl.format}"')
|
||||||
|
|
||||||
|
with Pool() as p:
|
||||||
|
tables = p.map(read_fvar_table, cl.inputs)
|
||||||
|
|
||||||
|
filenames = [os.path.basename(fn) for fn in cl.inputs]
|
||||||
|
print(format_fvar(tables, filenames, cl))
|
194
misc/tools/lsstat.py
Normal file
194
misc/tools/lsstat.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import sys, os, os.path, re, argparse, pprint, shutil
|
||||||
|
import yaml
|
||||||
|
try:
|
||||||
|
from yaml import CLoader as YamlLoader, CDumper as YamlDumper
|
||||||
|
except ImportError:
|
||||||
|
from yaml import Loader as YamlLoader, Dumper as YamlDumper
|
||||||
|
from collections import OrderedDict
|
||||||
|
from multiprocessing import Pool
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
|
||||||
|
|
||||||
|
TODO_PRINTS = dict()
|
||||||
|
|
||||||
|
|
||||||
|
def getName(nametab: object, nameID: int) -> str:
|
||||||
|
return nametab.getName(nameID, 3, 1).toUnicode()
|
||||||
|
|
||||||
|
|
||||||
|
def read_stat_table(filename: str) -> {str:dict}:
|
||||||
|
font = TTFont(filename, recalcBBoxes=False, recalcTimestamp=False)
|
||||||
|
nametab = font["name"]
|
||||||
|
stattab = font["STAT"].table.__dict__
|
||||||
|
|
||||||
|
designAxisRecord = stattab['DesignAxisRecord'].Axis
|
||||||
|
axisValueArray = stattab['AxisValueArray'].AxisValue
|
||||||
|
|
||||||
|
axes = OrderedDict()
|
||||||
|
axesArray = []
|
||||||
|
|
||||||
|
for r in designAxisRecord:
|
||||||
|
tag = str(r.AxisTag)
|
||||||
|
axis = dict(
|
||||||
|
tag = tag,
|
||||||
|
name = getName(nametab, r.AxisNameID),
|
||||||
|
values = [],
|
||||||
|
)
|
||||||
|
axesArray.append(axis)
|
||||||
|
axes[tag] = axis
|
||||||
|
|
||||||
|
for r in axisValueArray:
|
||||||
|
axis = axesArray[r.AxisIndex]
|
||||||
|
format = r.Format
|
||||||
|
value = dict(
|
||||||
|
axisIndex = r.AxisIndex,
|
||||||
|
format = format,
|
||||||
|
flags = r.Flags,
|
||||||
|
name = getName(nametab, r.ValueNameID),
|
||||||
|
)
|
||||||
|
if format == 1:
|
||||||
|
value['value'] = r.Value
|
||||||
|
elif format == 2:
|
||||||
|
value['value'] = r.NominalValue
|
||||||
|
value['rangeMaxValue'] = r.RangeMaxValue
|
||||||
|
value['rangeMinValue'] = r.RangeMinValue
|
||||||
|
elif format == 3:
|
||||||
|
value['value'] = r.Value
|
||||||
|
value['linkedValue'] = r.LinkedValue
|
||||||
|
elif format == 4:
|
||||||
|
if not TODO_PRINTS.get('format4'):
|
||||||
|
TODO_PRINTS['format4'] = True
|
||||||
|
print('TODO: implement support for format 4 STAT records', file=sys.stderr)
|
||||||
|
axis['values'].append(value)
|
||||||
|
|
||||||
|
version = stattab['Version'] # e.g. 0x00010002 for 1.2
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
version = '%d.%d' % (version >> 16, version & 0xffff),
|
||||||
|
axes = axes,
|
||||||
|
elidedFallbackName = getName(nametab, stattab['ElidedFallbackNameID']),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fmtstructured(tables: [dict], files: [str], cl: dict) -> str:
|
||||||
|
doc = {}
|
||||||
|
for i, tab in enumerate(tables):
|
||||||
|
file = files[i]
|
||||||
|
doc[file] = dict(tab)
|
||||||
|
cols = shutil.get_terminal_size((80, 20)).columns
|
||||||
|
if cl.format == 'yaml':
|
||||||
|
return yaml.dump(doc, Dumper=YamlDumper, width=cols)
|
||||||
|
return pprint.PrettyPrinter(indent=2, width=cols).pformat(doc)
|
||||||
|
|
||||||
|
|
||||||
|
def fmtplaintext(out: [str], header: [str], rows: [[str]], cl: dict) -> str:
|
||||||
|
# calculate width needed for columns
|
||||||
|
colw = [0] * len(header)
|
||||||
|
for i, s in enumerate(header):
|
||||||
|
colw[i] = max(colw[i], len(s))
|
||||||
|
colw2 = [0] * len(header)
|
||||||
|
for row in rows:
|
||||||
|
for i, s in enumerate(row):
|
||||||
|
colw[i] = max(colw[i], len(s))
|
||||||
|
colw2[i] = max(colw2[i], len(s))
|
||||||
|
|
||||||
|
# elide empty columns
|
||||||
|
if 0 in colw2:
|
||||||
|
emptyColsIndices = set([i for i, w in enumerate(colw2) if w == 0])
|
||||||
|
header = [s for i, s in enumerate(header) if colw2[i] > 0]
|
||||||
|
rows2 = []
|
||||||
|
for row in rows:
|
||||||
|
row2 = []
|
||||||
|
for i, val in enumerate(row):
|
||||||
|
if i not in emptyColsIndices:
|
||||||
|
row2.append(val)
|
||||||
|
rows2.append(row2)
|
||||||
|
rows = rows2
|
||||||
|
colw = [w for i, w in enumerate(colw) if colw2[i] > 0]
|
||||||
|
|
||||||
|
colglue, divglue, divchar = ' │ ', '─┼─', '─'
|
||||||
|
row_prefix, row_suffix, div_prefix = '', '\n', ''
|
||||||
|
if cl.format == 'md':
|
||||||
|
colglue, divglue, divchar = ' | ', ' | ', '-'
|
||||||
|
row_prefix, row_suffix, div_prefix = '| ', ' |\n', ':'
|
||||||
|
|
||||||
|
def format_row(row):
|
||||||
|
out.append(row_prefix)
|
||||||
|
out.append(colglue.join(['%-*s' % (colw[i], s) for i, s in enumerate(row)]))
|
||||||
|
out.append(row_suffix)
|
||||||
|
|
||||||
|
def format_divider():
|
||||||
|
out.append(row_prefix)
|
||||||
|
xs = [div_prefix + (divchar * (colw[i] - len(div_prefix))) for i in range(len(colw))]
|
||||||
|
out.append(divglue.join(xs))
|
||||||
|
out.append(row_suffix)
|
||||||
|
|
||||||
|
format_row(header)
|
||||||
|
format_divider()
|
||||||
|
for row in rows:
|
||||||
|
format_row(row)
|
||||||
|
|
||||||
|
|
||||||
|
def format_stat(tables: [dict], files: [str], cl: dict) -> str:
|
||||||
|
if cl.format in ('yaml', 'py'):
|
||||||
|
return fmtstructured(tables, files, cl)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
|
||||||
|
uniqueAxes = OrderedDict()
|
||||||
|
for tab in tables:
|
||||||
|
for a in tab["axes"].values():
|
||||||
|
uniqueAxes[a['tag']] = a['name']
|
||||||
|
|
||||||
|
rubric_prefix, rubric_suffix = '', '\n'
|
||||||
|
if cl.format == 'md':
|
||||||
|
rubric_prefix, rubric_suffix = '### ', '\n\n'
|
||||||
|
|
||||||
|
header = ['name', 'value', 'minval', 'maxval', 'linkval', 'format', 'flags', 'file']
|
||||||
|
for axisTag, axisName in uniqueAxes.items():
|
||||||
|
out.append(f"{rubric_prefix}{axisTag} ({axisName}){rubric_suffix}")
|
||||||
|
rows = []
|
||||||
|
for i, tab in enumerate(tables):
|
||||||
|
file = files[i]
|
||||||
|
axis = tab["axes"].get(axisTag)
|
||||||
|
if not axis:
|
||||||
|
continue
|
||||||
|
for v in axis['values']:
|
||||||
|
row = [
|
||||||
|
v['name'],
|
||||||
|
str(v['value']),
|
||||||
|
str(v.get('rangeMinValue', '')),
|
||||||
|
str(v.get('rangeMaxValue', '')),
|
||||||
|
str(v.get('linkedValue', '')),
|
||||||
|
str(v['format']),
|
||||||
|
str(v['flags']),
|
||||||
|
file,
|
||||||
|
]
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
fmtplaintext(out, header, rows, cl)
|
||||||
|
out.append('\n')
|
||||||
|
|
||||||
|
if len(uniqueAxes) > 0:
|
||||||
|
out.pop() # undo last \n
|
||||||
|
|
||||||
|
return ''.join(out)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
argparser = argparse.ArgumentParser(description='Print STAT table entries')
|
||||||
|
a = lambda *args, **kwargs: argparser.add_argument(*args, **kwargs)
|
||||||
|
a('-f', '--format', metavar='<format>', help='One of: plain, yaml, py, md')
|
||||||
|
a('inputs', metavar='<file>', nargs='+', help='Input fonts (ttf or otf)')
|
||||||
|
cl = argparser.parse_args()
|
||||||
|
|
||||||
|
if cl.format not in (None, 'plain', 'yaml', 'py', 'md'):
|
||||||
|
raise Exception(f'unknown format: "{cl.format}"')
|
||||||
|
|
||||||
|
with Pool() as p:
|
||||||
|
tables = p.map(read_stat_table, cl.inputs)
|
||||||
|
|
||||||
|
print(pprint.PrettyPrinter(indent=2).pformat(tables[0]))
|
||||||
|
|
||||||
|
filenames = [os.path.basename(fn) for fn in cl.inputs]
|
||||||
|
print(format_stat(tables, filenames, cl))
|
Loading…
Reference in New Issue
Block a user