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