Coverage for /usr/lib/python3/dist-packages/fontTools/misc/psCharStrings.py: 25%
957 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""psCharStrings.py -- module implementing various kinds of CharStrings:
2CFF dictionary data and Type1/Type2 CharStrings.
3"""
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
17log = logging.getLogger(__name__)
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
34def read_byte(self, b0, data, index):
35 return b0 - 139, index
38def read_smallInt1(self, b0, data, index):
39 b1 = byteord(data[index])
40 return (b0 - 247) * 256 + b1 + 108, index + 1
43def read_smallInt2(self, b0, data, index):
44 b1 = byteord(data[index])
45 return -(b0 - 251) * 256 - b1 - 108, index + 1
48def read_shortInt(self, b0, data, index):
49 (value,) = struct.unpack(">h", data[index : index + 2])
50 return value, index + 2
53def read_longInt(self, b0, data, index):
54 (value,) = struct.unpack(">l", data[index : index + 4])
55 return value, index + 4
58def read_fixed1616(self, b0, data, index):
59 (value,) = struct.unpack(">l", data[index : index + 4])
60 return fixedToFloat(value, precisionBits=16), index + 4
63def read_reserved(self, b0, data, index):
64 assert NotImplementedError
65 return NotImplemented, index
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
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
92t2OperandEncoding = t1OperandEncoding[:]
93t2OperandEncoding[28] = read_shortInt
94t2OperandEncoding[255] = read_fixed1616
96cffDictOperandEncoding = t2OperandEncoding[:]
97cffDictOperandEncoding[29] = read_longInt
98cffDictOperandEncoding[30] = read_realNumber
99cffDictOperandEncoding[255] = read_reserved
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)}
121maxOpStack = 193
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
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]
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
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
247 return encodeInt
250encodeIntCFF = getIntEncoder("cff")
251encodeIntT1 = getIntEncoder("t1")
252encodeIntT2 = getIntEncoder("t2")
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
264realZeroBytes = bytechr(30) + bytechr(0xF)
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
299class CharStringCompileError(Exception):
300 pass
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()
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
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]
352 def pop(self):
353 value = self.operandStack[-1]
354 del self.operandStack[-1]
355 return value
357 def popall(self):
358 stack = self.operandStack[:]
359 self.operandStack[:] = []
360 return stack
362 def push(self, value):
363 self.operandStack.append(value)
365 def op_return(self, index):
366 if self.operandStack:
367 pass
369 def op_endchar(self, index):
370 pass
372 def op_ignore(self, index):
373 pass
375 def op_callsubr(self, index):
376 subrIndex = self.pop()
377 subr = self.localSubrs[subrIndex + self.localBias]
378 self.execute(subr)
380 def op_callgsubr(self, index):
381 subrIndex = self.pop()
382 subr = self.globalSubrs[subrIndex + self.globalBias]
383 self.execute(subr)
385 def op_hstem(self, index):
386 self.countHints()
388 def op_vstem(self, index):
389 self.countHints()
391 def op_hstemhm(self, index):
392 self.countHints()
394 def op_vstemhm(self, index):
395 self.countHints()
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
404 op_cntrmask = op_hintmask
406 def countHints(self):
407 args = self.popall()
408 self.hintCount = self.hintCount + len(args) // 2
410 # misc
411 def op_and(self, index):
412 raise NotImplementedError
414 def op_or(self, index):
415 raise NotImplementedError
417 def op_not(self, index):
418 raise NotImplementedError
420 def op_store(self, index):
421 raise NotImplementedError
423 def op_abs(self, index):
424 raise NotImplementedError
426 def op_add(self, index):
427 raise NotImplementedError
429 def op_sub(self, index):
430 raise NotImplementedError
432 def op_div(self, index):
433 raise NotImplementedError
435 def op_load(self, index):
436 raise NotImplementedError
438 def op_neg(self, index):
439 raise NotImplementedError
441 def op_eq(self, index):
442 raise NotImplementedError
444 def op_drop(self, index):
445 raise NotImplementedError
447 def op_put(self, index):
448 raise NotImplementedError
450 def op_get(self, index):
451 raise NotImplementedError
453 def op_ifelse(self, index):
454 raise NotImplementedError
456 def op_random(self, index):
457 raise NotImplementedError
459 def op_mul(self, index):
460 raise NotImplementedError
462 def op_sqrt(self, index):
463 raise NotImplementedError
465 def op_dup(self, index):
466 raise NotImplementedError
468 def op_exch(self, index):
469 raise NotImplementedError
471 def op_index(self, index):
472 raise NotImplementedError
474 def op_roll(self, index):
475 raise NotImplementedError
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:] = []
498 def op_vsindex(self, index):
499 vi = self.pop()
500 self.vsIndex = vi
501 self.numRegions = self.private.getNumRegions(vi)
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]
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
548 def reset(self):
549 SimpleT2Decompiler.reset(self)
550 self.gotWidth = 0
551 self.width = 0
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
568 def countHints(self):
569 args = self.popallWidth()
570 self.hintCount = self.hintCount + len(args) // 2
572 def op_rmoveto(self, index):
573 self.popallWidth()
575 def op_hmoveto(self, index):
576 self.popallWidth(1)
578 def op_vmoveto(self, index):
579 self.popallWidth(1)
581 def op_endchar(self, index):
582 self.popallWidth()
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
608 def reset(self):
609 T2WidthExtractor.reset(self)
610 self.currentPoint = (0, 0)
611 self.sawMoveTo = 0
612 self.subrLevel = 0
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()
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
627 def rMoveTo(self, point):
628 self.pen.moveTo(self._nextPoint(point))
629 self.sawMoveTo = 1
631 def rLineTo(self, point):
632 if not self.sawMoveTo:
633 self.rMoveTo((0, 0))
634 self.pen.lineTo(self._nextPoint(point))
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))
642 def closePath(self):
643 if self.sawMoveTo:
644 self.pen.closePath()
645 self.sawMoveTo = 0
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()
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()
671 #
672 # path constructors, moveto
673 #
674 def op_rmoveto(self, index):
675 self.endPath()
676 self.rMoveTo(self.popallWidth())
678 def op_hmoveto(self, index):
679 self.endPath()
680 self.rMoveTo((self.popallWidth(1)[0], 0))
682 def op_vmoveto(self, index):
683 self.endPath()
684 self.rMoveTo((0, self.popallWidth(1)[0]))
686 def op_endchar(self, index):
687 self.endPath()
688 args = self.popallWidth()
689 if args:
690 from fontTools.encodings.StandardEncoding import StandardEncoding
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))
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)
709 def op_hlineto(self, index):
710 self.alternatingLineto(1)
712 def op_vlineto(self, index):
713 self.alternatingLineto(0)
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))
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:])
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))
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
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
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)
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)
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))
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))
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)
815 self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
816 self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
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))
831 # misc
832 def op_and(self, index):
833 raise NotImplementedError
835 def op_or(self, index):
836 raise NotImplementedError
838 def op_not(self, index):
839 raise NotImplementedError
841 def op_store(self, index):
842 raise NotImplementedError
844 def op_abs(self, index):
845 raise NotImplementedError
847 def op_add(self, index):
848 raise NotImplementedError
850 def op_sub(self, index):
851 raise NotImplementedError
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)
863 def op_load(self, index):
864 raise NotImplementedError
866 def op_neg(self, index):
867 raise NotImplementedError
869 def op_eq(self, index):
870 raise NotImplementedError
872 def op_drop(self, index):
873 raise NotImplementedError
875 def op_put(self, index):
876 raise NotImplementedError
878 def op_get(self, index):
879 raise NotImplementedError
881 def op_ifelse(self, index):
882 raise NotImplementedError
884 def op_random(self, index):
885 raise NotImplementedError
887 def op_mul(self, index):
888 raise NotImplementedError
890 def op_sqrt(self, index):
891 raise NotImplementedError
893 def op_dup(self, index):
894 raise NotImplementedError
896 def op_exch(self, index):
897 raise NotImplementedError
899 def op_index(self, index):
900 raise NotImplementedError
902 def op_roll(self, index):
903 raise NotImplementedError
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
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
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
941class T1OutlineExtractor(T2OutlineExtractor):
942 def __init__(self, pen, subrs):
943 self.pen = pen
944 self.subrs = subrs
945 self.reset()
947 def reset(self):
948 self.flexing = 0
949 self.width = 0
950 self.sbx = 0
951 T2OutlineExtractor.reset(self)
953 def endPath(self):
954 if self.sawMoveTo:
955 self.pen.endPath()
956 self.sawMoveTo = 0
958 def popallWidth(self, evenOdd=0):
959 return self.popall()
961 def exch(self):
962 stack = self.operandStack
963 stack[-1], stack[-2] = stack[-2], stack[-1]
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())
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))
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]))
991 def op_closepath(self, index):
992 self.closePath()
994 def op_setcurrentpoint(self, index):
995 args = self.popall()
996 x, y = args
997 self.currentPoint = x, y
999 def op_endchar(self, index):
1000 self.endPath()
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]
1008 def op_sbw(self, index):
1009 self.popall() # XXX
1011 #
1012 def op_callsubr(self, index):
1013 subrIndex = self.pop()
1014 subr = self.subrs[subrIndex]
1015 self.execute(subr)
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...
1028 def op_pop(self, index):
1029 pass # ignore...
1031 def doFlex(self):
1032 finaly = self.pop()
1033 finalx = self.pop()
1034 self.pop() # flex height is unused
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()
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)
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)
1069 # Push back final coords so subr 0 can find them
1070 self.push(finalx)
1071 self.push(finaly)
1073 def op_dotsection(self, index):
1074 self.popall() # XXX
1076 def op_hstem3(self, index):
1077 self.popall() # XXX
1079 def op_seac(self, index):
1080 "asb adx ady bchar achar seac"
1081 from fontTools.encodings.StandardEncoding import StandardEncoding
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))
1090 def op_vstem3(self, index):
1091 self.popall() # XXX
1094class T2CharString(object):
1095 operandEncoding = t2OperandEncoding
1096 operators, opcodes = buildOperatorDict(t2Operators)
1097 decompilerClass = SimpleT2Decompiler
1098 outlineExtractor = T2OutlineExtractor
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
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)
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))
1124 def getIntEncoder(self):
1125 return encodeIntT2
1127 def getFixedEncoder(self):
1128 return encodeFixed
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)
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
1151 def calcBounds(self, glyphSet):
1152 boundsPen = BoundsPen(glyphSet)
1153 self.draw(boundsPen)
1154 return boundsPen.bounds
1156 def compile(self, isCFF2=False):
1157 if self.bytecode is not None:
1158 return
1159 opcodes = self.opcodes
1160 program = self.program
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 )
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)
1200 def needsDecompilation(self):
1201 return self.bytecode is not None
1203 def setProgram(self, program):
1204 self.program = program
1205 self.bytecode = None
1207 def setBytecode(self, bytecode):
1208 self.bytecode = bytecode
1209 self.program = None
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
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
1238 def handle_operator(self, operator):
1239 return operator
1241 def toXML(self, xmlWriter, ttFont=None):
1242 from fontTools.misc.textTools import num2binary
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)
1279 def fromXML(self, name, attrs, content):
1280 from fontTools.misc.textTools import binary2num, readHex
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)
1314class T1CharString(T2CharString):
1315 operandEncoding = t1OperandEncoding
1316 operators, opcodes = buildOperatorDict(t1Operators)
1318 def __init__(self, bytecode=None, program=None, subrs=None):
1319 super().__init__(bytecode, program)
1320 self.subrs = subrs
1322 def getIntEncoder(self):
1323 return encodeIntT1
1325 def getFixedEncoder(self):
1326 def encodeFixed(value):
1327 raise TypeError("Type 1 charstrings don't support floating point operands")
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)
1341 def draw(self, pen):
1342 extractor = T1OutlineExtractor(pen, self.subrs)
1343 extractor.execute(self)
1344 self.width = extractor.width
1347class DictDecompiler(object):
1348 operandEncoding = cffDictOperandEncoding
1350 def __init__(self, strings, parent=None):
1351 self.stack = []
1352 self.strings = strings
1353 self.dict = {}
1354 self.parent = parent
1356 def getDict(self):
1357 assert len(self.stack) == 0, "non-empty stack"
1358 return self.dict
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)
1372 def pop(self):
1373 value = self.stack[-1]
1374 del self.stack[-1]
1375 return value
1377 def popall(self):
1378 args = self.stack[:]
1379 del self.stack[:]
1380 return args
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
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
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
1414 def arg_SID(self, name):
1415 return self.strings[self.pop()]
1417 def arg_array(self, name):
1418 return self.popall()
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.
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
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
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