| from fontTools.misc import sstruct |
| from fontTools.misc.fixedTools import floatToFixedToStr |
| from fontTools.misc.textTools import byteord, safeEval |
| |
| # from itertools import * |
| from . import DefaultTable |
| from . import grUtils |
| from array import array |
| from functools import reduce |
| import struct, re, sys |
| |
| Silf_hdr_format = """ |
| > |
| version: 16.16F |
| """ |
| |
| Silf_hdr_format_3 = """ |
| > |
| version: 16.16F |
| compilerVersion: L |
| numSilf: H |
| x |
| x |
| """ |
| |
| Silf_part1_format_v3 = """ |
| > |
| ruleVersion: 16.16F |
| passOffset: H |
| pseudosOffset: H |
| """ |
| |
| Silf_part1_format = """ |
| > |
| maxGlyphID: H |
| extraAscent: h |
| extraDescent: h |
| numPasses: B |
| iSubst: B |
| iPos: B |
| iJust: B |
| iBidi: B |
| flags: B |
| maxPreContext: B |
| maxPostContext: B |
| attrPseudo: B |
| attrBreakWeight: B |
| attrDirectionality: B |
| attrMirroring: B |
| attrSkipPasses: B |
| numJLevels: B |
| """ |
| |
| Silf_justify_format = """ |
| > |
| attrStretch: B |
| attrShrink: B |
| attrStep: B |
| attrWeight: B |
| runto: B |
| x |
| x |
| x |
| """ |
| |
| Silf_part2_format = """ |
| > |
| numLigComp: H |
| numUserDefn: B |
| maxCompPerLig: B |
| direction: B |
| attCollisions: B |
| x |
| x |
| x |
| numCritFeatures: B |
| """ |
| |
| Silf_pseudomap_format = """ |
| > |
| unicode: L |
| nPseudo: H |
| """ |
| |
| Silf_pseudomap_format_h = """ |
| > |
| unicode: H |
| nPseudo: H |
| """ |
| |
| Silf_classmap_format = """ |
| > |
| numClass: H |
| numLinear: H |
| """ |
| |
| Silf_lookupclass_format = """ |
| > |
| numIDs: H |
| searchRange: H |
| entrySelector: H |
| rangeShift: H |
| """ |
| |
| Silf_lookuppair_format = """ |
| > |
| glyphId: H |
| index: H |
| """ |
| |
| Silf_pass_format = """ |
| > |
| flags: B |
| maxRuleLoop: B |
| maxRuleContext: B |
| maxBackup: B |
| numRules: H |
| fsmOffset: H |
| pcCode: L |
| rcCode: L |
| aCode: L |
| oDebug: L |
| numRows: H |
| numTransitional: H |
| numSuccess: H |
| numColumns: H |
| """ |
| |
| aCode_info = ( |
| ("NOP", 0), |
| ("PUSH_BYTE", "b"), |
| ("PUSH_BYTE_U", "B"), |
| ("PUSH_SHORT", ">h"), |
| ("PUSH_SHORT_U", ">H"), |
| ("PUSH_LONG", ">L"), |
| ("ADD", 0), |
| ("SUB", 0), |
| ("MUL", 0), |
| ("DIV", 0), |
| ("MIN", 0), |
| ("MAX", 0), |
| ("NEG", 0), |
| ("TRUNC8", 0), |
| ("TRUNC16", 0), |
| ("COND", 0), |
| ("AND", 0), # x10 |
| ("OR", 0), |
| ("NOT", 0), |
| ("EQUAL", 0), |
| ("NOT_EQ", 0), |
| ("LESS", 0), |
| ("GTR", 0), |
| ("LESS_EQ", 0), |
| ("GTR_EQ", 0), |
| ("NEXT", 0), |
| ("NEXT_N", "b"), |
| ("COPY_NEXT", 0), |
| ("PUT_GLYPH_8BIT_OBS", "B"), |
| ("PUT_SUBS_8BIT_OBS", "bBB"), |
| ("PUT_COPY", "b"), |
| ("INSERT", 0), |
| ("DELETE", 0), # x20 |
| ("ASSOC", -1), |
| ("CNTXT_ITEM", "bB"), |
| ("ATTR_SET", "B"), |
| ("ATTR_ADD", "B"), |
| ("ATTR_SUB", "B"), |
| ("ATTR_SET_SLOT", "B"), |
| ("IATTR_SET_SLOT", "BB"), |
| ("PUSH_SLOT_ATTR", "Bb"), |
| ("PUSH_GLYPH_ATTR_OBS", "Bb"), |
| ("PUSH_GLYPH_METRIC", "Bbb"), |
| ("PUSH_FEAT", "Bb"), |
| ("PUSH_ATT_TO_GATTR_OBS", "Bb"), |
| ("PUSH_ATT_TO_GLYPH_METRIC", "Bbb"), |
| ("PUSH_ISLOT_ATTR", "Bbb"), |
| ("PUSH_IGLYPH_ATTR", "Bbb"), |
| ("POP_RET", 0), # x30 |
| ("RET_ZERO", 0), |
| ("RET_TRUE", 0), |
| ("IATTR_SET", "BB"), |
| ("IATTR_ADD", "BB"), |
| ("IATTR_SUB", "BB"), |
| ("PUSH_PROC_STATE", "B"), |
| ("PUSH_VERSION", 0), |
| ("PUT_SUBS", ">bHH"), |
| ("PUT_SUBS2", 0), |
| ("PUT_SUBS3", 0), |
| ("PUT_GLYPH", ">H"), |
| ("PUSH_GLYPH_ATTR", ">Hb"), |
| ("PUSH_ATT_TO_GLYPH_ATTR", ">Hb"), |
| ("BITOR", 0), |
| ("BITAND", 0), |
| ("BITNOT", 0), # x40 |
| ("BITSET", ">HH"), |
| ("SET_FEAT", "Bb"), |
| ) |
| aCode_map = dict([(x[0], (i, x[1])) for i, x in enumerate(aCode_info)]) |
| |
| |
| def disassemble(aCode): |
| codelen = len(aCode) |
| pc = 0 |
| res = [] |
| while pc < codelen: |
| opcode = byteord(aCode[pc : pc + 1]) |
| if opcode > len(aCode_info): |
| instr = aCode_info[0] |
| else: |
| instr = aCode_info[opcode] |
| pc += 1 |
| if instr[1] != 0 and pc >= codelen: |
| return res |
| if instr[1] == -1: |
| count = byteord(aCode[pc]) |
| fmt = "%dB" % count |
| pc += 1 |
| elif instr[1] == 0: |
| fmt = "" |
| else: |
| fmt = instr[1] |
| if fmt == "": |
| res.append(instr[0]) |
| continue |
| parms = struct.unpack_from(fmt, aCode[pc:]) |
| res.append(instr[0] + "(" + ", ".join(map(str, parms)) + ")") |
| pc += struct.calcsize(fmt) |
| return res |
| |
| |
| instre = re.compile(r"^\s*([^(]+)\s*(?:\(([^)]+)\))?") |
| |
| |
| def assemble(instrs): |
| res = b"" |
| for inst in instrs: |
| m = instre.match(inst) |
| if not m or not m.group(1) in aCode_map: |
| continue |
| opcode, parmfmt = aCode_map[m.group(1)] |
| res += struct.pack("B", opcode) |
| if m.group(2): |
| if parmfmt == 0: |
| continue |
| parms = [int(x) for x in re.split(r",\s*", m.group(2))] |
| if parmfmt == -1: |
| l = len(parms) |
| res += struct.pack(("%dB" % (l + 1)), l, *parms) |
| else: |
| res += struct.pack(parmfmt, *parms) |
| return res |
| |
| |
| def writecode(tag, writer, instrs): |
| writer.begintag(tag) |
| writer.newline() |
| for l in disassemble(instrs): |
| writer.write(l) |
| writer.newline() |
| writer.endtag(tag) |
| writer.newline() |
| |
| |
| def readcode(content): |
| res = [] |
| for e in content_string(content).split("\n"): |
| e = e.strip() |
| if not len(e): |
| continue |
| res.append(e) |
| return assemble(res) |
| |
| |
| attrs_info = ( |
| "flags", |
| "extraAscent", |
| "extraDescent", |
| "maxGlyphID", |
| "numLigComp", |
| "numUserDefn", |
| "maxCompPerLig", |
| "direction", |
| "lbGID", |
| ) |
| attrs_passindexes = ("iSubst", "iPos", "iJust", "iBidi") |
| attrs_contexts = ("maxPreContext", "maxPostContext") |
| attrs_attributes = ( |
| "attrPseudo", |
| "attrBreakWeight", |
| "attrDirectionality", |
| "attrMirroring", |
| "attrSkipPasses", |
| "attCollisions", |
| ) |
| pass_attrs_info = ( |
| "flags", |
| "maxRuleLoop", |
| "maxRuleContext", |
| "maxBackup", |
| "minRulePreContext", |
| "maxRulePreContext", |
| "collisionThreshold", |
| ) |
| pass_attrs_fsm = ("numRows", "numTransitional", "numSuccess", "numColumns") |
| |
| |
| def writesimple(tag, self, writer, *attrkeys): |
| attrs = dict([(k, getattr(self, k)) for k in attrkeys]) |
| writer.simpletag(tag, **attrs) |
| writer.newline() |
| |
| |
| def getSimple(self, attrs, *attr_list): |
| for k in attr_list: |
| if k in attrs: |
| setattr(self, k, int(safeEval(attrs[k]))) |
| |
| |
| def content_string(contents): |
| res = "" |
| for element in contents: |
| if isinstance(element, tuple): |
| continue |
| res += element |
| return res.strip() |
| |
| |
| def wrapline(writer, dat, length=80): |
| currline = "" |
| for d in dat: |
| if len(currline) > length: |
| writer.write(currline[:-1]) |
| writer.newline() |
| currline = "" |
| currline += d + " " |
| if len(currline): |
| writer.write(currline[:-1]) |
| writer.newline() |
| |
| |
| class _Object: |
| pass |
| |
| |
| class table_S__i_l_f(DefaultTable.DefaultTable): |
| """Silf table support""" |
| |
| def __init__(self, tag=None): |
| DefaultTable.DefaultTable.__init__(self, tag) |
| self.silfs = [] |
| |
| def decompile(self, data, ttFont): |
| sstruct.unpack2(Silf_hdr_format, data, self) |
| self.version = float(floatToFixedToStr(self.version, precisionBits=16)) |
| if self.version >= 5.0: |
| (data, self.scheme) = grUtils.decompress(data) |
| sstruct.unpack2(Silf_hdr_format_3, data, self) |
| base = sstruct.calcsize(Silf_hdr_format_3) |
| elif self.version < 3.0: |
| self.numSilf = struct.unpack(">H", data[4:6]) |
| self.scheme = 0 |
| self.compilerVersion = 0 |
| base = 8 |
| else: |
| self.scheme = 0 |
| sstruct.unpack2(Silf_hdr_format_3, data, self) |
| base = sstruct.calcsize(Silf_hdr_format_3) |
| |
| silfoffsets = struct.unpack_from((">%dL" % self.numSilf), data[base:]) |
| for offset in silfoffsets: |
| s = Silf() |
| self.silfs.append(s) |
| s.decompile(data[offset:], ttFont, self.version) |
| |
| def compile(self, ttFont): |
| self.numSilf = len(self.silfs) |
| if self.version < 3.0: |
| hdr = sstruct.pack(Silf_hdr_format, self) |
| hdr += struct.pack(">HH", self.numSilf, 0) |
| else: |
| hdr = sstruct.pack(Silf_hdr_format_3, self) |
| offset = len(hdr) + 4 * self.numSilf |
| data = b"" |
| for s in self.silfs: |
| hdr += struct.pack(">L", offset) |
| subdata = s.compile(ttFont, self.version) |
| offset += len(subdata) |
| data += subdata |
| if self.version >= 5.0: |
| return grUtils.compress(self.scheme, hdr + data) |
| return hdr + data |
| |
| def toXML(self, writer, ttFont): |
| writer.comment("Attributes starting with _ are informative only") |
| writer.newline() |
| writer.simpletag( |
| "version", |
| version=self.version, |
| compilerVersion=self.compilerVersion, |
| compressionScheme=self.scheme, |
| ) |
| writer.newline() |
| for s in self.silfs: |
| writer.begintag("silf") |
| writer.newline() |
| s.toXML(writer, ttFont, self.version) |
| writer.endtag("silf") |
| writer.newline() |
| |
| def fromXML(self, name, attrs, content, ttFont): |
| if name == "version": |
| self.scheme = int(safeEval(attrs["compressionScheme"])) |
| self.version = float(safeEval(attrs["version"])) |
| self.compilerVersion = int(safeEval(attrs["compilerVersion"])) |
| return |
| if name == "silf": |
| s = Silf() |
| self.silfs.append(s) |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| tag, attrs, subcontent = element |
| s.fromXML(tag, attrs, subcontent, ttFont, self.version) |
| |
| |
| class Silf(object): |
| """A particular Silf subtable""" |
| |
| def __init__(self): |
| self.passes = [] |
| self.scriptTags = [] |
| self.critFeatures = [] |
| self.jLevels = [] |
| self.pMap = {} |
| |
| def decompile(self, data, ttFont, version=2.0): |
| if version >= 3.0: |
| _, data = sstruct.unpack2(Silf_part1_format_v3, data, self) |
| self.ruleVersion = float( |
| floatToFixedToStr(self.ruleVersion, precisionBits=16) |
| ) |
| _, data = sstruct.unpack2(Silf_part1_format, data, self) |
| for jlevel in range(self.numJLevels): |
| j, data = sstruct.unpack2(Silf_justify_format, data, _Object()) |
| self.jLevels.append(j) |
| _, data = sstruct.unpack2(Silf_part2_format, data, self) |
| if self.numCritFeatures: |
| self.critFeatures = struct.unpack_from( |
| (">%dH" % self.numCritFeatures), data |
| ) |
| data = data[self.numCritFeatures * 2 + 1 :] |
| (numScriptTag,) = struct.unpack_from("B", data) |
| if numScriptTag: |
| self.scriptTags = [ |
| struct.unpack("4s", data[x : x + 4])[0].decode("ascii") |
| for x in range(1, 1 + 4 * numScriptTag, 4) |
| ] |
| data = data[1 + 4 * numScriptTag :] |
| (self.lbGID,) = struct.unpack(">H", data[:2]) |
| if self.numPasses: |
| self.oPasses = struct.unpack( |
| (">%dL" % (self.numPasses + 1)), data[2 : 6 + 4 * self.numPasses] |
| ) |
| data = data[6 + 4 * self.numPasses :] |
| (numPseudo,) = struct.unpack(">H", data[:2]) |
| for i in range(numPseudo): |
| if version >= 3.0: |
| pseudo = sstruct.unpack( |
| Silf_pseudomap_format, data[8 + 6 * i : 14 + 6 * i], _Object() |
| ) |
| else: |
| pseudo = sstruct.unpack( |
| Silf_pseudomap_format_h, data[8 + 4 * i : 12 + 4 * i], _Object() |
| ) |
| self.pMap[pseudo.unicode] = ttFont.getGlyphName(pseudo.nPseudo) |
| data = data[8 + 6 * numPseudo :] |
| currpos = ( |
| sstruct.calcsize(Silf_part1_format) |
| + sstruct.calcsize(Silf_justify_format) * self.numJLevels |
| + sstruct.calcsize(Silf_part2_format) |
| + 2 * self.numCritFeatures |
| + 1 |
| + 1 |
| + 4 * numScriptTag |
| + 6 |
| + 4 * self.numPasses |
| + 8 |
| + 6 * numPseudo |
| ) |
| if version >= 3.0: |
| currpos += sstruct.calcsize(Silf_part1_format_v3) |
| self.classes = Classes() |
| self.classes.decompile(data, ttFont, version) |
| for i in range(self.numPasses): |
| p = Pass() |
| self.passes.append(p) |
| p.decompile( |
| data[self.oPasses[i] - currpos : self.oPasses[i + 1] - currpos], |
| ttFont, |
| version, |
| ) |
| |
| def compile(self, ttFont, version=2.0): |
| self.numPasses = len(self.passes) |
| self.numJLevels = len(self.jLevels) |
| self.numCritFeatures = len(self.critFeatures) |
| numPseudo = len(self.pMap) |
| data = b"" |
| if version >= 3.0: |
| hdroffset = sstruct.calcsize(Silf_part1_format_v3) |
| else: |
| hdroffset = 0 |
| data += sstruct.pack(Silf_part1_format, self) |
| for j in self.jLevels: |
| data += sstruct.pack(Silf_justify_format, j) |
| data += sstruct.pack(Silf_part2_format, self) |
| if self.numCritFeatures: |
| data += struct.pack((">%dH" % self.numCritFeaturs), *self.critFeatures) |
| data += struct.pack("BB", 0, len(self.scriptTags)) |
| if len(self.scriptTags): |
| tdata = [struct.pack("4s", x.encode("ascii")) for x in self.scriptTags] |
| data += b"".join(tdata) |
| data += struct.pack(">H", self.lbGID) |
| self.passOffset = len(data) |
| |
| data1 = grUtils.bininfo(numPseudo, 6) |
| currpos = hdroffset + len(data) + 4 * (self.numPasses + 1) |
| self.pseudosOffset = currpos + len(data1) |
| for u, p in sorted(self.pMap.items()): |
| data1 += struct.pack( |
| (">LH" if version >= 3.0 else ">HH"), u, ttFont.getGlyphID(p) |
| ) |
| data1 += self.classes.compile(ttFont, version) |
| currpos += len(data1) |
| data2 = b"" |
| datao = b"" |
| for i, p in enumerate(self.passes): |
| base = currpos + len(data2) |
| datao += struct.pack(">L", base) |
| data2 += p.compile(ttFont, base, version) |
| datao += struct.pack(">L", currpos + len(data2)) |
| |
| if version >= 3.0: |
| data3 = sstruct.pack(Silf_part1_format_v3, self) |
| else: |
| data3 = b"" |
| return data3 + data + datao + data1 + data2 |
| |
| def toXML(self, writer, ttFont, version=2.0): |
| if version >= 3.0: |
| writer.simpletag("version", ruleVersion=self.ruleVersion) |
| writer.newline() |
| writesimple("info", self, writer, *attrs_info) |
| writesimple("passindexes", self, writer, *attrs_passindexes) |
| writesimple("contexts", self, writer, *attrs_contexts) |
| writesimple("attributes", self, writer, *attrs_attributes) |
| if len(self.jLevels): |
| writer.begintag("justifications") |
| writer.newline() |
| jformat, jnames, jfixes = sstruct.getformat(Silf_justify_format) |
| for i, j in enumerate(self.jLevels): |
| attrs = dict([(k, getattr(j, k)) for k in jnames]) |
| writer.simpletag("justify", **attrs) |
| writer.newline() |
| writer.endtag("justifications") |
| writer.newline() |
| if len(self.critFeatures): |
| writer.begintag("critFeatures") |
| writer.newline() |
| writer.write(" ".join(map(str, self.critFeatures))) |
| writer.newline() |
| writer.endtag("critFeatures") |
| writer.newline() |
| if len(self.scriptTags): |
| writer.begintag("scriptTags") |
| writer.newline() |
| writer.write(" ".join(self.scriptTags)) |
| writer.newline() |
| writer.endtag("scriptTags") |
| writer.newline() |
| if self.pMap: |
| writer.begintag("pseudoMap") |
| writer.newline() |
| for k, v in sorted(self.pMap.items()): |
| writer.simpletag("pseudo", unicode=hex(k), pseudo=v) |
| writer.newline() |
| writer.endtag("pseudoMap") |
| writer.newline() |
| self.classes.toXML(writer, ttFont, version) |
| if len(self.passes): |
| writer.begintag("passes") |
| writer.newline() |
| for i, p in enumerate(self.passes): |
| writer.begintag("pass", _index=i) |
| writer.newline() |
| p.toXML(writer, ttFont, version) |
| writer.endtag("pass") |
| writer.newline() |
| writer.endtag("passes") |
| writer.newline() |
| |
| def fromXML(self, name, attrs, content, ttFont, version=2.0): |
| if name == "version": |
| self.ruleVersion = float(safeEval(attrs.get("ruleVersion", "0"))) |
| if name == "info": |
| getSimple(self, attrs, *attrs_info) |
| elif name == "passindexes": |
| getSimple(self, attrs, *attrs_passindexes) |
| elif name == "contexts": |
| getSimple(self, attrs, *attrs_contexts) |
| elif name == "attributes": |
| getSimple(self, attrs, *attrs_attributes) |
| elif name == "justifications": |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| (tag, attrs, subcontent) = element |
| if tag == "justify": |
| j = _Object() |
| for k, v in attrs.items(): |
| setattr(j, k, int(v)) |
| self.jLevels.append(j) |
| elif name == "critFeatures": |
| self.critFeatures = [] |
| element = content_string(content) |
| self.critFeatures.extend(map(int, element.split())) |
| elif name == "scriptTags": |
| self.scriptTags = [] |
| element = content_string(content) |
| for n in element.split(): |
| self.scriptTags.append(n) |
| elif name == "pseudoMap": |
| self.pMap = {} |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| (tag, attrs, subcontent) = element |
| if tag == "pseudo": |
| k = int(attrs["unicode"], 16) |
| v = attrs["pseudo"] |
| self.pMap[k] = v |
| elif name == "classes": |
| self.classes = Classes() |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| tag, attrs, subcontent = element |
| self.classes.fromXML(tag, attrs, subcontent, ttFont, version) |
| elif name == "passes": |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| tag, attrs, subcontent = element |
| if tag == "pass": |
| p = Pass() |
| for e in subcontent: |
| if not isinstance(e, tuple): |
| continue |
| p.fromXML(e[0], e[1], e[2], ttFont, version) |
| self.passes.append(p) |
| |
| |
| class Classes(object): |
| def __init__(self): |
| self.linear = [] |
| self.nonLinear = [] |
| |
| def decompile(self, data, ttFont, version=2.0): |
| sstruct.unpack2(Silf_classmap_format, data, self) |
| if version >= 4.0: |
| oClasses = struct.unpack( |
| (">%dL" % (self.numClass + 1)), data[4 : 8 + 4 * self.numClass] |
| ) |
| else: |
| oClasses = struct.unpack( |
| (">%dH" % (self.numClass + 1)), data[4 : 6 + 2 * self.numClass] |
| ) |
| for s, e in zip(oClasses[: self.numLinear], oClasses[1 : self.numLinear + 1]): |
| self.linear.append( |
| ttFont.getGlyphName(x) |
| for x in struct.unpack((">%dH" % ((e - s) / 2)), data[s:e]) |
| ) |
| for s, e in zip( |
| oClasses[self.numLinear : self.numClass], |
| oClasses[self.numLinear + 1 : self.numClass + 1], |
| ): |
| nonLinids = [ |
| struct.unpack(">HH", data[x : x + 4]) for x in range(s + 8, e, 4) |
| ] |
| nonLin = dict([(ttFont.getGlyphName(x[0]), x[1]) for x in nonLinids]) |
| self.nonLinear.append(nonLin) |
| |
| def compile(self, ttFont, version=2.0): |
| data = b"" |
| oClasses = [] |
| if version >= 4.0: |
| offset = 8 + 4 * (len(self.linear) + len(self.nonLinear)) |
| else: |
| offset = 6 + 2 * (len(self.linear) + len(self.nonLinear)) |
| for l in self.linear: |
| oClasses.append(len(data) + offset) |
| gs = [ttFont.getGlyphID(x) for x in l] |
| data += struct.pack((">%dH" % len(l)), *gs) |
| for l in self.nonLinear: |
| oClasses.append(len(data) + offset) |
| gs = [(ttFont.getGlyphID(x[0]), x[1]) for x in l.items()] |
| data += grUtils.bininfo(len(gs)) |
| data += b"".join([struct.pack(">HH", *x) for x in sorted(gs)]) |
| oClasses.append(len(data) + offset) |
| self.numClass = len(oClasses) - 1 |
| self.numLinear = len(self.linear) |
| return ( |
| sstruct.pack(Silf_classmap_format, self) |
| + struct.pack( |
| ((">%dL" if version >= 4.0 else ">%dH") % len(oClasses)), *oClasses |
| ) |
| + data |
| ) |
| |
| def toXML(self, writer, ttFont, version=2.0): |
| writer.begintag("classes") |
| writer.newline() |
| writer.begintag("linearClasses") |
| writer.newline() |
| for i, l in enumerate(self.linear): |
| writer.begintag("linear", _index=i) |
| writer.newline() |
| wrapline(writer, l) |
| writer.endtag("linear") |
| writer.newline() |
| writer.endtag("linearClasses") |
| writer.newline() |
| writer.begintag("nonLinearClasses") |
| writer.newline() |
| for i, l in enumerate(self.nonLinear): |
| writer.begintag("nonLinear", _index=i + self.numLinear) |
| writer.newline() |
| for inp, ind in l.items(): |
| writer.simpletag("map", glyph=inp, index=ind) |
| writer.newline() |
| writer.endtag("nonLinear") |
| writer.newline() |
| writer.endtag("nonLinearClasses") |
| writer.newline() |
| writer.endtag("classes") |
| writer.newline() |
| |
| def fromXML(self, name, attrs, content, ttFont, version=2.0): |
| if name == "linearClasses": |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| tag, attrs, subcontent = element |
| if tag == "linear": |
| l = content_string(subcontent).split() |
| self.linear.append(l) |
| elif name == "nonLinearClasses": |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| tag, attrs, subcontent = element |
| if tag == "nonLinear": |
| l = {} |
| for e in subcontent: |
| if not isinstance(e, tuple): |
| continue |
| tag, attrs, subsubcontent = e |
| if tag == "map": |
| l[attrs["glyph"]] = int(safeEval(attrs["index"])) |
| self.nonLinear.append(l) |
| |
| |
| class Pass(object): |
| def __init__(self): |
| self.colMap = {} |
| self.rules = [] |
| self.rulePreContexts = [] |
| self.ruleSortKeys = [] |
| self.ruleConstraints = [] |
| self.passConstraints = b"" |
| self.actions = [] |
| self.stateTrans = [] |
| self.startStates = [] |
| |
| def decompile(self, data, ttFont, version=2.0): |
| _, data = sstruct.unpack2(Silf_pass_format, data, self) |
| (numRange, _, _, _) = struct.unpack(">4H", data[:8]) |
| data = data[8:] |
| for i in range(numRange): |
| (first, last, col) = struct.unpack(">3H", data[6 * i : 6 * i + 6]) |
| for g in range(first, last + 1): |
| self.colMap[ttFont.getGlyphName(g)] = col |
| data = data[6 * numRange :] |
| oRuleMap = struct.unpack_from((">%dH" % (self.numSuccess + 1)), data) |
| data = data[2 + 2 * self.numSuccess :] |
| rules = struct.unpack_from((">%dH" % oRuleMap[-1]), data) |
| self.rules = [rules[s:e] for (s, e) in zip(oRuleMap, oRuleMap[1:])] |
| data = data[2 * oRuleMap[-1] :] |
| (self.minRulePreContext, self.maxRulePreContext) = struct.unpack("BB", data[:2]) |
| numStartStates = self.maxRulePreContext - self.minRulePreContext + 1 |
| self.startStates = struct.unpack( |
| (">%dH" % numStartStates), data[2 : 2 + numStartStates * 2] |
| ) |
| data = data[2 + numStartStates * 2 :] |
| self.ruleSortKeys = struct.unpack( |
| (">%dH" % self.numRules), data[: 2 * self.numRules] |
| ) |
| data = data[2 * self.numRules :] |
| self.rulePreContexts = struct.unpack( |
| ("%dB" % self.numRules), data[: self.numRules] |
| ) |
| data = data[self.numRules :] |
| (self.collisionThreshold, pConstraint) = struct.unpack(">BH", data[:3]) |
| oConstraints = list( |
| struct.unpack( |
| (">%dH" % (self.numRules + 1)), data[3 : 5 + self.numRules * 2] |
| ) |
| ) |
| data = data[5 + self.numRules * 2 :] |
| oActions = list( |
| struct.unpack((">%dH" % (self.numRules + 1)), data[: 2 + self.numRules * 2]) |
| ) |
| data = data[2 * self.numRules + 2 :] |
| for i in range(self.numTransitional): |
| a = array( |
| "H", data[i * self.numColumns * 2 : (i + 1) * self.numColumns * 2] |
| ) |
| if sys.byteorder != "big": |
| a.byteswap() |
| self.stateTrans.append(a) |
| data = data[self.numTransitional * self.numColumns * 2 + 1 :] |
| self.passConstraints = data[:pConstraint] |
| data = data[pConstraint:] |
| for i in range(len(oConstraints) - 2, -1, -1): |
| if oConstraints[i] == 0: |
| oConstraints[i] = oConstraints[i + 1] |
| self.ruleConstraints = [ |
| (data[s:e] if (e - s > 1) else b"") |
| for (s, e) in zip(oConstraints, oConstraints[1:]) |
| ] |
| data = data[oConstraints[-1] :] |
| self.actions = [ |
| (data[s:e] if (e - s > 1) else "") for (s, e) in zip(oActions, oActions[1:]) |
| ] |
| data = data[oActions[-1] :] |
| # not using debug |
| |
| def compile(self, ttFont, base, version=2.0): |
| # build it all up backwards |
| oActions = reduce( |
| lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.actions + [b""], (0, []) |
| )[1] |
| oConstraints = reduce( |
| lambda a, x: (a[0] + len(x), a[1] + [a[0]]), |
| self.ruleConstraints + [b""], |
| (1, []), |
| )[1] |
| constraintCode = b"\000" + b"".join(self.ruleConstraints) |
| transes = [] |
| for t in self.stateTrans: |
| if sys.byteorder != "big": |
| t.byteswap() |
| transes.append(t.tobytes()) |
| if sys.byteorder != "big": |
| t.byteswap() |
| if not len(transes): |
| self.startStates = [0] |
| oRuleMap = reduce( |
| lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.rules + [[]], (0, []) |
| )[1] |
| passRanges = [] |
| gidcolmap = dict([(ttFont.getGlyphID(x[0]), x[1]) for x in self.colMap.items()]) |
| for e in grUtils.entries(gidcolmap, sameval=True): |
| if e[1]: |
| passRanges.append((e[0], e[0] + e[1] - 1, e[2][0])) |
| self.numRules = len(self.actions) |
| self.fsmOffset = ( |
| sstruct.calcsize(Silf_pass_format) |
| + 8 |
| + len(passRanges) * 6 |
| + len(oRuleMap) * 2 |
| + 2 * oRuleMap[-1] |
| + 2 |
| + 2 * len(self.startStates) |
| + 3 * self.numRules |
| + 3 |
| + 4 * self.numRules |
| + 4 |
| ) |
| self.pcCode = ( |
| self.fsmOffset + 2 * self.numTransitional * self.numColumns + 1 + base |
| ) |
| self.rcCode = self.pcCode + len(self.passConstraints) |
| self.aCode = self.rcCode + len(constraintCode) |
| self.oDebug = 0 |
| # now generate output |
| data = sstruct.pack(Silf_pass_format, self) |
| data += grUtils.bininfo(len(passRanges), 6) |
| data += b"".join(struct.pack(">3H", *p) for p in passRanges) |
| data += struct.pack((">%dH" % len(oRuleMap)), *oRuleMap) |
| flatrules = reduce(lambda a, x: a + x, self.rules, []) |
| data += struct.pack((">%dH" % oRuleMap[-1]), *flatrules) |
| data += struct.pack("BB", self.minRulePreContext, self.maxRulePreContext) |
| data += struct.pack((">%dH" % len(self.startStates)), *self.startStates) |
| data += struct.pack((">%dH" % self.numRules), *self.ruleSortKeys) |
| data += struct.pack(("%dB" % self.numRules), *self.rulePreContexts) |
| data += struct.pack(">BH", self.collisionThreshold, len(self.passConstraints)) |
| data += struct.pack((">%dH" % (self.numRules + 1)), *oConstraints) |
| data += struct.pack((">%dH" % (self.numRules + 1)), *oActions) |
| return ( |
| data |
| + b"".join(transes) |
| + struct.pack("B", 0) |
| + self.passConstraints |
| + constraintCode |
| + b"".join(self.actions) |
| ) |
| |
| def toXML(self, writer, ttFont, version=2.0): |
| writesimple("info", self, writer, *pass_attrs_info) |
| writesimple("fsminfo", self, writer, *pass_attrs_fsm) |
| writer.begintag("colmap") |
| writer.newline() |
| wrapline( |
| writer, |
| [ |
| "{}={}".format(*x) |
| for x in sorted( |
| self.colMap.items(), key=lambda x: ttFont.getGlyphID(x[0]) |
| ) |
| ], |
| ) |
| writer.endtag("colmap") |
| writer.newline() |
| writer.begintag("staterulemap") |
| writer.newline() |
| for i, r in enumerate(self.rules): |
| writer.simpletag( |
| "state", |
| number=self.numRows - self.numSuccess + i, |
| rules=" ".join(map(str, r)), |
| ) |
| writer.newline() |
| writer.endtag("staterulemap") |
| writer.newline() |
| writer.begintag("rules") |
| writer.newline() |
| for i in range(len(self.actions)): |
| writer.begintag( |
| "rule", |
| index=i, |
| precontext=self.rulePreContexts[i], |
| sortkey=self.ruleSortKeys[i], |
| ) |
| writer.newline() |
| if len(self.ruleConstraints[i]): |
| writecode("constraint", writer, self.ruleConstraints[i]) |
| writecode("action", writer, self.actions[i]) |
| writer.endtag("rule") |
| writer.newline() |
| writer.endtag("rules") |
| writer.newline() |
| if len(self.passConstraints): |
| writecode("passConstraint", writer, self.passConstraints) |
| if len(self.stateTrans): |
| writer.begintag("fsm") |
| writer.newline() |
| writer.begintag("starts") |
| writer.write(" ".join(map(str, self.startStates))) |
| writer.endtag("starts") |
| writer.newline() |
| for i, s in enumerate(self.stateTrans): |
| writer.begintag("row", _i=i) |
| # no newlines here |
| writer.write(" ".join(map(str, s))) |
| writer.endtag("row") |
| writer.newline() |
| writer.endtag("fsm") |
| writer.newline() |
| |
| def fromXML(self, name, attrs, content, ttFont, version=2.0): |
| if name == "info": |
| getSimple(self, attrs, *pass_attrs_info) |
| elif name == "fsminfo": |
| getSimple(self, attrs, *pass_attrs_fsm) |
| elif name == "colmap": |
| e = content_string(content) |
| for w in e.split(): |
| x = w.split("=") |
| if len(x) != 2 or x[0] == "" or x[1] == "": |
| continue |
| self.colMap[x[0]] = int(x[1]) |
| elif name == "staterulemap": |
| for e in content: |
| if not isinstance(e, tuple): |
| continue |
| tag, a, c = e |
| if tag == "state": |
| self.rules.append([int(x) for x in a["rules"].split(" ")]) |
| elif name == "rules": |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| tag, a, c = element |
| if tag != "rule": |
| continue |
| self.rulePreContexts.append(int(a["precontext"])) |
| self.ruleSortKeys.append(int(a["sortkey"])) |
| con = b"" |
| act = b"" |
| for e in c: |
| if not isinstance(e, tuple): |
| continue |
| tag, a, subc = e |
| if tag == "constraint": |
| con = readcode(subc) |
| elif tag == "action": |
| act = readcode(subc) |
| self.actions.append(act) |
| self.ruleConstraints.append(con) |
| elif name == "passConstraint": |
| self.passConstraints = readcode(content) |
| elif name == "fsm": |
| for element in content: |
| if not isinstance(element, tuple): |
| continue |
| tag, a, c = element |
| if tag == "row": |
| s = array("H") |
| e = content_string(c) |
| s.extend(map(int, e.split())) |
| self.stateTrans.append(s) |
| elif tag == "starts": |
| s = [] |
| e = content_string(c) |
| s.extend(map(int, e.split())) |
| self.startStates = s |