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