1
0
Fork 0
pysilfont/examples/gdl/ot.py
Daniel Baumann e40b3259c1
Adding upstream version 1.8.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-10 05:17:32 +01:00

448 lines
17 KiB
Python

#!/usr/bin/env python
'OpenType analysis for GDL conversion'
__url__ = 'https://github.com/silnrsi/pysilfont'
__copyright__ = 'Copyright (c) 2012 SIL International (https://www.sil.org)'
__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
import re, traceback, logging
from fontTools.ttLib.tables import otTables
def compress_strings(strings) :
'''If we replace one column in the string with different lists, can we reduce the number
of strings? Each string is a tuple of the string and a single value that will be put into
a class as well when list compression occurs'''
maxlen = max(map(lambda x: len(x[0]), strings))
scores = []
for r in range(maxlen) :
allchars = {}
count = 0
for s in strings :
if r >= len(s[0]) : continue
c = tuple(s[0][0:r] + (s[0][r+1:] if r < len(s[0]) - 1 else []))
if c in allchars :
allchars[c] += 1
else :
allchars[c] = 0
count += 1
scores.append((max(allchars.values()), len(allchars), count))
best = maxlen
bestr = 0
for r in range(maxlen) :
score = maxlen - (scores[r][2] - scores[r][1])
if score < best :
best = score
bestr = r
numstrings = len(strings)
i = 0
allchars = {}
while i < len(strings) :
s = strings[i][0]
if bestr >= len(s) :
i += 1
continue
c = tuple(s[0:bestr] + (s[bestr+1:] if bestr < len(s) - 1 else []))
if c in allchars :
allchars[c][1].append(s[bestr])
allchars[c][2].append(strings[i][1])
strings.pop(i)
else :
allchars[c] = [i, [s[bestr]], [strings[i][1]]]
i += 1
for v in allchars.values() :
if len(set(v[1])) != 1 : # if all values in the list identical, don't output list
strings[v[0]][0][bestr] = v[1]
if len(v[2]) > 1 : # don't need a list if length 1
strings[v[0]][1] = v[2]
return strings
def make_rule(left, right = None, before = None, after = None) :
res = " ".join(map(lambda x: x or "_", left))
if right :
res += " > " + " ".join(map(lambda x: x or "_", right))
if before or after :
res += " / "
if before : res += " ".join(map(lambda x: x or 'ANY', before))
res += " " + "_ " * len(left) + " "
if after : res += " ".join(map(lambda x: x or 'ANY', after))
res += ";"
return res
def add_class_classes(font, name, ctable) :
vals = {}
for k, v in ctable.classDefs.items() :
if v not in vals : vals[v] = []
vals[v].append(k)
numk = max(vals.keys())
res = [None] * (numk + 1)
for k, v in vals.items() :
if len(v) > 1 :
res[k] = font.alias(name+"{}".format(k))
font.addClass(res[k], map(font.glyph, v))
else :
res[k] = font.glyph(v[0]).GDLName()
return res
vrgdlmap = {
'XPlacement' : 'shift.x',
'YPlacement' : 'shift.y',
'XAdvance' : 'advance'
}
def valuerectogdl(vr) :
res = "{"
for k, v in vrgdlmap.items() :
if hasattr(vr, k) :
res += "{}={}; ".format(v, getattr(vr, k))
res = res[:-1] + "}"
if len(res) == 1 : return ""
return res
def _add_method(*clazzes):
"""Returns a decorator function that adds a new method to one or
more classes."""
def wrapper(method):
for c in clazzes:
assert c.__name__ != 'DefaultTable', \
'Oops, table class not found.'
assert not hasattr(c, method.__name__), \
"Oops, class '%s' has method '%s'." % (c.__name__,
method.__name__)
setattr(c, method.__name__, method)
return None
return wrapper
@_add_method(otTables.Lookup)
def process(self, font, index) :
for i, s in enumerate(self.SubTable) :
if hasattr(s, 'process') :
s.process(font, index + "_{}".format(i))
else :
logging.warning("No processing of {} {}_{}".format(str(s), index, i))
@_add_method(otTables.LookupList)
def process(self, font) :
for i, s in enumerate(self.Lookup) :
s.process(font, str(i))
@_add_method(otTables.ExtensionSubst, otTables.ExtensionPos)
def process(self, font, index) :
x = self.ExtSubTable
if hasattr(x, 'process') :
x.process(font, index)
else :
logging.warning("No processing of {} {}".format(str(x), index))
@_add_method(otTables.SingleSubst)
def process(self, font, index) :
cname = "cot_s{}".format(index)
if not len(font.alias(cname)) : return
lists = zip(*self.mapping.items())
font.addClass(font.alias(cname+"l"), map(font.glyph, lists[0]))
font.addClass(font.alias(cname+"r"), map(font.glyph, lists[1]))
@_add_method(otTables.MultipleSubst)
def process(self, font, index) :
cname = "cot_m{}".format(index)
if not len(font.alias(cname)) : return
nums = len(self.Coverage.glyphs)
strings = []
for i in range(nums) :
strings.append([self.Sequence[i].Substitute, self.Coverage.glyphs[i]])
res = compress_strings(strings)
count = 0
rules = []
for r in res :
if hasattr(r[1], '__iter__') :
lname = font.alias(cname+"l{}".format(count))
font.addClass(lname, map(font.glyph, r[1]))
rule = lname
else :
rule = font.glyph(r[1]).GDLName()
rule += " _" * (len(r[0]) - 1) + " >"
for c in r[0] :
if hasattr(c, '__iter__') :
rname = font.alias(cname+"r{}".format(count))
font.addClass(rname, map(font.glyph, c))
rule += " " + rname
count += 1
else :
rule += " " + font.glyph(c).GDLName()
rule += ';'
rules.append(rule)
font.addRules(rules, index)
@_add_method(otTables.LigatureSubst)
def process(self, font, index) :
cname = "cot_l{}".format(index)
if not len(font.alias(cname)) : return
strings = []
for lg, ls in self.ligatures.items() :
for l in ls :
strings.append([[lg] + l.Component, l.LigGlyph])
res = compress_strings(strings)
count = 0
rules = []
for r in res :
rule = ""
besti = 0
for i, c in enumerate(r[0]) :
if hasattr(c, '__iter__') :
lname = font.alias(cname+"l{}".format(count))
font.addClass(lname, map(font.glyph, c))
rule += lname + " "
besti = i
else :
rule += font.glyph(c).GDLName() + " "
rule += "> " + "_ " * besti
if hasattr(r[1], '__iter__') :
rname = font.alias(cname+"r{}".format(count))
font.addClass(rname, map(font.glyph, r[1]))
rule += rname
count += 1
else :
rule += font.glyph(r[1]).GDLName()
rule += " _" * (len(r[0]) - 1 - besti) + ";"
rules.append(rule)
font.addRules(rules, index)
@_add_method(otTables.ChainContextSubst)
def process(self, font, index) :
def procsubst(rule, action) :
for s in rule.SubstLookupRecord :
action[s.SequenceIndex] += "/*{}*/".format(s.LookupListIndex)
def procCover(cs, name) :
res = []
for i, c in enumerate(cs) :
if len(c.glyphs) > 1 :
n = font.alias(name+"{}".format(i))
font.addClass(n, map(font.glyph, c.glyphs))
res.append(n)
else :
res.append(font.glyph(c.glyphs[0]).GDLName())
return res
cname = "cot_c{}".format(index)
if not len(font.alias(cname)) : return
rules = []
if self.Format == 1 :
for i in range(len(self.ChainSubRuleSet)) :
for r in self.ChainSubRuleSet[i].ChainSubRule :
action = [self.Coverage.glyphs[i]] + r.Input
procsubst(r, action)
rules.append(make_rule(action, None, r.Backtrack, r.LookAhead))
elif self.Format == 2 :
ilist = add_class_classes(font, cname+"i", self.InputClassDef)
if self.BacktrackClassDef :
blist = add_class_classes(font, cname+"b", self.BacktrackClassDef)
if self.LookAheadClassDef :
alist = add_class_classes(font, cname+"a", self.LookAheadClassDef)
for i, s in enumerate(self.ChainSubClassSet) :
if s is None : continue
for r in s.ChainSubClassRule :
action = map(lambda x:ilist[x], [i]+r.Input)
procsubst(r, action)
rules.append(make_rule(action, None,
map(lambda x:blist[x], r.Backtrack or []),
map(lambda x:alist[x], r.LookAhead or [])))
elif self.Format == 3 :
backs = procCover(self.BacktrackCoverage, cname+"b")
aheads = procCover(self.LookAheadCoverage, cname+"a")
actions = procCover(self.InputCoverage, cname+"i")
procsubst(self, actions)
rules.append(make_rule(actions, None, backs, aheads))
font.addRules(rules, index)
@_add_method(otTables.SinglePos)
def process(self, font, index) :
cname = "cot_p{}".format(index)
if self.Format == 1 :
font.addClass(font.alias(cname), map(font.glyph, self.Coverage.glyphs))
rule = cname + " " + valuerectogdl(self.Value)
font.addPosRules([rule], index)
elif self.Format == 2 :
rules = []
for i, g in enumerage(map(font.glyph, self.Coverage.glyphs)) :
rule = font.glyph(g).GDLName()
rule += " " + valuerectogdl(self.Value[i])
rules.append(rule)
font.addPosRules(rules, index)
@_add_method(otTables.PairPos)
def process(self, font, index) :
pass
@_add_method(otTables.CursivePos)
def process(self, font, index) :
apname = "P{}".format(index)
if not len(font.alias(apname)) : return
if self.Format == 1 :
mark_names = self.Coverage.glyphs
for i, g in enumerate(map(font.glyph, mark_names)) :
rec = self.EntryExitRecord[i]
if rec.EntryAnchor is not None :
g.setAnchor(font.alias(apname+"_{}M".format(rec.EntryAnchor)),
rec.EntryAnchor.XCoordinate, rec.EntryAnchor.YCoordinate)
if rec.ExitAnchor is not None :
g.setAnchor(font.alias(apname+"_{}S".format(rec.ExitAnchor)),
rec.ExitAnchor.XCoordinate, rec.ExitAnchor.YCoordinate)
@_add_method(otTables.MarkBasePos)
def process(self, font, index) :
apname = "P{}".format(index)
if not len(font.alias(apname)) : return
if self.Format == 1 :
mark_names = self.MarkCoverage.glyphs
for i, g in enumerate(map(font.glyph, mark_names)) :
rec = self.MarkArray.MarkRecord[i]
g.setAnchor(font.alias(apname+"_{}M".format(rec.Class)),
rec.MarkAnchor.XCoordinate, rec.MarkAnchor.YCoordinate)
base_names = self.BaseCoverage.glyphs
for i, g in enumerate(map(font.glyph, base_names)) :
for j,b in enumerate(self.BaseArray.BaseRecord[i].BaseAnchor) :
if b : g.setAnchor(font.alias(apname+"_{}S".format(j)),
b.XCoordinate, b.YCoordinate)
@_add_method(otTables.MarkMarkPos)
def process(self, font, index) :
apname = "P{}".format(index)
if not len(font.alias(apname)) : return
if self.Format == 1 :
mark_names = self.Mark1Coverage.glyphs
for i, g in enumerate(map(font.glyph, mark_names)) :
rec = self.Mark1Array.MarkRecord[i]
g.setAnchor(font.alias(apname+"_{}M".format(rec.Class)),
rec.MarkAnchor.XCoordinate, rec.MarkAnchor.YCoordinate)
base_names = self.Mark2Coverage.glyphs
for i, g in enumerate(map(font.glyph, base_names)) :
for j,b in enumerate(self.Mark2Array.Mark2Record[i].Mark2Anchor) :
if b : g.setAnchor(font.alias(apname+"_{}S".format(j)),
b.XCoordinate, b.YCoordinate)
@_add_method(otTables.ContextSubst)
def process(self, font, index) :
def procsubst(rule, action) :
for s in rule.SubstLookupRecord :
action[s.SequenceIndex] += "/*{}*/".format(s.LookupListIndex)
def procCover(cs, name) :
res = []
for i, c in enumerate(cs) :
if len(c.glyphs) > 1 :
n = font.alias(name+"{}".format(i))
font.addClass(n, map(font.glyph, c.glyphs))
res.append(n)
else :
res.append(font.glyph(c.glyphs[0]).GDLName())
return res
cname = "cot_cs{}".format(index)
if not len(font.alias(cname)) : return
rules = []
if self.Format == 1 :
for i in range(len(self.SubRuleSet)) :
for r in self.SubRuleSet[i].SubRule :
action = [self.Coverage.glyphs[i]] + r.Input
procsubst(r, action)
rules.append(make_rule(action, None, None, None))
elif self.Format == 2 :
ilist = add_class_classes(font, cname+"i", self.ClassDef)
for i, s in enumerate(self.SubClassSet) :
if s is None : continue
for r in s.SubClassRule :
action = map(lambda x:ilist[x], [i]+r.Class)
procsubst(r, action)
rules.append(make_rule(action, None, None, None))
elif self.Format == 3 :
actions = procCover(self.Coverage, cname+"i")
procsubst(self, actions)
rules.append(make_rule(actions, None, None, None))
font.addRules(rules, index)
@_add_method(otTables.ContextPos)
def process(self, font, index) :
def procsubst(rule, action) :
for s in rule.PosLookupRecord :
action[s.SequenceIndex] += "/*{}*/".format(s.LookupListIndex)
def procCover(cs, name) :
res = []
for i, c in enumerate(cs) :
if len(c.glyphs) > 1 :
n = font.alias(name+"{}".format(i))
font.addClass(n, map(font.glyph, c.glyphs))
res.append(n)
else :
res.append(font.glyph(c.glyphs[0]).GDLName())
return res
cname = "cot_cp{}".format(index)
if not len(font.alias(cname)) : return
rules = []
if self.Format == 1 :
for i in range(len(self.PosRuleSet)) :
for r in self.PosRuleSet[i] :
action = [self.Coverage.glyphs[i]] + r.Input
procsubst(r, action)
rules.append(make_rule(action, None, None, None))
elif self.Format == 2 :
ilist = add_class_classes(font, cname+"i", self.ClassDef)
for i, s in enumerate(self.PosClassSet) :
if s is None : continue
for r in s.PosClassRule :
action = map(lambda x:ilist[x], [i]+r.Class)
procsubst(r, action)
rules.append(make_rule(action, None, None, None))
elif self.Format == 3 :
actions = procCover(self.Coverage, cname+"i")
procsubst(self, actions)
rules.append(make_rule(actions, None, None, None))
font.addPosRules(rules, index)
@_add_method(otTables.ChainContextPos)
def process(self, font, index) :
def procsubst(rule, action) :
for s in rule.PosLookupRecord :
action[s.SequenceIndex] += "/*{}*/".format(s.LookupListIndex)
def procCover(cs, name) :
res = []
for i, c in enumerate(cs) :
if len(c.glyphs) > 1 :
n = font.alias(name+"{}".format(i))
font.addClass(n, map(font.glyph, c.glyphs))
res.append(n)
else :
res.append(font.glyph(c.glyphs[0]).GDLName())
return res
cname = "cot_c{}".format(index)
if not len(font.alias(cname)) : return
rules = []
if self.Format == 1 :
for i in range(len(self.ChainPosRuleSet)) :
for r in self.ChainPosRuleSet[i].ChainPosRule :
action = [self.Coverage.glyphs[i]] + r.Input
procsubst(r, action)
rules.append(make_rule(action, None, r.Backtrack, r.LookAhead))
elif self.Format == 2 :
ilist = add_class_classes(font, cname+"i", self.InputClassDef)
if self.BacktrackClassDef :
blist = add_class_classes(font, cname+"b", self.BacktrackClassDef)
if self.LookAheadClassDef :
alist = add_class_classes(font, cname+"a", self.LookAheadClassDef)
for i, s in enumerate(self.ChainPosClassSet) :
if s is None : continue
for r in s.ChainPosClassRule :
action = map(lambda x:ilist[x], [i]+r.Input)
procsubst(r, action)
rules.append(make_rule(action, None,
map(lambda x:blist[x], r.Backtrack or []),
map(lambda x:alist[x], r.LookAhead or [])))
elif self.Format == 3 :
backs = procCover(self.BacktrackCoverage, cname+"b")
aheads = procCover(self.LookAheadCoverage, cname+"a")
actions = procCover(self.InputCoverage, cname+"i")
procsubst(self, actions)
rules.append(make_rule(actions, None, backs, aheads))
font.addPosRules(rules, index)