12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496 |
- """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 = [
- # opcode name
- (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"),
- # (28, 'shortint'), # not really an operator
- (29, "callgsubr"),
- (30, "vhcurveto"),
- (31, "hvcurveto"),
- ((12, 0), "ignore"), # dotsection. Yes, there a few very early OTF/CFF
- # fonts with this deprecated operator. Just ignore it.
- ((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:
- # Backwards compatible hack: due to a previous bug in FontTools,
- # 16.16 fixed numbers were written out as 4-byte ints. When
- # these numbers were small, they were wrongly written back as
- # small ints instead of 4-byte ints, breaking round-tripping.
- # This here workaround doesn't do it any better, since we can't
- # distinguish anymore between small ints that were supposed to
- # be small fixed numbers and small ints that were just small
- # ints. Hence the warning.
- 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: # check if the fractional part is zero
- return encodeIntT2(value >> 16) # encode only the integer part
- else:
- return b"\xff" + pack(">l", value) # encode the entire fixed point value
- realZeroBytes = bytechr(30) + bytechr(0xF)
- def encodeFloat(f):
- # For CFF only, used in cffLib
- if f == 0.0: # 0.0 == +0.0 == -0.0
- return realZeroBytes
- # Note: 14 decimal digits seems to be the limitation for CFF real numbers
- # in macOS. However, we use 8 here to match the implementation of AFDKO.
- s = "%.8G" % f
- if s[:2] == "0.":
- s = s[1:]
- elif s[:3] == "-0.":
- s = "-" + s[2:]
- elif s.endswith("000"):
- significantDigits = s.rstrip("0")
- s = "%sE%d" % (significantDigits, len(s) - len(significantDigits))
- else:
- dotIndex = s.find(".")
- eIndex = s.find("E")
- if dotIndex != -1 and eIndex != -1:
- integerPart = s[:dotIndex]
- fractionalPart = s[dotIndex + 1 : eIndex]
- exponent = int(s[eIndex + 1 :])
- newExponent = exponent - len(fractionalPart)
- if newExponent == 1:
- s = "%s%s0" % (integerPart, fractionalPart)
- else:
- s = "%s%sE%d" % (integerPart, fractionalPart, newExponent)
- if s.startswith((".0", "-.0")):
- sign, s = s.split(".", 1)
- s = "%s%sE-%d" % (sign, s.lstrip("0"), len(s))
- 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:]
- if s.startswith("0"):
- 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 # we're done!
- 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
- # misc
- 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) :
- ] # Leave the default operands on the stack.
- 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 = [
- # opcode name
- (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):
- # For CFF2 charstrings, this should never happen
- 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):
- # In T2 there are no open paths, so always do a closePath when
- # finishing a sub path. We avoid spurious calls to closePath()
- # because its a real T1 op we're emulating in T2 whereas
- # endPath() is just a means to that emulation
- if self.sawMoveTo:
- self.closePath()
- #
- # hint operators
- #
- # 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):
- # self.countHints()
- # def op_cntrmask(self, index):
- # self.countHints()
- #
- # path constructors, moveto
- #
- 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
- # endchar can do seac accent bulding; The T2 spec says it's deprecated,
- # but recent software that shall remain nameless does output it.
- 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))
- #
- # path constructors, lines
- #
- 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)
- #
- # path constructors, curves
- #
- 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)
- #
- # path constructors, flex
- #
- 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))
- # misc
- 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
- #
- # miscellaneous helpers
- #
- 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]
- #
- # path constructors
- #
- def op_rmoveto(self, index):
- if self.flexing:
- return
- self.endPath()
- self.rMoveTo(self.popall())
- def op_hmoveto(self, index):
- if self.flexing:
- # We must add a parameter to the stack if we are flexing
- self.push(0)
- return
- self.endPath()
- self.rMoveTo((self.popall()[0], 0))
- def op_vmoveto(self, index):
- if self.flexing:
- # We must add a parameter to the stack if we are 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() # XXX
- #
- 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()
- # print nArgs, subrIndex, "callothersubr"
- if subrIndex == 0 and nArgs == 3:
- self.doFlex()
- self.flexing = 0
- elif subrIndex == 1 and nArgs == 0:
- self.flexing = 1
- # ignore...
- def op_pop(self, index):
- pass # ignore...
- def doFlex(self):
- finaly = self.pop()
- finalx = self.pop()
- self.pop() # flex height is unused
- 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()
- # call rrcurveto
- self.push(bcp1x + rpx)
- self.push(bcp1y + rpy)
- self.push(bcp2x)
- self.push(bcp2y)
- self.push(p2x)
- self.push(p2y)
- self.op_rrcurveto(None)
- # call rrcurveto
- self.push(bcp3x)
- self.push(bcp3y)
- self.push(bcp4x)
- self.push(bcp4y)
- self.push(p3x)
- self.push(p3y)
- self.op_rrcurveto(None)
- # Push back final coords so subr 0 can find them
- self.push(finalx)
- self.push(finaly)
- def op_dotsection(self, index):
- self.popall() # XXX
- def op_hstem3(self, index):
- self.popall() # XXX
- 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 # seac weirdness
- self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady))
- def op_vstem3(self, index):
- self.popall() # XXX
- 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 present, remove return and endchar operators.
- 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]) # hint mask
- 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:
- # NOTE: only CFF2 charstrings/subrs can have numeric arguments on
- # the stack after the last operator. Compiling this would fail if
- # this is part of CFF 1.0 table.
- 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
- ) # only a PrivateDict has blended ops.
- numBlends = self.pop()
- args = self.popall()
- numArgs = len(args)
- # The spec says that there should be no non-blended Blue Values,.
- 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):
- # arg_blendList() has already converted these to absolute values.
- 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
|