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