[cffLib] Move desubroutinize from subset module to CFFFontSet
This way we can use it from both subset and merge modules.
This commit is contained in:
parent
e5e69852ed
commit
8e9ab24c11
@ -38,6 +38,85 @@ maxStackLimit = 513
|
||||
# maxstack operator has been deprecated. max stack is now always 513.
|
||||
|
||||
|
||||
class StopHintCountEvent(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
|
||||
stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto",
|
||||
"op_vmoveto")
|
||||
|
||||
def __init__(self, localSubrs, globalSubrs, private=None):
|
||||
psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs,
|
||||
private)
|
||||
|
||||
def execute(self, charString):
|
||||
self.need_hintcount = True # until proven otherwise
|
||||
for op_name in self.stop_hintcount_ops:
|
||||
setattr(self, op_name, self.stop_hint_count)
|
||||
|
||||
if hasattr(charString, '_desubroutinized'):
|
||||
# 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.
|
||||
if self.need_hintcount and self.callingStack:
|
||||
try:
|
||||
psCharStrings.SimpleT2Decompiler.execute(self, charString)
|
||||
except StopHintCountEvent:
|
||||
del self.callingStack[-1]
|
||||
return
|
||||
|
||||
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 not self.private.in_cff2:
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
def processSubr(self, index, subr):
|
||||
cs = self.callingStack[-1]
|
||||
if not hasattr(cs, '_desubroutinized'):
|
||||
cs._patches.append((index, subr._desubroutinized))
|
||||
|
||||
|
||||
class CFFFontSet(object):
|
||||
"""A CFF font "file" can contain more than one font, although this is
|
||||
extremely rare (and not allowed within OpenType fonts).
|
||||
@ -368,6 +447,35 @@ class CFFFontSet(object):
|
||||
file.seek(0)
|
||||
self.decompile(file, otFont, isCFF2=True)
|
||||
|
||||
def desubroutinize(self):
|
||||
for fontName in self.fontNames:
|
||||
font = self[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
|
||||
# Delete all the local subrs
|
||||
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']
|
||||
else:
|
||||
pd = font.Private
|
||||
if hasattr(pd, 'Subrs'):
|
||||
del pd.Subrs
|
||||
if 'Subrs' in pd.rawDict:
|
||||
del pd.rawDict['Subrs']
|
||||
# as well as the global subrs
|
||||
self.GlobalSubrs.clear()
|
||||
|
||||
|
||||
class CFFWriter(object):
|
||||
"""Helper class for serializing CFF data to binary. Used by
|
||||
|
@ -14,32 +14,11 @@ import time
|
||||
import operator
|
||||
import logging
|
||||
import os
|
||||
from fontTools import subset
|
||||
|
||||
|
||||
log = logging.getLogger("fontTools.merge")
|
||||
timer = Timer(logger=logging.getLogger(__name__+".timer"), level=logging.INFO)
|
||||
|
||||
def _desubtoutinize(otf):
|
||||
# Desubroutinize with Subsetter (like compreffor does)
|
||||
from io import BytesIO
|
||||
|
||||
stream = BytesIO()
|
||||
otf.save(stream, reorderTables=False)
|
||||
stream.flush()
|
||||
stream.seek(0)
|
||||
tmpfont = ttLib.TTFont(stream)
|
||||
|
||||
options = subset.Options()
|
||||
options.desubroutinize = True
|
||||
options.notdef_outline = True
|
||||
subsetter = subset.Subsetter(options=options)
|
||||
subsetter.populate(glyphs=tmpfont.getGlyphOrder())
|
||||
subsetter.subset(tmpfont)
|
||||
data = tmpfont['CFF '].compile(tmpfont)
|
||||
table = ttLib.newTable('CFF ')
|
||||
table.decompile(data, otf)
|
||||
otf['CFF '] = table
|
||||
tmpfont.close()
|
||||
|
||||
def _add_method(*clazzes, **kwargs):
|
||||
"""Returns a decorator function that adds a new method to one or
|
||||
@ -1090,7 +1069,7 @@ class Merger(object):
|
||||
cffTables = []
|
||||
if sfntVersion == "OTTO":
|
||||
for i, font in enumerate(fonts):
|
||||
_desubtoutinize(font)
|
||||
font['CFF '].cff.desubroutinize()
|
||||
cffTables.append(font['CFF '])
|
||||
|
||||
glyphOrders = [font.getGlyphOrder() for font in fonts]
|
||||
|
@ -2,6 +2,7 @@ from fontTools.misc import psCharStrings
|
||||
from fontTools import ttLib
|
||||
from fontTools.pens.basePen import NullPen
|
||||
from fontTools.misc.roundTools import otRound
|
||||
from fontTools.misc.loggingTools import deprecateFunction
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
from fontTools.subset.util import _add_method, _uniq_sort
|
||||
|
||||
@ -345,86 +346,6 @@ class _DehintingT2Decompiler(psCharStrings.T2WidthExtractor):
|
||||
|
||||
hints.status = max(hints.status, subr_hints.status)
|
||||
|
||||
class StopHintCountEvent(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
|
||||
stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto",
|
||||
"op_vmoveto")
|
||||
|
||||
def __init__(self, localSubrs, globalSubrs, private=None):
|
||||
psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs,
|
||||
private)
|
||||
|
||||
def execute(self, charString):
|
||||
self.need_hintcount = True # until proven otherwise
|
||||
for op_name in self.stop_hintcount_ops:
|
||||
setattr(self, op_name, self.stop_hint_count)
|
||||
|
||||
if hasattr(charString, '_desubroutinized'):
|
||||
# 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.
|
||||
if self.need_hintcount and self.callingStack:
|
||||
try:
|
||||
psCharStrings.SimpleT2Decompiler.execute(self, charString)
|
||||
except StopHintCountEvent:
|
||||
del self.callingStack[-1]
|
||||
return
|
||||
|
||||
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 not self.private.in_cff2:
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
def processSubr(self, index, subr):
|
||||
cs = self.callingStack[-1]
|
||||
if not hasattr(cs, '_desubroutinized'):
|
||||
cs._patches.append((index, subr._desubroutinized))
|
||||
|
||||
|
||||
@_add_method(ttLib.getTableClass('CFF '))
|
||||
def prune_post_subset(self, ttfFont, options):
|
||||
@ -444,7 +365,7 @@ def prune_post_subset(self, ttfFont, options):
|
||||
|
||||
# Desubroutinize if asked for
|
||||
if options.desubroutinize:
|
||||
self.desubroutinize()
|
||||
cff.desubroutinize()
|
||||
|
||||
# Drop hints if not needed
|
||||
if not options.hinting:
|
||||
@ -460,36 +381,11 @@ def _delete_empty_subrs(private_dict):
|
||||
del private_dict.rawDict['Subrs']
|
||||
del private_dict.Subrs
|
||||
|
||||
|
||||
@deprecateFunction("use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning)
|
||||
@_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
|
||||
# Delete all the local subrs
|
||||
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']
|
||||
else:
|
||||
pd = font.Private
|
||||
if hasattr(pd, 'Subrs'):
|
||||
del pd.Subrs
|
||||
if 'Subrs' in pd.rawDict:
|
||||
del pd.rawDict['Subrs']
|
||||
# as well as the global subrs
|
||||
cff.GlobalSubrs.clear()
|
||||
self.cff.desubroutinize()
|
||||
|
||||
|
||||
@_add_method(ttLib.getTableClass('CFF '))
|
||||
|
Loading…
x
Reference in New Issue
Block a user