xref: /aosp_15_r20/external/fonttools/Lib/fontTools/misc/psLib.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes, tostr
2*e1fe3e4aSElliott Hughesfrom fontTools.misc import eexec
3*e1fe3e4aSElliott Hughesfrom .psOperators import (
4*e1fe3e4aSElliott Hughes    PSOperators,
5*e1fe3e4aSElliott Hughes    ps_StandardEncoding,
6*e1fe3e4aSElliott Hughes    ps_array,
7*e1fe3e4aSElliott Hughes    ps_boolean,
8*e1fe3e4aSElliott Hughes    ps_dict,
9*e1fe3e4aSElliott Hughes    ps_integer,
10*e1fe3e4aSElliott Hughes    ps_literal,
11*e1fe3e4aSElliott Hughes    ps_mark,
12*e1fe3e4aSElliott Hughes    ps_name,
13*e1fe3e4aSElliott Hughes    ps_operator,
14*e1fe3e4aSElliott Hughes    ps_procedure,
15*e1fe3e4aSElliott Hughes    ps_procmark,
16*e1fe3e4aSElliott Hughes    ps_real,
17*e1fe3e4aSElliott Hughes    ps_string,
18*e1fe3e4aSElliott Hughes)
19*e1fe3e4aSElliott Hughesimport re
20*e1fe3e4aSElliott Hughesfrom collections.abc import Callable
21*e1fe3e4aSElliott Hughesfrom string import whitespace
22*e1fe3e4aSElliott Hughesimport logging
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott Hughes
25*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__)
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughesps_special = b"()<>[]{}%"  # / is one too, but we take care of that one differently
28*e1fe3e4aSElliott Hughes
29*e1fe3e4aSElliott HughesskipwhiteRE = re.compile(bytesjoin([b"[", whitespace, b"]*"]))
30*e1fe3e4aSElliott HughesendofthingPat = bytesjoin([b"[^][(){}<>/%", whitespace, b"]*"])
31*e1fe3e4aSElliott HughesendofthingRE = re.compile(endofthingPat)
32*e1fe3e4aSElliott HughescommentRE = re.compile(b"%[^\n\r]*")
33*e1fe3e4aSElliott Hughes
34*e1fe3e4aSElliott Hughes# XXX This not entirely correct as it doesn't allow *nested* embedded parens:
35*e1fe3e4aSElliott HughesstringPat = rb"""
36*e1fe3e4aSElliott Hughes	\(
37*e1fe3e4aSElliott Hughes		(
38*e1fe3e4aSElliott Hughes			(
39*e1fe3e4aSElliott Hughes				[^()]*   \   [()]
40*e1fe3e4aSElliott Hughes			)
41*e1fe3e4aSElliott Hughes			|
42*e1fe3e4aSElliott Hughes			(
43*e1fe3e4aSElliott Hughes				[^()]*  \(   [^()]*  \)
44*e1fe3e4aSElliott Hughes			)
45*e1fe3e4aSElliott Hughes		)*
46*e1fe3e4aSElliott Hughes		[^()]*
47*e1fe3e4aSElliott Hughes	\)
48*e1fe3e4aSElliott Hughes"""
49*e1fe3e4aSElliott HughesstringPat = b"".join(stringPat.split())
50*e1fe3e4aSElliott HughesstringRE = re.compile(stringPat)
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott HugheshexstringRE = re.compile(bytesjoin([b"<[", whitespace, b"0-9A-Fa-f]*>"]))
53*e1fe3e4aSElliott Hughes
54*e1fe3e4aSElliott Hughes
55*e1fe3e4aSElliott Hughesclass PSTokenError(Exception):
56*e1fe3e4aSElliott Hughes    pass
57*e1fe3e4aSElliott Hughes
58*e1fe3e4aSElliott Hughes
59*e1fe3e4aSElliott Hughesclass PSError(Exception):
60*e1fe3e4aSElliott Hughes    pass
61*e1fe3e4aSElliott Hughes
62*e1fe3e4aSElliott Hughes
63*e1fe3e4aSElliott Hughesclass PSTokenizer(object):
64*e1fe3e4aSElliott Hughes    def __init__(self, buf=b"", encoding="ascii"):
65*e1fe3e4aSElliott Hughes        # Force self.buf to be a byte string
66*e1fe3e4aSElliott Hughes        buf = tobytes(buf)
67*e1fe3e4aSElliott Hughes        self.buf = buf
68*e1fe3e4aSElliott Hughes        self.len = len(buf)
69*e1fe3e4aSElliott Hughes        self.pos = 0
70*e1fe3e4aSElliott Hughes        self.closed = False
71*e1fe3e4aSElliott Hughes        self.encoding = encoding
72*e1fe3e4aSElliott Hughes
73*e1fe3e4aSElliott Hughes    def read(self, n=-1):
74*e1fe3e4aSElliott Hughes        """Read at most 'n' bytes from the buffer, or less if the read
75*e1fe3e4aSElliott Hughes        hits EOF before obtaining 'n' bytes.
76*e1fe3e4aSElliott Hughes        If 'n' is negative or omitted, read all data until EOF is reached.
77*e1fe3e4aSElliott Hughes        """
78*e1fe3e4aSElliott Hughes        if self.closed:
79*e1fe3e4aSElliott Hughes            raise ValueError("I/O operation on closed file")
80*e1fe3e4aSElliott Hughes        if n is None or n < 0:
81*e1fe3e4aSElliott Hughes            newpos = self.len
82*e1fe3e4aSElliott Hughes        else:
83*e1fe3e4aSElliott Hughes            newpos = min(self.pos + n, self.len)
84*e1fe3e4aSElliott Hughes        r = self.buf[self.pos : newpos]
85*e1fe3e4aSElliott Hughes        self.pos = newpos
86*e1fe3e4aSElliott Hughes        return r
87*e1fe3e4aSElliott Hughes
88*e1fe3e4aSElliott Hughes    def close(self):
89*e1fe3e4aSElliott Hughes        if not self.closed:
90*e1fe3e4aSElliott Hughes            self.closed = True
91*e1fe3e4aSElliott Hughes            del self.buf, self.pos
92*e1fe3e4aSElliott Hughes
93*e1fe3e4aSElliott Hughes    def getnexttoken(
94*e1fe3e4aSElliott Hughes        self,
95*e1fe3e4aSElliott Hughes        # localize some stuff, for performance
96*e1fe3e4aSElliott Hughes        len=len,
97*e1fe3e4aSElliott Hughes        ps_special=ps_special,
98*e1fe3e4aSElliott Hughes        stringmatch=stringRE.match,
99*e1fe3e4aSElliott Hughes        hexstringmatch=hexstringRE.match,
100*e1fe3e4aSElliott Hughes        commentmatch=commentRE.match,
101*e1fe3e4aSElliott Hughes        endmatch=endofthingRE.match,
102*e1fe3e4aSElliott Hughes    ):
103*e1fe3e4aSElliott Hughes        self.skipwhite()
104*e1fe3e4aSElliott Hughes        if self.pos >= self.len:
105*e1fe3e4aSElliott Hughes            return None, None
106*e1fe3e4aSElliott Hughes        pos = self.pos
107*e1fe3e4aSElliott Hughes        buf = self.buf
108*e1fe3e4aSElliott Hughes        char = bytechr(byteord(buf[pos]))
109*e1fe3e4aSElliott Hughes        if char in ps_special:
110*e1fe3e4aSElliott Hughes            if char in b"{}[]":
111*e1fe3e4aSElliott Hughes                tokentype = "do_special"
112*e1fe3e4aSElliott Hughes                token = char
113*e1fe3e4aSElliott Hughes            elif char == b"%":
114*e1fe3e4aSElliott Hughes                tokentype = "do_comment"
115*e1fe3e4aSElliott Hughes                _, nextpos = commentmatch(buf, pos).span()
116*e1fe3e4aSElliott Hughes                token = buf[pos:nextpos]
117*e1fe3e4aSElliott Hughes            elif char == b"(":
118*e1fe3e4aSElliott Hughes                tokentype = "do_string"
119*e1fe3e4aSElliott Hughes                m = stringmatch(buf, pos)
120*e1fe3e4aSElliott Hughes                if m is None:
121*e1fe3e4aSElliott Hughes                    raise PSTokenError("bad string at character %d" % pos)
122*e1fe3e4aSElliott Hughes                _, nextpos = m.span()
123*e1fe3e4aSElliott Hughes                token = buf[pos:nextpos]
124*e1fe3e4aSElliott Hughes            elif char == b"<":
125*e1fe3e4aSElliott Hughes                tokentype = "do_hexstring"
126*e1fe3e4aSElliott Hughes                m = hexstringmatch(buf, pos)
127*e1fe3e4aSElliott Hughes                if m is None:
128*e1fe3e4aSElliott Hughes                    raise PSTokenError("bad hexstring at character %d" % pos)
129*e1fe3e4aSElliott Hughes                _, nextpos = m.span()
130*e1fe3e4aSElliott Hughes                token = buf[pos:nextpos]
131*e1fe3e4aSElliott Hughes            else:
132*e1fe3e4aSElliott Hughes                raise PSTokenError("bad token at character %d" % pos)
133*e1fe3e4aSElliott Hughes        else:
134*e1fe3e4aSElliott Hughes            if char == b"/":
135*e1fe3e4aSElliott Hughes                tokentype = "do_literal"
136*e1fe3e4aSElliott Hughes                m = endmatch(buf, pos + 1)
137*e1fe3e4aSElliott Hughes            else:
138*e1fe3e4aSElliott Hughes                tokentype = ""
139*e1fe3e4aSElliott Hughes                m = endmatch(buf, pos)
140*e1fe3e4aSElliott Hughes            if m is None:
141*e1fe3e4aSElliott Hughes                raise PSTokenError("bad token at character %d" % pos)
142*e1fe3e4aSElliott Hughes            _, nextpos = m.span()
143*e1fe3e4aSElliott Hughes            token = buf[pos:nextpos]
144*e1fe3e4aSElliott Hughes        self.pos = pos + len(token)
145*e1fe3e4aSElliott Hughes        token = tostr(token, encoding=self.encoding)
146*e1fe3e4aSElliott Hughes        return tokentype, token
147*e1fe3e4aSElliott Hughes
148*e1fe3e4aSElliott Hughes    def skipwhite(self, whitematch=skipwhiteRE.match):
149*e1fe3e4aSElliott Hughes        _, nextpos = whitematch(self.buf, self.pos).span()
150*e1fe3e4aSElliott Hughes        self.pos = nextpos
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes    def starteexec(self):
153*e1fe3e4aSElliott Hughes        self.pos = self.pos + 1
154*e1fe3e4aSElliott Hughes        self.dirtybuf = self.buf[self.pos :]
155*e1fe3e4aSElliott Hughes        self.buf, R = eexec.decrypt(self.dirtybuf, 55665)
156*e1fe3e4aSElliott Hughes        self.len = len(self.buf)
157*e1fe3e4aSElliott Hughes        self.pos = 4
158*e1fe3e4aSElliott Hughes
159*e1fe3e4aSElliott Hughes    def stopeexec(self):
160*e1fe3e4aSElliott Hughes        if not hasattr(self, "dirtybuf"):
161*e1fe3e4aSElliott Hughes            return
162*e1fe3e4aSElliott Hughes        self.buf = self.dirtybuf
163*e1fe3e4aSElliott Hughes        del self.dirtybuf
164*e1fe3e4aSElliott Hughes
165*e1fe3e4aSElliott Hughes
166*e1fe3e4aSElliott Hughesclass PSInterpreter(PSOperators):
167*e1fe3e4aSElliott Hughes    def __init__(self, encoding="ascii"):
168*e1fe3e4aSElliott Hughes        systemdict = {}
169*e1fe3e4aSElliott Hughes        userdict = {}
170*e1fe3e4aSElliott Hughes        self.encoding = encoding
171*e1fe3e4aSElliott Hughes        self.dictstack = [systemdict, userdict]
172*e1fe3e4aSElliott Hughes        self.stack = []
173*e1fe3e4aSElliott Hughes        self.proclevel = 0
174*e1fe3e4aSElliott Hughes        self.procmark = ps_procmark()
175*e1fe3e4aSElliott Hughes        self.fillsystemdict()
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes    def fillsystemdict(self):
178*e1fe3e4aSElliott Hughes        systemdict = self.dictstack[0]
179*e1fe3e4aSElliott Hughes        systemdict["["] = systemdict["mark"] = self.mark = ps_mark()
180*e1fe3e4aSElliott Hughes        systemdict["]"] = ps_operator("]", self.do_makearray)
181*e1fe3e4aSElliott Hughes        systemdict["true"] = ps_boolean(1)
182*e1fe3e4aSElliott Hughes        systemdict["false"] = ps_boolean(0)
183*e1fe3e4aSElliott Hughes        systemdict["StandardEncoding"] = ps_array(ps_StandardEncoding)
184*e1fe3e4aSElliott Hughes        systemdict["FontDirectory"] = ps_dict({})
185*e1fe3e4aSElliott Hughes        self.suckoperators(systemdict, self.__class__)
186*e1fe3e4aSElliott Hughes
187*e1fe3e4aSElliott Hughes    def suckoperators(self, systemdict, klass):
188*e1fe3e4aSElliott Hughes        for name in dir(klass):
189*e1fe3e4aSElliott Hughes            attr = getattr(self, name)
190*e1fe3e4aSElliott Hughes            if isinstance(attr, Callable) and name[:3] == "ps_":
191*e1fe3e4aSElliott Hughes                name = name[3:]
192*e1fe3e4aSElliott Hughes                systemdict[name] = ps_operator(name, attr)
193*e1fe3e4aSElliott Hughes        for baseclass in klass.__bases__:
194*e1fe3e4aSElliott Hughes            self.suckoperators(systemdict, baseclass)
195*e1fe3e4aSElliott Hughes
196*e1fe3e4aSElliott Hughes    def interpret(self, data, getattr=getattr):
197*e1fe3e4aSElliott Hughes        tokenizer = self.tokenizer = PSTokenizer(data, self.encoding)
198*e1fe3e4aSElliott Hughes        getnexttoken = tokenizer.getnexttoken
199*e1fe3e4aSElliott Hughes        do_token = self.do_token
200*e1fe3e4aSElliott Hughes        handle_object = self.handle_object
201*e1fe3e4aSElliott Hughes        try:
202*e1fe3e4aSElliott Hughes            while 1:
203*e1fe3e4aSElliott Hughes                tokentype, token = getnexttoken()
204*e1fe3e4aSElliott Hughes                if not token:
205*e1fe3e4aSElliott Hughes                    break
206*e1fe3e4aSElliott Hughes                if tokentype:
207*e1fe3e4aSElliott Hughes                    handler = getattr(self, tokentype)
208*e1fe3e4aSElliott Hughes                    object = handler(token)
209*e1fe3e4aSElliott Hughes                else:
210*e1fe3e4aSElliott Hughes                    object = do_token(token)
211*e1fe3e4aSElliott Hughes                if object is not None:
212*e1fe3e4aSElliott Hughes                    handle_object(object)
213*e1fe3e4aSElliott Hughes            tokenizer.close()
214*e1fe3e4aSElliott Hughes            self.tokenizer = None
215*e1fe3e4aSElliott Hughes        except:
216*e1fe3e4aSElliott Hughes            if self.tokenizer is not None:
217*e1fe3e4aSElliott Hughes                log.debug(
218*e1fe3e4aSElliott Hughes                    "ps error:\n"
219*e1fe3e4aSElliott Hughes                    "- - - - - - -\n"
220*e1fe3e4aSElliott Hughes                    "%s\n"
221*e1fe3e4aSElliott Hughes                    ">>>\n"
222*e1fe3e4aSElliott Hughes                    "%s\n"
223*e1fe3e4aSElliott Hughes                    "- - - - - - -",
224*e1fe3e4aSElliott Hughes                    self.tokenizer.buf[self.tokenizer.pos - 50 : self.tokenizer.pos],
225*e1fe3e4aSElliott Hughes                    self.tokenizer.buf[self.tokenizer.pos : self.tokenizer.pos + 50],
226*e1fe3e4aSElliott Hughes                )
227*e1fe3e4aSElliott Hughes            raise
228*e1fe3e4aSElliott Hughes
229*e1fe3e4aSElliott Hughes    def handle_object(self, object):
230*e1fe3e4aSElliott Hughes        if not (self.proclevel or object.literal or object.type == "proceduretype"):
231*e1fe3e4aSElliott Hughes            if object.type != "operatortype":
232*e1fe3e4aSElliott Hughes                object = self.resolve_name(object.value)
233*e1fe3e4aSElliott Hughes            if object.literal:
234*e1fe3e4aSElliott Hughes                self.push(object)
235*e1fe3e4aSElliott Hughes            else:
236*e1fe3e4aSElliott Hughes                if object.type == "proceduretype":
237*e1fe3e4aSElliott Hughes                    self.call_procedure(object)
238*e1fe3e4aSElliott Hughes                else:
239*e1fe3e4aSElliott Hughes                    object.function()
240*e1fe3e4aSElliott Hughes        else:
241*e1fe3e4aSElliott Hughes            self.push(object)
242*e1fe3e4aSElliott Hughes
243*e1fe3e4aSElliott Hughes    def call_procedure(self, proc):
244*e1fe3e4aSElliott Hughes        handle_object = self.handle_object
245*e1fe3e4aSElliott Hughes        for item in proc.value:
246*e1fe3e4aSElliott Hughes            handle_object(item)
247*e1fe3e4aSElliott Hughes
248*e1fe3e4aSElliott Hughes    def resolve_name(self, name):
249*e1fe3e4aSElliott Hughes        dictstack = self.dictstack
250*e1fe3e4aSElliott Hughes        for i in range(len(dictstack) - 1, -1, -1):
251*e1fe3e4aSElliott Hughes            if name in dictstack[i]:
252*e1fe3e4aSElliott Hughes                return dictstack[i][name]
253*e1fe3e4aSElliott Hughes        raise PSError("name error: " + str(name))
254*e1fe3e4aSElliott Hughes
255*e1fe3e4aSElliott Hughes    def do_token(
256*e1fe3e4aSElliott Hughes        self,
257*e1fe3e4aSElliott Hughes        token,
258*e1fe3e4aSElliott Hughes        int=int,
259*e1fe3e4aSElliott Hughes        float=float,
260*e1fe3e4aSElliott Hughes        ps_name=ps_name,
261*e1fe3e4aSElliott Hughes        ps_integer=ps_integer,
262*e1fe3e4aSElliott Hughes        ps_real=ps_real,
263*e1fe3e4aSElliott Hughes    ):
264*e1fe3e4aSElliott Hughes        try:
265*e1fe3e4aSElliott Hughes            num = int(token)
266*e1fe3e4aSElliott Hughes        except (ValueError, OverflowError):
267*e1fe3e4aSElliott Hughes            try:
268*e1fe3e4aSElliott Hughes                num = float(token)
269*e1fe3e4aSElliott Hughes            except (ValueError, OverflowError):
270*e1fe3e4aSElliott Hughes                if "#" in token:
271*e1fe3e4aSElliott Hughes                    hashpos = token.find("#")
272*e1fe3e4aSElliott Hughes                    try:
273*e1fe3e4aSElliott Hughes                        base = int(token[:hashpos])
274*e1fe3e4aSElliott Hughes                        num = int(token[hashpos + 1 :], base)
275*e1fe3e4aSElliott Hughes                    except (ValueError, OverflowError):
276*e1fe3e4aSElliott Hughes                        return ps_name(token)
277*e1fe3e4aSElliott Hughes                    else:
278*e1fe3e4aSElliott Hughes                        return ps_integer(num)
279*e1fe3e4aSElliott Hughes                else:
280*e1fe3e4aSElliott Hughes                    return ps_name(token)
281*e1fe3e4aSElliott Hughes            else:
282*e1fe3e4aSElliott Hughes                return ps_real(num)
283*e1fe3e4aSElliott Hughes        else:
284*e1fe3e4aSElliott Hughes            return ps_integer(num)
285*e1fe3e4aSElliott Hughes
286*e1fe3e4aSElliott Hughes    def do_comment(self, token):
287*e1fe3e4aSElliott Hughes        pass
288*e1fe3e4aSElliott Hughes
289*e1fe3e4aSElliott Hughes    def do_literal(self, token):
290*e1fe3e4aSElliott Hughes        return ps_literal(token[1:])
291*e1fe3e4aSElliott Hughes
292*e1fe3e4aSElliott Hughes    def do_string(self, token):
293*e1fe3e4aSElliott Hughes        return ps_string(token[1:-1])
294*e1fe3e4aSElliott Hughes
295*e1fe3e4aSElliott Hughes    def do_hexstring(self, token):
296*e1fe3e4aSElliott Hughes        hexStr = "".join(token[1:-1].split())
297*e1fe3e4aSElliott Hughes        if len(hexStr) % 2:
298*e1fe3e4aSElliott Hughes            hexStr = hexStr + "0"
299*e1fe3e4aSElliott Hughes        cleanstr = []
300*e1fe3e4aSElliott Hughes        for i in range(0, len(hexStr), 2):
301*e1fe3e4aSElliott Hughes            cleanstr.append(chr(int(hexStr[i : i + 2], 16)))
302*e1fe3e4aSElliott Hughes        cleanstr = "".join(cleanstr)
303*e1fe3e4aSElliott Hughes        return ps_string(cleanstr)
304*e1fe3e4aSElliott Hughes
305*e1fe3e4aSElliott Hughes    def do_special(self, token):
306*e1fe3e4aSElliott Hughes        if token == "{":
307*e1fe3e4aSElliott Hughes            self.proclevel = self.proclevel + 1
308*e1fe3e4aSElliott Hughes            return self.procmark
309*e1fe3e4aSElliott Hughes        elif token == "}":
310*e1fe3e4aSElliott Hughes            proc = []
311*e1fe3e4aSElliott Hughes            while 1:
312*e1fe3e4aSElliott Hughes                topobject = self.pop()
313*e1fe3e4aSElliott Hughes                if topobject == self.procmark:
314*e1fe3e4aSElliott Hughes                    break
315*e1fe3e4aSElliott Hughes                proc.append(topobject)
316*e1fe3e4aSElliott Hughes            self.proclevel = self.proclevel - 1
317*e1fe3e4aSElliott Hughes            proc.reverse()
318*e1fe3e4aSElliott Hughes            return ps_procedure(proc)
319*e1fe3e4aSElliott Hughes        elif token == "[":
320*e1fe3e4aSElliott Hughes            return self.mark
321*e1fe3e4aSElliott Hughes        elif token == "]":
322*e1fe3e4aSElliott Hughes            return ps_name("]")
323*e1fe3e4aSElliott Hughes        else:
324*e1fe3e4aSElliott Hughes            raise PSTokenError("huh?")
325*e1fe3e4aSElliott Hughes
326*e1fe3e4aSElliott Hughes    def push(self, object):
327*e1fe3e4aSElliott Hughes        self.stack.append(object)
328*e1fe3e4aSElliott Hughes
329*e1fe3e4aSElliott Hughes    def pop(self, *types):
330*e1fe3e4aSElliott Hughes        stack = self.stack
331*e1fe3e4aSElliott Hughes        if not stack:
332*e1fe3e4aSElliott Hughes            raise PSError("stack underflow")
333*e1fe3e4aSElliott Hughes        object = stack[-1]
334*e1fe3e4aSElliott Hughes        if types:
335*e1fe3e4aSElliott Hughes            if object.type not in types:
336*e1fe3e4aSElliott Hughes                raise PSError(
337*e1fe3e4aSElliott Hughes                    "typecheck, expected %s, found %s" % (repr(types), object.type)
338*e1fe3e4aSElliott Hughes                )
339*e1fe3e4aSElliott Hughes        del stack[-1]
340*e1fe3e4aSElliott Hughes        return object
341*e1fe3e4aSElliott Hughes
342*e1fe3e4aSElliott Hughes    def do_makearray(self):
343*e1fe3e4aSElliott Hughes        array = []
344*e1fe3e4aSElliott Hughes        while 1:
345*e1fe3e4aSElliott Hughes            topobject = self.pop()
346*e1fe3e4aSElliott Hughes            if topobject == self.mark:
347*e1fe3e4aSElliott Hughes                break
348*e1fe3e4aSElliott Hughes            array.append(topobject)
349*e1fe3e4aSElliott Hughes        array.reverse()
350*e1fe3e4aSElliott Hughes        self.push(ps_array(array))
351*e1fe3e4aSElliott Hughes
352*e1fe3e4aSElliott Hughes    def close(self):
353*e1fe3e4aSElliott Hughes        """Remove circular references."""
354*e1fe3e4aSElliott Hughes        del self.stack
355*e1fe3e4aSElliott Hughes        del self.dictstack
356*e1fe3e4aSElliott Hughes
357*e1fe3e4aSElliott Hughes
358*e1fe3e4aSElliott Hughesdef unpack_item(item):
359*e1fe3e4aSElliott Hughes    tp = type(item.value)
360*e1fe3e4aSElliott Hughes    if tp == dict:
361*e1fe3e4aSElliott Hughes        newitem = {}
362*e1fe3e4aSElliott Hughes        for key, value in item.value.items():
363*e1fe3e4aSElliott Hughes            newitem[key] = unpack_item(value)
364*e1fe3e4aSElliott Hughes    elif tp == list:
365*e1fe3e4aSElliott Hughes        newitem = [None] * len(item.value)
366*e1fe3e4aSElliott Hughes        for i in range(len(item.value)):
367*e1fe3e4aSElliott Hughes            newitem[i] = unpack_item(item.value[i])
368*e1fe3e4aSElliott Hughes        if item.type == "proceduretype":
369*e1fe3e4aSElliott Hughes            newitem = tuple(newitem)
370*e1fe3e4aSElliott Hughes    else:
371*e1fe3e4aSElliott Hughes        newitem = item.value
372*e1fe3e4aSElliott Hughes    return newitem
373*e1fe3e4aSElliott Hughes
374*e1fe3e4aSElliott Hughes
375*e1fe3e4aSElliott Hughesdef suckfont(data, encoding="ascii"):
376*e1fe3e4aSElliott Hughes    m = re.search(rb"/FontName\s+/([^ \t\n\r]+)\s+def", data)
377*e1fe3e4aSElliott Hughes    if m:
378*e1fe3e4aSElliott Hughes        fontName = m.group(1)
379*e1fe3e4aSElliott Hughes        fontName = fontName.decode()
380*e1fe3e4aSElliott Hughes    else:
381*e1fe3e4aSElliott Hughes        fontName = None
382*e1fe3e4aSElliott Hughes    interpreter = PSInterpreter(encoding=encoding)
383*e1fe3e4aSElliott Hughes    interpreter.interpret(
384*e1fe3e4aSElliott Hughes        b"/Helvetica 4 dict dup /Encoding StandardEncoding put definefont pop"
385*e1fe3e4aSElliott Hughes    )
386*e1fe3e4aSElliott Hughes    interpreter.interpret(data)
387*e1fe3e4aSElliott Hughes    fontdir = interpreter.dictstack[0]["FontDirectory"].value
388*e1fe3e4aSElliott Hughes    if fontName in fontdir:
389*e1fe3e4aSElliott Hughes        rawfont = fontdir[fontName]
390*e1fe3e4aSElliott Hughes    else:
391*e1fe3e4aSElliott Hughes        # fall back, in case fontName wasn't found
392*e1fe3e4aSElliott Hughes        fontNames = list(fontdir.keys())
393*e1fe3e4aSElliott Hughes        if len(fontNames) > 1:
394*e1fe3e4aSElliott Hughes            fontNames.remove("Helvetica")
395*e1fe3e4aSElliott Hughes        fontNames.sort()
396*e1fe3e4aSElliott Hughes        rawfont = fontdir[fontNames[0]]
397*e1fe3e4aSElliott Hughes    interpreter.close()
398*e1fe3e4aSElliott Hughes    return unpack_item(rawfont)
399