2018-11-19 18:25:25 -08:00
|
|
|
from fontTools.misc import psCharStrings
|
|
|
|
from fontTools import ttLib
|
|
|
|
from fontTools.pens.basePen import NullPen
|
|
|
|
from fontTools.misc.fixedTools import otRound
|
2018-11-20 12:44:21 -08:00
|
|
|
from fontTools.varLib.varStore import VarStoreInstancer
|
2018-11-19 18:25:25 -08:00
|
|
|
|
|
|
|
def _add_method(*clazzes):
|
|
|
|
"""Returns a decorator function that adds a new method to one or
|
|
|
|
more classes."""
|
|
|
|
def wrapper(method):
|
|
|
|
done = []
|
|
|
|
for clazz in clazzes:
|
|
|
|
if clazz in done: continue # Support multiple names of a clazz
|
|
|
|
done.append(clazz)
|
|
|
|
assert clazz.__name__ != 'DefaultTable', \
|
|
|
|
'Oops, table class not found.'
|
|
|
|
assert not hasattr(clazz, method.__name__), \
|
|
|
|
"Oops, class '%s' has method '%s'." % (clazz.__name__,
|
|
|
|
method.__name__)
|
|
|
|
setattr(clazz, method.__name__, method)
|
|
|
|
return None
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
def _uniq_sort(l):
|
|
|
|
return sorted(set(l))
|
|
|
|
|
|
|
|
class _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler):
|
|
|
|
|
|
|
|
def __init__(self, components, localSubrs, globalSubrs):
|
|
|
|
psCharStrings.SimpleT2Decompiler.__init__(self,
|
|
|
|
localSubrs,
|
|
|
|
globalSubrs)
|
|
|
|
self.components = components
|
|
|
|
|
|
|
|
def op_endchar(self, index):
|
|
|
|
args = self.popall()
|
|
|
|
if len(args) >= 4:
|
|
|
|
from fontTools.encodings.StandardEncoding import StandardEncoding
|
|
|
|
# endchar can do seac accent bulding; The T2 spec says it's deprecated,
|
|
|
|
# but recent software that shall remain nameless does output it.
|
|
|
|
adx, ady, bchar, achar = args[-4:]
|
|
|
|
baseGlyph = StandardEncoding[bchar]
|
|
|
|
accentGlyph = StandardEncoding[achar]
|
|
|
|
self.components.add(baseGlyph)
|
|
|
|
self.components.add(accentGlyph)
|
|
|
|
|
|
|
|
@_add_method(ttLib.getTableClass('CFF '))
|
|
|
|
def closure_glyphs(self, s):
|
|
|
|
cff = self.cff
|
|
|
|
assert len(cff) == 1
|
|
|
|
font = cff[cff.keys()[0]]
|
|
|
|
glyphSet = font.CharStrings
|
|
|
|
|
|
|
|
decompose = s.glyphs
|
|
|
|
while decompose:
|
|
|
|
components = set()
|
|
|
|
for g in decompose:
|
|
|
|
if g not in glyphSet:
|
|
|
|
continue
|
|
|
|
gl = glyphSet[g]
|
|
|
|
|
|
|
|
subrs = getattr(gl.private, "Subrs", [])
|
|
|
|
decompiler = _ClosureGlyphsT2Decompiler(components, subrs, gl.globalSubrs)
|
|
|
|
decompiler.execute(gl)
|
|
|
|
components -= s.glyphs
|
|
|
|
s.glyphs.update(components)
|
|
|
|
decompose = components
|
|
|
|
|
2019-01-16 16:01:12 +00:00
|
|
|
def _empty_charstring(font, glyphName, isCFF2, ignoreWidth=False):
|
2019-01-15 15:10:04 +00:00
|
|
|
c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName)
|
2019-01-16 16:01:12 +00:00
|
|
|
if isCFF2 or ignoreWidth:
|
2019-01-16 13:31:35 +00:00
|
|
|
# CFF2 charstrings have no widths nor 'endchar' operators
|
|
|
|
c.decompile()
|
2019-01-16 16:01:12 +00:00
|
|
|
c.program = [] if isCFF2 else ['endchar']
|
2019-01-15 15:10:04 +00:00
|
|
|
else:
|
2019-01-16 16:01:12 +00:00
|
|
|
if hasattr(font, 'FDArray') and font.FDArray is not None:
|
|
|
|
private = font.FDArray[fdSelectIndex].Private
|
|
|
|
else:
|
|
|
|
private = font.Private
|
|
|
|
dfltWdX = private.defaultWidthX
|
|
|
|
nmnlWdX = private.nominalWidthX
|
2019-01-15 15:10:04 +00:00
|
|
|
pen = NullPen()
|
|
|
|
c.draw(pen) # this will set the charstring's width
|
2019-01-16 16:01:12 +00:00
|
|
|
if c.width != dfltWdX:
|
|
|
|
c.program = [c.width - nmnlWdX, 'endchar']
|
|
|
|
else:
|
|
|
|
c.program = ['endchar']
|
2019-01-15 15:10:04 +00:00
|
|
|
|
2018-11-19 18:25:25 -08:00
|
|
|
@_add_method(ttLib.getTableClass('CFF '))
|
|
|
|
def prune_pre_subset(self, font, options):
|
|
|
|
cff = self.cff
|
|
|
|
# CFF table must have one font only
|
|
|
|
cff.fontNames = cff.fontNames[:1]
|
|
|
|
|
|
|
|
if options.notdef_glyph and not options.notdef_outline:
|
2019-01-16 16:10:13 +00:00
|
|
|
isCFF2 = cff.major > 1
|
2018-11-19 18:25:25 -08:00
|
|
|
for fontname in cff.keys():
|
|
|
|
font = cff[fontname]
|
2019-01-16 16:10:13 +00:00
|
|
|
_empty_charstring(font, ".notdef", isCFF2=isCFF2)
|
2018-11-19 18:25:25 -08:00
|
|
|
|
|
|
|
# Clear useless Encoding
|
|
|
|
for fontname in cff.keys():
|
|
|
|
font = cff[fontname]
|
2019-03-06 16:01:28 +01:00
|
|
|
# https://github.com/fonttools/fonttools/issues/620
|
2018-11-19 18:25:25 -08:00
|
|
|
font.Encoding = "StandardEncoding"
|
|
|
|
|
|
|
|
return True # bool(cff.fontNames)
|
|
|
|
|
|
|
|
@_add_method(ttLib.getTableClass('CFF '))
|
|
|
|
def subset_glyphs(self, s):
|
|
|
|
cff = self.cff
|
|
|
|
for fontname in cff.keys():
|
|
|
|
font = cff[fontname]
|
|
|
|
cs = font.CharStrings
|
|
|
|
|
2019-05-15 13:33:10 -07:00
|
|
|
glyphs = s.glyphs.union(s.glyphs_emptied)
|
|
|
|
|
|
|
|
# Load all glyphs
|
|
|
|
for g in font.charset:
|
|
|
|
if g not in glyphs: continue
|
|
|
|
c, _ = cs.getItemAndSelector(g)
|
|
|
|
|
|
|
|
if cs.charStringsAreIndexed:
|
|
|
|
indices = [i for i,g in enumerate(font.charset) if g in glyphs]
|
|
|
|
csi = cs.charStringsIndex
|
|
|
|
csi.items = [csi.items[i] for i in indices]
|
|
|
|
del csi.file, csi.offsets
|
|
|
|
if hasattr(font, "FDSelect"):
|
|
|
|
sel = font.FDSelect
|
|
|
|
# XXX We want to set sel.format to None, such that the
|
|
|
|
# most compact format is selected. However, OTS was
|
|
|
|
# broken and couldn't parse a FDSelect format 0 that
|
|
|
|
# happened before CharStrings. As such, always force
|
|
|
|
# format 3 until we fix cffLib to always generate
|
|
|
|
# FDSelect after CharStrings.
|
|
|
|
# https://github.com/khaledhosny/ots/pull/31
|
|
|
|
#sel.format = None
|
|
|
|
sel.format = 3
|
|
|
|
sel.gidArray = [sel.gidArray[i] for i in indices]
|
|
|
|
cs.charStrings = {g:indices.index(v)
|
|
|
|
for g,v in cs.charStrings.items()
|
|
|
|
if g in glyphs}
|
|
|
|
else:
|
|
|
|
cs.charStrings = {g:v
|
|
|
|
for g,v in cs.charStrings.items()
|
|
|
|
if g in glyphs}
|
|
|
|
font.charset = [g for g in font.charset if g in glyphs]
|
|
|
|
font.numGlyphs = len(font.charset)
|
|
|
|
|
|
|
|
|
2019-01-15 14:07:54 +00:00
|
|
|
if s.options.retain_gids:
|
2019-01-16 16:10:13 +00:00
|
|
|
isCFF2 = cff.major > 1
|
2019-01-15 15:10:04 +00:00
|
|
|
for g in s.glyphs_emptied:
|
2019-01-16 16:10:13 +00:00
|
|
|
_empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True)
|
2019-05-15 13:33:10 -07:00
|
|
|
|
2018-11-19 18:25:25 -08:00
|
|
|
|
|
|
|
return True # any(cff[fontname].numGlyphs for fontname in cff.keys())
|
|
|
|
|
|
|
|
@_add_method(psCharStrings.T2CharString)
|
|
|
|
def subset_subroutines(self, subrs, gsubrs):
|
|
|
|
p = self.program
|
|
|
|
for i in range(1, len(p)):
|
|
|
|
if p[i] == 'callsubr':
|
|
|
|
assert isinstance(p[i-1], int)
|
|
|
|
p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
|
|
|
|
elif p[i] == 'callgsubr':
|
|
|
|
assert isinstance(p[i-1], int)
|
|
|
|
p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
|
|
|
|
|
|
|
|
@_add_method(psCharStrings.T2CharString)
|
|
|
|
def drop_hints(self):
|
|
|
|
hints = self._hints
|
|
|
|
|
|
|
|
if hints.deletions:
|
|
|
|
p = self.program
|
|
|
|
for idx in reversed(hints.deletions):
|
|
|
|
del p[idx-2:idx]
|
|
|
|
|
|
|
|
if hints.has_hint:
|
|
|
|
assert not hints.deletions or hints.last_hint <= hints.deletions[0]
|
|
|
|
self.program = self.program[hints.last_hint:]
|
|
|
|
if not self.program:
|
|
|
|
# TODO CFF2 no need for endchar.
|
|
|
|
self.program.append('endchar')
|
|
|
|
if hasattr(self, 'width'):
|
|
|
|
# Insert width back if needed
|
|
|
|
if self.width != self.private.defaultWidthX:
|
[varLib subset CFF2] Set PrivateDict nominal and default WidthX to None
@bedhad
Address issues raised in #1403
I do think setting the dummy CFF2 PrivateDict nominalWidthX and defaultWidthX to None, which leads to the charstring.width also being None, is a good idea. I originally set them to 0, which produces a charstring width of 0, in order to avoid problems with logic that assumes that the field is good for math. However, I now think that it is better to find errors around charstring type assumptions earlier than later.
"drop_hints()" is actually not wrong - I did look at this when making the changes. For CFF2 charstrings, self.width is always equal to self.private.defaultWidthX, so the width is never inserted. This is because in psCharstrings.py::T2WidthExtractor.popallWidth(), the test "evenOdd ^ (len(args) % 2)" is alway False. Left to myself, I would not change this code. If the CFF2 charstring is correct, there is not a problem. if the CFF2 charstring is not correct, then both in drop_hints() and in T2WidthExtractor.popallWidth(), the logic will stack dump. I did add asserts, but am not totally sure it is worth the extra calls.
2018-12-06 11:55:48 -08:00
|
|
|
# For CFF2 charstrings, this should never happen
|
2018-12-11 17:07:51 -08:00
|
|
|
assert self.private.defaultWidthX is not None, "CFF2 CharStrings must not have an initial width value"
|
2018-11-19 18:25:25 -08:00
|
|
|
self.program.insert(0, self.width - self.private.nominalWidthX)
|
|
|
|
|
|
|
|
if hints.has_hintmask:
|
|
|
|
i = 0
|
|
|
|
p = self.program
|
|
|
|
while i < len(p):
|
|
|
|
if p[i] in ['hintmask', 'cntrmask']:
|
|
|
|
assert i + 1 <= len(p)
|
|
|
|
del p[i:i+2]
|
|
|
|
continue
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
assert len(self.program)
|
|
|
|
|
|
|
|
del self._hints
|
|
|
|
|
|
|
|
class _MarkingT2Decompiler(psCharStrings.SimpleT2Decompiler):
|
|
|
|
|
2018-12-04 18:34:22 -08:00
|
|
|
def __init__(self, localSubrs, globalSubrs, private):
|
2018-11-19 18:25:25 -08:00
|
|
|
psCharStrings.SimpleT2Decompiler.__init__(self,
|
|
|
|
localSubrs,
|
2018-12-04 18:34:22 -08:00
|
|
|
globalSubrs,
|
|
|
|
private)
|
2018-11-19 18:25:25 -08:00
|
|
|
for subrs in [localSubrs, globalSubrs]:
|
|
|
|
if subrs and not hasattr(subrs, "_used"):
|
|
|
|
subrs._used = set()
|
|
|
|
|
|
|
|
def op_callsubr(self, index):
|
|
|
|
self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
|
|
|
|
psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
|
|
|
|
|
|
|
|
def op_callgsubr(self, index):
|
|
|
|
self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
|
|
|
|
psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
|
|
|
|
|
|
|
|
class _DehintingT2Decompiler(psCharStrings.T2WidthExtractor):
|
|
|
|
|
|
|
|
class Hints(object):
|
|
|
|
def __init__(self):
|
|
|
|
# Whether calling this charstring produces any hint stems
|
|
|
|
# Note that if a charstring starts with hintmask, it will
|
|
|
|
# have has_hint set to True, because it *might* produce an
|
|
|
|
# implicit vstem if called under certain conditions.
|
|
|
|
self.has_hint = False
|
|
|
|
# Index to start at to drop all hints
|
|
|
|
self.last_hint = 0
|
|
|
|
# Index up to which we know more hints are possible.
|
|
|
|
# Only relevant if status is 0 or 1.
|
|
|
|
self.last_checked = 0
|
|
|
|
# The status means:
|
|
|
|
# 0: after dropping hints, this charstring is empty
|
|
|
|
# 1: after dropping hints, there may be more hints
|
|
|
|
# continuing after this, or there might be
|
|
|
|
# other things. Not clear yet.
|
|
|
|
# 2: no more hints possible after this charstring
|
|
|
|
self.status = 0
|
|
|
|
# Has hintmask instructions; not recursive
|
|
|
|
self.has_hintmask = False
|
|
|
|
# List of indices of calls to empty subroutines to remove.
|
|
|
|
self.deletions = []
|
|
|
|
pass
|
|
|
|
|
2018-12-04 18:34:22 -08:00
|
|
|
def __init__(self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None):
|
2018-11-19 18:25:25 -08:00
|
|
|
self._css = css
|
|
|
|
psCharStrings.T2WidthExtractor.__init__(
|
|
|
|
self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX)
|
2018-12-04 18:34:22 -08:00
|
|
|
self.private = private
|
2018-11-19 18:25:25 -08:00
|
|
|
|
|
|
|
def execute(self, charString):
|
|
|
|
old_hints = charString._hints if hasattr(charString, '_hints') else None
|
|
|
|
charString._hints = self.Hints()
|
|
|
|
|
|
|
|
psCharStrings.T2WidthExtractor.execute(self, charString)
|
|
|
|
|
|
|
|
hints = charString._hints
|
|
|
|
|
|
|
|
if hints.has_hint or hints.has_hintmask:
|
|
|
|
self._css.add(charString)
|
|
|
|
|
|
|
|
if hints.status != 2:
|
|
|
|
# Check from last_check, make sure we didn't have any operators.
|
|
|
|
for i in range(hints.last_checked, len(charString.program) - 1):
|
|
|
|
if isinstance(charString.program[i], str):
|
|
|
|
hints.status = 2
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
hints.status = 1 # There's *something* here
|
|
|
|
hints.last_checked = len(charString.program)
|
|
|
|
|
|
|
|
if old_hints:
|
|
|
|
assert hints.__dict__ == old_hints.__dict__
|
|
|
|
|
|
|
|
def op_callsubr(self, index):
|
|
|
|
subr = self.localSubrs[self.operandStack[-1]+self.localBias]
|
|
|
|
psCharStrings.T2WidthExtractor.op_callsubr(self, index)
|
|
|
|
self.processSubr(index, subr)
|
|
|
|
|
|
|
|
def op_callgsubr(self, index):
|
|
|
|
subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
|
|
|
|
psCharStrings.T2WidthExtractor.op_callgsubr(self, index)
|
|
|
|
self.processSubr(index, subr)
|
|
|
|
|
|
|
|
def op_hstem(self, index):
|
|
|
|
psCharStrings.T2WidthExtractor.op_hstem(self, index)
|
|
|
|
self.processHint(index)
|
|
|
|
def op_vstem(self, index):
|
|
|
|
psCharStrings.T2WidthExtractor.op_vstem(self, index)
|
|
|
|
self.processHint(index)
|
|
|
|
def op_hstemhm(self, index):
|
|
|
|
psCharStrings.T2WidthExtractor.op_hstemhm(self, index)
|
|
|
|
self.processHint(index)
|
|
|
|
def op_vstemhm(self, index):
|
|
|
|
psCharStrings.T2WidthExtractor.op_vstemhm(self, index)
|
|
|
|
self.processHint(index)
|
|
|
|
def op_hintmask(self, index):
|
|
|
|
rv = psCharStrings.T2WidthExtractor.op_hintmask(self, index)
|
|
|
|
self.processHintmask(index)
|
|
|
|
return rv
|
|
|
|
def op_cntrmask(self, index):
|
|
|
|
rv = psCharStrings.T2WidthExtractor.op_cntrmask(self, index)
|
|
|
|
self.processHintmask(index)
|
|
|
|
return rv
|
|
|
|
|
|
|
|
def processHintmask(self, index):
|
|
|
|
cs = self.callingStack[-1]
|
|
|
|
hints = cs._hints
|
|
|
|
hints.has_hintmask = True
|
|
|
|
if hints.status != 2:
|
|
|
|
# Check from last_check, see if we may be an implicit vstem
|
|
|
|
for i in range(hints.last_checked, index - 1):
|
|
|
|
if isinstance(cs.program[i], str):
|
|
|
|
hints.status = 2
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
# We are an implicit vstem
|
|
|
|
hints.has_hint = True
|
|
|
|
hints.last_hint = index + 1
|
|
|
|
hints.status = 0
|
|
|
|
hints.last_checked = index + 1
|
|
|
|
|
|
|
|
def processHint(self, index):
|
|
|
|
cs = self.callingStack[-1]
|
|
|
|
hints = cs._hints
|
|
|
|
hints.has_hint = True
|
|
|
|
hints.last_hint = index
|
|
|
|
hints.last_checked = index
|
|
|
|
|
|
|
|
def processSubr(self, index, subr):
|
|
|
|
cs = self.callingStack[-1]
|
|
|
|
hints = cs._hints
|
|
|
|
subr_hints = subr._hints
|
|
|
|
|
|
|
|
# Check from last_check, make sure we didn't have
|
|
|
|
# any operators.
|
|
|
|
if hints.status != 2:
|
|
|
|
for i in range(hints.last_checked, index - 1):
|
|
|
|
if isinstance(cs.program[i], str):
|
|
|
|
hints.status = 2
|
|
|
|
break
|
|
|
|
hints.last_checked = index
|
|
|
|
|
|
|
|
if hints.status != 2:
|
|
|
|
if subr_hints.has_hint:
|
|
|
|
hints.has_hint = True
|
|
|
|
|
|
|
|
# Decide where to chop off from
|
|
|
|
if subr_hints.status == 0:
|
|
|
|
hints.last_hint = index
|
|
|
|
else:
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
hints.last_hint = index - 2 # Leave the subr call in
|
2018-11-19 18:25:25 -08:00
|
|
|
|
|
|
|
elif subr_hints.status == 0:
|
|
|
|
hints.deletions.append(index)
|
|
|
|
|
|
|
|
hints.status = max(hints.status, subr_hints.status)
|
|
|
|
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
class StopHintCountEvent(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-11-19 18:25:25 -08:00
|
|
|
class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
|
2019-04-30 09:43:13 -07:00
|
|
|
stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto",
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
"op_vmoveto")
|
2018-11-19 18:25:25 -08:00
|
|
|
|
|
|
|
def __init__(self, localSubrs, globalSubrs, private=None):
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs,
|
|
|
|
private)
|
2018-11-19 18:25:25 -08:00
|
|
|
|
|
|
|
def execute(self, charString):
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
self.need_hintcount = True # until proven otherwise
|
|
|
|
for op_name in self.stop_hintcount_ops:
|
|
|
|
setattr(self, op_name, self.stop_hint_count)
|
|
|
|
|
2018-11-30 00:13:38 -05:00
|
|
|
if hasattr(charString, '_desubroutinized'):
|
2019-04-30 09:43:13 -07:00
|
|
|
# If a charstring has already been desubroutinized, we will still
|
|
|
|
# need to execute it if we need to count hints in order to
|
|
|
|
# compute the byte length for mask arguments, and haven't finished
|
|
|
|
# counting hints pairs.
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
if self.need_hintcount and self.callingStack:
|
|
|
|
try:
|
|
|
|
psCharStrings.SimpleT2Decompiler.execute(self, charString)
|
|
|
|
except StopHintCountEvent:
|
|
|
|
del self.callingStack[-1]
|
2018-11-19 18:25:25 -08:00
|
|
|
return
|
|
|
|
|
|
|
|
charString._patches = []
|
|
|
|
psCharStrings.SimpleT2Decompiler.execute(self, charString)
|
|
|
|
desubroutinized = charString.program[:]
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
for idx, expansion in reversed(charString._patches):
|
2018-11-19 18:25:25 -08:00
|
|
|
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
|
2018-12-04 18:34:22 -08:00
|
|
|
if not self.private.in_cff2:
|
2018-11-19 18:25:25 -08:00
|
|
|
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
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
def stop_hint_count(self, *args):
|
|
|
|
self.need_hintcount = False
|
|
|
|
for op_name in self.stop_hintcount_ops:
|
|
|
|
setattr(self, op_name, None)
|
|
|
|
cs = self.callingStack[-1]
|
|
|
|
if hasattr(cs, '_desubroutinized'):
|
|
|
|
raise StopHintCountEvent()
|
|
|
|
|
|
|
|
def op_hintmask(self, index):
|
|
|
|
psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
|
|
|
|
if self.need_hintcount:
|
|
|
|
self.stop_hint_count()
|
|
|
|
|
2018-11-19 18:25:25 -08:00
|
|
|
def processSubr(self, index, subr):
|
|
|
|
cs = self.callingStack[-1]
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
if not hasattr(cs, '_desubroutinized'):
|
|
|
|
cs._patches.append((index, subr._desubroutinized))
|
2018-11-19 18:25:25 -08:00
|
|
|
|
|
|
|
|
|
|
|
@_add_method(ttLib.getTableClass('CFF '))
|
2018-12-04 18:34:22 -08:00
|
|
|
def prune_post_subset(self, ttfFont, options):
|
2018-11-19 18:25:25 -08:00
|
|
|
cff = self.cff
|
|
|
|
for fontname in cff.keys():
|
|
|
|
font = cff[fontname]
|
|
|
|
cs = font.CharStrings
|
|
|
|
|
|
|
|
# Drop unused FontDictionaries
|
|
|
|
if hasattr(font, "FDSelect"):
|
|
|
|
sel = font.FDSelect
|
|
|
|
indices = _uniq_sort(sel.gidArray)
|
|
|
|
sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
|
|
|
|
arr = font.FDArray
|
|
|
|
arr.items = [arr[i] for i in indices]
|
|
|
|
del arr.file, arr.offsets
|
|
|
|
|
2018-12-04 18:34:22 -08:00
|
|
|
# Desubroutinize if asked for
|
|
|
|
if options.desubroutinize:
|
|
|
|
self.desubroutinize()
|
|
|
|
|
|
|
|
# Drop hints if not needed
|
|
|
|
if not options.hinting:
|
|
|
|
self.remove_hints()
|
2019-02-07 01:14:57 +01:00
|
|
|
elif not options.desubroutinize:
|
|
|
|
self.remove_unused_subroutines()
|
2018-12-04 18:34:22 -08:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def _delete_empty_subrs(private_dict):
|
|
|
|
if hasattr(private_dict, 'Subrs') and not private_dict.Subrs:
|
|
|
|
if 'Subrs' in private_dict.rawDict:
|
|
|
|
del private_dict.rawDict['Subrs']
|
|
|
|
del private_dict.Subrs
|
|
|
|
|
|
|
|
@_add_method(ttLib.getTableClass('CFF '))
|
|
|
|
def desubroutinize(self):
|
|
|
|
cff = self.cff
|
|
|
|
for fontname in cff.keys():
|
|
|
|
font = cff[fontname]
|
|
|
|
cs = font.CharStrings
|
|
|
|
for g in font.charset:
|
|
|
|
c, _ = cs.getItemAndSelector(g)
|
|
|
|
c.decompile()
|
|
|
|
subrs = getattr(c.private, "Subrs", [])
|
|
|
|
decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private)
|
|
|
|
decompiler.execute(c)
|
|
|
|
c.program = c._desubroutinized
|
|
|
|
del c._desubroutinized
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
# Delete all the local subrs
|
2018-12-04 18:34:22 -08:00
|
|
|
if hasattr(font, 'FDArray'):
|
|
|
|
for fd in font.FDArray:
|
|
|
|
pd = fd.Private
|
|
|
|
if hasattr(pd, 'Subrs'):
|
|
|
|
del pd.Subrs
|
|
|
|
if 'Subrs' in pd.rawDict:
|
|
|
|
del pd.rawDict['Subrs']
|
2019-02-07 01:14:57 +01:00
|
|
|
else:
|
|
|
|
pd = font.Private
|
|
|
|
if hasattr(pd, 'Subrs'):
|
|
|
|
del pd.Subrs
|
|
|
|
if 'Subrs' in pd.rawDict:
|
|
|
|
del pd.rawDict['Subrs']
|
[subset CFF] Fix de-subroutinizing bug when subroutines contain hints (#1499)
* [subset CFF] Fix bug in de-subroutinizing when subroutines contain hints, issue 1493
The code was skipping executing a subroutine if it had already been desubroutinized. The initial set of vstemhm and hstemhm operators and values may be in a subroutine. If a charstring is being executed which calls such subroutines, they still need to be executed in order to count the number of hint values seen, so that the byte length of the hintmask can be calculated.
I fixed this bug by executing subroutines even if they have already been desubroutinized, as long as (we don't know yet if we are doing hintmasks) or ( we do need a hintmask, but have not yet seen it).
Clean up code per Cosimo's suggestions:
In arg list for stop_hint_count(), use *args to accept unused argument, rather than a dummy positional argument.
Change stop_hintcount_ops to a from a global variable to a class variable in _DesubroutinizingT2Decompiler.
Remove un-needed 'return' at line 387
Remove duplicate assignment of cs at line 437
Add patch for the bug where AttributeError is encountered when remove_hints is run after desubroutinize: remove lines deleting the GlobalSubrs for each FontDict. This always needed to be done only once, and is now in any case done in cff.GlobalSubrs.clear(), at the end of the desubroutinize() function.
Changed test case subset_test.py::'test_no_hinting_desubroutinize_CFF' to reference a font with a non-empty GlobalSubr, in order to trigger AttributeError traceback.
2019-02-18 01:43:27 -08:00
|
|
|
# as well as the global subrs
|
2019-02-07 01:58:22 +01:00
|
|
|
cff.GlobalSubrs.clear()
|
2018-12-04 18:34:22 -08:00
|
|
|
|
|
|
|
|
|
|
|
@_add_method(ttLib.getTableClass('CFF '))
|
|
|
|
def remove_hints(self):
|
|
|
|
cff = self.cff
|
|
|
|
for fontname in cff.keys():
|
|
|
|
font = cff[fontname]
|
|
|
|
cs = font.CharStrings
|
|
|
|
# This can be tricky, but doesn't have to. What we do is:
|
|
|
|
#
|
|
|
|
# - Run all used glyph charstrings and recurse into subroutines,
|
|
|
|
# - For each charstring (including subroutines), if it has any
|
|
|
|
# of the hint stem operators, we mark it as such.
|
|
|
|
# Upon returning, for each charstring we note all the
|
|
|
|
# subroutine calls it makes that (recursively) contain a stem,
|
|
|
|
# - Dropping hinting then consists of the following two ops:
|
|
|
|
# * Drop the piece of the program in each charstring before the
|
|
|
|
# last call to a stem op or a stem-calling subroutine,
|
|
|
|
# * Drop all hintmask operations.
|
|
|
|
# - It's trickier... A hintmask right after hints and a few numbers
|
|
|
|
# will act as an implicit vstemhm. As such, we track whether
|
|
|
|
# we have seen any non-hint operators so far and do the right
|
|
|
|
# thing, recursively... Good luck understanding that :(
|
|
|
|
css = set()
|
|
|
|
for g in font.charset:
|
|
|
|
c, _ = cs.getItemAndSelector(g)
|
|
|
|
c.decompile()
|
|
|
|
subrs = getattr(c.private, "Subrs", [])
|
|
|
|
decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs,
|
|
|
|
c.private.nominalWidthX,
|
|
|
|
c.private.defaultWidthX,
|
|
|
|
c.private)
|
|
|
|
decompiler.execute(c)
|
|
|
|
c.width = decompiler.width
|
|
|
|
for charstring in css:
|
|
|
|
charstring.drop_hints()
|
|
|
|
del css
|
2019-02-07 01:14:57 +01:00
|
|
|
|
2018-12-04 18:34:22 -08:00
|
|
|
# Drop font-wide hinting values
|
|
|
|
all_privs = []
|
|
|
|
if hasattr(font, 'FDArray'):
|
|
|
|
all_privs.extend(fd.Private for fd in font.FDArray)
|
|
|
|
else:
|
|
|
|
all_privs.append(font.Private)
|
|
|
|
for priv in all_privs:
|
|
|
|
for k in ['BlueValues', 'OtherBlues',
|
|
|
|
'FamilyBlues', 'FamilyOtherBlues',
|
|
|
|
'BlueScale', 'BlueShift', 'BlueFuzz',
|
|
|
|
'StemSnapH', 'StemSnapV', 'StdHW', 'StdVW',
|
|
|
|
'ForceBold', 'LanguageGroup', 'ExpansionFactor']:
|
|
|
|
if hasattr(priv, k):
|
|
|
|
setattr(priv, k, None)
|
|
|
|
self.remove_unused_subroutines()
|
|
|
|
|
2018-11-19 18:25:25 -08:00
|
|
|
|
2018-12-04 18:34:22 -08:00
|
|
|
@_add_method(ttLib.getTableClass('CFF '))
|
|
|
|
def remove_unused_subroutines(self):
|
|
|
|
cff = self.cff
|
|
|
|
for fontname in cff.keys():
|
|
|
|
font = cff[fontname]
|
|
|
|
cs = font.CharStrings
|
2018-11-19 18:25:25 -08:00
|
|
|
# Renumber subroutines to remove unused ones
|
|
|
|
|
|
|
|
# Mark all used subroutines
|
|
|
|
for g in font.charset:
|
|
|
|
c, _ = cs.getItemAndSelector(g)
|
|
|
|
subrs = getattr(c.private, "Subrs", [])
|
2018-12-04 18:34:22 -08:00
|
|
|
decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private)
|
2018-11-19 18:25:25 -08:00
|
|
|
decompiler.execute(c)
|
|
|
|
|
|
|
|
all_subrs = [font.GlobalSubrs]
|
2018-12-04 18:34:22 -08:00
|
|
|
if hasattr(font, 'FDArray'):
|
2018-11-19 18:25:25 -08:00
|
|
|
all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs') and fd.Private.Subrs)
|
|
|
|
elif hasattr(font.Private, 'Subrs') and font.Private.Subrs:
|
|
|
|
all_subrs.append(font.Private.Subrs)
|
|
|
|
|
|
|
|
subrs = set(subrs) # Remove duplicates
|
|
|
|
|
|
|
|
# Prepare
|
|
|
|
for subrs in all_subrs:
|
|
|
|
if not hasattr(subrs, '_used'):
|
|
|
|
subrs._used = set()
|
|
|
|
subrs._used = _uniq_sort(subrs._used)
|
|
|
|
subrs._old_bias = psCharStrings.calcSubrBias(subrs)
|
|
|
|
subrs._new_bias = psCharStrings.calcSubrBias(subrs._used)
|
|
|
|
|
|
|
|
# Renumber glyph charstrings
|
|
|
|
for g in font.charset:
|
|
|
|
c, _ = cs.getItemAndSelector(g)
|
|
|
|
subrs = getattr(c.private, "Subrs", [])
|
|
|
|
c.subset_subroutines (subrs, font.GlobalSubrs)
|
|
|
|
|
|
|
|
# Renumber subroutines themselves
|
|
|
|
for subrs in all_subrs:
|
|
|
|
if subrs == font.GlobalSubrs:
|
2018-12-04 18:34:22 -08:00
|
|
|
if not hasattr(font, 'FDArray') and hasattr(font.Private, 'Subrs'):
|
2018-11-19 18:25:25 -08:00
|
|
|
local_subrs = font.Private.Subrs
|
|
|
|
else:
|
|
|
|
local_subrs = []
|
|
|
|
else:
|
|
|
|
local_subrs = subrs
|
|
|
|
|
|
|
|
subrs.items = [subrs.items[i] for i in subrs._used]
|
|
|
|
if hasattr(subrs, 'file'):
|
|
|
|
del subrs.file
|
|
|
|
if hasattr(subrs, 'offsets'):
|
|
|
|
del subrs.offsets
|
|
|
|
|
|
|
|
for subr in subrs.items:
|
|
|
|
subr.subset_subroutines (local_subrs, font.GlobalSubrs)
|
|
|
|
|
|
|
|
# Delete local SubrsIndex if empty
|
2018-12-04 18:34:22 -08:00
|
|
|
if hasattr(font, 'FDArray'):
|
2018-11-19 18:25:25 -08:00
|
|
|
for fd in font.FDArray:
|
|
|
|
_delete_empty_subrs(fd.Private)
|
|
|
|
else:
|
|
|
|
_delete_empty_subrs(font.Private)
|
|
|
|
|
|
|
|
# Cleanup
|
|
|
|
for subrs in all_subrs:
|
|
|
|
del subrs._used, subrs._old_bias, subrs._new_bias
|