1from fontTools.misc.py23 import byteord
2from fontTools.misc import sstruct
3from fontTools.misc.fixedTools import floatToFixedToStr
4from fontTools.misc.textTools import safeEval
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
200def disassemble(aCode):
201    codelen = len(aCode)
202    pc = 0
203    res = []
204    while pc < codelen:
205        opcode = byteord(aCode[pc:pc+1])
206        if opcode > len(aCode_info):
207            instr = aCode_info[0]
208        else:
209            instr = aCode_info[opcode]
210        pc += 1
211        if instr[1] != 0 and pc >= codelen : return res
212        if instr[1] == -1:
213            count = byteord(aCode[pc])
214            fmt = "%dB" % count
215            pc += 1
216        elif instr[1] == 0:
217            fmt = ""
218        else :
219            fmt = instr[1]
220        if fmt == "":
221            res.append(instr[0])
222            continue
223        parms = struct.unpack_from(fmt, aCode[pc:])
224        res.append(instr[0] + "(" + ", ".join(map(str, parms)) + ")")
225        pc += struct.calcsize(fmt)
226    return res
227
228instre = re.compile(r"^\s*([^(]+)\s*(?:\(([^)]+)\))?")
229def assemble(instrs):
230    res = b""
231    for inst in instrs:
232        m = instre.match(inst)
233        if not m or not m.group(1) in aCode_map:
234            continue
235        opcode, parmfmt = aCode_map[m.group(1)]
236        res += struct.pack("B", opcode)
237        if m.group(2):
238            if parmfmt == 0:
239                continue
240            parms = [int(x) for x in re.split(r",\s*", m.group(2))]
241            if parmfmt == -1:
242                l = len(parms)
243                res += struct.pack(("%dB" % (l+1)), l, *parms)
244            else:
245                res += struct.pack(parmfmt, *parms)
246    return res
247
248def writecode(tag, writer, instrs):
249    writer.begintag(tag)
250    writer.newline()
251    for l in disassemble(instrs):
252        writer.write(l)
253        writer.newline()
254    writer.endtag(tag)
255    writer.newline()
256
257def readcode(content):
258    res = []
259    for e in content_string(content).split('\n'):
260        e = e.strip()
261        if not len(e): continue
262        res.append(e)
263    return assemble(res)
264
265attrs_info=('flags', 'extraAscent', 'extraDescent', 'maxGlyphID',
266            'numLigComp', 'numUserDefn', 'maxCompPerLig', 'direction', 'lbGID')
267attrs_passindexes = ('iSubst', 'iPos', 'iJust', 'iBidi')
268attrs_contexts = ('maxPreContext', 'maxPostContext')
269attrs_attributes = ('attrPseudo', 'attrBreakWeight', 'attrDirectionality',
270                    'attrMirroring', 'attrSkipPasses', 'attCollisions')
271pass_attrs_info = ('flags', 'maxRuleLoop', 'maxRuleContext', 'maxBackup',
272            'minRulePreContext', 'maxRulePreContext', 'collisionThreshold')
273pass_attrs_fsm = ('numRows', 'numTransitional', 'numSuccess', 'numColumns')
274
275def writesimple(tag, self, writer, *attrkeys):
276    attrs = dict([(k, getattr(self, k)) for k in attrkeys])
277    writer.simpletag(tag, **attrs)
278    writer.newline()
279
280def getSimple(self, attrs, *attr_list):
281    for k in attr_list:
282        if k in attrs:
283            setattr(self, k, int(safeEval(attrs[k])))
284
285def content_string(contents):
286    res = ""
287    for element in contents:
288        if isinstance(element, tuple): continue
289        res += element
290    return res.strip()
291
292def wrapline(writer, dat, length=80):
293    currline = ""
294    for d in dat:
295        if len(currline) > length:
296            writer.write(currline[:-1])
297            writer.newline()
298            currline = ""
299        currline += d + " "
300    if len(currline):
301        writer.write(currline[:-1])
302        writer.newline()
303
304class _Object() :
305    pass
306
307class table_S__i_l_f(DefaultTable.DefaultTable):
308    '''Silf table support'''
309
310    def __init__(self, tag=None):
311        DefaultTable.DefaultTable.__init__(self, tag)
312        self.silfs = []
313
314    def decompile(self, data, ttFont):
315        sstruct.unpack2(Silf_hdr_format, data, self)
316        self.version = float(floatToFixedToStr(self.version, precisionBits=16))
317        if self.version >= 5.0:
318            (data, self.scheme) = grUtils.decompress(data)
319            sstruct.unpack2(Silf_hdr_format_3, data, self)
320            base = sstruct.calcsize(Silf_hdr_format_3)
321        elif self.version < 3.0:
322            self.numSilf = struct.unpack('>H', data[4:6])
323            self.scheme = 0
324            self.compilerVersion = 0
325            base = 8
326        else:
327            self.scheme = 0
328            sstruct.unpack2(Silf_hdr_format_3, data, self)
329            base = sstruct.calcsize(Silf_hdr_format_3)
330
331        silfoffsets = struct.unpack_from(('>%dL' % self.numSilf), data[base:])
332        for offset in silfoffsets:
333            s = Silf()
334            self.silfs.append(s)
335            s.decompile(data[offset:], ttFont, self.version)
336
337    def compile(self, ttFont):
338        self.numSilf = len(self.silfs)
339        if self.version < 3.0:
340            hdr = sstruct.pack(Silf_hdr_format, self)
341            hdr += struct.pack(">HH", self.numSilf, 0)
342        else:
343            hdr = sstruct.pack(Silf_hdr_format_3, self)
344        offset = len(hdr) + 4 * self.numSilf
345        data = b""
346        for s in self.silfs:
347            hdr += struct.pack(">L", offset)
348            subdata = s.compile(ttFont, self.version)
349            offset += len(subdata)
350            data += subdata
351        if self.version >= 5.0:
352            return grUtils.compress(self.scheme, hdr+data)
353        return hdr+data
354
355    def toXML(self, writer, ttFont):
356        writer.comment('Attributes starting with _ are informative only')
357        writer.newline()
358        writer.simpletag('version', version=self.version,
359            compilerVersion=self.compilerVersion, compressionScheme=self.scheme)
360        writer.newline()
361        for s in self.silfs:
362            writer.begintag('silf')
363            writer.newline()
364            s.toXML(writer, ttFont, self.version)
365            writer.endtag('silf')
366            writer.newline()
367
368    def fromXML(self, name, attrs, content, ttFont):
369        if name == 'version':
370            self.scheme=int(safeEval(attrs['compressionScheme']))
371            self.version = float(safeEval(attrs['version']))
372            self.compilerVersion = int(safeEval(attrs['compilerVersion']))
373            return
374        if name == 'silf':
375            s = Silf()
376            self.silfs.append(s)
377            for element in content:
378                if not isinstance(element, tuple): continue
379                tag, attrs, subcontent = element
380                s.fromXML(tag, attrs, subcontent, ttFont, self.version)
381
382class Silf(object):
383    '''A particular Silf subtable'''
384
385    def __init__(self):
386        self.passes = []
387        self.scriptTags = []
388        self.critFeatures = []
389        self.jLevels = []
390        self.pMap = {}
391
392    def decompile(self, data, ttFont, version=2.0):
393        if version >= 3.0 :
394            _, data = sstruct.unpack2(Silf_part1_format_v3, data, self)
395            self.ruleVersion = float(floatToFixedToStr(self.ruleVersion, precisionBits=16))
396        _, data = sstruct.unpack2(Silf_part1_format, data, self)
397        for jlevel in range(self.numJLevels):
398            j, data = sstruct.unpack2(Silf_justify_format, data, _Object())
399            self.jLevels.append(j)
400        _, data = sstruct.unpack2(Silf_part2_format, data, self)
401        if self.numCritFeatures:
402            self.critFeatures = struct.unpack_from(('>%dH' % self.numCritFeatures), data)
403        data = data[self.numCritFeatures * 2 + 1:]
404        (numScriptTag,) = struct.unpack_from('B', data)
405        if numScriptTag:
406            self.scriptTags = [struct.unpack("4s", data[x:x+4])[0].decode("ascii") for x in range(1, 1 + 4 * numScriptTag, 4)]
407        data = data[1 + 4 * numScriptTag:]
408        (self.lbGID,) = struct.unpack('>H', data[:2])
409        if self.numPasses:
410            self.oPasses = struct.unpack(('>%dL' % (self.numPasses+1)), data[2:6+4*self.numPasses])
411        data = data[6 + 4 * self.numPasses:]
412        (numPseudo,) = struct.unpack(">H", data[:2])
413        for i in range(numPseudo):
414            if version >= 3.0:
415                pseudo = sstruct.unpack(Silf_pseudomap_format, data[8+6*i:14+6*i], _Object())
416            else:
417                pseudo = sstruct.unpack(Silf_pseudomap_format_h, data[8+4*i:12+4*i], _Object())
418            self.pMap[pseudo.unicode] = ttFont.getGlyphName(pseudo.nPseudo)
419        data = data[8 + 6 * numPseudo:]
420        currpos = (sstruct.calcsize(Silf_part1_format)
421                    + sstruct.calcsize(Silf_justify_format) * self.numJLevels
422                    + sstruct.calcsize(Silf_part2_format) + 2 * self.numCritFeatures
423                    + 1 + 1 + 4 * numScriptTag + 6 + 4 * self.numPasses + 8 + 6 * numPseudo)
424        if version >= 3.0:
425            currpos += sstruct.calcsize(Silf_part1_format_v3)
426        self.classes = Classes()
427        self.classes.decompile(data, ttFont, version)
428        for i in range(self.numPasses):
429            p = Pass()
430            self.passes.append(p)
431            p.decompile(data[self.oPasses[i]-currpos:self.oPasses[i+1]-currpos],
432                        ttFont, version)
433
434    def compile(self, ttFont, version=2.0):
435        self.numPasses = len(self.passes)
436        self.numJLevels = len(self.jLevels)
437        self.numCritFeatures = len(self.critFeatures)
438        numPseudo = len(self.pMap)
439        data = b""
440        if version >= 3.0:
441            hdroffset = sstruct.calcsize(Silf_part1_format_v3)
442        else:
443            hdroffset = 0
444        data += sstruct.pack(Silf_part1_format, self)
445        for j in self.jLevels:
446            data += sstruct.pack(Silf_justify_format, j)
447        data += sstruct.pack(Silf_part2_format, self)
448        if self.numCritFeatures:
449            data += struct.pack((">%dH" % self.numCritFeaturs), *self.critFeatures)
450        data += struct.pack("BB", 0, len(self.scriptTags))
451        if len(self.scriptTags):
452            tdata = [struct.pack("4s", x.encode("ascii")) for x in self.scriptTags]
453            data += b"".join(tdata)
454        data += struct.pack(">H", self.lbGID)
455        self.passOffset = len(data)
456
457        data1 = grUtils.bininfo(numPseudo, 6)
458        currpos = hdroffset + len(data) + 4 * (self.numPasses + 1)
459        self.pseudosOffset = currpos + len(data1)
460        for u, p in sorted(self.pMap.items()):
461            data1 += struct.pack((">LH" if version >= 3.0 else ">HH"),
462                                u, ttFont.getGlyphID(p))
463        data1 += self.classes.compile(ttFont, version)
464        currpos += len(data1)
465        data2 = b""
466        datao = b""
467        for i, p in enumerate(self.passes):
468            base = currpos + len(data2)
469            datao += struct.pack(">L", base)
470            data2 += p.compile(ttFont, base, version)
471        datao += struct.pack(">L", currpos + len(data2))
472
473        if version >= 3.0:
474            data3 = sstruct.pack(Silf_part1_format_v3, self)
475        else:
476            data3 = b""
477        return data3 + data + datao + data1 + data2
478
479
480    def toXML(self, writer, ttFont, version=2.0):
481        if version >= 3.0:
482            writer.simpletag('version', ruleVersion=self.ruleVersion)
483            writer.newline()
484        writesimple('info', self, writer, *attrs_info)
485        writesimple('passindexes', self, writer, *attrs_passindexes)
486        writesimple('contexts', self, writer, *attrs_contexts)
487        writesimple('attributes', self, writer, *attrs_attributes)
488        if len(self.jLevels):
489            writer.begintag('justifications')
490            writer.newline()
491            jformat, jnames, jfixes = sstruct.getformat(Silf_justify_format)
492            for i, j in enumerate(self.jLevels):
493                attrs = dict([(k, getattr(j, k)) for k in jnames])
494                writer.simpletag('justify', **attrs)
495                writer.newline()
496            writer.endtag('justifications')
497            writer.newline()
498        if len(self.critFeatures):
499            writer.begintag('critFeatures')
500            writer.newline()
501            writer.write(" ".join(map(str, self.critFeatures)))
502            writer.newline()
503            writer.endtag('critFeatures')
504            writer.newline()
505        if len(self.scriptTags):
506            writer.begintag('scriptTags')
507            writer.newline()
508            writer.write(" ".join(self.scriptTags))
509            writer.newline()
510            writer.endtag('scriptTags')
511            writer.newline()
512        if self.pMap:
513            writer.begintag('pseudoMap')
514            writer.newline()
515            for k, v in sorted(self.pMap.items()):
516                writer.simpletag('pseudo', unicode=hex(k), pseudo=v)
517                writer.newline()
518            writer.endtag('pseudoMap')
519            writer.newline()
520        self.classes.toXML(writer, ttFont, version)
521        if len(self.passes):
522            writer.begintag('passes')
523            writer.newline()
524            for i, p in enumerate(self.passes):
525                writer.begintag('pass', _index=i)
526                writer.newline()
527                p.toXML(writer, ttFont, version)
528                writer.endtag('pass')
529                writer.newline()
530            writer.endtag('passes')
531            writer.newline()
532
533    def fromXML(self, name, attrs, content, ttFont, version=2.0):
534        if name == 'version':
535            self.ruleVersion = float(safeEval(attrs.get('ruleVersion', "0")))
536        if name == 'info':
537            getSimple(self, attrs, *attrs_info)
538        elif name == 'passindexes':
539            getSimple(self, attrs, *attrs_passindexes)
540        elif name == 'contexts':
541            getSimple(self, attrs, *attrs_contexts)
542        elif name == 'attributes':
543            getSimple(self, attrs, *attrs_attributes)
544        elif name == 'justifications':
545            for element in content:
546                if not isinstance(element, tuple): continue
547                (tag, attrs, subcontent) = element
548                if tag == 'justify':
549                    j = _Object()
550                    for k, v in attrs.items():
551                        setattr(j, k, int(v))
552                    self.jLevels.append(j)
553        elif name == 'critFeatures':
554            self.critFeatures = []
555            element = content_string(content)
556            self.critFeatures.extend(map(int, element.split()))
557        elif name == 'scriptTags':
558            self.scriptTags = []
559            element = content_string(content)
560            for n in element.split():
561                self.scriptTags.append(n)
562        elif name == 'pseudoMap':
563            self.pMap = {}
564            for element in content:
565                if not isinstance(element, tuple): continue
566                (tag, attrs, subcontent) = element
567                if tag == 'pseudo':
568                    k = int(attrs['unicode'], 16)
569                    v = attrs['pseudo']
570                self.pMap[k] = v
571        elif name == 'classes':
572            self.classes = Classes()
573            for element in content:
574                if not isinstance(element, tuple): continue
575                tag, attrs, subcontent = element
576                self.classes.fromXML(tag, attrs, subcontent, ttFont, version)
577        elif name == 'passes':
578            for element in content:
579                if not isinstance(element, tuple): continue
580                tag, attrs, subcontent = element
581                if tag == 'pass':
582                    p = Pass()
583                    for e in subcontent:
584                        if not isinstance(e, tuple): continue
585                        p.fromXML(e[0], e[1], e[2], ttFont, version)
586                    self.passes.append(p)
587
588
589class Classes(object):
590
591    def __init__(self):
592        self.linear = []
593        self.nonLinear = []
594
595    def decompile(self, data, ttFont, version=2.0):
596        sstruct.unpack2(Silf_classmap_format, data, self)
597        if version >= 4.0 :
598            oClasses = struct.unpack((">%dL" % (self.numClass+1)),
599                                        data[4:8+4*self.numClass])
600        else:
601            oClasses = struct.unpack((">%dH" % (self.numClass+1)),
602                                        data[4:6+2*self.numClass])
603        for s,e in zip(oClasses[:self.numLinear], oClasses[1:self.numLinear+1]):
604            self.linear.append(ttFont.getGlyphName(x) for x in
605                                   struct.unpack((">%dH" % ((e-s)/2)), data[s:e]))
606        for s,e in zip(oClasses[self.numLinear:self.numClass],
607                        oClasses[self.numLinear+1:self.numClass+1]):
608            nonLinids = [struct.unpack(">HH", data[x:x+4]) for x in range(s+8, e, 4)]
609            nonLin = dict([(ttFont.getGlyphName(x[0]), x[1]) for x in nonLinids])
610            self.nonLinear.append(nonLin)
611
612    def compile(self, ttFont, version=2.0):
613        data = b""
614        oClasses = []
615        if version >= 4.0:
616            offset = 8 + 4 * (len(self.linear) + len(self.nonLinear))
617        else:
618            offset = 6 + 2 * (len(self.linear) + len(self.nonLinear))
619        for l in self.linear:
620            oClasses.append(len(data) + offset)
621            gs = [ttFont.getGlyphID(x) for x in l]
622            data += struct.pack((">%dH" % len(l)), *gs)
623        for l in self.nonLinear:
624            oClasses.append(len(data) + offset)
625            gs = [(ttFont.getGlyphID(x[0]), x[1]) for x in l.items()]
626            data += grUtils.bininfo(len(gs))
627            data += b"".join([struct.pack(">HH", *x) for x in sorted(gs)])
628        oClasses.append(len(data) + offset)
629        self.numClass = len(oClasses) - 1
630        self.numLinear = len(self.linear)
631        return sstruct.pack(Silf_classmap_format, self) + \
632               struct.pack(((">%dL" if version >= 4.0 else ">%dH") % len(oClasses)),
633                            *oClasses) + data
634
635    def toXML(self, writer, ttFont, version=2.0):
636        writer.begintag('classes')
637        writer.newline()
638        writer.begintag('linearClasses')
639        writer.newline()
640        for i,l in enumerate(self.linear):
641            writer.begintag('linear', _index=i)
642            writer.newline()
643            wrapline(writer, l)
644            writer.endtag('linear')
645            writer.newline()
646        writer.endtag('linearClasses')
647        writer.newline()
648        writer.begintag('nonLinearClasses')
649        writer.newline()
650        for i, l in enumerate(self.nonLinear):
651            writer.begintag('nonLinear', _index=i + self.numLinear)
652            writer.newline()
653            for inp, ind in l.items():
654                writer.simpletag('map', glyph=inp, index=ind)
655                writer.newline()
656            writer.endtag('nonLinear')
657            writer.newline()
658        writer.endtag('nonLinearClasses')
659        writer.newline()
660        writer.endtag('classes')
661        writer.newline()
662
663    def fromXML(self, name, attrs, content, ttFont, version=2.0):
664        if name == 'linearClasses':
665            for element in content:
666                if not isinstance(element, tuple): continue
667                tag, attrs, subcontent = element
668                if tag == 'linear':
669                    l = content_string(subcontent).split()
670                    self.linear.append(l)
671        elif name == 'nonLinearClasses':
672            for element in content:
673                if not isinstance(element, tuple): continue
674                tag, attrs, subcontent = element
675                if tag =='nonLinear':
676                    l = {}
677                    for e in subcontent:
678                        if not isinstance(e, tuple): continue
679                        tag, attrs, subsubcontent = e
680                        if tag == 'map':
681                            l[attrs['glyph']] = int(safeEval(attrs['index']))
682                    self.nonLinear.append(l)
683
684class Pass(object):
685
686    def __init__(self):
687        self.colMap = {}
688        self.rules = []
689        self.rulePreContexts = []
690        self.ruleSortKeys = []
691        self.ruleConstraints = []
692        self.passConstraints = b""
693        self.actions = []
694        self.stateTrans = []
695        self.startStates = []
696
697    def decompile(self, data, ttFont, version=2.0):
698        _, data = sstruct.unpack2(Silf_pass_format, data, self)
699        (numRange, _, _, _) = struct.unpack(">4H", data[:8])
700        data = data[8:]
701        for i in range(numRange):
702            (first, last, col) = struct.unpack(">3H", data[6*i:6*i+6])
703            for g in range(first, last+1):
704                self.colMap[ttFont.getGlyphName(g)] = col
705        data = data[6*numRange:]
706        oRuleMap = struct.unpack_from((">%dH" % (self.numSuccess + 1)), data)
707        data = data[2+2*self.numSuccess:]
708        rules = struct.unpack_from((">%dH" % oRuleMap[-1]), data)
709        self.rules = [rules[s:e] for (s,e) in zip(oRuleMap, oRuleMap[1:])]
710        data = data[2*oRuleMap[-1]:]
711        (self.minRulePreContext, self.maxRulePreContext) = struct.unpack('BB', data[:2])
712        numStartStates = self.maxRulePreContext - self.minRulePreContext + 1
713        self.startStates = struct.unpack((">%dH" % numStartStates),
714                                        data[2:2 + numStartStates * 2])
715        data = data[2+numStartStates*2:]
716        self.ruleSortKeys = struct.unpack((">%dH" % self.numRules), data[:2 * self.numRules])
717        data = data[2*self.numRules:]
718        self.rulePreContexts = struct.unpack(("%dB" % self.numRules), data[:self.numRules])
719        data = data[self.numRules:]
720        (self.collisionThreshold, pConstraint) = struct.unpack(">BH", data[:3])
721        oConstraints = list(struct.unpack((">%dH" % (self.numRules + 1)),
722                                        data[3:5 + self.numRules * 2]))
723        data = data[5 + self.numRules * 2:]
724        oActions = list(struct.unpack((">%dH" % (self.numRules + 1)),
725                                        data[:2 + self.numRules * 2]))
726        data = data[2 * self.numRules + 2:]
727        for i in range(self.numTransitional):
728            a = array("H", data[i*self.numColumns*2:(i+1)*self.numColumns*2])
729            if sys.byteorder != "big": a.byteswap()
730            self.stateTrans.append(a)
731        data = data[self.numTransitional * self.numColumns * 2 + 1:]
732        self.passConstraints = data[:pConstraint]
733        data = data[pConstraint:]
734        for i in range(len(oConstraints)-2,-1,-1):
735            if oConstraints[i] == 0 :
736                oConstraints[i] = oConstraints[i+1]
737        self.ruleConstraints = [(data[s:e] if (e-s > 1) else b"") for (s,e) in zip(oConstraints, oConstraints[1:])]
738        data = data[oConstraints[-1]:]
739        self.actions = [(data[s:e] if (e-s > 1) else "") for (s,e) in zip(oActions, oActions[1:])]
740        data = data[oActions[-1]:]
741        # not using debug
742
743    def compile(self, ttFont, base, version=2.0):
744        # build it all up backwards
745        oActions = reduce(lambda a, x: (a[0]+len(x), a[1]+[a[0]]), self.actions + [b""], (0, []))[1]
746        oConstraints = reduce(lambda a, x: (a[0]+len(x), a[1]+[a[0]]), self.ruleConstraints + [b""], (1, []))[1]
747        constraintCode = b"\000" + b"".join(self.ruleConstraints)
748        transes = []
749        for t in self.stateTrans:
750            if sys.byteorder != "big": t.byteswap()
751            transes.append(t.tobytes())
752            if sys.byteorder != "big": t.byteswap()
753        if not len(transes):
754            self.startStates = [0]
755        oRuleMap = reduce(lambda a, x: (a[0]+len(x), a[1]+[a[0]]), self.rules+[[]], (0, []))[1]
756        passRanges = []
757        gidcolmap = dict([(ttFont.getGlyphID(x[0]), x[1]) for x in self.colMap.items()])
758        for e in grUtils.entries(gidcolmap, sameval = True):
759            if e[1]:
760                passRanges.append((e[0], e[0]+e[1]-1, e[2][0]))
761        self.numRules = len(self.actions)
762        self.fsmOffset = (sstruct.calcsize(Silf_pass_format) + 8 + len(passRanges) * 6
763                    + len(oRuleMap) * 2 + 2 * oRuleMap[-1] + 2
764                    + 2 * len(self.startStates) + 3 * self.numRules + 3
765                    + 4 * self.numRules + 4)
766        self.pcCode = self.fsmOffset + 2*self.numTransitional*self.numColumns + 1 + base
767        self.rcCode = self.pcCode + len(self.passConstraints)
768        self.aCode = self.rcCode + len(constraintCode)
769        self.oDebug = 0
770        # now generate output
771        data = sstruct.pack(Silf_pass_format, self)
772        data += grUtils.bininfo(len(passRanges), 6)
773        data += b"".join(struct.pack(">3H", *p) for p in passRanges)
774        data += struct.pack((">%dH" % len(oRuleMap)), *oRuleMap)
775        flatrules = reduce(lambda a,x: a+x, self.rules, [])
776        data += struct.pack((">%dH" % oRuleMap[-1]), *flatrules)
777        data += struct.pack("BB", self.minRulePreContext, self.maxRulePreContext)
778        data += struct.pack((">%dH" % len(self.startStates)), *self.startStates)
779        data += struct.pack((">%dH" % self.numRules), *self.ruleSortKeys)
780        data += struct.pack(("%dB" % self.numRules), *self.rulePreContexts)
781        data += struct.pack(">BH", self.collisionThreshold, len(self.passConstraints))
782        data += struct.pack((">%dH" % (self.numRules+1)), *oConstraints)
783        data += struct.pack((">%dH" % (self.numRules+1)), *oActions)
784        return data + b"".join(transes) + struct.pack("B", 0) + \
785                self.passConstraints + constraintCode + b"".join(self.actions)
786
787    def toXML(self, writer, ttFont, version=2.0):
788        writesimple('info', self, writer, *pass_attrs_info)
789        writesimple('fsminfo', self, writer, *pass_attrs_fsm)
790        writer.begintag('colmap')
791        writer.newline()
792        wrapline(writer, ["{}={}".format(*x) for x in sorted(self.colMap.items(),
793                                        key=lambda x:ttFont.getGlyphID(x[0]))])
794        writer.endtag('colmap')
795        writer.newline()
796        writer.begintag('staterulemap')
797        writer.newline()
798        for i, r in enumerate(self.rules):
799            writer.simpletag('state', number = self.numRows - self.numSuccess + i,
800                                rules = " ".join(map(str, r)))
801            writer.newline()
802        writer.endtag('staterulemap')
803        writer.newline()
804        writer.begintag('rules')
805        writer.newline()
806        for i in range(len(self.actions)):
807            writer.begintag('rule', index=i, precontext=self.rulePreContexts[i],
808                            sortkey=self.ruleSortKeys[i])
809            writer.newline()
810            if len(self.ruleConstraints[i]):
811                writecode('constraint', writer, self.ruleConstraints[i])
812            writecode('action', writer, self.actions[i])
813            writer.endtag('rule')
814            writer.newline()
815        writer.endtag('rules')
816        writer.newline()
817        if len(self.passConstraints):
818            writecode('passConstraint', writer, self.passConstraints)
819        if len(self.stateTrans):
820            writer.begintag('fsm')
821            writer.newline()
822            writer.begintag('starts')
823            writer.write(" ".join(map(str, self.startStates)))
824            writer.endtag('starts')
825            writer.newline()
826            for i, s in enumerate(self.stateTrans):
827                writer.begintag('row', _i=i)
828                # no newlines here
829                writer.write(" ".join(map(str, s)))
830                writer.endtag('row')
831                writer.newline()
832            writer.endtag('fsm')
833            writer.newline()
834
835    def fromXML(self, name, attrs, content, ttFont, version=2.0):
836        if name == 'info':
837            getSimple(self, attrs, *pass_attrs_info)
838        elif name == 'fsminfo':
839            getSimple(self, attrs, *pass_attrs_fsm)
840        elif name == 'colmap':
841            e = content_string(content)
842            for w in e.split():
843                x = w.split('=')
844                if len(x) != 2 or x[0] == '' or x[1] == '': continue
845                self.colMap[x[0]] = int(x[1])
846        elif name == 'staterulemap':
847            for e in content:
848                if not isinstance(e, tuple): continue
849                tag, a, c = e
850                if tag == 'state':
851                    self.rules.append([int(x) for x in a['rules'].split(" ")])
852        elif name == 'rules':
853            for element in content:
854                if not isinstance(element, tuple): continue
855                tag, a, c = element
856                if tag != 'rule': continue
857                self.rulePreContexts.append(int(a['precontext']))
858                self.ruleSortKeys.append(int(a['sortkey']))
859                con = b""
860                act = b""
861                for e in c:
862                    if not isinstance(e, tuple): continue
863                    tag, a, subc = e
864                    if tag == 'constraint':
865                        con = readcode(subc)
866                    elif tag == 'action':
867                        act = readcode(subc)
868                self.actions.append(act)
869                self.ruleConstraints.append(con)
870        elif name == 'passConstraint':
871            self.passConstraints = readcode(content)
872        elif name == 'fsm':
873            for element in content:
874                if not isinstance(element, tuple): continue
875                tag, a, c = element
876                if tag == 'row':
877                    s = array('H')
878                    e = content_string(c)
879                    s.extend(map(int, e.split()))
880                    self.stateTrans.append(s)
881                elif tag == 'starts':
882                    s = []
883                    e = content_string(c)
884                    s.extend(map(int, e.split()))
885                    self.startStates = s
886
887