xref: /aosp_15_r20/external/fonttools/Lib/fontTools/misc/psCharStrings.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1"""psCharStrings.py -- module implementing various kinds of CharStrings:
2CFF dictionary data and Type1/Type2 CharStrings.
3"""
4
5from fontTools.misc.fixedTools import (
6    fixedToFloat,
7    floatToFixed,
8    floatToFixedToStr,
9    strToFixedToFloat,
10)
11from fontTools.misc.textTools import bytechr, byteord, bytesjoin, strjoin
12from fontTools.pens.boundsPen import BoundsPen
13import struct
14import logging
15
16
17log = logging.getLogger(__name__)
18
19
20def read_operator(self, b0, data, index):
21    if b0 == 12:
22        op = (b0, byteord(data[index]))
23        index = index + 1
24    else:
25        op = b0
26    try:
27        operator = self.operators[op]
28    except KeyError:
29        return None, index
30    value = self.handle_operator(operator)
31    return value, index
32
33
34def read_byte(self, b0, data, index):
35    return b0 - 139, index
36
37
38def read_smallInt1(self, b0, data, index):
39    b1 = byteord(data[index])
40    return (b0 - 247) * 256 + b1 + 108, index + 1
41
42
43def read_smallInt2(self, b0, data, index):
44    b1 = byteord(data[index])
45    return -(b0 - 251) * 256 - b1 - 108, index + 1
46
47
48def read_shortInt(self, b0, data, index):
49    (value,) = struct.unpack(">h", data[index : index + 2])
50    return value, index + 2
51
52
53def read_longInt(self, b0, data, index):
54    (value,) = struct.unpack(">l", data[index : index + 4])
55    return value, index + 4
56
57
58def read_fixed1616(self, b0, data, index):
59    (value,) = struct.unpack(">l", data[index : index + 4])
60    return fixedToFloat(value, precisionBits=16), index + 4
61
62
63def read_reserved(self, b0, data, index):
64    assert NotImplementedError
65    return NotImplemented, index
66
67
68def read_realNumber(self, b0, data, index):
69    number = ""
70    while True:
71        b = byteord(data[index])
72        index = index + 1
73        nibble0 = (b & 0xF0) >> 4
74        nibble1 = b & 0x0F
75        if nibble0 == 0xF:
76            break
77        number = number + realNibbles[nibble0]
78        if nibble1 == 0xF:
79            break
80        number = number + realNibbles[nibble1]
81    return float(number), index
82
83
84t1OperandEncoding = [None] * 256
85t1OperandEncoding[0:32] = (32) * [read_operator]
86t1OperandEncoding[32:247] = (247 - 32) * [read_byte]
87t1OperandEncoding[247:251] = (251 - 247) * [read_smallInt1]
88t1OperandEncoding[251:255] = (255 - 251) * [read_smallInt2]
89t1OperandEncoding[255] = read_longInt
90assert len(t1OperandEncoding) == 256
91
92t2OperandEncoding = t1OperandEncoding[:]
93t2OperandEncoding[28] = read_shortInt
94t2OperandEncoding[255] = read_fixed1616
95
96cffDictOperandEncoding = t2OperandEncoding[:]
97cffDictOperandEncoding[29] = read_longInt
98cffDictOperandEncoding[30] = read_realNumber
99cffDictOperandEncoding[255] = read_reserved
100
101
102realNibbles = [
103    "0",
104    "1",
105    "2",
106    "3",
107    "4",
108    "5",
109    "6",
110    "7",
111    "8",
112    "9",
113    ".",
114    "E",
115    "E-",
116    None,
117    "-",
118]
119realNibblesDict = {v: i for i, v in enumerate(realNibbles)}
120
121maxOpStack = 193
122
123
124def buildOperatorDict(operatorList):
125    oper = {}
126    opc = {}
127    for item in operatorList:
128        if len(item) == 2:
129            oper[item[0]] = item[1]
130        else:
131            oper[item[0]] = item[1:]
132        if isinstance(item[0], tuple):
133            opc[item[1]] = item[0]
134        else:
135            opc[item[1]] = (item[0],)
136    return oper, opc
137
138
139t2Operators = [
140    # 	opcode		name
141    (1, "hstem"),
142    (3, "vstem"),
143    (4, "vmoveto"),
144    (5, "rlineto"),
145    (6, "hlineto"),
146    (7, "vlineto"),
147    (8, "rrcurveto"),
148    (10, "callsubr"),
149    (11, "return"),
150    (14, "endchar"),
151    (15, "vsindex"),
152    (16, "blend"),
153    (18, "hstemhm"),
154    (19, "hintmask"),
155    (20, "cntrmask"),
156    (21, "rmoveto"),
157    (22, "hmoveto"),
158    (23, "vstemhm"),
159    (24, "rcurveline"),
160    (25, "rlinecurve"),
161    (26, "vvcurveto"),
162    (27, "hhcurveto"),
163    # 	(28,		'shortint'),  # not really an operator
164    (29, "callgsubr"),
165    (30, "vhcurveto"),
166    (31, "hvcurveto"),
167    ((12, 0), "ignore"),  # dotsection. Yes, there a few very early OTF/CFF
168    # fonts with this deprecated operator. Just ignore it.
169    ((12, 3), "and"),
170    ((12, 4), "or"),
171    ((12, 5), "not"),
172    ((12, 8), "store"),
173    ((12, 9), "abs"),
174    ((12, 10), "add"),
175    ((12, 11), "sub"),
176    ((12, 12), "div"),
177    ((12, 13), "load"),
178    ((12, 14), "neg"),
179    ((12, 15), "eq"),
180    ((12, 18), "drop"),
181    ((12, 20), "put"),
182    ((12, 21), "get"),
183    ((12, 22), "ifelse"),
184    ((12, 23), "random"),
185    ((12, 24), "mul"),
186    ((12, 26), "sqrt"),
187    ((12, 27), "dup"),
188    ((12, 28), "exch"),
189    ((12, 29), "index"),
190    ((12, 30), "roll"),
191    ((12, 34), "hflex"),
192    ((12, 35), "flex"),
193    ((12, 36), "hflex1"),
194    ((12, 37), "flex1"),
195]
196
197
198def getIntEncoder(format):
199    if format == "cff":
200        twoByteOp = bytechr(28)
201        fourByteOp = bytechr(29)
202    elif format == "t1":
203        twoByteOp = None
204        fourByteOp = bytechr(255)
205    else:
206        assert format == "t2"
207        twoByteOp = bytechr(28)
208        fourByteOp = None
209
210    def encodeInt(
211        value,
212        fourByteOp=fourByteOp,
213        bytechr=bytechr,
214        pack=struct.pack,
215        unpack=struct.unpack,
216        twoByteOp=twoByteOp,
217    ):
218        if -107 <= value <= 107:
219            code = bytechr(value + 139)
220        elif 108 <= value <= 1131:
221            value = value - 108
222            code = bytechr((value >> 8) + 247) + bytechr(value & 0xFF)
223        elif -1131 <= value <= -108:
224            value = -value - 108
225            code = bytechr((value >> 8) + 251) + bytechr(value & 0xFF)
226        elif twoByteOp is not None and -32768 <= value <= 32767:
227            code = twoByteOp + pack(">h", value)
228        elif fourByteOp is None:
229            # Backwards compatible hack: due to a previous bug in FontTools,
230            # 16.16 fixed numbers were written out as 4-byte ints. When
231            # these numbers were small, they were wrongly written back as
232            # small ints instead of 4-byte ints, breaking round-tripping.
233            # This here workaround doesn't do it any better, since we can't
234            # distinguish anymore between small ints that were supposed to
235            # be small fixed numbers and small ints that were just small
236            # ints. Hence the warning.
237            log.warning(
238                "4-byte T2 number got passed to the "
239                "IntType handler. This should happen only when reading in "
240                "old XML files.\n"
241            )
242            code = bytechr(255) + pack(">l", value)
243        else:
244            code = fourByteOp + pack(">l", value)
245        return code
246
247    return encodeInt
248
249
250encodeIntCFF = getIntEncoder("cff")
251encodeIntT1 = getIntEncoder("t1")
252encodeIntT2 = getIntEncoder("t2")
253
254
255def encodeFixed(f, pack=struct.pack):
256    """For T2 only"""
257    value = floatToFixed(f, precisionBits=16)
258    if value & 0xFFFF == 0:  # check if the fractional part is zero
259        return encodeIntT2(value >> 16)  # encode only the integer part
260    else:
261        return b"\xff" + pack(">l", value)  # encode the entire fixed point value
262
263
264realZeroBytes = bytechr(30) + bytechr(0xF)
265
266
267def encodeFloat(f):
268    # For CFF only, used in cffLib
269    if f == 0.0:  # 0.0 == +0.0 == -0.0
270        return realZeroBytes
271    # Note: 14 decimal digits seems to be the limitation for CFF real numbers
272    # in macOS. However, we use 8 here to match the implementation of AFDKO.
273    s = "%.8G" % f
274    if s[:2] == "0.":
275        s = s[1:]
276    elif s[:3] == "-0.":
277        s = "-" + s[2:]
278    nibbles = []
279    while s:
280        c = s[0]
281        s = s[1:]
282        if c == "E":
283            c2 = s[:1]
284            if c2 == "-":
285                s = s[1:]
286                c = "E-"
287            elif c2 == "+":
288                s = s[1:]
289        nibbles.append(realNibblesDict[c])
290    nibbles.append(0xF)
291    if len(nibbles) % 2:
292        nibbles.append(0xF)
293    d = bytechr(30)
294    for i in range(0, len(nibbles), 2):
295        d = d + bytechr(nibbles[i] << 4 | nibbles[i + 1])
296    return d
297
298
299class CharStringCompileError(Exception):
300    pass
301
302
303class SimpleT2Decompiler(object):
304    def __init__(self, localSubrs, globalSubrs, private=None, blender=None):
305        self.localSubrs = localSubrs
306        self.localBias = calcSubrBias(localSubrs)
307        self.globalSubrs = globalSubrs
308        self.globalBias = calcSubrBias(globalSubrs)
309        self.private = private
310        self.blender = blender
311        self.reset()
312
313    def reset(self):
314        self.callingStack = []
315        self.operandStack = []
316        self.hintCount = 0
317        self.hintMaskBytes = 0
318        self.numRegions = 0
319        self.vsIndex = 0
320
321    def execute(self, charString):
322        self.callingStack.append(charString)
323        needsDecompilation = charString.needsDecompilation()
324        if needsDecompilation:
325            program = []
326            pushToProgram = program.append
327        else:
328            pushToProgram = lambda x: None
329        pushToStack = self.operandStack.append
330        index = 0
331        while True:
332            token, isOperator, index = charString.getToken(index)
333            if token is None:
334                break  # we're done!
335            pushToProgram(token)
336            if isOperator:
337                handlerName = "op_" + token
338                handler = getattr(self, handlerName, None)
339                if handler is not None:
340                    rv = handler(index)
341                    if rv:
342                        hintMaskBytes, index = rv
343                        pushToProgram(hintMaskBytes)
344                else:
345                    self.popall()
346            else:
347                pushToStack(token)
348        if needsDecompilation:
349            charString.setProgram(program)
350        del self.callingStack[-1]
351
352    def pop(self):
353        value = self.operandStack[-1]
354        del self.operandStack[-1]
355        return value
356
357    def popall(self):
358        stack = self.operandStack[:]
359        self.operandStack[:] = []
360        return stack
361
362    def push(self, value):
363        self.operandStack.append(value)
364
365    def op_return(self, index):
366        if self.operandStack:
367            pass
368
369    def op_endchar(self, index):
370        pass
371
372    def op_ignore(self, index):
373        pass
374
375    def op_callsubr(self, index):
376        subrIndex = self.pop()
377        subr = self.localSubrs[subrIndex + self.localBias]
378        self.execute(subr)
379
380    def op_callgsubr(self, index):
381        subrIndex = self.pop()
382        subr = self.globalSubrs[subrIndex + self.globalBias]
383        self.execute(subr)
384
385    def op_hstem(self, index):
386        self.countHints()
387
388    def op_vstem(self, index):
389        self.countHints()
390
391    def op_hstemhm(self, index):
392        self.countHints()
393
394    def op_vstemhm(self, index):
395        self.countHints()
396
397    def op_hintmask(self, index):
398        if not self.hintMaskBytes:
399            self.countHints()
400            self.hintMaskBytes = (self.hintCount + 7) // 8
401        hintMaskBytes, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes)
402        return hintMaskBytes, index
403
404    op_cntrmask = op_hintmask
405
406    def countHints(self):
407        args = self.popall()
408        self.hintCount = self.hintCount + len(args) // 2
409
410    # misc
411    def op_and(self, index):
412        raise NotImplementedError
413
414    def op_or(self, index):
415        raise NotImplementedError
416
417    def op_not(self, index):
418        raise NotImplementedError
419
420    def op_store(self, index):
421        raise NotImplementedError
422
423    def op_abs(self, index):
424        raise NotImplementedError
425
426    def op_add(self, index):
427        raise NotImplementedError
428
429    def op_sub(self, index):
430        raise NotImplementedError
431
432    def op_div(self, index):
433        raise NotImplementedError
434
435    def op_load(self, index):
436        raise NotImplementedError
437
438    def op_neg(self, index):
439        raise NotImplementedError
440
441    def op_eq(self, index):
442        raise NotImplementedError
443
444    def op_drop(self, index):
445        raise NotImplementedError
446
447    def op_put(self, index):
448        raise NotImplementedError
449
450    def op_get(self, index):
451        raise NotImplementedError
452
453    def op_ifelse(self, index):
454        raise NotImplementedError
455
456    def op_random(self, index):
457        raise NotImplementedError
458
459    def op_mul(self, index):
460        raise NotImplementedError
461
462    def op_sqrt(self, index):
463        raise NotImplementedError
464
465    def op_dup(self, index):
466        raise NotImplementedError
467
468    def op_exch(self, index):
469        raise NotImplementedError
470
471    def op_index(self, index):
472        raise NotImplementedError
473
474    def op_roll(self, index):
475        raise NotImplementedError
476
477    def op_blend(self, index):
478        if self.numRegions == 0:
479            self.numRegions = self.private.getNumRegions()
480        numBlends = self.pop()
481        numOps = numBlends * (self.numRegions + 1)
482        if self.blender is None:
483            del self.operandStack[
484                -(numOps - numBlends) :
485            ]  # Leave the default operands on the stack.
486        else:
487            argi = len(self.operandStack) - numOps
488            end_args = tuplei = argi + numBlends
489            while argi < end_args:
490                next_ti = tuplei + self.numRegions
491                deltas = self.operandStack[tuplei:next_ti]
492                delta = self.blender(self.vsIndex, deltas)
493                self.operandStack[argi] += delta
494                tuplei = next_ti
495                argi += 1
496            self.operandStack[end_args:] = []
497
498    def op_vsindex(self, index):
499        vi = self.pop()
500        self.vsIndex = vi
501        self.numRegions = self.private.getNumRegions(vi)
502
503
504t1Operators = [
505    # 	opcode		name
506    (1, "hstem"),
507    (3, "vstem"),
508    (4, "vmoveto"),
509    (5, "rlineto"),
510    (6, "hlineto"),
511    (7, "vlineto"),
512    (8, "rrcurveto"),
513    (9, "closepath"),
514    (10, "callsubr"),
515    (11, "return"),
516    (13, "hsbw"),
517    (14, "endchar"),
518    (21, "rmoveto"),
519    (22, "hmoveto"),
520    (30, "vhcurveto"),
521    (31, "hvcurveto"),
522    ((12, 0), "dotsection"),
523    ((12, 1), "vstem3"),
524    ((12, 2), "hstem3"),
525    ((12, 6), "seac"),
526    ((12, 7), "sbw"),
527    ((12, 12), "div"),
528    ((12, 16), "callothersubr"),
529    ((12, 17), "pop"),
530    ((12, 33), "setcurrentpoint"),
531]
532
533
534class T2WidthExtractor(SimpleT2Decompiler):
535    def __init__(
536        self,
537        localSubrs,
538        globalSubrs,
539        nominalWidthX,
540        defaultWidthX,
541        private=None,
542        blender=None,
543    ):
544        SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private, blender)
545        self.nominalWidthX = nominalWidthX
546        self.defaultWidthX = defaultWidthX
547
548    def reset(self):
549        SimpleT2Decompiler.reset(self)
550        self.gotWidth = 0
551        self.width = 0
552
553    def popallWidth(self, evenOdd=0):
554        args = self.popall()
555        if not self.gotWidth:
556            if evenOdd ^ (len(args) % 2):
557                # For CFF2 charstrings, this should never happen
558                assert (
559                    self.defaultWidthX is not None
560                ), "CFF2 CharStrings must not have an initial width value"
561                self.width = self.nominalWidthX + args[0]
562                args = args[1:]
563            else:
564                self.width = self.defaultWidthX
565            self.gotWidth = 1
566        return args
567
568    def countHints(self):
569        args = self.popallWidth()
570        self.hintCount = self.hintCount + len(args) // 2
571
572    def op_rmoveto(self, index):
573        self.popallWidth()
574
575    def op_hmoveto(self, index):
576        self.popallWidth(1)
577
578    def op_vmoveto(self, index):
579        self.popallWidth(1)
580
581    def op_endchar(self, index):
582        self.popallWidth()
583
584
585class T2OutlineExtractor(T2WidthExtractor):
586    def __init__(
587        self,
588        pen,
589        localSubrs,
590        globalSubrs,
591        nominalWidthX,
592        defaultWidthX,
593        private=None,
594        blender=None,
595    ):
596        T2WidthExtractor.__init__(
597            self,
598            localSubrs,
599            globalSubrs,
600            nominalWidthX,
601            defaultWidthX,
602            private,
603            blender,
604        )
605        self.pen = pen
606        self.subrLevel = 0
607
608    def reset(self):
609        T2WidthExtractor.reset(self)
610        self.currentPoint = (0, 0)
611        self.sawMoveTo = 0
612        self.subrLevel = 0
613
614    def execute(self, charString):
615        self.subrLevel += 1
616        super().execute(charString)
617        self.subrLevel -= 1
618        if self.subrLevel == 0:
619            self.endPath()
620
621    def _nextPoint(self, point):
622        x, y = self.currentPoint
623        point = x + point[0], y + point[1]
624        self.currentPoint = point
625        return point
626
627    def rMoveTo(self, point):
628        self.pen.moveTo(self._nextPoint(point))
629        self.sawMoveTo = 1
630
631    def rLineTo(self, point):
632        if not self.sawMoveTo:
633            self.rMoveTo((0, 0))
634        self.pen.lineTo(self._nextPoint(point))
635
636    def rCurveTo(self, pt1, pt2, pt3):
637        if not self.sawMoveTo:
638            self.rMoveTo((0, 0))
639        nextPoint = self._nextPoint
640        self.pen.curveTo(nextPoint(pt1), nextPoint(pt2), nextPoint(pt3))
641
642    def closePath(self):
643        if self.sawMoveTo:
644            self.pen.closePath()
645        self.sawMoveTo = 0
646
647    def endPath(self):
648        # In T2 there are no open paths, so always do a closePath when
649        # finishing a sub path. We avoid spurious calls to closePath()
650        # because its a real T1 op we're emulating in T2 whereas
651        # endPath() is just a means to that emulation
652        if self.sawMoveTo:
653            self.closePath()
654
655    #
656    # hint operators
657    #
658    # def op_hstem(self, index):
659    # 	self.countHints()
660    # def op_vstem(self, index):
661    # 	self.countHints()
662    # def op_hstemhm(self, index):
663    # 	self.countHints()
664    # def op_vstemhm(self, index):
665    # 	self.countHints()
666    # def op_hintmask(self, index):
667    # 	self.countHints()
668    # def op_cntrmask(self, index):
669    # 	self.countHints()
670
671    #
672    # path constructors, moveto
673    #
674    def op_rmoveto(self, index):
675        self.endPath()
676        self.rMoveTo(self.popallWidth())
677
678    def op_hmoveto(self, index):
679        self.endPath()
680        self.rMoveTo((self.popallWidth(1)[0], 0))
681
682    def op_vmoveto(self, index):
683        self.endPath()
684        self.rMoveTo((0, self.popallWidth(1)[0]))
685
686    def op_endchar(self, index):
687        self.endPath()
688        args = self.popallWidth()
689        if args:
690            from fontTools.encodings.StandardEncoding import StandardEncoding
691
692            # endchar can do seac accent bulding; The T2 spec says it's deprecated,
693            # but recent software that shall remain nameless does output it.
694            adx, ady, bchar, achar = args
695            baseGlyph = StandardEncoding[bchar]
696            self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0))
697            accentGlyph = StandardEncoding[achar]
698            self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady))
699
700    #
701    # path constructors, lines
702    #
703    def op_rlineto(self, index):
704        args = self.popall()
705        for i in range(0, len(args), 2):
706            point = args[i : i + 2]
707            self.rLineTo(point)
708
709    def op_hlineto(self, index):
710        self.alternatingLineto(1)
711
712    def op_vlineto(self, index):
713        self.alternatingLineto(0)
714
715    #
716    # path constructors, curves
717    #
718    def op_rrcurveto(self, index):
719        """{dxa dya dxb dyb dxc dyc}+ rrcurveto"""
720        args = self.popall()
721        for i in range(0, len(args), 6):
722            (
723                dxa,
724                dya,
725                dxb,
726                dyb,
727                dxc,
728                dyc,
729            ) = args[i : i + 6]
730            self.rCurveTo((dxa, dya), (dxb, dyb), (dxc, dyc))
731
732    def op_rcurveline(self, index):
733        """{dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline"""
734        args = self.popall()
735        for i in range(0, len(args) - 2, 6):
736            dxb, dyb, dxc, dyc, dxd, dyd = args[i : i + 6]
737            self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd))
738        self.rLineTo(args[-2:])
739
740    def op_rlinecurve(self, index):
741        """{dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve"""
742        args = self.popall()
743        lineArgs = args[:-6]
744        for i in range(0, len(lineArgs), 2):
745            self.rLineTo(lineArgs[i : i + 2])
746        dxb, dyb, dxc, dyc, dxd, dyd = args[-6:]
747        self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd))
748
749    def op_vvcurveto(self, index):
750        "dx1? {dya dxb dyb dyc}+ vvcurveto"
751        args = self.popall()
752        if len(args) % 2:
753            dx1 = args[0]
754            args = args[1:]
755        else:
756            dx1 = 0
757        for i in range(0, len(args), 4):
758            dya, dxb, dyb, dyc = args[i : i + 4]
759            self.rCurveTo((dx1, dya), (dxb, dyb), (0, dyc))
760            dx1 = 0
761
762    def op_hhcurveto(self, index):
763        """dy1? {dxa dxb dyb dxc}+ hhcurveto"""
764        args = self.popall()
765        if len(args) % 2:
766            dy1 = args[0]
767            args = args[1:]
768        else:
769            dy1 = 0
770        for i in range(0, len(args), 4):
771            dxa, dxb, dyb, dxc = args[i : i + 4]
772            self.rCurveTo((dxa, dy1), (dxb, dyb), (dxc, 0))
773            dy1 = 0
774
775    def op_vhcurveto(self, index):
776        """dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30)
777        {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto
778        """
779        args = self.popall()
780        while args:
781            args = self.vcurveto(args)
782            if args:
783                args = self.hcurveto(args)
784
785    def op_hvcurveto(self, index):
786        """dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
787        {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
788        """
789        args = self.popall()
790        while args:
791            args = self.hcurveto(args)
792            if args:
793                args = self.vcurveto(args)
794
795    #
796    # path constructors, flex
797    #
798    def op_hflex(self, index):
799        dx1, dx2, dy2, dx3, dx4, dx5, dx6 = self.popall()
800        dy1 = dy3 = dy4 = dy6 = 0
801        dy5 = -dy2
802        self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
803        self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
804
805    def op_flex(self, index):
806        dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, dx6, dy6, fd = self.popall()
807        self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
808        self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
809
810    def op_hflex1(self, index):
811        dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6 = self.popall()
812        dy3 = dy4 = 0
813        dy6 = -(dy1 + dy2 + dy3 + dy4 + dy5)
814
815        self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
816        self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
817
818    def op_flex1(self, index):
819        dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, d6 = self.popall()
820        dx = dx1 + dx2 + dx3 + dx4 + dx5
821        dy = dy1 + dy2 + dy3 + dy4 + dy5
822        if abs(dx) > abs(dy):
823            dx6 = d6
824            dy6 = -dy
825        else:
826            dx6 = -dx
827            dy6 = d6
828        self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
829        self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
830
831    # misc
832    def op_and(self, index):
833        raise NotImplementedError
834
835    def op_or(self, index):
836        raise NotImplementedError
837
838    def op_not(self, index):
839        raise NotImplementedError
840
841    def op_store(self, index):
842        raise NotImplementedError
843
844    def op_abs(self, index):
845        raise NotImplementedError
846
847    def op_add(self, index):
848        raise NotImplementedError
849
850    def op_sub(self, index):
851        raise NotImplementedError
852
853    def op_div(self, index):
854        num2 = self.pop()
855        num1 = self.pop()
856        d1 = num1 // num2
857        d2 = num1 / num2
858        if d1 == d2:
859            self.push(d1)
860        else:
861            self.push(d2)
862
863    def op_load(self, index):
864        raise NotImplementedError
865
866    def op_neg(self, index):
867        raise NotImplementedError
868
869    def op_eq(self, index):
870        raise NotImplementedError
871
872    def op_drop(self, index):
873        raise NotImplementedError
874
875    def op_put(self, index):
876        raise NotImplementedError
877
878    def op_get(self, index):
879        raise NotImplementedError
880
881    def op_ifelse(self, index):
882        raise NotImplementedError
883
884    def op_random(self, index):
885        raise NotImplementedError
886
887    def op_mul(self, index):
888        raise NotImplementedError
889
890    def op_sqrt(self, index):
891        raise NotImplementedError
892
893    def op_dup(self, index):
894        raise NotImplementedError
895
896    def op_exch(self, index):
897        raise NotImplementedError
898
899    def op_index(self, index):
900        raise NotImplementedError
901
902    def op_roll(self, index):
903        raise NotImplementedError
904
905    #
906    # miscellaneous helpers
907    #
908    def alternatingLineto(self, isHorizontal):
909        args = self.popall()
910        for arg in args:
911            if isHorizontal:
912                point = (arg, 0)
913            else:
914                point = (0, arg)
915            self.rLineTo(point)
916            isHorizontal = not isHorizontal
917
918    def vcurveto(self, args):
919        dya, dxb, dyb, dxc = args[:4]
920        args = args[4:]
921        if len(args) == 1:
922            dyc = args[0]
923            args = []
924        else:
925            dyc = 0
926        self.rCurveTo((0, dya), (dxb, dyb), (dxc, dyc))
927        return args
928
929    def hcurveto(self, args):
930        dxa, dxb, dyb, dyc = args[:4]
931        args = args[4:]
932        if len(args) == 1:
933            dxc = args[0]
934            args = []
935        else:
936            dxc = 0
937        self.rCurveTo((dxa, 0), (dxb, dyb), (dxc, dyc))
938        return args
939
940
941class T1OutlineExtractor(T2OutlineExtractor):
942    def __init__(self, pen, subrs):
943        self.pen = pen
944        self.subrs = subrs
945        self.reset()
946
947    def reset(self):
948        self.flexing = 0
949        self.width = 0
950        self.sbx = 0
951        T2OutlineExtractor.reset(self)
952
953    def endPath(self):
954        if self.sawMoveTo:
955            self.pen.endPath()
956        self.sawMoveTo = 0
957
958    def popallWidth(self, evenOdd=0):
959        return self.popall()
960
961    def exch(self):
962        stack = self.operandStack
963        stack[-1], stack[-2] = stack[-2], stack[-1]
964
965    #
966    # path constructors
967    #
968    def op_rmoveto(self, index):
969        if self.flexing:
970            return
971        self.endPath()
972        self.rMoveTo(self.popall())
973
974    def op_hmoveto(self, index):
975        if self.flexing:
976            # We must add a parameter to the stack if we are flexing
977            self.push(0)
978            return
979        self.endPath()
980        self.rMoveTo((self.popall()[0], 0))
981
982    def op_vmoveto(self, index):
983        if self.flexing:
984            # We must add a parameter to the stack if we are flexing
985            self.push(0)
986            self.exch()
987            return
988        self.endPath()
989        self.rMoveTo((0, self.popall()[0]))
990
991    def op_closepath(self, index):
992        self.closePath()
993
994    def op_setcurrentpoint(self, index):
995        args = self.popall()
996        x, y = args
997        self.currentPoint = x, y
998
999    def op_endchar(self, index):
1000        self.endPath()
1001
1002    def op_hsbw(self, index):
1003        sbx, wx = self.popall()
1004        self.width = wx
1005        self.sbx = sbx
1006        self.currentPoint = sbx, self.currentPoint[1]
1007
1008    def op_sbw(self, index):
1009        self.popall()  # XXX
1010
1011    #
1012    def op_callsubr(self, index):
1013        subrIndex = self.pop()
1014        subr = self.subrs[subrIndex]
1015        self.execute(subr)
1016
1017    def op_callothersubr(self, index):
1018        subrIndex = self.pop()
1019        nArgs = self.pop()
1020        # print nArgs, subrIndex, "callothersubr"
1021        if subrIndex == 0 and nArgs == 3:
1022            self.doFlex()
1023            self.flexing = 0
1024        elif subrIndex == 1 and nArgs == 0:
1025            self.flexing = 1
1026        # ignore...
1027
1028    def op_pop(self, index):
1029        pass  # ignore...
1030
1031    def doFlex(self):
1032        finaly = self.pop()
1033        finalx = self.pop()
1034        self.pop()  # flex height is unused
1035
1036        p3y = self.pop()
1037        p3x = self.pop()
1038        bcp4y = self.pop()
1039        bcp4x = self.pop()
1040        bcp3y = self.pop()
1041        bcp3x = self.pop()
1042        p2y = self.pop()
1043        p2x = self.pop()
1044        bcp2y = self.pop()
1045        bcp2x = self.pop()
1046        bcp1y = self.pop()
1047        bcp1x = self.pop()
1048        rpy = self.pop()
1049        rpx = self.pop()
1050
1051        # call rrcurveto
1052        self.push(bcp1x + rpx)
1053        self.push(bcp1y + rpy)
1054        self.push(bcp2x)
1055        self.push(bcp2y)
1056        self.push(p2x)
1057        self.push(p2y)
1058        self.op_rrcurveto(None)
1059
1060        # call rrcurveto
1061        self.push(bcp3x)
1062        self.push(bcp3y)
1063        self.push(bcp4x)
1064        self.push(bcp4y)
1065        self.push(p3x)
1066        self.push(p3y)
1067        self.op_rrcurveto(None)
1068
1069        # Push back final coords so subr 0 can find them
1070        self.push(finalx)
1071        self.push(finaly)
1072
1073    def op_dotsection(self, index):
1074        self.popall()  # XXX
1075
1076    def op_hstem3(self, index):
1077        self.popall()  # XXX
1078
1079    def op_seac(self, index):
1080        "asb adx ady bchar achar seac"
1081        from fontTools.encodings.StandardEncoding import StandardEncoding
1082
1083        asb, adx, ady, bchar, achar = self.popall()
1084        baseGlyph = StandardEncoding[bchar]
1085        self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0))
1086        accentGlyph = StandardEncoding[achar]
1087        adx = adx + self.sbx - asb  # seac weirdness
1088        self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady))
1089
1090    def op_vstem3(self, index):
1091        self.popall()  # XXX
1092
1093
1094class T2CharString(object):
1095    operandEncoding = t2OperandEncoding
1096    operators, opcodes = buildOperatorDict(t2Operators)
1097    decompilerClass = SimpleT2Decompiler
1098    outlineExtractor = T2OutlineExtractor
1099
1100    def __init__(self, bytecode=None, program=None, private=None, globalSubrs=None):
1101        if program is None:
1102            program = []
1103        self.bytecode = bytecode
1104        self.program = program
1105        self.private = private
1106        self.globalSubrs = globalSubrs if globalSubrs is not None else []
1107        self._cur_vsindex = None
1108
1109    def getNumRegions(self, vsindex=None):
1110        pd = self.private
1111        assert pd is not None
1112        if vsindex is not None:
1113            self._cur_vsindex = vsindex
1114        elif self._cur_vsindex is None:
1115            self._cur_vsindex = pd.vsindex if hasattr(pd, "vsindex") else 0
1116        return pd.getNumRegions(self._cur_vsindex)
1117
1118    def __repr__(self):
1119        if self.bytecode is None:
1120            return "<%s (source) at %x>" % (self.__class__.__name__, id(self))
1121        else:
1122            return "<%s (bytecode) at %x>" % (self.__class__.__name__, id(self))
1123
1124    def getIntEncoder(self):
1125        return encodeIntT2
1126
1127    def getFixedEncoder(self):
1128        return encodeFixed
1129
1130    def decompile(self):
1131        if not self.needsDecompilation():
1132            return
1133        subrs = getattr(self.private, "Subrs", [])
1134        decompiler = self.decompilerClass(subrs, self.globalSubrs, self.private)
1135        decompiler.execute(self)
1136
1137    def draw(self, pen, blender=None):
1138        subrs = getattr(self.private, "Subrs", [])
1139        extractor = self.outlineExtractor(
1140            pen,
1141            subrs,
1142            self.globalSubrs,
1143            self.private.nominalWidthX,
1144            self.private.defaultWidthX,
1145            self.private,
1146            blender,
1147        )
1148        extractor.execute(self)
1149        self.width = extractor.width
1150
1151    def calcBounds(self, glyphSet):
1152        boundsPen = BoundsPen(glyphSet)
1153        self.draw(boundsPen)
1154        return boundsPen.bounds
1155
1156    def compile(self, isCFF2=False):
1157        if self.bytecode is not None:
1158            return
1159        opcodes = self.opcodes
1160        program = self.program
1161
1162        if isCFF2:
1163            # If present, remove return and endchar operators.
1164            if program and program[-1] in ("return", "endchar"):
1165                program = program[:-1]
1166        elif program and not isinstance(program[-1], str):
1167            raise CharStringCompileError(
1168                "T2CharString or Subr has items on the stack after last operator."
1169            )
1170
1171        bytecode = []
1172        encodeInt = self.getIntEncoder()
1173        encodeFixed = self.getFixedEncoder()
1174        i = 0
1175        end = len(program)
1176        while i < end:
1177            token = program[i]
1178            i = i + 1
1179            if isinstance(token, str):
1180                try:
1181                    bytecode.extend(bytechr(b) for b in opcodes[token])
1182                except KeyError:
1183                    raise CharStringCompileError("illegal operator: %s" % token)
1184                if token in ("hintmask", "cntrmask"):
1185                    bytecode.append(program[i])  # hint mask
1186                    i = i + 1
1187            elif isinstance(token, int):
1188                bytecode.append(encodeInt(token))
1189            elif isinstance(token, float):
1190                bytecode.append(encodeFixed(token))
1191            else:
1192                assert 0, "unsupported type: %s" % type(token)
1193        try:
1194            bytecode = bytesjoin(bytecode)
1195        except TypeError:
1196            log.error(bytecode)
1197            raise
1198        self.setBytecode(bytecode)
1199
1200    def needsDecompilation(self):
1201        return self.bytecode is not None
1202
1203    def setProgram(self, program):
1204        self.program = program
1205        self.bytecode = None
1206
1207    def setBytecode(self, bytecode):
1208        self.bytecode = bytecode
1209        self.program = None
1210
1211    def getToken(self, index, len=len, byteord=byteord, isinstance=isinstance):
1212        if self.bytecode is not None:
1213            if index >= len(self.bytecode):
1214                return None, 0, 0
1215            b0 = byteord(self.bytecode[index])
1216            index = index + 1
1217            handler = self.operandEncoding[b0]
1218            token, index = handler(self, b0, self.bytecode, index)
1219        else:
1220            if index >= len(self.program):
1221                return None, 0, 0
1222            token = self.program[index]
1223            index = index + 1
1224        isOperator = isinstance(token, str)
1225        return token, isOperator, index
1226
1227    def getBytes(self, index, nBytes):
1228        if self.bytecode is not None:
1229            newIndex = index + nBytes
1230            bytes = self.bytecode[index:newIndex]
1231            index = newIndex
1232        else:
1233            bytes = self.program[index]
1234            index = index + 1
1235        assert len(bytes) == nBytes
1236        return bytes, index
1237
1238    def handle_operator(self, operator):
1239        return operator
1240
1241    def toXML(self, xmlWriter, ttFont=None):
1242        from fontTools.misc.textTools import num2binary
1243
1244        if self.bytecode is not None:
1245            xmlWriter.dumphex(self.bytecode)
1246        else:
1247            index = 0
1248            args = []
1249            while True:
1250                token, isOperator, index = self.getToken(index)
1251                if token is None:
1252                    break
1253                if isOperator:
1254                    if token in ("hintmask", "cntrmask"):
1255                        hintMask, isOperator, index = self.getToken(index)
1256                        bits = []
1257                        for byte in hintMask:
1258                            bits.append(num2binary(byteord(byte), 8))
1259                        hintMask = strjoin(bits)
1260                        line = " ".join(args + [token, hintMask])
1261                    else:
1262                        line = " ".join(args + [token])
1263                    xmlWriter.write(line)
1264                    xmlWriter.newline()
1265                    args = []
1266                else:
1267                    if isinstance(token, float):
1268                        token = floatToFixedToStr(token, precisionBits=16)
1269                    else:
1270                        token = str(token)
1271                    args.append(token)
1272            if args:
1273                # NOTE: only CFF2 charstrings/subrs can have numeric arguments on
1274                # the stack after the last operator. Compiling this would fail if
1275                # this is part of CFF 1.0 table.
1276                line = " ".join(args)
1277                xmlWriter.write(line)
1278
1279    def fromXML(self, name, attrs, content):
1280        from fontTools.misc.textTools import binary2num, readHex
1281
1282        if attrs.get("raw"):
1283            self.setBytecode(readHex(content))
1284            return
1285        content = strjoin(content)
1286        content = content.split()
1287        program = []
1288        end = len(content)
1289        i = 0
1290        while i < end:
1291            token = content[i]
1292            i = i + 1
1293            try:
1294                token = int(token)
1295            except ValueError:
1296                try:
1297                    token = strToFixedToFloat(token, precisionBits=16)
1298                except ValueError:
1299                    program.append(token)
1300                    if token in ("hintmask", "cntrmask"):
1301                        mask = content[i]
1302                        maskBytes = b""
1303                        for j in range(0, len(mask), 8):
1304                            maskBytes = maskBytes + bytechr(binary2num(mask[j : j + 8]))
1305                        program.append(maskBytes)
1306                        i = i + 1
1307                else:
1308                    program.append(token)
1309            else:
1310                program.append(token)
1311        self.setProgram(program)
1312
1313
1314class T1CharString(T2CharString):
1315    operandEncoding = t1OperandEncoding
1316    operators, opcodes = buildOperatorDict(t1Operators)
1317
1318    def __init__(self, bytecode=None, program=None, subrs=None):
1319        super().__init__(bytecode, program)
1320        self.subrs = subrs
1321
1322    def getIntEncoder(self):
1323        return encodeIntT1
1324
1325    def getFixedEncoder(self):
1326        def encodeFixed(value):
1327            raise TypeError("Type 1 charstrings don't support floating point operands")
1328
1329    def decompile(self):
1330        if self.bytecode is None:
1331            return
1332        program = []
1333        index = 0
1334        while True:
1335            token, isOperator, index = self.getToken(index)
1336            if token is None:
1337                break
1338            program.append(token)
1339        self.setProgram(program)
1340
1341    def draw(self, pen):
1342        extractor = T1OutlineExtractor(pen, self.subrs)
1343        extractor.execute(self)
1344        self.width = extractor.width
1345
1346
1347class DictDecompiler(object):
1348    operandEncoding = cffDictOperandEncoding
1349
1350    def __init__(self, strings, parent=None):
1351        self.stack = []
1352        self.strings = strings
1353        self.dict = {}
1354        self.parent = parent
1355
1356    def getDict(self):
1357        assert len(self.stack) == 0, "non-empty stack"
1358        return self.dict
1359
1360    def decompile(self, data):
1361        index = 0
1362        lenData = len(data)
1363        push = self.stack.append
1364        while index < lenData:
1365            b0 = byteord(data[index])
1366            index = index + 1
1367            handler = self.operandEncoding[b0]
1368            value, index = handler(self, b0, data, index)
1369            if value is not None:
1370                push(value)
1371
1372    def pop(self):
1373        value = self.stack[-1]
1374        del self.stack[-1]
1375        return value
1376
1377    def popall(self):
1378        args = self.stack[:]
1379        del self.stack[:]
1380        return args
1381
1382    def handle_operator(self, operator):
1383        operator, argType = operator
1384        if isinstance(argType, tuple):
1385            value = ()
1386            for i in range(len(argType) - 1, -1, -1):
1387                arg = argType[i]
1388                arghandler = getattr(self, "arg_" + arg)
1389                value = (arghandler(operator),) + value
1390        else:
1391            arghandler = getattr(self, "arg_" + argType)
1392            value = arghandler(operator)
1393        if operator == "blend":
1394            self.stack.extend(value)
1395        else:
1396            self.dict[operator] = value
1397
1398    def arg_number(self, name):
1399        if isinstance(self.stack[0], list):
1400            out = self.arg_blend_number(self.stack)
1401        else:
1402            out = self.pop()
1403        return out
1404
1405    def arg_blend_number(self, name):
1406        out = []
1407        blendArgs = self.pop()
1408        numMasters = len(blendArgs)
1409        out.append(blendArgs)
1410        out.append("blend")
1411        dummy = self.popall()
1412        return blendArgs
1413
1414    def arg_SID(self, name):
1415        return self.strings[self.pop()]
1416
1417    def arg_array(self, name):
1418        return self.popall()
1419
1420    def arg_blendList(self, name):
1421        """
1422        There may be non-blend args at the top of the stack. We first calculate
1423        where the blend args start in the stack. These are the last
1424        numMasters*numBlends) +1 args.
1425        The blend args starts with numMasters relative coordinate values, the  BlueValues in the list from the default master font. This is followed by
1426        numBlends list of values. Each of  value in one of these lists is the
1427        Variable Font delta for the matching region.
1428
1429        We re-arrange this to be a list of numMaster entries. Each entry starts with the corresponding default font relative value, and is followed by
1430        the delta values. We then convert the default values, the first item in each entry, to an absolute value.
1431        """
1432        vsindex = self.dict.get("vsindex", 0)
1433        numMasters = (
1434            self.parent.getNumRegions(vsindex) + 1
1435        )  # only a PrivateDict has blended ops.
1436        numBlends = self.pop()
1437        args = self.popall()
1438        numArgs = len(args)
1439        # The spec says that there should be no non-blended Blue Values,.
1440        assert numArgs == numMasters * numBlends
1441        value = [None] * numBlends
1442        numDeltas = numMasters - 1
1443        i = 0
1444        prevVal = 0
1445        while i < numBlends:
1446            newVal = args[i] + prevVal
1447            prevVal = newVal
1448            masterOffset = numBlends + (i * numDeltas)
1449            blendList = [newVal] + args[masterOffset : masterOffset + numDeltas]
1450            value[i] = blendList
1451            i += 1
1452        return value
1453
1454    def arg_delta(self, name):
1455        valueList = self.popall()
1456        out = []
1457        if valueList and isinstance(valueList[0], list):
1458            # arg_blendList() has already converted these to absolute values.
1459            out = valueList
1460        else:
1461            current = 0
1462            for v in valueList:
1463                current = current + v
1464                out.append(current)
1465        return out
1466
1467
1468def calcSubrBias(subrs):
1469    nSubrs = len(subrs)
1470    if nSubrs < 1240:
1471        bias = 107
1472    elif nSubrs < 33900:
1473        bias = 1131
1474    else:
1475        bias = 32768
1476    return bias
1477