From da875fcb62c801b8d19b3d4d984ad963574fb356 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 2 Mar 2023 21:01:10 +0100 Subject: Adding upstream version 1.6.0. Signed-off-by: Daniel Baumann --- examples/gdl/font.py | 394 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 examples/gdl/font.py (limited to 'examples/gdl/font.py') diff --git a/examples/gdl/font.py b/examples/gdl/font.py new file mode 100644 index 0000000..01f7691 --- /dev/null +++ b/examples/gdl/font.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python +'The main font object for GDL creation. Depends on fonttools' +__url__ = 'http://github.com/silnrsi/pysilfont' +__copyright__ = 'Copyright (c) 2012 SIL International (http://www.sil.org)' +__license__ = 'Released under the MIT License (http://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') + -- cgit v1.2.3