| | """psCharStrings.py -- module implementing various kinds of CharStrings: |
| | CFF dictionary data and Type1/Type2 CharStrings. |
| | """ |
| |
|
| | from fontTools.misc.fixedTools import ( |
| | fixedToFloat, |
| | floatToFixed, |
| | floatToFixedToStr, |
| | strToFixedToFloat, |
| | ) |
| | from fontTools.misc.textTools import bytechr, byteord, bytesjoin, strjoin |
| | from fontTools.pens.boundsPen import BoundsPen |
| | import struct |
| | import logging |
| |
|
| |
|
| | log = logging.getLogger(__name__) |
| |
|
| |
|
| | def read_operator(self, b0, data, index): |
| | if b0 == 12: |
| | op = (b0, byteord(data[index])) |
| | index = index + 1 |
| | else: |
| | op = b0 |
| | try: |
| | operator = self.operators[op] |
| | except KeyError: |
| | return None, index |
| | value = self.handle_operator(operator) |
| | return value, index |
| |
|
| |
|
| | def read_byte(self, b0, data, index): |
| | return b0 - 139, index |
| |
|
| |
|
| | def read_smallInt1(self, b0, data, index): |
| | b1 = byteord(data[index]) |
| | return (b0 - 247) * 256 + b1 + 108, index + 1 |
| |
|
| |
|
| | def read_smallInt2(self, b0, data, index): |
| | b1 = byteord(data[index]) |
| | return -(b0 - 251) * 256 - b1 - 108, index + 1 |
| |
|
| |
|
| | def read_shortInt(self, b0, data, index): |
| | (value,) = struct.unpack(">h", data[index : index + 2]) |
| | return value, index + 2 |
| |
|
| |
|
| | def read_longInt(self, b0, data, index): |
| | (value,) = struct.unpack(">l", data[index : index + 4]) |
| | return value, index + 4 |
| |
|
| |
|
| | def read_fixed1616(self, b0, data, index): |
| | (value,) = struct.unpack(">l", data[index : index + 4]) |
| | return fixedToFloat(value, precisionBits=16), index + 4 |
| |
|
| |
|
| | def read_reserved(self, b0, data, index): |
| | assert NotImplementedError |
| | return NotImplemented, index |
| |
|
| |
|
| | def read_realNumber(self, b0, data, index): |
| | number = "" |
| | while True: |
| | b = byteord(data[index]) |
| | index = index + 1 |
| | nibble0 = (b & 0xF0) >> 4 |
| | nibble1 = b & 0x0F |
| | if nibble0 == 0xF: |
| | break |
| | number = number + realNibbles[nibble0] |
| | if nibble1 == 0xF: |
| | break |
| | number = number + realNibbles[nibble1] |
| | return float(number), index |
| |
|
| |
|
| | t1OperandEncoding = [None] * 256 |
| | t1OperandEncoding[0:32] = (32) * [read_operator] |
| | t1OperandEncoding[32:247] = (247 - 32) * [read_byte] |
| | t1OperandEncoding[247:251] = (251 - 247) * [read_smallInt1] |
| | t1OperandEncoding[251:255] = (255 - 251) * [read_smallInt2] |
| | t1OperandEncoding[255] = read_longInt |
| | assert len(t1OperandEncoding) == 256 |
| |
|
| | t2OperandEncoding = t1OperandEncoding[:] |
| | t2OperandEncoding[28] = read_shortInt |
| | t2OperandEncoding[255] = read_fixed1616 |
| |
|
| | cffDictOperandEncoding = t2OperandEncoding[:] |
| | cffDictOperandEncoding[29] = read_longInt |
| | cffDictOperandEncoding[30] = read_realNumber |
| | cffDictOperandEncoding[255] = read_reserved |
| |
|
| |
|
| | realNibbles = [ |
| | "0", |
| | "1", |
| | "2", |
| | "3", |
| | "4", |
| | "5", |
| | "6", |
| | "7", |
| | "8", |
| | "9", |
| | ".", |
| | "E", |
| | "E-", |
| | None, |
| | "-", |
| | ] |
| | realNibblesDict = {v: i for i, v in enumerate(realNibbles)} |
| |
|
| | maxOpStack = 193 |
| |
|
| |
|
| | def buildOperatorDict(operatorList): |
| | oper = {} |
| | opc = {} |
| | for item in operatorList: |
| | if len(item) == 2: |
| | oper[item[0]] = item[1] |
| | else: |
| | oper[item[0]] = item[1:] |
| | if isinstance(item[0], tuple): |
| | opc[item[1]] = item[0] |
| | else: |
| | opc[item[1]] = (item[0],) |
| | return oper, opc |
| |
|
| |
|
| | t2Operators = [ |
| | |
| | (1, "hstem"), |
| | (3, "vstem"), |
| | (4, "vmoveto"), |
| | (5, "rlineto"), |
| | (6, "hlineto"), |
| | (7, "vlineto"), |
| | (8, "rrcurveto"), |
| | (10, "callsubr"), |
| | (11, "return"), |
| | (14, "endchar"), |
| | (15, "vsindex"), |
| | (16, "blend"), |
| | (18, "hstemhm"), |
| | (19, "hintmask"), |
| | (20, "cntrmask"), |
| | (21, "rmoveto"), |
| | (22, "hmoveto"), |
| | (23, "vstemhm"), |
| | (24, "rcurveline"), |
| | (25, "rlinecurve"), |
| | (26, "vvcurveto"), |
| | (27, "hhcurveto"), |
| | |
| | (29, "callgsubr"), |
| | (30, "vhcurveto"), |
| | (31, "hvcurveto"), |
| | ((12, 0), "ignore"), |
| | |
| | ((12, 3), "and"), |
| | ((12, 4), "or"), |
| | ((12, 5), "not"), |
| | ((12, 8), "store"), |
| | ((12, 9), "abs"), |
| | ((12, 10), "add"), |
| | ((12, 11), "sub"), |
| | ((12, 12), "div"), |
| | ((12, 13), "load"), |
| | ((12, 14), "neg"), |
| | ((12, 15), "eq"), |
| | ((12, 18), "drop"), |
| | ((12, 20), "put"), |
| | ((12, 21), "get"), |
| | ((12, 22), "ifelse"), |
| | ((12, 23), "random"), |
| | ((12, 24), "mul"), |
| | ((12, 26), "sqrt"), |
| | ((12, 27), "dup"), |
| | ((12, 28), "exch"), |
| | ((12, 29), "index"), |
| | ((12, 30), "roll"), |
| | ((12, 34), "hflex"), |
| | ((12, 35), "flex"), |
| | ((12, 36), "hflex1"), |
| | ((12, 37), "flex1"), |
| | ] |
| |
|
| |
|
| | def getIntEncoder(format): |
| | if format == "cff": |
| | twoByteOp = bytechr(28) |
| | fourByteOp = bytechr(29) |
| | elif format == "t1": |
| | twoByteOp = None |
| | fourByteOp = bytechr(255) |
| | else: |
| | assert format == "t2" |
| | twoByteOp = bytechr(28) |
| | fourByteOp = None |
| |
|
| | def encodeInt( |
| | value, |
| | fourByteOp=fourByteOp, |
| | bytechr=bytechr, |
| | pack=struct.pack, |
| | unpack=struct.unpack, |
| | twoByteOp=twoByteOp, |
| | ): |
| | if -107 <= value <= 107: |
| | code = bytechr(value + 139) |
| | elif 108 <= value <= 1131: |
| | value = value - 108 |
| | code = bytechr((value >> 8) + 247) + bytechr(value & 0xFF) |
| | elif -1131 <= value <= -108: |
| | value = -value - 108 |
| | code = bytechr((value >> 8) + 251) + bytechr(value & 0xFF) |
| | elif twoByteOp is not None and -32768 <= value <= 32767: |
| | code = twoByteOp + pack(">h", value) |
| | elif fourByteOp is None: |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | log.warning( |
| | "4-byte T2 number got passed to the " |
| | "IntType handler. This should happen only when reading in " |
| | "old XML files.\n" |
| | ) |
| | code = bytechr(255) + pack(">l", value) |
| | else: |
| | code = fourByteOp + pack(">l", value) |
| | return code |
| |
|
| | return encodeInt |
| |
|
| |
|
| | encodeIntCFF = getIntEncoder("cff") |
| | encodeIntT1 = getIntEncoder("t1") |
| | encodeIntT2 = getIntEncoder("t2") |
| |
|
| |
|
| | def encodeFixed(f, pack=struct.pack): |
| | """For T2 only""" |
| | value = floatToFixed(f, precisionBits=16) |
| | if value & 0xFFFF == 0: |
| | return encodeIntT2(value >> 16) |
| | else: |
| | return b"\xff" + pack(">l", value) |
| |
|
| |
|
| | realZeroBytes = bytechr(30) + bytechr(0xF) |
| |
|
| |
|
| | def encodeFloat(f): |
| | |
| | if f == 0.0: |
| | return realZeroBytes |
| | |
| | |
| | s = "%.8G" % f |
| | if s[:2] == "0.": |
| | s = s[1:] |
| | elif s[:3] == "-0.": |
| | s = "-" + s[2:] |
| | nibbles = [] |
| | while s: |
| | c = s[0] |
| | s = s[1:] |
| | if c == "E": |
| | c2 = s[:1] |
| | if c2 == "-": |
| | s = s[1:] |
| | c = "E-" |
| | elif c2 == "+": |
| | s = s[1:] |
| | nibbles.append(realNibblesDict[c]) |
| | nibbles.append(0xF) |
| | if len(nibbles) % 2: |
| | nibbles.append(0xF) |
| | d = bytechr(30) |
| | for i in range(0, len(nibbles), 2): |
| | d = d + bytechr(nibbles[i] << 4 | nibbles[i + 1]) |
| | return d |
| |
|
| |
|
| | class CharStringCompileError(Exception): |
| | pass |
| |
|
| |
|
| | class SimpleT2Decompiler(object): |
| | def __init__(self, localSubrs, globalSubrs, private=None, blender=None): |
| | self.localSubrs = localSubrs |
| | self.localBias = calcSubrBias(localSubrs) |
| | self.globalSubrs = globalSubrs |
| | self.globalBias = calcSubrBias(globalSubrs) |
| | self.private = private |
| | self.blender = blender |
| | self.reset() |
| |
|
| | def reset(self): |
| | self.callingStack = [] |
| | self.operandStack = [] |
| | self.hintCount = 0 |
| | self.hintMaskBytes = 0 |
| | self.numRegions = 0 |
| | self.vsIndex = 0 |
| |
|
| | def execute(self, charString): |
| | self.callingStack.append(charString) |
| | needsDecompilation = charString.needsDecompilation() |
| | if needsDecompilation: |
| | program = [] |
| | pushToProgram = program.append |
| | else: |
| | pushToProgram = lambda x: None |
| | pushToStack = self.operandStack.append |
| | index = 0 |
| | while True: |
| | token, isOperator, index = charString.getToken(index) |
| | if token is None: |
| | break |
| | pushToProgram(token) |
| | if isOperator: |
| | handlerName = "op_" + token |
| | handler = getattr(self, handlerName, None) |
| | if handler is not None: |
| | rv = handler(index) |
| | if rv: |
| | hintMaskBytes, index = rv |
| | pushToProgram(hintMaskBytes) |
| | else: |
| | self.popall() |
| | else: |
| | pushToStack(token) |
| | if needsDecompilation: |
| | charString.setProgram(program) |
| | del self.callingStack[-1] |
| |
|
| | def pop(self): |
| | value = self.operandStack[-1] |
| | del self.operandStack[-1] |
| | return value |
| |
|
| | def popall(self): |
| | stack = self.operandStack[:] |
| | self.operandStack[:] = [] |
| | return stack |
| |
|
| | def push(self, value): |
| | self.operandStack.append(value) |
| |
|
| | def op_return(self, index): |
| | if self.operandStack: |
| | pass |
| |
|
| | def op_endchar(self, index): |
| | pass |
| |
|
| | def op_ignore(self, index): |
| | pass |
| |
|
| | def op_callsubr(self, index): |
| | subrIndex = self.pop() |
| | subr = self.localSubrs[subrIndex + self.localBias] |
| | self.execute(subr) |
| |
|
| | def op_callgsubr(self, index): |
| | subrIndex = self.pop() |
| | subr = self.globalSubrs[subrIndex + self.globalBias] |
| | self.execute(subr) |
| |
|
| | def op_hstem(self, index): |
| | self.countHints() |
| |
|
| | def op_vstem(self, index): |
| | self.countHints() |
| |
|
| | def op_hstemhm(self, index): |
| | self.countHints() |
| |
|
| | def op_vstemhm(self, index): |
| | self.countHints() |
| |
|
| | def op_hintmask(self, index): |
| | if not self.hintMaskBytes: |
| | self.countHints() |
| | self.hintMaskBytes = (self.hintCount + 7) // 8 |
| | hintMaskBytes, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes) |
| | return hintMaskBytes, index |
| |
|
| | op_cntrmask = op_hintmask |
| |
|
| | def countHints(self): |
| | args = self.popall() |
| | self.hintCount = self.hintCount + len(args) // 2 |
| |
|
| | |
| | def op_and(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_or(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_not(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_store(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_abs(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_add(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_sub(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_div(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_load(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_neg(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_eq(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_drop(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_put(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_get(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_ifelse(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_random(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_mul(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_sqrt(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_dup(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_exch(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_index(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_roll(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_blend(self, index): |
| | if self.numRegions == 0: |
| | self.numRegions = self.private.getNumRegions() |
| | numBlends = self.pop() |
| | numOps = numBlends * (self.numRegions + 1) |
| | if self.blender is None: |
| | del self.operandStack[ |
| | -(numOps - numBlends) : |
| | ] |
| | else: |
| | argi = len(self.operandStack) - numOps |
| | end_args = tuplei = argi + numBlends |
| | while argi < end_args: |
| | next_ti = tuplei + self.numRegions |
| | deltas = self.operandStack[tuplei:next_ti] |
| | delta = self.blender(self.vsIndex, deltas) |
| | self.operandStack[argi] += delta |
| | tuplei = next_ti |
| | argi += 1 |
| | self.operandStack[end_args:] = [] |
| |
|
| | def op_vsindex(self, index): |
| | vi = self.pop() |
| | self.vsIndex = vi |
| | self.numRegions = self.private.getNumRegions(vi) |
| |
|
| |
|
| | t1Operators = [ |
| | |
| | (1, "hstem"), |
| | (3, "vstem"), |
| | (4, "vmoveto"), |
| | (5, "rlineto"), |
| | (6, "hlineto"), |
| | (7, "vlineto"), |
| | (8, "rrcurveto"), |
| | (9, "closepath"), |
| | (10, "callsubr"), |
| | (11, "return"), |
| | (13, "hsbw"), |
| | (14, "endchar"), |
| | (21, "rmoveto"), |
| | (22, "hmoveto"), |
| | (30, "vhcurveto"), |
| | (31, "hvcurveto"), |
| | ((12, 0), "dotsection"), |
| | ((12, 1), "vstem3"), |
| | ((12, 2), "hstem3"), |
| | ((12, 6), "seac"), |
| | ((12, 7), "sbw"), |
| | ((12, 12), "div"), |
| | ((12, 16), "callothersubr"), |
| | ((12, 17), "pop"), |
| | ((12, 33), "setcurrentpoint"), |
| | ] |
| |
|
| |
|
| | class T2WidthExtractor(SimpleT2Decompiler): |
| | def __init__( |
| | self, |
| | localSubrs, |
| | globalSubrs, |
| | nominalWidthX, |
| | defaultWidthX, |
| | private=None, |
| | blender=None, |
| | ): |
| | SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private, blender) |
| | self.nominalWidthX = nominalWidthX |
| | self.defaultWidthX = defaultWidthX |
| |
|
| | def reset(self): |
| | SimpleT2Decompiler.reset(self) |
| | self.gotWidth = 0 |
| | self.width = 0 |
| |
|
| | def popallWidth(self, evenOdd=0): |
| | args = self.popall() |
| | if not self.gotWidth: |
| | if evenOdd ^ (len(args) % 2): |
| | |
| | assert ( |
| | self.defaultWidthX is not None |
| | ), "CFF2 CharStrings must not have an initial width value" |
| | self.width = self.nominalWidthX + args[0] |
| | args = args[1:] |
| | else: |
| | self.width = self.defaultWidthX |
| | self.gotWidth = 1 |
| | return args |
| |
|
| | def countHints(self): |
| | args = self.popallWidth() |
| | self.hintCount = self.hintCount + len(args) // 2 |
| |
|
| | def op_rmoveto(self, index): |
| | self.popallWidth() |
| |
|
| | def op_hmoveto(self, index): |
| | self.popallWidth(1) |
| |
|
| | def op_vmoveto(self, index): |
| | self.popallWidth(1) |
| |
|
| | def op_endchar(self, index): |
| | self.popallWidth() |
| |
|
| |
|
| | class T2OutlineExtractor(T2WidthExtractor): |
| | def __init__( |
| | self, |
| | pen, |
| | localSubrs, |
| | globalSubrs, |
| | nominalWidthX, |
| | defaultWidthX, |
| | private=None, |
| | blender=None, |
| | ): |
| | T2WidthExtractor.__init__( |
| | self, |
| | localSubrs, |
| | globalSubrs, |
| | nominalWidthX, |
| | defaultWidthX, |
| | private, |
| | blender, |
| | ) |
| | self.pen = pen |
| | self.subrLevel = 0 |
| |
|
| | def reset(self): |
| | T2WidthExtractor.reset(self) |
| | self.currentPoint = (0, 0) |
| | self.sawMoveTo = 0 |
| | self.subrLevel = 0 |
| |
|
| | def execute(self, charString): |
| | self.subrLevel += 1 |
| | super().execute(charString) |
| | self.subrLevel -= 1 |
| | if self.subrLevel == 0: |
| | self.endPath() |
| |
|
| | def _nextPoint(self, point): |
| | x, y = self.currentPoint |
| | point = x + point[0], y + point[1] |
| | self.currentPoint = point |
| | return point |
| |
|
| | def rMoveTo(self, point): |
| | self.pen.moveTo(self._nextPoint(point)) |
| | self.sawMoveTo = 1 |
| |
|
| | def rLineTo(self, point): |
| | if not self.sawMoveTo: |
| | self.rMoveTo((0, 0)) |
| | self.pen.lineTo(self._nextPoint(point)) |
| |
|
| | def rCurveTo(self, pt1, pt2, pt3): |
| | if not self.sawMoveTo: |
| | self.rMoveTo((0, 0)) |
| | nextPoint = self._nextPoint |
| | self.pen.curveTo(nextPoint(pt1), nextPoint(pt2), nextPoint(pt3)) |
| |
|
| | def closePath(self): |
| | if self.sawMoveTo: |
| | self.pen.closePath() |
| | self.sawMoveTo = 0 |
| |
|
| | def endPath(self): |
| | |
| | |
| | |
| | |
| | if self.sawMoveTo: |
| | self.closePath() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | def op_rmoveto(self, index): |
| | self.endPath() |
| | self.rMoveTo(self.popallWidth()) |
| |
|
| | def op_hmoveto(self, index): |
| | self.endPath() |
| | self.rMoveTo((self.popallWidth(1)[0], 0)) |
| |
|
| | def op_vmoveto(self, index): |
| | self.endPath() |
| | self.rMoveTo((0, self.popallWidth(1)[0])) |
| |
|
| | def op_endchar(self, index): |
| | self.endPath() |
| | args = self.popallWidth() |
| | if args: |
| | from fontTools.encodings.StandardEncoding import StandardEncoding |
| |
|
| | |
| | |
| | adx, ady, bchar, achar = args |
| | baseGlyph = StandardEncoding[bchar] |
| | self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0)) |
| | accentGlyph = StandardEncoding[achar] |
| | self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady)) |
| |
|
| | |
| | |
| | |
| | def op_rlineto(self, index): |
| | args = self.popall() |
| | for i in range(0, len(args), 2): |
| | point = args[i : i + 2] |
| | self.rLineTo(point) |
| |
|
| | def op_hlineto(self, index): |
| | self.alternatingLineto(1) |
| |
|
| | def op_vlineto(self, index): |
| | self.alternatingLineto(0) |
| |
|
| | |
| | |
| | |
| | def op_rrcurveto(self, index): |
| | """{dxa dya dxb dyb dxc dyc}+ rrcurveto""" |
| | args = self.popall() |
| | for i in range(0, len(args), 6): |
| | ( |
| | dxa, |
| | dya, |
| | dxb, |
| | dyb, |
| | dxc, |
| | dyc, |
| | ) = args[i : i + 6] |
| | self.rCurveTo((dxa, dya), (dxb, dyb), (dxc, dyc)) |
| |
|
| | def op_rcurveline(self, index): |
| | """{dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline""" |
| | args = self.popall() |
| | for i in range(0, len(args) - 2, 6): |
| | dxb, dyb, dxc, dyc, dxd, dyd = args[i : i + 6] |
| | self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd)) |
| | self.rLineTo(args[-2:]) |
| |
|
| | def op_rlinecurve(self, index): |
| | """{dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve""" |
| | args = self.popall() |
| | lineArgs = args[:-6] |
| | for i in range(0, len(lineArgs), 2): |
| | self.rLineTo(lineArgs[i : i + 2]) |
| | dxb, dyb, dxc, dyc, dxd, dyd = args[-6:] |
| | self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd)) |
| |
|
| | def op_vvcurveto(self, index): |
| | "dx1? {dya dxb dyb dyc}+ vvcurveto" |
| | args = self.popall() |
| | if len(args) % 2: |
| | dx1 = args[0] |
| | args = args[1:] |
| | else: |
| | dx1 = 0 |
| | for i in range(0, len(args), 4): |
| | dya, dxb, dyb, dyc = args[i : i + 4] |
| | self.rCurveTo((dx1, dya), (dxb, dyb), (0, dyc)) |
| | dx1 = 0 |
| |
|
| | def op_hhcurveto(self, index): |
| | """dy1? {dxa dxb dyb dxc}+ hhcurveto""" |
| | args = self.popall() |
| | if len(args) % 2: |
| | dy1 = args[0] |
| | args = args[1:] |
| | else: |
| | dy1 = 0 |
| | for i in range(0, len(args), 4): |
| | dxa, dxb, dyb, dxc = args[i : i + 4] |
| | self.rCurveTo((dxa, dy1), (dxb, dyb), (dxc, 0)) |
| | dy1 = 0 |
| |
|
| | def op_vhcurveto(self, index): |
| | """dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30) |
| | {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto |
| | """ |
| | args = self.popall() |
| | while args: |
| | args = self.vcurveto(args) |
| | if args: |
| | args = self.hcurveto(args) |
| |
|
| | def op_hvcurveto(self, index): |
| | """dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? |
| | {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? |
| | """ |
| | args = self.popall() |
| | while args: |
| | args = self.hcurveto(args) |
| | if args: |
| | args = self.vcurveto(args) |
| |
|
| | |
| | |
| | |
| | def op_hflex(self, index): |
| | dx1, dx2, dy2, dx3, dx4, dx5, dx6 = self.popall() |
| | dy1 = dy3 = dy4 = dy6 = 0 |
| | dy5 = -dy2 |
| | self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3)) |
| | self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6)) |
| |
|
| | def op_flex(self, index): |
| | dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, dx6, dy6, fd = self.popall() |
| | self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3)) |
| | self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6)) |
| |
|
| | def op_hflex1(self, index): |
| | dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6 = self.popall() |
| | dy3 = dy4 = 0 |
| | dy6 = -(dy1 + dy2 + dy3 + dy4 + dy5) |
| |
|
| | self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3)) |
| | self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6)) |
| |
|
| | def op_flex1(self, index): |
| | dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, d6 = self.popall() |
| | dx = dx1 + dx2 + dx3 + dx4 + dx5 |
| | dy = dy1 + dy2 + dy3 + dy4 + dy5 |
| | if abs(dx) > abs(dy): |
| | dx6 = d6 |
| | dy6 = -dy |
| | else: |
| | dx6 = -dx |
| | dy6 = d6 |
| | self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3)) |
| | self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6)) |
| |
|
| | |
| | def op_and(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_or(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_not(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_store(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_abs(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_add(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_sub(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_div(self, index): |
| | num2 = self.pop() |
| | num1 = self.pop() |
| | d1 = num1 // num2 |
| | d2 = num1 / num2 |
| | if d1 == d2: |
| | self.push(d1) |
| | else: |
| | self.push(d2) |
| |
|
| | def op_load(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_neg(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_eq(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_drop(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_put(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_get(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_ifelse(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_random(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_mul(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_sqrt(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_dup(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_exch(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_index(self, index): |
| | raise NotImplementedError |
| |
|
| | def op_roll(self, index): |
| | raise NotImplementedError |
| |
|
| | |
| | |
| | |
| | def alternatingLineto(self, isHorizontal): |
| | args = self.popall() |
| | for arg in args: |
| | if isHorizontal: |
| | point = (arg, 0) |
| | else: |
| | point = (0, arg) |
| | self.rLineTo(point) |
| | isHorizontal = not isHorizontal |
| |
|
| | def vcurveto(self, args): |
| | dya, dxb, dyb, dxc = args[:4] |
| | args = args[4:] |
| | if len(args) == 1: |
| | dyc = args[0] |
| | args = [] |
| | else: |
| | dyc = 0 |
| | self.rCurveTo((0, dya), (dxb, dyb), (dxc, dyc)) |
| | return args |
| |
|
| | def hcurveto(self, args): |
| | dxa, dxb, dyb, dyc = args[:4] |
| | args = args[4:] |
| | if len(args) == 1: |
| | dxc = args[0] |
| | args = [] |
| | else: |
| | dxc = 0 |
| | self.rCurveTo((dxa, 0), (dxb, dyb), (dxc, dyc)) |
| | return args |
| |
|
| |
|
| | class T1OutlineExtractor(T2OutlineExtractor): |
| | def __init__(self, pen, subrs): |
| | self.pen = pen |
| | self.subrs = subrs |
| | self.reset() |
| |
|
| | def reset(self): |
| | self.flexing = 0 |
| | self.width = 0 |
| | self.sbx = 0 |
| | T2OutlineExtractor.reset(self) |
| |
|
| | def endPath(self): |
| | if self.sawMoveTo: |
| | self.pen.endPath() |
| | self.sawMoveTo = 0 |
| |
|
| | def popallWidth(self, evenOdd=0): |
| | return self.popall() |
| |
|
| | def exch(self): |
| | stack = self.operandStack |
| | stack[-1], stack[-2] = stack[-2], stack[-1] |
| |
|
| | |
| | |
| | |
| | def op_rmoveto(self, index): |
| | if self.flexing: |
| | return |
| | self.endPath() |
| | self.rMoveTo(self.popall()) |
| |
|
| | def op_hmoveto(self, index): |
| | if self.flexing: |
| | |
| | self.push(0) |
| | return |
| | self.endPath() |
| | self.rMoveTo((self.popall()[0], 0)) |
| |
|
| | def op_vmoveto(self, index): |
| | if self.flexing: |
| | |
| | self.push(0) |
| | self.exch() |
| | return |
| | self.endPath() |
| | self.rMoveTo((0, self.popall()[0])) |
| |
|
| | def op_closepath(self, index): |
| | self.closePath() |
| |
|
| | def op_setcurrentpoint(self, index): |
| | args = self.popall() |
| | x, y = args |
| | self.currentPoint = x, y |
| |
|
| | def op_endchar(self, index): |
| | self.endPath() |
| |
|
| | def op_hsbw(self, index): |
| | sbx, wx = self.popall() |
| | self.width = wx |
| | self.sbx = sbx |
| | self.currentPoint = sbx, self.currentPoint[1] |
| |
|
| | def op_sbw(self, index): |
| | self.popall() |
| |
|
| | |
| | def op_callsubr(self, index): |
| | subrIndex = self.pop() |
| | subr = self.subrs[subrIndex] |
| | self.execute(subr) |
| |
|
| | def op_callothersubr(self, index): |
| | subrIndex = self.pop() |
| | nArgs = self.pop() |
| | |
| | if subrIndex == 0 and nArgs == 3: |
| | self.doFlex() |
| | self.flexing = 0 |
| | elif subrIndex == 1 and nArgs == 0: |
| | self.flexing = 1 |
| | |
| |
|
| | def op_pop(self, index): |
| | pass |
| |
|
| | def doFlex(self): |
| | finaly = self.pop() |
| | finalx = self.pop() |
| | self.pop() |
| |
|
| | p3y = self.pop() |
| | p3x = self.pop() |
| | bcp4y = self.pop() |
| | bcp4x = self.pop() |
| | bcp3y = self.pop() |
| | bcp3x = self.pop() |
| | p2y = self.pop() |
| | p2x = self.pop() |
| | bcp2y = self.pop() |
| | bcp2x = self.pop() |
| | bcp1y = self.pop() |
| | bcp1x = self.pop() |
| | rpy = self.pop() |
| | rpx = self.pop() |
| |
|
| | |
| | self.push(bcp1x + rpx) |
| | self.push(bcp1y + rpy) |
| | self.push(bcp2x) |
| | self.push(bcp2y) |
| | self.push(p2x) |
| | self.push(p2y) |
| | self.op_rrcurveto(None) |
| |
|
| | |
| | self.push(bcp3x) |
| | self.push(bcp3y) |
| | self.push(bcp4x) |
| | self.push(bcp4y) |
| | self.push(p3x) |
| | self.push(p3y) |
| | self.op_rrcurveto(None) |
| |
|
| | |
| | self.push(finalx) |
| | self.push(finaly) |
| |
|
| | def op_dotsection(self, index): |
| | self.popall() |
| |
|
| | def op_hstem3(self, index): |
| | self.popall() |
| |
|
| | def op_seac(self, index): |
| | "asb adx ady bchar achar seac" |
| | from fontTools.encodings.StandardEncoding import StandardEncoding |
| |
|
| | asb, adx, ady, bchar, achar = self.popall() |
| | baseGlyph = StandardEncoding[bchar] |
| | self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0)) |
| | accentGlyph = StandardEncoding[achar] |
| | adx = adx + self.sbx - asb |
| | self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady)) |
| |
|
| | def op_vstem3(self, index): |
| | self.popall() |
| |
|
| |
|
| | class T2CharString(object): |
| | operandEncoding = t2OperandEncoding |
| | operators, opcodes = buildOperatorDict(t2Operators) |
| | decompilerClass = SimpleT2Decompiler |
| | outlineExtractor = T2OutlineExtractor |
| |
|
| | def __init__(self, bytecode=None, program=None, private=None, globalSubrs=None): |
| | if program is None: |
| | program = [] |
| | self.bytecode = bytecode |
| | self.program = program |
| | self.private = private |
| | self.globalSubrs = globalSubrs if globalSubrs is not None else [] |
| | self._cur_vsindex = None |
| |
|
| | def getNumRegions(self, vsindex=None): |
| | pd = self.private |
| | assert pd is not None |
| | if vsindex is not None: |
| | self._cur_vsindex = vsindex |
| | elif self._cur_vsindex is None: |
| | self._cur_vsindex = pd.vsindex if hasattr(pd, "vsindex") else 0 |
| | return pd.getNumRegions(self._cur_vsindex) |
| |
|
| | def __repr__(self): |
| | if self.bytecode is None: |
| | return "<%s (source) at %x>" % (self.__class__.__name__, id(self)) |
| | else: |
| | return "<%s (bytecode) at %x>" % (self.__class__.__name__, id(self)) |
| |
|
| | def getIntEncoder(self): |
| | return encodeIntT2 |
| |
|
| | def getFixedEncoder(self): |
| | return encodeFixed |
| |
|
| | def decompile(self): |
| | if not self.needsDecompilation(): |
| | return |
| | subrs = getattr(self.private, "Subrs", []) |
| | decompiler = self.decompilerClass(subrs, self.globalSubrs, self.private) |
| | decompiler.execute(self) |
| |
|
| | def draw(self, pen, blender=None): |
| | subrs = getattr(self.private, "Subrs", []) |
| | extractor = self.outlineExtractor( |
| | pen, |
| | subrs, |
| | self.globalSubrs, |
| | self.private.nominalWidthX, |
| | self.private.defaultWidthX, |
| | self.private, |
| | blender, |
| | ) |
| | extractor.execute(self) |
| | self.width = extractor.width |
| |
|
| | def calcBounds(self, glyphSet): |
| | boundsPen = BoundsPen(glyphSet) |
| | self.draw(boundsPen) |
| | return boundsPen.bounds |
| |
|
| | def compile(self, isCFF2=False): |
| | if self.bytecode is not None: |
| | return |
| | opcodes = self.opcodes |
| | program = self.program |
| |
|
| | if isCFF2: |
| | |
| | if program and program[-1] in ("return", "endchar"): |
| | program = program[:-1] |
| | elif program and not isinstance(program[-1], str): |
| | raise CharStringCompileError( |
| | "T2CharString or Subr has items on the stack after last operator." |
| | ) |
| |
|
| | bytecode = [] |
| | encodeInt = self.getIntEncoder() |
| | encodeFixed = self.getFixedEncoder() |
| | i = 0 |
| | end = len(program) |
| | while i < end: |
| | token = program[i] |
| | i = i + 1 |
| | if isinstance(token, str): |
| | try: |
| | bytecode.extend(bytechr(b) for b in opcodes[token]) |
| | except KeyError: |
| | raise CharStringCompileError("illegal operator: %s" % token) |
| | if token in ("hintmask", "cntrmask"): |
| | bytecode.append(program[i]) |
| | i = i + 1 |
| | elif isinstance(token, int): |
| | bytecode.append(encodeInt(token)) |
| | elif isinstance(token, float): |
| | bytecode.append(encodeFixed(token)) |
| | else: |
| | assert 0, "unsupported type: %s" % type(token) |
| | try: |
| | bytecode = bytesjoin(bytecode) |
| | except TypeError: |
| | log.error(bytecode) |
| | raise |
| | self.setBytecode(bytecode) |
| |
|
| | def needsDecompilation(self): |
| | return self.bytecode is not None |
| |
|
| | def setProgram(self, program): |
| | self.program = program |
| | self.bytecode = None |
| |
|
| | def setBytecode(self, bytecode): |
| | self.bytecode = bytecode |
| | self.program = None |
| |
|
| | def getToken(self, index, len=len, byteord=byteord, isinstance=isinstance): |
| | if self.bytecode is not None: |
| | if index >= len(self.bytecode): |
| | return None, 0, 0 |
| | b0 = byteord(self.bytecode[index]) |
| | index = index + 1 |
| | handler = self.operandEncoding[b0] |
| | token, index = handler(self, b0, self.bytecode, index) |
| | else: |
| | if index >= len(self.program): |
| | return None, 0, 0 |
| | token = self.program[index] |
| | index = index + 1 |
| | isOperator = isinstance(token, str) |
| | return token, isOperator, index |
| |
|
| | def getBytes(self, index, nBytes): |
| | if self.bytecode is not None: |
| | newIndex = index + nBytes |
| | bytes = self.bytecode[index:newIndex] |
| | index = newIndex |
| | else: |
| | bytes = self.program[index] |
| | index = index + 1 |
| | assert len(bytes) == nBytes |
| | return bytes, index |
| |
|
| | def handle_operator(self, operator): |
| | return operator |
| |
|
| | def toXML(self, xmlWriter, ttFont=None): |
| | from fontTools.misc.textTools import num2binary |
| |
|
| | if self.bytecode is not None: |
| | xmlWriter.dumphex(self.bytecode) |
| | else: |
| | index = 0 |
| | args = [] |
| | while True: |
| | token, isOperator, index = self.getToken(index) |
| | if token is None: |
| | break |
| | if isOperator: |
| | if token in ("hintmask", "cntrmask"): |
| | hintMask, isOperator, index = self.getToken(index) |
| | bits = [] |
| | for byte in hintMask: |
| | bits.append(num2binary(byteord(byte), 8)) |
| | hintMask = strjoin(bits) |
| | line = " ".join(args + [token, hintMask]) |
| | else: |
| | line = " ".join(args + [token]) |
| | xmlWriter.write(line) |
| | xmlWriter.newline() |
| | args = [] |
| | else: |
| | if isinstance(token, float): |
| | token = floatToFixedToStr(token, precisionBits=16) |
| | else: |
| | token = str(token) |
| | args.append(token) |
| | if args: |
| | |
| | |
| | |
| | line = " ".join(args) |
| | xmlWriter.write(line) |
| |
|
| | def fromXML(self, name, attrs, content): |
| | from fontTools.misc.textTools import binary2num, readHex |
| |
|
| | if attrs.get("raw"): |
| | self.setBytecode(readHex(content)) |
| | return |
| | content = strjoin(content) |
| | content = content.split() |
| | program = [] |
| | end = len(content) |
| | i = 0 |
| | while i < end: |
| | token = content[i] |
| | i = i + 1 |
| | try: |
| | token = int(token) |
| | except ValueError: |
| | try: |
| | token = strToFixedToFloat(token, precisionBits=16) |
| | except ValueError: |
| | program.append(token) |
| | if token in ("hintmask", "cntrmask"): |
| | mask = content[i] |
| | maskBytes = b"" |
| | for j in range(0, len(mask), 8): |
| | maskBytes = maskBytes + bytechr(binary2num(mask[j : j + 8])) |
| | program.append(maskBytes) |
| | i = i + 1 |
| | else: |
| | program.append(token) |
| | else: |
| | program.append(token) |
| | self.setProgram(program) |
| |
|
| |
|
| | class T1CharString(T2CharString): |
| | operandEncoding = t1OperandEncoding |
| | operators, opcodes = buildOperatorDict(t1Operators) |
| |
|
| | def __init__(self, bytecode=None, program=None, subrs=None): |
| | super().__init__(bytecode, program) |
| | self.subrs = subrs |
| |
|
| | def getIntEncoder(self): |
| | return encodeIntT1 |
| |
|
| | def getFixedEncoder(self): |
| | def encodeFixed(value): |
| | raise TypeError("Type 1 charstrings don't support floating point operands") |
| |
|
| | def decompile(self): |
| | if self.bytecode is None: |
| | return |
| | program = [] |
| | index = 0 |
| | while True: |
| | token, isOperator, index = self.getToken(index) |
| | if token is None: |
| | break |
| | program.append(token) |
| | self.setProgram(program) |
| |
|
| | def draw(self, pen): |
| | extractor = T1OutlineExtractor(pen, self.subrs) |
| | extractor.execute(self) |
| | self.width = extractor.width |
| |
|
| |
|
| | class DictDecompiler(object): |
| | operandEncoding = cffDictOperandEncoding |
| |
|
| | def __init__(self, strings, parent=None): |
| | self.stack = [] |
| | self.strings = strings |
| | self.dict = {} |
| | self.parent = parent |
| |
|
| | def getDict(self): |
| | assert len(self.stack) == 0, "non-empty stack" |
| | return self.dict |
| |
|
| | def decompile(self, data): |
| | index = 0 |
| | lenData = len(data) |
| | push = self.stack.append |
| | while index < lenData: |
| | b0 = byteord(data[index]) |
| | index = index + 1 |
| | handler = self.operandEncoding[b0] |
| | value, index = handler(self, b0, data, index) |
| | if value is not None: |
| | push(value) |
| |
|
| | def pop(self): |
| | value = self.stack[-1] |
| | del self.stack[-1] |
| | return value |
| |
|
| | def popall(self): |
| | args = self.stack[:] |
| | del self.stack[:] |
| | return args |
| |
|
| | def handle_operator(self, operator): |
| | operator, argType = operator |
| | if isinstance(argType, tuple): |
| | value = () |
| | for i in range(len(argType) - 1, -1, -1): |
| | arg = argType[i] |
| | arghandler = getattr(self, "arg_" + arg) |
| | value = (arghandler(operator),) + value |
| | else: |
| | arghandler = getattr(self, "arg_" + argType) |
| | value = arghandler(operator) |
| | if operator == "blend": |
| | self.stack.extend(value) |
| | else: |
| | self.dict[operator] = value |
| |
|
| | def arg_number(self, name): |
| | if isinstance(self.stack[0], list): |
| | out = self.arg_blend_number(self.stack) |
| | else: |
| | out = self.pop() |
| | return out |
| |
|
| | def arg_blend_number(self, name): |
| | out = [] |
| | blendArgs = self.pop() |
| | numMasters = len(blendArgs) |
| | out.append(blendArgs) |
| | out.append("blend") |
| | dummy = self.popall() |
| | return blendArgs |
| |
|
| | def arg_SID(self, name): |
| | return self.strings[self.pop()] |
| |
|
| | def arg_array(self, name): |
| | return self.popall() |
| |
|
| | def arg_blendList(self, name): |
| | """ |
| | There may be non-blend args at the top of the stack. We first calculate |
| | where the blend args start in the stack. These are the last |
| | numMasters*numBlends) +1 args. |
| | The blend args starts with numMasters relative coordinate values, the BlueValues in the list from the default master font. This is followed by |
| | numBlends list of values. Each of value in one of these lists is the |
| | Variable Font delta for the matching region. |
| | |
| | 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 |
| | the delta values. We then convert the default values, the first item in each entry, to an absolute value. |
| | """ |
| | vsindex = self.dict.get("vsindex", 0) |
| | numMasters = ( |
| | self.parent.getNumRegions(vsindex) + 1 |
| | ) |
| | numBlends = self.pop() |
| | args = self.popall() |
| | numArgs = len(args) |
| | |
| | assert numArgs == numMasters * numBlends |
| | value = [None] * numBlends |
| | numDeltas = numMasters - 1 |
| | i = 0 |
| | prevVal = 0 |
| | while i < numBlends: |
| | newVal = args[i] + prevVal |
| | prevVal = newVal |
| | masterOffset = numBlends + (i * numDeltas) |
| | blendList = [newVal] + args[masterOffset : masterOffset + numDeltas] |
| | value[i] = blendList |
| | i += 1 |
| | return value |
| |
|
| | def arg_delta(self, name): |
| | valueList = self.popall() |
| | out = [] |
| | if valueList and isinstance(valueList[0], list): |
| | |
| | out = valueList |
| | else: |
| | current = 0 |
| | for v in valueList: |
| | current = current + v |
| | out.append(current) |
| | return out |
| |
|
| |
|
| | def calcSubrBias(subrs): |
| | nSubrs = len(subrs) |
| | if nSubrs < 1240: |
| | bias = 107 |
| | elif nSubrs < 33900: |
| | bias = 1131 |
| | else: |
| | bias = 32768 |
| | return bias |
| |
|