Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.misc import sstruct
      4 from fontTools.misc.textTools import safeEval
      5 from itertools import *
      6 from . import DefaultTable
      7 from . import grUtils
      8 from array import array
      9 from functools import reduce
     10 import struct, operator, warnings, re, sys
     11 
     12 Silf_hdr_format = '''
     13     >
     14     version:            16.16F
     15 '''
     16 
     17 Silf_hdr_format_3 = '''
     18     >
     19     version:            16.16F
     20     compilerVersion:    L
     21     numSilf:            H
     22                         x
     23                         x
     24 '''
     25 
     26 Silf_part1_format_v3 = '''
     27     >
     28     ruleVersion:        16.16F
     29     passOffset:         H
     30     pseudosOffset:      H
     31 '''
     32 
     33 Silf_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 
     54 Silf_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 
     66 Silf_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 
     79 Silf_pseudomap_format = '''
     80     >
     81     unicode:            L
     82     nPseudo:            H
     83 '''
     84 
     85 Silf_classmap_format = '''
     86     >
     87     numClass:           H
     88     numLinear:          H
     89 '''
     90 
     91 Silf_lookupclass_format = '''
     92     >
     93     numIDs:             H
     94     searchRange:        H
     95     entrySelector:      H
     96     rangeShift:         H
     97 '''
     98 
     99 Silf_lookuppair_format = '''
    100     >
    101     glyphId:            H
    102     index:              H
    103 '''
    104 
    105 Silf_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 
    123 aCode_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 )
    192 aCode_map = dict([(x[0], (i, x[1])) for i,x in enumerate(aCode_info)])
    193 
    194 def 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 
    222 instre = re.compile("^\s*([^(]+)\s*(?:\(([^)]+)\))?")
    223 def 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 
    242 def 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 
    251 def 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     
    259 attrs_info=('flags', 'extraAscent', 'extraDescent', 'maxGlyphID',
    260             'numLigComp', 'numUserDefn', 'maxCompPerLig', 'direction', 'lbGID')
    261 attrs_passindexes = ('iSubst', 'iPos', 'iJust', 'iBidi')
    262 attrs_contexts = ('maxPreContext', 'maxPostContext')
    263 attrs_attributes = ('attrPseudo', 'attrBreakWeight', 'attrDirectionality',
    264                     'attrMirroring', 'attrSkipPasses', 'attCollisions')
    265 pass_attrs_info = ('flags', 'maxRuleLoop', 'maxRuleContext', 'maxBackup',
    266             'minRulePreContext', 'maxRulePreContext', 'collisionThreshold')
    267 pass_attrs_fsm = ('numRows', 'numTransitional', 'numSuccess', 'numColumns')
    268 
    269 def 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 
    274 def 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 
    279 def content_string(contents):
    280     res = ""
    281     for element in contents:
    282         if isinstance(element, tuple): continue
    283         res += element
    284     return res.strip()
    285 
    286 def 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 
    298 class _Object() :
    299     pass
    300 
    301 class 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 
    375 class 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 
    581 class 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 
    676 class 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