remove venv
This commit is contained in:
parent
056387013d
commit
0680c7594e
13999 changed files with 0 additions and 2895688 deletions
|
|
@ -1,210 +0,0 @@
|
|||
# -*- 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue