125 lines
3.6 KiB
Python
125 lines
3.6 KiB
Python
|
import argparse
|
||
|
from collections import defaultdict
|
||
|
import csv
|
||
|
import time
|
||
|
import sys
|
||
|
from pathlib import Path
|
||
|
from typing import Any, Iterable, List, Optional, Sequence, Tuple
|
||
|
|
||
|
from fontTools.ttLib import TTFont
|
||
|
from fontTools.otlLib.optimize import compact
|
||
|
|
||
|
MODES = [str(c) for c in range(1, 10)]
|
||
|
|
||
|
|
||
|
def main(args: Optional[List[str]] = None):
|
||
|
parser = argparse.ArgumentParser()
|
||
|
parser.add_argument("fonts", type=Path, nargs="+", help="Path to TTFs.")
|
||
|
parsed_args = parser.parse_args(args)
|
||
|
|
||
|
runtimes = defaultdict(list)
|
||
|
rows = []
|
||
|
font_path: Path
|
||
|
for font_path in parsed_args.fonts:
|
||
|
font = TTFont(font_path)
|
||
|
if "GPOS" not in font:
|
||
|
print(f"No GPOS in {font_path.name}, skipping.", file=sys.stderr)
|
||
|
continue
|
||
|
size_orig = len(font.getTableData("GPOS")) / 1024
|
||
|
print(f"Measuring {font_path.name}...", file=sys.stderr)
|
||
|
|
||
|
fonts = {}
|
||
|
font_paths = {}
|
||
|
sizes = {}
|
||
|
for mode in MODES:
|
||
|
print(f" Running mode={mode}", file=sys.stderr)
|
||
|
fonts[mode] = TTFont(font_path)
|
||
|
before = time.perf_counter()
|
||
|
compact(fonts[mode], mode=str(mode))
|
||
|
runtimes[mode].append(time.perf_counter() - before)
|
||
|
font_paths[mode] = (
|
||
|
font_path.parent
|
||
|
/ "compact"
|
||
|
/ (font_path.stem + f"_{mode}" + font_path.suffix)
|
||
|
)
|
||
|
font_paths[mode].parent.mkdir(parents=True, exist_ok=True)
|
||
|
fonts[mode].save(font_paths[mode])
|
||
|
fonts[mode] = TTFont(font_paths[mode])
|
||
|
sizes[mode] = len(fonts[mode].getTableData("GPOS")) / 1024
|
||
|
|
||
|
print(f" Runtimes:", file=sys.stderr)
|
||
|
for mode, times in runtimes.items():
|
||
|
print(
|
||
|
f" {mode:10} {' '.join(f'{t:5.2f}' for t in times)}",
|
||
|
file=sys.stderr,
|
||
|
)
|
||
|
|
||
|
# Bonus: measure WOFF2 file sizes.
|
||
|
print(f" Measuring WOFF2 sizes", file=sys.stderr)
|
||
|
size_woff_orig = woff_size(font, font_path) / 1024
|
||
|
sizes_woff = {
|
||
|
mode: woff_size(fonts[mode], font_paths[mode]) / 1024 for mode in MODES
|
||
|
}
|
||
|
|
||
|
rows.append(
|
||
|
(
|
||
|
font_path.name,
|
||
|
size_orig,
|
||
|
size_woff_orig,
|
||
|
*flatten(
|
||
|
(
|
||
|
sizes[mode],
|
||
|
pct(sizes[mode], size_orig),
|
||
|
sizes_woff[mode],
|
||
|
pct(sizes_woff[mode], size_woff_orig),
|
||
|
)
|
||
|
for mode in MODES
|
||
|
),
|
||
|
)
|
||
|
)
|
||
|
|
||
|
write_csv(rows)
|
||
|
|
||
|
|
||
|
def woff_size(font: TTFont, path: Path) -> int:
|
||
|
font.flavor = "woff2"
|
||
|
woff_path = path.with_suffix(".woff2")
|
||
|
font.save(woff_path)
|
||
|
return woff_path.stat().st_size
|
||
|
|
||
|
|
||
|
def write_csv(rows: List[Tuple[Any]]) -> None:
|
||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||
|
sys.stdout.write("\uFEFF")
|
||
|
writer = csv.writer(sys.stdout, lineterminator="\n")
|
||
|
writer.writerow(
|
||
|
[
|
||
|
"File",
|
||
|
"Original GPOS Size",
|
||
|
"Original WOFF2 Size",
|
||
|
*flatten(
|
||
|
(
|
||
|
f"mode={mode}",
|
||
|
f"Change {mode}",
|
||
|
f"mode={mode} WOFF2 Size",
|
||
|
f"Change {mode} WOFF2 Size",
|
||
|
)
|
||
|
for mode in MODES
|
||
|
),
|
||
|
]
|
||
|
)
|
||
|
for row in rows:
|
||
|
writer.writerow(row)
|
||
|
|
||
|
|
||
|
def pct(new: float, old: float) -> float:
|
||
|
return -(1 - (new / old))
|
||
|
|
||
|
|
||
|
def flatten(seq_seq: Iterable[Iterable[Any]]) -> List[Any]:
|
||
|
return [thing for seq in seq_seq for thing in seq]
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|