[subset] Add --desubroutinize

This seems to both make the font smaller for small subsets, and works
around a bug in our CFF hint stripping logic.

So, if you are passing --no-hinting, do pass --desubroutinize.
This commit is contained in:
Sam Fishman 2014-06-20 15:30:26 -07:00 committed by Behdad Esfahbod
parent 825ef55a95
commit de66a6466c

View File

@ -171,6 +171,15 @@ Hinting options:
might make the font unusable as a webfont as they will be rejected by
OpenType Sanitizer used in common browsers. For more information see:
https://github.com/behdad/fonttools/issues/144
The --desubroutinize options works around that bug.
Optimization options:
--desubroutinize
Remove CFF use of subroutinizes. Subroutinization is a way to make CFF
fonts smaller. For small subsets however, desubroutinizing might make
the font smaller. Also see note under --no-hinting.
--no-desubroutinize [default]
Leave CFF subroutinizes as is, only throw away unused subroutinizes.
Font table options:
--drop-tables[+|-]=<table>[,<table>...]
@ -1866,6 +1875,60 @@ class _DehintingT2Decompiler(psCharStrings.SimpleT2Decompiler):
else:
hints.last_hint = index - 2 # Leave the subr call in
class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
def __init__(self, localSubrs, globalSubrs):
psCharStrings.SimpleT2Decompiler.__init__(self,
localSubrs,
globalSubrs)
def execute(self, charString):
# Note: Currently we recompute _desubroutinized each time. This
# is more robust in some cases, but in other places we assume
# that each subroutine always expands to the same code, so
# maybe it doesn't matter. To speed up we can just not
# recompute _desubroutinized if it's there. For now I just
# double-check that it desubroutinized to the same thing.
old_desubroutinized = charString._desubroutinized if hasattr(charString, '_desubroutinized') else None
charString._patches = []
psCharStrings.SimpleT2Decompiler.execute(self, charString)
desubroutinized = charString.program[:]
for idx,expansion in reversed (charString._patches):
assert idx >= 2
assert desubroutinized[idx - 1] in ['callsubr', 'callgsubr'], desubroutinized[idx - 1]
assert type(desubroutinized[idx - 2]) == int
if expansion[-1] == 'return':
expansion = expansion[:-1]
desubroutinized[idx-2:idx] = expansion
if 'endchar' in desubroutinized:
# Cut off after first endchar
desubroutinized = desubroutinized[:desubroutinized.index('endchar') + 1]
else:
if not len(desubroutinized) or desubroutinized[-1] != 'return':
desubroutinized.append('return')
charString._desubroutinized = desubroutinized
del charString._patches
if old_desubroutinized:
assert desubroutinized == old_desubroutinized
def op_callsubr(self, index):
subr = self.localSubrs[self.operandStack[-1]+self.localBias]
psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
self.processSubr(index, subr)
def op_callgsubr(self, index):
subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
self.processSubr(index, subr)
def processSubr(self, index, subr):
cs = self.callingStack[-1]
cs._patches.append((index, subr._desubroutinized))
@_add_method(ttLib.getTableClass('CFF '))
def prune_post_subset(self, options):
cff = self.cff
@ -1886,6 +1949,18 @@ def prune_post_subset(self, options):
del arr.file, arr.offsets
#
# Desubroutinize if asked for
if options.desubroutinize:
for g in font.charset:
c,sel = cs.getItemAndSelector(g)
c.decompile()
subrs = getattr(c.private, "Subrs", [])
decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs)
decompiler.execute(c)
c.program = c._desubroutinized
#
# Drop hints if not needed
#
@ -1911,14 +1986,13 @@ def prune_post_subset(self, options):
css = set()
for g in font.charset:
c,sel = cs.getItemAndSelector(g)
# Make sure it's decompiled. We want our "decompiler" to walk
# the program, not the bytecode.
c.draw(basePen.NullPen())
c.decompile()
subrs = getattr(c.private, "Subrs", [])
decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs)
decompiler.execute(c)
for charstring in css:
charstring.drop_hints()
del css
# Drop font-wide hinting values
all_privs = []
@ -1933,7 +2007,6 @@ def prune_post_subset(self, options):
if hasattr(priv, k):
setattr(priv, k, None)
#
# Renumber subroutines to remove unused ones
#
@ -2170,6 +2243,7 @@ class Options(object):
recalc_timestamp = False # Recalculate font modified timestamp
canonical_order = False # Order tables as recommended
flavor = None # May be 'woff'
desubroutinize = False # Desubroutinize CFF CharStrings
def __init__(self, **kwargs):