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