[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:
parent
825ef55a95
commit
de66a6466c
@ -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):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user