1"""psCharStrings.py -- module implementing various kinds of CharStrings: 2CFF dictionary data and Type1/Type2 CharStrings. 3""" 4 5from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin 6from fontTools.misc.fixedTools import ( 7 fixedToFloat, floatToFixed, floatToFixedToStr, strToFixedToFloat, 8) 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 506 def reset(self): 507 T2WidthExtractor.reset(self) 508 self.currentPoint = (0, 0) 509 self.sawMoveTo = 0 510 511 def _nextPoint(self, point): 512 x, y = self.currentPoint 513 point = x + point[0], y + point[1] 514 self.currentPoint = point 515 return point 516 517 def rMoveTo(self, point): 518 self.pen.moveTo(self._nextPoint(point)) 519 self.sawMoveTo = 1 520 521 def rLineTo(self, point): 522 if not self.sawMoveTo: 523 self.rMoveTo((0, 0)) 524 self.pen.lineTo(self._nextPoint(point)) 525 526 def rCurveTo(self, pt1, pt2, pt3): 527 if not self.sawMoveTo: 528 self.rMoveTo((0, 0)) 529 nextPoint = self._nextPoint 530 self.pen.curveTo(nextPoint(pt1), nextPoint(pt2), nextPoint(pt3)) 531 532 def closePath(self): 533 if self.sawMoveTo: 534 self.pen.closePath() 535 self.sawMoveTo = 0 536 537 def endPath(self): 538 # In T2 there are no open paths, so always do a closePath when 539 # finishing a sub path. 540 self.closePath() 541 542 # 543 # hint operators 544 # 545 #def op_hstem(self, index): 546 # self.countHints() 547 #def op_vstem(self, index): 548 # self.countHints() 549 #def op_hstemhm(self, index): 550 # self.countHints() 551 #def op_vstemhm(self, index): 552 # self.countHints() 553 #def op_hintmask(self, index): 554 # self.countHints() 555 #def op_cntrmask(self, index): 556 # self.countHints() 557 558 # 559 # path constructors, moveto 560 # 561 def op_rmoveto(self, index): 562 self.endPath() 563 self.rMoveTo(self.popallWidth()) 564 def op_hmoveto(self, index): 565 self.endPath() 566 self.rMoveTo((self.popallWidth(1)[0], 0)) 567 def op_vmoveto(self, index): 568 self.endPath() 569 self.rMoveTo((0, self.popallWidth(1)[0])) 570 def op_endchar(self, index): 571 self.endPath() 572 args = self.popallWidth() 573 if args: 574 from fontTools.encodings.StandardEncoding import StandardEncoding 575 # endchar can do seac accent bulding; The T2 spec says it's deprecated, 576 # but recent software that shall remain nameless does output it. 577 adx, ady, bchar, achar = args 578 baseGlyph = StandardEncoding[bchar] 579 self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0)) 580 accentGlyph = StandardEncoding[achar] 581 self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady)) 582 583 # 584 # path constructors, lines 585 # 586 def op_rlineto(self, index): 587 args = self.popall() 588 for i in range(0, len(args), 2): 589 point = args[i:i+2] 590 self.rLineTo(point) 591 592 def op_hlineto(self, index): 593 self.alternatingLineto(1) 594 def op_vlineto(self, index): 595 self.alternatingLineto(0) 596 597 # 598 # path constructors, curves 599 # 600 def op_rrcurveto(self, index): 601 """{dxa dya dxb dyb dxc dyc}+ rrcurveto""" 602 args = self.popall() 603 for i in range(0, len(args), 6): 604 dxa, dya, dxb, dyb, dxc, dyc, = args[i:i+6] 605 self.rCurveTo((dxa, dya), (dxb, dyb), (dxc, dyc)) 606 607 def op_rcurveline(self, index): 608 """{dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline""" 609 args = self.popall() 610 for i in range(0, len(args)-2, 6): 611 dxb, dyb, dxc, dyc, dxd, dyd = args[i:i+6] 612 self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd)) 613 self.rLineTo(args[-2:]) 614 615 def op_rlinecurve(self, index): 616 """{dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve""" 617 args = self.popall() 618 lineArgs = args[:-6] 619 for i in range(0, len(lineArgs), 2): 620 self.rLineTo(lineArgs[i:i+2]) 621 dxb, dyb, dxc, dyc, dxd, dyd = args[-6:] 622 self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd)) 623 624 def op_vvcurveto(self, index): 625 "dx1? {dya dxb dyb dyc}+ vvcurveto" 626 args = self.popall() 627 if len(args) % 2: 628 dx1 = args[0] 629 args = args[1:] 630 else: 631 dx1 = 0 632 for i in range(0, len(args), 4): 633 dya, dxb, dyb, dyc = args[i:i+4] 634 self.rCurveTo((dx1, dya), (dxb, dyb), (0, dyc)) 635 dx1 = 0 636 637 def op_hhcurveto(self, index): 638 """dy1? {dxa dxb dyb dxc}+ hhcurveto""" 639 args = self.popall() 640 if len(args) % 2: 641 dy1 = args[0] 642 args = args[1:] 643 else: 644 dy1 = 0 645 for i in range(0, len(args), 4): 646 dxa, dxb, dyb, dxc = args[i:i+4] 647 self.rCurveTo((dxa, dy1), (dxb, dyb), (dxc, 0)) 648 dy1 = 0 649 650 def op_vhcurveto(self, index): 651 """dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30) 652 {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto 653 """ 654 args = self.popall() 655 while args: 656 args = self.vcurveto(args) 657 if args: 658 args = self.hcurveto(args) 659 660 def op_hvcurveto(self, index): 661 """dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? 662 {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? 663 """ 664 args = self.popall() 665 while args: 666 args = self.hcurveto(args) 667 if args: 668 args = self.vcurveto(args) 669 670 # 671 # path constructors, flex 672 # 673 def op_hflex(self, index): 674 dx1, dx2, dy2, dx3, dx4, dx5, dx6 = self.popall() 675 dy1 = dy3 = dy4 = dy6 = 0 676 dy5 = -dy2 677 self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3)) 678 self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6)) 679 def op_flex(self, index): 680 dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, dx6, dy6, fd = self.popall() 681 self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3)) 682 self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6)) 683 def op_hflex1(self, index): 684 dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6 = self.popall() 685 dy3 = dy4 = 0 686 dy6 = -(dy1 + dy2 + dy3 + dy4 + dy5) 687 688 self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3)) 689 self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6)) 690 def op_flex1(self, index): 691 dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, d6 = self.popall() 692 dx = dx1 + dx2 + dx3 + dx4 + dx5 693 dy = dy1 + dy2 + dy3 + dy4 + dy5 694 if abs(dx) > abs(dy): 695 dx6 = d6 696 dy6 = -dy 697 else: 698 dx6 = -dx 699 dy6 = d6 700 self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3)) 701 self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6)) 702 703 # misc 704 def op_and(self, index): 705 raise NotImplementedError 706 def op_or(self, index): 707 raise NotImplementedError 708 def op_not(self, index): 709 raise NotImplementedError 710 def op_store(self, index): 711 raise NotImplementedError 712 def op_abs(self, index): 713 raise NotImplementedError 714 def op_add(self, index): 715 raise NotImplementedError 716 def op_sub(self, index): 717 raise NotImplementedError 718 def op_div(self, index): 719 num2 = self.pop() 720 num1 = self.pop() 721 d1 = num1//num2 722 d2 = num1/num2 723 if d1 == d2: 724 self.push(d1) 725 else: 726 self.push(d2) 727 def op_load(self, index): 728 raise NotImplementedError 729 def op_neg(self, index): 730 raise NotImplementedError 731 def op_eq(self, index): 732 raise NotImplementedError 733 def op_drop(self, index): 734 raise NotImplementedError 735 def op_put(self, index): 736 raise NotImplementedError 737 def op_get(self, index): 738 raise NotImplementedError 739 def op_ifelse(self, index): 740 raise NotImplementedError 741 def op_random(self, index): 742 raise NotImplementedError 743 def op_mul(self, index): 744 raise NotImplementedError 745 def op_sqrt(self, index): 746 raise NotImplementedError 747 def op_dup(self, index): 748 raise NotImplementedError 749 def op_exch(self, index): 750 raise NotImplementedError 751 def op_index(self, index): 752 raise NotImplementedError 753 def op_roll(self, index): 754 raise NotImplementedError 755 756 # 757 # miscellaneous helpers 758 # 759 def alternatingLineto(self, isHorizontal): 760 args = self.popall() 761 for arg in args: 762 if isHorizontal: 763 point = (arg, 0) 764 else: 765 point = (0, arg) 766 self.rLineTo(point) 767 isHorizontal = not isHorizontal 768 769 def vcurveto(self, args): 770 dya, dxb, dyb, dxc = args[:4] 771 args = args[4:] 772 if len(args) == 1: 773 dyc = args[0] 774 args = [] 775 else: 776 dyc = 0 777 self.rCurveTo((0, dya), (dxb, dyb), (dxc, dyc)) 778 return args 779 780 def hcurveto(self, args): 781 dxa, dxb, dyb, dyc = args[:4] 782 args = args[4:] 783 if len(args) == 1: 784 dxc = args[0] 785 args = [] 786 else: 787 dxc = 0 788 self.rCurveTo((dxa, 0), (dxb, dyb), (dxc, dyc)) 789 return args 790 791class T1OutlineExtractor(T2OutlineExtractor): 792 793 def __init__(self, pen, subrs): 794 self.pen = pen 795 self.subrs = subrs 796 self.reset() 797 798 def reset(self): 799 self.flexing = 0 800 self.width = 0 801 self.sbx = 0 802 T2OutlineExtractor.reset(self) 803 804 def endPath(self): 805 if self.sawMoveTo: 806 self.pen.endPath() 807 self.sawMoveTo = 0 808 809 def popallWidth(self, evenOdd=0): 810 return self.popall() 811 812 def exch(self): 813 stack = self.operandStack 814 stack[-1], stack[-2] = stack[-2], stack[-1] 815 816 # 817 # path constructors 818 # 819 def op_rmoveto(self, index): 820 if self.flexing: 821 return 822 self.endPath() 823 self.rMoveTo(self.popall()) 824 def op_hmoveto(self, index): 825 if self.flexing: 826 # We must add a parameter to the stack if we are flexing 827 self.push(0) 828 return 829 self.endPath() 830 self.rMoveTo((self.popall()[0], 0)) 831 def op_vmoveto(self, index): 832 if self.flexing: 833 # We must add a parameter to the stack if we are flexing 834 self.push(0) 835 self.exch() 836 return 837 self.endPath() 838 self.rMoveTo((0, self.popall()[0])) 839 def op_closepath(self, index): 840 self.closePath() 841 def op_setcurrentpoint(self, index): 842 args = self.popall() 843 x, y = args 844 self.currentPoint = x, y 845 846 def op_endchar(self, index): 847 self.endPath() 848 849 def op_hsbw(self, index): 850 sbx, wx = self.popall() 851 self.width = wx 852 self.sbx = sbx 853 self.currentPoint = sbx, self.currentPoint[1] 854 def op_sbw(self, index): 855 self.popall() # XXX 856 857 # 858 def op_callsubr(self, index): 859 subrIndex = self.pop() 860 subr = self.subrs[subrIndex] 861 self.execute(subr) 862 def op_callothersubr(self, index): 863 subrIndex = self.pop() 864 nArgs = self.pop() 865 #print nArgs, subrIndex, "callothersubr" 866 if subrIndex == 0 and nArgs == 3: 867 self.doFlex() 868 self.flexing = 0 869 elif subrIndex == 1 and nArgs == 0: 870 self.flexing = 1 871 # ignore... 872 def op_pop(self, index): 873 pass # ignore... 874 875 def doFlex(self): 876 finaly = self.pop() 877 finalx = self.pop() 878 self.pop() # flex height is unused 879 880 p3y = self.pop() 881 p3x = self.pop() 882 bcp4y = self.pop() 883 bcp4x = self.pop() 884 bcp3y = self.pop() 885 bcp3x = self.pop() 886 p2y = self.pop() 887 p2x = self.pop() 888 bcp2y = self.pop() 889 bcp2x = self.pop() 890 bcp1y = self.pop() 891 bcp1x = self.pop() 892 rpy = self.pop() 893 rpx = self.pop() 894 895 # call rrcurveto 896 self.push(bcp1x+rpx) 897 self.push(bcp1y+rpy) 898 self.push(bcp2x) 899 self.push(bcp2y) 900 self.push(p2x) 901 self.push(p2y) 902 self.op_rrcurveto(None) 903 904 # call rrcurveto 905 self.push(bcp3x) 906 self.push(bcp3y) 907 self.push(bcp4x) 908 self.push(bcp4y) 909 self.push(p3x) 910 self.push(p3y) 911 self.op_rrcurveto(None) 912 913 # Push back final coords so subr 0 can find them 914 self.push(finalx) 915 self.push(finaly) 916 917 def op_dotsection(self, index): 918 self.popall() # XXX 919 def op_hstem3(self, index): 920 self.popall() # XXX 921 def op_seac(self, index): 922 "asb adx ady bchar achar seac" 923 from fontTools.encodings.StandardEncoding import StandardEncoding 924 asb, adx, ady, bchar, achar = self.popall() 925 baseGlyph = StandardEncoding[bchar] 926 self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0)) 927 accentGlyph = StandardEncoding[achar] 928 adx = adx + self.sbx - asb # seac weirdness 929 self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady)) 930 def op_vstem3(self, index): 931 self.popall() # XXX 932 933class T2CharString(object): 934 935 operandEncoding = t2OperandEncoding 936 operators, opcodes = buildOperatorDict(t2Operators) 937 decompilerClass = SimpleT2Decompiler 938 outlineExtractor = T2OutlineExtractor 939 940 def __init__(self, bytecode=None, program=None, private=None, globalSubrs=None): 941 if program is None: 942 program = [] 943 self.bytecode = bytecode 944 self.program = program 945 self.private = private 946 self.globalSubrs = globalSubrs if globalSubrs is not None else [] 947 self._cur_vsindex = None 948 949 def getNumRegions(self, vsindex=None): 950 pd = self.private 951 assert(pd is not None) 952 if vsindex is not None: 953 self._cur_vsindex = vsindex 954 elif self._cur_vsindex is None: 955 self._cur_vsindex = pd.vsindex if hasattr(pd, 'vsindex') else 0 956 return pd.getNumRegions(self._cur_vsindex) 957 958 def __repr__(self): 959 if self.bytecode is None: 960 return "<%s (source) at %x>" % (self.__class__.__name__, id(self)) 961 else: 962 return "<%s (bytecode) at %x>" % (self.__class__.__name__, id(self)) 963 964 def getIntEncoder(self): 965 return encodeIntT2 966 967 def getFixedEncoder(self): 968 return encodeFixed 969 970 def decompile(self): 971 if not self.needsDecompilation(): 972 return 973 subrs = getattr(self.private, "Subrs", []) 974 decompiler = self.decompilerClass(subrs, self.globalSubrs, self.private) 975 decompiler.execute(self) 976 977 def draw(self, pen): 978 subrs = getattr(self.private, "Subrs", []) 979 extractor = self.outlineExtractor(pen, subrs, self.globalSubrs, 980 self.private.nominalWidthX, self.private.defaultWidthX, 981 self.private) 982 extractor.execute(self) 983 self.width = extractor.width 984 985 def calcBounds(self, glyphSet): 986 boundsPen = BoundsPen(glyphSet) 987 self.draw(boundsPen) 988 return boundsPen.bounds 989 990 def compile(self, isCFF2=False): 991 if self.bytecode is not None: 992 return 993 opcodes = self.opcodes 994 program = self.program 995 996 if isCFF2: 997 # If present, remove return and endchar operators. 998 if program and program[-1] in ("return", "endchar"): 999 program = program[:-1] 1000 elif program and not isinstance(program[-1], str): 1001 raise CharStringCompileError( 1002 "T2CharString or Subr has items on the stack after last operator." 1003 ) 1004 1005 bytecode = [] 1006 encodeInt = self.getIntEncoder() 1007 encodeFixed = self.getFixedEncoder() 1008 i = 0 1009 end = len(program) 1010 while i < end: 1011 token = program[i] 1012 i = i + 1 1013 if isinstance(token, str): 1014 try: 1015 bytecode.extend(bytechr(b) for b in opcodes[token]) 1016 except KeyError: 1017 raise CharStringCompileError("illegal operator: %s" % token) 1018 if token in ('hintmask', 'cntrmask'): 1019 bytecode.append(program[i]) # hint mask 1020 i = i + 1 1021 elif isinstance(token, int): 1022 bytecode.append(encodeInt(token)) 1023 elif isinstance(token, float): 1024 bytecode.append(encodeFixed(token)) 1025 else: 1026 assert 0, "unsupported type: %s" % type(token) 1027 try: 1028 bytecode = bytesjoin(bytecode) 1029 except TypeError: 1030 log.error(bytecode) 1031 raise 1032 self.setBytecode(bytecode) 1033 1034 def needsDecompilation(self): 1035 return self.bytecode is not None 1036 1037 def setProgram(self, program): 1038 self.program = program 1039 self.bytecode = None 1040 1041 def setBytecode(self, bytecode): 1042 self.bytecode = bytecode 1043 self.program = None 1044 1045 def getToken(self, index, 1046 len=len, byteord=byteord, isinstance=isinstance): 1047 if self.bytecode is not None: 1048 if index >= len(self.bytecode): 1049 return None, 0, 0 1050 b0 = byteord(self.bytecode[index]) 1051 index = index + 1 1052 handler = self.operandEncoding[b0] 1053 token, index = handler(self, b0, self.bytecode, index) 1054 else: 1055 if index >= len(self.program): 1056 return None, 0, 0 1057 token = self.program[index] 1058 index = index + 1 1059 isOperator = isinstance(token, str) 1060 return token, isOperator, index 1061 1062 def getBytes(self, index, nBytes): 1063 if self.bytecode is not None: 1064 newIndex = index + nBytes 1065 bytes = self.bytecode[index:newIndex] 1066 index = newIndex 1067 else: 1068 bytes = self.program[index] 1069 index = index + 1 1070 assert len(bytes) == nBytes 1071 return bytes, index 1072 1073 def handle_operator(self, operator): 1074 return operator 1075 1076 def toXML(self, xmlWriter, ttFont=None): 1077 from fontTools.misc.textTools import num2binary 1078 if self.bytecode is not None: 1079 xmlWriter.dumphex(self.bytecode) 1080 else: 1081 index = 0 1082 args = [] 1083 while True: 1084 token, isOperator, index = self.getToken(index) 1085 if token is None: 1086 break 1087 if isOperator: 1088 if token in ('hintmask', 'cntrmask'): 1089 hintMask, isOperator, index = self.getToken(index) 1090 bits = [] 1091 for byte in hintMask: 1092 bits.append(num2binary(byteord(byte), 8)) 1093 hintMask = strjoin(bits) 1094 line = ' '.join(args + [token, hintMask]) 1095 else: 1096 line = ' '.join(args + [token]) 1097 xmlWriter.write(line) 1098 xmlWriter.newline() 1099 args = [] 1100 else: 1101 if isinstance(token, float): 1102 token = floatToFixedToStr(token, precisionBits=16) 1103 else: 1104 token = str(token) 1105 args.append(token) 1106 if args: 1107 # NOTE: only CFF2 charstrings/subrs can have numeric arguments on 1108 # the stack after the last operator. Compiling this would fail if 1109 # this is part of CFF 1.0 table. 1110 line = ' '.join(args) 1111 xmlWriter.write(line) 1112 1113 def fromXML(self, name, attrs, content): 1114 from fontTools.misc.textTools import binary2num, readHex 1115 if attrs.get("raw"): 1116 self.setBytecode(readHex(content)) 1117 return 1118 content = strjoin(content) 1119 content = content.split() 1120 program = [] 1121 end = len(content) 1122 i = 0 1123 while i < end: 1124 token = content[i] 1125 i = i + 1 1126 try: 1127 token = int(token) 1128 except ValueError: 1129 try: 1130 token = strToFixedToFloat(token, precisionBits=16) 1131 except ValueError: 1132 program.append(token) 1133 if token in ('hintmask', 'cntrmask'): 1134 mask = content[i] 1135 maskBytes = b"" 1136 for j in range(0, len(mask), 8): 1137 maskBytes = maskBytes + bytechr(binary2num(mask[j:j+8])) 1138 program.append(maskBytes) 1139 i = i + 1 1140 else: 1141 program.append(token) 1142 else: 1143 program.append(token) 1144 self.setProgram(program) 1145 1146class T1CharString(T2CharString): 1147 1148 operandEncoding = t1OperandEncoding 1149 operators, opcodes = buildOperatorDict(t1Operators) 1150 1151 def __init__(self, bytecode=None, program=None, subrs=None): 1152 super().__init__(bytecode, program) 1153 self.subrs = subrs 1154 1155 def getIntEncoder(self): 1156 return encodeIntT1 1157 1158 def getFixedEncoder(self): 1159 def encodeFixed(value): 1160 raise TypeError("Type 1 charstrings don't support floating point operands") 1161 1162 def decompile(self): 1163 if self.bytecode is None: 1164 return 1165 program = [] 1166 index = 0 1167 while True: 1168 token, isOperator, index = self.getToken(index) 1169 if token is None: 1170 break 1171 program.append(token) 1172 self.setProgram(program) 1173 1174 def draw(self, pen): 1175 extractor = T1OutlineExtractor(pen, self.subrs) 1176 extractor.execute(self) 1177 self.width = extractor.width 1178 1179class DictDecompiler(object): 1180 1181 operandEncoding = cffDictOperandEncoding 1182 1183 def __init__(self, strings, parent=None): 1184 self.stack = [] 1185 self.strings = strings 1186 self.dict = {} 1187 self.parent = parent 1188 1189 def getDict(self): 1190 assert len(self.stack) == 0, "non-empty stack" 1191 return self.dict 1192 1193 def decompile(self, data): 1194 index = 0 1195 lenData = len(data) 1196 push = self.stack.append 1197 while index < lenData: 1198 b0 = byteord(data[index]) 1199 index = index + 1 1200 handler = self.operandEncoding[b0] 1201 value, index = handler(self, b0, data, index) 1202 if value is not None: 1203 push(value) 1204 def pop(self): 1205 value = self.stack[-1] 1206 del self.stack[-1] 1207 return value 1208 1209 def popall(self): 1210 args = self.stack[:] 1211 del self.stack[:] 1212 return args 1213 1214 def handle_operator(self, operator): 1215 operator, argType = operator 1216 if isinstance(argType, tuple): 1217 value = () 1218 for i in range(len(argType)-1, -1, -1): 1219 arg = argType[i] 1220 arghandler = getattr(self, "arg_" + arg) 1221 value = (arghandler(operator),) + value 1222 else: 1223 arghandler = getattr(self, "arg_" + argType) 1224 value = arghandler(operator) 1225 if operator == "blend": 1226 self.stack.extend(value) 1227 else: 1228 self.dict[operator] = value 1229 1230 def arg_number(self, name): 1231 if isinstance(self.stack[0], list): 1232 out = self.arg_blend_number(self.stack) 1233 else: 1234 out = self.pop() 1235 return out 1236 1237 def arg_blend_number(self, name): 1238 out = [] 1239 blendArgs = self.pop() 1240 numMasters = len(blendArgs) 1241 out.append(blendArgs) 1242 out.append("blend") 1243 dummy = self.popall() 1244 return blendArgs 1245 1246 def arg_SID(self, name): 1247 return self.strings[self.pop()] 1248 def arg_array(self, name): 1249 return self.popall() 1250 def arg_blendList(self, name): 1251 """ 1252 There may be non-blend args at the top of the stack. We first calculate 1253 where the blend args start in the stack. These are the last 1254 numMasters*numBlends) +1 args. 1255 The blend args starts with numMasters relative coordinate values, the BlueValues in the list from the default master font. This is followed by 1256 numBlends list of values. Each of value in one of these lists is the 1257 Variable Font delta for the matching region. 1258 1259 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 1260 the delta values. We then convert the default values, the first item in each entry, to an absolute value. 1261 """ 1262 vsindex = self.dict.get('vsindex', 0) 1263 numMasters = self.parent.getNumRegions(vsindex) + 1 # only a PrivateDict has blended ops. 1264 numBlends = self.pop() 1265 args = self.popall() 1266 numArgs = len(args) 1267 # The spec says that there should be no non-blended Blue Values,. 1268 assert(numArgs == numMasters * numBlends) 1269 value = [None]*numBlends 1270 numDeltas = numMasters-1 1271 i = 0 1272 prevVal = 0 1273 while i < numBlends: 1274 newVal = args[i] + prevVal 1275 prevVal = newVal 1276 masterOffset = numBlends + (i* numDeltas) 1277 blendList = [newVal] + args[masterOffset:masterOffset+numDeltas] 1278 value[i] = blendList 1279 i += 1 1280 return value 1281 1282 def arg_delta(self, name): 1283 valueList = self.popall() 1284 out = [] 1285 if valueList and isinstance(valueList[0], list): 1286 # arg_blendList() has already converted these to absolute values. 1287 out = valueList 1288 else: 1289 current = 0 1290 for v in valueList: 1291 current = current + v 1292 out.append(current) 1293 return out 1294 1295 1296def calcSubrBias(subrs): 1297 nSubrs = len(subrs) 1298 if nSubrs < 1240: 1299 bias = 107 1300 elif nSubrs < 33900: 1301 bias = 1131 1302 else: 1303 bias = 32768 1304 return bias 1305