Adding upstream version 1.8.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
c48d95b7fa
commit
e40b3259c1
2403 changed files with 153656 additions and 0 deletions
20
examples/gdl/__init__.py
Normal file
20
examples/gdl/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copyright 2012, SIL International
|
||||
# All rights reserved.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation; either version 2.1 of License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should also have received a copy of the GNU Lesser General Public
|
||||
# License along with this library in the file named "LICENSE".
|
||||
# If not, write to the Free Software Foundation, 51 Franklin Street,
|
||||
# suite 500, Boston, MA 02110-1335, USA or visit their web page on the
|
||||
# internet at https://www.fsf.org/licenses/lgpl.html.
|
||||
|
||||
__all__ = ['makegdl', 'psnames']
|
394
examples/gdl/font.py
Normal file
394
examples/gdl/font.py
Normal file
|
@ -0,0 +1,394 @@
|
|||
#!/usr/bin/env python
|
||||
'The main font object for GDL creation. Depends on fonttools'
|
||||
__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 os, re, traceback
|
||||
from silfont.gdl.glyph import Glyph
|
||||
from silfont.gdl.psnames import Name
|
||||
from xml.etree.cElementTree import ElementTree, parse, Element
|
||||
from fontTools.ttLib import TTFont
|
||||
|
||||
# A collection of glyphs that have a given attachment point defined
|
||||
class PointClass(object) :
|
||||
|
||||
def __init__(self, name) :
|
||||
self.name = name
|
||||
self.glyphs = []
|
||||
self.dias = []
|
||||
|
||||
def addBaseGlyph(self, g) :
|
||||
self.glyphs.append(g)
|
||||
|
||||
def addDiaGlyph(self, g) :
|
||||
self.dias.append(g)
|
||||
g.isDia = True
|
||||
|
||||
def hasDias(self) :
|
||||
if len(self.dias) and len(self.glyphs) :
|
||||
return True
|
||||
else :
|
||||
return False
|
||||
|
||||
def classGlyphs(self, isDia = False) :
|
||||
if isDia :
|
||||
return self.dias
|
||||
else :
|
||||
return self.glyphs
|
||||
|
||||
def isNotInClass(self, g, isDia = False) :
|
||||
if not g : return False
|
||||
if not g.isDia : return False
|
||||
|
||||
if isDia :
|
||||
return g not in self.dias
|
||||
else :
|
||||
return g not in self.dias and g not in self.glyphs
|
||||
|
||||
|
||||
class FontClass(object) :
|
||||
|
||||
def __init__(self, elements = None, fname = None, lineno = None, generated = False, editable = False) :
|
||||
self.elements = elements or []
|
||||
self.fname = fname
|
||||
self.lineno = lineno
|
||||
self.generated = generated
|
||||
self.editable = editable
|
||||
|
||||
def append(self, element) :
|
||||
self.elements.append(element)
|
||||
|
||||
|
||||
class Font(object) :
|
||||
|
||||
def __init__(self, fontfile) :
|
||||
self.glyphs = []
|
||||
self.psnames = {}
|
||||
self.canons = {}
|
||||
self.gdls = {}
|
||||
self.anchors = {}
|
||||
self.ligs = {}
|
||||
self.subclasses = {}
|
||||
self.points = {}
|
||||
self.classes = {}
|
||||
self.aliases = {}
|
||||
self.rules = {}
|
||||
self.posRules = {}
|
||||
if fontfile :
|
||||
self.font = TTFont(fontfile)
|
||||
for i, n in enumerate(self.font.getGlyphOrder()) :
|
||||
self.addGlyph(i, n)
|
||||
else :
|
||||
self.font = None
|
||||
|
||||
def __len__(self) :
|
||||
return len(self.glyphs)
|
||||
|
||||
# [] syntax returns the indicated element of the glyphs array.
|
||||
def __getitem__(self, y) :
|
||||
try :
|
||||
return self.glyphs[y]
|
||||
except IndexError :
|
||||
return None
|
||||
|
||||
def glyph(self, name) :
|
||||
return self.psnames.get(name, None)
|
||||
|
||||
def alias(self, s) :
|
||||
return self.aliases.get(s, s)
|
||||
|
||||
def emunits(self) :
|
||||
return 0
|
||||
|
||||
def initGlyphs(self, nGlyphs) :
|
||||
#print "Font::initGlyphs",nGlyphs
|
||||
self.glyphs = [None] * nGlyphs
|
||||
self.numRealGlyphs = nGlyphs # does not include pseudo-glyphs
|
||||
self.psnames = {}
|
||||
self.canons = {}
|
||||
self.gdls = {}
|
||||
self.classes = {}
|
||||
|
||||
def addGlyph(self, index = None, psName = None, gdlName = None, factory = Glyph) :
|
||||
#print "Font::addGlyph",index,psName,gdlName
|
||||
if psName in self.psnames :
|
||||
return self.psnames[psName]
|
||||
if index is not None and index < len(self.glyphs) and self.glyphs[index] :
|
||||
g = self.glyphs[index]
|
||||
return g
|
||||
g = factory(psName, index) # create a new glyph of the given class
|
||||
self.renameGlyph(g, psName, gdlName)
|
||||
if index is None : # give it the next available index
|
||||
index = len(self.glyphs)
|
||||
self.glyphs.append(g)
|
||||
elif index >= len(self.glyphs) :
|
||||
self.glyphs.extend([None] * (len(self.glyphs) - index + 1))
|
||||
self.glyphs[index] = g
|
||||
return g
|
||||
|
||||
def renameGlyph(self, g, name, gdlName = None) :
|
||||
if g.psname != name :
|
||||
for n in g.parseNames() :
|
||||
del self.psnames[n.psname]
|
||||
del self.canons[n.canonical()]
|
||||
if gdlName :
|
||||
self.setGDL(g, gdlName)
|
||||
else :
|
||||
self.setGDL(g, g.GDLName())
|
||||
for n in g.parseNames() :
|
||||
if n is None : break
|
||||
self.psnames[n.psname] = g
|
||||
self.canons[n.canonical()] = (n, g)
|
||||
|
||||
def setGDL(self, glyph, name) :
|
||||
if not glyph : return
|
||||
n = glyph.GDLName()
|
||||
if n != name and n in self.gdls : del self.gdls[n]
|
||||
if name and name in self.gdls and self.gdls[name] is not glyph :
|
||||
count = 1
|
||||
index = -2
|
||||
name = name + "_1"
|
||||
while name in self.gdls :
|
||||
if self.gdls[name] is glyph : break
|
||||
count = count + 1
|
||||
name = name[0:index] + "_" + str(count)
|
||||
if count == 10 : index = -3
|
||||
if count == 100 : index = -4
|
||||
self.gdls[name] = glyph
|
||||
glyph.setGDL(name)
|
||||
|
||||
def addClass(self, name, elements, fname = None, lineno = 0, generated = False, editable = False) :
|
||||
if name :
|
||||
self.classes[name] = FontClass(elements, fname, lineno, generated, editable)
|
||||
|
||||
def addGlyphClass(self, name, gid, editable = False) :
|
||||
if name not in self.classes :
|
||||
self.classes[name] = FontClass()
|
||||
if gid not in self.classes[name].elements :
|
||||
self.classes[name].append(gid)
|
||||
|
||||
def addRules(self, rules, index) :
|
||||
self.rules[index] = rules
|
||||
|
||||
def addPosRules(self, rules, index) :
|
||||
self.posRules[index] = rules
|
||||
|
||||
def classUpdated(self, name, value) :
|
||||
c = []
|
||||
if name in self.classes :
|
||||
for gid in self.classes[name].elements :
|
||||
g = self[gid]
|
||||
if g : g.removeClass(name)
|
||||
if value is None and name in classes :
|
||||
del self.classes[name]
|
||||
return
|
||||
for n in value.split() :
|
||||
g = self.gdls.get(n, None)
|
||||
if g :
|
||||
c.append(g.gid)
|
||||
g.addClass(name)
|
||||
if name in self.classes :
|
||||
self.classes[name].elements = c
|
||||
else :
|
||||
self.classes[name] = FontClass(c)
|
||||
|
||||
# Return the list of classes that should be updated in the AP XML file.
|
||||
# This does not include classes that are auto-generated or defined in the hand-crafted GDL code.
|
||||
def filterAutoClasses(self, names, autoGdlFile) :
|
||||
res = []
|
||||
for n in names :
|
||||
c = self.classes[n]
|
||||
if not c.generated and (not c.fname or c.fname == autoGdlFile) : res.append(n)
|
||||
return res
|
||||
|
||||
def loadAlias(self, fname) :
|
||||
with open(fname) as f :
|
||||
for l in f.readlines() :
|
||||
l = l.strip()
|
||||
l = re.sub(ur'#.*$', '', l).strip()
|
||||
if not len(l) : continue
|
||||
try :
|
||||
k, v = re.split(ur'\s*[,;\s]\s*', l, 1)
|
||||
except ValueError :
|
||||
k = l
|
||||
v = ''
|
||||
self.aliases[k] = v
|
||||
|
||||
# TODO: move this method to GraideFont, or refactor
|
||||
def loadAP(self, apFileName) :
|
||||
if not os.path.exists(apFileName) : return False
|
||||
etree = parse(apFileName)
|
||||
self.initGlyphs(len(etree.getroot())) # guess each child is a glyph
|
||||
i = 0
|
||||
for e in etree.getroot().iterfind("glyph") :
|
||||
g = self.addGlyph(i, e.get('PSName'))
|
||||
g.readAP(e, self)
|
||||
i += 1
|
||||
return True
|
||||
|
||||
def saveAP(self, apFileName, autoGdlFile) :
|
||||
root = Element('font')
|
||||
root.set('upem', str(self.emunits()))
|
||||
root.set('producer', 'graide 1.0')
|
||||
root.text = "\n\n"
|
||||
for g in self.glyphs :
|
||||
if g : g.createAP(root, self, autoGdlFile)
|
||||
ElementTree(root).write(apFileName, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
def createClasses(self) :
|
||||
self.subclasses = {}
|
||||
for k, v in self.canons.items() :
|
||||
if v[0].ext :
|
||||
h = v[0].head()
|
||||
o = self.canons.get(h.canonical(), None)
|
||||
if o :
|
||||
if v[0].ext not in self.subclasses : self.subclasses[v[0].ext] = {}
|
||||
self.subclasses[v[0].ext][o[1].GDLName()] = v[1].GDLName()
|
||||
# for g in self.glyphs :
|
||||
# if not g : continue
|
||||
# for c in g.classes :
|
||||
# if c not in self.classes :
|
||||
# self.classes[c] = []
|
||||
# self.classes[c].append(g.gid)
|
||||
|
||||
def calculatePointClasses(self) :
|
||||
self.points = {}
|
||||
for g in self.glyphs :
|
||||
if not g : continue
|
||||
for apName in g.anchors.keys() :
|
||||
genericName = apName[:-1] # without the M or S
|
||||
if genericName not in self.points :
|
||||
self.points[genericName] = PointClass(genericName)
|
||||
if apName.endswith('S') :
|
||||
self.points[genericName].addBaseGlyph(g)
|
||||
else :
|
||||
self.points[genericName].addDiaGlyph(g)
|
||||
|
||||
def calculateOTLookups(self) :
|
||||
if self.font :
|
||||
for t in ('GSUB', 'GPOS') :
|
||||
if t in self.font :
|
||||
self.font[t].table.LookupList.process(self)
|
||||
|
||||
def getPointClasses(self) :
|
||||
if len(self.points) == 0 :
|
||||
self.calculatePointClasses()
|
||||
return self.points
|
||||
|
||||
def ligClasses(self) :
|
||||
self.ligs = {}
|
||||
for g in self.glyphs :
|
||||
if not g or not g.name : continue
|
||||
(h, t) = g.name.split_last()
|
||||
if t :
|
||||
o = self.canons.get(h.canonical(), None)
|
||||
if o and o[0].ext == t.ext :
|
||||
t.ext = None
|
||||
t.cname = None
|
||||
tn = t.canonical(noprefix = True)
|
||||
if tn in self.ligs :
|
||||
self.ligs[tn].append((g.GDLName(), o[0].GDL()))
|
||||
else :
|
||||
self.ligs[tn] = [(g.GDLName(), o[0].GDL())]
|
||||
|
||||
def outGDL(self, fh, args) :
|
||||
munits = self.emunits()
|
||||
fh.write('table(glyph) {MUnits = ' + str(munits) + '};\n')
|
||||
nglyphs = 0
|
||||
for g in self.glyphs :
|
||||
if not g or not g.psname : continue
|
||||
if g.psname == '.notdef' :
|
||||
fh.write(g.GDLName() + ' = glyphid(0)')
|
||||
else :
|
||||
fh.write(g.GDLName() + ' = postscript("' + g.psname + '")')
|
||||
outs = []
|
||||
if len(g.anchors) :
|
||||
for a in g.anchors.keys() :
|
||||
v = g.anchors[a]
|
||||
outs.append(a + "=point(" + str(int(v[0])) + "m, " + str(int(v[1])) + "m)")
|
||||
for (p, v) in g.gdl_properties.items() :
|
||||
outs.append("%s=%s" % (p, v))
|
||||
if len(outs) : fh.write(" {" + "; ".join(outs) + "}")
|
||||
fh.write(";\n")
|
||||
nglyphs += 1
|
||||
fh.write("\n")
|
||||
fh.write("\n/* Point Classes */\n")
|
||||
for p in sorted(self.points.values(), key=lambda x: x.name) :
|
||||
if not p.hasDias() : continue
|
||||
n = p.name + "Dia"
|
||||
self.outclass(fh, "c" + n, p.classGlyphs(True))
|
||||
self.outclass(fh, "cTakes" + n, p.classGlyphs(False))
|
||||
self.outclass(fh, 'cn' + n, filter(lambda x : p.isNotInClass(x, True), self.glyphs))
|
||||
self.outclass(fh, 'cnTakes' + n, filter(lambda x : p.isNotInClass(x, False), self.glyphs))
|
||||
fh.write("\n/* Classes */\n")
|
||||
for c in sorted(self.classes.keys()) : # c = class name, l = class object
|
||||
if c not in self.subclasses and not self.classes[c].generated : # don't output the class to the AP file if it was autogenerated
|
||||
self.outclass(fh, c, self.classes[c].elements)
|
||||
for p in self.subclasses.keys() :
|
||||
ins = []
|
||||
outs = []
|
||||
for k, v in self.subclasses[p].items() :
|
||||
ins.append(k)
|
||||
outs.append(v)
|
||||
n = p.replace('.', '_')
|
||||
self.outclass(fh, 'cno_' + n, ins)
|
||||
self.outclass(fh, 'c' + n, outs)
|
||||
fh.write("/* Ligature Classes */\n")
|
||||
for k in sorted(self.ligs.keys()) :
|
||||
self.outclass(fh, "clig" + k, map(lambda x: self.gdls[x[0]], self.ligs[k]))
|
||||
self.outclass(fh, "cligno_" + k, map(lambda x: self.gdls[x[1]], self.ligs[k]))
|
||||
fh.write("\nendtable;\n")
|
||||
fh.write("/* Substitution Rules */\n")
|
||||
for k, v in sorted(self.rules.items(), key=lambda x:map(int,x[0].split('_'))) :
|
||||
fh.write('\n// lookup ' + k + '\n')
|
||||
fh.write('// ' + "\n// ".join(v) + "\n")
|
||||
fh.write("\n/* Positioning Rules */\n")
|
||||
for k, v in sorted(self.posRules.items(), key=lambda x:map(int,x[0].split('_'))) :
|
||||
fh.write('\n// lookup ' + k + '\n')
|
||||
fh.write('// ' + "\n// ".join(v) + "\n")
|
||||
fh.write("\n\n#define MAXGLYPH %d\n\n" % (nglyphs - 1))
|
||||
if args.include :
|
||||
fh.write("#include \"%s\"\n" % args.include)
|
||||
|
||||
def outPosRules(self, fh, num) :
|
||||
fh.write("""
|
||||
#ifndef opt2
|
||||
#define opt(x) [x]?
|
||||
#define opt2(x) [opt(x) x]?
|
||||
#define opt3(x) [opt2(x) x]?
|
||||
#define opt4(x) [opt3(x) x]?
|
||||
#endif
|
||||
#define posrule(x) c##x##Dia {attach{to=@1; at=x##S; with=x##M}} / cTakes##x##Dia opt4(cnTakes##x##Dia) _;
|
||||
|
||||
table(positioning);
|
||||
pass(%d);
|
||||
""" % num)
|
||||
for p in self.points.values() :
|
||||
if p.hasDias() :
|
||||
fh.write("posrule(%s);\n" % p.name)
|
||||
fh.write("endpass;\nendtable;\n")
|
||||
|
||||
|
||||
def outclass(self, fh, name, glyphs) :
|
||||
fh.write(name + " = (")
|
||||
count = 1
|
||||
sep = ""
|
||||
for g in glyphs :
|
||||
if not g : continue
|
||||
|
||||
|
||||
if isinstance(g, basestring) :
|
||||
fh.write(sep + g)
|
||||
else :
|
||||
if g.GDLName() is None :
|
||||
print "Can't output " + str(g.gid) + " to class " + name
|
||||
else :
|
||||
fh.write(sep + g.GDLName())
|
||||
if count % 8 == 0 :
|
||||
sep = ',\n '
|
||||
else :
|
||||
sep = ', '
|
||||
count += 1
|
||||
fh.write(');\n\n')
|
||||
|
174
examples/gdl/glyph.py
Normal file
174
examples/gdl/glyph.py
Normal file
|
@ -0,0 +1,174 @@
|
|||
#!/usr/bin/env python
|
||||
'Corresponds to a glyph, for analysis purposes, for GDL generation'
|
||||
__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
|
||||
from silfont.gdl.psnames import Name
|
||||
from xml.etree.cElementTree import SubElement
|
||||
|
||||
# Convert from Graphite AP name to the standard name, eg upperM -> _upper
|
||||
def gr_ap(txt) :
|
||||
if txt.endswith('M') :
|
||||
return "_" + txt[:-1]
|
||||
elif txt.endswith('S') :
|
||||
return txt[:-1]
|
||||
else :
|
||||
return txt
|
||||
|
||||
# Convert from standard AP name to the Graphite name, eg _upper -> upperM
|
||||
def ap_gr(txt) :
|
||||
if txt.startswith('_') :
|
||||
return txt[1:] + 'M'
|
||||
else :
|
||||
return txt + 'S'
|
||||
|
||||
|
||||
class Glyph(object) :
|
||||
|
||||
isDia = False
|
||||
|
||||
def __init__(self, name, gid = 0) :
|
||||
self.clear()
|
||||
self.setName(name)
|
||||
self.gdl = None
|
||||
self.gid = gid
|
||||
self.uid = "" # this is a string!
|
||||
self.comment = ""
|
||||
self.isDia = False
|
||||
|
||||
def clear(self) :
|
||||
self.anchors = {}
|
||||
self.classes = set()
|
||||
self.gdl_properties = {}
|
||||
self.properties = {}
|
||||
|
||||
def setName(self, name) :
|
||||
self.psname = name
|
||||
self.name = next(self.parseNames())
|
||||
|
||||
def setAnchor(self, name, x, y, t = None) :
|
||||
send = True
|
||||
if name in self.anchors :
|
||||
if x is None and y is None :
|
||||
del self.anchors[name]
|
||||
return True
|
||||
if x is None : x = self.anchors[name][0]
|
||||
if y is None : y = self.anchors[name][1]
|
||||
send = self.anchors[name] != (x, y)
|
||||
self.anchors[name] = (x, y)
|
||||
return send
|
||||
# if not name.startswith("_") and t != 'basemark' :
|
||||
# self.isBase = True
|
||||
|
||||
def parseNames(self) :
|
||||
if self.psname :
|
||||
for name in self.psname.split("/") :
|
||||
res = Name(name)
|
||||
yield res
|
||||
else :
|
||||
yield None
|
||||
|
||||
def GDLName(self) :
|
||||
if self.gdl :
|
||||
return self.gdl
|
||||
elif self.name :
|
||||
return self.name.GDL()
|
||||
else :
|
||||
return None
|
||||
|
||||
def setGDL(self, name) :
|
||||
self.gdl = name
|
||||
|
||||
def readAP(self, elem, font) :
|
||||
self.uid = elem.get('UID', None)
|
||||
for p in elem.iterfind('property') :
|
||||
n = p.get('name')
|
||||
if n == 'GDLName' :
|
||||
self.setGDL(p.get('value'))
|
||||
elif n.startswith('GDL_') :
|
||||
self.gdl_properties[n[4:]] = p.get('value')
|
||||
else :
|
||||
self.properties[n] = p.get('value')
|
||||
for p in elem.iterfind('point') :
|
||||
l = p.find('location')
|
||||
self.setAnchor(ap_gr(p.get('type')), int(l.get('x', 0)), int(l.get('y', 0)))
|
||||
p = elem.find('note')
|
||||
if p is not None and p.text :
|
||||
self.comment = p.text
|
||||
if 'classes' in self.properties :
|
||||
for c in self.properties['classes'].split() :
|
||||
if c not in self.classes :
|
||||
self.classes.add(c)
|
||||
font.addGlyphClass(c, self, editable = True)
|
||||
|
||||
def createAP(self, elem, font, autoGdlFile) :
|
||||
e = SubElement(elem, 'glyph')
|
||||
if self.psname : e.set('PSName', self.psname)
|
||||
if self.uid : e.set('UID', self.uid)
|
||||
if self.gid is not None : e.set('GID', str(self.gid))
|
||||
ce = None
|
||||
if 'classes' in self.properties and self.properties['classes'].strip() :
|
||||
tempClasses = self.properties['classes']
|
||||
self.properties['classes'] = " ".join(font.filterAutoClasses(self.properties['classes'].split(), autoGdlFile))
|
||||
|
||||
for k in sorted(self.anchors.keys()) :
|
||||
v = self.anchors[k]
|
||||
p = SubElement(e, 'point')
|
||||
p.set('type', gr_ap(k))
|
||||
p.text = "\n "
|
||||
l = SubElement(p, 'location')
|
||||
l.set('x', str(v[0]))
|
||||
l.set('y', str(v[1]))
|
||||
l.tail = "\n "
|
||||
if ce is not None : ce.tail = "\n "
|
||||
ce = p
|
||||
|
||||
for k in sorted(self.gdl_properties.keys()) :
|
||||
if k == "*skipPasses*" : continue # not set in GDL
|
||||
|
||||
v = self.gdl_properties[k]
|
||||
if v :
|
||||
p = SubElement(e, 'property')
|
||||
p.set('name', 'GDL_' + k)
|
||||
p.set('value', v)
|
||||
if ce is not None : ce.tail = "\n "
|
||||
ce = p
|
||||
|
||||
if self.gdl and (not self.name or self.gdl != self.name.GDL()) :
|
||||
p = SubElement(e, 'property')
|
||||
p.set('name', 'GDLName')
|
||||
p.set('value', self.GDLName())
|
||||
if ce is not None : ce.tail = "\n "
|
||||
ce = p
|
||||
|
||||
for k in sorted(self.properties.keys()) :
|
||||
v = self.properties[k]
|
||||
if v :
|
||||
p = SubElement(e, 'property')
|
||||
p.set('name', k)
|
||||
p.set('value', v)
|
||||
if ce is not None : ce.tail = "\n "
|
||||
ce = p
|
||||
|
||||
if self.comment :
|
||||
p = SubElement(e, 'note')
|
||||
p.text = self.comment
|
||||
if ce is not None : ce.tail = "\n "
|
||||
ce = p
|
||||
|
||||
if 'classes' in self.properties and self.properties['classes'].strip() :
|
||||
self.properties['classes'] = tempClasses
|
||||
if ce is not None :
|
||||
ce.tail = "\n"
|
||||
e.text = "\n "
|
||||
e.tail = "\n"
|
||||
return e
|
||||
|
||||
def isMakeGDLSpecialClass(name) :
|
||||
# if re.match(r'^cn?(Takes)?.*?Dia$', name) : return True
|
||||
# if name.startswith('clig') : return True
|
||||
# if name.startswith('cno_') : return True
|
||||
if re.match(r'^\*GC\d+\*$', name) : return True # auto-pseudo glyph with name = *GCXXXX*
|
||||
return False
|
31
examples/gdl/makeGdl.py
Executable file
31
examples/gdl/makeGdl.py
Executable file
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env python
|
||||
'Analyse a font and generate GDL to help with the creation of graphite fonts'
|
||||
__url__ = 'https://github.com/silnrsi/pysilfont'
|
||||
__copyright__ = 'Copyright (c) 2015 SIL International (https://www.sil.org)'
|
||||
__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
|
||||
|
||||
from gdl.font import Font
|
||||
import gdl.ot
|
||||
from argparse import ArgumentParser
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('infont')
|
||||
parser.add_argument('outgdl')
|
||||
parser.add_argument('-a','--ap')
|
||||
parser.add_argument('-i','--include')
|
||||
parser.add_argument('-y','--alias')
|
||||
args = parser.parse_args()
|
||||
|
||||
f = Font(args.infont)
|
||||
if args.alias : f.loadAlias(args.alias)
|
||||
if args.ap : f.loadAP(args.ap)
|
||||
|
||||
f.createClasses()
|
||||
f.calculateOTLookups()
|
||||
f.calculatePointClasses()
|
||||
f.ligClasses()
|
||||
|
||||
outf = open(args.outgdl, "w")
|
||||
f.outGDL(outf, args)
|
||||
outf.close()
|
||||
|
448
examples/gdl/ot.py
Normal file
448
examples/gdl/ot.py
Normal file
|
@ -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)
|
||||
|
4506
examples/gdl/psnames.py
Normal file
4506
examples/gdl/psnames.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue