diff options
Diffstat (limited to 'examples/gdl/ot.py')
-rw-r--r-- | examples/gdl/ot.py | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/examples/gdl/ot.py b/examples/gdl/ot.py new file mode 100644 index 0000000..dc2481d --- /dev/null +++ b/examples/gdl/ot.py @@ -0,0 +1,448 @@ +#!/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) + |