ajout fichiers manquant
This commit is contained in:
183
venv/Lib/site-packages/fontTools/cffLib/width.py
Normal file
183
venv/Lib/site-packages/fontTools/cffLib/width.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
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-1131]
|
||||
for start in starts:
|
||||
while cumMaxU[start] and cumMaxU[start] == cumMaxU[start-1]:
|
||||
start -= 1
|
||||
ends.append(start)
|
||||
else:
|
||||
starts = [nominal, nominal+108, nominal+1131]
|
||||
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()
|
Reference in New Issue
Block a user