[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
|
might make the font unusable as a webfont as they will be rejected by
|
||||||
OpenType Sanitizer used in common browsers. For more information see:
|
OpenType Sanitizer used in common browsers. For more information see:
|
||||||
https://github.com/behdad/fonttools/issues/144
|
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:
|
Font table options:
|
||||||
--drop-tables[+|-]=<table>[,<table>...]
|
--drop-tables[+|-]=<table>[,<table>...]
|
||||||
@ -1866,6 +1875,60 @@ class _DehintingT2Decompiler(psCharStrings.SimpleT2Decompiler):
|
|||||||
else:
|
else:
|
||||||
hints.last_hint = index - 2 # Leave the subr call in
|
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 '))
|
@_add_method(ttLib.getTableClass('CFF '))
|
||||||
def prune_post_subset(self, options):
|
def prune_post_subset(self, options):
|
||||||
cff = self.cff
|
cff = self.cff
|
||||||
@ -1886,6 +1949,18 @@ def prune_post_subset(self, options):
|
|||||||
del arr.file, arr.offsets
|
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
|
# Drop hints if not needed
|
||||||
#
|
#
|
||||||
@ -1911,14 +1986,13 @@ def prune_post_subset(self, options):
|
|||||||
css = set()
|
css = set()
|
||||||
for g in font.charset:
|
for g in font.charset:
|
||||||
c,sel = cs.getItemAndSelector(g)
|
c,sel = cs.getItemAndSelector(g)
|
||||||
# Make sure it's decompiled. We want our "decompiler" to walk
|
c.decompile()
|
||||||
# the program, not the bytecode.
|
|
||||||
c.draw(basePen.NullPen())
|
|
||||||
subrs = getattr(c.private, "Subrs", [])
|
subrs = getattr(c.private, "Subrs", [])
|
||||||
decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs)
|
decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs)
|
||||||
decompiler.execute(c)
|
decompiler.execute(c)
|
||||||
for charstring in css:
|
for charstring in css:
|
||||||
charstring.drop_hints()
|
charstring.drop_hints()
|
||||||
|
del css
|
||||||
|
|
||||||
# Drop font-wide hinting values
|
# Drop font-wide hinting values
|
||||||
all_privs = []
|
all_privs = []
|
||||||
@ -1933,7 +2007,6 @@ def prune_post_subset(self, options):
|
|||||||
if hasattr(priv, k):
|
if hasattr(priv, k):
|
||||||
setattr(priv, k, None)
|
setattr(priv, k, None)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Renumber subroutines to remove unused ones
|
# Renumber subroutines to remove unused ones
|
||||||
#
|
#
|
||||||
@ -2170,6 +2243,7 @@ class Options(object):
|
|||||||
recalc_timestamp = False # Recalculate font modified timestamp
|
recalc_timestamp = False # Recalculate font modified timestamp
|
||||||
canonical_order = False # Order tables as recommended
|
canonical_order = False # Order tables as recommended
|
||||||
flavor = None # May be 'woff'
|
flavor = None # May be 'woff'
|
||||||
|
desubroutinize = False # Desubroutinize CFF CharStrings
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user