Support Silf table

This commit is contained in:
Martin Hosken 2017-09-13 14:30:11 +07:00 committed by Sascha Brawer
parent 575b44e1f6
commit bb162e00b9
3 changed files with 945 additions and 72 deletions

View File

@ -5,6 +5,7 @@ from fontTools.misc.textTools import safeEval
from itertools import *
from functools import partial
from . import DefaultTable
from . import grUtils
import struct, operator, warnings
try:
import lz4
@ -17,8 +18,9 @@ Glat_format_0 = """
version: 16.16F
"""
Glat_format_3_addition = """
Glat_format_3 = """
>
version: 16.16F
compression:L # compression scheme or reserved
"""
@ -69,42 +71,16 @@ class table_G__l_a_t(DefaultTable.DefaultTable):
DefaultTable.DefaultTable.__init__(self, tag)
self.scheme = 0
def decompress(self, size, data):
if self.scheme == 0:
pass
elif self.scheme == 1 and lz4:
res = lz4.decompress(struct.pack("<L", size) + data)
if (not res
or len(res) != size
or sstruct.unpack2(Glat_format_0, res)['version'] != self.version
or sstruct.unpack2(Glat_format_3_addition, res[4:]) >> 27 != 0):
warnings.warn("Glat table decompression failed.")
else:
data = res
else:
warnings.warn("Glat table is compressed with an unsupported compression scheme.")
return data
def compress(self, data):
hdr = {'compression' : (self.scheme << 27) + (len(data) & 0x07ffffff)}
hdrdat = sstruct.pack(Glat_format_0, self) + sstruct.pack(Glat_format_3_addition, hdr)
if self.scheme == 0 :
res = data
if self.scheme == 1 and lz4:
res = lz4.compress(hdrdat + data, 16, content_size_header=False)
return hdrdat + res
def decompile(self, data, ttFont):
sstruct.unpack2(Glat_format_0, data, self)
if self.version == 1.0:
if self.version <= 1.9:
decoder = partial(self.decompileAttributes12,fmt=Glat_format_1_entry)
elif self.version == 2.0:
elif self.version <= 2.9:
decoder = partial(self.decompileAttributes12,fmt=Glat_format_23_entry)
elif self.version == 3.0:
hdr, _ = sstruct.unpack2(Glat_format_3_addition, data[4:])
self.scheme = hdr['compression'] >> 27
if self.scheme :
data = self.decompress(hdr['compression'] & 0x07ffffff, data[8:])
elif self.version >= 3.0:
(data, self.scheme) = grUtils.decompress(data)
sstruct.unpack2(Glat_format_3, data, self)
self.hasOctaboxes = (self.compression & 1) == 1
decoder = self.decompileAttributes3
gloc = ttFont['Gloc']
@ -130,26 +106,28 @@ class table_G__l_a_t(DefaultTable.DefaultTable):
return attributes
def decompileAttributes3(self, data):
o, data = sstruct.unpack2(Glat_format_3_octabox_metrics, data, _Object())
numsub = bin(o.subboxBitmap).count("1")
o.subboxes = []
for b in range(numsub):
if len(data) >= 8 :
subbox, data = sstruct.unpack2(Glat_format_3_subbox_entry, data, _Object())
o.subboxes.append(subbox)
if self.hasOctaboxes:
o, data = sstruct.unpack2(Glat_format_3_octabox_metrics, data, _Object())
numsub = bin(o.subboxBitmap).count("1")
o.subboxes = []
for b in range(numsub):
if len(data) >= 8 :
subbox, data = sstruct.unpack2(Glat_format_3_subbox_entry, data, _Object())
o.subboxes.append(subbox)
attrs = self.decompileAttributes12(data, Glat_format_23_entry)
attrs.octabox = o
if self.hasOctaboxes:
attrs.octabox = o
return attrs
def compile(self, ttFont):
data = sstruct.pack(Glat_format_0, self)
if self.version == 1.0:
if self.version <= 1.9:
encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
elif self.version == 2.0:
elif self.version <= 2.9:
encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
elif self.version == 3.0:
compression = self.scheme << 27
data += sstruct.pack(Glat_format_3_addition, {'compression': compression})
elif self.version >= 3.0:
self.compression = (self.scheme << 27) + (1 if self.hasOctaboxes else 0)
data = sstruct.pack(Glat_format_3, self)
encoder = self.compileAttributes3
glocs = []
@ -166,27 +144,30 @@ class table_G__l_a_t(DefaultTable.DefaultTable):
glocs.append(len(data))
ttFont['Gloc'].set(glocs)
if self.version == 3.0:
data = self.compress(data[8:])
if self.version >= 3.0:
data = grUtils.compress(self.scheme, data)
return data
def compileAttributes12(self, attrs, fmt):
data = []
for e in entries(attrs):
data.extend(sstruct.pack(fmt, e))
data.extend(struct.pack(('>%dh' % len(e['values'])), *e['values']))
for e in grUtils.entries(attrs):
data.extend(sstruct.pack(fmt, {'attNum' : e[0], 'num' : e[1]}))
data.extend(struct.pack(('>%dh' % len(e[2])), *e[2]))
return "".join(data)
def compileAttributes3(self, attrs):
o = attrs.octabox
data = sstruct.pack(Glat_format_3_octabox_metrics, o)
numsub = bin(o.subboxBitmap).count("1")
for b in range(numsub) :
data += sstruct.pack(Glat_format_3_subbox_entry, o.subboxes[b])
if self.hasOctaboxes:
o = attrs.octabox
data = sstruct.pack(Glat_format_3_octabox_metrics, o)
numsub = bin(o.subboxBitmap).count("1")
for b in range(numsub) :
data += sstruct.pack(Glat_format_3_subbox_entry, o.subboxes[b])
else:
data = ""
return data + self.compileAttributes12(attrs, Glat_format_23_entry)
def toXML(self, writer, ttFont):
writer.simpletag('version', version=self.version)
writer.simpletag('version', version=self.version, compressionScheme=self.scheme)
writer.newline()
for n, a in sorted(self.attributes.items()):
writer.begintag('glyph', name=n)
@ -232,6 +213,7 @@ class table_G__l_a_t(DefaultTable.DefaultTable):
v = int(safeEval(attrs['value']))
attributes[k]=v
elif tag == 'octaboxes':
self.hasOctaboxes = True
o = _Object()
o.subboxBitmap = int(attrs['bitmap'], 16)
o.subboxes = []
@ -247,18 +229,3 @@ class table_G__l_a_t(DefaultTable.DefaultTable):
o.subboxes.append(so)
attributes.octabox = o
self.attributes[gname] = attributes
def _entries(attrs):
ak = 0
vals = []
for k,v in attrs:
if len(vals) and k != ak + 1 :
yield {'attNum': ak - len(vals) + 1, 'num':len(vals), 'values':vals}
vals = []
ak = k
vals.append(v)
yield {'attNum': ak - len(vals) + 1, 'num':len(vals), 'values':vals}
def entries(attributes):
g = _entries(sorted(attributes.iteritems(), key=lambda x:int(x[0])))
return g

View File

@ -0,0 +1,854 @@
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from itertools import *
from functools import partial
from . import DefaultTable
from . import grUtils
from array import array
import struct, operator, warnings, re
Silf_hdr_format = '''
>
version: 16.16F
compilerVersion: L
numSilf: H
x
x
'''
Silf_part1_format_v3 = '''
>
ruleVersion: 16.16F
passOffset: H
pseudosOffset: H
'''
Silf_part1_format = '''
>
maxGlyphID: H
extraAscent: h
extraDescent: h
numPasses: B
iSubst: B
iPos: B
iJust: B
iBidi: B
flags: B
maxPreContext: B
maxPostContext: B
attrPseudo: B
attrBreakWeight: B
attrDirectionality: B
attrMirroring: B
attrSkipPasses: B
numJLevels: B
'''
Silf_justify_format = '''
>
attrStretch: B
attrShrink: B
attrStep: B
attrWeight: B
runto: B
x
x
x
'''
Silf_part2_format = '''
>
numLigComp: H
numUserDefn: B
maxCompPerLig: B
direction: B
attCollisions: B
x
x
x
numCritFeatures: B
'''
Silf_pseudomap_format = '''
>
unicode: L
nPseudo: H
'''
Silf_classmap_format = '''
>
numClass: H
numLinear: H
'''
Silf_lookupclass_format = '''
>
numIDs: H
searchRange: H
entrySelector: H
rangeShift: H
'''
Silf_lookuppair_format = '''
>
glyphId: H
index: H
'''
Silf_pass_format = '''
>
flags: B
maxRuleLoop: B
maxRuleContext: B
maxBackup: B
numRules: H
fsmOffset: H
pcCode: L
rcCode: L
aCode: L
oDebug: L
numRows: H
numTransitional: H
numSuccess: H
numColumns: H
'''
aCode_info = (
("NOP", 0),
("PUSH_BYTE", "b"),
("PUSH_BYTE_U", "B"),
("PUSH_SHORT", ">h"),
("PUSH_SHORT_U", ">H"),
("PUSH_LONG", ">L"),
("ADD", 0),
("SUB", 0),
("MUL", 0),
("DIV", 0),
("MIN", 0),
("MAX", 0),
("NEG", 0),
("TRUNC8", 0),
("TRUNC16", 0),
("COND", 0),
("AND", 0), # x10
("OR", 0),
("NOT", 0),
("EQUAL", 0),
("NOT_EQ", 0),
("LESS", 0),
("GTR", 0),
("LESS_EQ", 0),
("GTR_EQ", 0),
("NEXT", 0),
("NEXT_N", "b"),
("COPY_NEXT", 0),
("PUT_GLYPH_8BIT_OBS", "B"),
("PUT_SUBS_8BIT_OBS", "bBB"),
("PUT_COPY", "b"),
("INSERT", 0),
("DELETE", 0), # x20
("ASSOC", -1),
("CNTXT_ITEM", "bB"),
("ATTR_SET", "B"),
("ATTR_ADD", "B"),
("ATTR_SUB", "B"),
("ATTR_SET_SLOT", "B"),
("IATTR_SET_SLOT", "BB"),
("PUSH_SLOT_ATTR", "Bb"),
("PUSH_GLYPH_ATTR_OBS", "Bb"),
("PUSH_GLYPH_METRIC", "Bbb"),
("PUSH_FEAT", "Bb"),
("PUSH_ATT_TO_GATTR_OBS", "Bbb"),
("PUSH_ATT_TO_GLYPH_METRIC", "Bbb"),
("PUSH_ISLOT_ATTR", "Bbb"),
("PUSH_IGLYPH_ATTR", "Bbb"),
("POP_RET", 0), # x30
("RET_ZERO", 0),
("RET_TRUE", 0),
("IATTR_SET", "BB"),
("IATTR_ADD", "BB"),
("IATTR_SUB", "BB"),
("PUSH_PROC_STATE", "B"),
("PUSH_VERSION", 0),
("PUT_SUBS", ">bHH"),
("PUT_SUBS2", 0),
("PUT_SUBS3", 0),
("PUT_GLYPH", ">H"),
("PUSH_GLYPH_ATTR", ">Hb"),
("PUSH_ATT_TO_GLYPH_ATTR", ">Hb"),
("BITOR", 0),
("BITAND", 0),
("BITNOT", 0), # x40
("BITSET", ">HH"),
("SET_FEAT", "Bb")
)
aCode_map = dict([(x[0], (i, x[1])) for i,x in enumerate(aCode_info)])
def disassemble(aCode):
codelen = len(aCode)
pc = 0
res = []
while pc < codelen:
opcode = ord(aCode[pc])
if opcode > len(aCode_info):
instr = aCode_info[0]
else:
instr = aCode_info[opcode]
pc += 1
if instr[1] != 0 and pc >= codelen : return res
if instr[1] == -1:
count = ord(aCode[pc])
fmt = "%dB" % count
pc += 1
elif instr[1] == 0:
fmt = ""
else :
fmt = instr[1]
if fmt == "":
res.append(instr[0])
continue
parms = struct.unpack_from(fmt, aCode[pc:])
res.append(instr[0] + "(" + ", ".join(map(str, parms)) + ")")
pc += struct.calcsize(fmt)
return res
instre = re.compile("^\s*([^(]+)\s*(?:\(([^)]+)\))?")
def assemble(instrs):
res = []
for inst in instrs:
m = instre.match(inst)
if not m or not m.group(1) in aCode_map:
continue
opcode, parmfmt = aCode_map[m.group(1)]
res.append(chr(opcode))
if m.group(2):
if parmfmt == 0:
continue
parms = map(int, re.split(",\s*", m.group(2)))
if parmfmt == -1:
res.append(chr(len(parms)))
res.append("".join(map(chr, parms)))
else:
res.append(struct.pack(parmfmt, *parms))
return "".join(res)
def writecode(tag, writer, instrs):
writer.begintag(tag)
writer.newline()
for l in disassemble(instrs):
writer.write(l)
writer.newline()
writer.endtag(tag)
writer.newline()
def readcode(content):
res = []
for e in content_string(content).split('\n'):
e = e.strip()
if not len(e): continue
res.append(e)
return assemble(res)
attrs_info=('flags', 'extraAscent', 'extraDescent', 'maxGlyphID',
'numLigComp', 'numUserDefn', 'maxCompPerLig', 'direction', 'lbGID')
attrs_passindexes = ('iSubst', 'iPos', 'iJust', 'iBidi')
attrs_contexts = ('maxPreContext', 'maxPostContext')
attrs_attributes = ('attrPseudo', 'attrBreakWeight',
'attrDirectionality', 'attrMirroring', 'attrSkipPasses', 'attCollisions')
pass_attrs_info = ('flags', 'maxRuleLoop', 'maxRuleContext', 'maxBackup', 'minRulePreContext',
'maxRulePreContext', 'collisionThreshold')
pass_attrs_fsm = ('numRows', 'numTransitional', 'numSuccess', 'numColumns')
def writesimple(tag, self, writer, *attrkeys):
attrs = dict([(k, getattr(self, k)) for k in attrkeys])
writer.simpletag(tag, **attrs)
writer.newline()
def getSimple(self, attrs, *attr_list):
for k in attr_list:
if k in attrs:
setattr(self, k, int(safeEval(attrs[k])))
def content_string(contents):
res = ""
for element in contents:
if isinstance(element, tuple): continue
res += element
return res.strip()
def bininfo(num, size=1):
if num == 0:
return struct.pack(">4H", 0, 0, 0, 0)
srange = 1;
select = 0
while srange <= num:
srange *= 2
select += 1
select -= 1
srange /= 2
srange *= size
shift = num * size - srange
return struct.pack(">4H", num, srange, select, shift)
class _Object() :
pass
class table_S__i_l_f(DefaultTable.DefaultTable):
'''Silf table support'''
def __init__(self, tag=None):
DefaultTable.DefaultTable.__init__(self, tag)
self.silfs = []
def decompile(self, data, ttFont):
sstruct.unpack2(Silf_hdr_format, data, self)
if self.version >= 5.0:
(data, self.scheme) = grUtils.decompress(data)
sstruct.unpack2(Silf_hdr_format, data, self)
else:
self.scheme = 0
silfoffsets = struct.unpack_from(('>%dL' % self.numSilf), data[sstruct.calcsize(Silf_hdr_format):])
for offset in silfoffsets:
s = Silf()
self.silfs.append(s)
s.decompile(data[offset:], ttFont, self.version)
def compile(self, ttFont):
self.numSilf = len(self.silfs)
hdr = sstruct.pack(Silf_hdr_format, self)
offset = len(hdr) + 4 * self.numSilf
data = ""
for s in self.silfs:
hdr += struct.pack(">L", offset)
subdata = s.compile(ttFont, self.version)
offset += len(subdata)
data += subdata
if self.version >= 5.0:
return grUtils.compress(self.scheme, hdr+data)
return hdr+data
def toXML(self, writer, ttFont):
writer.simpletag('version', version=self.version, compilerVersion=self.compilerVersion, compressionScheme=self.scheme)
writer.newline()
for s in self.silfs:
writer.begintag('silf')
writer.newline()
s.toXML(writer, ttFont, self.version)
writer.endtag('silf')
writer.newline()
def fromXML(self, name, attrs, content, ttFont):
if name == 'version':
self.scheme=int(safeEval(attrs['compressionScheme']))
self.version = float(safeEval(attrs['version']))
self.compilerVersion = int(safeEval(attrs['compilerVersion']))
return
if name == 'silf':
s = Silf()
self.silfs.append(s)
for element in content:
if not isinstance(element, tuple): continue
tag, attrs, subcontent = element
s.fromXML(tag, attrs, subcontent, ttFont, self.version)
class Silf(object):
'''A particular Silf subtable'''
def __init__(self):
self.passes = []
self.scriptTags = []
self.critFeatures = []
self.jLevels = []
self.pMap = {}
def decompile(self, data, ttFont, version=2.0):
if version >= 3.0 :
_, data = sstruct.unpack2(Silf_part1_format_v3, data, self)
_, data = sstruct.unpack2(Silf_part1_format, data, self)
for jlevel in range(self.numJLevels):
j, data = sstruct.unpack2(Silf_justify_format, data, _Object())
self.jLevels.append(j)
_, data = sstruct.unpack2(Silf_part2_format, data, self)
if self.numCritFeatures:
self.critFeatures = struct.unpack_from(('>%dH' % self.numCritFeatures), data)
data = data[self.numCritFeatures * 2 + 1:]
(numScriptTag,) = struct.unpack_from('B', data)
if numScriptTag:
self.scriptTags = [struct.unpack("4s", data[x:x+4])[0] for x in range(1, 1 + 4 * numScriptTag, 4)]
data = data[1 + 4 * numScriptTag:]
(self.lbGID,) = struct.unpack('>H', data[:2])
if self.numPasses:
self.oPasses = struct.unpack(('>%dL' % (self.numPasses+1)), data[2:6+4*self.numPasses])
data = data[6 + 4 * self.numPasses:]
(numPseudo,) = struct.unpack(">H", data[:2])
for i in range(numPseudo):
pseudo = sstruct.unpack(Silf_pseudomap_format, data[8+6*i:14+6*i], _Object())
self.pMap[pseudo.unicode] = pseudo.nPseudo
data = data[8 + 6 * numPseudo:]
currpos = sstruct.calcsize(Silf_part1_format) + sstruct.calcsize(Silf_justify_format) * self.numJLevels + \
sstruct.calcsize(Silf_part2_format) + 2 * self.numCritFeatures + 1 + 1 + 4 * numScriptTag + \
6 + 4 * self.numPasses + 8 + 6 * numPseudo
if version >= 3.0:
currpos += sstruct.calcsize(Silf_part1_format_v3)
self.classes = Classes()
self.classes.decompile(data, ttFont, version)
for i in range(self.numPasses):
p = Pass()
self.passes.append(p)
p.decompile(data[self.oPasses[i]-currpos:self.oPasses[i+1]-currpos], ttFont, version)
def compile(self, ttFont, version=2.0):
self.numPasses = len(self.passes)
self.numJLevels = len(self.jLevels)
self.numCritFeatures = len(self.critFeatures)
numPseudo = len(self.pMap)
data = ""
if version >= 3.0:
hdroffset = sstruct.calcsize(Silf_part1_format_v3)
else:
hdroffset = 0
data += sstruct.pack(Silf_part1_format, self)
for j in self.jLevels:
data += sstruct.pack(Silf_justify_format, j)
data += sstruct.pack(Silf_part2_format, self)
if self.numCritFeatures:
data += struct.pack((">%dH" % self.numCritFeaturs), *self.critFeatures)
data += struct.pack("BB", 0, len(self.scriptTags))
if len(self.scriptTags):
tdata = [struct.pack("4s", x) for x in self.scriptTags]
data += "".join(tdata)
data += struct.pack(">H", self.lbGID)
self.passOffset = len(data)
data1 = bininfo(numPseudo, 6)
currpos = hdroffset + len(data) + 4 * (self.numPasses + 1)
self.pseudosOffset = currpos + len(data1)
for u, p in sorted(self.pMap.items()):
data1 += struct.pack(">LH", u, p)
data1 += self.classes.compile(ttFont, version)
currpos += len(data1)
data2 = ""
datao = ""
for i, p in enumerate(self.passes):
base = currpos + len(data2)
datao += struct.pack(">L", base)
data2 += p.compile(ttFont, base, version)
datao += struct.pack(">L", currpos + len(data2))
if version >= 3.0:
data3 = sstruct.pack(Silf_part1_format_v3, self)
else:
data3 = ""
return data3 + data + datao + data1 + data2
def toXML(self, writer, ttFont, version=2.0):
if version >= 3.0:
writer.simpletag('version', ruleVersion=self.ruleVersion)
writer.newline()
writesimple('info', self, writer, *attrs_info)
writesimple('passindexes', self, writer, *attrs_passindexes)
writesimple('contexts', self, writer, *attrs_contexts)
writesimple('attributes', self, writer, *attrs_attributes)
if len(self.jLevels):
writer.begintag('justifications')
writer.newline()
jformat, jnames, jfixes = sstruct.getformat(Silf_justify_format)
for i, j in enumerate(self.jLevels):
attrs = dict([(k, getattr(j, k)) for k in jnames])
writer.simpletag('justify', **attrs)
writer.newline()
writer.endtag('justifications')
writer.newline()
if self.numCritFeatures:
writer.begintag('critFeatures')
writer.newline()
writer.write(" ".join(map(str, self.critFeatures)))
writer.newline()
writer.endtag('critFeatures')
writer.newline()
if len(self.scriptTags):
writer.begintag('scriptTags')
writer.newline()
writer.write(" ".join(self.scriptTags))
writer.newline()
writer.endtag('scriptTags')
writer.newline()
if self.pMap:
writer.begintag('pseudoMap')
writer.newline()
for k, v in sorted(self.pMap.items()):
writer.simpletag('pseudo', unicode=hex(k), pseudo=v)
writer.newline()
writer.endtag('pseudoMap')
writer.newline()
self.classes.toXML(writer, ttFont, version)
if self.numPasses:
writer.begintag('passes')
writer.newline()
for i, p in enumerate(self.passes):
writer.begintag('pass', index=i)
writer.newline()
p.toXML(writer, ttFont, version)
writer.endtag('pass')
writer.newline()
writer.endtag('passes')
writer.newline()
def fromXML(self, name, attrs, content, ttFont, version=2.0):
if name == 'version':
self.ruleVersion = float(safeEval(attrs.get('ruleVersion', "0")))
if name == 'info':
getSimple(self, attrs, *attrs_info)
elif name == 'passindexes':
getSimple(self, attrs, *attrs_passindexes)
elif name == 'contexts':
getSimple(self, attrs, *attrs_contexts)
elif name == 'attributes':
getSimple(self, attrs, *attrs_attributes)
elif name == 'justifications':
for element in content:
if not isinstance(element, tuple): continue
(tag, attrs, subcontent) = element
if tag == 'justify':
j = _Object()
for k, v in attrs.items():
setattr(j, k, int(v))
self.jLevels.append(j)
elif name == 'critFeatures':
self.critFeatures = []
element = content_string(content)
self.critFeatures.extend(map(int, element.split()))
elif name == 'scriptTags':
self.scriptTags = []
element = content_string(content)
for n in element.split():
self.scriptTags.append(n)
elif name == 'pseudoMap':
self.pMap = {}
for element in content:
if not isinstance(element, tuple): continue
(tag, attrs, subcontent) = element
if tag == 'pseudo':
k = int(attrs['unicode'], 16)
v = int(safeEval(attrs['pseudo']))
self.pMap[k] = v
elif name == 'classes':
self.classes = Classes()
for element in content:
if not isinstance(element, tuple): continue
tag, attrs, subcontent = element
self.classes.fromXML(tag, attrs, subcontent, ttFont, version)
elif name == 'passes':
for element in content:
if not isinstance(element, tuple): continue
tag, attrs, subcontent = element
if tag == 'pass':
p = Pass()
for e in subcontent:
if not isinstance(e, tuple): continue
p.fromXML(e[0], e[1], e[2], ttFont, version)
self.passes.append(p)
class Classes(object):
def __init__(self):
self.linear = []
self.nonLinear = []
def decompile(self, data, ttFont, version=2.0):
sstruct.unpack2(Silf_classmap_format, data, self)
if version >= 4.0 :
oClasses = struct.unpack((">%dL" % (self.numClass+1)), data[4:8+4*self.numClass])
else:
oClasses = struct.unpack((">%dH" % (self.numClass+1)), data[4:6+2*self.numClass])
for s,e in zip(oClasses[:self.numLinear], oClasses[1:self.numLinear+1]):
self.linear.append(struct.unpack((">%dH" % ((e-s)/2)), data[s:e]))
for s,e in zip(oClasses[self.numLinear:self.numClass], oClasses[self.numLinear+1:self.numClass+1]):
nonLin = sstruct.unpack(Silf_lookupclass_format, data[s:s+8], _Object())
nonLin.lookups = [struct.unpack(">HH", data[x:x+4]) for x in range(s+8, e, 4)]
self.nonLinear.append(nonLin)
def compile(self, ttFont, version=2.0):
data = ""
oClasses = []
offset = 8 + 4 * (len(self.linear) + len(self.nonLinear))
for l in self.linear:
oClasses.append(len(data) + offset)
data += struct.pack((">%dH" % len(l)), *l)
for l in self.nonLinear:
oClasses.append(len(data) + offset)
data += bininfo(len(l.lookups))
data += "".join([struct.pack(">HH", *x) for x in l.lookups])
oClasses.append(len(data) + offset)
self.numClass = len(oClasses) - 1
self.numLinear = len(self.linear)
return sstruct.pack(Silf_classmap_format, self) + \
struct.pack((">%dL" % len(oClasses)), *oClasses) + \
data
def toXML(self, writer, ttFont, version=2.0):
writer.begintag('classes')
writer.newline()
writer.begintag('linearClasses')
writer.newline()
for i,l in enumerate(self.linear):
writer.begintag('linear', index=i)
writer.newline()
writer.write(" ".join(map(str, l)))
writer.newline()
writer.endtag('linear')
writer.newline()
writer.endtag('linearClasses')
writer.newline()
writer.begintag('nonLinearClasses')
writer.newline()
for i,l in enumerate(self.nonLinear):
writer.begintag('nonLinear', index=i + self.numLinear)
writer.newline()
for o in l.lookups:
writer.simpletag('map', gid=o[0], index=o[1])
writer.newline()
writer.endtag('nonLinear')
writer.newline()
writer.endtag('nonLinearClasses')
writer.newline()
writer.endtag('classes')
writer.newline()
def fromXML(self, name, attrs, content, ttFont, version=2.0):
if name == 'linearClasses':
for element in content:
if not isinstance(element, tuple): continue
tag, attrs, subcontent = element
if tag == 'linear':
e = content_string(subcontent)
l = [int(x) for x in e.split() if x]
self.linear.append(l)
elif name == 'nonLinearClasses':
for element in content:
if not isinstance(element, tuple): continue
tag, attrs, subcontent = element
if tag =='nonLinear':
l = _Object()
l.lookups = []
for e in subcontent:
if not isinstance(e, tuple): continue
tag, attrs, subsubcontent = e
if tag == 'map':
l.lookups.append((int(safeEval(attrs['gid'])), int(safeEval(attrs['index']))))
l.lookups.sort(key=lambda x: x[0])
self.nonLinear.append(l)
class Pass(object):
def __init__(self):
self.colMap = {}
self.rules = []
self.rulePreContexts = []
self.ruleSortKeys = []
self.ruleConstraints = []
self.passConstraints = ""
self.actions = []
self.stateTrans = []
self.startStates = []
def decompile(self, data, ttFont, version=2.0):
_, data = sstruct.unpack2(Silf_pass_format, data, self)
(numRange, _, _, _) = struct.unpack(">4H", data[:8])
data = data[8:]
for i in range(numRange):
(first, last, col) = struct.unpack(">3H", data[6*i:6*i+6])
for g in range(first, last+1):
self.colMap[g] = col
data = data[6*numRange:]
oRuleMap = struct.unpack_from((">%dH" % (self.numSuccess + 1)), data)
data = data[2+2*self.numSuccess:]
rules = struct.unpack_from((">%dH" % oRuleMap[-1]), data)
self.rules = [rules[s:e] for (s,e) in izip(oRuleMap, oRuleMap[1:])]
data = data[2*oRuleMap[-1]:]
(self.minRulePreContext, self.maxRulePreContext) = struct.unpack('BB', data[:2])
numStartStates = self.maxRulePreContext - self.minRulePreContext + 1
self.startStates = struct.unpack((">%dH" % numStartStates), data[2:2 + numStartStates * 2])
data = data[2+numStartStates*2:]
self.ruleSortKeys = struct.unpack((">%dH" % self.numRules), data[:2 * self.numRules])
data = data[2*self.numRules:]
self.rulePreContexts = struct.unpack(("%dB" % self.numRules), data[:self.numRules])
data = data[self.numRules:]
(self.collisionThreshold, pConstraint) = struct.unpack(">BH", data[:3])
oConstraints = list(struct.unpack((">%dH" % (self.numRules + 1)), data[3:5 + self.numRules * 2]))
data = data[5 + self.numRules * 2:]
oActions = list(struct.unpack((">%dH" % (self.numRules + 1)), data[:2 + self.numRules * 2]))
data = data[2 * self.numRules + 2:]
for i in range(self.numTransitional):
a = array("H", data[i*self.numColumns*2:(i+1)*self.numColumns*2])
a.byteswap()
self.stateTrans.append(a)
data = data[self.numTransitional * self.numColumns * 2 + 1:]
self.passConstraints = data[:pConstraint]
data = data[pConstraint:]
for i in range(len(oConstraints)-1,0,-1):
if oConstraints[i] == 0 :
oConstraints[i] = oConstraints[i+1]
self.ruleConstraints = [data[s:e] for (s,e) in izip(oConstraints, oConstraints[1:])]
data = data[oConstraints[-1]:]
for i in range(len(oActions)-1,0,-1):
if oActions[i] == 0:
oActions[i] = oActions[i+1]
self.actions = [data[s:e] for (s,e) in izip(oActions, oActions[1:])]
data = data[oActions[-1]:]
# not using debug
def compile(self, ttFont, base, version=2.0):
# build it all up backwards
oActions = reduce(lambda (a,b), x: (a+len(x), b+[a]), self.actions + [""], (0, []))[1]
oConstraints = reduce(lambda (a,b), x: (a+len(x), b+[a]), self.ruleConstraints + [""], (0, []))[1]
constraintCode = "".join(self.ruleConstraints)
transes = []
for t in self.stateTrans:
t.byteswap()
transes.append(t.tostring())
t.byteswap()
if not len(transes):
self.startStates = [0]
oRuleMap = reduce(lambda (a,b), x: (a+len(x), b+[a]), self.rules+[[]], (0, []))[1]
passRanges = []
for e in grUtils.entries(self.colMap, sameval = True):
if e[1]:
passRanges.append((e[0], e[0]+e[1]-1, e[2][0]))
self.numRules = len(self.actions)
self.fsmOffset = sstruct.calcsize(Silf_pass_format) + 8 + len(passRanges) * 6 \
+ len(oRuleMap) * 2 + 2 * oRuleMap[-1] + 2 \
+ 2 * len(self.startStates) \
+ 3 * self.numRules + 3 + 4 * self.numRules + 4
self.pcCode = self.fsmOffset + 2 * self.numTransitional * self.numColumns + 1 + base
self.rcCode = self.pcCode + len(self.passConstraints)
self.aCode = self.rcCode + len(constraintCode)
self.oDebug = 0
# now generate output
data = sstruct.pack(Silf_pass_format, self)
data += bininfo(len(passRanges), 6)
data += "".join(struct.pack(">3H", *p) for p in passRanges)
data += struct.pack((">%dH" % len(oRuleMap)), *oRuleMap)
flatrules = reduce(lambda a,x: a+x, self.rules, [])
data += struct.pack((">%dH" % oRuleMap[-1]), *flatrules)
data += struct.pack("BB", self.minRulePreContext, self.maxRulePreContext)
data += struct.pack((">%dH" % len(self.startStates)), *self.startStates)
data += struct.pack((">%dH" % self.numRules), *self.ruleSortKeys)
data += struct.pack(("%dB" % self.numRules), *self.rulePreContexts)
data += struct.pack(">BH", self.collisionThreshold, len(self.passConstraints))
data += struct.pack((">%dH" % (self.numRules+1)), *oConstraints)
data += struct.pack((">%dH" % (self.numRules+1)), *oActions)
return data + "".join(transes) + struct.pack("B", 0) + self.passConstraints + constraintCode + "".join(self.actions)
def toXML(self, writer, ttFont, version=2.0):
writesimple('info', self, writer, *pass_attrs_info)
writesimple('fsminfo', self, writer, *pass_attrs_fsm)
writer.begintag('colmap')
writer.newline()
currline = ""
for (g, c) in sorted(self.colMap.items()):
s = "{}={} ".format(g, c)
if len(s) + len(currline) > 80:
writer.write(currline)
writer.newline()
currline = ""
currline += s
if len(currline):
writer.write(currline)
writer.newline()
writer.endtag('colmap')
writer.newline()
writer.begintag('staterulemap')
writer.newline()
for i, r in enumerate(self.rules):
writer.simpletag('state', number = self.numRows - self.numSuccess + i, rules = " ".join(map(str, r)))
writer.newline()
writer.endtag('staterulemap')
writer.newline()
writer.begintag('rules')
writer.newline()
for i in range(self.numRules):
writer.begintag('rule', index=i, precontext=self.rulePreContexts[i], sortkey=self.ruleSortKeys[i])
writer.newline()
if len(self.ruleConstraints[i]):
writecode('constraint', writer, self.ruleConstraints[i])
writecode('action', writer, self.actions[i])
writer.endtag('rule')
writer.newline()
writer.endtag('rules')
writer.newline()
if len(self.passConstraints):
writecode('passConstraint', writer, self.passConstraints)
if len(self.stateTrans):
writer.begintag('fsm')
writer.newline()
writer.begintag('starts')
writer.write(" ".join(map(str, self.startStates)))
writer.endtag('starts')
writer.newline()
for i, s in enumerate(self.stateTrans):
writer.begintag('row', i=i)
# no newlines here
writer.write(" ".join(map(str, s)))
writer.endtag('row')
writer.newline()
writer.endtag('fsm')
writer.newline()
def fromXML(self, name, attrs, content, ttFont, version=2.0):
if name == 'info':
getSimple(self, attrs, *pass_attrs_info)
elif name == 'fsminfo':
getSimple(self, attrs, *pass_attrs_fsm)
elif name == 'colmap':
e = content_string(content)
for w in e.split():
x = w.split('=')
if len(x) != 2 or x[0] == '' or x[1] == '': continue
self.colMap[int(x[0])] = int(x[1])
elif name == 'staterulemap':
for e in content:
if not isinstance(e, tuple): continue
tag, a, c = e
if tag == 'state':
self.rules.append(map(int, a['rules'].split(" ")))
elif name == 'rules':
for element in content:
if not isinstance(element, tuple): continue
tag, a, c = element
if tag != 'rule': continue
self.rulePreContexts.append(int(a['precontext']))
self.ruleSortKeys.append(int(a['sortkey']))
con = ""
act = ""
for e in c:
if not isinstance(e, tuple): continue
tag, a, subc = e
if tag == 'constraint':
con = readcode(subc)
elif tag == 'action':
act = readcode(subc)
self.actions.append(act)
self.ruleConstraints.append(con)
elif name == 'passConstraint':
self.passConstraints = readcode(content)
elif name == 'fsm':
for element in content:
if not isinstance(element, tuple): continue
tag, a, c = element
if tag == 'row':
s = array('H')
e = content_string(c)
s.extend(map(int, e.split()))
self.stateTrans.append(s)
elif tag == 'starts':
s = []
e = content_string(c)
s.extend(map(int, e.split()))
self.startStates = s

View File

@ -0,0 +1,52 @@
import struct, warnings
try:
import lz4
except:
lz4 = None
#old scheme for VERSION < 0.9 otherwise use lz4.block
def decompress(data):
(compression,) = struct.unpack(">L", data[4:8])
scheme = compression >> 27
size = compression & 0x07ffffff
if scheme == 0:
pass
elif scheme == 1 and lz4:
res = lz4.decompress(struct.pack("<L", size) + data[8:])
if len(res) != size:
warnings.warn("Table decompression failed.")
else:
data = res
else:
warnings.warn("Table is compressed with an unsupported compression scheme")
return (data, scheme)
def compress(scheme, data):
hdr = data[:4] + struct.pack(">L", (scheme << 27) + (len(data) & 0x07ffffff))
if scheme == 0 :
return data
elif scheme == 1 and lz4:
res = lz4.compress(hdr + data)
return res
else:
warnings.warn("Table failed to compress by unsupported compression scheme")
return data
def _entries(attrs, sameval):
ak = 0
vals = []
lastv = 0
for k,v in attrs:
if len(vals) and (k != ak + 1 or (sameval and v != lastv)) :
yield (ak - len(vals) + 1, len(vals), vals)
vals = []
ak = k
vals.append(v)
lastv = v
yield (ak - len(vals) + 1, len(vals), vals)
def entries(attributes, sameval = False):
g = _entries(sorted(attributes.iteritems(), key=lambda x:int(x[0])), sameval)
return g