• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc import sstruct
2from fontTools.misc.fixedTools import floatToFixedToStr
3from fontTools.misc.textTools import byteord, safeEval
4
5# from itertools import *
6from . import DefaultTable
7from . import grUtils
8from array import array
9from functools import reduce
10import struct, re, sys
11
12Silf_hdr_format = """
13    >
14    version:            16.16F
15"""
16
17Silf_hdr_format_3 = """
18    >
19    version:            16.16F
20    compilerVersion:    L
21    numSilf:            H
22                        x
23                        x
24"""
25
26Silf_part1_format_v3 = """
27    >
28    ruleVersion:        16.16F
29    passOffset:         H
30    pseudosOffset:      H
31"""
32
33Silf_part1_format = """
34    >
35    maxGlyphID:         H
36    extraAscent:        h
37    extraDescent:       h
38    numPasses:          B
39    iSubst:             B
40    iPos:               B
41    iJust:              B
42    iBidi:              B
43    flags:              B
44    maxPreContext:      B
45    maxPostContext:     B
46    attrPseudo:         B
47    attrBreakWeight:    B
48    attrDirectionality: B
49    attrMirroring:      B
50    attrSkipPasses:     B
51    numJLevels:         B
52"""
53
54Silf_justify_format = """
55    >
56    attrStretch:        B
57    attrShrink:         B
58    attrStep:           B
59    attrWeight:         B
60    runto:              B
61                        x
62                        x
63                        x
64"""
65
66Silf_part2_format = """
67    >
68    numLigComp:         H
69    numUserDefn:        B
70    maxCompPerLig:      B
71    direction:          B
72    attCollisions:      B
73                        x
74                        x
75                        x
76    numCritFeatures:    B
77"""
78
79Silf_pseudomap_format = """
80    >
81    unicode:            L
82    nPseudo:            H
83"""
84
85Silf_pseudomap_format_h = """
86    >
87    unicode:            H
88    nPseudo:            H
89"""
90
91Silf_classmap_format = """
92    >
93    numClass:           H
94    numLinear:          H
95"""
96
97Silf_lookupclass_format = """
98    >
99    numIDs:             H
100    searchRange:        H
101    entrySelector:      H
102    rangeShift:         H
103"""
104
105Silf_lookuppair_format = """
106    >
107    glyphId:            H
108    index:              H
109"""
110
111Silf_pass_format = """
112    >
113    flags:              B
114    maxRuleLoop:        B
115    maxRuleContext:     B
116    maxBackup:          B
117    numRules:           H
118    fsmOffset:          H
119    pcCode:             L
120    rcCode:             L
121    aCode:              L
122    oDebug:             L
123    numRows:            H
124    numTransitional:    H
125    numSuccess:         H
126    numColumns:         H
127"""
128
129aCode_info = (
130    ("NOP", 0),
131    ("PUSH_BYTE", "b"),
132    ("PUSH_BYTE_U", "B"),
133    ("PUSH_SHORT", ">h"),
134    ("PUSH_SHORT_U", ">H"),
135    ("PUSH_LONG", ">L"),
136    ("ADD", 0),
137    ("SUB", 0),
138    ("MUL", 0),
139    ("DIV", 0),
140    ("MIN", 0),
141    ("MAX", 0),
142    ("NEG", 0),
143    ("TRUNC8", 0),
144    ("TRUNC16", 0),
145    ("COND", 0),
146    ("AND", 0),  # x10
147    ("OR", 0),
148    ("NOT", 0),
149    ("EQUAL", 0),
150    ("NOT_EQ", 0),
151    ("LESS", 0),
152    ("GTR", 0),
153    ("LESS_EQ", 0),
154    ("GTR_EQ", 0),
155    ("NEXT", 0),
156    ("NEXT_N", "b"),
157    ("COPY_NEXT", 0),
158    ("PUT_GLYPH_8BIT_OBS", "B"),
159    ("PUT_SUBS_8BIT_OBS", "bBB"),
160    ("PUT_COPY", "b"),
161    ("INSERT", 0),
162    ("DELETE", 0),  # x20
163    ("ASSOC", -1),
164    ("CNTXT_ITEM", "bB"),
165    ("ATTR_SET", "B"),
166    ("ATTR_ADD", "B"),
167    ("ATTR_SUB", "B"),
168    ("ATTR_SET_SLOT", "B"),
169    ("IATTR_SET_SLOT", "BB"),
170    ("PUSH_SLOT_ATTR", "Bb"),
171    ("PUSH_GLYPH_ATTR_OBS", "Bb"),
172    ("PUSH_GLYPH_METRIC", "Bbb"),
173    ("PUSH_FEAT", "Bb"),
174    ("PUSH_ATT_TO_GATTR_OBS", "Bb"),
175    ("PUSH_ATT_TO_GLYPH_METRIC", "Bbb"),
176    ("PUSH_ISLOT_ATTR", "Bbb"),
177    ("PUSH_IGLYPH_ATTR", "Bbb"),
178    ("POP_RET", 0),  # x30
179    ("RET_ZERO", 0),
180    ("RET_TRUE", 0),
181    ("IATTR_SET", "BB"),
182    ("IATTR_ADD", "BB"),
183    ("IATTR_SUB", "BB"),
184    ("PUSH_PROC_STATE", "B"),
185    ("PUSH_VERSION", 0),
186    ("PUT_SUBS", ">bHH"),
187    ("PUT_SUBS2", 0),
188    ("PUT_SUBS3", 0),
189    ("PUT_GLYPH", ">H"),
190    ("PUSH_GLYPH_ATTR", ">Hb"),
191    ("PUSH_ATT_TO_GLYPH_ATTR", ">Hb"),
192    ("BITOR", 0),
193    ("BITAND", 0),
194    ("BITNOT", 0),  # x40
195    ("BITSET", ">HH"),
196    ("SET_FEAT", "Bb"),
197)
198aCode_map = dict([(x[0], (i, x[1])) for i, x in enumerate(aCode_info)])
199
200
201def disassemble(aCode):
202    codelen = len(aCode)
203    pc = 0
204    res = []
205    while pc < codelen:
206        opcode = byteord(aCode[pc : pc + 1])
207        if opcode > len(aCode_info):
208            instr = aCode_info[0]
209        else:
210            instr = aCode_info[opcode]
211        pc += 1
212        if instr[1] != 0 and pc >= codelen:
213            return res
214        if instr[1] == -1:
215            count = byteord(aCode[pc])
216            fmt = "%dB" % count
217            pc += 1
218        elif instr[1] == 0:
219            fmt = ""
220        else:
221            fmt = instr[1]
222        if fmt == "":
223            res.append(instr[0])
224            continue
225        parms = struct.unpack_from(fmt, aCode[pc:])
226        res.append(instr[0] + "(" + ", ".join(map(str, parms)) + ")")
227        pc += struct.calcsize(fmt)
228    return res
229
230
231instre = re.compile(r"^\s*([^(]+)\s*(?:\(([^)]+)\))?")
232
233
234def assemble(instrs):
235    res = b""
236    for inst in instrs:
237        m = instre.match(inst)
238        if not m or not m.group(1) in aCode_map:
239            continue
240        opcode, parmfmt = aCode_map[m.group(1)]
241        res += struct.pack("B", opcode)
242        if m.group(2):
243            if parmfmt == 0:
244                continue
245            parms = [int(x) for x in re.split(r",\s*", m.group(2))]
246            if parmfmt == -1:
247                l = len(parms)
248                res += struct.pack(("%dB" % (l + 1)), l, *parms)
249            else:
250                res += struct.pack(parmfmt, *parms)
251    return res
252
253
254def writecode(tag, writer, instrs):
255    writer.begintag(tag)
256    writer.newline()
257    for l in disassemble(instrs):
258        writer.write(l)
259        writer.newline()
260    writer.endtag(tag)
261    writer.newline()
262
263
264def readcode(content):
265    res = []
266    for e in content_string(content).split("\n"):
267        e = e.strip()
268        if not len(e):
269            continue
270        res.append(e)
271    return assemble(res)
272
273
274attrs_info = (
275    "flags",
276    "extraAscent",
277    "extraDescent",
278    "maxGlyphID",
279    "numLigComp",
280    "numUserDefn",
281    "maxCompPerLig",
282    "direction",
283    "lbGID",
284)
285attrs_passindexes = ("iSubst", "iPos", "iJust", "iBidi")
286attrs_contexts = ("maxPreContext", "maxPostContext")
287attrs_attributes = (
288    "attrPseudo",
289    "attrBreakWeight",
290    "attrDirectionality",
291    "attrMirroring",
292    "attrSkipPasses",
293    "attCollisions",
294)
295pass_attrs_info = (
296    "flags",
297    "maxRuleLoop",
298    "maxRuleContext",
299    "maxBackup",
300    "minRulePreContext",
301    "maxRulePreContext",
302    "collisionThreshold",
303)
304pass_attrs_fsm = ("numRows", "numTransitional", "numSuccess", "numColumns")
305
306
307def writesimple(tag, self, writer, *attrkeys):
308    attrs = dict([(k, getattr(self, k)) for k in attrkeys])
309    writer.simpletag(tag, **attrs)
310    writer.newline()
311
312
313def getSimple(self, attrs, *attr_list):
314    for k in attr_list:
315        if k in attrs:
316            setattr(self, k, int(safeEval(attrs[k])))
317
318
319def content_string(contents):
320    res = ""
321    for element in contents:
322        if isinstance(element, tuple):
323            continue
324        res += element
325    return res.strip()
326
327
328def wrapline(writer, dat, length=80):
329    currline = ""
330    for d in dat:
331        if len(currline) > length:
332            writer.write(currline[:-1])
333            writer.newline()
334            currline = ""
335        currline += d + " "
336    if len(currline):
337        writer.write(currline[:-1])
338        writer.newline()
339
340
341class _Object:
342    pass
343
344
345class table_S__i_l_f(DefaultTable.DefaultTable):
346    """Silf table support"""
347
348    def __init__(self, tag=None):
349        DefaultTable.DefaultTable.__init__(self, tag)
350        self.silfs = []
351
352    def decompile(self, data, ttFont):
353        sstruct.unpack2(Silf_hdr_format, data, self)
354        self.version = float(floatToFixedToStr(self.version, precisionBits=16))
355        if self.version >= 5.0:
356            (data, self.scheme) = grUtils.decompress(data)
357            sstruct.unpack2(Silf_hdr_format_3, data, self)
358            base = sstruct.calcsize(Silf_hdr_format_3)
359        elif self.version < 3.0:
360            self.numSilf = struct.unpack(">H", data[4:6])
361            self.scheme = 0
362            self.compilerVersion = 0
363            base = 8
364        else:
365            self.scheme = 0
366            sstruct.unpack2(Silf_hdr_format_3, data, self)
367            base = sstruct.calcsize(Silf_hdr_format_3)
368
369        silfoffsets = struct.unpack_from((">%dL" % self.numSilf), data[base:])
370        for offset in silfoffsets:
371            s = Silf()
372            self.silfs.append(s)
373            s.decompile(data[offset:], ttFont, self.version)
374
375    def compile(self, ttFont):
376        self.numSilf = len(self.silfs)
377        if self.version < 3.0:
378            hdr = sstruct.pack(Silf_hdr_format, self)
379            hdr += struct.pack(">HH", self.numSilf, 0)
380        else:
381            hdr = sstruct.pack(Silf_hdr_format_3, self)
382        offset = len(hdr) + 4 * self.numSilf
383        data = b""
384        for s in self.silfs:
385            hdr += struct.pack(">L", offset)
386            subdata = s.compile(ttFont, self.version)
387            offset += len(subdata)
388            data += subdata
389        if self.version >= 5.0:
390            return grUtils.compress(self.scheme, hdr + data)
391        return hdr + data
392
393    def toXML(self, writer, ttFont):
394        writer.comment("Attributes starting with _ are informative only")
395        writer.newline()
396        writer.simpletag(
397            "version",
398            version=self.version,
399            compilerVersion=self.compilerVersion,
400            compressionScheme=self.scheme,
401        )
402        writer.newline()
403        for s in self.silfs:
404            writer.begintag("silf")
405            writer.newline()
406            s.toXML(writer, ttFont, self.version)
407            writer.endtag("silf")
408            writer.newline()
409
410    def fromXML(self, name, attrs, content, ttFont):
411        if name == "version":
412            self.scheme = int(safeEval(attrs["compressionScheme"]))
413            self.version = float(safeEval(attrs["version"]))
414            self.compilerVersion = int(safeEval(attrs["compilerVersion"]))
415            return
416        if name == "silf":
417            s = Silf()
418            self.silfs.append(s)
419            for element in content:
420                if not isinstance(element, tuple):
421                    continue
422                tag, attrs, subcontent = element
423                s.fromXML(tag, attrs, subcontent, ttFont, self.version)
424
425
426class Silf(object):
427    """A particular Silf subtable"""
428
429    def __init__(self):
430        self.passes = []
431        self.scriptTags = []
432        self.critFeatures = []
433        self.jLevels = []
434        self.pMap = {}
435
436    def decompile(self, data, ttFont, version=2.0):
437        if version >= 3.0:
438            _, data = sstruct.unpack2(Silf_part1_format_v3, data, self)
439            self.ruleVersion = float(
440                floatToFixedToStr(self.ruleVersion, precisionBits=16)
441            )
442        _, data = sstruct.unpack2(Silf_part1_format, data, self)
443        for jlevel in range(self.numJLevels):
444            j, data = sstruct.unpack2(Silf_justify_format, data, _Object())
445            self.jLevels.append(j)
446        _, data = sstruct.unpack2(Silf_part2_format, data, self)
447        if self.numCritFeatures:
448            self.critFeatures = struct.unpack_from(
449                (">%dH" % self.numCritFeatures), data
450            )
451        data = data[self.numCritFeatures * 2 + 1 :]
452        (numScriptTag,) = struct.unpack_from("B", data)
453        if numScriptTag:
454            self.scriptTags = [
455                struct.unpack("4s", data[x : x + 4])[0].decode("ascii")
456                for x in range(1, 1 + 4 * numScriptTag, 4)
457            ]
458        data = data[1 + 4 * numScriptTag :]
459        (self.lbGID,) = struct.unpack(">H", data[:2])
460        if self.numPasses:
461            self.oPasses = struct.unpack(
462                (">%dL" % (self.numPasses + 1)), data[2 : 6 + 4 * self.numPasses]
463            )
464        data = data[6 + 4 * self.numPasses :]
465        (numPseudo,) = struct.unpack(">H", data[:2])
466        for i in range(numPseudo):
467            if version >= 3.0:
468                pseudo = sstruct.unpack(
469                    Silf_pseudomap_format, data[8 + 6 * i : 14 + 6 * i], _Object()
470                )
471            else:
472                pseudo = sstruct.unpack(
473                    Silf_pseudomap_format_h, data[8 + 4 * i : 12 + 4 * i], _Object()
474                )
475            self.pMap[pseudo.unicode] = ttFont.getGlyphName(pseudo.nPseudo)
476        data = data[8 + 6 * numPseudo :]
477        currpos = (
478            sstruct.calcsize(Silf_part1_format)
479            + sstruct.calcsize(Silf_justify_format) * self.numJLevels
480            + sstruct.calcsize(Silf_part2_format)
481            + 2 * self.numCritFeatures
482            + 1
483            + 1
484            + 4 * numScriptTag
485            + 6
486            + 4 * self.numPasses
487            + 8
488            + 6 * numPseudo
489        )
490        if version >= 3.0:
491            currpos += sstruct.calcsize(Silf_part1_format_v3)
492        self.classes = Classes()
493        self.classes.decompile(data, ttFont, version)
494        for i in range(self.numPasses):
495            p = Pass()
496            self.passes.append(p)
497            p.decompile(
498                data[self.oPasses[i] - currpos : self.oPasses[i + 1] - currpos],
499                ttFont,
500                version,
501            )
502
503    def compile(self, ttFont, version=2.0):
504        self.numPasses = len(self.passes)
505        self.numJLevels = len(self.jLevels)
506        self.numCritFeatures = len(self.critFeatures)
507        numPseudo = len(self.pMap)
508        data = b""
509        if version >= 3.0:
510            hdroffset = sstruct.calcsize(Silf_part1_format_v3)
511        else:
512            hdroffset = 0
513        data += sstruct.pack(Silf_part1_format, self)
514        for j in self.jLevels:
515            data += sstruct.pack(Silf_justify_format, j)
516        data += sstruct.pack(Silf_part2_format, self)
517        if self.numCritFeatures:
518            data += struct.pack((">%dH" % self.numCritFeaturs), *self.critFeatures)
519        data += struct.pack("BB", 0, len(self.scriptTags))
520        if len(self.scriptTags):
521            tdata = [struct.pack("4s", x.encode("ascii")) for x in self.scriptTags]
522            data += b"".join(tdata)
523        data += struct.pack(">H", self.lbGID)
524        self.passOffset = len(data)
525
526        data1 = grUtils.bininfo(numPseudo, 6)
527        currpos = hdroffset + len(data) + 4 * (self.numPasses + 1)
528        self.pseudosOffset = currpos + len(data1)
529        for u, p in sorted(self.pMap.items()):
530            data1 += struct.pack(
531                (">LH" if version >= 3.0 else ">HH"), u, ttFont.getGlyphID(p)
532            )
533        data1 += self.classes.compile(ttFont, version)
534        currpos += len(data1)
535        data2 = b""
536        datao = b""
537        for i, p in enumerate(self.passes):
538            base = currpos + len(data2)
539            datao += struct.pack(">L", base)
540            data2 += p.compile(ttFont, base, version)
541        datao += struct.pack(">L", currpos + len(data2))
542
543        if version >= 3.0:
544            data3 = sstruct.pack(Silf_part1_format_v3, self)
545        else:
546            data3 = b""
547        return data3 + data + datao + data1 + data2
548
549    def toXML(self, writer, ttFont, version=2.0):
550        if version >= 3.0:
551            writer.simpletag("version", ruleVersion=self.ruleVersion)
552            writer.newline()
553        writesimple("info", self, writer, *attrs_info)
554        writesimple("passindexes", self, writer, *attrs_passindexes)
555        writesimple("contexts", self, writer, *attrs_contexts)
556        writesimple("attributes", self, writer, *attrs_attributes)
557        if len(self.jLevels):
558            writer.begintag("justifications")
559            writer.newline()
560            jformat, jnames, jfixes = sstruct.getformat(Silf_justify_format)
561            for i, j in enumerate(self.jLevels):
562                attrs = dict([(k, getattr(j, k)) for k in jnames])
563                writer.simpletag("justify", **attrs)
564                writer.newline()
565            writer.endtag("justifications")
566            writer.newline()
567        if len(self.critFeatures):
568            writer.begintag("critFeatures")
569            writer.newline()
570            writer.write(" ".join(map(str, self.critFeatures)))
571            writer.newline()
572            writer.endtag("critFeatures")
573            writer.newline()
574        if len(self.scriptTags):
575            writer.begintag("scriptTags")
576            writer.newline()
577            writer.write(" ".join(self.scriptTags))
578            writer.newline()
579            writer.endtag("scriptTags")
580            writer.newline()
581        if self.pMap:
582            writer.begintag("pseudoMap")
583            writer.newline()
584            for k, v in sorted(self.pMap.items()):
585                writer.simpletag("pseudo", unicode=hex(k), pseudo=v)
586                writer.newline()
587            writer.endtag("pseudoMap")
588            writer.newline()
589        self.classes.toXML(writer, ttFont, version)
590        if len(self.passes):
591            writer.begintag("passes")
592            writer.newline()
593            for i, p in enumerate(self.passes):
594                writer.begintag("pass", _index=i)
595                writer.newline()
596                p.toXML(writer, ttFont, version)
597                writer.endtag("pass")
598                writer.newline()
599            writer.endtag("passes")
600            writer.newline()
601
602    def fromXML(self, name, attrs, content, ttFont, version=2.0):
603        if name == "version":
604            self.ruleVersion = float(safeEval(attrs.get("ruleVersion", "0")))
605        if name == "info":
606            getSimple(self, attrs, *attrs_info)
607        elif name == "passindexes":
608            getSimple(self, attrs, *attrs_passindexes)
609        elif name == "contexts":
610            getSimple(self, attrs, *attrs_contexts)
611        elif name == "attributes":
612            getSimple(self, attrs, *attrs_attributes)
613        elif name == "justifications":
614            for element in content:
615                if not isinstance(element, tuple):
616                    continue
617                (tag, attrs, subcontent) = element
618                if tag == "justify":
619                    j = _Object()
620                    for k, v in attrs.items():
621                        setattr(j, k, int(v))
622                    self.jLevels.append(j)
623        elif name == "critFeatures":
624            self.critFeatures = []
625            element = content_string(content)
626            self.critFeatures.extend(map(int, element.split()))
627        elif name == "scriptTags":
628            self.scriptTags = []
629            element = content_string(content)
630            for n in element.split():
631                self.scriptTags.append(n)
632        elif name == "pseudoMap":
633            self.pMap = {}
634            for element in content:
635                if not isinstance(element, tuple):
636                    continue
637                (tag, attrs, subcontent) = element
638                if tag == "pseudo":
639                    k = int(attrs["unicode"], 16)
640                    v = attrs["pseudo"]
641                self.pMap[k] = v
642        elif name == "classes":
643            self.classes = Classes()
644            for element in content:
645                if not isinstance(element, tuple):
646                    continue
647                tag, attrs, subcontent = element
648                self.classes.fromXML(tag, attrs, subcontent, ttFont, version)
649        elif name == "passes":
650            for element in content:
651                if not isinstance(element, tuple):
652                    continue
653                tag, attrs, subcontent = element
654                if tag == "pass":
655                    p = Pass()
656                    for e in subcontent:
657                        if not isinstance(e, tuple):
658                            continue
659                        p.fromXML(e[0], e[1], e[2], ttFont, version)
660                    self.passes.append(p)
661
662
663class Classes(object):
664    def __init__(self):
665        self.linear = []
666        self.nonLinear = []
667
668    def decompile(self, data, ttFont, version=2.0):
669        sstruct.unpack2(Silf_classmap_format, data, self)
670        if version >= 4.0:
671            oClasses = struct.unpack(
672                (">%dL" % (self.numClass + 1)), data[4 : 8 + 4 * self.numClass]
673            )
674        else:
675            oClasses = struct.unpack(
676                (">%dH" % (self.numClass + 1)), data[4 : 6 + 2 * self.numClass]
677            )
678        for s, e in zip(oClasses[: self.numLinear], oClasses[1 : self.numLinear + 1]):
679            self.linear.append(
680                ttFont.getGlyphName(x)
681                for x in struct.unpack((">%dH" % ((e - s) / 2)), data[s:e])
682            )
683        for s, e in zip(
684            oClasses[self.numLinear : self.numClass],
685            oClasses[self.numLinear + 1 : self.numClass + 1],
686        ):
687            nonLinids = [
688                struct.unpack(">HH", data[x : x + 4]) for x in range(s + 8, e, 4)
689            ]
690            nonLin = dict([(ttFont.getGlyphName(x[0]), x[1]) for x in nonLinids])
691            self.nonLinear.append(nonLin)
692
693    def compile(self, ttFont, version=2.0):
694        data = b""
695        oClasses = []
696        if version >= 4.0:
697            offset = 8 + 4 * (len(self.linear) + len(self.nonLinear))
698        else:
699            offset = 6 + 2 * (len(self.linear) + len(self.nonLinear))
700        for l in self.linear:
701            oClasses.append(len(data) + offset)
702            gs = [ttFont.getGlyphID(x) for x in l]
703            data += struct.pack((">%dH" % len(l)), *gs)
704        for l in self.nonLinear:
705            oClasses.append(len(data) + offset)
706            gs = [(ttFont.getGlyphID(x[0]), x[1]) for x in l.items()]
707            data += grUtils.bininfo(len(gs))
708            data += b"".join([struct.pack(">HH", *x) for x in sorted(gs)])
709        oClasses.append(len(data) + offset)
710        self.numClass = len(oClasses) - 1
711        self.numLinear = len(self.linear)
712        return (
713            sstruct.pack(Silf_classmap_format, self)
714            + struct.pack(
715                ((">%dL" if version >= 4.0 else ">%dH") % len(oClasses)), *oClasses
716            )
717            + data
718        )
719
720    def toXML(self, writer, ttFont, version=2.0):
721        writer.begintag("classes")
722        writer.newline()
723        writer.begintag("linearClasses")
724        writer.newline()
725        for i, l in enumerate(self.linear):
726            writer.begintag("linear", _index=i)
727            writer.newline()
728            wrapline(writer, l)
729            writer.endtag("linear")
730            writer.newline()
731        writer.endtag("linearClasses")
732        writer.newline()
733        writer.begintag("nonLinearClasses")
734        writer.newline()
735        for i, l in enumerate(self.nonLinear):
736            writer.begintag("nonLinear", _index=i + self.numLinear)
737            writer.newline()
738            for inp, ind in l.items():
739                writer.simpletag("map", glyph=inp, index=ind)
740                writer.newline()
741            writer.endtag("nonLinear")
742            writer.newline()
743        writer.endtag("nonLinearClasses")
744        writer.newline()
745        writer.endtag("classes")
746        writer.newline()
747
748    def fromXML(self, name, attrs, content, ttFont, version=2.0):
749        if name == "linearClasses":
750            for element in content:
751                if not isinstance(element, tuple):
752                    continue
753                tag, attrs, subcontent = element
754                if tag == "linear":
755                    l = content_string(subcontent).split()
756                    self.linear.append(l)
757        elif name == "nonLinearClasses":
758            for element in content:
759                if not isinstance(element, tuple):
760                    continue
761                tag, attrs, subcontent = element
762                if tag == "nonLinear":
763                    l = {}
764                    for e in subcontent:
765                        if not isinstance(e, tuple):
766                            continue
767                        tag, attrs, subsubcontent = e
768                        if tag == "map":
769                            l[attrs["glyph"]] = int(safeEval(attrs["index"]))
770                    self.nonLinear.append(l)
771
772
773class Pass(object):
774    def __init__(self):
775        self.colMap = {}
776        self.rules = []
777        self.rulePreContexts = []
778        self.ruleSortKeys = []
779        self.ruleConstraints = []
780        self.passConstraints = b""
781        self.actions = []
782        self.stateTrans = []
783        self.startStates = []
784
785    def decompile(self, data, ttFont, version=2.0):
786        _, data = sstruct.unpack2(Silf_pass_format, data, self)
787        (numRange, _, _, _) = struct.unpack(">4H", data[:8])
788        data = data[8:]
789        for i in range(numRange):
790            (first, last, col) = struct.unpack(">3H", data[6 * i : 6 * i + 6])
791            for g in range(first, last + 1):
792                self.colMap[ttFont.getGlyphName(g)] = col
793        data = data[6 * numRange :]
794        oRuleMap = struct.unpack_from((">%dH" % (self.numSuccess + 1)), data)
795        data = data[2 + 2 * self.numSuccess :]
796        rules = struct.unpack_from((">%dH" % oRuleMap[-1]), data)
797        self.rules = [rules[s:e] for (s, e) in zip(oRuleMap, oRuleMap[1:])]
798        data = data[2 * oRuleMap[-1] :]
799        (self.minRulePreContext, self.maxRulePreContext) = struct.unpack("BB", data[:2])
800        numStartStates = self.maxRulePreContext - self.minRulePreContext + 1
801        self.startStates = struct.unpack(
802            (">%dH" % numStartStates), data[2 : 2 + numStartStates * 2]
803        )
804        data = data[2 + numStartStates * 2 :]
805        self.ruleSortKeys = struct.unpack(
806            (">%dH" % self.numRules), data[: 2 * self.numRules]
807        )
808        data = data[2 * self.numRules :]
809        self.rulePreContexts = struct.unpack(
810            ("%dB" % self.numRules), data[: self.numRules]
811        )
812        data = data[self.numRules :]
813        (self.collisionThreshold, pConstraint) = struct.unpack(">BH", data[:3])
814        oConstraints = list(
815            struct.unpack(
816                (">%dH" % (self.numRules + 1)), data[3 : 5 + self.numRules * 2]
817            )
818        )
819        data = data[5 + self.numRules * 2 :]
820        oActions = list(
821            struct.unpack((">%dH" % (self.numRules + 1)), data[: 2 + self.numRules * 2])
822        )
823        data = data[2 * self.numRules + 2 :]
824        for i in range(self.numTransitional):
825            a = array(
826                "H", data[i * self.numColumns * 2 : (i + 1) * self.numColumns * 2]
827            )
828            if sys.byteorder != "big":
829                a.byteswap()
830            self.stateTrans.append(a)
831        data = data[self.numTransitional * self.numColumns * 2 + 1 :]
832        self.passConstraints = data[:pConstraint]
833        data = data[pConstraint:]
834        for i in range(len(oConstraints) - 2, -1, -1):
835            if oConstraints[i] == 0:
836                oConstraints[i] = oConstraints[i + 1]
837        self.ruleConstraints = [
838            (data[s:e] if (e - s > 1) else b"")
839            for (s, e) in zip(oConstraints, oConstraints[1:])
840        ]
841        data = data[oConstraints[-1] :]
842        self.actions = [
843            (data[s:e] if (e - s > 1) else "") for (s, e) in zip(oActions, oActions[1:])
844        ]
845        data = data[oActions[-1] :]
846        # not using debug
847
848    def compile(self, ttFont, base, version=2.0):
849        # build it all up backwards
850        oActions = reduce(
851            lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.actions + [b""], (0, [])
852        )[1]
853        oConstraints = reduce(
854            lambda a, x: (a[0] + len(x), a[1] + [a[0]]),
855            self.ruleConstraints + [b""],
856            (1, []),
857        )[1]
858        constraintCode = b"\000" + b"".join(self.ruleConstraints)
859        transes = []
860        for t in self.stateTrans:
861            if sys.byteorder != "big":
862                t.byteswap()
863            transes.append(t.tobytes())
864            if sys.byteorder != "big":
865                t.byteswap()
866        if not len(transes):
867            self.startStates = [0]
868        oRuleMap = reduce(
869            lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.rules + [[]], (0, [])
870        )[1]
871        passRanges = []
872        gidcolmap = dict([(ttFont.getGlyphID(x[0]), x[1]) for x in self.colMap.items()])
873        for e in grUtils.entries(gidcolmap, sameval=True):
874            if e[1]:
875                passRanges.append((e[0], e[0] + e[1] - 1, e[2][0]))
876        self.numRules = len(self.actions)
877        self.fsmOffset = (
878            sstruct.calcsize(Silf_pass_format)
879            + 8
880            + len(passRanges) * 6
881            + len(oRuleMap) * 2
882            + 2 * oRuleMap[-1]
883            + 2
884            + 2 * len(self.startStates)
885            + 3 * self.numRules
886            + 3
887            + 4 * self.numRules
888            + 4
889        )
890        self.pcCode = (
891            self.fsmOffset + 2 * self.numTransitional * self.numColumns + 1 + base
892        )
893        self.rcCode = self.pcCode + len(self.passConstraints)
894        self.aCode = self.rcCode + len(constraintCode)
895        self.oDebug = 0
896        # now generate output
897        data = sstruct.pack(Silf_pass_format, self)
898        data += grUtils.bininfo(len(passRanges), 6)
899        data += b"".join(struct.pack(">3H", *p) for p in passRanges)
900        data += struct.pack((">%dH" % len(oRuleMap)), *oRuleMap)
901        flatrules = reduce(lambda a, x: a + x, self.rules, [])
902        data += struct.pack((">%dH" % oRuleMap[-1]), *flatrules)
903        data += struct.pack("BB", self.minRulePreContext, self.maxRulePreContext)
904        data += struct.pack((">%dH" % len(self.startStates)), *self.startStates)
905        data += struct.pack((">%dH" % self.numRules), *self.ruleSortKeys)
906        data += struct.pack(("%dB" % self.numRules), *self.rulePreContexts)
907        data += struct.pack(">BH", self.collisionThreshold, len(self.passConstraints))
908        data += struct.pack((">%dH" % (self.numRules + 1)), *oConstraints)
909        data += struct.pack((">%dH" % (self.numRules + 1)), *oActions)
910        return (
911            data
912            + b"".join(transes)
913            + struct.pack("B", 0)
914            + self.passConstraints
915            + constraintCode
916            + b"".join(self.actions)
917        )
918
919    def toXML(self, writer, ttFont, version=2.0):
920        writesimple("info", self, writer, *pass_attrs_info)
921        writesimple("fsminfo", self, writer, *pass_attrs_fsm)
922        writer.begintag("colmap")
923        writer.newline()
924        wrapline(
925            writer,
926            [
927                "{}={}".format(*x)
928                for x in sorted(
929                    self.colMap.items(), key=lambda x: ttFont.getGlyphID(x[0])
930                )
931            ],
932        )
933        writer.endtag("colmap")
934        writer.newline()
935        writer.begintag("staterulemap")
936        writer.newline()
937        for i, r in enumerate(self.rules):
938            writer.simpletag(
939                "state",
940                number=self.numRows - self.numSuccess + i,
941                rules=" ".join(map(str, r)),
942            )
943            writer.newline()
944        writer.endtag("staterulemap")
945        writer.newline()
946        writer.begintag("rules")
947        writer.newline()
948        for i in range(len(self.actions)):
949            writer.begintag(
950                "rule",
951                index=i,
952                precontext=self.rulePreContexts[i],
953                sortkey=self.ruleSortKeys[i],
954            )
955            writer.newline()
956            if len(self.ruleConstraints[i]):
957                writecode("constraint", writer, self.ruleConstraints[i])
958            writecode("action", writer, self.actions[i])
959            writer.endtag("rule")
960            writer.newline()
961        writer.endtag("rules")
962        writer.newline()
963        if len(self.passConstraints):
964            writecode("passConstraint", writer, self.passConstraints)
965        if len(self.stateTrans):
966            writer.begintag("fsm")
967            writer.newline()
968            writer.begintag("starts")
969            writer.write(" ".join(map(str, self.startStates)))
970            writer.endtag("starts")
971            writer.newline()
972            for i, s in enumerate(self.stateTrans):
973                writer.begintag("row", _i=i)
974                # no newlines here
975                writer.write(" ".join(map(str, s)))
976                writer.endtag("row")
977                writer.newline()
978            writer.endtag("fsm")
979            writer.newline()
980
981    def fromXML(self, name, attrs, content, ttFont, version=2.0):
982        if name == "info":
983            getSimple(self, attrs, *pass_attrs_info)
984        elif name == "fsminfo":
985            getSimple(self, attrs, *pass_attrs_fsm)
986        elif name == "colmap":
987            e = content_string(content)
988            for w in e.split():
989                x = w.split("=")
990                if len(x) != 2 or x[0] == "" or x[1] == "":
991                    continue
992                self.colMap[x[0]] = int(x[1])
993        elif name == "staterulemap":
994            for e in content:
995                if not isinstance(e, tuple):
996                    continue
997                tag, a, c = e
998                if tag == "state":
999                    self.rules.append([int(x) for x in a["rules"].split(" ")])
1000        elif name == "rules":
1001            for element in content:
1002                if not isinstance(element, tuple):
1003                    continue
1004                tag, a, c = element
1005                if tag != "rule":
1006                    continue
1007                self.rulePreContexts.append(int(a["precontext"]))
1008                self.ruleSortKeys.append(int(a["sortkey"]))
1009                con = b""
1010                act = b""
1011                for e in c:
1012                    if not isinstance(e, tuple):
1013                        continue
1014                    tag, a, subc = e
1015                    if tag == "constraint":
1016                        con = readcode(subc)
1017                    elif tag == "action":
1018                        act = readcode(subc)
1019                self.actions.append(act)
1020                self.ruleConstraints.append(con)
1021        elif name == "passConstraint":
1022            self.passConstraints = readcode(content)
1023        elif name == "fsm":
1024            for element in content:
1025                if not isinstance(element, tuple):
1026                    continue
1027                tag, a, c = element
1028                if tag == "row":
1029                    s = array("H")
1030                    e = content_string(c)
1031                    s.extend(map(int, e.split()))
1032                    self.stateTrans.append(s)
1033                elif tag == "starts":
1034                    s = []
1035                    e = content_string(c)
1036                    s.extend(map(int, e.split()))
1037                    self.startStates = s
1038