1041 lines
34 KiB
Python
1041 lines
34 KiB
Python
from fontTools.misc import sstruct
|
|
from fontTools.misc.fixedTools import floatToFixedToStr
|
|
from fontTools.misc.textTools import byteord, safeEval
|
|
|
|
# from itertools import *
|
|
from . import DefaultTable
|
|
from . import grUtils
|
|
from array import array
|
|
from functools import reduce
|
|
import struct, re, sys
|
|
|
|
Silf_hdr_format = """
|
|
>
|
|
version: 16.16F
|
|
"""
|
|
|
|
Silf_hdr_format_3 = """
|
|
>
|
|
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_pseudomap_format_h = """
|
|
>
|
|
unicode: H
|
|
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", "Bb"),
|
|
("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 = byteord(aCode[pc : pc + 1])
|
|
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 = byteord(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(r"^\s*([^(]+)\s*(?:\(([^)]+)\))?")
|
|
|
|
|
|
def assemble(instrs):
|
|
res = b""
|
|
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 += struct.pack("B", opcode)
|
|
if m.group(2):
|
|
if parmfmt == 0:
|
|
continue
|
|
parms = [int(x) for x in re.split(r",\s*", m.group(2))]
|
|
if parmfmt == -1:
|
|
l = len(parms)
|
|
res += struct.pack(("%dB" % (l + 1)), l, *parms)
|
|
else:
|
|
res += struct.pack(parmfmt, *parms)
|
|
return 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 wrapline(writer, dat, length=80):
|
|
currline = ""
|
|
for d in dat:
|
|
if len(currline) > length:
|
|
writer.write(currline[:-1])
|
|
writer.newline()
|
|
currline = ""
|
|
currline += d + " "
|
|
if len(currline):
|
|
writer.write(currline[:-1])
|
|
writer.newline()
|
|
|
|
|
|
class _Object:
|
|
pass
|
|
|
|
|
|
class table_S__i_l_f(DefaultTable.DefaultTable):
|
|
"""Graphite Rules table
|
|
|
|
See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
|
|
"""
|
|
|
|
def __init__(self, tag=None):
|
|
DefaultTable.DefaultTable.__init__(self, tag)
|
|
self.silfs = []
|
|
|
|
def decompile(self, data, ttFont):
|
|
sstruct.unpack2(Silf_hdr_format, data, self)
|
|
self.version = float(floatToFixedToStr(self.version, precisionBits=16))
|
|
if self.version >= 5.0:
|
|
(data, self.scheme) = grUtils.decompress(data)
|
|
sstruct.unpack2(Silf_hdr_format_3, data, self)
|
|
base = sstruct.calcsize(Silf_hdr_format_3)
|
|
elif self.version < 3.0:
|
|
self.numSilf = struct.unpack(">H", data[4:6])
|
|
self.scheme = 0
|
|
self.compilerVersion = 0
|
|
base = 8
|
|
else:
|
|
self.scheme = 0
|
|
sstruct.unpack2(Silf_hdr_format_3, data, self)
|
|
base = sstruct.calcsize(Silf_hdr_format_3)
|
|
|
|
silfoffsets = struct.unpack_from((">%dL" % self.numSilf), data[base:])
|
|
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)
|
|
if self.version < 3.0:
|
|
hdr = sstruct.pack(Silf_hdr_format, self)
|
|
hdr += struct.pack(">HH", self.numSilf, 0)
|
|
else:
|
|
hdr = sstruct.pack(Silf_hdr_format_3, self)
|
|
offset = len(hdr) + 4 * self.numSilf
|
|
data = b""
|
|
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.comment("Attributes starting with _ are informative only")
|
|
writer.newline()
|
|
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)
|
|
self.ruleVersion = float(
|
|
floatToFixedToStr(self.ruleVersion, precisionBits=16)
|
|
)
|
|
_, 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].decode("ascii")
|
|
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):
|
|
if version >= 3.0:
|
|
pseudo = sstruct.unpack(
|
|
Silf_pseudomap_format, data[8 + 6 * i : 14 + 6 * i], _Object()
|
|
)
|
|
else:
|
|
pseudo = sstruct.unpack(
|
|
Silf_pseudomap_format_h, data[8 + 4 * i : 12 + 4 * i], _Object()
|
|
)
|
|
self.pMap[pseudo.unicode] = ttFont.getGlyphName(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 = b""
|
|
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.encode("ascii")) for x in self.scriptTags]
|
|
data += b"".join(tdata)
|
|
data += struct.pack(">H", self.lbGID)
|
|
self.passOffset = len(data)
|
|
|
|
data1 = grUtils.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" if version >= 3.0 else ">HH"), u, ttFont.getGlyphID(p)
|
|
)
|
|
data1 += self.classes.compile(ttFont, version)
|
|
currpos += len(data1)
|
|
data2 = b""
|
|
datao = b""
|
|
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 = b""
|
|
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 len(self.critFeatures):
|
|
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 len(self.passes):
|
|
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 = 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(
|
|
ttFont.getGlyphName(x)
|
|
for x in 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],
|
|
):
|
|
nonLinids = [
|
|
struct.unpack(">HH", data[x : x + 4]) for x in range(s + 8, e, 4)
|
|
]
|
|
nonLin = dict([(ttFont.getGlyphName(x[0]), x[1]) for x in nonLinids])
|
|
self.nonLinear.append(nonLin)
|
|
|
|
def compile(self, ttFont, version=2.0):
|
|
data = b""
|
|
oClasses = []
|
|
if version >= 4.0:
|
|
offset = 8 + 4 * (len(self.linear) + len(self.nonLinear))
|
|
else:
|
|
offset = 6 + 2 * (len(self.linear) + len(self.nonLinear))
|
|
for l in self.linear:
|
|
oClasses.append(len(data) + offset)
|
|
gs = [ttFont.getGlyphID(x) for x in l]
|
|
data += struct.pack((">%dH" % len(l)), *gs)
|
|
for l in self.nonLinear:
|
|
oClasses.append(len(data) + offset)
|
|
gs = [(ttFont.getGlyphID(x[0]), x[1]) for x in l.items()]
|
|
data += grUtils.bininfo(len(gs))
|
|
data += b"".join([struct.pack(">HH", *x) for x in sorted(gs)])
|
|
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" if version >= 4.0 else ">%dH") % 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()
|
|
wrapline(writer, l)
|
|
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 inp, ind in l.items():
|
|
writer.simpletag("map", glyph=inp, index=ind)
|
|
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":
|
|
l = content_string(subcontent).split()
|
|
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 = {}
|
|
for e in subcontent:
|
|
if not isinstance(e, tuple):
|
|
continue
|
|
tag, attrs, subsubcontent = e
|
|
if tag == "map":
|
|
l[attrs["glyph"]] = int(safeEval(attrs["index"]))
|
|
self.nonLinear.append(l)
|
|
|
|
|
|
class Pass(object):
|
|
def __init__(self):
|
|
self.colMap = {}
|
|
self.rules = []
|
|
self.rulePreContexts = []
|
|
self.ruleSortKeys = []
|
|
self.ruleConstraints = []
|
|
self.passConstraints = b""
|
|
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[ttFont.getGlyphName(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 zip(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]
|
|
)
|
|
if sys.byteorder != "big":
|
|
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) - 2, -1, -1):
|
|
if oConstraints[i] == 0:
|
|
oConstraints[i] = oConstraints[i + 1]
|
|
self.ruleConstraints = [
|
|
(data[s:e] if (e - s > 1) else b"")
|
|
for (s, e) in zip(oConstraints, oConstraints[1:])
|
|
]
|
|
data = data[oConstraints[-1] :]
|
|
self.actions = [
|
|
(data[s:e] if (e - s > 1) else "") for (s, e) in zip(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, x: (a[0] + len(x), a[1] + [a[0]]), self.actions + [b""], (0, [])
|
|
)[1]
|
|
oConstraints = reduce(
|
|
lambda a, x: (a[0] + len(x), a[1] + [a[0]]),
|
|
self.ruleConstraints + [b""],
|
|
(1, []),
|
|
)[1]
|
|
constraintCode = b"\000" + b"".join(self.ruleConstraints)
|
|
transes = []
|
|
for t in self.stateTrans:
|
|
if sys.byteorder != "big":
|
|
t.byteswap()
|
|
transes.append(t.tobytes())
|
|
if sys.byteorder != "big":
|
|
t.byteswap()
|
|
if not len(transes):
|
|
self.startStates = [0]
|
|
oRuleMap = reduce(
|
|
lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.rules + [[]], (0, [])
|
|
)[1]
|
|
passRanges = []
|
|
gidcolmap = dict([(ttFont.getGlyphID(x[0]), x[1]) for x in self.colMap.items()])
|
|
for e in grUtils.entries(gidcolmap, 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 += grUtils.bininfo(len(passRanges), 6)
|
|
data += b"".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
|
|
+ b"".join(transes)
|
|
+ struct.pack("B", 0)
|
|
+ self.passConstraints
|
|
+ constraintCode
|
|
+ b"".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()
|
|
wrapline(
|
|
writer,
|
|
[
|
|
"{}={}".format(*x)
|
|
for x in sorted(
|
|
self.colMap.items(), key=lambda x: ttFont.getGlyphID(x[0])
|
|
)
|
|
],
|
|
)
|
|
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(len(self.actions)):
|
|
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[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([int(x) for x in 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 = b""
|
|
act = b""
|
|
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
|