Spaces:
Running
Running
# -*- coding: utf-8 -*- | |
"""T2CharString glyph width optimizer. | |
CFF glyphs whose width equals the CFF Private dictionary's ``defaultWidthX`` | |
value do not need to specify their width in their charstring, saving bytes. | |
This module determines the optimum ``defaultWidthX`` and ``nominalWidthX`` | |
values for a font, when provided with a list of glyph widths.""" | |
from fontTools.ttLib import TTFont | |
from collections import defaultdict | |
from operator import add | |
from functools import reduce | |
__all__ = ["optimizeWidths", "main"] | |
class missingdict(dict): | |
def __init__(self, missing_func): | |
self.missing_func = missing_func | |
def __missing__(self, v): | |
return self.missing_func(v) | |
def cumSum(f, op=add, start=0, decreasing=False): | |
keys = sorted(f.keys()) | |
minx, maxx = keys[0], keys[-1] | |
total = reduce(op, f.values(), start) | |
if decreasing: | |
missing = lambda x: start if x > maxx else total | |
domain = range(maxx, minx - 1, -1) | |
else: | |
missing = lambda x: start if x < minx else total | |
domain = range(minx, maxx + 1) | |
out = missingdict(missing) | |
v = start | |
for x in domain: | |
v = op(v, f[x]) | |
out[x] = v | |
return out | |
def byteCost(widths, default, nominal): | |
if not hasattr(widths, "items"): | |
d = defaultdict(int) | |
for w in widths: | |
d[w] += 1 | |
widths = d | |
cost = 0 | |
for w, freq in widths.items(): | |
if w == default: | |
continue | |
diff = abs(w - nominal) | |
if diff <= 107: | |
cost += freq | |
elif diff <= 1131: | |
cost += freq * 2 | |
else: | |
cost += freq * 5 | |
return cost | |
def optimizeWidthsBruteforce(widths): | |
"""Bruteforce version. Veeeeeeeeeeeeeeeeery slow. Only works for smallests of fonts.""" | |
d = defaultdict(int) | |
for w in widths: | |
d[w] += 1 | |
# Maximum number of bytes using default can possibly save | |
maxDefaultAdvantage = 5 * max(d.values()) | |
minw, maxw = min(widths), max(widths) | |
domain = list(range(minw, maxw + 1)) | |
bestCostWithoutDefault = min(byteCost(widths, None, nominal) for nominal in domain) | |
bestCost = len(widths) * 5 + 1 | |
for nominal in domain: | |
if byteCost(widths, None, nominal) > bestCost + maxDefaultAdvantage: | |
continue | |
for default in domain: | |
cost = byteCost(widths, default, nominal) | |
if cost < bestCost: | |
bestCost = cost | |
bestDefault = default | |
bestNominal = nominal | |
return bestDefault, bestNominal | |
def optimizeWidths(widths): | |
"""Given a list of glyph widths, or dictionary mapping glyph width to number of | |
glyphs having that, returns a tuple of best CFF default and nominal glyph widths. | |
This algorithm is linear in UPEM+numGlyphs.""" | |
if not hasattr(widths, "items"): | |
d = defaultdict(int) | |
for w in widths: | |
d[w] += 1 | |
widths = d | |
keys = sorted(widths.keys()) | |
minw, maxw = keys[0], keys[-1] | |
domain = list(range(minw, maxw + 1)) | |
# Cumulative sum/max forward/backward. | |
cumFrqU = cumSum(widths, op=add) | |
cumMaxU = cumSum(widths, op=max) | |
cumFrqD = cumSum(widths, op=add, decreasing=True) | |
cumMaxD = cumSum(widths, op=max, decreasing=True) | |
# Cost per nominal choice, without default consideration. | |
nomnCostU = missingdict( | |
lambda x: cumFrqU[x] + cumFrqU[x - 108] + cumFrqU[x - 1132] * 3 | |
) | |
nomnCostD = missingdict( | |
lambda x: cumFrqD[x] + cumFrqD[x + 108] + cumFrqD[x + 1132] * 3 | |
) | |
nomnCost = missingdict(lambda x: nomnCostU[x] + nomnCostD[x] - widths[x]) | |
# Cost-saving per nominal choice, by best default choice. | |
dfltCostU = missingdict( | |
lambda x: max(cumMaxU[x], cumMaxU[x - 108] * 2, cumMaxU[x - 1132] * 5) | |
) | |
dfltCostD = missingdict( | |
lambda x: max(cumMaxD[x], cumMaxD[x + 108] * 2, cumMaxD[x + 1132] * 5) | |
) | |
dfltCost = missingdict(lambda x: max(dfltCostU[x], dfltCostD[x])) | |
# Combined cost per nominal choice. | |
bestCost = missingdict(lambda x: nomnCost[x] - dfltCost[x]) | |
# Best nominal. | |
nominal = min(domain, key=lambda x: bestCost[x]) | |
# Work back the best default. | |
bestC = bestCost[nominal] | |
dfltC = nomnCost[nominal] - bestCost[nominal] | |
ends = [] | |
if dfltC == dfltCostU[nominal]: | |
starts = [nominal, nominal - 108, nominal - 1132] | |
for start in starts: | |
while cumMaxU[start] and cumMaxU[start] == cumMaxU[start - 1]: | |
start -= 1 | |
ends.append(start) | |
else: | |
starts = [nominal, nominal + 108, nominal + 1132] | |
for start in starts: | |
while cumMaxD[start] and cumMaxD[start] == cumMaxD[start + 1]: | |
start += 1 | |
ends.append(start) | |
default = min(ends, key=lambda default: byteCost(widths, default, nominal)) | |
return default, nominal | |
def main(args=None): | |
"""Calculate optimum defaultWidthX/nominalWidthX values""" | |
import argparse | |
parser = argparse.ArgumentParser( | |
"fonttools cffLib.width", | |
description=main.__doc__, | |
) | |
parser.add_argument( | |
"inputs", metavar="FILE", type=str, nargs="+", help="Input TTF files" | |
) | |
parser.add_argument( | |
"-b", | |
"--brute-force", | |
dest="brute", | |
action="store_true", | |
help="Use brute-force approach (VERY slow)", | |
) | |
args = parser.parse_args(args) | |
for fontfile in args.inputs: | |
font = TTFont(fontfile) | |
hmtx = font["hmtx"] | |
widths = [m[0] for m in hmtx.metrics.values()] | |
if args.brute: | |
default, nominal = optimizeWidthsBruteforce(widths) | |
else: | |
default, nominal = optimizeWidths(widths) | |
print( | |
"glyphs=%d default=%d nominal=%d byteCost=%d" | |
% (len(widths), default, nominal, byteCost(widths, default, nominal)) | |
) | |
if __name__ == "__main__": | |
import sys | |
if len(sys.argv) == 1: | |
import doctest | |
sys.exit(doctest.testmod().failed) | |
main() | |