xref: /aosp_15_r20/external/fonttools/Lib/fontTools/subset/cff.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.misc import psCharStrings
2*e1fe3e4aSElliott Hughesfrom fontTools import ttLib
3*e1fe3e4aSElliott Hughesfrom fontTools.pens.basePen import NullPen
4*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import otRound
5*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import deprecateFunction
6*e1fe3e4aSElliott Hughesfrom fontTools.subset.util import _add_method, _uniq_sort
7*e1fe3e4aSElliott Hughes
8*e1fe3e4aSElliott Hughes
9*e1fe3e4aSElliott Hughesclass _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler):
10*e1fe3e4aSElliott Hughes    def __init__(self, components, localSubrs, globalSubrs):
11*e1fe3e4aSElliott Hughes        psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs)
12*e1fe3e4aSElliott Hughes        self.components = components
13*e1fe3e4aSElliott Hughes
14*e1fe3e4aSElliott Hughes    def op_endchar(self, index):
15*e1fe3e4aSElliott Hughes        args = self.popall()
16*e1fe3e4aSElliott Hughes        if len(args) >= 4:
17*e1fe3e4aSElliott Hughes            from fontTools.encodings.StandardEncoding import StandardEncoding
18*e1fe3e4aSElliott Hughes
19*e1fe3e4aSElliott Hughes            # endchar can do seac accent bulding; The T2 spec says it's deprecated,
20*e1fe3e4aSElliott Hughes            # but recent software that shall remain nameless does output it.
21*e1fe3e4aSElliott Hughes            adx, ady, bchar, achar = args[-4:]
22*e1fe3e4aSElliott Hughes            baseGlyph = StandardEncoding[bchar]
23*e1fe3e4aSElliott Hughes            accentGlyph = StandardEncoding[achar]
24*e1fe3e4aSElliott Hughes            self.components.add(baseGlyph)
25*e1fe3e4aSElliott Hughes            self.components.add(accentGlyph)
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughes
28*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF "))
29*e1fe3e4aSElliott Hughesdef closure_glyphs(self, s):
30*e1fe3e4aSElliott Hughes    cff = self.cff
31*e1fe3e4aSElliott Hughes    assert len(cff) == 1
32*e1fe3e4aSElliott Hughes    font = cff[cff.keys()[0]]
33*e1fe3e4aSElliott Hughes    glyphSet = font.CharStrings
34*e1fe3e4aSElliott Hughes
35*e1fe3e4aSElliott Hughes    decompose = s.glyphs
36*e1fe3e4aSElliott Hughes    while decompose:
37*e1fe3e4aSElliott Hughes        components = set()
38*e1fe3e4aSElliott Hughes        for g in decompose:
39*e1fe3e4aSElliott Hughes            if g not in glyphSet:
40*e1fe3e4aSElliott Hughes                continue
41*e1fe3e4aSElliott Hughes            gl = glyphSet[g]
42*e1fe3e4aSElliott Hughes
43*e1fe3e4aSElliott Hughes            subrs = getattr(gl.private, "Subrs", [])
44*e1fe3e4aSElliott Hughes            decompiler = _ClosureGlyphsT2Decompiler(components, subrs, gl.globalSubrs)
45*e1fe3e4aSElliott Hughes            decompiler.execute(gl)
46*e1fe3e4aSElliott Hughes        components -= s.glyphs
47*e1fe3e4aSElliott Hughes        s.glyphs.update(components)
48*e1fe3e4aSElliott Hughes        decompose = components
49*e1fe3e4aSElliott Hughes
50*e1fe3e4aSElliott Hughes
51*e1fe3e4aSElliott Hughesdef _empty_charstring(font, glyphName, isCFF2, ignoreWidth=False):
52*e1fe3e4aSElliott Hughes    c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName)
53*e1fe3e4aSElliott Hughes    if isCFF2 or ignoreWidth:
54*e1fe3e4aSElliott Hughes        # CFF2 charstrings have no widths nor 'endchar' operators
55*e1fe3e4aSElliott Hughes        c.setProgram([] if isCFF2 else ["endchar"])
56*e1fe3e4aSElliott Hughes    else:
57*e1fe3e4aSElliott Hughes        if hasattr(font, "FDArray") and font.FDArray is not None:
58*e1fe3e4aSElliott Hughes            private = font.FDArray[fdSelectIndex].Private
59*e1fe3e4aSElliott Hughes        else:
60*e1fe3e4aSElliott Hughes            private = font.Private
61*e1fe3e4aSElliott Hughes        dfltWdX = private.defaultWidthX
62*e1fe3e4aSElliott Hughes        nmnlWdX = private.nominalWidthX
63*e1fe3e4aSElliott Hughes        pen = NullPen()
64*e1fe3e4aSElliott Hughes        c.draw(pen)  # this will set the charstring's width
65*e1fe3e4aSElliott Hughes        if c.width != dfltWdX:
66*e1fe3e4aSElliott Hughes            c.program = [c.width - nmnlWdX, "endchar"]
67*e1fe3e4aSElliott Hughes        else:
68*e1fe3e4aSElliott Hughes            c.program = ["endchar"]
69*e1fe3e4aSElliott Hughes
70*e1fe3e4aSElliott Hughes
71*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF "))
72*e1fe3e4aSElliott Hughesdef prune_pre_subset(self, font, options):
73*e1fe3e4aSElliott Hughes    cff = self.cff
74*e1fe3e4aSElliott Hughes    # CFF table must have one font only
75*e1fe3e4aSElliott Hughes    cff.fontNames = cff.fontNames[:1]
76*e1fe3e4aSElliott Hughes
77*e1fe3e4aSElliott Hughes    if options.notdef_glyph and not options.notdef_outline:
78*e1fe3e4aSElliott Hughes        isCFF2 = cff.major > 1
79*e1fe3e4aSElliott Hughes        for fontname in cff.keys():
80*e1fe3e4aSElliott Hughes            font = cff[fontname]
81*e1fe3e4aSElliott Hughes            _empty_charstring(font, ".notdef", isCFF2=isCFF2)
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott Hughes    # Clear useless Encoding
84*e1fe3e4aSElliott Hughes    for fontname in cff.keys():
85*e1fe3e4aSElliott Hughes        font = cff[fontname]
86*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/620
87*e1fe3e4aSElliott Hughes        font.Encoding = "StandardEncoding"
88*e1fe3e4aSElliott Hughes
89*e1fe3e4aSElliott Hughes    return True  # bool(cff.fontNames)
90*e1fe3e4aSElliott Hughes
91*e1fe3e4aSElliott Hughes
92*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF "))
93*e1fe3e4aSElliott Hughesdef subset_glyphs(self, s):
94*e1fe3e4aSElliott Hughes    cff = self.cff
95*e1fe3e4aSElliott Hughes    for fontname in cff.keys():
96*e1fe3e4aSElliott Hughes        font = cff[fontname]
97*e1fe3e4aSElliott Hughes        cs = font.CharStrings
98*e1fe3e4aSElliott Hughes
99*e1fe3e4aSElliott Hughes        glyphs = s.glyphs.union(s.glyphs_emptied)
100*e1fe3e4aSElliott Hughes
101*e1fe3e4aSElliott Hughes        # Load all glyphs
102*e1fe3e4aSElliott Hughes        for g in font.charset:
103*e1fe3e4aSElliott Hughes            if g not in glyphs:
104*e1fe3e4aSElliott Hughes                continue
105*e1fe3e4aSElliott Hughes            c, _ = cs.getItemAndSelector(g)
106*e1fe3e4aSElliott Hughes
107*e1fe3e4aSElliott Hughes        if cs.charStringsAreIndexed:
108*e1fe3e4aSElliott Hughes            indices = [i for i, g in enumerate(font.charset) if g in glyphs]
109*e1fe3e4aSElliott Hughes            csi = cs.charStringsIndex
110*e1fe3e4aSElliott Hughes            csi.items = [csi.items[i] for i in indices]
111*e1fe3e4aSElliott Hughes            del csi.file, csi.offsets
112*e1fe3e4aSElliott Hughes            if hasattr(font, "FDSelect"):
113*e1fe3e4aSElliott Hughes                sel = font.FDSelect
114*e1fe3e4aSElliott Hughes                sel.format = None
115*e1fe3e4aSElliott Hughes                sel.gidArray = [sel.gidArray[i] for i in indices]
116*e1fe3e4aSElliott Hughes            newCharStrings = {}
117*e1fe3e4aSElliott Hughes            for indicesIdx, charsetIdx in enumerate(indices):
118*e1fe3e4aSElliott Hughes                g = font.charset[charsetIdx]
119*e1fe3e4aSElliott Hughes                if g in cs.charStrings:
120*e1fe3e4aSElliott Hughes                    newCharStrings[g] = indicesIdx
121*e1fe3e4aSElliott Hughes            cs.charStrings = newCharStrings
122*e1fe3e4aSElliott Hughes        else:
123*e1fe3e4aSElliott Hughes            cs.charStrings = {g: v for g, v in cs.charStrings.items() if g in glyphs}
124*e1fe3e4aSElliott Hughes        font.charset = [g for g in font.charset if g in glyphs]
125*e1fe3e4aSElliott Hughes        font.numGlyphs = len(font.charset)
126*e1fe3e4aSElliott Hughes
127*e1fe3e4aSElliott Hughes        if s.options.retain_gids:
128*e1fe3e4aSElliott Hughes            isCFF2 = cff.major > 1
129*e1fe3e4aSElliott Hughes            for g in s.glyphs_emptied:
130*e1fe3e4aSElliott Hughes                _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True)
131*e1fe3e4aSElliott Hughes
132*e1fe3e4aSElliott Hughes    return True  # any(cff[fontname].numGlyphs for fontname in cff.keys())
133*e1fe3e4aSElliott Hughes
134*e1fe3e4aSElliott Hughes
135*e1fe3e4aSElliott Hughes@_add_method(psCharStrings.T2CharString)
136*e1fe3e4aSElliott Hughesdef subset_subroutines(self, subrs, gsubrs):
137*e1fe3e4aSElliott Hughes    p = self.program
138*e1fe3e4aSElliott Hughes    for i in range(1, len(p)):
139*e1fe3e4aSElliott Hughes        if p[i] == "callsubr":
140*e1fe3e4aSElliott Hughes            assert isinstance(p[i - 1], int)
141*e1fe3e4aSElliott Hughes            p[i - 1] = subrs._used.index(p[i - 1] + subrs._old_bias) - subrs._new_bias
142*e1fe3e4aSElliott Hughes        elif p[i] == "callgsubr":
143*e1fe3e4aSElliott Hughes            assert isinstance(p[i - 1], int)
144*e1fe3e4aSElliott Hughes            p[i - 1] = (
145*e1fe3e4aSElliott Hughes                gsubrs._used.index(p[i - 1] + gsubrs._old_bias) - gsubrs._new_bias
146*e1fe3e4aSElliott Hughes            )
147*e1fe3e4aSElliott Hughes
148*e1fe3e4aSElliott Hughes
149*e1fe3e4aSElliott Hughes@_add_method(psCharStrings.T2CharString)
150*e1fe3e4aSElliott Hughesdef drop_hints(self):
151*e1fe3e4aSElliott Hughes    hints = self._hints
152*e1fe3e4aSElliott Hughes
153*e1fe3e4aSElliott Hughes    if hints.deletions:
154*e1fe3e4aSElliott Hughes        p = self.program
155*e1fe3e4aSElliott Hughes        for idx in reversed(hints.deletions):
156*e1fe3e4aSElliott Hughes            del p[idx - 2 : idx]
157*e1fe3e4aSElliott Hughes
158*e1fe3e4aSElliott Hughes    if hints.has_hint:
159*e1fe3e4aSElliott Hughes        assert not hints.deletions or hints.last_hint <= hints.deletions[0]
160*e1fe3e4aSElliott Hughes        self.program = self.program[hints.last_hint :]
161*e1fe3e4aSElliott Hughes        if not self.program:
162*e1fe3e4aSElliott Hughes            # TODO CFF2 no need for endchar.
163*e1fe3e4aSElliott Hughes            self.program.append("endchar")
164*e1fe3e4aSElliott Hughes        if hasattr(self, "width"):
165*e1fe3e4aSElliott Hughes            # Insert width back if needed
166*e1fe3e4aSElliott Hughes            if self.width != self.private.defaultWidthX:
167*e1fe3e4aSElliott Hughes                # For CFF2 charstrings, this should never happen
168*e1fe3e4aSElliott Hughes                assert (
169*e1fe3e4aSElliott Hughes                    self.private.defaultWidthX is not None
170*e1fe3e4aSElliott Hughes                ), "CFF2 CharStrings must not have an initial width value"
171*e1fe3e4aSElliott Hughes                self.program.insert(0, self.width - self.private.nominalWidthX)
172*e1fe3e4aSElliott Hughes
173*e1fe3e4aSElliott Hughes    if hints.has_hintmask:
174*e1fe3e4aSElliott Hughes        i = 0
175*e1fe3e4aSElliott Hughes        p = self.program
176*e1fe3e4aSElliott Hughes        while i < len(p):
177*e1fe3e4aSElliott Hughes            if p[i] in ["hintmask", "cntrmask"]:
178*e1fe3e4aSElliott Hughes                assert i + 1 <= len(p)
179*e1fe3e4aSElliott Hughes                del p[i : i + 2]
180*e1fe3e4aSElliott Hughes                continue
181*e1fe3e4aSElliott Hughes            i += 1
182*e1fe3e4aSElliott Hughes
183*e1fe3e4aSElliott Hughes    assert len(self.program)
184*e1fe3e4aSElliott Hughes
185*e1fe3e4aSElliott Hughes    del self._hints
186*e1fe3e4aSElliott Hughes
187*e1fe3e4aSElliott Hughes
188*e1fe3e4aSElliott Hughesclass _MarkingT2Decompiler(psCharStrings.SimpleT2Decompiler):
189*e1fe3e4aSElliott Hughes    def __init__(self, localSubrs, globalSubrs, private):
190*e1fe3e4aSElliott Hughes        psCharStrings.SimpleT2Decompiler.__init__(
191*e1fe3e4aSElliott Hughes            self, localSubrs, globalSubrs, private
192*e1fe3e4aSElliott Hughes        )
193*e1fe3e4aSElliott Hughes        for subrs in [localSubrs, globalSubrs]:
194*e1fe3e4aSElliott Hughes            if subrs and not hasattr(subrs, "_used"):
195*e1fe3e4aSElliott Hughes                subrs._used = set()
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes    def op_callsubr(self, index):
198*e1fe3e4aSElliott Hughes        self.localSubrs._used.add(self.operandStack[-1] + self.localBias)
199*e1fe3e4aSElliott Hughes        psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
200*e1fe3e4aSElliott Hughes
201*e1fe3e4aSElliott Hughes    def op_callgsubr(self, index):
202*e1fe3e4aSElliott Hughes        self.globalSubrs._used.add(self.operandStack[-1] + self.globalBias)
203*e1fe3e4aSElliott Hughes        psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
204*e1fe3e4aSElliott Hughes
205*e1fe3e4aSElliott Hughes
206*e1fe3e4aSElliott Hughesclass _DehintingT2Decompiler(psCharStrings.T2WidthExtractor):
207*e1fe3e4aSElliott Hughes    class Hints(object):
208*e1fe3e4aSElliott Hughes        def __init__(self):
209*e1fe3e4aSElliott Hughes            # Whether calling this charstring produces any hint stems
210*e1fe3e4aSElliott Hughes            # Note that if a charstring starts with hintmask, it will
211*e1fe3e4aSElliott Hughes            # have has_hint set to True, because it *might* produce an
212*e1fe3e4aSElliott Hughes            # implicit vstem if called under certain conditions.
213*e1fe3e4aSElliott Hughes            self.has_hint = False
214*e1fe3e4aSElliott Hughes            # Index to start at to drop all hints
215*e1fe3e4aSElliott Hughes            self.last_hint = 0
216*e1fe3e4aSElliott Hughes            # Index up to which we know more hints are possible.
217*e1fe3e4aSElliott Hughes            # Only relevant if status is 0 or 1.
218*e1fe3e4aSElliott Hughes            self.last_checked = 0
219*e1fe3e4aSElliott Hughes            # The status means:
220*e1fe3e4aSElliott Hughes            # 0: after dropping hints, this charstring is empty
221*e1fe3e4aSElliott Hughes            # 1: after dropping hints, there may be more hints
222*e1fe3e4aSElliott Hughes            # 	continuing after this, or there might be
223*e1fe3e4aSElliott Hughes            # 	other things.  Not clear yet.
224*e1fe3e4aSElliott Hughes            # 2: no more hints possible after this charstring
225*e1fe3e4aSElliott Hughes            self.status = 0
226*e1fe3e4aSElliott Hughes            # Has hintmask instructions; not recursive
227*e1fe3e4aSElliott Hughes            self.has_hintmask = False
228*e1fe3e4aSElliott Hughes            # List of indices of calls to empty subroutines to remove.
229*e1fe3e4aSElliott Hughes            self.deletions = []
230*e1fe3e4aSElliott Hughes
231*e1fe3e4aSElliott Hughes        pass
232*e1fe3e4aSElliott Hughes
233*e1fe3e4aSElliott Hughes    def __init__(
234*e1fe3e4aSElliott Hughes        self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None
235*e1fe3e4aSElliott Hughes    ):
236*e1fe3e4aSElliott Hughes        self._css = css
237*e1fe3e4aSElliott Hughes        psCharStrings.T2WidthExtractor.__init__(
238*e1fe3e4aSElliott Hughes            self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX
239*e1fe3e4aSElliott Hughes        )
240*e1fe3e4aSElliott Hughes        self.private = private
241*e1fe3e4aSElliott Hughes
242*e1fe3e4aSElliott Hughes    def execute(self, charString):
243*e1fe3e4aSElliott Hughes        old_hints = charString._hints if hasattr(charString, "_hints") else None
244*e1fe3e4aSElliott Hughes        charString._hints = self.Hints()
245*e1fe3e4aSElliott Hughes
246*e1fe3e4aSElliott Hughes        psCharStrings.T2WidthExtractor.execute(self, charString)
247*e1fe3e4aSElliott Hughes
248*e1fe3e4aSElliott Hughes        hints = charString._hints
249*e1fe3e4aSElliott Hughes
250*e1fe3e4aSElliott Hughes        if hints.has_hint or hints.has_hintmask:
251*e1fe3e4aSElliott Hughes            self._css.add(charString)
252*e1fe3e4aSElliott Hughes
253*e1fe3e4aSElliott Hughes        if hints.status != 2:
254*e1fe3e4aSElliott Hughes            # Check from last_check, make sure we didn't have any operators.
255*e1fe3e4aSElliott Hughes            for i in range(hints.last_checked, len(charString.program) - 1):
256*e1fe3e4aSElliott Hughes                if isinstance(charString.program[i], str):
257*e1fe3e4aSElliott Hughes                    hints.status = 2
258*e1fe3e4aSElliott Hughes                    break
259*e1fe3e4aSElliott Hughes                else:
260*e1fe3e4aSElliott Hughes                    hints.status = 1  # There's *something* here
261*e1fe3e4aSElliott Hughes            hints.last_checked = len(charString.program)
262*e1fe3e4aSElliott Hughes
263*e1fe3e4aSElliott Hughes        if old_hints:
264*e1fe3e4aSElliott Hughes            assert hints.__dict__ == old_hints.__dict__
265*e1fe3e4aSElliott Hughes
266*e1fe3e4aSElliott Hughes    def op_callsubr(self, index):
267*e1fe3e4aSElliott Hughes        subr = self.localSubrs[self.operandStack[-1] + self.localBias]
268*e1fe3e4aSElliott Hughes        psCharStrings.T2WidthExtractor.op_callsubr(self, index)
269*e1fe3e4aSElliott Hughes        self.processSubr(index, subr)
270*e1fe3e4aSElliott Hughes
271*e1fe3e4aSElliott Hughes    def op_callgsubr(self, index):
272*e1fe3e4aSElliott Hughes        subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
273*e1fe3e4aSElliott Hughes        psCharStrings.T2WidthExtractor.op_callgsubr(self, index)
274*e1fe3e4aSElliott Hughes        self.processSubr(index, subr)
275*e1fe3e4aSElliott Hughes
276*e1fe3e4aSElliott Hughes    def op_hstem(self, index):
277*e1fe3e4aSElliott Hughes        psCharStrings.T2WidthExtractor.op_hstem(self, index)
278*e1fe3e4aSElliott Hughes        self.processHint(index)
279*e1fe3e4aSElliott Hughes
280*e1fe3e4aSElliott Hughes    def op_vstem(self, index):
281*e1fe3e4aSElliott Hughes        psCharStrings.T2WidthExtractor.op_vstem(self, index)
282*e1fe3e4aSElliott Hughes        self.processHint(index)
283*e1fe3e4aSElliott Hughes
284*e1fe3e4aSElliott Hughes    def op_hstemhm(self, index):
285*e1fe3e4aSElliott Hughes        psCharStrings.T2WidthExtractor.op_hstemhm(self, index)
286*e1fe3e4aSElliott Hughes        self.processHint(index)
287*e1fe3e4aSElliott Hughes
288*e1fe3e4aSElliott Hughes    def op_vstemhm(self, index):
289*e1fe3e4aSElliott Hughes        psCharStrings.T2WidthExtractor.op_vstemhm(self, index)
290*e1fe3e4aSElliott Hughes        self.processHint(index)
291*e1fe3e4aSElliott Hughes
292*e1fe3e4aSElliott Hughes    def op_hintmask(self, index):
293*e1fe3e4aSElliott Hughes        rv = psCharStrings.T2WidthExtractor.op_hintmask(self, index)
294*e1fe3e4aSElliott Hughes        self.processHintmask(index)
295*e1fe3e4aSElliott Hughes        return rv
296*e1fe3e4aSElliott Hughes
297*e1fe3e4aSElliott Hughes    def op_cntrmask(self, index):
298*e1fe3e4aSElliott Hughes        rv = psCharStrings.T2WidthExtractor.op_cntrmask(self, index)
299*e1fe3e4aSElliott Hughes        self.processHintmask(index)
300*e1fe3e4aSElliott Hughes        return rv
301*e1fe3e4aSElliott Hughes
302*e1fe3e4aSElliott Hughes    def processHintmask(self, index):
303*e1fe3e4aSElliott Hughes        cs = self.callingStack[-1]
304*e1fe3e4aSElliott Hughes        hints = cs._hints
305*e1fe3e4aSElliott Hughes        hints.has_hintmask = True
306*e1fe3e4aSElliott Hughes        if hints.status != 2:
307*e1fe3e4aSElliott Hughes            # Check from last_check, see if we may be an implicit vstem
308*e1fe3e4aSElliott Hughes            for i in range(hints.last_checked, index - 1):
309*e1fe3e4aSElliott Hughes                if isinstance(cs.program[i], str):
310*e1fe3e4aSElliott Hughes                    hints.status = 2
311*e1fe3e4aSElliott Hughes                    break
312*e1fe3e4aSElliott Hughes            else:
313*e1fe3e4aSElliott Hughes                # We are an implicit vstem
314*e1fe3e4aSElliott Hughes                hints.has_hint = True
315*e1fe3e4aSElliott Hughes                hints.last_hint = index + 1
316*e1fe3e4aSElliott Hughes                hints.status = 0
317*e1fe3e4aSElliott Hughes        hints.last_checked = index + 1
318*e1fe3e4aSElliott Hughes
319*e1fe3e4aSElliott Hughes    def processHint(self, index):
320*e1fe3e4aSElliott Hughes        cs = self.callingStack[-1]
321*e1fe3e4aSElliott Hughes        hints = cs._hints
322*e1fe3e4aSElliott Hughes        hints.has_hint = True
323*e1fe3e4aSElliott Hughes        hints.last_hint = index
324*e1fe3e4aSElliott Hughes        hints.last_checked = index
325*e1fe3e4aSElliott Hughes
326*e1fe3e4aSElliott Hughes    def processSubr(self, index, subr):
327*e1fe3e4aSElliott Hughes        cs = self.callingStack[-1]
328*e1fe3e4aSElliott Hughes        hints = cs._hints
329*e1fe3e4aSElliott Hughes        subr_hints = subr._hints
330*e1fe3e4aSElliott Hughes
331*e1fe3e4aSElliott Hughes        # Check from last_check, make sure we didn't have
332*e1fe3e4aSElliott Hughes        # any operators.
333*e1fe3e4aSElliott Hughes        if hints.status != 2:
334*e1fe3e4aSElliott Hughes            for i in range(hints.last_checked, index - 1):
335*e1fe3e4aSElliott Hughes                if isinstance(cs.program[i], str):
336*e1fe3e4aSElliott Hughes                    hints.status = 2
337*e1fe3e4aSElliott Hughes                    break
338*e1fe3e4aSElliott Hughes            hints.last_checked = index
339*e1fe3e4aSElliott Hughes
340*e1fe3e4aSElliott Hughes        if hints.status != 2:
341*e1fe3e4aSElliott Hughes            if subr_hints.has_hint:
342*e1fe3e4aSElliott Hughes                hints.has_hint = True
343*e1fe3e4aSElliott Hughes
344*e1fe3e4aSElliott Hughes                # Decide where to chop off from
345*e1fe3e4aSElliott Hughes                if subr_hints.status == 0:
346*e1fe3e4aSElliott Hughes                    hints.last_hint = index
347*e1fe3e4aSElliott Hughes                else:
348*e1fe3e4aSElliott Hughes                    hints.last_hint = index - 2  # Leave the subr call in
349*e1fe3e4aSElliott Hughes
350*e1fe3e4aSElliott Hughes        elif subr_hints.status == 0:
351*e1fe3e4aSElliott Hughes            hints.deletions.append(index)
352*e1fe3e4aSElliott Hughes
353*e1fe3e4aSElliott Hughes        hints.status = max(hints.status, subr_hints.status)
354*e1fe3e4aSElliott Hughes
355*e1fe3e4aSElliott Hughes
356*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF "))
357*e1fe3e4aSElliott Hughesdef prune_post_subset(self, ttfFont, options):
358*e1fe3e4aSElliott Hughes    cff = self.cff
359*e1fe3e4aSElliott Hughes    for fontname in cff.keys():
360*e1fe3e4aSElliott Hughes        font = cff[fontname]
361*e1fe3e4aSElliott Hughes        cs = font.CharStrings
362*e1fe3e4aSElliott Hughes
363*e1fe3e4aSElliott Hughes        # Drop unused FontDictionaries
364*e1fe3e4aSElliott Hughes        if hasattr(font, "FDSelect"):
365*e1fe3e4aSElliott Hughes            sel = font.FDSelect
366*e1fe3e4aSElliott Hughes            indices = _uniq_sort(sel.gidArray)
367*e1fe3e4aSElliott Hughes            sel.gidArray = [indices.index(ss) for ss in sel.gidArray]
368*e1fe3e4aSElliott Hughes            arr = font.FDArray
369*e1fe3e4aSElliott Hughes            arr.items = [arr[i] for i in indices]
370*e1fe3e4aSElliott Hughes            del arr.file, arr.offsets
371*e1fe3e4aSElliott Hughes
372*e1fe3e4aSElliott Hughes    # Desubroutinize if asked for
373*e1fe3e4aSElliott Hughes    if options.desubroutinize:
374*e1fe3e4aSElliott Hughes        cff.desubroutinize()
375*e1fe3e4aSElliott Hughes
376*e1fe3e4aSElliott Hughes    # Drop hints if not needed
377*e1fe3e4aSElliott Hughes    if not options.hinting:
378*e1fe3e4aSElliott Hughes        self.remove_hints()
379*e1fe3e4aSElliott Hughes    elif not options.desubroutinize:
380*e1fe3e4aSElliott Hughes        self.remove_unused_subroutines()
381*e1fe3e4aSElliott Hughes    return True
382*e1fe3e4aSElliott Hughes
383*e1fe3e4aSElliott Hughes
384*e1fe3e4aSElliott Hughesdef _delete_empty_subrs(private_dict):
385*e1fe3e4aSElliott Hughes    if hasattr(private_dict, "Subrs") and not private_dict.Subrs:
386*e1fe3e4aSElliott Hughes        if "Subrs" in private_dict.rawDict:
387*e1fe3e4aSElliott Hughes            del private_dict.rawDict["Subrs"]
388*e1fe3e4aSElliott Hughes        del private_dict.Subrs
389*e1fe3e4aSElliott Hughes
390*e1fe3e4aSElliott Hughes
391*e1fe3e4aSElliott Hughes@deprecateFunction(
392*e1fe3e4aSElliott Hughes    "use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning
393*e1fe3e4aSElliott Hughes)
394*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF "))
395*e1fe3e4aSElliott Hughesdef desubroutinize(self):
396*e1fe3e4aSElliott Hughes    self.cff.desubroutinize()
397*e1fe3e4aSElliott Hughes
398*e1fe3e4aSElliott Hughes
399*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF "))
400*e1fe3e4aSElliott Hughesdef remove_hints(self):
401*e1fe3e4aSElliott Hughes    cff = self.cff
402*e1fe3e4aSElliott Hughes    for fontname in cff.keys():
403*e1fe3e4aSElliott Hughes        font = cff[fontname]
404*e1fe3e4aSElliott Hughes        cs = font.CharStrings
405*e1fe3e4aSElliott Hughes        # This can be tricky, but doesn't have to. What we do is:
406*e1fe3e4aSElliott Hughes        #
407*e1fe3e4aSElliott Hughes        # - Run all used glyph charstrings and recurse into subroutines,
408*e1fe3e4aSElliott Hughes        # - For each charstring (including subroutines), if it has any
409*e1fe3e4aSElliott Hughes        #   of the hint stem operators, we mark it as such.
410*e1fe3e4aSElliott Hughes        #   Upon returning, for each charstring we note all the
411*e1fe3e4aSElliott Hughes        #   subroutine calls it makes that (recursively) contain a stem,
412*e1fe3e4aSElliott Hughes        # - Dropping hinting then consists of the following two ops:
413*e1fe3e4aSElliott Hughes        #   * Drop the piece of the program in each charstring before the
414*e1fe3e4aSElliott Hughes        #     last call to a stem op or a stem-calling subroutine,
415*e1fe3e4aSElliott Hughes        #   * Drop all hintmask operations.
416*e1fe3e4aSElliott Hughes        # - It's trickier... A hintmask right after hints and a few numbers
417*e1fe3e4aSElliott Hughes        #    will act as an implicit vstemhm. As such, we track whether
418*e1fe3e4aSElliott Hughes        #    we have seen any non-hint operators so far and do the right
419*e1fe3e4aSElliott Hughes        #    thing, recursively... Good luck understanding that :(
420*e1fe3e4aSElliott Hughes        css = set()
421*e1fe3e4aSElliott Hughes        for g in font.charset:
422*e1fe3e4aSElliott Hughes            c, _ = cs.getItemAndSelector(g)
423*e1fe3e4aSElliott Hughes            c.decompile()
424*e1fe3e4aSElliott Hughes            subrs = getattr(c.private, "Subrs", [])
425*e1fe3e4aSElliott Hughes            decompiler = _DehintingT2Decompiler(
426*e1fe3e4aSElliott Hughes                css,
427*e1fe3e4aSElliott Hughes                subrs,
428*e1fe3e4aSElliott Hughes                c.globalSubrs,
429*e1fe3e4aSElliott Hughes                c.private.nominalWidthX,
430*e1fe3e4aSElliott Hughes                c.private.defaultWidthX,
431*e1fe3e4aSElliott Hughes                c.private,
432*e1fe3e4aSElliott Hughes            )
433*e1fe3e4aSElliott Hughes            decompiler.execute(c)
434*e1fe3e4aSElliott Hughes            c.width = decompiler.width
435*e1fe3e4aSElliott Hughes        for charstring in css:
436*e1fe3e4aSElliott Hughes            charstring.drop_hints()
437*e1fe3e4aSElliott Hughes        del css
438*e1fe3e4aSElliott Hughes
439*e1fe3e4aSElliott Hughes        # Drop font-wide hinting values
440*e1fe3e4aSElliott Hughes        all_privs = []
441*e1fe3e4aSElliott Hughes        if hasattr(font, "FDArray"):
442*e1fe3e4aSElliott Hughes            all_privs.extend(fd.Private for fd in font.FDArray)
443*e1fe3e4aSElliott Hughes        else:
444*e1fe3e4aSElliott Hughes            all_privs.append(font.Private)
445*e1fe3e4aSElliott Hughes        for priv in all_privs:
446*e1fe3e4aSElliott Hughes            for k in [
447*e1fe3e4aSElliott Hughes                "BlueValues",
448*e1fe3e4aSElliott Hughes                "OtherBlues",
449*e1fe3e4aSElliott Hughes                "FamilyBlues",
450*e1fe3e4aSElliott Hughes                "FamilyOtherBlues",
451*e1fe3e4aSElliott Hughes                "BlueScale",
452*e1fe3e4aSElliott Hughes                "BlueShift",
453*e1fe3e4aSElliott Hughes                "BlueFuzz",
454*e1fe3e4aSElliott Hughes                "StemSnapH",
455*e1fe3e4aSElliott Hughes                "StemSnapV",
456*e1fe3e4aSElliott Hughes                "StdHW",
457*e1fe3e4aSElliott Hughes                "StdVW",
458*e1fe3e4aSElliott Hughes                "ForceBold",
459*e1fe3e4aSElliott Hughes                "LanguageGroup",
460*e1fe3e4aSElliott Hughes                "ExpansionFactor",
461*e1fe3e4aSElliott Hughes            ]:
462*e1fe3e4aSElliott Hughes                if hasattr(priv, k):
463*e1fe3e4aSElliott Hughes                    setattr(priv, k, None)
464*e1fe3e4aSElliott Hughes    self.remove_unused_subroutines()
465*e1fe3e4aSElliott Hughes
466*e1fe3e4aSElliott Hughes
467*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF "))
468*e1fe3e4aSElliott Hughesdef remove_unused_subroutines(self):
469*e1fe3e4aSElliott Hughes    cff = self.cff
470*e1fe3e4aSElliott Hughes    for fontname in cff.keys():
471*e1fe3e4aSElliott Hughes        font = cff[fontname]
472*e1fe3e4aSElliott Hughes        cs = font.CharStrings
473*e1fe3e4aSElliott Hughes        # Renumber subroutines to remove unused ones
474*e1fe3e4aSElliott Hughes
475*e1fe3e4aSElliott Hughes        # Mark all used subroutines
476*e1fe3e4aSElliott Hughes        for g in font.charset:
477*e1fe3e4aSElliott Hughes            c, _ = cs.getItemAndSelector(g)
478*e1fe3e4aSElliott Hughes            subrs = getattr(c.private, "Subrs", [])
479*e1fe3e4aSElliott Hughes            decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private)
480*e1fe3e4aSElliott Hughes            decompiler.execute(c)
481*e1fe3e4aSElliott Hughes
482*e1fe3e4aSElliott Hughes        all_subrs = [font.GlobalSubrs]
483*e1fe3e4aSElliott Hughes        if hasattr(font, "FDArray"):
484*e1fe3e4aSElliott Hughes            all_subrs.extend(
485*e1fe3e4aSElliott Hughes                fd.Private.Subrs
486*e1fe3e4aSElliott Hughes                for fd in font.FDArray
487*e1fe3e4aSElliott Hughes                if hasattr(fd.Private, "Subrs") and fd.Private.Subrs
488*e1fe3e4aSElliott Hughes            )
489*e1fe3e4aSElliott Hughes        elif hasattr(font.Private, "Subrs") and font.Private.Subrs:
490*e1fe3e4aSElliott Hughes            all_subrs.append(font.Private.Subrs)
491*e1fe3e4aSElliott Hughes
492*e1fe3e4aSElliott Hughes        subrs = set(subrs)  # Remove duplicates
493*e1fe3e4aSElliott Hughes
494*e1fe3e4aSElliott Hughes        # Prepare
495*e1fe3e4aSElliott Hughes        for subrs in all_subrs:
496*e1fe3e4aSElliott Hughes            if not hasattr(subrs, "_used"):
497*e1fe3e4aSElliott Hughes                subrs._used = set()
498*e1fe3e4aSElliott Hughes            subrs._used = _uniq_sort(subrs._used)
499*e1fe3e4aSElliott Hughes            subrs._old_bias = psCharStrings.calcSubrBias(subrs)
500*e1fe3e4aSElliott Hughes            subrs._new_bias = psCharStrings.calcSubrBias(subrs._used)
501*e1fe3e4aSElliott Hughes
502*e1fe3e4aSElliott Hughes        # Renumber glyph charstrings
503*e1fe3e4aSElliott Hughes        for g in font.charset:
504*e1fe3e4aSElliott Hughes            c, _ = cs.getItemAndSelector(g)
505*e1fe3e4aSElliott Hughes            subrs = getattr(c.private, "Subrs", None)
506*e1fe3e4aSElliott Hughes            c.subset_subroutines(subrs, font.GlobalSubrs)
507*e1fe3e4aSElliott Hughes
508*e1fe3e4aSElliott Hughes        # Renumber subroutines themselves
509*e1fe3e4aSElliott Hughes        for subrs in all_subrs:
510*e1fe3e4aSElliott Hughes            if subrs == font.GlobalSubrs:
511*e1fe3e4aSElliott Hughes                if not hasattr(font, "FDArray") and hasattr(font.Private, "Subrs"):
512*e1fe3e4aSElliott Hughes                    local_subrs = font.Private.Subrs
513*e1fe3e4aSElliott Hughes                else:
514*e1fe3e4aSElliott Hughes                    local_subrs = None
515*e1fe3e4aSElliott Hughes            else:
516*e1fe3e4aSElliott Hughes                local_subrs = subrs
517*e1fe3e4aSElliott Hughes
518*e1fe3e4aSElliott Hughes            subrs.items = [subrs.items[i] for i in subrs._used]
519*e1fe3e4aSElliott Hughes            if hasattr(subrs, "file"):
520*e1fe3e4aSElliott Hughes                del subrs.file
521*e1fe3e4aSElliott Hughes            if hasattr(subrs, "offsets"):
522*e1fe3e4aSElliott Hughes                del subrs.offsets
523*e1fe3e4aSElliott Hughes
524*e1fe3e4aSElliott Hughes            for subr in subrs.items:
525*e1fe3e4aSElliott Hughes                subr.subset_subroutines(local_subrs, font.GlobalSubrs)
526*e1fe3e4aSElliott Hughes
527*e1fe3e4aSElliott Hughes        # Delete local SubrsIndex if empty
528*e1fe3e4aSElliott Hughes        if hasattr(font, "FDArray"):
529*e1fe3e4aSElliott Hughes            for fd in font.FDArray:
530*e1fe3e4aSElliott Hughes                _delete_empty_subrs(fd.Private)
531*e1fe3e4aSElliott Hughes        else:
532*e1fe3e4aSElliott Hughes            _delete_empty_subrs(font.Private)
533*e1fe3e4aSElliott Hughes
534*e1fe3e4aSElliott Hughes        # Cleanup
535*e1fe3e4aSElliott Hughes        for subrs in all_subrs:
536*e1fe3e4aSElliott Hughes            del subrs._used, subrs._old_bias, subrs._new_bias
537