aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/cffLib/__init__.py
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-31 18:51:49 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-31 18:51:49 +0000
commit02a5f1e78c4b1b2ed412593f39612f50bbd624bd (patch)
tree98d81cb66669c50af608fd8a844e22039a00f5ae /Lib/fontTools/cffLib/__init__.py
parentec30550b149d3ba5c81fc88f2f90dd171b3f4013 (diff)
parent29956f91d34a6e7e114e9e04c4c22296e20b80c8 (diff)
downloadfonttools-02a5f1e78c4b1b2ed412593f39612f50bbd624bd.tar.gz
Snap for 8389696 from 29956f91d34a6e7e114e9e04c4c22296e20b80c8 to tm-d2-releaseandroid-13.0.0_r55android13-d2-release
Change-Id: Ia7295471df01d224f7e034f9ea778aca269e4f86
Diffstat (limited to 'Lib/fontTools/cffLib/__init__.py')
-rw-r--r--Lib/fontTools/cffLib/__init__.py111
1 files changed, 109 insertions, 2 deletions
diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py
index d4cd7a17..07d0d513 100644
--- a/Lib/fontTools/cffLib/__init__.py
+++ b/Lib/fontTools/cffLib/__init__.py
@@ -11,11 +11,10 @@ the demands of variable fonts. This module parses both original CFF and CFF2.
"""
-from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc import psCharStrings
from fontTools.misc.arrayTools import unionRect, intRect
-from fontTools.misc.textTools import safeEval
+from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes, tostr, safeEval
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.otBase import OTTableWriter
from fontTools.ttLib.tables.otBase import OTTableReader
@@ -39,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).
@@ -369,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