summaryrefslogtreecommitdiffstats
path: root/examples/gdl/ot.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/gdl/ot.py')
-rw-r--r--examples/gdl/ot.py448
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)
+