aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/subset/cff.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/subset/cff.py')
-rw-r--r--Lib/fontTools/subset/cff.py952
1 files changed, 493 insertions, 459 deletions
diff --git a/Lib/fontTools/subset/cff.py b/Lib/fontTools/subset/cff.py
index d6872f39..dd79f6db 100644
--- a/Lib/fontTools/subset/cff.py
+++ b/Lib/fontTools/subset/cff.py
@@ -7,496 +7,530 @@ from fontTools.subset.util import _add_method, _uniq_sort
class _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler):
+ def __init__(self, components, localSubrs, globalSubrs):
+ psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs)
+ self.components = components
- 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 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
+ 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
+
def _empty_charstring(font, glyphName, isCFF2, ignoreWidth=False):
- c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName)
- if isCFF2 or ignoreWidth:
- # CFF2 charstrings have no widths nor 'endchar' operators
- c.setProgram([] if isCFF2 else ['endchar'])
- else:
- 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
- pen = NullPen()
- c.draw(pen) # this will set the charstring's width
- if c.width != dfltWdX:
- c.program = [c.width - nmnlWdX, 'endchar']
- else:
- c.program = ['endchar']
-
-@_add_method(ttLib.getTableClass('CFF '))
+ c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName)
+ if isCFF2 or ignoreWidth:
+ # CFF2 charstrings have no widths nor 'endchar' operators
+ c.setProgram([] if isCFF2 else ["endchar"])
+ else:
+ 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
+ pen = NullPen()
+ c.draw(pen) # this will set the charstring's width
+ if c.width != dfltWdX:
+ c.program = [c.width - nmnlWdX, "endchar"]
+ else:
+ c.program = ["endchar"]
+
+
+@_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]
+ cff = self.cff
+ # CFF table must have one font only
+ cff.fontNames = cff.fontNames[:1]
+
+ if options.notdef_glyph and not options.notdef_outline:
+ isCFF2 = cff.major > 1
+ for fontname in cff.keys():
+ font = cff[fontname]
+ _empty_charstring(font, ".notdef", isCFF2=isCFF2)
- if options.notdef_glyph and not options.notdef_outline:
- isCFF2 = cff.major > 1
- for fontname in cff.keys():
- font = cff[fontname]
- _empty_charstring(font, ".notdef", isCFF2=isCFF2)
+ # Clear useless Encoding
+ for fontname in cff.keys():
+ font = cff[fontname]
+ # https://github.com/fonttools/fonttools/issues/620
+ font.Encoding = "StandardEncoding"
- # Clear useless Encoding
- for fontname in cff.keys():
- font = cff[fontname]
- # https://github.com/fonttools/fonttools/issues/620
- font.Encoding = "StandardEncoding"
+ return True # bool(cff.fontNames)
- return True # bool(cff.fontNames)
-@_add_method(ttLib.getTableClass('CFF '))
+@_add_method(ttLib.getTableClass("CFF "))
def subset_glyphs(self, s):
- cff = self.cff
- for fontname in cff.keys():
- font = cff[fontname]
- cs = font.CharStrings
-
- 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
- sel.format = None
- sel.gidArray = [sel.gidArray[i] for i in indices]
- newCharStrings = {}
- for indicesIdx, charsetIdx in enumerate(indices):
- g = font.charset[charsetIdx]
- if g in cs.charStrings:
- newCharStrings[g] = indicesIdx
- cs.charStrings = newCharStrings
- 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)
-
-
- if s.options.retain_gids:
- isCFF2 = cff.major > 1
- for g in s.glyphs_emptied:
- _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True)
-
-
- return True # any(cff[fontname].numGlyphs for fontname in cff.keys())
+ cff = self.cff
+ for fontname in cff.keys():
+ font = cff[fontname]
+ cs = font.CharStrings
+
+ 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
+ sel.format = None
+ sel.gidArray = [sel.gidArray[i] for i in indices]
+ newCharStrings = {}
+ for indicesIdx, charsetIdx in enumerate(indices):
+ g = font.charset[charsetIdx]
+ if g in cs.charStrings:
+ newCharStrings[g] = indicesIdx
+ cs.charStrings = newCharStrings
+ 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)
+
+ if s.options.retain_gids:
+ isCFF2 = cff.major > 1
+ for g in s.glyphs_emptied:
+ _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True)
+
+ 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
+ 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:
- # For CFF2 charstrings, this should never happen
- assert self.private.defaultWidthX is not None, "CFF2 CharStrings must not have an initial width value"
- 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
+ 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:
+ # For CFF2 charstrings, this should never happen
+ assert (
+ self.private.defaultWidthX is not None
+ ), "CFF2 CharStrings must not have an initial width value"
+ 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):
+ def __init__(self, localSubrs, globalSubrs, private):
+ psCharStrings.SimpleT2Decompiler.__init__(
+ self, localSubrs, globalSubrs, private
+ )
+ for subrs in [localSubrs, globalSubrs]:
+ if subrs and not hasattr(subrs, "_used"):
+ subrs._used = set()
- def __init__(self, localSubrs, globalSubrs, private):
- psCharStrings.SimpleT2Decompiler.__init__(self,
- localSubrs,
- globalSubrs,
- private)
- 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_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)
- 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
-
- def __init__(self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None):
- self._css = css
- psCharStrings.T2WidthExtractor.__init__(
- self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX)
- self.private = private
-
- 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:
- hints.last_hint = index - 2 # Leave the subr call in
-
- elif subr_hints.status == 0:
- hints.deletions.append(index)
-
- hints.status = max(hints.status, subr_hints.status)
-
-
-@_add_method(ttLib.getTableClass('CFF '))
+ 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
+
+ def __init__(
+ self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None
+ ):
+ self._css = css
+ psCharStrings.T2WidthExtractor.__init__(
+ self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX
+ )
+ self.private = private
+
+ 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:
+ hints.last_hint = index - 2 # Leave the subr call in
+
+ elif subr_hints.status == 0:
+ hints.deletions.append(index)
+
+ hints.status = max(hints.status, subr_hints.status)
+
+
+@_add_method(ttLib.getTableClass("CFF "))
def prune_post_subset(self, ttfFont, options):
- 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
-
- # Desubroutinize if asked for
- if options.desubroutinize:
- cff.desubroutinize()
-
- # Drop hints if not needed
- if not options.hinting:
- self.remove_hints()
- elif not options.desubroutinize:
- self.remove_unused_subroutines()
- return True
+ 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
+
+ # Desubroutinize if asked for
+ if options.desubroutinize:
+ cff.desubroutinize()
+
+ # Drop hints if not needed
+ if not options.hinting:
+ self.remove_hints()
+ elif not options.desubroutinize:
+ self.remove_unused_subroutines()
+ 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
+ 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
-@deprecateFunction("use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning)
-@_add_method(ttLib.getTableClass('CFF '))
+@deprecateFunction(
+ "use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning
+)
+@_add_method(ttLib.getTableClass("CFF "))
def desubroutinize(self):
- self.cff.desubroutinize()
+ self.cff.desubroutinize()
-@_add_method(ttLib.getTableClass('CFF '))
+@_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
-
- # 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()
-
-
-@_add_method(ttLib.getTableClass('CFF '))
+ 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
+
+ # 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()
+
+
+@_add_method(ttLib.getTableClass("CFF "))
def remove_unused_subroutines(self):
- cff = self.cff
- for fontname in cff.keys():
- font = cff[fontname]
- cs = font.CharStrings
- # Renumber subroutines to remove unused ones
-
- # Mark all used subroutines
- for g in font.charset:
- c, _ = cs.getItemAndSelector(g)
- subrs = getattr(c.private, "Subrs", [])
- decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private)
- decompiler.execute(c)
-
- all_subrs = [font.GlobalSubrs]
- if hasattr(font, 'FDArray'):
- 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:
- if not hasattr(font, 'FDArray') and hasattr(font.Private, 'Subrs'):
- 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
- if hasattr(font, 'FDArray'):
- 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
+ cff = self.cff
+ for fontname in cff.keys():
+ font = cff[fontname]
+ cs = font.CharStrings
+ # Renumber subroutines to remove unused ones
+
+ # Mark all used subroutines
+ for g in font.charset:
+ c, _ = cs.getItemAndSelector(g)
+ subrs = getattr(c.private, "Subrs", [])
+ decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private)
+ decompiler.execute(c)
+
+ all_subrs = [font.GlobalSubrs]
+ if hasattr(font, "FDArray"):
+ 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:
+ if not hasattr(font, "FDArray") and hasattr(font.Private, "Subrs"):
+ 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
+ if hasattr(font, "FDArray"):
+ 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