summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-11-21 15:00:40 +0100
committerDaniel Baumann <daniel@debian.org>2024-11-21 15:00:40 +0100
commit012d9cb5faed22cb9b4151569d30cc08563b02d1 (patch)
treefd901b9c231aeb8afa713851f23369fa4a1af2b3 /examples
parentInitial commit. (diff)
downloadpysilfont-upstream.tar.xz
pysilfont-upstream.zip
Adding upstream version 1.8.0.upstream/1.8.0upstream
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rwxr-xr-xexamples/FFmapGdlNames.py99
-rwxr-xr-xexamples/FFmapGdlNames2.py72
-rwxr-xr-xexamples/FLWriteXml.py118
-rw-r--r--examples/FTMLnorm.py24
-rw-r--r--examples/FTaddEmptyOT.py49
-rw-r--r--examples/accesslibplist.py34
-rw-r--r--examples/chaindemo.py43
-rw-r--r--examples/fbonecheck.py17
-rw-r--r--examples/fbttfchecks.py65
-rwxr-xr-xexamples/ffchangeglyphnames.py65
-rw-r--r--examples/ffcopyglyphs.py161
-rwxr-xr-xexamples/ffremovealloverlaps.py31
-rwxr-xr-xexamples/fontforge-old/FFaddPUA.py31
-rwxr-xr-xexamples/fontforge-old/FFcheckDupUSV.py62
-rwxr-xr-xexamples/fontforge-old/FFcolourGlyphs.py51
-rwxr-xr-xexamples/fontforge-old/FFcompareFonts.py44
-rwxr-xr-xexamples/fontforge-old/FFdblEncode.py48
-rwxr-xr-xexamples/fontforge-old/FFfromAP.py74
-rwxr-xr-xexamples/fontforge-old/FFlistAPNum.py38
-rwxr-xr-xexamples/fontforge-old/FFlistGlyphNames.py22
-rwxr-xr-xexamples/fontforge-old/FFlistGlyphinfo.py61
-rwxr-xr-xexamples/fontforge-old/FFlistRefNum.py33
-rwxr-xr-xexamples/fontforge-old/FFnameSearchNReplace.py38
-rwxr-xr-xexamples/fontforge-old/FFundblEncode.py50
-rwxr-xr-xexamples/fontforge-old/demoAddToMenu.py38
-rwxr-xr-xexamples/fontforge-old/demoExecuteScript.py29
-rwxr-xr-xexamples/fontforge-old/demoFunctions.py90
-rw-r--r--examples/gdl/__init__.py20
-rw-r--r--examples/gdl/font.py394
-rw-r--r--examples/gdl/glyph.py174
-rwxr-xr-xexamples/gdl/makeGdl.py31
-rw-r--r--examples/gdl/ot.py448
-rw-r--r--examples/gdl/psnames.py4506
-rwxr-xr-xexamples/preflight9
-rwxr-xr-xexamples/psfaddGlyphDemo.py53
-rwxr-xr-xexamples/psfexpandstroke.py641
-rw-r--r--examples/psfexportnamesunicodesfp.py30
-rw-r--r--examples/psfgenftml.py189
-rwxr-xr-xexamples/psftidyfontlabufo.py140
-rw-r--r--examples/psftoneletters.py327
-rwxr-xr-xexamples/xmlDemo.py54
41 files changed, 8503 insertions, 0 deletions
diff --git a/examples/FFmapGdlNames.py b/examples/FFmapGdlNames.py
new file mode 100755
index 0000000..3e22df6
--- /dev/null
+++ b/examples/FFmapGdlNames.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+'''Write mapping of graphite names to new graphite names based on:
+ - two ttf files
+ - the gdl files produced by makeGdl run against those fonts
+ This could be different versions of makeGdl
+ - a csv mapping glyph names used in original ttf to those in the new font '''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2016 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+import datetime
+
+suffix = "_mapGDLnames2"
+argspec = [
+ ('ifont1',{'help': 'First ttf font file'}, {'type': 'infont'}),
+ ('ifont2',{'help': 'Second ttf font file'}, {'type': 'infont'}),
+ ('gdl1',{'help': 'Original make_gdl file'}, {'type': 'infile'}),
+ ('gdl2',{'help': 'Updated make_gdl file'}, {'type': 'infile'}),
+ ('-m','--mapping',{'help': 'Mapping csv file'}, {'type': 'incsv', 'def': '_map.csv'}),
+ ('-o','--output',{'help': 'Ouput csv file'}, {'type': 'outfile', 'def': suffix+'.csv'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': suffix+'.log'}),
+ ('--nocomments',{'help': 'No comments in output files', 'action': 'store_true', 'default': False},{})]
+
+def doit(args) :
+ logger = args.paramsobj.logger
+ # Check input fonts are ttf
+ fontfile1 = args.cmdlineargs[1]
+ fontfile2 = args.cmdlineargs[2]
+
+ if fontfile1[-3:] != "ttf" or fontfile2[-3:] != "ttf" :
+ logger.log("Input fonts needs to be ttf files", "S")
+
+ font1 = args.ifont1
+ font2 = args.ifont2
+ gdlfile1 = args.gdl1
+ gdlfile2 = args.gdl2
+ mapping = args.mapping
+ outfile = args.output
+
+ # Add initial comments to outfile
+ if not args.nocomments :
+ outfile.write("# " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S ") + args.cmdlineargs[0] + "\n")
+ outfile.write("# "+" ".join(args.cmdlineargs[1:])+"\n\n")
+
+ # Process gdl files
+ oldgrnames = {}
+ for line in gdlfile1 :
+ # Look for lines of format <grname> = glyphid(nnn)...
+ pos = line.find(" = glyphid(")
+ if pos == -1 : continue
+ grname = line[0:pos]
+ gid = line[pos+11:line.find(")")]
+ oldgrnames[int(gid)]=grname
+
+ newgrnames = {}
+ for line in gdlfile2 :
+ # Look for lines of format <grname> = glyphid(nnn)...
+ pos = line.find(" = glyphid(")
+ if pos == -1 : continue
+ grname = line[0:pos]
+ gid = line[pos+11:line.find(")")]
+ newgrnames[int(gid)]=grname
+
+ # Process mapping file
+ SILnames = {}
+ mapping.numfields = 2
+ for line in mapping : SILnames[line[1]] = line[0]
+
+ # Map SIL name to gids in font 2
+ SILtogid2={}
+ for glyph in font2.glyphs(): SILtogid2[glyph.glyphname] = glyph.originalgid
+
+ # Combine all the mappings via ttf1!
+ cnt1 = 0
+ cnt2 = 0
+ for glyph in font1.glyphs():
+ gid1 = glyph.originalgid
+ gname1 = glyph.glyphname
+ gname2 = SILnames[gname1]
+ gid2 = SILtogid2[gname2]
+ oldgrname = oldgrnames[gid1] if gid1 in oldgrnames else None
+ newgrname = newgrnames[gid2] if gid2 in newgrnames else None
+ if oldgrname is None or newgrname is None :
+ print type(gid1), gname1, oldgrname
+ print gid2, gname2, newgrname
+ cnt2 += 1
+ if cnt2 > 10 : break
+ else:
+ outfile.write(oldgrname + "," + newgrname+"\n")
+ cnt1 += 1
+
+ print cnt1,cnt2
+
+ outfile.close()
+ return
+
+execute("FF",doit, argspec)
diff --git a/examples/FFmapGdlNames2.py b/examples/FFmapGdlNames2.py
new file mode 100755
index 0000000..21a06b6
--- /dev/null
+++ b/examples/FFmapGdlNames2.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+'''Write mapping of graphite names to new graphite names based on:
+ - an original ttf font
+ - the gdl file produced by makeGdl when original font was produced
+ - a csv mapping glyph names used in original ttf to those in the new font
+ - pysilfont's gdl library - so assumes pysilfonts makeGdl will be used with new font'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2016 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+import silfont.gdl.psnames as ps
+import datetime
+
+suffix = "_mapGDLnames"
+argspec = [
+ ('ifont',{'help': 'Input ttf font file'}, {'type': 'infont'}),
+ ('-g','--gdl',{'help': 'Input gdl file'}, {'type': 'infile', 'def': '.gdl'}),
+ ('-m','--mapping',{'help': 'Mapping csv file'}, {'type': 'incsv', 'def': '_map.csv'}),
+ ('-o','--output',{'help': 'Ouput csv file'}, {'type': 'outfile', 'def': suffix+'.csv'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': suffix+'.log'}),
+ ('--nocomments',{'help': 'No comments in output files', 'action': 'store_true', 'default': False},{})]
+
+def doit(args) :
+ logger = args.paramsobj.logger
+ # Check input font is a ttf
+ fontfile = args.cmdlineargs[1]
+ if fontfile[-3:] != "ttf" :
+ logger.log("Input font needs to be a ttf file", "S")
+
+ font = args.ifont
+ gdlfile = args.gdl
+ mapping = args.mapping
+ outfile = args.output
+
+ # Add initial comments to outfile
+ if not args.nocomments :
+ outfile.write("# " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S ") + args.cmdlineargs[0] + "\n")
+ outfile.write("# "+" ".join(args.cmdlineargs[1:])+"\n\n")
+
+ # Process gdl file
+ oldgrnames = {}
+ for line in args.gdl :
+ # Look for lines of format <grname> = glyphid(nnn)...
+ pos = line.find(" = glyphid(")
+ if pos == -1 : continue
+ grname = line[0:pos]
+ gid = line[pos+11:line.find(")")]
+ oldgrnames[int(gid)]=grname
+
+ # Create map from AGL name to new graphite name
+ newgrnames = {}
+ mapping.numfields = 2
+ for line in mapping :
+ AGLname = line[1]
+ SILname = line[0]
+ grname = ps.Name(SILname).GDL()
+ newgrnames[AGLname] = grname
+
+ # Find glyph names in ttf font
+ for glyph in font.glyphs():
+ gid = glyph.originalgid
+ gname = glyph.glyphname
+ oldgrname = oldgrnames[gid] if gid in oldgrnames else None
+ newgrname = newgrnames[gname] if gname in newgrnames else None
+ outfile.write(oldgrname + "," + newgrname+"\n")
+
+ outfile.close()
+ return
+
+execute("FF",doit, argspec)
diff --git a/examples/FLWriteXml.py b/examples/FLWriteXml.py
new file mode 100755
index 0000000..49e1259
--- /dev/null
+++ b/examples/FLWriteXml.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+'''Outputs attachment point information and notes as XML file for TTFBuilder'''
+__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)'
+__author__ = 'M Hosken'
+
+# user controls
+
+# output entries for all glyphs even those with nothing interesting to say about them
+all_glyphs = 1
+
+# output the glyph id as part of the information
+output_gid = 1
+
+# output the glyph notes
+output_note = 0
+
+# output UID with "U+" prefix
+output_uid_prefix = 0
+
+# print progress indicator
+print_progress = 0
+
+# no user serviceable parts under here!
+from xml.sax.saxutils import XMLGenerator
+import os
+
+def print_glyph(font, glyph, index):
+ if print_progress and index % 100 == 0:
+ print "%d: %s" % (index, glyph.name)
+
+ if (not all_glyphs and len(glyph.anchors) == 0 and len(glyph.components) == 0 and
+ not (glyph.note and output_note)):
+ return
+ attribs = {}
+ if output_gid:
+ attribs["GID"] = unicode(index)
+ if glyph.unicode:
+ if output_uid_prefix:
+ attribs["UID"] = unicode("U+%04X" % glyph.unicode)
+ else:
+ attribs["UID"] = unicode("%04X" % glyph.unicode)
+ if glyph.name:
+ attribs["PSName"] = unicode(glyph.name)
+ xg.startElement("glyph", attribs)
+
+ for anchor in (glyph.anchors):
+ xg.startElement("point", {"type":unicode(anchor.name), "mark":unicode(anchor.mark)})
+ xg.startElement("location", {"x":unicode(anchor.x), "y":unicode(anchor.y)})
+ xg.endElement("location")
+ xg.endElement("point")
+
+ for comp in (glyph.components):
+ g = font.glyphs[comp.index]
+ r = g.GetBoundingRect()
+ x0 = 0.5 * (r.ll.x * (1 + comp.scale.x) + r.ur.x * (1 - comp.scale.x)) + comp.delta.x
+ y0 = 0.5 * (r.ll.y * (1 + comp.scale.y) + r.ur.y * (1 - comp.scale.y)) + comp.delta.y
+ x1 = 0.5 * (r.ll.x * (1 - comp.scale.x) + r.ur.x * (1 + comp.scale.x)) + comp.delta.x
+ y1 = 0.5 * (r.ll.y * (1 - comp.scale.x) + r.ur.y * (1 + comp.scale.y)) + comp.delta.y
+
+ attribs = {"bbox":unicode("%d, %d, %d, %d" % (x0, y0, x1, y1))}
+ attribs["GID"] = unicode(comp.index)
+ if (g.unicode):
+ if output_uid_prefix:
+ attribs["UID"] = unicode("U+%04X" % g.unicode)
+ else:
+ attribs["UID"] = unicode("%04X" % g.unicode)
+ if (g.name):
+ attribs["PSName"] = unicode(g.name)
+ xg.startElement("compound", attribs)
+ xg.endElement("compound")
+
+ if glyph.mark:
+ xg.startElement("property", {"name":unicode("mark"), "value":unicode(glyph.mark)})
+ xg.endElement("property")
+
+ if glyph.customdata:
+ xg.startElement("customdata", {})
+ xg.characters(unicode(glyph.customdata.strip()))
+ xg.endElement("customdata")
+
+ if glyph.note and output_note:
+ xg.startElement("note", {})
+ xg.characters(glyph.note)
+ xg.endElement("note")
+ xg.endElement("glyph")
+
+outname = fl.font.file_name.replace(".vfb", "_tmp.xml")
+fh = open(outname, "w")
+xg = XMLGenerator(fh, "utf-8")
+xg.startDocument()
+
+#fl.font.full_name is needed to get the name as it appears to Windows
+#fl.font.font_name seems to be the PS name. This messes up GenTest.pl when it generates WPFeatures.wpx
+xg.startElement("font", {'name':unicode(fl.font.full_name), "upem":unicode(fl.font.upm)})
+for i in range(0, len(fl.font.glyphs)):
+ print_glyph(fl.font, fl.font.glyphs[i], i)
+xg.endElement("font")
+
+xg.endDocument()
+fh.close()
+
+#somehow this enables UNC naming (\\Gutenberg vs i:) to work when Saxon is called with popen
+#without this, if outname is UNC-based, then drive letters and UNC volumes are invisible
+# if outname is drive-letter-based, then drive letters and UNC volumes are already visible
+if (outname[0:2] == r'\\'):
+ os.chdir("c:")
+tidy = "tidy -i -xml -n -wrap 0 --char-encoding utf8 --indent-spaces 4 --quote-nbsp no --tab-size 4 -m %s"
+saxon = "saxon %s %s" % ('"' + outname + '"', r'"C:\Roman Font\rfs_font\10 Misc Utils\glyph_norm.xsl"') #handle spaces in file name
+f = os.popen(saxon, "rb")
+g = open(outname.replace("_tmp.xml", ".xml"), "wb")
+output = f.read()
+g.write(output)
+f.close()
+g.close()
+
+print "Done"
diff --git a/examples/FTMLnorm.py b/examples/FTMLnorm.py
new file mode 100644
index 0000000..b98e0b0
--- /dev/null
+++ b/examples/FTMLnorm.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+'Normalize an FTML file'
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2016 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+import silfont.ftml as ftml
+from xml.etree import cElementTree as ET
+
+argspec = [
+ ('infile',{'help': 'Input ftml file'}, {'type': 'infile'}),
+ ('outfile',{'help': 'Output ftml file', 'nargs': '?'}, {'type': 'outfile', 'def': '_new.xml'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_ftmltest.log'})
+ ]
+
+def doit(args) :
+ f = ftml.Fxml(args.infile)
+ f.save(args.outfile)
+
+def cmd() : execute("",doit,argspec)
+if __name__ == "__main__": cmd()execute("", doit, argspec)
+
diff --git a/examples/FTaddEmptyOT.py b/examples/FTaddEmptyOT.py
new file mode 100644
index 0000000..46319ac
--- /dev/null
+++ b/examples/FTaddEmptyOT.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+'Add empty Opentype tables to ttf font'
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2014 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'Martin Hosken'
+
+from silfont.core import execute
+from fontTools import ttLib
+from fontTools.ttLib.tables import otTables
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_conv.log'}),
+ ('-s','--script',{'help': 'Script tag to generate [DFLT]', 'default': 'DFLT', }, {}),
+ ('-t','--type',{'help': 'Table to create: gpos, gsub, [both]', 'default': 'both', }, {}) ]
+
+def doit(args) :
+ font = args.ifont
+ args.type = args.type.upper()
+
+ for tag in ('GSUB', 'GPOS') :
+ if tag == args.type or args.type == 'BOTH' :
+ table = ttLib.getTableClass(tag)()
+ t = getattr(otTables, tag, None)()
+ t.Version = 1.0
+ t.ScriptList = otTables.ScriptList()
+ t.ScriptList.ScriptRecord = []
+ t.FeatureList = otTables.FeatureList()
+ t.FeatureList.FeatureRecord = []
+ t.LookupList = otTables.LookupList()
+ t.LookupList.Lookup = []
+ srec = otTables.ScriptRecord()
+ srec.ScriptTag = args.script
+ srec.Script = otTables.Script()
+ srec.Script.DefaultLangSys = None
+ srec.Script.LangSysRecord = []
+ t.ScriptList.ScriptRecord.append(srec)
+ t.ScriptList.ScriptCount = 1
+ t.FeatureList.FeatureCount = 0
+ t.LookupList.LookupCount = 0
+ table.table = t
+ font[tag] = table
+
+ return font
+
+def cmd() : execute("FT",doit, argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/accesslibplist.py b/examples/accesslibplist.py
new file mode 100644
index 0000000..e7b6348
--- /dev/null
+++ b/examples/accesslibplist.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+'Demo script for accessing fields in lib.plist'
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2017 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont', {'help': 'Input font file'}, {'type': 'infont'}),
+ ('field', {'help': 'field to access'},{})]
+
+def doit(args):
+ font = args.ifont
+ field = args.field
+ lib = font.lib
+
+ if field in lib:
+ val = lib.getval(field)
+ print
+ print val
+ print
+ print "Field " + field + " is type " + lib[field][1].tag + " in xml"
+
+ print "The retrieved value is " + str(type(val)) + " in Python"
+ else:
+ print "Field not in lib.plist"
+
+ return
+
+
+def cmd(): execute("UFO", doit, argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/chaindemo.py b/examples/chaindemo.py
new file mode 100644
index 0000000..6290b58
--- /dev/null
+++ b/examples/chaindemo.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+''' Demo of how to chain calls to multiple scripts together.
+Running
+ python chaindemo.py infont outfont --featfile feat.csv --uidsfile uids.csv
+will run execute() against psfnormalize, psfsetassocfeat and psfsetassocuids passing the font, parameters
+and logger objects from one call to the next. So:
+- the font is only opened once and written once
+- there is a single log file produced
+'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2017 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute, chain
+import silfont.scripts.psfnormalize as psfnormalize
+import silfont.scripts.psfsetassocfeat as psfsetassocfeat
+import silfont.scripts.psfsetassocuids as psfsetassocuids
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont'}),
+ ('--featfile',{'help': 'Associate features csv'}, {'type': 'filename'}),
+ ('--uidsfile', {'help': 'Associate uids csv'}, {'type': 'filename'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_chain.log'})]
+
+def doit(args) :
+
+ argv = ['psfnormalize', 'dummy'] # 'dummy' replaces input font since font object is being passed. Other parameters could be added.
+ font = chain(argv, psfnormalize.doit, psfnormalize.argspec, args.ifont, args.paramsobj, args.logger, args.quiet)
+
+ argv = ['psfsetassocfeat', 'dummy', '-i', args.featfile]
+ font = chain(argv, psfsetassocfeat.doit, psfsetassocfeat.argspec, font, args.paramsobj, args.logger, args.quiet)
+
+ argv = ['psfsetassocuids', 'dummy', '-i', args.uidsfile]
+ font = chain(argv, psfsetassocuids.doit, psfsetassocuids.argspec, font, args.paramsobj, args.logger, args.quiet)
+
+ return font
+
+def cmd() : execute("UFO",doit, argspec)
+
+if __name__ == "__main__": cmd()
+
diff --git a/examples/fbonecheck.py b/examples/fbonecheck.py
new file mode 100644
index 0000000..79d8351
--- /dev/null
+++ b/examples/fbonecheck.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+'''Example profile for use with psfrunfbchecks that will just run one or more specified checks'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2022 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.fbtests.ttfchecks import psfcheck_list, make_profile, check, PASS, FAIL
+
+# Exclude all checks bar those listed
+for check in psfcheck_list:
+ if check not in ["org.sil/check/whitespace_widths"]:
+ psfcheck_list[check] = {'exclude': True}
+
+# Create the fontbakery profile
+profile = make_profile(psfcheck_list, variable_font = False)
+
diff --git a/examples/fbttfchecks.py b/examples/fbttfchecks.py
new file mode 100644
index 0000000..2f37775
--- /dev/null
+++ b/examples/fbttfchecks.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+'''Example for making project-specific changes to the standard pysilfont set of Font Bakery ttf checks.
+It will start with all the checks normally run by pysilfont's ttfchecks profile then modify as described below'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2020 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.fbtests.ttfchecks import psfcheck_list, make_profile, check, PASS, FAIL
+
+#
+# General settings
+#
+psfvariable_font = False # Set to True for variable fonts, so different checks will be run
+
+#
+# psfcheck_list is a dictionary of all standard Fontbakery checks with a dictionary for each check indicating
+# pysilfont's standard processing of that check
+#
+# Specifically:
+# - If the dictionary has "exclude" set to True, that check will be excluded from the profile
+# - If change_status is set, the status values reported by psfrunfbchecks will be changed based on its values
+# - If a change in status is temporary - eg just until something is fixed, use temp_change_status instead
+#
+# Projects can edit this dictionary to change behaviour from Pysilfont defaults. See examples below
+
+# To reinstate the copyright check (which is normally excluded):
+psfcheck_list["com.google.fonts/check/metadata/copyright"]["exclude"] = False
+
+# To prevent the hinting_impact check from running:
+psfcheck_list["com.google.fonts/check/hinting_impact"]["exclude"] = True
+
+# To change a FAIL status for com.google.fonts/check/whitespace_glyphnames to WARN:
+psfcheck_list["com.google.fonts/check/whitespace_glyphnames"]["temp_change_status"] = {
+ "FAIL": "WARN", "reason": "This font currently uses non-standard names"}
+
+#
+# Create the fontbakery profile
+#
+profile = make_profile(psfcheck_list, variable_font = psfvariable_font)
+
+# Add any project-specific tests (This dummy test should normally be commented out!)
+
+@profile.register_check
+@check(
+ id = 'org.sil/dummy',
+ rationale = """
+ There is no reason for this test!
+ """
+)
+def org_sil_dummy():
+ """Dummy test that always fails"""
+ if True: yield FAIL, "Oops!"
+
+'''
+Run this using
+
+ $ psfrunfbchecks --profile fbttfchecks.py <ttf file(s) to check> ...
+
+It can also be used with fontbakery directly if you want to use options that psfrunfbchecks does not support, however
+status changes will not be actioned.
+
+ $ fontbakery check-profile fbttfchecks.py <ttf file(s) to check> ...
+
+''' \ No newline at end of file
diff --git a/examples/ffchangeglyphnames.py b/examples/ffchangeglyphnames.py
new file mode 100755
index 0000000..a7cb2e3
--- /dev/null
+++ b/examples/ffchangeglyphnames.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+from __future__ import unicode_literals
+'''Update glyph names in a font based on csv file
+ - Using FontForge rather than UFOlib so it can work with ttf (or sfd) files'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2016 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+''' This will need updating, since FontForge is no longer supported as a tool by execute() So:
+- ifont and ofont will need to be changed to have type 'filename'
+- ifont will then need to be opened using fontforge.open
+- The font will need to be saved with font.save
+- execute will need to be called with the tool set to None instead of "FF"
+'''
+
+argspec = [
+ ('ifont',{'help': 'Input ttf font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont'}),
+ ('-i','--input',{'help': 'Mapping csv file'}, {'type': 'incsv', 'def': 'psnames.csv'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_setPostNames.log'}),
+ ('--reverse',{'help': 'Change names in reverse', 'action': 'store_true', 'default': False},{})]
+
+def doit(args) :
+ logger = args.paramsobj.logger
+
+ font = args.ifont
+
+ # Process csv
+ csv = args.input
+ csv.numfields = 2
+ newnames={}
+ namescheck=[]
+ missingnames = False
+ for line in csv :
+ if args.reverse :
+ newnames[line[1]] = line[0]
+ namescheck.append(line[1])
+ else :
+ newnames[line[0]] = line[1]
+ namescheck.append(line[0])
+
+ for glyph in font.glyphs():
+ gname = glyph.glyphname
+ if gname in newnames :
+ namescheck.remove(gname)
+ glyph.glyphname = newnames[gname]
+ else:
+ missingnames = True
+ logger.log(gname + " in font but not csv file","W")
+
+ if missingnames : logger.log("Font glyph names missing from csv - see log for details","E")
+
+ for name in namescheck : # Any names left in namescheck were in csv but not ttf
+ logger.log(name + " in csv but not in font","W")
+
+ if namescheck != [] : logger.log("csv file names missing from font - see log for details","E")
+
+ return font
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
+
diff --git a/examples/ffcopyglyphs.py b/examples/ffcopyglyphs.py
new file mode 100644
index 0000000..3fcfe76
--- /dev/null
+++ b/examples/ffcopyglyphs.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+'''FontForge: Copy glyphs from one font to another, without using ffbuilder'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2015-2019 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'Martin Hosken'
+
+from silfont.core import execute
+import psMat
+import io
+
+
+''' This will need updating, since FontForge is no longer supported as a tool by execute() So:
+- ifont and ofont will need to be changed to have type 'filename'
+- ifont will then need to be opened using fontforge.open
+- The font will need to be saved with font.save
+- execute will need to be called with the tool set to None instead of "FF"
+'''
+
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont', 'def': 'new'}),
+ ('-i','--input',{'help': 'Font to get glyphs from', 'required' : True}, {'type': 'infont'}),
+ ('-r','--range',{'help': 'StartUnicode..EndUnicode no spaces, e.g. 20..7E', 'action' : 'append'}, {}),
+ ('--rangefile',{'help': 'File with USVs e.g. 20 or a range e.g. 20..7E or both', 'action' : 'append'}, {}),
+ ('-n','--name',{'help': 'Include glyph named name', 'action' : 'append'}, {}),
+ ('--namefile',{'help': 'File with glyph names', 'action' : 'append'}, {}),
+ ('-a','--anchors',{'help' : 'Copy across anchor points', 'action' : 'store_true'}, {}),
+ ('-f','--force',{'help' : 'Overwrite existing glyphs in the font', 'action' : 'store_true'}, {}),
+ ('-s','--scale',{'type' : float, 'help' : 'Scale glyphs by this factor'}, {})
+]
+
+def copyglyph(font, infont, g, u, args) :
+ extras = set()
+ if args.scale is None :
+ scale = psMat.identity()
+ else :
+ scale = psMat.scale(args.scale)
+ o = font.findEncodingSlot(u)
+ if o == -1 :
+ glyph = font.createChar(u, g.glyphname)
+ else :
+ glyph = font[o]
+ if len(g.references) == 0 :
+ font.selection.select(glyph)
+ pen = glyph.glyphPen()
+ g.draw(pen)
+ glyph.transform(scale)
+ else :
+ for r in g.references :
+ t = psMat.compose(r[1], scale)
+ newt = psMat.compose(psMat.identity(), psMat.translate(t[4], t[5]))
+ glyph.addReference(r[0], newt)
+ extras.add(r[0])
+ glyph.width = g.width * scale[0]
+ if args.anchors :
+ for a in g.anchorPoints :
+ try :
+ l = font.getSubtableOfAnchor(a[1])
+ except EnvironmentError :
+ font.addAnchorClass("", a[0]*scale[0], a[1]*scale[3])
+ glyph.anchorPoints = g.anchorPoints
+ return list(extras)
+
+def doit(args) :
+ font = args.ifont
+ infont = args.input
+ font.encoding = "Original"
+ infont.encoding = "Original" # compact the font so findEncodingSlot will work
+ infont.layers["Fore"].is_quadratic = font.layers["Fore"].is_quadratic
+
+ # list of glyphs to copy
+ glist = list()
+
+ # glyphs specified on the command line
+ for n in args.name or [] :
+ glist.append(n)
+
+ # glyphs specified in a file
+ for filename in args.namefile or [] :
+ namefile = io.open(filename, 'r')
+ for line in namefile :
+ # ignore comments
+ line = line.partition('#')[0]
+ line = line.strip()
+
+ # ignore blank lines
+ if (line == ''):
+ continue
+
+ glist.append(line)
+
+ # copy glyphs by name
+ reportErrors = True
+ while len(glist) :
+ tglist = glist[:]
+ glist = []
+ for n in tglist:
+ if n in font and not args.force :
+ if reportErrors :
+ print("Glyph {} already present. Skipping".format(n))
+ continue
+ if n not in infont :
+ print("Can't find glyph {}".format(n))
+ continue
+ g = infont[n]
+ glist.extend(copyglyph(font, infont, g, -1, args))
+ reportErrors = False
+
+ # list of characters to copy
+ ulist = list()
+
+ # characters specified on the command line
+ for r in args.range or [] :
+ (rstart, rend) = [int(x, 16) for x in r.split('..')]
+ for u in range(rstart, rend + 1) :
+ ulist.append(u)
+
+ # characters specified in a file
+ for filename in args.rangefile or [] :
+ rangefile = io.open(filename, 'r')
+ for line in rangefile :
+ # ignore comments
+ line = line.partition('#')[0]
+ line = line.strip()
+
+ # ignore blank lines
+ if (line == ''):
+ continue
+
+ # obtain USVs
+ try:
+ (rstart, rend) = line.split('..')
+ except ValueError:
+ rstart = line
+ rend = line
+
+ rstart = int(rstart, 16)
+ rend = int(rend, 16)
+
+ for u in range(rstart, rend + 1):
+ ulist.append(u)
+
+ # copy the characters from the generated list
+ for u in ulist:
+ o = font.findEncodingSlot(u)
+ if o != -1 and not args.force :
+ print("Glyph for {:x} already present. Skipping".format(u))
+ continue
+ e = infont.findEncodingSlot(u)
+ if e == -1 :
+ print("Can't find glyph for {:04x}".format(u))
+ continue
+ g = infont[e]
+ copyglyph(font, infont, g, u, args)
+
+ return font
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/ffremovealloverlaps.py b/examples/ffremovealloverlaps.py
new file mode 100755
index 0000000..374b755
--- /dev/null
+++ b/examples/ffremovealloverlaps.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+from __future__ import unicode_literals
+'FontForge: Remove overlap on all glyphs in font'
+__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)'
+__author__ = 'Victor Gaultney'
+
+from silfont.core import execute
+
+
+''' This will need updating, since FontForge is no longer supported as a tool by execute() So:
+- ifont and ofont will need to be changed to have type 'filename'
+- ifont will then need to be opened using fontforge.open
+- The font will need to be saved with font.save
+- execute will need to be called with the tool set to None instead of "FF"
+'''
+
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont', 'def': 'new'})]
+
+def doit(args) :
+ font = args.ifont
+ for glyph in font:
+ font[glyph].removeOverlap()
+ return font
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFaddPUA.py b/examples/fontforge-old/FFaddPUA.py
new file mode 100755
index 0000000..a5f0d6f
--- /dev/null
+++ b/examples/fontforge-old/FFaddPUA.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+'''FontForge: Add cmap entries for all glyphs in the font'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2016 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'Martin Hosken'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont', 'def': 'new'})
+]
+
+def nextpua(p) :
+ if p == 0 : return 0xE000
+ if p == 0xF8FF : return 0xF0000
+ return p + 1
+
+def doit(args) :
+ p = nextpua(0)
+ font = args.ifont
+ for n in font :
+ g = font[n]
+ if g.unicode == -1 :
+ g.unicode = p
+ p = nextpua(p)
+ return font
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFcheckDupUSV.py b/examples/fontforge-old/FFcheckDupUSV.py
new file mode 100755
index 0000000..b7a65e2
--- /dev/null
+++ b/examples/fontforge-old/FFcheckDupUSV.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+'FontForge: Check for duplicate USVs in unicode or altuni fields'
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('-o','--output',{'help': 'Output text file'}, {'type': 'outfile', 'def': 'DupUSV.txt'})]
+
+def doit(args) :
+ font = args.ifont
+ outf = args.output
+
+ # Process unicode and altunicode for all glyphs
+ usvs={}
+ for glyph in font:
+ g = font[glyph]
+ if g.unicode != -1:
+ usv=UniStr(g.unicode)
+ AddUSV(usvs,usv,glyph)
+ # Check any alternate usvs
+ altuni=g.altuni
+ if altuni != None:
+ for au in altuni:
+ usv=UniStr(au[0]) # (may need to check variant flag)
+ AddUSV(usvs,usv,glyph + ' (alt)')
+
+ items = usvs.items()
+ items = filter(lambda x: len(x[1]) > 1, items)
+ items.sort()
+
+ for i in items:
+ usv = i[0]
+ print usv + ' has duplicates'
+ gl = i[1]
+ glyphs = gl[0]
+ for j in range(1,len(gl)):
+ glyphs = glyphs + ', ' + gl[j]
+
+ outf.write('%s: %s\n' % (usv,glyphs))
+
+ outf.close()
+ print "Done!"
+
+def UniStr(u):
+ if u:
+ return "U+{0:04X}".format(u)
+ else:
+ return "No USV" #length same as above
+
+def AddUSV(usvs,usv,glyph):
+ if not usvs.has_key(usv):
+ usvs[usv] = [glyph]
+ else:
+ usvs[usv].append(glyph)
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFcolourGlyphs.py b/examples/fontforge-old/FFcolourGlyphs.py
new file mode 100755
index 0000000..95ec700
--- /dev/null
+++ b/examples/fontforge-old/FFcolourGlyphs.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+'Set Glyph colours based on a csv file - format glyphname,colour'
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont', 'def': 'new'}),
+ ('-i','--input',{'help': 'Input csv file'}, {'type': 'infile', 'def': 'colourGlyphs.csv'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': 'colourGlyphs.log'})]
+
+def doit(args) :
+ font=args.ifont
+ inpf = args.input
+ logf = args.log
+# define colours
+ colours = {
+ 'black' :0x000000,
+ 'red' :0xFF0000,
+ 'green' :0x00FF00,
+ 'blue' :0x0000FF,
+ 'cyan' :0x00FFFF,
+ 'magenta':0xFF00FF,
+ 'yellow' :0xFFFF00,
+ 'white' :0xFFFFFF }
+
+# Change colour of Glyphs
+ for line in inpf.readlines() :
+ glyphn, colour = line.strip().split(",") # will exception if not 2 elements
+ colour=colour.lower()
+ if glyphn[0] in '"\'' : glyphn = glyphn[1:-1] # slice off quote marks, if present
+ if glyphn not in font:
+ logf.write("Glyph %s not in font\n" % (glyphn))
+ print "Glyph %s not in font" % (glyphn)
+ continue
+ g = font[glyphn]
+ if colour in colours.keys():
+ g.color=colours[colour]
+ else:
+ logf.write("Glyph: %s - non-standard colour %s\n" % (glyphn,colour))
+ print "Glyph: %s - non-standard colour %s" % (glyphn,colour)
+
+ logf.close()
+ return font
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFcompareFonts.py b/examples/fontforge-old/FFcompareFonts.py
new file mode 100755
index 0000000..1c68a43
--- /dev/null
+++ b/examples/fontforge-old/FFcompareFonts.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+'Compare two fonts based on specified criteria and report differences'
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ifont2',{'help': 'Input font file 2'}, {'type': 'infont', 'def': 'new'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': 'compareFonts.log'}),
+ ('-o','--options',{'help': 'Options', 'choices': ['c'], 'nargs': '*'}, {})
+ ]
+
+def doit(args) :
+ font1=args.ifont
+ font2=args.ifont2
+ logf = args.log
+ options = args.options
+ logf.write("Comparing fonts: \n %s (%s)\n %s (%s)\n" % (font1.path,font1.fontname,font2.path,font2.fontname))
+ if options != None : logf.write('with options: %s\n' % (options))
+ logf.write("\n")
+ compare(font1,font2,logf,options)
+ compare(font2,font1,logf,None) # Compare again the other way around, just looking for missing Glyphs
+ logf.close()
+ return
+
+def compare(fonta,fontb,logf,options) :
+ for glyph in fonta :
+ if glyph in fontb :
+ if options != None : # Do extra checks based on options supplied
+ ga=fonta[glyph]
+ gb=fontb[glyph]
+ for opt in options :
+ if opt == "c" :
+ if len(ga.references) != len(gb.references) :
+ logf.write("Glyph %s: number of components is different - %s v %s\n" % (glyph,len(ga.references),len(gb.references)))
+ else :
+ logf.write("Glyph %s missing from %s\n" % (glyph,fonta.path))
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFdblEncode.py b/examples/fontforge-old/FFdblEncode.py
new file mode 100755
index 0000000..e713b41
--- /dev/null
+++ b/examples/fontforge-old/FFdblEncode.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+'''FontForge: Double encode glyphs based on double encoding data in a file
+Lines in file should look like: "LtnSmARetrHook",U+F236,U+1D8F'''
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont', 'def': 'new'}),
+ ('-i','--input',{'help': 'Input csv text file'}, {'type': 'infile', 'def': 'DblEnc.txt'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': 'DblEnc.log'})]
+
+def doit(args) :
+ font = args.ifont
+ inpf = args.input
+ logf = args.log
+#Create dbl_encode list from the input file
+ dbl_encode = {}
+ for line in inpf.readlines() :
+ glyphn, pua_usv_str, std_usv_str = line.strip().split(",") # will exception if not 3 elements
+ if glyphn[0] in '"\'' : glyphn = glyphn[1:-1] # slice off quote marks, if present
+ pua_usv, std_usv = int(pua_usv_str[2:], 16), int(std_usv_str[2:], 16)
+ dbl_encode[glyphn] = [std_usv, pua_usv]
+ inpf.close()
+
+ for glyph in sorted(dbl_encode.keys()) :
+ if glyph not in font:
+ logf.write("Glyph %s not in font\n" % (glyph))
+ continue
+ g = font[glyph]
+ ousvs=[g.unicode]
+ oalt=g.altuni
+ if oalt != None:
+ for au in oalt:
+ ousvs.append(au[0]) # (may need to check variant flag)
+ dbl = dbl_encode[glyph]
+ g.unicode = dbl[0]
+ g.altuni = ((dbl[1],),)
+ logf.write("encoding for %s changed: %s -> %s\n" % (glyph, ousvs, dbl))
+ logf.close()
+ return font
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFfromAP.py b/examples/fontforge-old/FFfromAP.py
new file mode 100755
index 0000000..6c85276
--- /dev/null
+++ b/examples/fontforge-old/FFfromAP.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+'''Import Attachment Point database into a fontforge font'''
+__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)'
+__author__ = 'Martin Hosken'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont', {'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont', {'help': 'Output font file'}, {'type': 'outfont'}),
+ ('-a','--ap', {'nargs' : 1, 'help': 'Input AP database (required)'}, {})
+]
+
+def assign(varlist, expr) :
+ """passes a variable to be assigned as a list and returns the value"""
+ varlist[0] = expr
+ return expr
+
+def getuidenc(e, f) :
+ if 'UID' in e.attrib :
+ u = int(e.get('UID'), 16)
+ return f.findEncodingSlot(u)
+ else :
+ return -1
+
+def getgid(e, f) :
+ if 'GID' in e.attrib :
+ return int(e.get('GID'))
+ else :
+ return -1
+
+def doit(args) :
+ from xml.etree.ElementTree import parse
+
+ f = args.ifont
+ g = None
+ etree = parse(args.ap)
+ u = []
+ for e in etree.getroot().iterfind("glyph") :
+ name = e.get('PSName')
+ if name in f :
+ g = f[name]
+ elif assign(u, getuidenc(e, f)) != -1 :
+ g = f[u[0]]
+ elif assign(u, getgid(e, f)) != -1 :
+ g = f[u[0]]
+ elif g is not None : # assume a rename so just take next glyph
+ g = f[g.encoding + 1]
+ else :
+ g = f[0]
+ g.name = name
+ g.anchorPoints = ()
+ for p in e.iterfind('point') :
+ pname = p.get('type')
+ l = p[0]
+ x = int(l.get('x'))
+ y = int(l.get('y'))
+ if pname.startswith('_') :
+ ptype = 'mark'
+ pname = pname[1:]
+ else :
+ ptype = 'base'
+ g.addAnchorPoint(pname, ptype, float(x), float(y))
+ comment = []
+ for p in e.iterfind('property') :
+ comment.append("{}: {}".format(e.get('name'), e.get('value')))
+ for p in e.iterfind('note') :
+ comment.append(e.text.strip())
+ g.comment = "\n".join(comment)
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFlistAPNum.py b/examples/fontforge-old/FFlistAPNum.py
new file mode 100755
index 0000000..21a3898
--- /dev/null
+++ b/examples/fontforge-old/FFlistAPNum.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+'FontForge: Report Glyph name, number of anchors - sorted by number of anchors'
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('-o','--output',{'help': 'Output text file'}, {'type': 'outfile', 'def': 'APnum.txt'})]
+
+def doit(args) :
+ font = args.ifont
+ outf = args.output
+
+ # Make a list of glyphs and number of anchor points
+ AP_lst = []
+ for glyph in font:
+ AP_lst.append( [glyph, len(font[glyph].anchorPoints)] )
+ # Sort by numb of APs then glyphname
+ AP_lst.sort(AP_cmp)
+ for AP in AP_lst:
+ outf.write("%s,%s\n" % (AP[0], AP[1]))
+
+ outf.close()
+ print "done"
+
+def AP_cmp(a, b): # Comparison to sort first by number of attachment points) then by Glyph name
+ c = cmp(a[1], b[1])
+ if c != 0:
+ return c
+ else:
+ return cmp(a[0], b[0])
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFlistGlyphNames.py b/examples/fontforge-old/FFlistGlyphNames.py
new file mode 100755
index 0000000..79c0030
--- /dev/null
+++ b/examples/fontforge-old/FFlistGlyphNames.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+'FontForge: List all gyphs with encoding and name'
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('-o','--output',{'help': 'Output text file'}, {'type': 'outfile', 'def': 'Gnames.txt'})]
+
+def doit(args) :
+ outf = args.output
+ for glyph in args.ifont:
+ g = args.ifont[glyph]
+ outf.write('%s: %s, %s\n' % (glyph, g.encoding, g.glyphname))
+ outf.close()
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFlistGlyphinfo.py b/examples/fontforge-old/FFlistGlyphinfo.py
new file mode 100755
index 0000000..883d033
--- /dev/null
+++ b/examples/fontforge-old/FFlistGlyphinfo.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+'FontForge: List all the data in a glyph object in key, value pairs'
+__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)'
+__author__ = 'David Raymond'
+
+import fontforge, types, sys
+from silfont.core import execute
+
+argspec = [
+ ('font',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('-o','--output',{'help': 'Output text file'}, {'type': 'outfile', 'def': 'glyphinfo.txt'})]
+
+
+def doit(args) :
+ font=args.font
+ outf = args.output
+
+ glyphn = raw_input("Glyph name or number: ")
+
+ while glyphn:
+
+ isglyph=True
+ if not(glyphn in font):
+ try:
+ glyphn=int(glyphn)
+ except ValueError:
+ isglyph=False
+ else:
+ if not(glyphn in font):
+ isglyph=False
+
+ if isglyph:
+ g=font[glyphn]
+ outf.write("\n%s\n\n" % glyphn)
+ # Write to file all normal key,value pairs - exclude __ and built in functions
+ for k in dir(g):
+ if k[0:2] == "__": continue
+ attrk=getattr(g,k)
+ if attrk is None: continue
+ tk=type(attrk)
+ if tk == types.BuiltinFunctionType: continue
+ if k == "ttinstrs": # ttinstr values are not printable characters
+ outf.write("%s,%s\n" % (k,"<has values>"))
+ else:
+ outf.write("%s,%s\n" % (k,attrk))
+ # Write out all normal keys where value is none
+ for k in dir(g):
+ attrk=getattr(g,k)
+ if attrk is None:
+ outf.write("%s,%s\n" % (k,attrk))
+ else:
+ print "Invalid glyph"
+
+ glyphn = raw_input("Glyph name or number: ")
+ print "done"
+ outf.close
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFlistRefNum.py b/examples/fontforge-old/FFlistRefNum.py
new file mode 100755
index 0000000..eef8248
--- /dev/null
+++ b/examples/fontforge-old/FFlistRefNum.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+'FontForge: Report Glyph name, Number of references (components)'
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('-o','--output',{'help': 'Output text file'}, {'type': 'outfile', 'def': 'RefNum.txt'})]
+
+def doit(args) :
+ font = args.ifont
+ outf = args.output
+
+ outf.write("# glyphs with number of components\n\n")
+ for glyph in font:
+ gname=font[glyph].glyphname
+ ref = font[glyph].references
+ if ref is None:
+ n=0
+ else:
+ n=len(ref)
+ outf.write("%s %i\n" % (gname,n))
+
+ outf.close()
+
+ print "Done!"
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFnameSearchNReplace.py b/examples/fontforge-old/FFnameSearchNReplace.py
new file mode 100755
index 0000000..d2c6176
--- /dev/null
+++ b/examples/fontforge-old/FFnameSearchNReplace.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+'Search and replace strings in Glyph names. Strings can be regular expressions'
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+import re
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont', 'def': 'new'}),
+ ('search',{'help': 'Expression to search for'}, {}),
+ ('replace',{'help': 'Expression to replace with'}, {}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': 'searchNReplace.log'})]
+
+def doit(args) :
+ font=args.ifont
+ search=args.search
+ replace=args.replace
+ logf = args.log
+
+ changes=False
+ for glyph in font :
+ newname = re.sub(search, replace, glyph)
+ if newname != glyph :
+ font[glyph].glyphname=newname
+ changes=True
+ logf.write('Glyph %s renamed to %s\n' % (glyph,newname))
+ logf.close()
+ if changes :
+ return font
+ else :
+ return
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/FFundblEncode.py b/examples/fontforge-old/FFundblEncode.py
new file mode 100755
index 0000000..3b43c1c
--- /dev/null
+++ b/examples/fontforge-old/FFundblEncode.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+'''FontForge: Re-encode double-encoded glyphs based on double encoding data in a file
+Lines in file should look like: "LtnSmARetrHook",U+F236,U+1D8F'''
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont', 'def': 'new'}),
+ ('-i','--input',{'help': 'Input csv text file'}, {'type': 'infile', 'def': 'DblEnc.txt'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': 'unDblEnc.log'})]
+
+def doit(args) :
+ font=args.ifont
+ inpf = args.input
+ logf = args.log
+# Create dbl_encode list from the input file
+ dbl_encode = {}
+ for line in inpf.readlines():
+ glyphn, pua_usv_str, std_usv_str = line.strip().split(",") # will exception if not 3 elements
+ if glyphn[0] in '"\'' : glyphn = glyphn[1:-1] # slice off quote marks, if present
+ pua_usv, std_usv = int(pua_usv_str[2:], 16), int(std_usv_str[2:], 16)
+ dbl_encode[glyphn] = [std_usv, pua_usv]
+ inpf.close()
+
+ for glyph in sorted(dbl_encode.keys()):
+ logf.write (reincode(font,glyph,dbl_encode[glyph][0]))
+ logf.write (reincode(font,glyph+"Dep",dbl_encode[glyph][1]))
+ logf.close()
+ return font
+
+def reincode(font,glyph,usv):
+ if glyph not in font:
+ return ("Glyph %s not in font\n" % (glyph))
+ g = font[glyph]
+ ousvs=[g.unicode]
+ oalt=g.altuni
+ if oalt != None:
+ for au in oalt:
+ ousvs.append(au[0]) # (may need to check variant flag)
+ g.unicode = usv
+ g.altuni = None
+ return ("encoding for %s changed: %s -> %s\n" % (glyph, ousvs, usv))
+
+def cmd() : execute("FF",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/fontforge-old/demoAddToMenu.py b/examples/fontforge-old/demoAddToMenu.py
new file mode 100755
index 0000000..8012b0a
--- /dev/null
+++ b/examples/fontforge-old/demoAddToMenu.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+'FontForge: Demo script to add menu items to FF tools menu'
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2014 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+import sys, os, fontforge
+sys.path.append(os.path.join(os.environ['HOME'], 'src/pysilfont/scripts'))
+import samples.demoFunctions
+from samples.demoFunctions import functionList, callFunctions
+#from samples.demoCallFunctions import callFunctions
+
+def toolMenuFunction(functionGroup,font) :
+ reload (samples.demoFunctions)
+ callFunctions(functionGroup,font)
+
+funcList=functionList()
+
+for functionGroup in funcList :
+ menuType = funcList[functionGroup][0]
+ fontforge.registerMenuItem(toolMenuFunction,None,functionGroup,menuType,None,functionGroup);
+ print functionGroup, " registered"
+
+''' This script needs to be called from one of the folders that FontForge looks in for scripts to
+run when it is started. With current versions of FontForge, one is Home/.config/fontforge/python.
+You may need to turn on showing hidden files (ctrl-H in Nautilus) before you can see the .config
+folder. Within there create a one-line python script, say call sampledemo.py containing a call
+to this script, eg:
+
+execfile("/home/david/src/pysilfont/scripts/samples/demoAddToMenu.py")
+
+Due to the reload(samples.demoFunctions) line above, changes functions defined in demoFunctions.py
+are dynamic, ie FontForge does not have to be restarted (as would be the case if the functions were
+called directly from the tools menu. Functions can even be added dynamically to the function groups.
+
+If new function groups are defined, FontForge does have to be restarted to add them to the tools menu.
+'''
diff --git a/examples/fontforge-old/demoExecuteScript.py b/examples/fontforge-old/demoExecuteScript.py
new file mode 100755
index 0000000..c058aaf
--- /dev/null
+++ b/examples/fontforge-old/demoExecuteScript.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+'FontForge: Demo code to paste into the "Execute Script" dialog'
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2013 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+import sys, os, fontforge
+sys.path.append(os.path.join(os.environ['HOME'], 'src/pysilfont/scripts'))
+import samples.demoFunctions # Loads demoFunctions.py module from src/pysilfont/scripts/samples
+reload (samples.demoFunctions) # Reload the demo module each time you execute the script to pick up any recent edits
+samples.demoFunctions.callFunctions("Colour Glyphs",fontforge.activeFont())
+
+'''Demo usage:
+Open the "Execute Script" dialog (from the FontForge File menu or press ctrl+.),
+paste just the code section this (from "import..." to "samples...") into there then
+run it (Alt+o) and see how it pops up a dialogue with a choice of 3 functions to run.
+Edit demoFunctions.py and alter one of the functions.
+Execute the script again and see that that the function's behaviour has changed.
+
+Additional functions can be added to demoFunctions.py and, if also defined functionList()
+become availably immdiately.
+
+If you want to see the output from print statements, or use commands like input, (eg
+for degugging purposes) then start FontForge from a terminal window rather than the
+desktop launcher.
+
+When starting from a terminal window, you can also specify the font to use,
+eg $ fontforge /home/david/RFS/GenBasR.sfd''' \ No newline at end of file
diff --git a/examples/fontforge-old/demoFunctions.py b/examples/fontforge-old/demoFunctions.py
new file mode 100755
index 0000000..2aa65a7
--- /dev/null
+++ b/examples/fontforge-old/demoFunctions.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+'FontForge: Sample functions to call from other demo scripts'
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2014 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+import fontforge
+
+def colLtnAGlyphs(font) :
+
+ #print "Toggling colour of glyphs with LtnCapA in their name"
+ for glyph in font:
+ g = font[glyph]
+ if glyph.find('LtnCapA') >= 0:
+ if g.color != 0x00FF00:
+ g.color = 0x00FF00 # Green
+ else :
+ g.color = 0xFFFFFF # White
+ print "LtnCapA glyphs coloured"
+
+def markOverlaps(font) :
+ print "Toggling colour of glyphs where contours overlap"
+ for glyph in font:
+ g = font[glyph]
+ if g.selfIntersects() :
+ if g.color != 0xFF0000:
+ g.color = 0xFF0000 # Red
+ else :
+ g.color = 0xFFFFFF # White
+ print "Glyphs coloured"
+
+def markScaled(font) :
+ print "Toggling colour of glyphs with scaled components"
+ for glyph in font:
+ g = font[glyph]
+ for ref in g.references:
+ transform=ref[1]
+ if transform[0] != 1.0 or transform[3] != 1.0 :
+ if g.color != 0xFF0000:
+ g.color = 0xFF0000 # Red
+ else :
+ g.color = 0xFFFFFF # White
+ print "Glyphs coloured"
+
+def clearColours(font) :
+ for glyph in font :
+ g = font[glyph]
+ g.color = 0xFFFFFF
+
+def functionList() :
+ ''' Returns a dictionary to be used by callFunctions() and demoAddToMenu.py
+ The dictionary is indexed by a group name which could be used as Tools menu
+ entry or to reference the group of functions. For each group there is a tuple
+ consisting of the Tools menu type (Font or Glyph) then one tuple per function.
+ For each function the tuple contains:
+ Function name
+ Label for the individual function in dialog box called from Tools menu
+ Actual function object'''
+ funcList = {
+ "Colour Glyphs":("Font",
+ ("colLtnAGlyphs","Colour Latin A Glyphs",colLtnAGlyphs),
+ ("markOverlaps","Mark Overlaps",markOverlaps),
+ ("markScaled","Mark Scaled",markScaled),
+ ("clearColours","Clear all colours",clearColours)),
+ "Group with single item":("Font",
+ ("clearColours","Clear all colours",clearColours))}
+ return funcList
+
+def callFunctions(functionGroup,font) :
+ funcList=functionList()[functionGroup]
+ i=0
+ for tuple in funcList :
+ if i == 0 :
+ pass # Font/Glyph parameter not relevant here
+ elif i == 1 :
+ functionDescs=[tuple[1]]
+ functions=[tuple[2]]
+ else :
+ functionDescs.append(tuple[1])
+ functions.append(tuple[2])
+ i=i+1
+
+ if i == 2 : # Only one function in the group, so just call the function
+ functions[0](font)
+ else :
+ functionNum=fontforge.ask(functionGroup,"Please choose the function to run",functionDescs)
+ functions[functionNum](font)
+
+ \ No newline at end of file
diff --git a/examples/gdl/__init__.py b/examples/gdl/__init__.py
new file mode 100644
index 0000000..d26492d
--- /dev/null
+++ b/examples/gdl/__init__.py
@@ -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']
diff --git a/examples/gdl/font.py b/examples/gdl/font.py
new file mode 100644
index 0000000..30589f6
--- /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__ = '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')
+
diff --git a/examples/gdl/glyph.py b/examples/gdl/glyph.py
new file mode 100644
index 0000000..0f16af4
--- /dev/null
+++ b/examples/gdl/glyph.py
@@ -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
diff --git a/examples/gdl/makeGdl.py b/examples/gdl/makeGdl.py
new file mode 100755
index 0000000..e3c2720
--- /dev/null
+++ b/examples/gdl/makeGdl.py
@@ -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()
+
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)
+
diff --git a/examples/gdl/psnames.py b/examples/gdl/psnames.py
new file mode 100644
index 0000000..a3831d9
--- /dev/null
+++ b/examples/gdl/psnames.py
@@ -0,0 +1,4506 @@
+#!/usr/bin/env python
+'Glyph name analyser to create GDL names from AGL type names'
+__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
+import traceback # Debug
+
+uniToPsnameMap = {
+ '0020' : 'space',
+ '0021' : 'exclam',
+ '0022' : 'quotedbl',
+ '0023' : 'numbersign',
+ '0024' : 'dollar',
+ '0025' : 'percent',
+ '0026' : 'ampersand',
+ '0027' : 'quotesingle',
+ '0028' : 'parenleft',
+ '0029' : 'parenright',
+ '002A' : 'asterisk',
+ '002B' : 'plus',
+ '002C' : 'comma',
+ '002D' : 'hyphen',
+ '002E' : 'period',
+ '002F' : 'slash',
+ '0030' : 'zero',
+ '0031' : 'one',
+ '0032' : 'two',
+ '0033' : 'three',
+ '0034' : 'four',
+ '0035' : 'five',
+ '0036' : 'six',
+ '0037' : 'seven',
+ '0038' : 'eight',
+ '0039' : 'nine',
+ '003A' : 'colon',
+ '003B' : 'semicolon',
+ '003C' : 'less',
+ '003D' : 'equal',
+ '003E' : 'greater',
+ '003F' : 'question',
+ '0040' : 'at',
+ '0041' : 'A',
+ '0042' : 'B',
+ '0043' : 'C',
+ '0044' : 'D',
+ '0045' : 'E',
+ '0046' : 'F',
+ '0047' : 'G',
+ '0048' : 'H',
+ '0049' : 'I',
+ '004A' : 'J',
+ '004B' : 'K',
+ '004C' : 'L',
+ '004D' : 'M',
+ '004E' : 'N',
+ '004F' : 'O',
+ '0050' : 'P',
+ '0051' : 'Q',
+ '0052' : 'R',
+ '0053' : 'S',
+ '0054' : 'T',
+ '0055' : 'U',
+ '0056' : 'V',
+ '0057' : 'W',
+ '0058' : 'X',
+ '0059' : 'Y',
+ '005A' : 'Z',
+ '005B' : 'bracketleft',
+ '005C' : 'backslash',
+ '005D' : 'bracketright',
+ '005E' : 'asciicircum',
+ '005F' : 'underscore',
+ '0060' : 'grave',
+ '0061' : 'a',
+ '0062' : 'b',
+ '0063' : 'c',
+ '0064' : 'd',
+ '0065' : 'e',
+ '0066' : 'f',
+ '0067' : 'g',
+ '0068' : 'h',
+ '0069' : 'i',
+ '006A' : 'j',
+ '006B' : 'k',
+ '006C' : 'l',
+ '006D' : 'm',
+ '006E' : 'n',
+ '006F' : 'o',
+ '0070' : 'p',
+ '0071' : 'q',
+ '0072' : 'r',
+ '0073' : 's',
+ '0074' : 't',
+ '0075' : 'u',
+ '0076' : 'v',
+ '0077' : 'w',
+ '0078' : 'x',
+ '0079' : 'y',
+ '007A' : 'z',
+ '007B' : 'braceleft',
+ '007C' : 'bar',
+ '007D' : 'braceright',
+ '007E' : 'asciitilde',
+# '00A0' : 'space',
+ '00A1' : 'exclamdown',
+ '00A2' : 'cent',
+ '00A3' : 'sterling',
+ '00A4' : 'currency',
+ '00A5' : 'yen',
+ '00A6' : 'brokenbar',
+ '00A7' : 'section',
+ '00A8' : 'dieresis',
+ '00A9' : 'copyright',
+ '00AA' : 'ordfeminine',
+ '00AB' : 'guillemotleft',
+ '00AC' : 'logicalnot',
+# '00AD' : 'hyphen',
+ '00AE' : 'registered',
+ '00AF' : 'macron',
+ '00B0' : 'degree',
+ '00B1' : 'plusminus',
+ '00B2' : 'twosuperior',
+ '00B3' : 'threesuperior',
+ '00B4' : 'acute',
+ '00B5' : 'mu',
+ '00B6' : 'paragraph',
+ '00B7' : 'periodcentered',
+ '00B8' : 'cedilla',
+ '00B9' : 'onesuperior',
+ '00BA' : 'ordmasculine',
+ '00BB' : 'guillemotright',
+ '00BC' : 'onequarter',
+ '00BD' : 'onehalf',
+ '00BE' : 'threequarters',
+ '00BF' : 'questiondown',
+ '00C0' : 'Agrave',
+ '00C1' : 'Aacute',
+ '00C2' : 'Acircumflex',
+ '00C3' : 'Atilde',
+ '00C4' : 'Adieresis',
+ '00C5' : 'Aring',
+ '00C6' : 'AE',
+ '00C7' : 'Ccedilla',
+ '00C8' : 'Egrave',
+ '00C9' : 'Eacute',
+ '00CA' : 'Ecircumflex',
+ '00CB' : 'Edieresis',
+ '00CC' : 'Igrave',
+ '00CD' : 'Iacute',
+ '00CE' : 'Icircumflex',
+ '00CF' : 'Idieresis',
+ '00D0' : 'Eth',
+ '00D1' : 'Ntilde',
+ '00D2' : 'Ograve',
+ '00D3' : 'Oacute',
+ '00D4' : 'Ocircumflex',
+ '00D5' : 'Otilde',
+ '00D6' : 'Odieresis',
+ '00D7' : 'multiply',
+ '00D8' : 'Oslash',
+ '00D9' : 'Ugrave',
+ '00DA' : 'Uacute',
+ '00DB' : 'Ucircumflex',
+ '00DC' : 'Udieresis',
+ '00DD' : 'Yacute',
+ '00DE' : 'Thorn',
+ '00DF' : 'germandbls',
+ '00E0' : 'agrave',
+ '00E1' : 'aacute',
+ '00E2' : 'acircumflex',
+ '00E3' : 'atilde',
+ '00E4' : 'adieresis',
+ '00E5' : 'aring',
+ '00E6' : 'ae',
+ '00E7' : 'ccedilla',
+ '00E8' : 'egrave',
+ '00E9' : 'eacute',
+ '00EA' : 'ecircumflex',
+ '00EB' : 'edieresis',
+ '00EC' : 'igrave',
+ '00ED' : 'iacute',
+ '00EE' : 'icircumflex',
+ '00EF' : 'idieresis',
+ '00F0' : 'eth',
+ '00F1' : 'ntilde',
+ '00F2' : 'ograve',
+ '00F3' : 'oacute',
+ '00F4' : 'ocircumflex',
+ '00F5' : 'otilde',
+ '00F6' : 'odieresis',
+ '00F7' : 'divide',
+ '00F8' : 'oslash',
+ '00F9' : 'ugrave',
+ '00FA' : 'uacute',
+ '00FB' : 'ucircumflex',
+ '00FC' : 'udieresis',
+ '00FD' : 'yacute',
+ '00FE' : 'thorn',
+ '00FF' : 'ydieresis',
+ '0100' : 'Amacron',
+ '0101' : 'amacron',
+ '0102' : 'Abreve',
+ '0103' : 'abreve',
+ '0104' : 'Aogonek',
+ '0105' : 'aogonek',
+ '0106' : 'Cacute',
+ '0107' : 'cacute',
+ '0108' : 'Ccircumflex',
+ '0109' : 'ccircumflex',
+ '010A' : 'Cdotaccent',
+ '010B' : 'cdotaccent',
+ '010C' : 'Ccaron',
+ '010D' : 'ccaron',
+ '010E' : 'Dcaron',
+ '010F' : 'dcaron',
+ '0110' : 'Dcroat',
+ '0111' : 'dcroat',
+ '0112' : 'Emacron',
+ '0113' : 'emacron',
+ '0114' : 'Ebreve',
+ '0115' : 'ebreve',
+ '0116' : 'Edotaccent',
+ '0117' : 'edotaccent',
+ '0118' : 'Eogonek',
+ '0119' : 'eogonek',
+ '011A' : 'Ecaron',
+ '011B' : 'ecaron',
+ '011C' : 'Gcircumflex',
+ '011D' : 'gcircumflex',
+ '011E' : 'Gbreve',
+ '011F' : 'gbreve',
+ '0120' : 'Gdotaccent',
+ '0121' : 'gdotaccent',
+ '0122' : 'Gcommaaccent',
+ '0123' : 'gcommaaccent',
+ '0124' : 'Hcircumflex',
+ '0125' : 'hcircumflex',
+ '0126' : 'Hbar',
+ '0127' : 'hbar',
+ '0128' : 'Itilde',
+ '0129' : 'itilde',
+ '012A' : 'Imacron',
+ '012B' : 'imacron',
+ '012C' : 'Ibreve',
+ '012D' : 'ibreve',
+ '012E' : 'Iogonek',
+ '012F' : 'iogonek',
+ '0130' : 'Idotaccent',
+ '0131' : 'dotlessi',
+ '0132' : 'IJ',
+ '0133' : 'ij',
+ '0134' : 'Jcircumflex',
+ '0135' : 'jcircumflex',
+ '0136' : 'Kcommaaccent',
+ '0137' : 'kcommaaccent',
+ '0138' : 'kgreenlandic',
+ '0139' : 'Lacute',
+ '013A' : 'lacute',
+ '013B' : 'Lcommaaccent',
+ '013C' : 'lcommaaccent',
+ '013D' : 'Lcaron',
+ '013E' : 'lcaron',
+ '013F' : 'Ldot',
+ '0140' : 'ldot',
+ '0141' : 'Lslash',
+ '0142' : 'lslash',
+ '0143' : 'Nacute',
+ '0144' : 'nacute',
+ '0145' : 'Ncommaaccent',
+ '0146' : 'ncommaaccent',
+ '0147' : 'Ncaron',
+ '0148' : 'ncaron',
+ '0149' : 'napostrophe',
+ '014A' : 'Eng',
+ '014B' : 'eng',
+ '014C' : 'Omacron',
+ '014D' : 'omacron',
+ '014E' : 'Obreve',
+ '014F' : 'obreve',
+ '0150' : 'Ohungarumlaut',
+ '0151' : 'ohungarumlaut',
+ '0152' : 'OE',
+ '0153' : 'oe',
+ '0154' : 'Racute',
+ '0155' : 'racute',
+ '0156' : 'Rcommaaccent',
+ '0157' : 'rcommaaccent',
+ '0158' : 'Rcaron',
+ '0159' : 'rcaron',
+ '015A' : 'Sacute',
+ '015B' : 'sacute',
+ '015C' : 'Scircumflex',
+ '015D' : 'scircumflex',
+ '015E' : 'Scedilla',
+ '015F' : 'scedilla',
+ '0160' : 'Scaron',
+ '0161' : 'scaron',
+ '0162' : 'Tcommaaccent',
+ '0163' : 'tcommaaccent',
+ '0164' : 'Tcaron',
+ '0165' : 'tcaron',
+ '0166' : 'Tbar',
+ '0167' : 'tbar',
+ '0168' : 'Utilde',
+ '0169' : 'utilde',
+ '016A' : 'Umacron',
+ '016B' : 'umacron',
+ '016C' : 'Ubreve',
+ '016D' : 'ubreve',
+ '016E' : 'Uring',
+ '016F' : 'uring',
+ '0170' : 'Uhungarumlaut',
+ '0171' : 'uhungarumlaut',
+ '0172' : 'Uogonek',
+ '0173' : 'uogonek',
+ '0174' : 'Wcircumflex',
+ '0175' : 'wcircumflex',
+ '0176' : 'Ycircumflex',
+ '0177' : 'ycircumflex',
+ '0178' : 'Ydieresis',
+ '0179' : 'Zacute',
+ '017A' : 'zacute',
+ '017B' : 'Zdotaccent',
+ '017C' : 'zdotaccent',
+ '017D' : 'Zcaron',
+ '017E' : 'zcaron',
+ '017F' : 'longs',
+ '0192' : 'florin',
+ '01A0' : 'Ohorn',
+ '01A1' : 'ohorn',
+ '01AF' : 'Uhorn',
+ '01B0' : 'uhorn',
+ '01E6' : 'Gcaron',
+ '01E7' : 'gcaron',
+ '01FA' : 'Aringacute',
+ '01FB' : 'aringacute',
+ '01FC' : 'AEacute',
+ '01FD' : 'aeacute',
+ '01FE' : 'Oslashacute',
+ '01FF' : 'oslashacute',
+ '0218' : 'Scommaaccent',
+ '0219' : 'scommaaccent',
+# '021A' : 'Tcommaaccent',
+# '021B' : 'tcommaaccent',
+ '02BC' : 'afii57929',
+ '02BD' : 'afii64937',
+ '02C6' : 'circumflex',
+ '02C7' : 'caron',
+# '02C9' : 'macron',
+ '02D8' : 'breve',
+ '02D9' : 'dotaccent',
+ '02DA' : 'ring',
+ '02DB' : 'ogonek',
+ '02DC' : 'tilde',
+ '02DD' : 'hungarumlaut',
+ '0300' : 'gravecomb',
+ '0301' : 'acutecomb',
+ '0303' : 'tildecomb',
+ '0309' : 'hookabovecomb',
+ '0323' : 'dotbelowcomb',
+ '0384' : 'tonos',
+ '0385' : 'dieresistonos',
+ '0386' : 'Alphatonos',
+ '0387' : 'anoteleia',
+ '0388' : 'Epsilontonos',
+ '0389' : 'Etatonos',
+ '038A' : 'Iotatonos',
+ '038C' : 'Omicrontonos',
+ '038E' : 'Upsilontonos',
+ '038F' : 'Omegatonos',
+ '0390' : 'iotadieresistonos',
+ '0391' : 'Alpha',
+ '0392' : 'Beta',
+ '0393' : 'Gamma',
+# '0394' : 'Delta',
+ '0395' : 'Epsilon',
+ '0396' : 'Zeta',
+ '0397' : 'Eta',
+ '0398' : 'Theta',
+ '0399' : 'Iota',
+ '039A' : 'Kappa',
+ '039B' : 'Lambda',
+ '039C' : 'Mu',
+ '039D' : 'Nu',
+ '039E' : 'Xi',
+ '039F' : 'Omicron',
+ '03A0' : 'Pi',
+ '03A1' : 'Rho',
+ '03A3' : 'Sigma',
+ '03A4' : 'Tau',
+ '03A5' : 'Upsilon',
+ '03A6' : 'Phi',
+ '03A7' : 'Chi',
+ '03A8' : 'Psi',
+# '03A9' : 'Omega',
+ '03AA' : 'Iotadieresis',
+ '03AB' : 'Upsilondieresis',
+ '03AC' : 'alphatonos',
+ '03AD' : 'epsilontonos',
+ '03AE' : 'etatonos',
+ '03AF' : 'iotatonos',
+ '03B0' : 'upsilondieresistonos',
+ '03B1' : 'alpha',
+ '03B2' : 'beta',
+ '03B3' : 'gamma',
+ '03B4' : 'delta',
+ '03B5' : 'epsilon',
+ '03B6' : 'zeta',
+ '03B7' : 'eta',
+ '03B8' : 'theta',
+ '03B9' : 'iota',
+ '03BA' : 'kappa',
+ '03BB' : 'lambda',
+# '03BC' : 'mu',
+ '03BD' : 'nu',
+ '03BE' : 'xi',
+ '03BF' : 'omicron',
+ '03C0' : 'pi',
+ '03C1' : 'rho',
+ '03C2' : 'sigma1',
+ '03C3' : 'sigma',
+ '03C4' : 'tau',
+ '03C5' : 'upsilon',
+ '03C6' : 'phi',
+ '03C7' : 'chi',
+ '03C8' : 'psi',
+ '03C9' : 'omega',
+ '03CA' : 'iotadieresis',
+ '03CB' : 'upsilondieresis',
+ '03CC' : 'omicrontonos',
+ '03CD' : 'upsilontonos',
+ '03CE' : 'omegatonos',
+ '03D1' : 'theta1',
+ '03D2' : 'Upsilon1',
+ '03D5' : 'phi1',
+ '03D6' : 'omega1',
+ '1E80' : 'Wgrave',
+ '1E81' : 'wgrave',
+ '1E82' : 'Wacute',
+ '1E83' : 'wacute',
+ '1E84' : 'Wdieresis',
+ '1E85' : 'wdieresis',
+ '1EF2' : 'Ygrave',
+ '1EF3' : 'ygrave',
+ '2012' : 'figuredash',
+ '2013' : 'endash',
+ '2014' : 'emdash',
+ '2015' : 'afii00208',
+ '2017' : 'underscoredbl',
+ '2018' : 'quoteleft',
+ '2019' : 'quoteright',
+ '201A' : 'quotesinglbase',
+ '201B' : 'quotereversed',
+ '201C' : 'quotedblleft',
+ '201D' : 'quotedblright',
+ '201E' : 'quotedblbase',
+ '2020' : 'dagger',
+ '2021' : 'daggerdbl',
+ '2022' : 'bullet',
+ '2024' : 'onedotenleader',
+ '2025' : 'twodotenleader',
+ '2026' : 'ellipsis',
+ '202C' : 'afii61573',
+ '202D' : 'afii61574',
+ '202E' : 'afii61575',
+ '2030' : 'perthousand',
+ '2032' : 'minute',
+ '2033' : 'second',
+ '2039' : 'guilsinglleft',
+ '203A' : 'guilsinglright',
+ '203C' : 'exclamdbl',
+ '2044' : 'fraction',
+# '2070' : 'zerosuperior',
+# '2074' : 'foursuperior',
+# '2075' : 'fivesuperior',
+# '2076' : 'sixsuperior',
+# '2077' : 'sevensuperior',
+# '2078' : 'eightsuperior',
+# '2079' : 'ninesuperior',
+# '207D' : 'parenleftsuperior',
+# '207E' : 'parenrightsuperior',
+# '207F' : 'nsuperior',
+# '2080' : 'zeroinferior',
+# '2081' : 'oneinferior',
+# '2082' : 'twoinferior',
+# '2083' : 'threeinferior',
+# '2084' : 'fourinferior',
+# '2085' : 'fiveinferior',
+# '2086' : 'sixinferior',
+# '2087' : 'seveninferior',
+# '2088' : 'eightinferior',
+# '2089' : 'nineinferior',
+# '208D' : 'parenleftinferior',
+# '208E' : 'parenrightinferior',
+ '20A1' : 'colonmonetary',
+ '20A3' : 'franc',
+ '20A4' : 'lira',
+ '20A7' : 'peseta',
+ '20AA' : 'afii57636',
+ '20AB' : 'dong',
+ '20AC' : 'Euro',
+ '2105' : 'afii61248',
+ '2111' : 'Ifraktur',
+ '2113' : 'afii61289',
+ '2116' : 'afii61352',
+ '2118' : 'weierstrass',
+ '211C' : 'Rfraktur',
+ '211E' : 'prescription',
+ '2122' : 'trademark',
+ '2126' : 'Omega',
+ '212E' : 'estimated',
+ '2135' : 'aleph',
+ '2153' : 'onethird',
+ '2154' : 'twothirds',
+ '215B' : 'oneeighth',
+ '215C' : 'threeeighths',
+ '215D' : 'fiveeighths',
+ '215E' : 'seveneighths',
+ '2190' : 'arrowleft',
+ '2191' : 'arrowup',
+ '2192' : 'arrowright',
+ '2193' : 'arrowdown',
+ '2194' : 'arrowboth',
+ '2195' : 'arrowupdn',
+ '21A8' : 'arrowupdnbse',
+ '21B5' : 'carriagereturn',
+ '21D0' : 'arrowdblleft',
+ '21D1' : 'arrowdblup',
+ '21D2' : 'arrowdblright',
+ '21D3' : 'arrowdbldown',
+ '21D4' : 'arrowdblboth',
+ '2200' : 'universal',
+ '2202' : 'partialdiff',
+ '2203' : 'existential',
+ '2205' : 'emptyset',
+ '2206' : 'Delta',
+ '2207' : 'gradient',
+ '2208' : 'element',
+ '2209' : 'notelement',
+ '220B' : 'suchthat',
+ '220F' : 'product',
+ '2211' : 'summation',
+ '2212' : 'minus',
+# '2215' : 'fraction',
+ '2217' : 'asteriskmath',
+# '2219' : 'periodcentered',
+ '221A' : 'radical',
+ '221D' : 'proportional',
+ '221E' : 'infinity',
+ '221F' : 'orthogonal',
+ '2220' : 'angle',
+ '2227' : 'logicaland',
+ '2228' : 'logicalor',
+ '2229' : 'intersection',
+ '222A' : 'union',
+ '222B' : 'integral',
+ '2234' : 'therefore',
+ '223C' : 'similar',
+ '2245' : 'congruent',
+ '2248' : 'approxequal',
+ '2260' : 'notequal',
+ '2261' : 'equivalence',
+ '2264' : 'lessequal',
+ '2265' : 'greaterequal',
+ '2282' : 'propersubset',
+ '2283' : 'propersuperset',
+ '2284' : 'notsubset',
+ '2286' : 'reflexsubset',
+ '2287' : 'reflexsuperset',
+ '2295' : 'circleplus',
+ '2297' : 'circlemultiply',
+ '22A5' : 'perpendicular',
+ '22C5' : 'dotmath',
+ '2302' : 'house',
+ '2310' : 'revlogicalnot',
+ '2320' : 'integraltp',
+ '2321' : 'integralbt',
+ '2329' : 'angleleft',
+ '232A' : 'angleright',
+ '2580' : 'upblock',
+ '2584' : 'dnblock',
+ '2588' : 'block',
+ '258C' : 'lfblock',
+ '2590' : 'rtblock',
+ '2591' : 'ltshade',
+ '2592' : 'shade',
+ '2593' : 'dkshade',
+ '25A0' : 'filledbox',
+ '25A1' : 'H22073',
+ '25AA' : 'H18543',
+ '25AB' : 'H18551',
+ '25AC' : 'filledrect',
+ '25B2' : 'triagup',
+ '25BA' : 'triagrt',
+ '25BC' : 'triagdn',
+ '25C4' : 'triaglf',
+ '25CA' : 'lozenge',
+ '25CB' : 'circle',
+ '25CC' : 'circledash',
+ '25CF' : 'H18533',
+ '25D8' : 'invbullet',
+ '25D9' : 'invcircle',
+ '25E6' : 'openbullet',
+ '263A' : 'smileface',
+ '263B' : 'invsmileface',
+ '263C' : 'sun',
+ '2640' : 'female',
+ '2642' : 'male',
+ '2660' : 'spade',
+ '2663' : 'club',
+ '2665' : 'heart',
+ '2666' : 'diamond',
+ '266A' : 'musicalnote',
+ '266B' : 'musicalnotedbl',
+ 'FB00' : 'ff',
+ 'FB01' : 'fi',
+ 'FB02' : 'fl',
+ 'FB03' : 'ffi',
+ 'FB04' : 'ffl'
+}
+
+uniToAfiinameMap = {
+ '0401' : 'afii10023',
+ '0402' : 'afii10051',
+ '0403' : 'afii10052',
+ '0404' : 'afii10053',
+ '0405' : 'afii10054',
+ '0406' : 'afii10055',
+ '0407' : 'afii10056',
+ '0408' : 'afii10057',
+ '0409' : 'afii10058',
+ '040A' : 'afii10059',
+ '040B' : 'afii10060',
+ '040C' : 'afii10061',
+ '040E' : 'afii10062',
+ '040F' : 'afii10145',
+ '0410' : 'afii10017',
+ '0411' : 'afii10018',
+ '0412' : 'afii10019',
+ '0413' : 'afii10020',
+ '0414' : 'afii10021',
+ '0415' : 'afii10022',
+ '0416' : 'afii10024',
+ '0417' : 'afii10025',
+ '0418' : 'afii10026',
+ '0419' : 'afii10027',
+ '041A' : 'afii10028',
+ '041B' : 'afii10029',
+ '041C' : 'afii10030',
+ '041D' : 'afii10031',
+ '041E' : 'afii10032',
+ '041F' : 'afii10033',
+ '0420' : 'afii10034',
+ '0421' : 'afii10035',
+ '0422' : 'afii10036',
+ '0423' : 'afii10037',
+ '0424' : 'afii10038',
+ '0425' : 'afii10039',
+ '0426' : 'afii10040',
+ '0427' : 'afii10041',
+ '0428' : 'afii10042',
+ '0429' : 'afii10043',
+ '042A' : 'afii10044',
+ '042B' : 'afii10045',
+ '042C' : 'afii10046',
+ '042D' : 'afii10047',
+ '042E' : 'afii10048',
+ '042F' : 'afii10049',
+ '0430' : 'afii10065',
+ '0431' : 'afii10066',
+ '0432' : 'afii10067',
+ '0433' : 'afii10068',
+ '0434' : 'afii10069',
+ '0435' : 'afii10070',
+ '0436' : 'afii10072',
+ '0437' : 'afii10073',
+ '0438' : 'afii10074',
+ '0439' : 'afii10075',
+ '043A' : 'afii10076',
+ '043B' : 'afii10077',
+ '043C' : 'afii10078',
+ '043D' : 'afii10079',
+ '043E' : 'afii10080',
+ '043F' : 'afii10081',
+ '0440' : 'afii10082',
+ '0441' : 'afii10083',
+ '0442' : 'afii10084',
+ '0443' : 'afii10085',
+ '0444' : 'afii10086',
+ '0445' : 'afii10087',
+ '0446' : 'afii10088',
+ '0447' : 'afii10089',
+ '0448' : 'afii10090',
+ '0449' : 'afii10091',
+ '044A' : 'afii10092',
+ '044B' : 'afii10093',
+ '044C' : 'afii10094',
+ '044D' : 'afii10095',
+ '044E' : 'afii10096',
+ '044F' : 'afii10097',
+ '0451' : 'afii10071',
+ '0452' : 'afii10099',
+ '0453' : 'afii10100',
+ '0454' : 'afii10101',
+ '0455' : 'afii10102',
+ '0456' : 'afii10103',
+ '0457' : 'afii10104',
+ '0458' : 'afii10105',
+ '0459' : 'afii10106',
+ '045A' : 'afii10107',
+ '045B' : 'afii10108',
+ '045C' : 'afii10109',
+ '045E' : 'afii10110',
+ '045F' : 'afii10193',
+ '0462' : 'afii10146',
+ '0463' : 'afii10194',
+ '0472' : 'afii10147',
+ '0473' : 'afii10195',
+ '0474' : 'afii10148',
+ '0475' : 'afii10196',
+ '0490' : 'afii10050',
+ '0491' : 'afii10098',
+ '04D9' : 'afii10846',
+ '05B0' : 'afii57799',
+ '05B1' : 'afii57801',
+ '05B2' : 'afii57800',
+ '05B3' : 'afii57802',
+ '05B4' : 'afii57793',
+ '05B5' : 'afii57794',
+ '05B6' : 'afii57795',
+ '05B7' : 'afii57798',
+ '05B8' : 'afii57797',
+ '05B9' : 'afii57806',
+ '05BB' : 'afii57796',
+ '05BC' : 'afii57807',
+ '05BD' : 'afii57839',
+ '05BE' : 'afii57645',
+ '05BF' : 'afii57841',
+ '05C0' : 'afii57842',
+ '05C1' : 'afii57804',
+ '05C2' : 'afii57803',
+ '05C3' : 'afii57658',
+ '05D0' : 'afii57664',
+ '05D1' : 'afii57665',
+ '05D2' : 'afii57666',
+ '05D3' : 'afii57667',
+ '05D4' : 'afii57668',
+ '05D5' : 'afii57669',
+ '05D6' : 'afii57670',
+ '05D7' : 'afii57671',
+ '05D8' : 'afii57672',
+ '05D9' : 'afii57673',
+ '05DA' : 'afii57674',
+ '05DB' : 'afii57675',
+ '05DC' : 'afii57676',
+ '05DD' : 'afii57677',
+ '05DE' : 'afii57678',
+ '05DF' : 'afii57679',
+ '05E0' : 'afii57680',
+ '05E1' : 'afii57681',
+ '05E2' : 'afii57682',
+ '05E3' : 'afii57683',
+ '05E4' : 'afii57684',
+ '05E5' : 'afii57685',
+ '05E6' : 'afii57686',
+ '05E7' : 'afii57687',
+ '05E8' : 'afii57688',
+ '05E9' : 'afii57689',
+ '05EA' : 'afii57690',
+ '05F0' : 'afii57716',
+ '05F1' : 'afii57717',
+ '05F2' : 'afii57718',
+ '060C' : 'afii57388',
+ '061B' : 'afii57403',
+ '061F' : 'afii57407',
+ '0621' : 'afii57409',
+ '0622' : 'afii57410',
+ '0623' : 'afii57411',
+ '0624' : 'afii57412',
+ '0625' : 'afii57413',
+ '0626' : 'afii57414',
+ '0627' : 'afii57415',
+ '0628' : 'afii57416',
+ '0629' : 'afii57417',
+ '062A' : 'afii57418',
+ '062B' : 'afii57419',
+ '062C' : 'afii57420',
+ '062D' : 'afii57421',
+ '062E' : 'afii57422',
+ '062F' : 'afii57423',
+ '0630' : 'afii57424',
+ '0631' : 'afii57425',
+ '0632' : 'afii57426',
+ '0633' : 'afii57427',
+ '0634' : 'afii57428',
+ '0635' : 'afii57429',
+ '0636' : 'afii57430',
+ '0637' : 'afii57431',
+ '0638' : 'afii57432',
+ '0639' : 'afii57433',
+ '063A' : 'afii57434',
+ '0640' : 'afii57440',
+ '0641' : 'afii57441',
+ '0642' : 'afii57442',
+ '0643' : 'afii57443',
+ '0644' : 'afii57444',
+ '0645' : 'afii57445',
+ '0646' : 'afii57446',
+ '0647' : 'afii57470',
+ '0648' : 'afii57448',
+ '0649' : 'afii57449',
+ '064A' : 'afii57450',
+ '064B' : 'afii57451',
+ '064C' : 'afii57452',
+ '064D' : 'afii57453',
+ '064E' : 'afii57454',
+ '064F' : 'afii57455',
+ '0650' : 'afii57456',
+ '0651' : 'afii57457',
+ '0652' : 'afii57458',
+ '0660' : 'afii57392',
+ '0661' : 'afii57393',
+ '0662' : 'afii57394',
+ '0663' : 'afii57395',
+ '0664' : 'afii57396',
+ '0665' : 'afii57397',
+ '0666' : 'afii57398',
+ '0667' : 'afii57399',
+ '0668' : 'afii57400',
+ '0669' : 'afii57401',
+ '066A' : 'afii57381',
+ '066D' : 'afii63167',
+ '0679' : 'afii57511',
+ '067E' : 'afii57506',
+ '0686' : 'afii57507',
+ '0688' : 'afii57512',
+ '0691' : 'afii57513',
+ '0698' : 'afii57508',
+ '06A4' : 'afii57505',
+ '06AF' : 'afii57509',
+ '06BA' : 'afii57514',
+ '06D2' : 'afii57519',
+ '06D5' : 'afii57534',
+ '2500' : 'SF100000',
+ '2502' : 'SF110000',
+ '250C' : 'SF010000',
+ '2510' : 'SF030000',
+ '2514' : 'SF020000',
+ '2518' : 'SF040000',
+ '251C' : 'SF080000',
+ '2524' : 'SF090000',
+ '252C' : 'SF060000',
+ '2534' : 'SF070000',
+ '253C' : 'SF050000',
+ '2550' : 'SF430000',
+ '2551' : 'SF240000',
+ '2552' : 'SF510000',
+ '2553' : 'SF520000',
+ '2554' : 'SF390000',
+ '2555' : 'SF220000',
+ '2556' : 'SF210000',
+ '2557' : 'SF250000',
+ '2558' : 'SF500000',
+ '2559' : 'SF490000',
+ '255A' : 'SF380000',
+ '255B' : 'SF280000',
+ '255C' : 'SF270000',
+ '255D' : 'SF260000',
+ '255E' : 'SF360000',
+ '255F' : 'SF370000',
+ '2560' : 'SF420000',
+ '2561' : 'SF190000',
+ '2562' : 'SF200000',
+ '2563' : 'SF230000',
+ '2564' : 'SF470000',
+ '2565' : 'SF480000',
+ '2566' : 'SF410000',
+ '2567' : 'SF450000',
+ '2568' : 'SF460000',
+ '2569' : 'SF400000',
+ '256A' : 'SF540000',
+ '256B' : 'SF530000',
+ '256C' : 'SF440000',
+ 'FB1F' : 'afii57705',
+ 'FB2A' : 'afii57694',
+ 'FB2B' : 'afii57695',
+ 'FB35' : 'afii57723',
+ 'FB4B' : 'afii57700'
+}
+
+
+# Adobe Glyph List 2.0 (sans those in glyph list for *new* fonts) -- thus
+# these are all historic names that could occur in fonts
+# from https://partners.adobe.com/asn/tech/type/glyphlist.txt
+
+aglToUniMap = {
+ 'AEmacron' : u"\u01E2",
+ 'AEsmall' : u"\uF7E6",
+ 'Aacutesmall' : u"\uF7E1",
+ 'Abreveacute' : u"\u1EAE",
+ 'Abrevecyrillic' : u"\u04D0",
+ 'Abrevedotbelow' : u"\u1EB6",
+ 'Abrevegrave' : u"\u1EB0",
+ 'Abrevehookabove' : u"\u1EB2",
+ 'Abrevetilde' : u"\u1EB4",
+ 'Acaron' : u"\u01CD",
+ 'Acircle' : u"\u24B6",
+ 'Acircumflexacute' : u"\u1EA4",
+ 'Acircumflexdotbelow' : u"\u1EAC",
+ 'Acircumflexgrave' : u"\u1EA6",
+ 'Acircumflexhookabove' : u"\u1EA8",
+ 'Acircumflexsmall' : u"\uF7E2",
+ 'Acircumflextilde' : u"\u1EAA",
+ 'Acute' : u"\uF6C9",
+ 'Acutesmall' : u"\uF7B4",
+ 'Acyrillic' : u"\u0410",
+ 'Adblgrave' : u"\u0200",
+ 'Adieresiscyrillic' : u"\u04D2",
+ 'Adieresismacron' : u"\u01DE",
+ 'Adieresissmall' : u"\uF7E4",
+ 'Adotbelow' : u"\u1EA0",
+ 'Adotmacron' : u"\u01E0",
+ 'Agravesmall' : u"\uF7E0",
+ 'Ahookabove' : u"\u1EA2",
+ 'Aiecyrillic' : u"\u04D4",
+ 'Ainvertedbreve' : u"\u0202",
+ 'Amonospace' : u"\uFF21",
+ 'Aringbelow' : u"\u1E00",
+ 'Aringsmall' : u"\uF7E5",
+ 'Asmall' : u"\uF761",
+ 'Atildesmall' : u"\uF7E3",
+ 'Aybarmenian' : u"\u0531",
+ 'Bcircle' : u"\u24B7",
+ 'Bdotaccent' : u"\u1E02",
+ 'Bdotbelow' : u"\u1E04",
+ 'Becyrillic' : u"\u0411",
+ 'Benarmenian' : u"\u0532",
+ 'Bhook' : u"\u0181",
+ 'Blinebelow' : u"\u1E06",
+ 'Bmonospace' : u"\uFF22",
+ 'Brevesmall' : u"\uF6F4",
+ 'Bsmall' : u"\uF762",
+ 'Btopbar' : u"\u0182",
+ 'Caarmenian' : u"\u053E",
+ 'Caron' : u"\uF6CA",
+ 'Caronsmall' : u"\uF6F5",
+ 'Ccedillaacute' : u"\u1E08",
+ 'Ccedillasmall' : u"\uF7E7",
+ 'Ccircle' : u"\u24B8",
+ 'Cdot' : u"\u010A",
+ 'Cedillasmall' : u"\uF7B8",
+ 'Chaarmenian' : u"\u0549",
+ 'Cheabkhasiancyrillic' : u"\u04BC",
+ 'Checyrillic' : u"\u0427",
+ 'Chedescenderabkhasiancyrillic' : u"\u04BE",
+ 'Chedescendercyrillic' : u"\u04B6",
+ 'Chedieresiscyrillic' : u"\u04F4",
+ 'Cheharmenian' : u"\u0543",
+ 'Chekhakassiancyrillic' : u"\u04CB",
+ 'Cheverticalstrokecyrillic' : u"\u04B8",
+ 'Chook' : u"\u0187",
+ 'Circumflexsmall' : u"\uF6F6",
+ 'Cmonospace' : u"\uFF23",
+ 'Coarmenian' : u"\u0551",
+ 'Csmall' : u"\uF763",
+ 'DZ' : u"\u01F1",
+ 'DZcaron' : u"\u01C4",
+ 'Daarmenian' : u"\u0534",
+ 'Dafrican' : u"\u0189",
+ 'Dcedilla' : u"\u1E10",
+ 'Dcircle' : u"\u24B9",
+ 'Dcircumflexbelow' : u"\u1E12",
+ 'Ddotaccent' : u"\u1E0A",
+ 'Ddotbelow' : u"\u1E0C",
+ 'Decyrillic' : u"\u0414",
+ 'Deicoptic' : u"\u03EE",
+ 'Deltagreek' : u"\u0394",
+ 'Dhook' : u"\u018A",
+ 'Dieresis' : u"\uF6CB",
+ 'DieresisAcute' : u"\uF6CC",
+ 'DieresisGrave' : u"\uF6CD",
+ 'Dieresissmall' : u"\uF7A8",
+ 'Digammagreek' : u"\u03DC",
+ 'Djecyrillic' : u"\u0402",
+ 'Dlinebelow' : u"\u1E0E",
+ 'Dmonospace' : u"\uFF24",
+ 'Dotaccentsmall' : u"\uF6F7",
+ 'Dslash' : u"\u0110",
+ 'Dsmall' : u"\uF764",
+ 'Dtopbar' : u"\u018B",
+ 'Dz' : u"\u01F2",
+ 'Dzcaron' : u"\u01C5",
+ 'Dzeabkhasiancyrillic' : u"\u04E0",
+ 'Dzecyrillic' : u"\u0405",
+ 'Dzhecyrillic' : u"\u040F",
+ 'Eacutesmall' : u"\uF7E9",
+ 'Ecedillabreve' : u"\u1E1C",
+ 'Echarmenian' : u"\u0535",
+ 'Ecircle' : u"\u24BA",
+ 'Ecircumflexacute' : u"\u1EBE",
+ 'Ecircumflexbelow' : u"\u1E18",
+ 'Ecircumflexdotbelow' : u"\u1EC6",
+ 'Ecircumflexgrave' : u"\u1EC0",
+ 'Ecircumflexhookabove' : u"\u1EC2",
+ 'Ecircumflexsmall' : u"\uF7EA",
+ 'Ecircumflextilde' : u"\u1EC4",
+ 'Ecyrillic' : u"\u0404",
+ 'Edblgrave' : u"\u0204",
+ 'Edieresissmall' : u"\uF7EB",
+ 'Edot' : u"\u0116",
+ 'Edotbelow' : u"\u1EB8",
+ 'Efcyrillic' : u"\u0424",
+ 'Egravesmall' : u"\uF7E8",
+ 'Eharmenian' : u"\u0537",
+ 'Ehookabove' : u"\u1EBA",
+ 'Eightroman' : u"\u2167",
+ 'Einvertedbreve' : u"\u0206",
+ 'Eiotifiedcyrillic' : u"\u0464",
+ 'Elcyrillic' : u"\u041B",
+ 'Elevenroman' : u"\u216A",
+ 'Emacronacute' : u"\u1E16",
+ 'Emacrongrave' : u"\u1E14",
+ 'Emcyrillic' : u"\u041C",
+ 'Emonospace' : u"\uFF25",
+ 'Encyrillic' : u"\u041D",
+ 'Endescendercyrillic' : u"\u04A2",
+ 'Enghecyrillic' : u"\u04A4",
+ 'Enhookcyrillic' : u"\u04C7",
+ 'Eopen' : u"\u0190",
+ 'Ercyrillic' : u"\u0420",
+ 'Ereversed' : u"\u018E",
+ 'Ereversedcyrillic' : u"\u042D",
+ 'Escyrillic' : u"\u0421",
+ 'Esdescendercyrillic' : u"\u04AA",
+ 'Esh' : u"\u01A9",
+ 'Esmall' : u"\uF765",
+ 'Etarmenian' : u"\u0538",
+ 'Ethsmall' : u"\uF7F0",
+ 'Etilde' : u"\u1EBC",
+ 'Etildebelow' : u"\u1E1A",
+ 'Ezh' : u"\u01B7",
+ 'Ezhcaron' : u"\u01EE",
+ 'Ezhreversed' : u"\u01B8",
+ 'Fcircle' : u"\u24BB",
+ 'Fdotaccent' : u"\u1E1E",
+ 'Feharmenian' : u"\u0556",
+ 'Feicoptic' : u"\u03E4",
+ 'Fhook' : u"\u0191",
+ 'Fitacyrillic' : u"\u0472",
+ 'Fiveroman' : u"\u2164",
+ 'Fmonospace' : u"\uFF26",
+ 'Fourroman' : u"\u2163",
+ 'Fsmall' : u"\uF766",
+ 'GBsquare' : u"\u3387",
+ 'Gacute' : u"\u01F4",
+ 'Gammaafrican' : u"\u0194",
+ 'Gangiacoptic' : u"\u03EA",
+ 'Gcedilla' : u"\u0122",
+ 'Gcircle' : u"\u24BC",
+ 'Gdot' : u"\u0120",
+ 'Gecyrillic' : u"\u0413",
+ 'Ghadarmenian' : u"\u0542",
+ 'Ghemiddlehookcyrillic' : u"\u0494",
+ 'Ghestrokecyrillic' : u"\u0492",
+ 'Gheupturncyrillic' : u"\u0490",
+ 'Ghook' : u"\u0193",
+ 'Gimarmenian' : u"\u0533",
+ 'Gjecyrillic' : u"\u0403",
+ 'Gmacron' : u"\u1E20",
+ 'Gmonospace' : u"\uFF27",
+ 'Grave' : u"\uF6CE",
+ 'Gravesmall' : u"\uF760",
+ 'Gsmall' : u"\uF767",
+ 'Gsmallhook' : u"\u029B",
+ 'Gstroke' : u"\u01E4",
+ 'HPsquare' : u"\u33CB",
+ 'Haabkhasiancyrillic' : u"\u04A8",
+ 'Hadescendercyrillic' : u"\u04B2",
+ 'Hardsigncyrillic' : u"\u042A",
+ 'Hbrevebelow' : u"\u1E2A",
+ 'Hcedilla' : u"\u1E28",
+ 'Hcircle' : u"\u24BD",
+ 'Hdieresis' : u"\u1E26",
+ 'Hdotaccent' : u"\u1E22",
+ 'Hdotbelow' : u"\u1E24",
+ 'Hmonospace' : u"\uFF28",
+ 'Hoarmenian' : u"\u0540",
+ 'Horicoptic' : u"\u03E8",
+ 'Hsmall' : u"\uF768",
+ 'Hungarumlaut' : u"\uF6CF",
+ 'Hungarumlautsmall' : u"\uF6F8",
+ 'Hzsquare' : u"\u3390",
+ 'IAcyrillic' : u"\u042F",
+ 'IUcyrillic' : u"\u042E",
+ 'Iacutesmall' : u"\uF7ED",
+ 'Icaron' : u"\u01CF",
+ 'Icircle' : u"\u24BE",
+ 'Icircumflexsmall' : u"\uF7EE",
+ 'Icyrillic' : u"\u0406",
+ 'Idblgrave' : u"\u0208",
+ 'Idieresisacute' : u"\u1E2E",
+ 'Idieresiscyrillic' : u"\u04E4",
+ 'Idieresissmall' : u"\uF7EF",
+ 'Idot' : u"\u0130",
+ 'Idotbelow' : u"\u1ECA",
+ 'Iebrevecyrillic' : u"\u04D6",
+ 'Iecyrillic' : u"\u0415",
+ 'Igravesmall' : u"\uF7EC",
+ 'Ihookabove' : u"\u1EC8",
+ 'Iicyrillic' : u"\u0418",
+ 'Iinvertedbreve' : u"\u020A",
+ 'Iishortcyrillic' : u"\u0419",
+ 'Imacroncyrillic' : u"\u04E2",
+ 'Imonospace' : u"\uFF29",
+ 'Iniarmenian' : u"\u053B",
+ 'Iocyrillic' : u"\u0401",
+ 'Iotaafrican' : u"\u0196",
+ 'Ismall' : u"\uF769",
+ 'Istroke' : u"\u0197",
+ 'Itildebelow' : u"\u1E2C",
+ 'Izhitsacyrillic' : u"\u0474",
+ 'Izhitsadblgravecyrillic' : u"\u0476",
+ 'Jaarmenian' : u"\u0541",
+ 'Jcircle' : u"\u24BF",
+ 'Jecyrillic' : u"\u0408",
+ 'Jheharmenian' : u"\u054B",
+ 'Jmonospace' : u"\uFF2A",
+ 'Jsmall' : u"\uF76A",
+ 'KBsquare' : u"\u3385",
+ 'KKsquare' : u"\u33CD",
+ 'Kabashkircyrillic' : u"\u04A0",
+ 'Kacute' : u"\u1E30",
+ 'Kacyrillic' : u"\u041A",
+ 'Kadescendercyrillic' : u"\u049A",
+ 'Kahookcyrillic' : u"\u04C3",
+ 'Kastrokecyrillic' : u"\u049E",
+ 'Kaverticalstrokecyrillic' : u"\u049C",
+ 'Kcaron' : u"\u01E8",
+ 'Kcedilla' : u"\u0136",
+ 'Kcircle' : u"\u24C0",
+ 'Kdotbelow' : u"\u1E32",
+ 'Keharmenian' : u"\u0554",
+ 'Kenarmenian' : u"\u053F",
+ 'Khacyrillic' : u"\u0425",
+ 'Kheicoptic' : u"\u03E6",
+ 'Khook' : u"\u0198",
+ 'Kjecyrillic' : u"\u040C",
+ 'Klinebelow' : u"\u1E34",
+ 'Kmonospace' : u"\uFF2B",
+ 'Koppacyrillic' : u"\u0480",
+ 'Koppagreek' : u"\u03DE",
+ 'Ksicyrillic' : u"\u046E",
+ 'Ksmall' : u"\uF76B",
+ 'LJ' : u"\u01C7",
+ 'LL' : u"\uF6BF",
+ 'Lcedilla' : u"\u013B",
+ 'Lcircle' : u"\u24C1",
+ 'Lcircumflexbelow' : u"\u1E3C",
+ 'Ldotaccent' : u"\u013F",
+ 'Ldotbelow' : u"\u1E36",
+ 'Ldotbelowmacron' : u"\u1E38",
+ 'Liwnarmenian' : u"\u053C",
+ 'Lj' : u"\u01C8",
+ 'Ljecyrillic' : u"\u0409",
+ 'Llinebelow' : u"\u1E3A",
+ 'Lmonospace' : u"\uFF2C",
+ 'Lslashsmall' : u"\uF6F9",
+ 'Lsmall' : u"\uF76C",
+ 'MBsquare' : u"\u3386",
+ 'Macron' : u"\uF6D0",
+ 'Macronsmall' : u"\uF7AF",
+ 'Macute' : u"\u1E3E",
+ 'Mcircle' : u"\u24C2",
+ 'Mdotaccent' : u"\u1E40",
+ 'Mdotbelow' : u"\u1E42",
+ 'Menarmenian' : u"\u0544",
+ 'Mmonospace' : u"\uFF2D",
+ 'Msmall' : u"\uF76D",
+ 'Mturned' : u"\u019C",
+ 'NJ' : u"\u01CA",
+ 'Ncedilla' : u"\u0145",
+ 'Ncircle' : u"\u24C3",
+ 'Ncircumflexbelow' : u"\u1E4A",
+ 'Ndotaccent' : u"\u1E44",
+ 'Ndotbelow' : u"\u1E46",
+ 'Nhookleft' : u"\u019D",
+ 'Nineroman' : u"\u2168",
+ 'Nj' : u"\u01CB",
+ 'Njecyrillic' : u"\u040A",
+ 'Nlinebelow' : u"\u1E48",
+ 'Nmonospace' : u"\uFF2E",
+ 'Nowarmenian' : u"\u0546",
+ 'Nsmall' : u"\uF76E",
+ 'Ntildesmall' : u"\uF7F1",
+ 'OEsmall' : u"\uF6FA",
+ 'Oacutesmall' : u"\uF7F3",
+ 'Obarredcyrillic' : u"\u04E8",
+ 'Obarreddieresiscyrillic' : u"\u04EA",
+ 'Ocaron' : u"\u01D1",
+ 'Ocenteredtilde' : u"\u019F",
+ 'Ocircle' : u"\u24C4",
+ 'Ocircumflexacute' : u"\u1ED0",
+ 'Ocircumflexdotbelow' : u"\u1ED8",
+ 'Ocircumflexgrave' : u"\u1ED2",
+ 'Ocircumflexhookabove' : u"\u1ED4",
+ 'Ocircumflexsmall' : u"\uF7F4",
+ 'Ocircumflextilde' : u"\u1ED6",
+ 'Ocyrillic' : u"\u041E",
+ 'Odblacute' : u"\u0150",
+ 'Odblgrave' : u"\u020C",
+ 'Odieresiscyrillic' : u"\u04E6",
+ 'Odieresissmall' : u"\uF7F6",
+ 'Odotbelow' : u"\u1ECC",
+ 'Ogoneksmall' : u"\uF6FB",
+ 'Ogravesmall' : u"\uF7F2",
+ 'Oharmenian' : u"\u0555",
+ 'Ohm' : u"\u2126",
+ 'Ohookabove' : u"\u1ECE",
+ 'Ohornacute' : u"\u1EDA",
+ 'Ohorndotbelow' : u"\u1EE2",
+ 'Ohorngrave' : u"\u1EDC",
+ 'Ohornhookabove' : u"\u1EDE",
+ 'Ohorntilde' : u"\u1EE0",
+ 'Oi' : u"\u01A2",
+ 'Oinvertedbreve' : u"\u020E",
+ 'Omacronacute' : u"\u1E52",
+ 'Omacrongrave' : u"\u1E50",
+ 'Omegacyrillic' : u"\u0460",
+ 'Omegagreek' : u"\u03A9",
+ 'Omegaroundcyrillic' : u"\u047A",
+ 'Omegatitlocyrillic' : u"\u047C",
+ 'Omonospace' : u"\uFF2F",
+ 'Oneroman' : u"\u2160",
+ 'Oogonek' : u"\u01EA",
+ 'Oogonekmacron' : u"\u01EC",
+ 'Oopen' : u"\u0186",
+ 'Oslashsmall' : u"\uF7F8",
+ 'Osmall' : u"\uF76F",
+ 'Ostrokeacute' : u"\u01FE",
+ 'Otcyrillic' : u"\u047E",
+ 'Otildeacute' : u"\u1E4C",
+ 'Otildedieresis' : u"\u1E4E",
+ 'Otildesmall' : u"\uF7F5",
+ 'Pacute' : u"\u1E54",
+ 'Pcircle' : u"\u24C5",
+ 'Pdotaccent' : u"\u1E56",
+ 'Pecyrillic' : u"\u041F",
+ 'Peharmenian' : u"\u054A",
+ 'Pemiddlehookcyrillic' : u"\u04A6",
+ 'Phook' : u"\u01A4",
+ 'Piwrarmenian' : u"\u0553",
+ 'Pmonospace' : u"\uFF30",
+ 'Psicyrillic' : u"\u0470",
+ 'Psmall' : u"\uF770",
+ 'Qcircle' : u"\u24C6",
+ 'Qmonospace' : u"\uFF31",
+ 'Qsmall' : u"\uF771",
+ 'Raarmenian' : u"\u054C",
+ 'Rcedilla' : u"\u0156",
+ 'Rcircle' : u"\u24C7",
+ 'Rdblgrave' : u"\u0210",
+ 'Rdotaccent' : u"\u1E58",
+ 'Rdotbelow' : u"\u1E5A",
+ 'Rdotbelowmacron' : u"\u1E5C",
+ 'Reharmenian' : u"\u0550",
+ 'Ringsmall' : u"\uF6FC",
+ 'Rinvertedbreve' : u"\u0212",
+ 'Rlinebelow' : u"\u1E5E",
+ 'Rmonospace' : u"\uFF32",
+ 'Rsmall' : u"\uF772",
+ 'Rsmallinverted' : u"\u0281",
+ 'Rsmallinvertedsuperior' : u"\u02B6",
+ 'Sacutedotaccent' : u"\u1E64",
+ 'Sampigreek' : u"\u03E0",
+ 'Scarondotaccent' : u"\u1E66",
+ 'Scaronsmall' : u"\uF6FD",
+ 'Schwa' : u"\u018F",
+ 'Schwacyrillic' : u"\u04D8",
+ 'Schwadieresiscyrillic' : u"\u04DA",
+ 'Scircle' : u"\u24C8",
+ 'Sdotaccent' : u"\u1E60",
+ 'Sdotbelow' : u"\u1E62",
+ 'Sdotbelowdotaccent' : u"\u1E68",
+ 'Seharmenian' : u"\u054D",
+ 'Sevenroman' : u"\u2166",
+ 'Shaarmenian' : u"\u0547",
+ 'Shacyrillic' : u"\u0428",
+ 'Shchacyrillic' : u"\u0429",
+ 'Sheicoptic' : u"\u03E2",
+ 'Shhacyrillic' : u"\u04BA",
+ 'Shimacoptic' : u"\u03EC",
+ 'Sixroman' : u"\u2165",
+ 'Smonospace' : u"\uFF33",
+ 'Softsigncyrillic' : u"\u042C",
+ 'Ssmall' : u"\uF773",
+ 'Stigmagreek' : u"\u03DA",
+ 'Tcedilla' : u"\u0162",
+ 'Tcircle' : u"\u24C9",
+ 'Tcircumflexbelow' : u"\u1E70",
+ 'Tdotaccent' : u"\u1E6A",
+ 'Tdotbelow' : u"\u1E6C",
+ 'Tecyrillic' : u"\u0422",
+ 'Tedescendercyrillic' : u"\u04AC",
+ 'Tenroman' : u"\u2169",
+ 'Tetsecyrillic' : u"\u04B4",
+ 'Thook' : u"\u01AC",
+ 'Thornsmall' : u"\uF7FE",
+ 'Threeroman' : u"\u2162",
+ 'Tildesmall' : u"\uF6FE",
+ 'Tiwnarmenian' : u"\u054F",
+ 'Tlinebelow' : u"\u1E6E",
+ 'Tmonospace' : u"\uFF34",
+ 'Toarmenian' : u"\u0539",
+ 'Tonefive' : u"\u01BC",
+ 'Tonesix' : u"\u0184",
+ 'Tonetwo' : u"\u01A7",
+ 'Tretroflexhook' : u"\u01AE",
+ 'Tsecyrillic' : u"\u0426",
+ 'Tshecyrillic' : u"\u040B",
+ 'Tsmall' : u"\uF774",
+ 'Twelveroman' : u"\u216B",
+ 'Tworoman' : u"\u2161",
+ 'Uacutesmall' : u"\uF7FA",
+ 'Ucaron' : u"\u01D3",
+ 'Ucircle' : u"\u24CA",
+ 'Ucircumflexbelow' : u"\u1E76",
+ 'Ucircumflexsmall' : u"\uF7FB",
+ 'Ucyrillic' : u"\u0423",
+ 'Udblacute' : u"\u0170",
+ 'Udblgrave' : u"\u0214",
+ 'Udieresisacute' : u"\u01D7",
+ 'Udieresisbelow' : u"\u1E72",
+ 'Udieresiscaron' : u"\u01D9",
+ 'Udieresiscyrillic' : u"\u04F0",
+ 'Udieresisgrave' : u"\u01DB",
+ 'Udieresismacron' : u"\u01D5",
+ 'Udieresissmall' : u"\uF7FC",
+ 'Udotbelow' : u"\u1EE4",
+ 'Ugravesmall' : u"\uF7F9",
+ 'Uhookabove' : u"\u1EE6",
+ 'Uhornacute' : u"\u1EE8",
+ 'Uhorndotbelow' : u"\u1EF0",
+ 'Uhorngrave' : u"\u1EEA",
+ 'Uhornhookabove' : u"\u1EEC",
+ 'Uhorntilde' : u"\u1EEE",
+ 'Uhungarumlautcyrillic' : u"\u04F2",
+ 'Uinvertedbreve' : u"\u0216",
+ 'Ukcyrillic' : u"\u0478",
+ 'Umacroncyrillic' : u"\u04EE",
+ 'Umacrondieresis' : u"\u1E7A",
+ 'Umonospace' : u"\uFF35",
+ 'Upsilonacutehooksymbolgreek' : u"\u03D3",
+ 'Upsilonafrican' : u"\u01B1",
+ 'Upsilondieresishooksymbolgreek' : u"\u03D4",
+ 'Upsilonhooksymbol' : u"\u03D2",
+ 'Ushortcyrillic' : u"\u040E",
+ 'Usmall' : u"\uF775",
+ 'Ustraightcyrillic' : u"\u04AE",
+ 'Ustraightstrokecyrillic' : u"\u04B0",
+ 'Utildeacute' : u"\u1E78",
+ 'Utildebelow' : u"\u1E74",
+ 'Vcircle' : u"\u24CB",
+ 'Vdotbelow' : u"\u1E7E",
+ 'Vecyrillic' : u"\u0412",
+ 'Vewarmenian' : u"\u054E",
+ 'Vhook' : u"\u01B2",
+ 'Vmonospace' : u"\uFF36",
+ 'Voarmenian' : u"\u0548",
+ 'Vsmall' : u"\uF776",
+ 'Vtilde' : u"\u1E7C",
+ 'Wcircle' : u"\u24CC",
+ 'Wdotaccent' : u"\u1E86",
+ 'Wdotbelow' : u"\u1E88",
+ 'Wmonospace' : u"\uFF37",
+ 'Wsmall' : u"\uF777",
+ 'Xcircle' : u"\u24CD",
+ 'Xdieresis' : u"\u1E8C",
+ 'Xdotaccent' : u"\u1E8A",
+ 'Xeharmenian' : u"\u053D",
+ 'Xmonospace' : u"\uFF38",
+ 'Xsmall' : u"\uF778",
+ 'Yacutesmall' : u"\uF7FD",
+ 'Yatcyrillic' : u"\u0462",
+ 'Ycircle' : u"\u24CE",
+ 'Ydieresissmall' : u"\uF7FF",
+ 'Ydotaccent' : u"\u1E8E",
+ 'Ydotbelow' : u"\u1EF4",
+ 'Yericyrillic' : u"\u042B",
+ 'Yerudieresiscyrillic' : u"\u04F8",
+ 'Yhook' : u"\u01B3",
+ 'Yhookabove' : u"\u1EF6",
+ 'Yiarmenian' : u"\u0545",
+ 'Yicyrillic' : u"\u0407",
+ 'Yiwnarmenian' : u"\u0552",
+ 'Ymonospace' : u"\uFF39",
+ 'Ysmall' : u"\uF779",
+ 'Ytilde' : u"\u1EF8",
+ 'Yusbigcyrillic' : u"\u046A",
+ 'Yusbigiotifiedcyrillic' : u"\u046C",
+ 'Yuslittlecyrillic' : u"\u0466",
+ 'Yuslittleiotifiedcyrillic' : u"\u0468",
+ 'Zaarmenian' : u"\u0536",
+ 'Zcaronsmall' : u"\uF6FF",
+ 'Zcircle' : u"\u24CF",
+ 'Zcircumflex' : u"\u1E90",
+ 'Zdot' : u"\u017B",
+ 'Zdotbelow' : u"\u1E92",
+ 'Zecyrillic' : u"\u0417",
+ 'Zedescendercyrillic' : u"\u0498",
+ 'Zedieresiscyrillic' : u"\u04DE",
+ 'Zhearmenian' : u"\u053A",
+ 'Zhebrevecyrillic' : u"\u04C1",
+ 'Zhecyrillic' : u"\u0416",
+ 'Zhedescendercyrillic' : u"\u0496",
+ 'Zhedieresiscyrillic' : u"\u04DC",
+ 'Zlinebelow' : u"\u1E94",
+ 'Zmonospace' : u"\uFF3A",
+ 'Zsmall' : u"\uF77A",
+ 'Zstroke' : u"\u01B5",
+ 'aabengali' : u"\u0986",
+ 'aadeva' : u"\u0906",
+ 'aagujarati' : u"\u0A86",
+ 'aagurmukhi' : u"\u0A06",
+ 'aamatragurmukhi' : u"\u0A3E",
+ 'aarusquare' : u"\u3303",
+ 'aavowelsignbengali' : u"\u09BE",
+ 'aavowelsigndeva' : u"\u093E",
+ 'aavowelsigngujarati' : u"\u0ABE",
+ 'abbreviationmarkarmenian' : u"\u055F",
+ 'abbreviationsigndeva' : u"\u0970",
+ 'abengali' : u"\u0985",
+ 'abopomofo' : u"\u311A",
+ 'abreveacute' : u"\u1EAF",
+ 'abrevecyrillic' : u"\u04D1",
+ 'abrevedotbelow' : u"\u1EB7",
+ 'abrevegrave' : u"\u1EB1",
+ 'abrevehookabove' : u"\u1EB3",
+ 'abrevetilde' : u"\u1EB5",
+ 'acaron' : u"\u01CE",
+ 'acircle' : u"\u24D0",
+ 'acircumflexacute' : u"\u1EA5",
+ 'acircumflexdotbelow' : u"\u1EAD",
+ 'acircumflexgrave' : u"\u1EA7",
+ 'acircumflexhookabove' : u"\u1EA9",
+ 'acircumflextilde' : u"\u1EAB",
+ 'acutebelowcmb' : u"\u0317",
+ 'acutecmb' : u"\u0301",
+ 'acutedeva' : u"\u0954",
+ 'acutelowmod' : u"\u02CF",
+ 'acutetonecmb' : u"\u0341",
+ 'acyrillic' : u"\u0430",
+ 'adblgrave' : u"\u0201",
+ 'addakgurmukhi' : u"\u0A71",
+ 'adeva' : u"\u0905",
+ 'adieresiscyrillic' : u"\u04D3",
+ 'adieresismacron' : u"\u01DF",
+ 'adotbelow' : u"\u1EA1",
+ 'adotmacron' : u"\u01E1",
+ 'aekorean' : u"\u3150",
+ 'aemacron' : u"\u01E3",
+ 'afii08941' : u"\u20A4",
+ 'afii10063' : u"\uF6C4",
+ 'afii10064' : u"\uF6C5",
+ 'afii10192' : u"\uF6C6",
+ 'afii10831' : u"\uF6C7",
+ 'afii10832' : u"\uF6C8",
+ 'agujarati' : u"\u0A85",
+ 'agurmukhi' : u"\u0A05",
+ 'ahiragana' : u"\u3042",
+ 'ahookabove' : u"\u1EA3",
+ 'aibengali' : u"\u0990",
+ 'aibopomofo' : u"\u311E",
+ 'aideva' : u"\u0910",
+ 'aiecyrillic' : u"\u04D5",
+ 'aigujarati' : u"\u0A90",
+ 'aigurmukhi' : u"\u0A10",
+ 'aimatragurmukhi' : u"\u0A48",
+ 'ainarabic' : u"\u0639",
+ 'ainfinalarabic' : u"\uFECA",
+ 'aininitialarabic' : u"\uFECB",
+ 'ainmedialarabic' : u"\uFECC",
+ 'ainvertedbreve' : u"\u0203",
+ 'aivowelsignbengali' : u"\u09C8",
+ 'aivowelsigndeva' : u"\u0948",
+ 'aivowelsigngujarati' : u"\u0AC8",
+ 'akatakana' : u"\u30A2",
+ 'akatakanahalfwidth' : u"\uFF71",
+ 'akorean' : u"\u314F",
+ 'alef' : u"\u05D0",
+ 'alefarabic' : u"\u0627",
+ 'alefdageshhebrew' : u"\uFB30",
+ 'aleffinalarabic' : u"\uFE8E",
+ 'alefhamzaabovearabic' : u"\u0623",
+ 'alefhamzaabovefinalarabic' : u"\uFE84",
+ 'alefhamzabelowarabic' : u"\u0625",
+ 'alefhamzabelowfinalarabic' : u"\uFE88",
+ 'alefhebrew' : u"\u05D0",
+ 'aleflamedhebrew' : u"\uFB4F",
+ 'alefmaddaabovearabic' : u"\u0622",
+ 'alefmaddaabovefinalarabic' : u"\uFE82",
+ 'alefmaksuraarabic' : u"\u0649",
+ 'alefmaksurafinalarabic' : u"\uFEF0",
+ 'alefmaksurainitialarabic' : u"\uFEF3",
+ 'alefmaksuramedialarabic' : u"\uFEF4",
+ 'alefpatahhebrew' : u"\uFB2E",
+ 'alefqamatshebrew' : u"\uFB2F",
+ 'allequal' : u"\u224C",
+ 'amonospace' : u"\uFF41",
+ 'ampersandmonospace' : u"\uFF06",
+ 'ampersandsmall' : u"\uF726",
+ 'amsquare' : u"\u33C2",
+ 'anbopomofo' : u"\u3122",
+ 'angbopomofo' : u"\u3124",
+ 'angkhankhuthai' : u"\u0E5A",
+ 'anglebracketleft' : u"\u3008",
+ 'anglebracketleftvertical' : u"\uFE3F",
+ 'anglebracketright' : u"\u3009",
+ 'anglebracketrightvertical' : u"\uFE40",
+ 'angstrom' : u"\u212B",
+ 'anudattadeva' : u"\u0952",
+ 'anusvarabengali' : u"\u0982",
+ 'anusvaradeva' : u"\u0902",
+ 'anusvaragujarati' : u"\u0A82",
+ 'apaatosquare' : u"\u3300",
+ 'aparen' : u"\u249C",
+ 'apostrophearmenian' : u"\u055A",
+ 'apostrophemod' : u"\u02BC",
+ 'apple' : u"\uF8FF",
+ 'approaches' : u"\u2250",
+ 'approxequalorimage' : u"\u2252",
+ 'approximatelyequal' : u"\u2245",
+ 'araeaekorean' : u"\u318E",
+ 'araeakorean' : u"\u318D",
+ 'arc' : u"\u2312",
+ 'arighthalfring' : u"\u1E9A",
+ 'aringbelow' : u"\u1E01",
+ 'arrowdashdown' : u"\u21E3",
+ 'arrowdashleft' : u"\u21E0",
+ 'arrowdashright' : u"\u21E2",
+ 'arrowdashup' : u"\u21E1",
+ 'arrowdownleft' : u"\u2199",
+ 'arrowdownright' : u"\u2198",
+ 'arrowdownwhite' : u"\u21E9",
+ 'arrowheaddownmod' : u"\u02C5",
+ 'arrowheadleftmod' : u"\u02C2",
+ 'arrowheadrightmod' : u"\u02C3",
+ 'arrowheadupmod' : u"\u02C4",
+ 'arrowhorizex' : u"\uF8E7",
+ 'arrowleftdbl' : u"\u21D0",
+ 'arrowleftdblstroke' : u"\u21CD",
+ 'arrowleftoverright' : u"\u21C6",
+ 'arrowleftwhite' : u"\u21E6",
+ 'arrowrightdblstroke' : u"\u21CF",
+ 'arrowrightheavy' : u"\u279E",
+ 'arrowrightoverleft' : u"\u21C4",
+ 'arrowrightwhite' : u"\u21E8",
+ 'arrowtableft' : u"\u21E4",
+ 'arrowtabright' : u"\u21E5",
+ 'arrowupdownbase' : u"\u21A8",
+ 'arrowupleft' : u"\u2196",
+ 'arrowupleftofdown' : u"\u21C5",
+ 'arrowupright' : u"\u2197",
+ 'arrowupwhite' : u"\u21E7",
+ 'arrowvertex' : u"\uF8E6",
+ 'asciicircummonospace' : u"\uFF3E",
+ 'asciitildemonospace' : u"\uFF5E",
+ 'ascript' : u"\u0251",
+ 'ascriptturned' : u"\u0252",
+ 'asmallhiragana' : u"\u3041",
+ 'asmallkatakana' : u"\u30A1",
+ 'asmallkatakanahalfwidth' : u"\uFF67",
+ 'asteriskaltonearabic' : u"\u066D",
+ 'asteriskarabic' : u"\u066D",
+ 'asteriskmonospace' : u"\uFF0A",
+ 'asterisksmall' : u"\uFE61",
+ 'asterism' : u"\u2042",
+ 'asuperior' : u"\uF6E9",
+ 'asymptoticallyequal' : u"\u2243",
+ 'atmonospace' : u"\uFF20",
+ 'atsmall' : u"\uFE6B",
+ 'aturned' : u"\u0250",
+ 'aubengali' : u"\u0994",
+ 'aubopomofo' : u"\u3120",
+ 'audeva' : u"\u0914",
+ 'augujarati' : u"\u0A94",
+ 'augurmukhi' : u"\u0A14",
+ 'aulengthmarkbengali' : u"\u09D7",
+ 'aumatragurmukhi' : u"\u0A4C",
+ 'auvowelsignbengali' : u"\u09CC",
+ 'auvowelsigndeva' : u"\u094C",
+ 'auvowelsigngujarati' : u"\u0ACC",
+ 'avagrahadeva' : u"\u093D",
+ 'aybarmenian' : u"\u0561",
+ 'ayin' : u"\u05E2",
+ 'ayinaltonehebrew' : u"\uFB20",
+ 'ayinhebrew' : u"\u05E2",
+ 'babengali' : u"\u09AC",
+ 'backslashmonospace' : u"\uFF3C",
+ 'badeva' : u"\u092C",
+ 'bagujarati' : u"\u0AAC",
+ 'bagurmukhi' : u"\u0A2C",
+ 'bahiragana' : u"\u3070",
+ 'bahtthai' : u"\u0E3F",
+ 'bakatakana' : u"\u30D0",
+ 'barmonospace' : u"\uFF5C",
+ 'bbopomofo' : u"\u3105",
+ 'bcircle' : u"\u24D1",
+ 'bdotaccent' : u"\u1E03",
+ 'bdotbelow' : u"\u1E05",
+ 'beamedsixteenthnotes' : u"\u266C",
+ 'because' : u"\u2235",
+ 'becyrillic' : u"\u0431",
+ 'beharabic' : u"\u0628",
+ 'behfinalarabic' : u"\uFE90",
+ 'behinitialarabic' : u"\uFE91",
+ 'behiragana' : u"\u3079",
+ 'behmedialarabic' : u"\uFE92",
+ 'behmeeminitialarabic' : u"\uFC9F",
+ 'behmeemisolatedarabic' : u"\uFC08",
+ 'behnoonfinalarabic' : u"\uFC6D",
+ 'bekatakana' : u"\u30D9",
+ 'benarmenian' : u"\u0562",
+ 'bet' : u"\u05D1",
+ 'betasymbolgreek' : u"\u03D0",
+ 'betdagesh' : u"\uFB31",
+ 'betdageshhebrew' : u"\uFB31",
+ 'bethebrew' : u"\u05D1",
+ 'betrafehebrew' : u"\uFB4C",
+ 'bhabengali' : u"\u09AD",
+ 'bhadeva' : u"\u092D",
+ 'bhagujarati' : u"\u0AAD",
+ 'bhagurmukhi' : u"\u0A2D",
+ 'bhook' : u"\u0253",
+ 'bihiragana' : u"\u3073",
+ 'bikatakana' : u"\u30D3",
+ 'bilabialclick' : u"\u0298",
+ 'bindigurmukhi' : u"\u0A02",
+ 'birusquare' : u"\u3331",
+ 'blackcircle' : u"\u25CF",
+ 'blackdiamond' : u"\u25C6",
+ 'blackdownpointingtriangle' : u"\u25BC",
+ 'blackleftpointingpointer' : u"\u25C4",
+ 'blackleftpointingtriangle' : u"\u25C0",
+ 'blacklenticularbracketleft' : u"\u3010",
+ 'blacklenticularbracketleftvertical' : u"\uFE3B",
+ 'blacklenticularbracketright' : u"\u3011",
+ 'blacklenticularbracketrightvertical' : u"\uFE3C",
+ 'blacklowerlefttriangle' : u"\u25E3",
+ 'blacklowerrighttriangle' : u"\u25E2",
+ 'blackrectangle' : u"\u25AC",
+ 'blackrightpointingpointer' : u"\u25BA",
+ 'blackrightpointingtriangle' : u"\u25B6",
+ 'blacksmallsquare' : u"\u25AA",
+ 'blacksmilingface' : u"\u263B",
+ 'blacksquare' : u"\u25A0",
+ 'blackstar' : u"\u2605",
+ 'blackupperlefttriangle' : u"\u25E4",
+ 'blackupperrighttriangle' : u"\u25E5",
+ 'blackuppointingsmalltriangle' : u"\u25B4",
+ 'blackuppointingtriangle' : u"\u25B2",
+ 'blank' : u"\u2423",
+ 'blinebelow' : u"\u1E07",
+ 'bmonospace' : u"\uFF42",
+ 'bobaimaithai' : u"\u0E1A",
+ 'bohiragana' : u"\u307C",
+ 'bokatakana' : u"\u30DC",
+ 'bparen' : u"\u249D",
+ 'bqsquare' : u"\u33C3",
+ 'braceex' : u"\uF8F4",
+ 'braceleftbt' : u"\uF8F3",
+ 'braceleftmid' : u"\uF8F2",
+ 'braceleftmonospace' : u"\uFF5B",
+ 'braceleftsmall' : u"\uFE5B",
+ 'bracelefttp' : u"\uF8F1",
+ 'braceleftvertical' : u"\uFE37",
+ 'bracerightbt' : u"\uF8FE",
+ 'bracerightmid' : u"\uF8FD",
+ 'bracerightmonospace' : u"\uFF5D",
+ 'bracerightsmall' : u"\uFE5C",
+ 'bracerighttp' : u"\uF8FC",
+ 'bracerightvertical' : u"\uFE38",
+ 'bracketleftbt' : u"\uF8F0",
+ 'bracketleftex' : u"\uF8EF",
+ 'bracketleftmonospace' : u"\uFF3B",
+ 'bracketlefttp' : u"\uF8EE",
+ 'bracketrightbt' : u"\uF8FB",
+ 'bracketrightex' : u"\uF8FA",
+ 'bracketrightmonospace' : u"\uFF3D",
+ 'bracketrighttp' : u"\uF8F9",
+ 'brevebelowcmb' : u"\u032E",
+ 'brevecmb' : u"\u0306",
+ 'breveinvertedbelowcmb' : u"\u032F",
+ 'breveinvertedcmb' : u"\u0311",
+ 'breveinverteddoublecmb' : u"\u0361",
+ 'bridgebelowcmb' : u"\u032A",
+ 'bridgeinvertedbelowcmb' : u"\u033A",
+ 'bstroke' : u"\u0180",
+ 'bsuperior' : u"\uF6EA",
+ 'btopbar' : u"\u0183",
+ 'buhiragana' : u"\u3076",
+ 'bukatakana' : u"\u30D6",
+ 'bulletinverse' : u"\u25D8",
+ 'bulletoperator' : u"\u2219",
+ 'bullseye' : u"\u25CE",
+ 'caarmenian' : u"\u056E",
+ 'cabengali' : u"\u099A",
+ 'cadeva' : u"\u091A",
+ 'cagujarati' : u"\u0A9A",
+ 'cagurmukhi' : u"\u0A1A",
+ 'calsquare' : u"\u3388",
+ 'candrabindubengali' : u"\u0981",
+ 'candrabinducmb' : u"\u0310",
+ 'candrabindudeva' : u"\u0901",
+ 'candrabindugujarati' : u"\u0A81",
+ 'capslock' : u"\u21EA",
+ 'careof' : u"\u2105",
+ 'caronbelowcmb' : u"\u032C",
+ 'caroncmb' : u"\u030C",
+ 'cbopomofo' : u"\u3118",
+ 'ccedillaacute' : u"\u1E09",
+ 'ccircle' : u"\u24D2",
+ 'ccurl' : u"\u0255",
+ 'cdot' : u"\u010B",
+ 'cdsquare' : u"\u33C5",
+ 'cedillacmb' : u"\u0327",
+ 'centigrade' : u"\u2103",
+ 'centinferior' : u"\uF6DF",
+ 'centmonospace' : u"\uFFE0",
+ 'centoldstyle' : u"\uF7A2",
+ 'centsuperior' : u"\uF6E0",
+ 'chaarmenian' : u"\u0579",
+ 'chabengali' : u"\u099B",
+ 'chadeva' : u"\u091B",
+ 'chagujarati' : u"\u0A9B",
+ 'chagurmukhi' : u"\u0A1B",
+ 'chbopomofo' : u"\u3114",
+ 'cheabkhasiancyrillic' : u"\u04BD",
+ 'checkmark' : u"\u2713",
+ 'checyrillic' : u"\u0447",
+ 'chedescenderabkhasiancyrillic' : u"\u04BF",
+ 'chedescendercyrillic' : u"\u04B7",
+ 'chedieresiscyrillic' : u"\u04F5",
+ 'cheharmenian' : u"\u0573",
+ 'chekhakassiancyrillic' : u"\u04CC",
+ 'cheverticalstrokecyrillic' : u"\u04B9",
+ 'chieuchacirclekorean' : u"\u3277",
+ 'chieuchaparenkorean' : u"\u3217",
+ 'chieuchcirclekorean' : u"\u3269",
+ 'chieuchkorean' : u"\u314A",
+ 'chieuchparenkorean' : u"\u3209",
+ 'chochangthai' : u"\u0E0A",
+ 'chochanthai' : u"\u0E08",
+ 'chochingthai' : u"\u0E09",
+ 'chochoethai' : u"\u0E0C",
+ 'chook' : u"\u0188",
+ 'cieucacirclekorean' : u"\u3276",
+ 'cieucaparenkorean' : u"\u3216",
+ 'cieuccirclekorean' : u"\u3268",
+ 'cieuckorean' : u"\u3148",
+ 'cieucparenkorean' : u"\u3208",
+ 'cieucuparenkorean' : u"\u321C",
+ 'circledash' : u"\u25CC",
+ 'circleot' : u"\u2299", # Actual Adobe glyph list entry -- identified as typo, May 2008
+ 'circledot' : u"\u2299", # What it should have been
+ 'circlepostalmark' : u"\u3036",
+ 'circlewithlefthalfblack' : u"\u25D0",
+ 'circlewithrighthalfblack' : u"\u25D1",
+ 'circumflexbelowcmb' : u"\u032D",
+ 'circumflexcmb' : u"\u0302",
+ 'clear' : u"\u2327",
+ 'clickalveolar' : u"\u01C2",
+ 'clickdental' : u"\u01C0",
+ 'clicklateral' : u"\u01C1",
+ 'clickretroflex' : u"\u01C3",
+ 'clubsuitblack' : u"\u2663",
+ 'clubsuitwhite' : u"\u2667",
+ 'cmcubedsquare' : u"\u33A4",
+ 'cmonospace' : u"\uFF43",
+ 'cmsquaredsquare' : u"\u33A0",
+ 'coarmenian' : u"\u0581",
+ 'colonmonospace' : u"\uFF1A",
+ 'colonsign' : u"\u20A1",
+ 'colonsmall' : u"\uFE55",
+ 'colontriangularhalfmod' : u"\u02D1",
+ 'colontriangularmod' : u"\u02D0",
+ 'commaabovecmb' : u"\u0313",
+ 'commaaboverightcmb' : u"\u0315",
+ 'commaaccent' : u"\uF6C3",
+ 'commaarabic' : u"\u060C",
+ 'commaarmenian' : u"\u055D",
+ 'commainferior' : u"\uF6E1",
+ 'commamonospace' : u"\uFF0C",
+ 'commareversedabovecmb' : u"\u0314",
+ 'commareversedmod' : u"\u02BD",
+ 'commasmall' : u"\uFE50",
+ 'commasuperior' : u"\uF6E2",
+ 'commaturnedabovecmb' : u"\u0312",
+ 'commaturnedmod' : u"\u02BB",
+ 'compass' : u"\u263C",
+ 'contourintegral' : u"\u222E",
+ 'control' : u"\u2303",
+ 'controlACK' : u"\u0006",
+ 'controlBEL' : u"\u0007",
+ 'controlBS' : u"\u0008",
+ 'controlCAN' : u"\u0018",
+ 'controlCR' : u"\u000D",
+ 'controlDC1' : u"\u0011",
+ 'controlDC2' : u"\u0012",
+ 'controlDC3' : u"\u0013",
+ 'controlDC4' : u"\u0014",
+ 'controlDEL' : u"\u007F",
+ 'controlDLE' : u"\u0010",
+ 'controlEM' : u"\u0019",
+ 'controlENQ' : u"\u0005",
+ 'controlEOT' : u"\u0004",
+ 'controlESC' : u"\u001B",
+ 'controlETB' : u"\u0017",
+ 'controlETX' : u"\u0003",
+ 'controlFF' : u"\u000C",
+ 'controlFS' : u"\u001C",
+ 'controlGS' : u"\u001D",
+ 'controlHT' : u"\u0009",
+ 'controlLF' : u"\u000A",
+ 'controlNAK' : u"\u0015",
+ 'controlRS' : u"\u001E",
+ 'controlSI' : u"\u000F",
+ 'controlSO' : u"\u000E",
+ 'controlSOT' : u"\u0002",
+ 'controlSTX' : u"\u0001",
+ 'controlSUB' : u"\u001A",
+ 'controlSYN' : u"\u0016",
+ 'controlUS' : u"\u001F",
+ 'controlVT' : u"\u000B",
+ 'copyrightsans' : u"\uF8E9",
+ 'copyrightserif' : u"\uF6D9",
+ 'cornerbracketleft' : u"\u300C",
+ 'cornerbracketlefthalfwidth' : u"\uFF62",
+ 'cornerbracketleftvertical' : u"\uFE41",
+ 'cornerbracketright' : u"\u300D",
+ 'cornerbracketrighthalfwidth' : u"\uFF63",
+ 'cornerbracketrightvertical' : u"\uFE42",
+ 'corporationsquare' : u"\u337F",
+ 'cosquare' : u"\u33C7",
+ 'coverkgsquare' : u"\u33C6",
+ 'cparen' : u"\u249E",
+ 'cruzeiro' : u"\u20A2",
+ 'cstretched' : u"\u0297",
+ 'curlyand' : u"\u22CF",
+ 'curlyor' : u"\u22CE",
+ 'cyrBreve' : u"\uF6D1",
+ 'cyrFlex' : u"\uF6D2",
+ 'cyrbreve' : u"\uF6D4",
+ 'cyrflex' : u"\uF6D5",
+ 'daarmenian' : u"\u0564",
+ 'dabengali' : u"\u09A6",
+ 'dadarabic' : u"\u0636",
+ 'dadeva' : u"\u0926",
+ 'dadfinalarabic' : u"\uFEBE",
+ 'dadinitialarabic' : u"\uFEBF",
+ 'dadmedialarabic' : u"\uFEC0",
+ 'dagesh' : u"\u05BC",
+ 'dageshhebrew' : u"\u05BC",
+ 'dagujarati' : u"\u0AA6",
+ 'dagurmukhi' : u"\u0A26",
+ 'dahiragana' : u"\u3060",
+ 'dakatakana' : u"\u30C0",
+ 'dalarabic' : u"\u062F",
+ 'dalet' : u"\u05D3",
+ 'daletdagesh' : u"\uFB33",
+ 'daletdageshhebrew' : u"\uFB33",
+ 'dalethatafpatah' : u"\u05D3\u05B2",
+ 'dalethatafpatahhebrew' : u"\u05D3\u05B2",
+ 'dalethatafsegol' : u"\u05D3\u05B1",
+ 'dalethatafsegolhebrew' : u"\u05D3\u05B1",
+ 'dalethebrew' : u"\u05D3",
+ 'dalethiriq' : u"\u05D3\u05B4",
+ 'dalethiriqhebrew' : u"\u05D3\u05B4",
+ 'daletholam' : u"\u05D3\u05B9",
+ 'daletholamhebrew' : u"\u05D3\u05B9",
+ 'daletpatah' : u"\u05D3\u05B7",
+ 'daletpatahhebrew' : u"\u05D3\u05B7",
+ 'daletqamats' : u"\u05D3\u05B8",
+ 'daletqamatshebrew' : u"\u05D3\u05B8",
+ 'daletqubuts' : u"\u05D3\u05BB",
+ 'daletqubutshebrew' : u"\u05D3\u05BB",
+ 'daletsegol' : u"\u05D3\u05B6",
+ 'daletsegolhebrew' : u"\u05D3\u05B6",
+ 'daletsheva' : u"\u05D3\u05B0",
+ 'daletshevahebrew' : u"\u05D3\u05B0",
+ 'dalettsere' : u"\u05D3\u05B5",
+ 'dalettserehebrew' : u"\u05D3\u05B5",
+ 'dalfinalarabic' : u"\uFEAA",
+ 'dammaarabic' : u"\u064F",
+ 'dammalowarabic' : u"\u064F",
+ 'dammatanaltonearabic' : u"\u064C",
+ 'dammatanarabic' : u"\u064C",
+ 'danda' : u"\u0964",
+ 'dargahebrew' : u"\u05A7",
+ 'dargalefthebrew' : u"\u05A7",
+ 'dasiapneumatacyrilliccmb' : u"\u0485",
+ 'dblGrave' : u"\uF6D3",
+ 'dblanglebracketleft' : u"\u300A",
+ 'dblanglebracketleftvertical' : u"\uFE3D",
+ 'dblanglebracketright' : u"\u300B",
+ 'dblanglebracketrightvertical' : u"\uFE3E",
+ 'dblarchinvertedbelowcmb' : u"\u032B",
+ 'dblarrowleft' : u"\u21D4",
+ 'dblarrowright' : u"\u21D2",
+ 'dbldanda' : u"\u0965",
+ 'dblgrave' : u"\uF6D6",
+ 'dblgravecmb' : u"\u030F",
+ 'dblintegral' : u"\u222C",
+ 'dbllowline' : u"\u2017",
+ 'dbllowlinecmb' : u"\u0333",
+ 'dbloverlinecmb' : u"\u033F",
+ 'dblprimemod' : u"\u02BA",
+ 'dblverticalbar' : u"\u2016",
+ 'dblverticallineabovecmb' : u"\u030E",
+ 'dbopomofo' : u"\u3109",
+ 'dbsquare' : u"\u33C8",
+ 'dcedilla' : u"\u1E11",
+ 'dcircle' : u"\u24D3",
+ 'dcircumflexbelow' : u"\u1E13",
+ 'ddabengali' : u"\u09A1",
+ 'ddadeva' : u"\u0921",
+ 'ddagujarati' : u"\u0AA1",
+ 'ddagurmukhi' : u"\u0A21",
+ 'ddalarabic' : u"\u0688",
+ 'ddalfinalarabic' : u"\uFB89",
+ 'dddhadeva' : u"\u095C",
+ 'ddhabengali' : u"\u09A2",
+ 'ddhadeva' : u"\u0922",
+ 'ddhagujarati' : u"\u0AA2",
+ 'ddhagurmukhi' : u"\u0A22",
+ 'ddotaccent' : u"\u1E0B",
+ 'ddotbelow' : u"\u1E0D",
+ 'decimalseparatorarabic' : u"\u066B",
+ 'decimalseparatorpersian' : u"\u066B",
+ 'decyrillic' : u"\u0434",
+ 'dehihebrew' : u"\u05AD",
+ 'dehiragana' : u"\u3067",
+ 'deicoptic' : u"\u03EF",
+ 'dekatakana' : u"\u30C7",
+ 'deleteleft' : u"\u232B",
+ 'deleteright' : u"\u2326",
+ 'deltaturned' : u"\u018D",
+ 'denominatorminusonenumeratorbengali' : u"\u09F8",
+ 'dezh' : u"\u02A4",
+ 'dhabengali' : u"\u09A7",
+ 'dhadeva' : u"\u0927",
+ 'dhagujarati' : u"\u0AA7",
+ 'dhagurmukhi' : u"\u0A27",
+ 'dhook' : u"\u0257",
+ 'dialytikatonos' : u"\u0385",
+ 'dialytikatonoscmb' : u"\u0344",
+ 'diamondsuitwhite' : u"\u2662",
+ 'dieresisacute' : u"\uF6D7",
+ 'dieresisbelowcmb' : u"\u0324",
+ 'dieresiscmb' : u"\u0308",
+ 'dieresisgrave' : u"\uF6D8",
+ 'dihiragana' : u"\u3062",
+ 'dikatakana' : u"\u30C2",
+ 'dittomark' : u"\u3003",
+ 'divides' : u"\u2223",
+ 'divisionslash' : u"\u2215",
+ 'djecyrillic' : u"\u0452",
+ 'dlinebelow' : u"\u1E0F",
+ 'dlsquare' : u"\u3397",
+ 'dmacron' : u"\u0111",
+ 'dmonospace' : u"\uFF44",
+ 'dochadathai' : u"\u0E0E",
+ 'dodekthai' : u"\u0E14",
+ 'dohiragana' : u"\u3069",
+ 'dokatakana' : u"\u30C9",
+ 'dollarinferior' : u"\uF6E3",
+ 'dollarmonospace' : u"\uFF04",
+ 'dollaroldstyle' : u"\uF724",
+ 'dollarsmall' : u"\uFE69",
+ 'dollarsuperior' : u"\uF6E4",
+ 'dorusquare' : u"\u3326",
+ 'dotaccentcmb' : u"\u0307",
+ 'dotbelowcmb' : u"\u0323",
+ 'dotkatakana' : u"\u30FB",
+ 'dotlessj' : u"\uF6BE",
+ 'dotlessjstrokehook' : u"\u0284",
+ 'dottedcircle' : u"\u25CC",
+ 'doubleyodpatah' : u"\uFB1F",
+ 'doubleyodpatahhebrew' : u"\uFB1F",
+ 'downtackbelowcmb' : u"\u031E",
+ 'downtackmod' : u"\u02D5",
+ 'dparen' : u"\u249F",
+ 'dsuperior' : u"\uF6EB",
+ 'dtail' : u"\u0256",
+ 'dtopbar' : u"\u018C",
+ 'duhiragana' : u"\u3065",
+ 'dukatakana' : u"\u30C5",
+ 'dz' : u"\u01F3",
+ 'dzaltone' : u"\u02A3",
+ 'dzcaron' : u"\u01C6",
+ 'dzcurl' : u"\u02A5",
+ 'dzeabkhasiancyrillic' : u"\u04E1",
+ 'dzecyrillic' : u"\u0455",
+ 'dzhecyrillic' : u"\u045F",
+ 'earth' : u"\u2641",
+ 'ebengali' : u"\u098F",
+ 'ebopomofo' : u"\u311C",
+ 'ecandradeva' : u"\u090D",
+ 'ecandragujarati' : u"\u0A8D",
+ 'ecandravowelsigndeva' : u"\u0945",
+ 'ecandravowelsigngujarati' : u"\u0AC5",
+ 'ecedillabreve' : u"\u1E1D",
+ 'echarmenian' : u"\u0565",
+ 'echyiwnarmenian' : u"\u0587",
+ 'ecircle' : u"\u24D4",
+ 'ecircumflexacute' : u"\u1EBF",
+ 'ecircumflexbelow' : u"\u1E19",
+ 'ecircumflexdotbelow' : u"\u1EC7",
+ 'ecircumflexgrave' : u"\u1EC1",
+ 'ecircumflexhookabove' : u"\u1EC3",
+ 'ecircumflextilde' : u"\u1EC5",
+ 'ecyrillic' : u"\u0454",
+ 'edblgrave' : u"\u0205",
+ 'edeva' : u"\u090F",
+ 'edot' : u"\u0117",
+ 'edotbelow' : u"\u1EB9",
+ 'eegurmukhi' : u"\u0A0F",
+ 'eematragurmukhi' : u"\u0A47",
+ 'efcyrillic' : u"\u0444",
+ 'egujarati' : u"\u0A8F",
+ 'eharmenian' : u"\u0567",
+ 'ehbopomofo' : u"\u311D",
+ 'ehiragana' : u"\u3048",
+ 'ehookabove' : u"\u1EBB",
+ 'eibopomofo' : u"\u311F",
+ 'eightarabic' : u"\u0668",
+ 'eightbengali' : u"\u09EE",
+ 'eightcircle' : u"\u2467",
+ 'eightcircleinversesansserif' : u"\u2791",
+ 'eightdeva' : u"\u096E",
+ 'eighteencircle' : u"\u2471",
+ 'eighteenparen' : u"\u2485",
+ 'eighteenperiod' : u"\u2499",
+ 'eightgujarati' : u"\u0AEE",
+ 'eightgurmukhi' : u"\u0A6E",
+ 'eighthackarabic' : u"\u0668",
+ 'eighthangzhou' : u"\u3028",
+ 'eighthnotebeamed' : u"\u266B",
+ 'eightideographicparen' : u"\u3227",
+ 'eightinferior' : u"\u2088",
+ 'eightmonospace' : u"\uFF18",
+ 'eightoldstyle' : u"\uF738",
+ 'eightparen' : u"\u247B",
+ 'eightperiod' : u"\u248F",
+ 'eightpersian' : u"\u06F8",
+ 'eightroman' : u"\u2177",
+ 'eightsuperior' : u"\u2078",
+ 'eightthai' : u"\u0E58",
+ 'einvertedbreve' : u"\u0207",
+ 'eiotifiedcyrillic' : u"\u0465",
+ 'ekatakana' : u"\u30A8",
+ 'ekatakanahalfwidth' : u"\uFF74",
+ 'ekonkargurmukhi' : u"\u0A74",
+ 'ekorean' : u"\u3154",
+ 'elcyrillic' : u"\u043B",
+ 'elevencircle' : u"\u246A",
+ 'elevenparen' : u"\u247E",
+ 'elevenperiod' : u"\u2492",
+ 'elevenroman' : u"\u217A",
+ 'ellipsisvertical' : u"\u22EE",
+ 'emacronacute' : u"\u1E17",
+ 'emacrongrave' : u"\u1E15",
+ 'emcyrillic' : u"\u043C",
+ 'emdashvertical' : u"\uFE31",
+ 'emonospace' : u"\uFF45",
+ 'emphasismarkarmenian' : u"\u055B",
+ 'enbopomofo' : u"\u3123",
+ 'encyrillic' : u"\u043D",
+ 'endashvertical' : u"\uFE32",
+ 'endescendercyrillic' : u"\u04A3",
+ 'engbopomofo' : u"\u3125",
+ 'enghecyrillic' : u"\u04A5",
+ 'enhookcyrillic' : u"\u04C8",
+ 'enspace' : u"\u2002",
+ 'eokorean' : u"\u3153",
+ 'eopen' : u"\u025B",
+ 'eopenclosed' : u"\u029A",
+ 'eopenreversed' : u"\u025C",
+ 'eopenreversedclosed' : u"\u025E",
+ 'eopenreversedhook' : u"\u025D",
+ 'eparen' : u"\u24A0",
+ 'equalmonospace' : u"\uFF1D",
+ 'equalsmall' : u"\uFE66",
+ 'equalsuperior' : u"\u207C",
+ 'erbopomofo' : u"\u3126",
+ 'ercyrillic' : u"\u0440",
+ 'ereversed' : u"\u0258",
+ 'ereversedcyrillic' : u"\u044D",
+ 'escyrillic' : u"\u0441",
+ 'esdescendercyrillic' : u"\u04AB",
+ 'esh' : u"\u0283",
+ 'eshcurl' : u"\u0286",
+ 'eshortdeva' : u"\u090E",
+ 'eshortvowelsigndeva' : u"\u0946",
+ 'eshreversedloop' : u"\u01AA",
+ 'eshsquatreversed' : u"\u0285",
+ 'esmallhiragana' : u"\u3047",
+ 'esmallkatakana' : u"\u30A7",
+ 'esmallkatakanahalfwidth' : u"\uFF6A",
+ 'esuperior' : u"\uF6EC",
+ 'etarmenian' : u"\u0568",
+ 'etilde' : u"\u1EBD",
+ 'etildebelow' : u"\u1E1B",
+ 'etnahtafoukhhebrew' : u"\u0591",
+ 'etnahtafoukhlefthebrew' : u"\u0591",
+ 'etnahtahebrew' : u"\u0591",
+ 'etnahtalefthebrew' : u"\u0591",
+ 'eturned' : u"\u01DD",
+ 'eukorean' : u"\u3161",
+ 'euro' : u"\u20AC",
+ 'evowelsignbengali' : u"\u09C7",
+ 'evowelsigndeva' : u"\u0947",
+ 'evowelsigngujarati' : u"\u0AC7",
+ 'exclamarmenian' : u"\u055C",
+ 'exclamdownsmall' : u"\uF7A1",
+ 'exclammonospace' : u"\uFF01",
+ 'exclamsmall' : u"\uF721",
+ 'ezh' : u"\u0292",
+ 'ezhcaron' : u"\u01EF",
+ 'ezhcurl' : u"\u0293",
+ 'ezhreversed' : u"\u01B9",
+ 'ezhtail' : u"\u01BA",
+ 'fadeva' : u"\u095E",
+ 'fagurmukhi' : u"\u0A5E",
+ 'fahrenheit' : u"\u2109",
+ 'fathaarabic' : u"\u064E",
+ 'fathalowarabic' : u"\u064E",
+ 'fathatanarabic' : u"\u064B",
+ 'fbopomofo' : u"\u3108",
+ 'fcircle' : u"\u24D5",
+ 'fdotaccent' : u"\u1E1F",
+ 'feharabic' : u"\u0641",
+ 'feharmenian' : u"\u0586",
+ 'fehfinalarabic' : u"\uFED2",
+ 'fehinitialarabic' : u"\uFED3",
+ 'fehmedialarabic' : u"\uFED4",
+ 'feicoptic' : u"\u03E5",
+ 'fifteencircle' : u"\u246E",
+ 'fifteenparen' : u"\u2482",
+ 'fifteenperiod' : u"\u2496",
+ 'finalkaf' : u"\u05DA",
+ 'finalkafdagesh' : u"\uFB3A",
+ 'finalkafdageshhebrew' : u"\uFB3A",
+ 'finalkafhebrew' : u"\u05DA",
+ 'finalkafqamats' : u"\u05DA\u05B8",
+ 'finalkafqamatshebrew' : u"\u05DA\u05B8",
+ 'finalkafsheva' : u"\u05DA\u05B0",
+ 'finalkafshevahebrew' : u"\u05DA\u05B0",
+ 'finalmem' : u"\u05DD",
+ 'finalmemhebrew' : u"\u05DD",
+ 'finalnun' : u"\u05DF",
+ 'finalnunhebrew' : u"\u05DF",
+ 'finalpe' : u"\u05E3",
+ 'finalpehebrew' : u"\u05E3",
+ 'finaltsadi' : u"\u05E5",
+ 'finaltsadihebrew' : u"\u05E5",
+ 'firsttonechinese' : u"\u02C9",
+ 'fisheye' : u"\u25C9",
+ 'fitacyrillic' : u"\u0473",
+ 'fivearabic' : u"\u0665",
+ 'fivebengali' : u"\u09EB",
+ 'fivecircle' : u"\u2464",
+ 'fivecircleinversesansserif' : u"\u278E",
+ 'fivedeva' : u"\u096B",
+ 'fivegujarati' : u"\u0AEB",
+ 'fivegurmukhi' : u"\u0A6B",
+ 'fivehackarabic' : u"\u0665",
+ 'fivehangzhou' : u"\u3025",
+ 'fiveideographicparen' : u"\u3224",
+ 'fiveinferior' : u"\u2085",
+ 'fivemonospace' : u"\uFF15",
+ 'fiveoldstyle' : u"\uF735",
+ 'fiveparen' : u"\u2478",
+ 'fiveperiod' : u"\u248C",
+ 'fivepersian' : u"\u06F5",
+ 'fiveroman' : u"\u2174",
+ 'fivesuperior' : u"\u2075",
+ 'fivethai' : u"\u0E55",
+ 'fmonospace' : u"\uFF46",
+ 'fmsquare' : u"\u3399",
+ 'fofanthai' : u"\u0E1F",
+ 'fofathai' : u"\u0E1D",
+ 'fongmanthai' : u"\u0E4F",
+ 'forall' : u"\u2200",
+ 'fourarabic' : u"\u0664",
+ 'fourbengali' : u"\u09EA",
+ 'fourcircle' : u"\u2463",
+ 'fourcircleinversesansserif' : u"\u278D",
+ 'fourdeva' : u"\u096A",
+ 'fourgujarati' : u"\u0AEA",
+ 'fourgurmukhi' : u"\u0A6A",
+ 'fourhackarabic' : u"\u0664",
+ 'fourhangzhou' : u"\u3024",
+ 'fourideographicparen' : u"\u3223",
+ 'fourinferior' : u"\u2084",
+ 'fourmonospace' : u"\uFF14",
+ 'fournumeratorbengali' : u"\u09F7",
+ 'fouroldstyle' : u"\uF734",
+ 'fourparen' : u"\u2477",
+ 'fourperiod' : u"\u248B",
+ 'fourpersian' : u"\u06F4",
+ 'fourroman' : u"\u2173",
+ 'foursuperior' : u"\u2074",
+ 'fourteencircle' : u"\u246D",
+ 'fourteenparen' : u"\u2481",
+ 'fourteenperiod' : u"\u2495",
+ 'fourthai' : u"\u0E54",
+ 'fourthtonechinese' : u"\u02CB",
+ 'fparen' : u"\u24A1",
+ 'gabengali' : u"\u0997",
+ 'gacute' : u"\u01F5",
+ 'gadeva' : u"\u0917",
+ 'gafarabic' : u"\u06AF",
+ 'gaffinalarabic' : u"\uFB93",
+ 'gafinitialarabic' : u"\uFB94",
+ 'gafmedialarabic' : u"\uFB95",
+ 'gagujarati' : u"\u0A97",
+ 'gagurmukhi' : u"\u0A17",
+ 'gahiragana' : u"\u304C",
+ 'gakatakana' : u"\u30AC",
+ 'gammalatinsmall' : u"\u0263",
+ 'gammasuperior' : u"\u02E0",
+ 'gangiacoptic' : u"\u03EB",
+ 'gbopomofo' : u"\u310D",
+ 'gcedilla' : u"\u0123",
+ 'gcircle' : u"\u24D6",
+ 'gdot' : u"\u0121",
+ 'gecyrillic' : u"\u0433",
+ 'gehiragana' : u"\u3052",
+ 'gekatakana' : u"\u30B2",
+ 'geometricallyequal' : u"\u2251",
+ 'gereshaccenthebrew' : u"\u059C",
+ 'gereshhebrew' : u"\u05F3",
+ 'gereshmuqdamhebrew' : u"\u059D",
+ 'gershayimaccenthebrew' : u"\u059E",
+ 'gershayimhebrew' : u"\u05F4",
+ 'getamark' : u"\u3013",
+ 'ghabengali' : u"\u0998",
+ 'ghadarmenian' : u"\u0572",
+ 'ghadeva' : u"\u0918",
+ 'ghagujarati' : u"\u0A98",
+ 'ghagurmukhi' : u"\u0A18",
+ 'ghainarabic' : u"\u063A",
+ 'ghainfinalarabic' : u"\uFECE",
+ 'ghaininitialarabic' : u"\uFECF",
+ 'ghainmedialarabic' : u"\uFED0",
+ 'ghemiddlehookcyrillic' : u"\u0495",
+ 'ghestrokecyrillic' : u"\u0493",
+ 'gheupturncyrillic' : u"\u0491",
+ 'ghhadeva' : u"\u095A",
+ 'ghhagurmukhi' : u"\u0A5A",
+ 'ghook' : u"\u0260",
+ 'ghzsquare' : u"\u3393",
+ 'gihiragana' : u"\u304E",
+ 'gikatakana' : u"\u30AE",
+ 'gimarmenian' : u"\u0563",
+ 'gimel' : u"\u05D2",
+ 'gimeldagesh' : u"\uFB32",
+ 'gimeldageshhebrew' : u"\uFB32",
+ 'gimelhebrew' : u"\u05D2",
+ 'gjecyrillic' : u"\u0453",
+ 'glottalinvertedstroke' : u"\u01BE",
+ 'glottalstop' : u"\u0294",
+ 'glottalstopinverted' : u"\u0296",
+ 'glottalstopmod' : u"\u02C0",
+ 'glottalstopreversed' : u"\u0295",
+ 'glottalstopreversedmod' : u"\u02C1",
+ 'glottalstopreversedsuperior' : u"\u02E4",
+ 'glottalstopstroke' : u"\u02A1",
+ 'glottalstopstrokereversed' : u"\u02A2",
+ 'gmacron' : u"\u1E21",
+ 'gmonospace' : u"\uFF47",
+ 'gohiragana' : u"\u3054",
+ 'gokatakana' : u"\u30B4",
+ 'gparen' : u"\u24A2",
+ 'gpasquare' : u"\u33AC",
+ 'gravebelowcmb' : u"\u0316",
+ 'gravecmb' : u"\u0300",
+ 'gravedeva' : u"\u0953",
+ 'gravelowmod' : u"\u02CE",
+ 'gravemonospace' : u"\uFF40",
+ 'gravetonecmb' : u"\u0340",
+ 'greaterequalorless' : u"\u22DB",
+ 'greatermonospace' : u"\uFF1E",
+ 'greaterorequivalent' : u"\u2273",
+ 'greaterorless' : u"\u2277",
+ 'greateroverequal' : u"\u2267",
+ 'greatersmall' : u"\uFE65",
+ 'gscript' : u"\u0261",
+ 'gstroke' : u"\u01E5",
+ 'guhiragana' : u"\u3050",
+ 'gukatakana' : u"\u30B0",
+ 'guramusquare' : u"\u3318",
+ 'gysquare' : u"\u33C9",
+ 'haabkhasiancyrillic' : u"\u04A9",
+ 'haaltonearabic' : u"\u06C1",
+ 'habengali' : u"\u09B9",
+ 'hadescendercyrillic' : u"\u04B3",
+ 'hadeva' : u"\u0939",
+ 'hagujarati' : u"\u0AB9",
+ 'hagurmukhi' : u"\u0A39",
+ 'haharabic' : u"\u062D",
+ 'hahfinalarabic' : u"\uFEA2",
+ 'hahinitialarabic' : u"\uFEA3",
+ 'hahiragana' : u"\u306F",
+ 'hahmedialarabic' : u"\uFEA4",
+ 'haitusquare' : u"\u332A",
+ 'hakatakana' : u"\u30CF",
+ 'hakatakanahalfwidth' : u"\uFF8A",
+ 'halantgurmukhi' : u"\u0A4D",
+ 'hamzaarabic' : u"\u0621",
+ 'hamzadammaarabic' : u"\u0621\u064F",
+ 'hamzadammatanarabic' : u"\u0621\u064C",
+ 'hamzafathaarabic' : u"\u0621\u064E",
+ 'hamzafathatanarabic' : u"\u0621\u064B",
+ 'hamzalowarabic' : u"\u0621",
+ 'hamzalowkasraarabic' : u"\u0621\u0650",
+ 'hamzalowkasratanarabic' : u"\u0621\u064D",
+ 'hamzasukunarabic' : u"\u0621\u0652",
+ 'hangulfiller' : u"\u3164",
+ 'hardsigncyrillic' : u"\u044A",
+ 'harpoonleftbarbup' : u"\u21BC",
+ 'harpoonrightbarbup' : u"\u21C0",
+ 'hasquare' : u"\u33CA",
+ 'hatafpatah' : u"\u05B2",
+ 'hatafpatah16' : u"\u05B2",
+ 'hatafpatah23' : u"\u05B2",
+ 'hatafpatah2f' : u"\u05B2",
+ 'hatafpatahhebrew' : u"\u05B2",
+ 'hatafpatahnarrowhebrew' : u"\u05B2",
+ 'hatafpatahquarterhebrew' : u"\u05B2",
+ 'hatafpatahwidehebrew' : u"\u05B2",
+ 'hatafqamats' : u"\u05B3",
+ 'hatafqamats1b' : u"\u05B3",
+ 'hatafqamats28' : u"\u05B3",
+ 'hatafqamats34' : u"\u05B3",
+ 'hatafqamatshebrew' : u"\u05B3",
+ 'hatafqamatsnarrowhebrew' : u"\u05B3",
+ 'hatafqamatsquarterhebrew' : u"\u05B3",
+ 'hatafqamatswidehebrew' : u"\u05B3",
+ 'hatafsegol' : u"\u05B1",
+ 'hatafsegol17' : u"\u05B1",
+ 'hatafsegol24' : u"\u05B1",
+ 'hatafsegol30' : u"\u05B1",
+ 'hatafsegolhebrew' : u"\u05B1",
+ 'hatafsegolnarrowhebrew' : u"\u05B1",
+ 'hatafsegolquarterhebrew' : u"\u05B1",
+ 'hatafsegolwidehebrew' : u"\u05B1",
+ 'hbopomofo' : u"\u310F",
+ 'hbrevebelow' : u"\u1E2B",
+ 'hcedilla' : u"\u1E29",
+ 'hcircle' : u"\u24D7",
+ 'hdieresis' : u"\u1E27",
+ 'hdotaccent' : u"\u1E23",
+ 'hdotbelow' : u"\u1E25",
+ 'he' : u"\u05D4",
+ 'heartsuitblack' : u"\u2665",
+ 'heartsuitwhite' : u"\u2661",
+ 'hedagesh' : u"\uFB34",
+ 'hedageshhebrew' : u"\uFB34",
+ 'hehaltonearabic' : u"\u06C1",
+ 'heharabic' : u"\u0647",
+ 'hehebrew' : u"\u05D4",
+ 'hehfinalaltonearabic' : u"\uFBA7",
+ 'hehfinalalttwoarabic' : u"\uFEEA",
+ 'hehfinalarabic' : u"\uFEEA",
+ 'hehhamzaabovefinalarabic' : u"\uFBA5",
+ 'hehhamzaaboveisolatedarabic' : u"\uFBA4",
+ 'hehinitialaltonearabic' : u"\uFBA8",
+ 'hehinitialarabic' : u"\uFEEB",
+ 'hehiragana' : u"\u3078",
+ 'hehmedialaltonearabic' : u"\uFBA9",
+ 'hehmedialarabic' : u"\uFEEC",
+ 'heiseierasquare' : u"\u337B",
+ 'hekatakana' : u"\u30D8",
+ 'hekatakanahalfwidth' : u"\uFF8D",
+ 'hekutaarusquare' : u"\u3336",
+ 'henghook' : u"\u0267",
+ 'herutusquare' : u"\u3339",
+ 'het' : u"\u05D7",
+ 'hethebrew' : u"\u05D7",
+ 'hhook' : u"\u0266",
+ 'hhooksuperior' : u"\u02B1",
+ 'hieuhacirclekorean' : u"\u327B",
+ 'hieuhaparenkorean' : u"\u321B",
+ 'hieuhcirclekorean' : u"\u326D",
+ 'hieuhkorean' : u"\u314E",
+ 'hieuhparenkorean' : u"\u320D",
+ 'hihiragana' : u"\u3072",
+ 'hikatakana' : u"\u30D2",
+ 'hikatakanahalfwidth' : u"\uFF8B",
+ 'hiriq' : u"\u05B4",
+ 'hiriq14' : u"\u05B4",
+ 'hiriq21' : u"\u05B4",
+ 'hiriq2d' : u"\u05B4",
+ 'hiriqhebrew' : u"\u05B4",
+ 'hiriqnarrowhebrew' : u"\u05B4",
+ 'hiriqquarterhebrew' : u"\u05B4",
+ 'hiriqwidehebrew' : u"\u05B4",
+ 'hlinebelow' : u"\u1E96",
+ 'hmonospace' : u"\uFF48",
+ 'hoarmenian' : u"\u0570",
+ 'hohipthai' : u"\u0E2B",
+ 'hohiragana' : u"\u307B",
+ 'hokatakana' : u"\u30DB",
+ 'hokatakanahalfwidth' : u"\uFF8E",
+ 'holam' : u"\u05B9",
+ 'holam19' : u"\u05B9",
+ 'holam26' : u"\u05B9",
+ 'holam32' : u"\u05B9",
+ 'holamhebrew' : u"\u05B9",
+ 'holamnarrowhebrew' : u"\u05B9",
+ 'holamquarterhebrew' : u"\u05B9",
+ 'holamwidehebrew' : u"\u05B9",
+ 'honokhukthai' : u"\u0E2E",
+ 'hookcmb' : u"\u0309",
+ 'hookpalatalizedbelowcmb' : u"\u0321",
+ 'hookretroflexbelowcmb' : u"\u0322",
+ 'hoonsquare' : u"\u3342",
+ 'horicoptic' : u"\u03E9",
+ 'horizontalbar' : u"\u2015",
+ 'horncmb' : u"\u031B",
+ 'hotsprings' : u"\u2668",
+ 'hparen' : u"\u24A3",
+ 'hsuperior' : u"\u02B0",
+ 'hturned' : u"\u0265",
+ 'huhiragana' : u"\u3075",
+ 'huiitosquare' : u"\u3333",
+ 'hukatakana' : u"\u30D5",
+ 'hukatakanahalfwidth' : u"\uFF8C",
+ 'hungarumlautcmb' : u"\u030B",
+ 'hv' : u"\u0195",
+ 'hypheninferior' : u"\uF6E5",
+ 'hyphenmonospace' : u"\uFF0D",
+ 'hyphensmall' : u"\uFE63",
+ 'hyphensuperior' : u"\uF6E6",
+ 'hyphentwo' : u"\u2010",
+ 'iacyrillic' : u"\u044F",
+ 'ibengali' : u"\u0987",
+ 'ibopomofo' : u"\u3127",
+ 'icaron' : u"\u01D0",
+ 'icircle' : u"\u24D8",
+ 'icyrillic' : u"\u0456",
+ 'idblgrave' : u"\u0209",
+ 'ideographearthcircle' : u"\u328F",
+ 'ideographfirecircle' : u"\u328B",
+ 'ideographicallianceparen' : u"\u323F",
+ 'ideographiccallparen' : u"\u323A",
+ 'ideographiccentrecircle' : u"\u32A5",
+ 'ideographicclose' : u"\u3006",
+ 'ideographiccomma' : u"\u3001",
+ 'ideographiccommaleft' : u"\uFF64",
+ 'ideographiccongratulationparen' : u"\u3237",
+ 'ideographiccorrectcircle' : u"\u32A3",
+ 'ideographicearthparen' : u"\u322F",
+ 'ideographicenterpriseparen' : u"\u323D",
+ 'ideographicexcellentcircle' : u"\u329D",
+ 'ideographicfestivalparen' : u"\u3240",
+ 'ideographicfinancialcircle' : u"\u3296",
+ 'ideographicfinancialparen' : u"\u3236",
+ 'ideographicfireparen' : u"\u322B",
+ 'ideographichaveparen' : u"\u3232",
+ 'ideographichighcircle' : u"\u32A4",
+ 'ideographiciterationmark' : u"\u3005",
+ 'ideographiclaborcircle' : u"\u3298",
+ 'ideographiclaborparen' : u"\u3238",
+ 'ideographicleftcircle' : u"\u32A7",
+ 'ideographiclowcircle' : u"\u32A6",
+ 'ideographicmedicinecircle' : u"\u32A9",
+ 'ideographicmetalparen' : u"\u322E",
+ 'ideographicmoonparen' : u"\u322A",
+ 'ideographicnameparen' : u"\u3234",
+ 'ideographicperiod' : u"\u3002",
+ 'ideographicprintcircle' : u"\u329E",
+ 'ideographicreachparen' : u"\u3243",
+ 'ideographicrepresentparen' : u"\u3239",
+ 'ideographicresourceparen' : u"\u323E",
+ 'ideographicrightcircle' : u"\u32A8",
+ 'ideographicsecretcircle' : u"\u3299",
+ 'ideographicselfparen' : u"\u3242",
+ 'ideographicsocietyparen' : u"\u3233",
+ 'ideographicspace' : u"\u3000",
+ 'ideographicspecialparen' : u"\u3235",
+ 'ideographicstockparen' : u"\u3231",
+ 'ideographicstudyparen' : u"\u323B",
+ 'ideographicsunparen' : u"\u3230",
+ 'ideographicsuperviseparen' : u"\u323C",
+ 'ideographicwaterparen' : u"\u322C",
+ 'ideographicwoodparen' : u"\u322D",
+ 'ideographiczero' : u"\u3007",
+ 'ideographmetalcircle' : u"\u328E",
+ 'ideographmooncircle' : u"\u328A",
+ 'ideographnamecircle' : u"\u3294",
+ 'ideographsuncircle' : u"\u3290",
+ 'ideographwatercircle' : u"\u328C",
+ 'ideographwoodcircle' : u"\u328D",
+ 'ideva' : u"\u0907",
+ 'idieresisacute' : u"\u1E2F",
+ 'idieresiscyrillic' : u"\u04E5",
+ 'idotbelow' : u"\u1ECB",
+ 'iebrevecyrillic' : u"\u04D7",
+ 'iecyrillic' : u"\u0435",
+ 'ieungacirclekorean' : u"\u3275",
+ 'ieungaparenkorean' : u"\u3215",
+ 'ieungcirclekorean' : u"\u3267",
+ 'ieungkorean' : u"\u3147",
+ 'ieungparenkorean' : u"\u3207",
+ 'igujarati' : u"\u0A87",
+ 'igurmukhi' : u"\u0A07",
+ 'ihiragana' : u"\u3044",
+ 'ihookabove' : u"\u1EC9",
+ 'iibengali' : u"\u0988",
+ 'iicyrillic' : u"\u0438",
+ 'iideva' : u"\u0908",
+ 'iigujarati' : u"\u0A88",
+ 'iigurmukhi' : u"\u0A08",
+ 'iimatragurmukhi' : u"\u0A40",
+ 'iinvertedbreve' : u"\u020B",
+ 'iishortcyrillic' : u"\u0439",
+ 'iivowelsignbengali' : u"\u09C0",
+ 'iivowelsigndeva' : u"\u0940",
+ 'iivowelsigngujarati' : u"\u0AC0",
+ 'ikatakana' : u"\u30A4",
+ 'ikatakanahalfwidth' : u"\uFF72",
+ 'ikorean' : u"\u3163",
+ 'ilde' : u"\u02DC",
+ 'iluyhebrew' : u"\u05AC",
+ 'imacroncyrillic' : u"\u04E3",
+ 'imageorapproximatelyequal' : u"\u2253",
+ 'imatragurmukhi' : u"\u0A3F",
+ 'imonospace' : u"\uFF49",
+ 'increment' : u"\u2206",
+ 'iniarmenian' : u"\u056B",
+ 'integralbottom' : u"\u2321",
+ 'integralex' : u"\uF8F5",
+ 'integraltop' : u"\u2320",
+ 'intisquare' : u"\u3305",
+ 'iocyrillic' : u"\u0451",
+ 'iotalatin' : u"\u0269",
+ 'iparen' : u"\u24A4",
+ 'irigurmukhi' : u"\u0A72",
+ 'ismallhiragana' : u"\u3043",
+ 'ismallkatakana' : u"\u30A3",
+ 'ismallkatakanahalfwidth' : u"\uFF68",
+ 'issharbengali' : u"\u09FA",
+ 'istroke' : u"\u0268",
+ 'isuperior' : u"\uF6ED",
+ 'iterationhiragana' : u"\u309D",
+ 'iterationkatakana' : u"\u30FD",
+ 'itildebelow' : u"\u1E2D",
+ 'iubopomofo' : u"\u3129",
+ 'iucyrillic' : u"\u044E",
+ 'ivowelsignbengali' : u"\u09BF",
+ 'ivowelsigndeva' : u"\u093F",
+ 'ivowelsigngujarati' : u"\u0ABF",
+ 'izhitsacyrillic' : u"\u0475",
+ 'izhitsadblgravecyrillic' : u"\u0477",
+ 'jaarmenian' : u"\u0571",
+ 'jabengali' : u"\u099C",
+ 'jadeva' : u"\u091C",
+ 'jagujarati' : u"\u0A9C",
+ 'jagurmukhi' : u"\u0A1C",
+ 'jbopomofo' : u"\u3110",
+ 'jcaron' : u"\u01F0",
+ 'jcircle' : u"\u24D9",
+ 'jcrossedtail' : u"\u029D",
+ 'jdotlessstroke' : u"\u025F",
+ 'jecyrillic' : u"\u0458",
+ 'jeemarabic' : u"\u062C",
+ 'jeemfinalarabic' : u"\uFE9E",
+ 'jeeminitialarabic' : u"\uFE9F",
+ 'jeemmedialarabic' : u"\uFEA0",
+ 'jeharabic' : u"\u0698",
+ 'jehfinalarabic' : u"\uFB8B",
+ 'jhabengali' : u"\u099D",
+ 'jhadeva' : u"\u091D",
+ 'jhagujarati' : u"\u0A9D",
+ 'jhagurmukhi' : u"\u0A1D",
+ 'jheharmenian' : u"\u057B",
+ 'jis' : u"\u3004",
+ 'jmonospace' : u"\uFF4A",
+ 'jparen' : u"\u24A5",
+ 'jsuperior' : u"\u02B2",
+ 'kabashkircyrillic' : u"\u04A1",
+ 'kabengali' : u"\u0995",
+ 'kacute' : u"\u1E31",
+ 'kacyrillic' : u"\u043A",
+ 'kadescendercyrillic' : u"\u049B",
+ 'kadeva' : u"\u0915",
+ 'kaf' : u"\u05DB",
+ 'kafarabic' : u"\u0643",
+ 'kafdagesh' : u"\uFB3B",
+ 'kafdageshhebrew' : u"\uFB3B",
+ 'kaffinalarabic' : u"\uFEDA",
+ 'kafhebrew' : u"\u05DB",
+ 'kafinitialarabic' : u"\uFEDB",
+ 'kafmedialarabic' : u"\uFEDC",
+ 'kafrafehebrew' : u"\uFB4D",
+ 'kagujarati' : u"\u0A95",
+ 'kagurmukhi' : u"\u0A15",
+ 'kahiragana' : u"\u304B",
+ 'kahookcyrillic' : u"\u04C4",
+ 'kakatakana' : u"\u30AB",
+ 'kakatakanahalfwidth' : u"\uFF76",
+ 'kappasymbolgreek' : u"\u03F0",
+ 'kapyeounmieumkorean' : u"\u3171",
+ 'kapyeounphieuphkorean' : u"\u3184",
+ 'kapyeounpieupkorean' : u"\u3178",
+ 'kapyeounssangpieupkorean' : u"\u3179",
+ 'karoriisquare' : u"\u330D",
+ 'kashidaautoarabic' : u"\u0640",
+ 'kashidaautonosidebearingarabic' : u"\u0640",
+ 'kasmallkatakana' : u"\u30F5",
+ 'kasquare' : u"\u3384",
+ 'kasraarabic' : u"\u0650",
+ 'kasratanarabic' : u"\u064D",
+ 'kastrokecyrillic' : u"\u049F",
+ 'katahiraprolongmarkhalfwidth' : u"\uFF70",
+ 'kaverticalstrokecyrillic' : u"\u049D",
+ 'kbopomofo' : u"\u310E",
+ 'kcalsquare' : u"\u3389",
+ 'kcaron' : u"\u01E9",
+ 'kcedilla' : u"\u0137",
+ 'kcircle' : u"\u24DA",
+ 'kdotbelow' : u"\u1E33",
+ 'keharmenian' : u"\u0584",
+ 'kehiragana' : u"\u3051",
+ 'kekatakana' : u"\u30B1",
+ 'kekatakanahalfwidth' : u"\uFF79",
+ 'kenarmenian' : u"\u056F",
+ 'kesmallkatakana' : u"\u30F6",
+ 'khabengali' : u"\u0996",
+ 'khacyrillic' : u"\u0445",
+ 'khadeva' : u"\u0916",
+ 'khagujarati' : u"\u0A96",
+ 'khagurmukhi' : u"\u0A16",
+ 'khaharabic' : u"\u062E",
+ 'khahfinalarabic' : u"\uFEA6",
+ 'khahinitialarabic' : u"\uFEA7",
+ 'khahmedialarabic' : u"\uFEA8",
+ 'kheicoptic' : u"\u03E7",
+ 'khhadeva' : u"\u0959",
+ 'khhagurmukhi' : u"\u0A59",
+ 'khieukhacirclekorean' : u"\u3278",
+ 'khieukhaparenkorean' : u"\u3218",
+ 'khieukhcirclekorean' : u"\u326A",
+ 'khieukhkorean' : u"\u314B",
+ 'khieukhparenkorean' : u"\u320A",
+ 'khokhaithai' : u"\u0E02",
+ 'khokhonthai' : u"\u0E05",
+ 'khokhuatthai' : u"\u0E03",
+ 'khokhwaithai' : u"\u0E04",
+ 'khomutthai' : u"\u0E5B",
+ 'khook' : u"\u0199",
+ 'khorakhangthai' : u"\u0E06",
+ 'khzsquare' : u"\u3391",
+ 'kihiragana' : u"\u304D",
+ 'kikatakana' : u"\u30AD",
+ 'kikatakanahalfwidth' : u"\uFF77",
+ 'kiroguramusquare' : u"\u3315",
+ 'kiromeetorusquare' : u"\u3316",
+ 'kirosquare' : u"\u3314",
+ 'kiyeokacirclekorean' : u"\u326E",
+ 'kiyeokaparenkorean' : u"\u320E",
+ 'kiyeokcirclekorean' : u"\u3260",
+ 'kiyeokkorean' : u"\u3131",
+ 'kiyeokparenkorean' : u"\u3200",
+ 'kiyeoksioskorean' : u"\u3133",
+ 'kjecyrillic' : u"\u045C",
+ 'klinebelow' : u"\u1E35",
+ 'klsquare' : u"\u3398",
+ 'kmcubedsquare' : u"\u33A6",
+ 'kmonospace' : u"\uFF4B",
+ 'kmsquaredsquare' : u"\u33A2",
+ 'kohiragana' : u"\u3053",
+ 'kohmsquare' : u"\u33C0",
+ 'kokaithai' : u"\u0E01",
+ 'kokatakana' : u"\u30B3",
+ 'kokatakanahalfwidth' : u"\uFF7A",
+ 'kooposquare' : u"\u331E",
+ 'koppacyrillic' : u"\u0481",
+ 'koreanstandardsymbol' : u"\u327F",
+ 'koroniscmb' : u"\u0343",
+ 'kparen' : u"\u24A6",
+ 'kpasquare' : u"\u33AA",
+ 'ksicyrillic' : u"\u046F",
+ 'ktsquare' : u"\u33CF",
+ 'kturned' : u"\u029E",
+ 'kuhiragana' : u"\u304F",
+ 'kukatakana' : u"\u30AF",
+ 'kukatakanahalfwidth' : u"\uFF78",
+ 'kvsquare' : u"\u33B8",
+ 'kwsquare' : u"\u33BE",
+ 'labengali' : u"\u09B2",
+ 'ladeva' : u"\u0932",
+ 'lagujarati' : u"\u0AB2",
+ 'lagurmukhi' : u"\u0A32",
+ 'lakkhangyaothai' : u"\u0E45",
+ 'lamaleffinalarabic' : u"\uFEFC",
+ 'lamalefhamzaabovefinalarabic' : u"\uFEF8",
+ 'lamalefhamzaaboveisolatedarabic' : u"\uFEF7",
+ 'lamalefhamzabelowfinalarabic' : u"\uFEFA",
+ 'lamalefhamzabelowisolatedarabic' : u"\uFEF9",
+ 'lamalefisolatedarabic' : u"\uFEFB",
+ 'lamalefmaddaabovefinalarabic' : u"\uFEF6",
+ 'lamalefmaddaaboveisolatedarabic' : u"\uFEF5",
+ 'lamarabic' : u"\u0644",
+ 'lambdastroke' : u"\u019B",
+ 'lamed' : u"\u05DC",
+ 'lameddagesh' : u"\uFB3C",
+ 'lameddageshhebrew' : u"\uFB3C",
+ 'lamedhebrew' : u"\u05DC",
+ 'lamedholam' : u"\u05DC\u05B9",
+ 'lamedholamdagesh' : u"\u05DC\u05B9\u05BC",
+ 'lamedholamdageshhebrew' : u"\u05DC\u05B9\u05BC",
+ 'lamedholamhebrew' : u"\u05DC\u05B9",
+ 'lamfinalarabic' : u"\uFEDE",
+ 'lamhahinitialarabic' : u"\uFCCA",
+ 'laminitialarabic' : u"\uFEDF",
+ 'lamjeeminitialarabic' : u"\uFCC9",
+ 'lamkhahinitialarabic' : u"\uFCCB",
+ 'lamlamhehisolatedarabic' : u"\uFDF2",
+ 'lammedialarabic' : u"\uFEE0",
+ 'lammeemhahinitialarabic' : u"\uFD88",
+ 'lammeeminitialarabic' : u"\uFCCC",
+ 'lammeemjeeminitialarabic' : u"\uFEDF\uFEE4\uFEA0",
+ 'lammeemkhahinitialarabic' : u"\uFEDF\uFEE4\uFEA8",
+ 'largecircle' : u"\u25EF",
+ 'lbar' : u"\u019A",
+ 'lbelt' : u"\u026C",
+ 'lbopomofo' : u"\u310C",
+ 'lcedilla' : u"\u013C",
+ 'lcircle' : u"\u24DB",
+ 'lcircumflexbelow' : u"\u1E3D",
+ 'ldotaccent' : u"\u0140",
+ 'ldotbelow' : u"\u1E37",
+ 'ldotbelowmacron' : u"\u1E39",
+ 'leftangleabovecmb' : u"\u031A",
+ 'lefttackbelowcmb' : u"\u0318",
+ 'lessequalorgreater' : u"\u22DA",
+ 'lessmonospace' : u"\uFF1C",
+ 'lessorequivalent' : u"\u2272",
+ 'lessorgreater' : u"\u2276",
+ 'lessoverequal' : u"\u2266",
+ 'lesssmall' : u"\uFE64",
+ 'lezh' : u"\u026E",
+ 'lhookretroflex' : u"\u026D",
+ 'liwnarmenian' : u"\u056C",
+ 'lj' : u"\u01C9",
+ 'ljecyrillic' : u"\u0459",
+ 'll' : u"\uF6C0",
+ 'lladeva' : u"\u0933",
+ 'llagujarati' : u"\u0AB3",
+ 'llinebelow' : u"\u1E3B",
+ 'llladeva' : u"\u0934",
+ 'llvocalicbengali' : u"\u09E1",
+ 'llvocalicdeva' : u"\u0961",
+ 'llvocalicvowelsignbengali' : u"\u09E3",
+ 'llvocalicvowelsigndeva' : u"\u0963",
+ 'lmiddletilde' : u"\u026B",
+ 'lmonospace' : u"\uFF4C",
+ 'lmsquare' : u"\u33D0",
+ 'lochulathai' : u"\u0E2C",
+ 'logicalnotreversed' : u"\u2310",
+ 'lolingthai' : u"\u0E25",
+ 'lowlinecenterline' : u"\uFE4E",
+ 'lowlinecmb' : u"\u0332",
+ 'lowlinedashed' : u"\uFE4D",
+ 'lparen' : u"\u24A7",
+ 'lsquare' : u"\u2113",
+ 'lsuperior' : u"\uF6EE",
+ 'luthai' : u"\u0E26",
+ 'lvocalicbengali' : u"\u098C",
+ 'lvocalicdeva' : u"\u090C",
+ 'lvocalicvowelsignbengali' : u"\u09E2",
+ 'lvocalicvowelsigndeva' : u"\u0962",
+ 'lxsquare' : u"\u33D3",
+ 'mabengali' : u"\u09AE",
+ 'macronbelowcmb' : u"\u0331",
+ 'macroncmb' : u"\u0304",
+ 'macronlowmod' : u"\u02CD",
+ 'macronmonospace' : u"\uFFE3",
+ 'macute' : u"\u1E3F",
+ 'madeva' : u"\u092E",
+ 'magujarati' : u"\u0AAE",
+ 'magurmukhi' : u"\u0A2E",
+ 'mahapakhhebrew' : u"\u05A4",
+ 'mahapakhlefthebrew' : u"\u05A4",
+ 'mahiragana' : u"\u307E",
+ 'maichattawalowleftthai' : u"\uF895",
+ 'maichattawalowrightthai' : u"\uF894",
+ 'maichattawathai' : u"\u0E4B",
+ 'maichattawaupperleftthai' : u"\uF893",
+ 'maieklowleftthai' : u"\uF88C",
+ 'maieklowrightthai' : u"\uF88B",
+ 'maiekthai' : u"\u0E48",
+ 'maiekupperleftthai' : u"\uF88A",
+ 'maihanakatleftthai' : u"\uF884",
+ 'maihanakatthai' : u"\u0E31",
+ 'maitaikhuleftthai' : u"\uF889",
+ 'maitaikhuthai' : u"\u0E47",
+ 'maitholowleftthai' : u"\uF88F",
+ 'maitholowrightthai' : u"\uF88E",
+ 'maithothai' : u"\u0E49",
+ 'maithoupperleftthai' : u"\uF88D",
+ 'maitrilowleftthai' : u"\uF892",
+ 'maitrilowrightthai' : u"\uF891",
+ 'maitrithai' : u"\u0E4A",
+ 'maitriupperleftthai' : u"\uF890",
+ 'maiyamokthai' : u"\u0E46",
+ 'makatakana' : u"\u30DE",
+ 'makatakanahalfwidth' : u"\uFF8F",
+ 'mansyonsquare' : u"\u3347",
+ 'maqafhebrew' : u"\u05BE",
+ 'mars' : u"\u2642",
+ 'masoracirclehebrew' : u"\u05AF",
+ 'masquare' : u"\u3383",
+ 'mbopomofo' : u"\u3107",
+ 'mbsquare' : u"\u33D4",
+ 'mcircle' : u"\u24DC",
+ 'mcubedsquare' : u"\u33A5",
+ 'mdotaccent' : u"\u1E41",
+ 'mdotbelow' : u"\u1E43",
+ 'meemarabic' : u"\u0645",
+ 'meemfinalarabic' : u"\uFEE2",
+ 'meeminitialarabic' : u"\uFEE3",
+ 'meemmedialarabic' : u"\uFEE4",
+ 'meemmeeminitialarabic' : u"\uFCD1",
+ 'meemmeemisolatedarabic' : u"\uFC48",
+ 'meetorusquare' : u"\u334D",
+ 'mehiragana' : u"\u3081",
+ 'meizierasquare' : u"\u337E",
+ 'mekatakana' : u"\u30E1",
+ 'mekatakanahalfwidth' : u"\uFF92",
+ 'mem' : u"\u05DE",
+ 'memdagesh' : u"\uFB3E",
+ 'memdageshhebrew' : u"\uFB3E",
+ 'memhebrew' : u"\u05DE",
+ 'menarmenian' : u"\u0574",
+ 'merkhahebrew' : u"\u05A5",
+ 'merkhakefulahebrew' : u"\u05A6",
+ 'merkhakefulalefthebrew' : u"\u05A6",
+ 'merkhalefthebrew' : u"\u05A5",
+ 'mhook' : u"\u0271",
+ 'mhzsquare' : u"\u3392",
+ 'middledotkatakanahalfwidth' : u"\uFF65",
+ 'middot' : u"\u00B7",
+ 'mieumacirclekorean' : u"\u3272",
+ 'mieumaparenkorean' : u"\u3212",
+ 'mieumcirclekorean' : u"\u3264",
+ 'mieumkorean' : u"\u3141",
+ 'mieumpansioskorean' : u"\u3170",
+ 'mieumparenkorean' : u"\u3204",
+ 'mieumpieupkorean' : u"\u316E",
+ 'mieumsioskorean' : u"\u316F",
+ 'mihiragana' : u"\u307F",
+ 'mikatakana' : u"\u30DF",
+ 'mikatakanahalfwidth' : u"\uFF90",
+ 'minusbelowcmb' : u"\u0320",
+ 'minuscircle' : u"\u2296",
+ 'minusmod' : u"\u02D7",
+ 'minusplus' : u"\u2213",
+ 'miribaarusquare' : u"\u334A",
+ 'mirisquare' : u"\u3349",
+ 'mlonglegturned' : u"\u0270",
+ 'mlsquare' : u"\u3396",
+ 'mmcubedsquare' : u"\u33A3",
+ 'mmonospace' : u"\uFF4D",
+ 'mmsquaredsquare' : u"\u339F",
+ 'mohiragana' : u"\u3082",
+ 'mohmsquare' : u"\u33C1",
+ 'mokatakana' : u"\u30E2",
+ 'mokatakanahalfwidth' : u"\uFF93",
+ 'molsquare' : u"\u33D6",
+ 'momathai' : u"\u0E21",
+ 'moverssquare' : u"\u33A7",
+ 'moverssquaredsquare' : u"\u33A8",
+ 'mparen' : u"\u24A8",
+ 'mpasquare' : u"\u33AB",
+ 'mssquare' : u"\u33B3",
+ 'msuperior' : u"\uF6EF",
+ 'mturned' : u"\u026F",
+ 'mu1' : u"\u00B5",
+ 'muasquare' : u"\u3382",
+ 'muchgreater' : u"\u226B",
+ 'muchless' : u"\u226A",
+ 'mufsquare' : u"\u338C",
+ 'mugreek' : u"\u03BC",
+ 'mugsquare' : u"\u338D",
+ 'muhiragana' : u"\u3080",
+ 'mukatakana' : u"\u30E0",
+ 'mukatakanahalfwidth' : u"\uFF91",
+ 'mulsquare' : u"\u3395",
+ 'mumsquare' : u"\u339B",
+ 'munahhebrew' : u"\u05A3",
+ 'munahlefthebrew' : u"\u05A3",
+ 'musicflatsign' : u"\u266D",
+ 'musicsharpsign' : u"\u266F",
+ 'mussquare' : u"\u33B2",
+ 'muvsquare' : u"\u33B6",
+ 'muwsquare' : u"\u33BC",
+ 'mvmegasquare' : u"\u33B9",
+ 'mvsquare' : u"\u33B7",
+ 'mwmegasquare' : u"\u33BF",
+ 'mwsquare' : u"\u33BD",
+ 'nabengali' : u"\u09A8",
+ 'nabla' : u"\u2207",
+ 'nadeva' : u"\u0928",
+ 'nagujarati' : u"\u0AA8",
+ 'nagurmukhi' : u"\u0A28",
+ 'nahiragana' : u"\u306A",
+ 'nakatakana' : u"\u30CA",
+ 'nakatakanahalfwidth' : u"\uFF85",
+ 'nasquare' : u"\u3381",
+ 'nbopomofo' : u"\u310B",
+ 'nbspace' : u"\u00A0",
+ 'ncedilla' : u"\u0146",
+ 'ncircle' : u"\u24DD",
+ 'ncircumflexbelow' : u"\u1E4B",
+ 'ndotaccent' : u"\u1E45",
+ 'ndotbelow' : u"\u1E47",
+ 'nehiragana' : u"\u306D",
+ 'nekatakana' : u"\u30CD",
+ 'nekatakanahalfwidth' : u"\uFF88",
+ 'newsheqelsign' : u"\u20AA",
+ 'nfsquare' : u"\u338B",
+ 'ngabengali' : u"\u0999",
+ 'ngadeva' : u"\u0919",
+ 'ngagujarati' : u"\u0A99",
+ 'ngagurmukhi' : u"\u0A19",
+ 'ngonguthai' : u"\u0E07",
+ 'nhiragana' : u"\u3093",
+ 'nhookleft' : u"\u0272",
+ 'nhookretroflex' : u"\u0273",
+ 'nieunacirclekorean' : u"\u326F",
+ 'nieunaparenkorean' : u"\u320F",
+ 'nieuncieuckorean' : u"\u3135",
+ 'nieuncirclekorean' : u"\u3261",
+ 'nieunhieuhkorean' : u"\u3136",
+ 'nieunkorean' : u"\u3134",
+ 'nieunpansioskorean' : u"\u3168",
+ 'nieunparenkorean' : u"\u3201",
+ 'nieunsioskorean' : u"\u3167",
+ 'nieuntikeutkorean' : u"\u3166",
+ 'nihiragana' : u"\u306B",
+ 'nikatakana' : u"\u30CB",
+ 'nikatakanahalfwidth' : u"\uFF86",
+ 'nikhahitleftthai' : u"\uF899",
+ 'nikhahitthai' : u"\u0E4D",
+ 'ninearabic' : u"\u0669",
+ 'ninebengali' : u"\u09EF",
+ 'ninecircle' : u"\u2468",
+ 'ninecircleinversesansserif' : u"\u2792",
+ 'ninedeva' : u"\u096F",
+ 'ninegujarati' : u"\u0AEF",
+ 'ninegurmukhi' : u"\u0A6F",
+ 'ninehackarabic' : u"\u0669",
+ 'ninehangzhou' : u"\u3029",
+ 'nineideographicparen' : u"\u3228",
+ 'nineinferior' : u"\u2089",
+ 'ninemonospace' : u"\uFF19",
+ 'nineoldstyle' : u"\uF739",
+ 'nineparen' : u"\u247C",
+ 'nineperiod' : u"\u2490",
+ 'ninepersian' : u"\u06F9",
+ 'nineroman' : u"\u2178",
+ 'ninesuperior' : u"\u2079",
+ 'nineteencircle' : u"\u2472",
+ 'nineteenparen' : u"\u2486",
+ 'nineteenperiod' : u"\u249A",
+ 'ninethai' : u"\u0E59",
+ 'nj' : u"\u01CC",
+ 'njecyrillic' : u"\u045A",
+ 'nkatakana' : u"\u30F3",
+ 'nkatakanahalfwidth' : u"\uFF9D",
+ 'nlegrightlong' : u"\u019E",
+ 'nlinebelow' : u"\u1E49",
+ 'nmonospace' : u"\uFF4E",
+ 'nmsquare' : u"\u339A",
+ 'nnabengali' : u"\u09A3",
+ 'nnadeva' : u"\u0923",
+ 'nnagujarati' : u"\u0AA3",
+ 'nnagurmukhi' : u"\u0A23",
+ 'nnnadeva' : u"\u0929",
+ 'nohiragana' : u"\u306E",
+ 'nokatakana' : u"\u30CE",
+ 'nokatakanahalfwidth' : u"\uFF89",
+ 'nonbreakingspace' : u"\u00A0",
+ 'nonenthai' : u"\u0E13",
+ 'nonuthai' : u"\u0E19",
+ 'noonarabic' : u"\u0646",
+ 'noonfinalarabic' : u"\uFEE6",
+ 'noonghunnaarabic' : u"\u06BA",
+ 'noonghunnafinalarabic' : u"\uFB9F",
+ 'noonhehinitialarabic' : u"\uFEE7\uFEEC",
+ 'nooninitialarabic' : u"\uFEE7",
+ 'noonjeeminitialarabic' : u"\uFCD2",
+ 'noonjeemisolatedarabic' : u"\uFC4B",
+ 'noonmedialarabic' : u"\uFEE8",
+ 'noonmeeminitialarabic' : u"\uFCD5",
+ 'noonmeemisolatedarabic' : u"\uFC4E",
+ 'noonnoonfinalarabic' : u"\uFC8D",
+ 'notcontains' : u"\u220C",
+ 'notelementof' : u"\u2209",
+ 'notgreater' : u"\u226F",
+ 'notgreaternorequal' : u"\u2271",
+ 'notgreaternorless' : u"\u2279",
+ 'notidentical' : u"\u2262",
+ 'notless' : u"\u226E",
+ 'notlessnorequal' : u"\u2270",
+ 'notparallel' : u"\u2226",
+ 'notprecedes' : u"\u2280",
+ 'notsucceeds' : u"\u2281",
+ 'notsuperset' : u"\u2285",
+ 'nowarmenian' : u"\u0576",
+ 'nparen' : u"\u24A9",
+ 'nssquare' : u"\u33B1",
+ 'nsuperior' : u"\u207F",
+ 'nuhiragana' : u"\u306C",
+ 'nukatakana' : u"\u30CC",
+ 'nukatakanahalfwidth' : u"\uFF87",
+ 'nuktabengali' : u"\u09BC",
+ 'nuktadeva' : u"\u093C",
+ 'nuktagujarati' : u"\u0ABC",
+ 'nuktagurmukhi' : u"\u0A3C",
+ 'numbersignmonospace' : u"\uFF03",
+ 'numbersignsmall' : u"\uFE5F",
+ 'numeralsigngreek' : u"\u0374",
+ 'numeralsignlowergreek' : u"\u0375",
+ 'numero' : u"\u2116",
+ 'nun' : u"\u05E0",
+ 'nundagesh' : u"\uFB40",
+ 'nundageshhebrew' : u"\uFB40",
+ 'nunhebrew' : u"\u05E0",
+ 'nvsquare' : u"\u33B5",
+ 'nwsquare' : u"\u33BB",
+ 'nyabengali' : u"\u099E",
+ 'nyadeva' : u"\u091E",
+ 'nyagujarati' : u"\u0A9E",
+ 'nyagurmukhi' : u"\u0A1E",
+ 'oangthai' : u"\u0E2D",
+ 'obarred' : u"\u0275",
+ 'obarredcyrillic' : u"\u04E9",
+ 'obarreddieresiscyrillic' : u"\u04EB",
+ 'obengali' : u"\u0993",
+ 'obopomofo' : u"\u311B",
+ 'ocandradeva' : u"\u0911",
+ 'ocandragujarati' : u"\u0A91",
+ 'ocandravowelsigndeva' : u"\u0949",
+ 'ocandravowelsigngujarati' : u"\u0AC9",
+ 'ocaron' : u"\u01D2",
+ 'ocircle' : u"\u24DE",
+ 'ocircumflexacute' : u"\u1ED1",
+ 'ocircumflexdotbelow' : u"\u1ED9",
+ 'ocircumflexgrave' : u"\u1ED3",
+ 'ocircumflexhookabove' : u"\u1ED5",
+ 'ocircumflextilde' : u"\u1ED7",
+ 'ocyrillic' : u"\u043E",
+ 'odblacute' : u"\u0151",
+ 'odblgrave' : u"\u020D",
+ 'odeva' : u"\u0913",
+ 'odieresiscyrillic' : u"\u04E7",
+ 'odotbelow' : u"\u1ECD",
+ 'oekorean' : u"\u315A",
+ 'ogonekcmb' : u"\u0328",
+ 'ogujarati' : u"\u0A93",
+ 'oharmenian' : u"\u0585",
+ 'ohiragana' : u"\u304A",
+ 'ohookabove' : u"\u1ECF",
+ 'ohornacute' : u"\u1EDB",
+ 'ohorndotbelow' : u"\u1EE3",
+ 'ohorngrave' : u"\u1EDD",
+ 'ohornhookabove' : u"\u1EDF",
+ 'ohorntilde' : u"\u1EE1",
+ 'oi' : u"\u01A3",
+ 'oinvertedbreve' : u"\u020F",
+ 'okatakana' : u"\u30AA",
+ 'okatakanahalfwidth' : u"\uFF75",
+ 'okorean' : u"\u3157",
+ 'olehebrew' : u"\u05AB",
+ 'omacronacute' : u"\u1E53",
+ 'omacrongrave' : u"\u1E51",
+ 'omdeva' : u"\u0950",
+ 'omegacyrillic' : u"\u0461",
+ 'omegalatinclosed' : u"\u0277",
+ 'omegaroundcyrillic' : u"\u047B",
+ 'omegatitlocyrillic' : u"\u047D",
+ 'omgujarati' : u"\u0AD0",
+ 'omonospace' : u"\uFF4F",
+ 'onearabic' : u"\u0661",
+ 'onebengali' : u"\u09E7",
+ 'onecircle' : u"\u2460",
+ 'onecircleinversesansserif' : u"\u278A",
+ 'onedeva' : u"\u0967",
+ 'onefitted' : u"\uF6DC",
+ 'onegujarati' : u"\u0AE7",
+ 'onegurmukhi' : u"\u0A67",
+ 'onehackarabic' : u"\u0661",
+ 'onehangzhou' : u"\u3021",
+ 'oneideographicparen' : u"\u3220",
+ 'oneinferior' : u"\u2081",
+ 'onemonospace' : u"\uFF11",
+ 'onenumeratorbengali' : u"\u09F4",
+ 'oneoldstyle' : u"\uF731",
+ 'oneparen' : u"\u2474",
+ 'oneperiod' : u"\u2488",
+ 'onepersian' : u"\u06F1",
+ 'oneroman' : u"\u2170",
+ 'onethai' : u"\u0E51",
+ 'oogonek' : u"\u01EB",
+ 'oogonekmacron' : u"\u01ED",
+ 'oogurmukhi' : u"\u0A13",
+ 'oomatragurmukhi' : u"\u0A4B",
+ 'oopen' : u"\u0254",
+ 'oparen' : u"\u24AA",
+ 'option' : u"\u2325",
+ 'oshortdeva' : u"\u0912",
+ 'oshortvowelsigndeva' : u"\u094A",
+ 'osmallhiragana' : u"\u3049",
+ 'osmallkatakana' : u"\u30A9",
+ 'osmallkatakanahalfwidth' : u"\uFF6B",
+ 'ostrokeacute' : u"\u01FF",
+ 'osuperior' : u"\uF6F0",
+ 'otcyrillic' : u"\u047F",
+ 'otildeacute' : u"\u1E4D",
+ 'otildedieresis' : u"\u1E4F",
+ 'oubopomofo' : u"\u3121",
+ 'overline' : u"\u203E",
+ 'overlinecenterline' : u"\uFE4A",
+ 'overlinecmb' : u"\u0305",
+ 'overlinedashed' : u"\uFE49",
+ 'overlinedblwavy' : u"\uFE4C",
+ 'overlinewavy' : u"\uFE4B",
+ 'overscore' : u"\u00AF",
+ 'ovowelsignbengali' : u"\u09CB",
+ 'ovowelsigndeva' : u"\u094B",
+ 'ovowelsigngujarati' : u"\u0ACB",
+ 'paampssquare' : u"\u3380",
+ 'paasentosquare' : u"\u332B",
+ 'pabengali' : u"\u09AA",
+ 'pacute' : u"\u1E55",
+ 'padeva' : u"\u092A",
+ 'pagedown' : u"\u21DF",
+ 'pageup' : u"\u21DE",
+ 'pagujarati' : u"\u0AAA",
+ 'pagurmukhi' : u"\u0A2A",
+ 'pahiragana' : u"\u3071",
+ 'paiyannoithai' : u"\u0E2F",
+ 'pakatakana' : u"\u30D1",
+ 'palatalizationcyrilliccmb' : u"\u0484",
+ 'palochkacyrillic' : u"\u04C0",
+ 'pansioskorean' : u"\u317F",
+ 'parallel' : u"\u2225",
+ 'parenleftaltonearabic' : u"\uFD3E",
+ 'parenleftbt' : u"\uF8ED",
+ 'parenleftex' : u"\uF8EC",
+ 'parenleftinferior' : u"\u208D",
+ 'parenleftmonospace' : u"\uFF08",
+ 'parenleftsmall' : u"\uFE59",
+ 'parenleftsuperior' : u"\u207D",
+ 'parenlefttp' : u"\uF8EB",
+ 'parenleftvertical' : u"\uFE35",
+ 'parenrightaltonearabic' : u"\uFD3F",
+ 'parenrightbt' : u"\uF8F8",
+ 'parenrightex' : u"\uF8F7",
+ 'parenrightinferior' : u"\u208E",
+ 'parenrightmonospace' : u"\uFF09",
+ 'parenrightsmall' : u"\uFE5A",
+ 'parenrightsuperior' : u"\u207E",
+ 'parenrighttp' : u"\uF8F6",
+ 'parenrightvertical' : u"\uFE36",
+ 'paseqhebrew' : u"\u05C0",
+ 'pashtahebrew' : u"\u0599",
+ 'pasquare' : u"\u33A9",
+ 'patah' : u"\u05B7",
+ 'patah11' : u"\u05B7",
+ 'patah1d' : u"\u05B7",
+ 'patah2a' : u"\u05B7",
+ 'patahhebrew' : u"\u05B7",
+ 'patahnarrowhebrew' : u"\u05B7",
+ 'patahquarterhebrew' : u"\u05B7",
+ 'patahwidehebrew' : u"\u05B7",
+ 'pazerhebrew' : u"\u05A1",
+ 'pbopomofo' : u"\u3106",
+ 'pcircle' : u"\u24DF",
+ 'pdotaccent' : u"\u1E57",
+ 'pe' : u"\u05E4",
+ 'pecyrillic' : u"\u043F",
+ 'pedagesh' : u"\uFB44",
+ 'pedageshhebrew' : u"\uFB44",
+ 'peezisquare' : u"\u333B",
+ 'pefinaldageshhebrew' : u"\uFB43",
+ 'peharabic' : u"\u067E",
+ 'peharmenian' : u"\u057A",
+ 'pehebrew' : u"\u05E4",
+ 'pehfinalarabic' : u"\uFB57",
+ 'pehinitialarabic' : u"\uFB58",
+ 'pehiragana' : u"\u307A",
+ 'pehmedialarabic' : u"\uFB59",
+ 'pekatakana' : u"\u30DA",
+ 'pemiddlehookcyrillic' : u"\u04A7",
+ 'perafehebrew' : u"\uFB4E",
+ 'percentarabic' : u"\u066A",
+ 'percentmonospace' : u"\uFF05",
+ 'percentsmall' : u"\uFE6A",
+ 'periodarmenian' : u"\u0589",
+ 'periodhalfwidth' : u"\uFF61",
+ 'periodinferior' : u"\uF6E7",
+ 'periodmonospace' : u"\uFF0E",
+ 'periodsmall' : u"\uFE52",
+ 'periodsuperior' : u"\uF6E8",
+ 'perispomenigreekcmb' : u"\u0342",
+ 'pfsquare' : u"\u338A",
+ 'phabengali' : u"\u09AB",
+ 'phadeva' : u"\u092B",
+ 'phagujarati' : u"\u0AAB",
+ 'phagurmukhi' : u"\u0A2B",
+ 'phieuphacirclekorean' : u"\u327A",
+ 'phieuphaparenkorean' : u"\u321A",
+ 'phieuphcirclekorean' : u"\u326C",
+ 'phieuphkorean' : u"\u314D",
+ 'phieuphparenkorean' : u"\u320C",
+ 'philatin' : u"\u0278",
+ 'phinthuthai' : u"\u0E3A",
+ 'phisymbolgreek' : u"\u03D5",
+ 'phook' : u"\u01A5",
+ 'phophanthai' : u"\u0E1E",
+ 'phophungthai' : u"\u0E1C",
+ 'phosamphaothai' : u"\u0E20",
+ 'pieupacirclekorean' : u"\u3273",
+ 'pieupaparenkorean' : u"\u3213",
+ 'pieupcieuckorean' : u"\u3176",
+ 'pieupcirclekorean' : u"\u3265",
+ 'pieupkiyeokkorean' : u"\u3172",
+ 'pieupkorean' : u"\u3142",
+ 'pieupparenkorean' : u"\u3205",
+ 'pieupsioskiyeokkorean' : u"\u3174",
+ 'pieupsioskorean' : u"\u3144",
+ 'pieupsiostikeutkorean' : u"\u3175",
+ 'pieupthieuthkorean' : u"\u3177",
+ 'pieuptikeutkorean' : u"\u3173",
+ 'pihiragana' : u"\u3074",
+ 'pikatakana' : u"\u30D4",
+ 'pisymbolgreek' : u"\u03D6",
+ 'piwrarmenian' : u"\u0583",
+ 'plusbelowcmb' : u"\u031F",
+ 'pluscircle' : u"\u2295",
+ 'plusmod' : u"\u02D6",
+ 'plusmonospace' : u"\uFF0B",
+ 'plussmall' : u"\uFE62",
+ 'plussuperior' : u"\u207A",
+ 'pmonospace' : u"\uFF50",
+ 'pmsquare' : u"\u33D8",
+ 'pohiragana' : u"\u307D",
+ 'pointingindexdownwhite' : u"\u261F",
+ 'pointingindexleftwhite' : u"\u261C",
+ 'pointingindexrightwhite' : u"\u261E",
+ 'pointingindexupwhite' : u"\u261D",
+ 'pokatakana' : u"\u30DD",
+ 'poplathai' : u"\u0E1B",
+ 'postalmark' : u"\u3012",
+ 'postalmarkface' : u"\u3020",
+ 'pparen' : u"\u24AB",
+ 'precedes' : u"\u227A",
+ 'primemod' : u"\u02B9",
+ 'primereversed' : u"\u2035",
+ 'projective' : u"\u2305",
+ 'prolongedkana' : u"\u30FC",
+ 'propellor' : u"\u2318",
+ 'proportion' : u"\u2237",
+ 'psicyrillic' : u"\u0471",
+ 'psilipneumatacyrilliccmb' : u"\u0486",
+ 'pssquare' : u"\u33B0",
+ 'puhiragana' : u"\u3077",
+ 'pukatakana' : u"\u30D7",
+ 'pvsquare' : u"\u33B4",
+ 'pwsquare' : u"\u33BA",
+ 'qadeva' : u"\u0958",
+ 'qadmahebrew' : u"\u05A8",
+ 'qafarabic' : u"\u0642",
+ 'qaffinalarabic' : u"\uFED6",
+ 'qafinitialarabic' : u"\uFED7",
+ 'qafmedialarabic' : u"\uFED8",
+ 'qamats' : u"\u05B8",
+ 'qamats10' : u"\u05B8",
+ 'qamats1a' : u"\u05B8",
+ 'qamats1c' : u"\u05B8",
+ 'qamats27' : u"\u05B8",
+ 'qamats29' : u"\u05B8",
+ 'qamats33' : u"\u05B8",
+ 'qamatsde' : u"\u05B8",
+ 'qamatshebrew' : u"\u05B8",
+ 'qamatsnarrowhebrew' : u"\u05B8",
+ 'qamatsqatanhebrew' : u"\u05B8",
+ 'qamatsqatannarrowhebrew' : u"\u05B8",
+ 'qamatsqatanquarterhebrew' : u"\u05B8",
+ 'qamatsqatanwidehebrew' : u"\u05B8",
+ 'qamatsquarterhebrew' : u"\u05B8",
+ 'qamatswidehebrew' : u"\u05B8",
+ 'qarneyparahebrew' : u"\u059F",
+ 'qbopomofo' : u"\u3111",
+ 'qcircle' : u"\u24E0",
+ 'qhook' : u"\u02A0",
+ 'qmonospace' : u"\uFF51",
+ 'qof' : u"\u05E7",
+ 'qofdagesh' : u"\uFB47",
+ 'qofdageshhebrew' : u"\uFB47",
+ 'qofhatafpatah' : u"\u05E7\u05B2",
+ 'qofhatafpatahhebrew' : u"\u05E7\u05B2",
+ 'qofhatafsegol' : u"\u05E7\u05B1",
+ 'qofhatafsegolhebrew' : u"\u05E7\u05B1",
+ 'qofhebrew' : u"\u05E7",
+ 'qofhiriq' : u"\u05E7\u05B4",
+ 'qofhiriqhebrew' : u"\u05E7\u05B4",
+ 'qofholam' : u"\u05E7\u05B9",
+ 'qofholamhebrew' : u"\u05E7\u05B9",
+ 'qofpatah' : u"\u05E7\u05B7",
+ 'qofpatahhebrew' : u"\u05E7\u05B7",
+ 'qofqamats' : u"\u05E7\u05B8",
+ 'qofqamatshebrew' : u"\u05E7\u05B8",
+ 'qofqubuts' : u"\u05E7\u05BB",
+ 'qofqubutshebrew' : u"\u05E7\u05BB",
+ 'qofsegol' : u"\u05E7\u05B6",
+ 'qofsegolhebrew' : u"\u05E7\u05B6",
+ 'qofsheva' : u"\u05E7\u05B0",
+ 'qofshevahebrew' : u"\u05E7\u05B0",
+ 'qoftsere' : u"\u05E7\u05B5",
+ 'qoftserehebrew' : u"\u05E7\u05B5",
+ 'qparen' : u"\u24AC",
+ 'quarternote' : u"\u2669",
+ 'qubuts' : u"\u05BB",
+ 'qubuts18' : u"\u05BB",
+ 'qubuts25' : u"\u05BB",
+ 'qubuts31' : u"\u05BB",
+ 'qubutshebrew' : u"\u05BB",
+ 'qubutsnarrowhebrew' : u"\u05BB",
+ 'qubutsquarterhebrew' : u"\u05BB",
+ 'qubutswidehebrew' : u"\u05BB",
+ 'questionarabic' : u"\u061F",
+ 'questionarmenian' : u"\u055E",
+ 'questiondownsmall' : u"\uF7BF",
+ 'questiongreek' : u"\u037E",
+ 'questionmonospace' : u"\uFF1F",
+ 'questionsmall' : u"\uF73F",
+ 'quotedblmonospace' : u"\uFF02",
+ 'quotedblprime' : u"\u301E",
+ 'quotedblprimereversed' : u"\u301D",
+ 'quoteleftreversed' : u"\u201B",
+ 'quoterightn' : u"\u0149",
+ 'quotesinglemonospace' : u"\uFF07",
+ 'raarmenian' : u"\u057C",
+ 'rabengali' : u"\u09B0",
+ 'radeva' : u"\u0930",
+ 'radicalex' : u"\uF8E5",
+ 'radoverssquare' : u"\u33AE",
+ 'radoverssquaredsquare' : u"\u33AF",
+ 'radsquare' : u"\u33AD",
+ 'rafe' : u"\u05BF",
+ 'rafehebrew' : u"\u05BF",
+ 'ragujarati' : u"\u0AB0",
+ 'ragurmukhi' : u"\u0A30",
+ 'rahiragana' : u"\u3089",
+ 'rakatakana' : u"\u30E9",
+ 'rakatakanahalfwidth' : u"\uFF97",
+ 'ralowerdiagonalbengali' : u"\u09F1",
+ 'ramiddlediagonalbengali' : u"\u09F0",
+ 'ramshorn' : u"\u0264",
+ 'ratio' : u"\u2236",
+ 'rbopomofo' : u"\u3116",
+ 'rcedilla' : u"\u0157",
+ 'rcircle' : u"\u24E1",
+ 'rdblgrave' : u"\u0211",
+ 'rdotaccent' : u"\u1E59",
+ 'rdotbelow' : u"\u1E5B",
+ 'rdotbelowmacron' : u"\u1E5D",
+ 'referencemark' : u"\u203B",
+ 'registersans' : u"\uF8E8",
+ 'registerserif' : u"\uF6DA",
+ 'reharabic' : u"\u0631",
+ 'reharmenian' : u"\u0580",
+ 'rehfinalarabic' : u"\uFEAE",
+ 'rehiragana' : u"\u308C",
+ 'rehyehaleflamarabic' : u"\u0631\uFEF3\uFE8E\u0644",
+ 'rekatakana' : u"\u30EC",
+ 'rekatakanahalfwidth' : u"\uFF9A",
+ 'resh' : u"\u05E8",
+ 'reshdageshhebrew' : u"\uFB48",
+ 'reshhatafpatah' : u"\u05E8\u05B2",
+ 'reshhatafpatahhebrew' : u"\u05E8\u05B2",
+ 'reshhatafsegol' : u"\u05E8\u05B1",
+ 'reshhatafsegolhebrew' : u"\u05E8\u05B1",
+ 'reshhebrew' : u"\u05E8",
+ 'reshhiriq' : u"\u05E8\u05B4",
+ 'reshhiriqhebrew' : u"\u05E8\u05B4",
+ 'reshholam' : u"\u05E8\u05B9",
+ 'reshholamhebrew' : u"\u05E8\u05B9",
+ 'reshpatah' : u"\u05E8\u05B7",
+ 'reshpatahhebrew' : u"\u05E8\u05B7",
+ 'reshqamats' : u"\u05E8\u05B8",
+ 'reshqamatshebrew' : u"\u05E8\u05B8",
+ 'reshqubuts' : u"\u05E8\u05BB",
+ 'reshqubutshebrew' : u"\u05E8\u05BB",
+ 'reshsegol' : u"\u05E8\u05B6",
+ 'reshsegolhebrew' : u"\u05E8\u05B6",
+ 'reshsheva' : u"\u05E8\u05B0",
+ 'reshshevahebrew' : u"\u05E8\u05B0",
+ 'reshtsere' : u"\u05E8\u05B5",
+ 'reshtserehebrew' : u"\u05E8\u05B5",
+ 'reversedtilde' : u"\u223D",
+ 'reviahebrew' : u"\u0597",
+ 'reviamugrashhebrew' : u"\u0597",
+ 'rfishhook' : u"\u027E",
+ 'rfishhookreversed' : u"\u027F",
+ 'rhabengali' : u"\u09DD",
+ 'rhadeva' : u"\u095D",
+ 'rhook' : u"\u027D",
+ 'rhookturned' : u"\u027B",
+ 'rhookturnedsuperior' : u"\u02B5",
+ 'rhosymbolgreek' : u"\u03F1",
+ 'rhotichookmod' : u"\u02DE",
+ 'rieulacirclekorean' : u"\u3271",
+ 'rieulaparenkorean' : u"\u3211",
+ 'rieulcirclekorean' : u"\u3263",
+ 'rieulhieuhkorean' : u"\u3140",
+ 'rieulkiyeokkorean' : u"\u313A",
+ 'rieulkiyeoksioskorean' : u"\u3169",
+ 'rieulkorean' : u"\u3139",
+ 'rieulmieumkorean' : u"\u313B",
+ 'rieulpansioskorean' : u"\u316C",
+ 'rieulparenkorean' : u"\u3203",
+ 'rieulphieuphkorean' : u"\u313F",
+ 'rieulpieupkorean' : u"\u313C",
+ 'rieulpieupsioskorean' : u"\u316B",
+ 'rieulsioskorean' : u"\u313D",
+ 'rieulthieuthkorean' : u"\u313E",
+ 'rieultikeutkorean' : u"\u316A",
+ 'rieulyeorinhieuhkorean' : u"\u316D",
+ 'rightangle' : u"\u221F",
+ 'righttackbelowcmb' : u"\u0319",
+ 'righttriangle' : u"\u22BF",
+ 'rihiragana' : u"\u308A",
+ 'rikatakana' : u"\u30EA",
+ 'rikatakanahalfwidth' : u"\uFF98",
+ 'ringbelowcmb' : u"\u0325",
+ 'ringcmb' : u"\u030A",
+ 'ringhalfleft' : u"\u02BF",
+ 'ringhalfleftarmenian' : u"\u0559",
+ 'ringhalfleftbelowcmb' : u"\u031C",
+ 'ringhalfleftcentered' : u"\u02D3",
+ 'ringhalfright' : u"\u02BE",
+ 'ringhalfrightbelowcmb' : u"\u0339",
+ 'ringhalfrightcentered' : u"\u02D2",
+ 'rinvertedbreve' : u"\u0213",
+ 'rittorusquare' : u"\u3351",
+ 'rlinebelow' : u"\u1E5F",
+ 'rlongleg' : u"\u027C",
+ 'rlonglegturned' : u"\u027A",
+ 'rmonospace' : u"\uFF52",
+ 'rohiragana' : u"\u308D",
+ 'rokatakana' : u"\u30ED",
+ 'rokatakanahalfwidth' : u"\uFF9B",
+ 'roruathai' : u"\u0E23",
+ 'rparen' : u"\u24AD",
+ 'rrabengali' : u"\u09DC",
+ 'rradeva' : u"\u0931",
+ 'rragurmukhi' : u"\u0A5C",
+ 'rreharabic' : u"\u0691",
+ 'rrehfinalarabic' : u"\uFB8D",
+ 'rrvocalicbengali' : u"\u09E0",
+ 'rrvocalicdeva' : u"\u0960",
+ 'rrvocalicgujarati' : u"\u0AE0",
+ 'rrvocalicvowelsignbengali' : u"\u09C4",
+ 'rrvocalicvowelsigndeva' : u"\u0944",
+ 'rrvocalicvowelsigngujarati' : u"\u0AC4",
+ 'rsuperior' : u"\uF6F1",
+ 'rturned' : u"\u0279",
+ 'rturnedsuperior' : u"\u02B4",
+ 'ruhiragana' : u"\u308B",
+ 'rukatakana' : u"\u30EB",
+ 'rukatakanahalfwidth' : u"\uFF99",
+ 'rupeemarkbengali' : u"\u09F2",
+ 'rupeesignbengali' : u"\u09F3",
+ 'rupiah' : u"\uF6DD",
+ 'ruthai' : u"\u0E24",
+ 'rvocalicbengali' : u"\u098B",
+ 'rvocalicdeva' : u"\u090B",
+ 'rvocalicgujarati' : u"\u0A8B",
+ 'rvocalicvowelsignbengali' : u"\u09C3",
+ 'rvocalicvowelsigndeva' : u"\u0943",
+ 'rvocalicvowelsigngujarati' : u"\u0AC3",
+ 'sabengali' : u"\u09B8",
+ 'sacutedotaccent' : u"\u1E65",
+ 'sadarabic' : u"\u0635",
+ 'sadeva' : u"\u0938",
+ 'sadfinalarabic' : u"\uFEBA",
+ 'sadinitialarabic' : u"\uFEBB",
+ 'sadmedialarabic' : u"\uFEBC",
+ 'sagujarati' : u"\u0AB8",
+ 'sagurmukhi' : u"\u0A38",
+ 'sahiragana' : u"\u3055",
+ 'sakatakana' : u"\u30B5",
+ 'sakatakanahalfwidth' : u"\uFF7B",
+ 'sallallahoualayhewasallamarabic' : u"\uFDFA",
+ 'samekh' : u"\u05E1",
+ 'samekhdagesh' : u"\uFB41",
+ 'samekhdageshhebrew' : u"\uFB41",
+ 'samekhhebrew' : u"\u05E1",
+ 'saraaathai' : u"\u0E32",
+ 'saraaethai' : u"\u0E41",
+ 'saraaimaimalaithai' : u"\u0E44",
+ 'saraaimaimuanthai' : u"\u0E43",
+ 'saraamthai' : u"\u0E33",
+ 'saraathai' : u"\u0E30",
+ 'saraethai' : u"\u0E40",
+ 'saraiileftthai' : u"\uF886",
+ 'saraiithai' : u"\u0E35",
+ 'saraileftthai' : u"\uF885",
+ 'saraithai' : u"\u0E34",
+ 'saraothai' : u"\u0E42",
+ 'saraueeleftthai' : u"\uF888",
+ 'saraueethai' : u"\u0E37",
+ 'saraueleftthai' : u"\uF887",
+ 'sarauethai' : u"\u0E36",
+ 'sarauthai' : u"\u0E38",
+ 'sarauuthai' : u"\u0E39",
+ 'sbopomofo' : u"\u3119",
+ 'scarondotaccent' : u"\u1E67",
+ 'schwa' : u"\u0259",
+ 'schwacyrillic' : u"\u04D9",
+ 'schwadieresiscyrillic' : u"\u04DB",
+ 'schwahook' : u"\u025A",
+ 'scircle' : u"\u24E2",
+ 'sdotaccent' : u"\u1E61",
+ 'sdotbelow' : u"\u1E63",
+ 'sdotbelowdotaccent' : u"\u1E69",
+ 'seagullbelowcmb' : u"\u033C",
+ 'secondtonechinese' : u"\u02CA",
+ 'seenarabic' : u"\u0633",
+ 'seenfinalarabic' : u"\uFEB2",
+ 'seeninitialarabic' : u"\uFEB3",
+ 'seenmedialarabic' : u"\uFEB4",
+ 'segol' : u"\u05B6",
+ 'segol13' : u"\u05B6",
+ 'segol1f' : u"\u05B6",
+ 'segol2c' : u"\u05B6",
+ 'segolhebrew' : u"\u05B6",
+ 'segolnarrowhebrew' : u"\u05B6",
+ 'segolquarterhebrew' : u"\u05B6",
+ 'segoltahebrew' : u"\u0592",
+ 'segolwidehebrew' : u"\u05B6",
+ 'seharmenian' : u"\u057D",
+ 'sehiragana' : u"\u305B",
+ 'sekatakana' : u"\u30BB",
+ 'sekatakanahalfwidth' : u"\uFF7E",
+ 'semicolonarabic' : u"\u061B",
+ 'semicolonmonospace' : u"\uFF1B",
+ 'semicolonsmall' : u"\uFE54",
+ 'semivoicedmarkkana' : u"\u309C",
+ 'semivoicedmarkkanahalfwidth' : u"\uFF9F",
+ 'sentisquare' : u"\u3322",
+ 'sentosquare' : u"\u3323",
+ 'sevenarabic' : u"\u0667",
+ 'sevenbengali' : u"\u09ED",
+ 'sevencircle' : u"\u2466",
+ 'sevencircleinversesansserif' : u"\u2790",
+ 'sevendeva' : u"\u096D",
+ 'sevengujarati' : u"\u0AED",
+ 'sevengurmukhi' : u"\u0A6D",
+ 'sevenhackarabic' : u"\u0667",
+ 'sevenhangzhou' : u"\u3027",
+ 'sevenideographicparen' : u"\u3226",
+ 'seveninferior' : u"\u2087",
+ 'sevenmonospace' : u"\uFF17",
+ 'sevenoldstyle' : u"\uF737",
+ 'sevenparen' : u"\u247A",
+ 'sevenperiod' : u"\u248E",
+ 'sevenpersian' : u"\u06F7",
+ 'sevenroman' : u"\u2176",
+ 'sevensuperior' : u"\u2077",
+ 'seventeencircle' : u"\u2470",
+ 'seventeenparen' : u"\u2484",
+ 'seventeenperiod' : u"\u2498",
+ 'seventhai' : u"\u0E57",
+ 'sfthyphen' : u"\u00AD",
+ 'shaarmenian' : u"\u0577",
+ 'shabengali' : u"\u09B6",
+ 'shacyrillic' : u"\u0448",
+ 'shaddaarabic' : u"\u0651",
+ 'shaddadammaarabic' : u"\uFC61",
+ 'shaddadammatanarabic' : u"\uFC5E",
+ 'shaddafathaarabic' : u"\uFC60",
+ 'shaddafathatanarabic' : u"\u0651\u064B",
+ 'shaddakasraarabic' : u"\uFC62",
+ 'shaddakasratanarabic' : u"\uFC5F",
+ 'shadedark' : u"\u2593",
+ 'shadelight' : u"\u2591",
+ 'shademedium' : u"\u2592",
+ 'shadeva' : u"\u0936",
+ 'shagujarati' : u"\u0AB6",
+ 'shagurmukhi' : u"\u0A36",
+ 'shalshelethebrew' : u"\u0593",
+ 'shbopomofo' : u"\u3115",
+ 'shchacyrillic' : u"\u0449",
+ 'sheenarabic' : u"\u0634",
+ 'sheenfinalarabic' : u"\uFEB6",
+ 'sheeninitialarabic' : u"\uFEB7",
+ 'sheenmedialarabic' : u"\uFEB8",
+ 'sheicoptic' : u"\u03E3",
+ 'sheqel' : u"\u20AA",
+ 'sheqelhebrew' : u"\u20AA",
+ 'sheva' : u"\u05B0",
+ 'sheva115' : u"\u05B0",
+ 'sheva15' : u"\u05B0",
+ 'sheva22' : u"\u05B0",
+ 'sheva2e' : u"\u05B0",
+ 'shevahebrew' : u"\u05B0",
+ 'shevanarrowhebrew' : u"\u05B0",
+ 'shevaquarterhebrew' : u"\u05B0",
+ 'shevawidehebrew' : u"\u05B0",
+ 'shhacyrillic' : u"\u04BB",
+ 'shimacoptic' : u"\u03ED",
+ 'shin' : u"\u05E9",
+ 'shindagesh' : u"\uFB49",
+ 'shindageshhebrew' : u"\uFB49",
+ 'shindageshshindot' : u"\uFB2C",
+ 'shindageshshindothebrew' : u"\uFB2C",
+ 'shindageshsindot' : u"\uFB2D",
+ 'shindageshsindothebrew' : u"\uFB2D",
+ 'shindothebrew' : u"\u05C1",
+ 'shinhebrew' : u"\u05E9",
+ 'shinshindot' : u"\uFB2A",
+ 'shinshindothebrew' : u"\uFB2A",
+ 'shinsindot' : u"\uFB2B",
+ 'shinsindothebrew' : u"\uFB2B",
+ 'shook' : u"\u0282",
+ 'sigmafinal' : u"\u03C2",
+ 'sigmalunatesymbolgreek' : u"\u03F2",
+ 'sihiragana' : u"\u3057",
+ 'sikatakana' : u"\u30B7",
+ 'sikatakanahalfwidth' : u"\uFF7C",
+ 'siluqhebrew' : u"\u05BD",
+ 'siluqlefthebrew' : u"\u05BD",
+ 'sindothebrew' : u"\u05C2",
+ 'siosacirclekorean' : u"\u3274",
+ 'siosaparenkorean' : u"\u3214",
+ 'sioscieuckorean' : u"\u317E",
+ 'sioscirclekorean' : u"\u3266",
+ 'sioskiyeokkorean' : u"\u317A",
+ 'sioskorean' : u"\u3145",
+ 'siosnieunkorean' : u"\u317B",
+ 'siosparenkorean' : u"\u3206",
+ 'siospieupkorean' : u"\u317D",
+ 'siostikeutkorean' : u"\u317C",
+ 'sixarabic' : u"\u0666",
+ 'sixbengali' : u"\u09EC",
+ 'sixcircle' : u"\u2465",
+ 'sixcircleinversesansserif' : u"\u278F",
+ 'sixdeva' : u"\u096C",
+ 'sixgujarati' : u"\u0AEC",
+ 'sixgurmukhi' : u"\u0A6C",
+ 'sixhackarabic' : u"\u0666",
+ 'sixhangzhou' : u"\u3026",
+ 'sixideographicparen' : u"\u3225",
+ 'sixinferior' : u"\u2086",
+ 'sixmonospace' : u"\uFF16",
+ 'sixoldstyle' : u"\uF736",
+ 'sixparen' : u"\u2479",
+ 'sixperiod' : u"\u248D",
+ 'sixpersian' : u"\u06F6",
+ 'sixroman' : u"\u2175",
+ 'sixsuperior' : u"\u2076",
+ 'sixteencircle' : u"\u246F",
+ 'sixteencurrencydenominatorbengali' : u"\u09F9",
+ 'sixteenparen' : u"\u2483",
+ 'sixteenperiod' : u"\u2497",
+ 'sixthai' : u"\u0E56",
+ 'slashmonospace' : u"\uFF0F",
+ 'slong' : u"\u017F",
+ 'slongdotaccent' : u"\u1E9B",
+ 'smonospace' : u"\uFF53",
+ 'sofpasuqhebrew' : u"\u05C3",
+ 'softhyphen' : u"\u00AD",
+ 'softsigncyrillic' : u"\u044C",
+ 'sohiragana' : u"\u305D",
+ 'sokatakana' : u"\u30BD",
+ 'sokatakanahalfwidth' : u"\uFF7F",
+ 'soliduslongoverlaycmb' : u"\u0338",
+ 'solidusshortoverlaycmb' : u"\u0337",
+ 'sorusithai' : u"\u0E29",
+ 'sosalathai' : u"\u0E28",
+ 'sosothai' : u"\u0E0B",
+ 'sosuathai' : u"\u0E2A",
+ 'spacehackarabic' : u"\u0020",
+ 'spadesuitblack' : u"\u2660",
+ 'spadesuitwhite' : u"\u2664",
+ 'sparen' : u"\u24AE",
+ 'squarebelowcmb' : u"\u033B",
+ 'squarecc' : u"\u33C4",
+ 'squarecm' : u"\u339D",
+ 'squarediagonalcrosshatchfill' : u"\u25A9",
+ 'squarehorizontalfill' : u"\u25A4",
+ 'squarekg' : u"\u338F",
+ 'squarekm' : u"\u339E",
+ 'squarekmcapital' : u"\u33CE",
+ 'squareln' : u"\u33D1",
+ 'squarelog' : u"\u33D2",
+ 'squaremg' : u"\u338E",
+ 'squaremil' : u"\u33D5",
+ 'squaremm' : u"\u339C",
+ 'squaremsquared' : u"\u33A1",
+ 'squareorthogonalcrosshatchfill' : u"\u25A6",
+ 'squareupperlefttolowerrightfill' : u"\u25A7",
+ 'squareupperrighttolowerleftfill' : u"\u25A8",
+ 'squareverticalfill' : u"\u25A5",
+ 'squarewhitewithsmallblack' : u"\u25A3",
+ 'srsquare' : u"\u33DB",
+ 'ssabengali' : u"\u09B7",
+ 'ssadeva' : u"\u0937",
+ 'ssagujarati' : u"\u0AB7",
+ 'ssangcieuckorean' : u"\u3149",
+ 'ssanghieuhkorean' : u"\u3185",
+ 'ssangieungkorean' : u"\u3180",
+ 'ssangkiyeokkorean' : u"\u3132",
+ 'ssangnieunkorean' : u"\u3165",
+ 'ssangpieupkorean' : u"\u3143",
+ 'ssangsioskorean' : u"\u3146",
+ 'ssangtikeutkorean' : u"\u3138",
+ 'ssuperior' : u"\uF6F2",
+ 'sterlingmonospace' : u"\uFFE1",
+ 'strokelongoverlaycmb' : u"\u0336",
+ 'strokeshortoverlaycmb' : u"\u0335",
+ 'subset' : u"\u2282",
+ 'subsetnotequal' : u"\u228A",
+ 'subsetorequal' : u"\u2286",
+ 'succeeds' : u"\u227B",
+ 'suhiragana' : u"\u3059",
+ 'sukatakana' : u"\u30B9",
+ 'sukatakanahalfwidth' : u"\uFF7D",
+ 'sukunarabic' : u"\u0652",
+ 'superset' : u"\u2283",
+ 'supersetnotequal' : u"\u228B",
+ 'supersetorequal' : u"\u2287",
+ 'svsquare' : u"\u33DC",
+ 'syouwaerasquare' : u"\u337C",
+ 'tabengali' : u"\u09A4",
+ 'tackdown' : u"\u22A4",
+ 'tackleft' : u"\u22A3",
+ 'tadeva' : u"\u0924",
+ 'tagujarati' : u"\u0AA4",
+ 'tagurmukhi' : u"\u0A24",
+ 'taharabic' : u"\u0637",
+ 'tahfinalarabic' : u"\uFEC2",
+ 'tahinitialarabic' : u"\uFEC3",
+ 'tahiragana' : u"\u305F",
+ 'tahmedialarabic' : u"\uFEC4",
+ 'taisyouerasquare' : u"\u337D",
+ 'takatakana' : u"\u30BF",
+ 'takatakanahalfwidth' : u"\uFF80",
+ 'tatweelarabic' : u"\u0640",
+ 'tav' : u"\u05EA",
+ 'tavdages' : u"\uFB4A",
+ 'tavdagesh' : u"\uFB4A",
+ 'tavdageshhebrew' : u"\uFB4A",
+ 'tavhebrew' : u"\u05EA",
+ 'tbopomofo' : u"\u310A",
+ 'tccurl' : u"\u02A8",
+ 'tcedilla' : u"\u0163",
+ 'tcheharabic' : u"\u0686",
+ 'tchehfinalarabic' : u"\uFB7B",
+ 'tchehinitialarabic' : u"\uFB7C",
+ 'tchehmedialarabic' : u"\uFB7D",
+ 'tchehmeeminitialarabic' : u"\uFB7C\uFEE4",
+ 'tcircle' : u"\u24E3",
+ 'tcircumflexbelow' : u"\u1E71",
+ 'tdieresis' : u"\u1E97",
+ 'tdotaccent' : u"\u1E6B",
+ 'tdotbelow' : u"\u1E6D",
+ 'tecyrillic' : u"\u0442",
+ 'tedescendercyrillic' : u"\u04AD",
+ 'teharabic' : u"\u062A",
+ 'tehfinalarabic' : u"\uFE96",
+ 'tehhahinitialarabic' : u"\uFCA2",
+ 'tehhahisolatedarabic' : u"\uFC0C",
+ 'tehinitialarabic' : u"\uFE97",
+ 'tehiragana' : u"\u3066",
+ 'tehjeeminitialarabic' : u"\uFCA1",
+ 'tehjeemisolatedarabic' : u"\uFC0B",
+ 'tehmarbutaarabic' : u"\u0629",
+ 'tehmarbutafinalarabic' : u"\uFE94",
+ 'tehmedialarabic' : u"\uFE98",
+ 'tehmeeminitialarabic' : u"\uFCA4",
+ 'tehmeemisolatedarabic' : u"\uFC0E",
+ 'tehnoonfinalarabic' : u"\uFC73",
+ 'tekatakana' : u"\u30C6",
+ 'tekatakanahalfwidth' : u"\uFF83",
+ 'telephone' : u"\u2121",
+ 'telephoneblack' : u"\u260E",
+ 'telishagedolahebrew' : u"\u05A0",
+ 'telishaqetanahebrew' : u"\u05A9",
+ 'tencircle' : u"\u2469",
+ 'tenideographicparen' : u"\u3229",
+ 'tenparen' : u"\u247D",
+ 'tenperiod' : u"\u2491",
+ 'tenroman' : u"\u2179",
+ 'tesh' : u"\u02A7",
+ 'tet' : u"\u05D8",
+ 'tetdagesh' : u"\uFB38",
+ 'tetdageshhebrew' : u"\uFB38",
+ 'tethebrew' : u"\u05D8",
+ 'tetsecyrillic' : u"\u04B5",
+ 'tevirhebrew' : u"\u059B",
+ 'tevirlefthebrew' : u"\u059B",
+ 'thabengali' : u"\u09A5",
+ 'thadeva' : u"\u0925",
+ 'thagujarati' : u"\u0AA5",
+ 'thagurmukhi' : u"\u0A25",
+ 'thalarabic' : u"\u0630",
+ 'thalfinalarabic' : u"\uFEAC",
+ 'thanthakhatlowleftthai' : u"\uF898",
+ 'thanthakhatlowrightthai' : u"\uF897",
+ 'thanthakhatthai' : u"\u0E4C",
+ 'thanthakhatupperleftthai' : u"\uF896",
+ 'theharabic' : u"\u062B",
+ 'thehfinalarabic' : u"\uFE9A",
+ 'thehinitialarabic' : u"\uFE9B",
+ 'thehmedialarabic' : u"\uFE9C",
+ 'thereexists' : u"\u2203",
+ 'thetasymbolgreek' : u"\u03D1",
+ 'thieuthacirclekorean' : u"\u3279",
+ 'thieuthaparenkorean' : u"\u3219",
+ 'thieuthcirclekorean' : u"\u326B",
+ 'thieuthkorean' : u"\u314C",
+ 'thieuthparenkorean' : u"\u320B",
+ 'thirteencircle' : u"\u246C",
+ 'thirteenparen' : u"\u2480",
+ 'thirteenperiod' : u"\u2494",
+ 'thonangmonthothai' : u"\u0E11",
+ 'thook' : u"\u01AD",
+ 'thophuthaothai' : u"\u0E12",
+ 'thothahanthai' : u"\u0E17",
+ 'thothanthai' : u"\u0E10",
+ 'thothongthai' : u"\u0E18",
+ 'thothungthai' : u"\u0E16",
+ 'thousandcyrillic' : u"\u0482",
+ 'thousandsseparatorarabic' : u"\u066C",
+ 'thousandsseparatorpersian' : u"\u066C",
+ 'threearabic' : u"\u0663",
+ 'threebengali' : u"\u09E9",
+ 'threecircle' : u"\u2462",
+ 'threecircleinversesansserif' : u"\u278C",
+ 'threedeva' : u"\u0969",
+ 'threegujarati' : u"\u0AE9",
+ 'threegurmukhi' : u"\u0A69",
+ 'threehackarabic' : u"\u0663",
+ 'threehangzhou' : u"\u3023",
+ 'threeideographicparen' : u"\u3222",
+ 'threeinferior' : u"\u2083",
+ 'threemonospace' : u"\uFF13",
+ 'threenumeratorbengali' : u"\u09F6",
+ 'threeoldstyle' : u"\uF733",
+ 'threeparen' : u"\u2476",
+ 'threeperiod' : u"\u248A",
+ 'threepersian' : u"\u06F3",
+ 'threequartersemdash' : u"\uF6DE",
+ 'threeroman' : u"\u2172",
+ 'threethai' : u"\u0E53",
+ 'thzsquare' : u"\u3394",
+ 'tihiragana' : u"\u3061",
+ 'tikatakana' : u"\u30C1",
+ 'tikatakanahalfwidth' : u"\uFF81",
+ 'tikeutacirclekorean' : u"\u3270",
+ 'tikeutaparenkorean' : u"\u3210",
+ 'tikeutcirclekorean' : u"\u3262",
+ 'tikeutkorean' : u"\u3137",
+ 'tikeutparenkorean' : u"\u3202",
+ 'tildebelowcmb' : u"\u0330",
+ 'tildecmb' : u"\u0303",
+ 'tildedoublecmb' : u"\u0360",
+ 'tildeoperator' : u"\u223C",
+ 'tildeoverlaycmb' : u"\u0334",
+ 'tildeverticalcmb' : u"\u033E",
+ 'timescircle' : u"\u2297",
+ 'tipehahebrew' : u"\u0596",
+ 'tipehalefthebrew' : u"\u0596",
+ 'tippigurmukhi' : u"\u0A70",
+ 'titlocyrilliccmb' : u"\u0483",
+ 'tiwnarmenian' : u"\u057F",
+ 'tlinebelow' : u"\u1E6F",
+ 'tmonospace' : u"\uFF54",
+ 'toarmenian' : u"\u0569",
+ 'tohiragana' : u"\u3068",
+ 'tokatakana' : u"\u30C8",
+ 'tokatakanahalfwidth' : u"\uFF84",
+ 'tonebarextrahighmod' : u"\u02E5",
+ 'tonebarextralowmod' : u"\u02E9",
+ 'tonebarhighmod' : u"\u02E6",
+ 'tonebarlowmod' : u"\u02E8",
+ 'tonebarmidmod' : u"\u02E7",
+ 'tonefive' : u"\u01BD",
+ 'tonesix' : u"\u0185",
+ 'tonetwo' : u"\u01A8",
+ 'tonsquare' : u"\u3327",
+ 'topatakthai' : u"\u0E0F",
+ 'tortoiseshellbracketleft' : u"\u3014",
+ 'tortoiseshellbracketleftsmall' : u"\uFE5D",
+ 'tortoiseshellbracketleftvertical' : u"\uFE39",
+ 'tortoiseshellbracketright' : u"\u3015",
+ 'tortoiseshellbracketrightsmall' : u"\uFE5E",
+ 'tortoiseshellbracketrightvertical' : u"\uFE3A",
+ 'totaothai' : u"\u0E15",
+ 'tpalatalhook' : u"\u01AB",
+ 'tparen' : u"\u24AF",
+ 'trademarksans' : u"\uF8EA",
+ 'trademarkserif' : u"\uF6DB",
+ 'tretroflexhook' : u"\u0288",
+ 'ts' : u"\u02A6",
+ 'tsadi' : u"\u05E6",
+ 'tsadidagesh' : u"\uFB46",
+ 'tsadidageshhebrew' : u"\uFB46",
+ 'tsadihebrew' : u"\u05E6",
+ 'tsecyrillic' : u"\u0446",
+ 'tsere' : u"\u05B5",
+ 'tsere12' : u"\u05B5",
+ 'tsere1e' : u"\u05B5",
+ 'tsere2b' : u"\u05B5",
+ 'tserehebrew' : u"\u05B5",
+ 'tserenarrowhebrew' : u"\u05B5",
+ 'tserequarterhebrew' : u"\u05B5",
+ 'tserewidehebrew' : u"\u05B5",
+ 'tshecyrillic' : u"\u045B",
+ 'tsuperior' : u"\uF6F3",
+ 'ttabengali' : u"\u099F",
+ 'ttadeva' : u"\u091F",
+ 'ttagujarati' : u"\u0A9F",
+ 'ttagurmukhi' : u"\u0A1F",
+ 'tteharabic' : u"\u0679",
+ 'ttehfinalarabic' : u"\uFB67",
+ 'ttehinitialarabic' : u"\uFB68",
+ 'ttehmedialarabic' : u"\uFB69",
+ 'tthabengali' : u"\u09A0",
+ 'tthadeva' : u"\u0920",
+ 'tthagujarati' : u"\u0AA0",
+ 'tthagurmukhi' : u"\u0A20",
+ 'tturned' : u"\u0287",
+ 'tuhiragana' : u"\u3064",
+ 'tukatakana' : u"\u30C4",
+ 'tukatakanahalfwidth' : u"\uFF82",
+ 'tusmallhiragana' : u"\u3063",
+ 'tusmallkatakana' : u"\u30C3",
+ 'tusmallkatakanahalfwidth' : u"\uFF6F",
+ 'twelvecircle' : u"\u246B",
+ 'twelveparen' : u"\u247F",
+ 'twelveperiod' : u"\u2493",
+ 'twelveroman' : u"\u217B",
+ 'twentycircle' : u"\u2473",
+ 'twentyhangzhou' : u"\u5344",
+ 'twentyparen' : u"\u2487",
+ 'twentyperiod' : u"\u249B",
+ 'twoarabic' : u"\u0662",
+ 'twobengali' : u"\u09E8",
+ 'twocircle' : u"\u2461",
+ 'twocircleinversesansserif' : u"\u278B",
+ 'twodeva' : u"\u0968",
+ 'twodotleader' : u"\u2025",
+ 'twodotleadervertical' : u"\uFE30",
+ 'twogujarati' : u"\u0AE8",
+ 'twogurmukhi' : u"\u0A68",
+ 'twohackarabic' : u"\u0662",
+ 'twohangzhou' : u"\u3022",
+ 'twoideographicparen' : u"\u3221",
+ 'twoinferior' : u"\u2082",
+ 'twomonospace' : u"\uFF12",
+ 'twonumeratorbengali' : u"\u09F5",
+ 'twooldstyle' : u"\uF732",
+ 'twoparen' : u"\u2475",
+ 'twoperiod' : u"\u2489",
+ 'twopersian' : u"\u06F2",
+ 'tworoman' : u"\u2171",
+ 'twostroke' : u"\u01BB",
+ 'twothai' : u"\u0E52",
+ 'ubar' : u"\u0289",
+ 'ubengali' : u"\u0989",
+ 'ubopomofo' : u"\u3128",
+ 'ucaron' : u"\u01D4",
+ 'ucircle' : u"\u24E4",
+ 'ucircumflexbelow' : u"\u1E77",
+ 'ucyrillic' : u"\u0443",
+ 'udattadeva' : u"\u0951",
+ 'udblacute' : u"\u0171",
+ 'udblgrave' : u"\u0215",
+ 'udeva' : u"\u0909",
+ 'udieresisacute' : u"\u01D8",
+ 'udieresisbelow' : u"\u1E73",
+ 'udieresiscaron' : u"\u01DA",
+ 'udieresiscyrillic' : u"\u04F1",
+ 'udieresisgrave' : u"\u01DC",
+ 'udieresismacron' : u"\u01D6",
+ 'udotbelow' : u"\u1EE5",
+ 'ugujarati' : u"\u0A89",
+ 'ugurmukhi' : u"\u0A09",
+ 'uhiragana' : u"\u3046",
+ 'uhookabove' : u"\u1EE7",
+ 'uhornacute' : u"\u1EE9",
+ 'uhorndotbelow' : u"\u1EF1",
+ 'uhorngrave' : u"\u1EEB",
+ 'uhornhookabove' : u"\u1EED",
+ 'uhorntilde' : u"\u1EEF",
+ 'uhungarumlautcyrillic' : u"\u04F3",
+ 'uinvertedbreve' : u"\u0217",
+ 'ukatakana' : u"\u30A6",
+ 'ukatakanahalfwidth' : u"\uFF73",
+ 'ukcyrillic' : u"\u0479",
+ 'ukorean' : u"\u315C",
+ 'umacroncyrillic' : u"\u04EF",
+ 'umacrondieresis' : u"\u1E7B",
+ 'umatragurmukhi' : u"\u0A41",
+ 'umonospace' : u"\uFF55",
+ 'underscoremonospace' : u"\uFF3F",
+ 'underscorevertical' : u"\uFE33",
+ 'underscorewavy' : u"\uFE4F",
+ 'uparen' : u"\u24B0",
+ 'upperdothebrew' : u"\u05C4",
+ 'upsilonlatin' : u"\u028A",
+ 'uptackbelowcmb' : u"\u031D",
+ 'uptackmod' : u"\u02D4",
+ 'uragurmukhi' : u"\u0A73",
+ 'ushortcyrillic' : u"\u045E",
+ 'usmallhiragana' : u"\u3045",
+ 'usmallkatakana' : u"\u30A5",
+ 'usmallkatakanahalfwidth' : u"\uFF69",
+ 'ustraightcyrillic' : u"\u04AF",
+ 'ustraightstrokecyrillic' : u"\u04B1",
+ 'utildeacute' : u"\u1E79",
+ 'utildebelow' : u"\u1E75",
+ 'uubengali' : u"\u098A",
+ 'uudeva' : u"\u090A",
+ 'uugujarati' : u"\u0A8A",
+ 'uugurmukhi' : u"\u0A0A",
+ 'uumatragurmukhi' : u"\u0A42",
+ 'uuvowelsignbengali' : u"\u09C2",
+ 'uuvowelsigndeva' : u"\u0942",
+ 'uuvowelsigngujarati' : u"\u0AC2",
+ 'uvowelsignbengali' : u"\u09C1",
+ 'uvowelsigndeva' : u"\u0941",
+ 'uvowelsigngujarati' : u"\u0AC1",
+ 'vadeva' : u"\u0935",
+ 'vagujarati' : u"\u0AB5",
+ 'vagurmukhi' : u"\u0A35",
+ 'vakatakana' : u"\u30F7",
+ 'vav' : u"\u05D5",
+ 'vavdagesh' : u"\uFB35",
+ 'vavdagesh65' : u"\uFB35",
+ 'vavdageshhebrew' : u"\uFB35",
+ 'vavhebrew' : u"\u05D5",
+ 'vavholam' : u"\uFB4B",
+ 'vavholamhebrew' : u"\uFB4B",
+ 'vavvavhebrew' : u"\u05F0",
+ 'vavyodhebrew' : u"\u05F1",
+ 'vcircle' : u"\u24E5",
+ 'vdotbelow' : u"\u1E7F",
+ 'vecyrillic' : u"\u0432",
+ 'veharabic' : u"\u06A4",
+ 'vehfinalarabic' : u"\uFB6B",
+ 'vehinitialarabic' : u"\uFB6C",
+ 'vehmedialarabic' : u"\uFB6D",
+ 'vekatakana' : u"\u30F9",
+ 'venus' : u"\u2640",
+ 'verticalbar' : u"\u007C",
+ 'verticallineabovecmb' : u"\u030D",
+ 'verticallinebelowcmb' : u"\u0329",
+ 'verticallinelowmod' : u"\u02CC",
+ 'verticallinemod' : u"\u02C8",
+ 'vewarmenian' : u"\u057E",
+ 'vhook' : u"\u028B",
+ 'vikatakana' : u"\u30F8",
+ 'viramabengali' : u"\u09CD",
+ 'viramadeva' : u"\u094D",
+ 'viramagujarati' : u"\u0ACD",
+ 'visargabengali' : u"\u0983",
+ 'visargadeva' : u"\u0903",
+ 'visargagujarati' : u"\u0A83",
+ 'vmonospace' : u"\uFF56",
+ 'voarmenian' : u"\u0578",
+ 'voicediterationhiragana' : u"\u309E",
+ 'voicediterationkatakana' : u"\u30FE",
+ 'voicedmarkkana' : u"\u309B",
+ 'voicedmarkkanahalfwidth' : u"\uFF9E",
+ 'vokatakana' : u"\u30FA",
+ 'vparen' : u"\u24B1",
+ 'vtilde' : u"\u1E7D",
+ 'vturned' : u"\u028C",
+ 'vuhiragana' : u"\u3094",
+ 'vukatakana' : u"\u30F4",
+ 'waekorean' : u"\u3159",
+ 'wahiragana' : u"\u308F",
+ 'wakatakana' : u"\u30EF",
+ 'wakatakanahalfwidth' : u"\uFF9C",
+ 'wakorean' : u"\u3158",
+ 'wasmallhiragana' : u"\u308E",
+ 'wasmallkatakana' : u"\u30EE",
+ 'wattosquare' : u"\u3357",
+ 'wavedash' : u"\u301C",
+ 'wavyunderscorevertical' : u"\uFE34",
+ 'wawarabic' : u"\u0648",
+ 'wawfinalarabic' : u"\uFEEE",
+ 'wawhamzaabovearabic' : u"\u0624",
+ 'wawhamzaabovefinalarabic' : u"\uFE86",
+ 'wbsquare' : u"\u33DD",
+ 'wcircle' : u"\u24E6",
+ 'wdotaccent' : u"\u1E87",
+ 'wdotbelow' : u"\u1E89",
+ 'wehiragana' : u"\u3091",
+ 'wekatakana' : u"\u30F1",
+ 'wekorean' : u"\u315E",
+ 'weokorean' : u"\u315D",
+ 'whitebullet' : u"\u25E6",
+ 'whitecircle' : u"\u25CB",
+ 'whitecircleinverse' : u"\u25D9",
+ 'whitecornerbracketleft' : u"\u300E",
+ 'whitecornerbracketleftvertical' : u"\uFE43",
+ 'whitecornerbracketright' : u"\u300F",
+ 'whitecornerbracketrightvertical' : u"\uFE44",
+ 'whitediamond' : u"\u25C7",
+ 'whitediamondcontainingblacksmalldiamond' : u"\u25C8",
+ 'whitedownpointingsmalltriangle' : u"\u25BF",
+ 'whitedownpointingtriangle' : u"\u25BD",
+ 'whiteleftpointingsmalltriangle' : u"\u25C3",
+ 'whiteleftpointingtriangle' : u"\u25C1",
+ 'whitelenticularbracketleft' : u"\u3016",
+ 'whitelenticularbracketright' : u"\u3017",
+ 'whiterightpointingsmalltriangle' : u"\u25B9",
+ 'whiterightpointingtriangle' : u"\u25B7",
+ 'whitesmallsquare' : u"\u25AB",
+ 'whitesmilingface' : u"\u263A",
+ 'whitesquare' : u"\u25A1",
+ 'whitestar' : u"\u2606",
+ 'whitetelephone' : u"\u260F",
+ 'whitetortoiseshellbracketleft' : u"\u3018",
+ 'whitetortoiseshellbracketright' : u"\u3019",
+ 'whiteuppointingsmalltriangle' : u"\u25B5",
+ 'whiteuppointingtriangle' : u"\u25B3",
+ 'wihiragana' : u"\u3090",
+ 'wikatakana' : u"\u30F0",
+ 'wikorean' : u"\u315F",
+ 'wmonospace' : u"\uFF57",
+ 'wohiragana' : u"\u3092",
+ 'wokatakana' : u"\u30F2",
+ 'wokatakanahalfwidth' : u"\uFF66",
+ 'won' : u"\u20A9",
+ 'wonmonospace' : u"\uFFE6",
+ 'wowaenthai' : u"\u0E27",
+ 'wparen' : u"\u24B2",
+ 'wring' : u"\u1E98",
+ 'wsuperior' : u"\u02B7",
+ 'wturned' : u"\u028D",
+ 'wynn' : u"\u01BF",
+ 'xabovecmb' : u"\u033D",
+ 'xbopomofo' : u"\u3112",
+ 'xcircle' : u"\u24E7",
+ 'xdieresis' : u"\u1E8D",
+ 'xdotaccent' : u"\u1E8B",
+ 'xeharmenian' : u"\u056D",
+ 'xmonospace' : u"\uFF58",
+ 'xparen' : u"\u24B3",
+ 'xsuperior' : u"\u02E3",
+ 'yaadosquare' : u"\u334E",
+ 'yabengali' : u"\u09AF",
+ 'yadeva' : u"\u092F",
+ 'yaekorean' : u"\u3152",
+ 'yagujarati' : u"\u0AAF",
+ 'yagurmukhi' : u"\u0A2F",
+ 'yahiragana' : u"\u3084",
+ 'yakatakana' : u"\u30E4",
+ 'yakatakanahalfwidth' : u"\uFF94",
+ 'yakorean' : u"\u3151",
+ 'yamakkanthai' : u"\u0E4E",
+ 'yasmallhiragana' : u"\u3083",
+ 'yasmallkatakana' : u"\u30E3",
+ 'yasmallkatakanahalfwidth' : u"\uFF6C",
+ 'yatcyrillic' : u"\u0463",
+ 'ycircle' : u"\u24E8",
+ 'ydotaccent' : u"\u1E8F",
+ 'ydotbelow' : u"\u1EF5",
+ 'yeharabic' : u"\u064A",
+ 'yehbarreearabic' : u"\u06D2",
+ 'yehbarreefinalarabic' : u"\uFBAF",
+ 'yehfinalarabic' : u"\uFEF2",
+ 'yehhamzaabovearabic' : u"\u0626",
+ 'yehhamzaabovefinalarabic' : u"\uFE8A",
+ 'yehhamzaaboveinitialarabic' : u"\uFE8B",
+ 'yehhamzaabovemedialarabic' : u"\uFE8C",
+ 'yehinitialarabic' : u"\uFEF3",
+ 'yehmedialarabic' : u"\uFEF4",
+ 'yehmeeminitialarabic' : u"\uFCDD",
+ 'yehmeemisolatedarabic' : u"\uFC58",
+ 'yehnoonfinalarabic' : u"\uFC94",
+ 'yehthreedotsbelowarabic' : u"\u06D1",
+ 'yekorean' : u"\u3156",
+ 'yenmonospace' : u"\uFFE5",
+ 'yeokorean' : u"\u3155",
+ 'yeorinhieuhkorean' : u"\u3186",
+ 'yerahbenyomohebrew' : u"\u05AA",
+ 'yerahbenyomolefthebrew' : u"\u05AA",
+ 'yericyrillic' : u"\u044B",
+ 'yerudieresiscyrillic' : u"\u04F9",
+ 'yesieungkorean' : u"\u3181",
+ 'yesieungpansioskorean' : u"\u3183",
+ 'yesieungsioskorean' : u"\u3182",
+ 'yetivhebrew' : u"\u059A",
+ 'yhook' : u"\u01B4",
+ 'yhookabove' : u"\u1EF7",
+ 'yiarmenian' : u"\u0575",
+ 'yicyrillic' : u"\u0457",
+ 'yikorean' : u"\u3162",
+ 'yinyang' : u"\u262F",
+ 'yiwnarmenian' : u"\u0582",
+ 'ymonospace' : u"\uFF59",
+ 'yod' : u"\u05D9",
+ 'yoddagesh' : u"\uFB39",
+ 'yoddageshhebrew' : u"\uFB39",
+ 'yodhebrew' : u"\u05D9",
+ 'yodyodhebrew' : u"\u05F2",
+ 'yodyodpatahhebrew' : u"\uFB1F",
+ 'yohiragana' : u"\u3088",
+ 'yoikorean' : u"\u3189",
+ 'yokatakana' : u"\u30E8",
+ 'yokatakanahalfwidth' : u"\uFF96",
+ 'yokorean' : u"\u315B",
+ 'yosmallhiragana' : u"\u3087",
+ 'yosmallkatakana' : u"\u30E7",
+ 'yosmallkatakanahalfwidth' : u"\uFF6E",
+ 'yotgreek' : u"\u03F3",
+ 'yoyaekorean' : u"\u3188",
+ 'yoyakorean' : u"\u3187",
+ 'yoyakthai' : u"\u0E22",
+ 'yoyingthai' : u"\u0E0D",
+ 'yparen' : u"\u24B4",
+ 'ypogegrammeni' : u"\u037A",
+ 'ypogegrammenigreekcmb' : u"\u0345",
+ 'yr' : u"\u01A6",
+ 'yring' : u"\u1E99",
+ 'ysuperior' : u"\u02B8",
+ 'ytilde' : u"\u1EF9",
+ 'yturned' : u"\u028E",
+ 'yuhiragana' : u"\u3086",
+ 'yuikorean' : u"\u318C",
+ 'yukatakana' : u"\u30E6",
+ 'yukatakanahalfwidth' : u"\uFF95",
+ 'yukorean' : u"\u3160",
+ 'yusbigcyrillic' : u"\u046B",
+ 'yusbigiotifiedcyrillic' : u"\u046D",
+ 'yuslittlecyrillic' : u"\u0467",
+ 'yuslittleiotifiedcyrillic' : u"\u0469",
+ 'yusmallhiragana' : u"\u3085",
+ 'yusmallkatakana' : u"\u30E5",
+ 'yusmallkatakanahalfwidth' : u"\uFF6D",
+ 'yuyekorean' : u"\u318B",
+ 'yuyeokorean' : u"\u318A",
+ 'yyabengali' : u"\u09DF",
+ 'yyadeva' : u"\u095F",
+ 'zaarmenian' : u"\u0566",
+ 'zadeva' : u"\u095B",
+ 'zagurmukhi' : u"\u0A5B",
+ 'zaharabic' : u"\u0638",
+ 'zahfinalarabic' : u"\uFEC6",
+ 'zahinitialarabic' : u"\uFEC7",
+ 'zahiragana' : u"\u3056",
+ 'zahmedialarabic' : u"\uFEC8",
+ 'zainarabic' : u"\u0632",
+ 'zainfinalarabic' : u"\uFEB0",
+ 'zakatakana' : u"\u30B6",
+ 'zaqefgadolhebrew' : u"\u0595",
+ 'zaqefqatanhebrew' : u"\u0594",
+ 'zarqahebrew' : u"\u0598",
+ 'zayin' : u"\u05D6",
+ 'zayindagesh' : u"\uFB36",
+ 'zayindageshhebrew' : u"\uFB36",
+ 'zayinhebrew' : u"\u05D6",
+ 'zbopomofo' : u"\u3117",
+ 'zcircle' : u"\u24E9",
+ 'zcircumflex' : u"\u1E91",
+ 'zcurl' : u"\u0291",
+ 'zdot' : u"\u017C",
+ 'zdotbelow' : u"\u1E93",
+ 'zecyrillic' : u"\u0437",
+ 'zedescendercyrillic' : u"\u0499",
+ 'zedieresiscyrillic' : u"\u04DF",
+ 'zehiragana' : u"\u305C",
+ 'zekatakana' : u"\u30BC",
+ 'zeroarabic' : u"\u0660",
+ 'zerobengali' : u"\u09E6",
+ 'zerodeva' : u"\u0966",
+ 'zerogujarati' : u"\u0AE6",
+ 'zerogurmukhi' : u"\u0A66",
+ 'zerohackarabic' : u"\u0660",
+ 'zeroinferior' : u"\u2080",
+ 'zeromonospace' : u"\uFF10",
+ 'zerooldstyle' : u"\uF730",
+ 'zeropersian' : u"\u06F0",
+ 'zerosuperior' : u"\u2070",
+ 'zerothai' : u"\u0E50",
+ 'zerowidthjoiner' : u"\uFEFF",
+ 'zerowidthnonjoiner' : u"\u200C",
+ 'zerowidthspace' : u"\u200B",
+ 'zhbopomofo' : u"\u3113",
+ 'zhearmenian' : u"\u056A",
+ 'zhebrevecyrillic' : u"\u04C2",
+ 'zhecyrillic' : u"\u0436",
+ 'zhedescendercyrillic' : u"\u0497",
+ 'zhedieresiscyrillic' : u"\u04DD",
+ 'zihiragana' : u"\u3058",
+ 'zikatakana' : u"\u30B8",
+ 'zinorhebrew' : u"\u05AE",
+ 'zlinebelow' : u"\u1E95",
+ 'zmonospace' : u"\uFF5A",
+ 'zohiragana' : u"\u305E",
+ 'zokatakana' : u"\u30BE",
+ 'zparen' : u"\u24B5",
+ 'zretroflexhook' : u"\u0290",
+ 'zstroke' : u"\u01B6",
+ 'zuhiragana' : u"\u305A",
+ 'zukatakana' : u"\u30BA"
+ }
+
+for k, v in uniToPsnameMap.items() :
+ aglToUniMap[v] = unichr(int(k, 16))
+
+def parse(name) :
+ res = []
+ return res
+
+class Name(object) :
+ def __init__(self, name = None, finalcomp = False) :
+ self.psname = name
+ self.components = []
+ self.ext = None
+ self.cname = None
+ self.GDLName = None
+ self.finalcomp = finalcomp
+ if not name : return
+
+ # Determine the components for any glyph that represents multiple USVs.
+ for comp in name.split("_") :
+ base, dot, mod = comp.partition(".")
+ if not base and mod :
+ base = dot + mod
+ mod = None
+ else :
+ mod = mod.split(".")
+ if re.match(r"^uni[0-9A-Fa-f]{4}", base) :
+ self.components.extend((int(x, 16), None) for x in re.findall(r"[0-9A-Fa-f]{4}", base))
+ if mod :
+ self.components[-1] = (self.components[-1][0], mod)
+ elif re.match(r"^u?[0-9A-Fa-f]{4,6}$", base) :
+ if base[0] == 'u' :
+ self.components.append((int(base[1:], 16), mod))
+ else :
+ self.components.append((int(base, 16), mod))
+ elif base in aglToUniMap :
+ self.components.append((ord(aglToUniMap[base]), mod))
+ elif len(self.components) :
+ self.components[-1] = (self.components[-1][0], mod)
+ else :
+ self.components.append((base, mod))
+ if not finalcomp or len(self.components) == 1 :
+ if self.components[-1][1] :
+ self.ext = self.components[-1][1].pop()
+ if not len(self.components[-1][1]) :
+ self.components[-1] = (self.components[-1][0], None)
+ if self.ext and not self.components[-1][0] :
+ self.components.pop()
+ self.canonical()
+ return
+
+ @classmethod
+ def createFromGDL(cls, name) :
+ """Convert from GDLName back to canonical name. An inexact science"""
+ self = cls()
+ if name.startswith("g_") :
+ name = name[2:]
+ # assume only first letter can be capitalised, everything else is components
+ if name.startswith("_") :
+ c = name[1].upper()
+ name = name[2:]
+ else :
+ c = name[0]
+ name = name[1:]
+ cs = name.split("_")
+ cs[0] = c + cs[0]
+ for i in range(len(cs)) :
+ if cs[i] in aglToUniMap :
+ self.components.append((0, c))
+ else :
+ self.ext = ".".join(cs[i:])
+ break
+ self.psname = self.canonical()
+ return self
+
+ # Calculate the canonical name (if necessary) and return it.
+ def canonical(self, noprefix = False) :
+ if self.cname and not noprefix : return self.cname
+ res = ""
+ if not len(self.components) :
+ self.cname = self.psname
+ return self.cname
+ for k in self.components :
+ u = k[0]
+ if not isinstance(u, basestring) and u :
+ n = "%04X" % u
+ else :
+ n = u
+ if not u :
+ pass
+ elif n in uniToPsnameMap :
+ res += uniToPsnameMap[n]
+ elif not res and not noprefix :
+ res = "u" + n
+ else :
+ res += n
+ if k[1] :
+ res += "." + ".".join(k[1])
+ res += "_"
+ cname = res[0:-1]
+ if self.ext :
+ cname += "." + self.ext
+ if not noprefix : self.cname = cname
+ return cname
+
+ def GDL(self) :
+ if self.GDLName :
+ return self.GDLName
+ res = ""
+ if not len(self.components) :
+ if not self.psname :
+ return None
+
+ res = "g_" + self.psname.replace('.', '_')
+ self.GDLName = re.sub(r"([A-Z])", lambda x : "_" + x.group(1).lower(), res)
+ return self.GDLName
+
+ for k in self.components :
+ u = k[0]
+ if not isinstance(u, basestring) and u :
+ n = "%04X" % u
+ elif u :
+ n = u.replace('.', '_')
+
+ if not u :
+ pass
+ elif n in uniToPsnameMap :
+ if not res : res = "g_"
+ res += re.sub("([A-Z])", lambda x : "_" + x.group(1).lower(), uniToPsnameMap[n])
+ elif not res :
+ res = "g" + n.lower()
+ else :
+ res += n.lower()
+ if res and k[1] :
+ res += "_" + "_".join(k[1])
+ res += "_"
+
+ self.GDLName = res[0:-1]
+
+ if self.ext :
+ self.GDLName += "_" + self.ext.lower().replace('.', '_')
+
+ if self.GDLName == "" : # last resort for some names
+ self.GDLName = "g" + self.psname
+ if self.GDLName[0:1] == "_" :
+ self.GDLName = "g" + self.GDLName
+
+ return self.GDLName
+
+ def head(self) :
+ res = Name()
+ comps = list(self.components)
+ if len(comps) and not self.ext :
+ comps[-1] = (comps[-1], None)
+ res.components = comps
+ return res
+
+ def split_last(self) :
+ if len(self.components) < 2 :
+ return (self, None)
+ head = Name(finalcomp = self.finalcomp)
+ head.components = self.components[:-1]
+ head.ext = self.ext
+ head.psname = head.canonical()
+ tail = Name(finalcomp = self.finalcomp)
+ tail.components = [self.components[-1]]
+ tail.ext = self.ext
+ tail.psname = tail.canonical()
+ return (head, tail)
+
+ def __str__(self) : return self.psname
+
diff --git a/examples/preflight b/examples/preflight
new file mode 100755
index 0000000..65e790c
--- /dev/null
+++ b/examples/preflight
@@ -0,0 +1,9 @@
+#!/bin/sh
+# Sample script for calling multiple routines on a project, typically prior to committing to a repository.
+# Place this in root of a project, adjust the font path, then set it to be executable by typing:
+# chmod +x preflight
+
+psfnormalize -p checkfix=fix source/font-Regular.ufo
+psfnormalize -p checkfix=fix source/font-Bold.ufo
+
+psfsyncmasters source/font-RB.designspace
diff --git a/examples/psfaddGlyphDemo.py b/examples/psfaddGlyphDemo.py
new file mode 100755
index 0000000..888c391
--- /dev/null
+++ b/examples/psfaddGlyphDemo.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+'''Demo script for UFOlib to add a glyph to a UFO font'''
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+import silfont.ufo as ufo
+from xml.etree import cElementTree as ET
+
+suffix = '_addGlyph'
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': suffix+'log'})]
+
+def doit(args) :
+ ''' This will add the following glyph to the font
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <glyph name="Test" format="1">
+ <unicode hex="007D"/>
+ <outline>
+ <contour>
+ <point x="275" y="1582" type="line"/>
+ <point x="275" y="-493" type="line"/>
+ </contour>
+ </outline>
+ </glyph>
+ '''
+
+ font = args.ifont
+
+ # Create basic glyph
+ newglyph = ufo.Uglif(layer = font.deflayer, name = "Test")
+ newglyph.add("unicode", {"hex": "007D"})
+ # Add an outline
+ newglyph.add("outline")
+ # Create a contour and add to outline
+ element = ET.Element("contour")
+ ET.SubElement(element, "point", {"x": "275", "y": "1582", "type": "line"})
+ ET.SubElement(element, "point", {"x": "275", "y": "-493", "type": "line"})
+ contour =ufo.Ucontour(newglyph["outline"],element)
+ newglyph["outline"].appendobject(contour, "contour")
+
+ font.deflayer.addGlyph(newglyph)
+
+ return args.ifont
+
+def cmd() : execute("UFO",doit,argspec)
+if __name__ == "__main__": cmd()
+
diff --git a/examples/psfexpandstroke.py b/examples/psfexpandstroke.py
new file mode 100755
index 0000000..54a340e
--- /dev/null
+++ b/examples/psfexpandstroke.py
@@ -0,0 +1,641 @@
+#!/usr/bin/env python3
+from __future__ import unicode_literals
+'''Expands an unclosed UFO stroke font into monoline forms with a fixed width'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2017 SIL International (https://www.sil.org), based on outlinerRoboFontExtension Copyright (c) 2016 Frederik Berlaen'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'Victor Gaultney'
+
+# Usage: psfexpandstroke ifont ofont expansion
+# expansion is the number of units added to each side of the stroke
+
+# To Do
+# - Simplify to assume round caps and corners
+
+# main input, output, and execution handled by pysilfont framework
+from silfont.core import execute
+
+from fontTools.pens.basePen import BasePen
+from fontTools.misc.bezierTools import splitCubicAtT
+from robofab.world import OpenFont
+from robofab.pens.pointPen import AbstractPointPen
+from robofab.pens.reverseContourPointPen import ReverseContourPointPen
+from robofab.pens.adapterPens import PointToSegmentPen
+
+from defcon import Glyph
+
+from math import sqrt, cos, sin, acos, asin, degrees, radians, pi
+
+suffix = '_expanded'
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'filename'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'filename', 'def': "_"+suffix}),
+ ('thickness',{'help': 'Stroke thickness'}, {}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': suffix+'.log'})]
+
+
+# The following functions are straight from outlinerRoboFontExtension
+
+def roundFloat(f):
+ error = 1000000.
+ return round(f*error)/error
+
+def checkSmooth(firstAngle, lastAngle):
+ if firstAngle is None or lastAngle is None:
+ return True
+ error = 4
+ firstAngle = degrees(firstAngle)
+ lastAngle = degrees(lastAngle)
+
+ if int(firstAngle) + error >= int(lastAngle) >= int(firstAngle) - error:
+ return True
+ return False
+
+def checkInnerOuter(firstAngle, lastAngle):
+ if firstAngle is None or lastAngle is None:
+ return True
+ dirAngle = degrees(firstAngle) - degrees(lastAngle)
+
+ if dirAngle > 180:
+ dirAngle = 180 - dirAngle
+ elif dirAngle < -180:
+ dirAngle = -180 - dirAngle
+
+ if dirAngle > 0:
+ return True
+
+ if dirAngle <= 0:
+ return False
+
+
+def interSect((seg1s, seg1e), (seg2s, seg2e)):
+ denom = (seg2e.y - seg2s.y)*(seg1e.x - seg1s.x) - (seg2e.x - seg2s.x)*(seg1e.y - seg1s.y)
+ if roundFloat(denom) == 0:
+ # print 'parallel: %s' % denom
+ return None
+ uanum = (seg2e.x - seg2s.x)*(seg1s.y - seg2s.y) - (seg2e.y - seg2s.y)*(seg1s.x - seg2s.x)
+ ubnum = (seg1e.x - seg1s.x)*(seg1s.y - seg2s.y) - (seg1e.y - seg1s.y)*(seg1s.x - seg2s.x)
+ ua = uanum / denom
+ # ub = ubnum / denom
+ x = seg1s.x + ua*(seg1e.x - seg1s.x)
+ y = seg1s.y + ua*(seg1e.y - seg1s.y)
+ return MathPoint(x, y)
+
+
+def pointOnACurve((x1, y1), (cx1, cy1), (cx2, cy2), (x2, y2), value):
+ dx = x1
+ cx = (cx1 - dx) * 3.0
+ bx = (cx2 - cx1) * 3.0 - cx
+ ax = x2 - dx - cx - bx
+ dy = y1
+ cy = (cy1 - dy) * 3.0
+ by = (cy2 - cy1) * 3.0 - cy
+ ay = y2 - dy - cy - by
+ mx = ax*(value)**3 + bx*(value)**2 + cx*(value) + dx
+ my = ay*(value)**3 + by*(value)**2 + cy*(value) + dy
+ return MathPoint(mx, my)
+
+
+class MathPoint(object):
+
+ def __init__(self, x, y=None):
+ if y is None:
+ x, y = x
+ self.x = x
+ self.y = y
+
+ def __repr__(self):
+ return "<MathPoint x:%s y:%s>" % (self.x, self.y)
+
+ def __getitem__(self, index):
+ if index == 0:
+ return self.x
+ if index == 1:
+ return self.y
+ raise IndexError
+
+ def __iter__(self):
+ for value in [self.x, self.y]:
+ yield value
+
+ def __add__(self, p): # p+ p
+ if not isinstance(p, self.__class__):
+ return self.__class__(self.x + p, self.y + p)
+ return self.__class__(self.x + p.x, self.y + p.y)
+
+ def __sub__(self, p): # p - p
+ if not isinstance(p, self.__class__):
+ return self.__class__(self.x - p, self.y - p)
+ return self.__class__(self.x - p.x, self.y - p.y)
+
+ def __mul__(self, p): # p * p
+ if not isinstance(p, self.__class__):
+ return self.__class__(self.x * p, self.y * p)
+ return self.__class__(self.x * p.x, self.y * p.y)
+
+ def __div__(self, p):
+ if not isinstance(p, self.__class__):
+ return self.__class__(self.x / p, self.y / p)
+ return self.__class__(self.x / p.x, self.y / p.y)
+
+ def __eq__(self, p): # if p == p
+ if not isinstance(p, self.__class__):
+ return False
+ return roundFloat(self.x) == roundFloat(p.x) and roundFloat(self.y) == roundFloat(p.y)
+
+ def __ne__(self, p): # if p != p
+ return not self.__eq__(p)
+
+ def copy(self):
+ return self.__class__(self.x, self.y)
+
+ def round(self):
+ self.x = round(self.x)
+ self.y = round(self.y)
+
+ def distance(self, p):
+ return sqrt((p.x - self.x)**2 + (p.y - self.y)**2)
+
+ def angle(self, other, add=90):
+ # returns the angle of a Line in radians
+ b = other.x - self.x
+ a = other.y - self.y
+ c = sqrt(a**2 + b**2)
+ if c == 0:
+ return None
+ if add is None:
+ return b/c
+ cosAngle = degrees(acos(b/c))
+ sinAngle = degrees(asin(a/c))
+ if sinAngle < 0:
+ cosAngle = 360 - cosAngle
+ return radians(cosAngle + add)
+
+
+class CleanPointPen(AbstractPointPen):
+
+ def __init__(self, pointPen):
+ self.pointPen = pointPen
+ self.currentContour = None
+
+ def processContour(self):
+ pointPen = self.pointPen
+ contour = self.currentContour
+
+ index = 0
+ prevAngle = None
+ toRemove = []
+ for data in contour:
+ if data["segmentType"] in ["line", "move"]:
+ prevPoint = contour[index-1]
+ if prevPoint["segmentType"] in ["line", "move"]:
+ angle = MathPoint(data["point"]).angle(MathPoint(prevPoint["point"]))
+ if prevAngle is not None and angle is not None and roundFloat(prevAngle) == roundFloat(angle):
+ prevPoint["uniqueID"] = id(prevPoint)
+ toRemove.append(prevPoint)
+ prevAngle = angle
+ else:
+ prevAngle = None
+ else:
+ prevAngle = None
+ index += 1
+
+ for data in toRemove:
+ contour.remove(data)
+
+ pointPen.beginPath()
+ for data in contour:
+ pointPen.addPoint(data["point"], **data)
+ pointPen.endPath()
+
+ def beginPath(self):
+ assert self.currentContour is None
+ self.currentContour = []
+ self.onCurve = []
+
+ def endPath(self):
+ assert self.currentContour is not None
+ self.processContour()
+ self.currentContour = None
+
+ def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
+ data = dict(point=pt, segmentType=segmentType, smooth=smooth, name=name)
+ data.update(kwargs)
+ self.currentContour.append(data)
+
+ def addComponent(self, glyphName, transform):
+ assert self.currentContour is None
+ self.pointPen.addComponent(glyphName, transform)
+
+# The following class has been been adjusted to work around how outline types use closePath() and endPath(),
+# to remove unneeded bits, and hard-code some assumptions.
+
+class OutlinePen(BasePen):
+
+ pointClass = MathPoint
+ magicCurve = 0.5522847498
+
+ def __init__(self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="round", cap="round", miterLimit=None, optimizeCurve=True):
+ BasePen.__init__(self, glyphSet)
+
+ self.offset = abs(offset)
+ self.contrast = abs(contrast)
+ self.contrastAngle = contrastAngle
+ self._inputmiterLimit = miterLimit
+ if miterLimit is None:
+ miterLimit = self.offset * 2
+ self.miterLimit = abs(miterLimit)
+
+ self.optimizeCurve = optimizeCurve
+
+ self.connectionCallback = getattr(self, "connection%s" % (connection.title()))
+ self.capCallback = getattr(self, "cap%s" % (cap.title()))
+
+ self.originalGlyph = Glyph()
+ self.originalPen = self.originalGlyph.getPen()
+
+ self.outerGlyph = Glyph()
+ self.outerPen = self.outerGlyph.getPen()
+ self.outerCurrentPoint = None
+ self.outerFirstPoint = None
+ self.outerPrevPoint = None
+
+ self.innerGlyph = Glyph()
+ self.innerPen = self.innerGlyph.getPen()
+ self.innerCurrentPoint = None
+ self.innerFirstPoint = None
+ self.innerPrevPoint = None
+
+ self.prevPoint = None
+ self.firstPoint = None
+ self.firstAngle = None
+ self.prevAngle = None
+
+ self.shouldHandleMove = True
+
+ self.components = []
+
+ self.drawSettings()
+
+ def _moveTo(self, (x, y)):
+ if self.offset == 0:
+ self.outerPen.moveTo((x, y))
+ self.innerPen.moveTo((x, y))
+ return
+ self.originalPen.moveTo((x, y))
+
+ p = self.pointClass(x, y)
+ self.prevPoint = p
+ self.firstPoint = p
+ self.shouldHandleMove = True
+
+ def _lineTo(self, (x, y)):
+ if self.offset == 0:
+ self.outerPen.lineTo((x, y))
+ self.innerPen.lineTo((x, y))
+ return
+ self.originalPen.lineTo((x, y))
+
+ currentPoint = self.pointClass(x, y)
+ if currentPoint == self.prevPoint:
+ return
+
+ self.currentAngle = self.prevPoint.angle(currentPoint)
+ thickness = self.getThickness(self.currentAngle)
+ self.innerCurrentPoint = self.prevPoint - self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness
+ self.outerCurrentPoint = self.prevPoint + self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness
+
+ if self.shouldHandleMove:
+ self.shouldHandleMove = False
+
+ self.innerPen.moveTo(self.innerCurrentPoint)
+ self.innerFirstPoint = self.innerCurrentPoint
+
+ self.outerPen.moveTo(self.outerCurrentPoint)
+ self.outerFirstPoint = self.outerCurrentPoint
+
+ self.firstAngle = self.currentAngle
+ else:
+ self.buildConnection()
+
+ self.innerCurrentPoint = currentPoint - self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness
+ self.innerPen.lineTo(self.innerCurrentPoint)
+ self.innerPrevPoint = self.innerCurrentPoint
+
+ self.outerCurrentPoint = currentPoint + self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness
+ self.outerPen.lineTo(self.outerCurrentPoint)
+ self.outerPrevPoint = self.outerCurrentPoint
+
+ self.prevPoint = currentPoint
+ self.prevAngle = self.currentAngle
+
+ def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
+ if self.optimizeCurve:
+ curves = splitCubicAtT(self.prevPoint, (x1, y1), (x2, y2), (x3, y3), .5)
+ else:
+ curves = [(self.prevPoint, (x1, y1), (x2, y2), (x3, y3))]
+ for curve in curves:
+ p1, h1, h2, p2 = curve
+ self._processCurveToOne(h1, h2, p2)
+
+ def _processCurveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
+ if self.offset == 0:
+ self.outerPen.curveTo((x1, y1), (x2, y2), (x3, y3))
+ self.innerPen.curveTo((x1, y1), (x2, y2), (x3, y3))
+ return
+ self.originalPen.curveTo((x1, y1), (x2, y2), (x3, y3))
+
+ p1 = self.pointClass(x1, y1)
+ p2 = self.pointClass(x2, y2)
+ p3 = self.pointClass(x3, y3)
+
+ if p1 == self.prevPoint:
+ p1 = pointOnACurve(self.prevPoint, p1, p2, p3, 0.01)
+ if p2 == p3:
+ p2 = pointOnACurve(self.prevPoint, p1, p2, p3, 0.99)
+
+ a1 = self.prevPoint.angle(p1)
+ a2 = p2.angle(p3)
+
+ self.currentAngle = a1
+ tickness1 = self.getThickness(a1)
+ tickness2 = self.getThickness(a2)
+
+ a1bis = self.prevPoint.angle(p1, 0)
+ a2bis = p3.angle(p2, 0)
+ intersectPoint = interSect((self.prevPoint, self.prevPoint + self.pointClass(cos(a1), sin(a1)) * 100),
+ (p3, p3 + self.pointClass(cos(a2), sin(a2)) * 100))
+ self.innerCurrentPoint = self.prevPoint - self.pointClass(cos(a1), sin(a1)) * tickness1
+ self.outerCurrentPoint = self.prevPoint + self.pointClass(cos(a1), sin(a1)) * tickness1
+
+ if self.shouldHandleMove:
+ self.shouldHandleMove = False
+
+ self.innerPen.moveTo(self.innerCurrentPoint)
+ self.innerFirstPoint = self.innerPrevPoint = self.innerCurrentPoint
+
+ self.outerPen.moveTo(self.outerCurrentPoint)
+ self.outerFirstPoint = self.outerPrevPoint = self.outerCurrentPoint
+
+ self.firstAngle = a1
+ else:
+ self.buildConnection()
+
+ h1 = None
+ if intersectPoint is not None:
+ h1 = interSect((self.innerCurrentPoint, self.innerCurrentPoint + self.pointClass(cos(a1bis), sin(a1bis)) * tickness1), (intersectPoint, p1))
+ if h1 is None:
+ h1 = p1 - self.pointClass(cos(a1), sin(a1)) * tickness1
+
+ self.innerCurrentPoint = p3 - self.pointClass(cos(a2), sin(a2)) * tickness2
+
+ h2 = None
+ if intersectPoint is not None:
+ h2 = interSect((self.innerCurrentPoint, self.innerCurrentPoint + self.pointClass(cos(a2bis), sin(a2bis)) * tickness2), (intersectPoint, p2))
+ if h2 is None:
+ h2 = p2 - self.pointClass(cos(a1), sin(a1)) * tickness1
+
+ self.innerPen.curveTo(h1, h2, self.innerCurrentPoint)
+ self.innerPrevPoint = self.innerCurrentPoint
+
+ ########
+ h1 = None
+ if intersectPoint is not None:
+ h1 = interSect((self.outerCurrentPoint, self.outerCurrentPoint + self.pointClass(cos(a1bis), sin(a1bis)) * tickness1), (intersectPoint, p1))
+ if h1 is None:
+ h1 = p1 + self.pointClass(cos(a1), sin(a1)) * tickness1
+
+ self.outerCurrentPoint = p3 + self.pointClass(cos(a2), sin(a2)) * tickness2
+
+ h2 = None
+ if intersectPoint is not None:
+ h2 = interSect((self.outerCurrentPoint, self.outerCurrentPoint + self.pointClass(cos(a2bis), sin(a2bis)) * tickness2), (intersectPoint, p2))
+ if h2 is None:
+ h2 = p2 + self.pointClass(cos(a1), sin(a1)) * tickness1
+ self.outerPen.curveTo(h1, h2, self.outerCurrentPoint)
+ self.outerPrevPoint = self.outerCurrentPoint
+
+ self.prevPoint = p3
+ self.currentAngle = a2
+ self.prevAngle = a2
+
+ def _closePath(self):
+ if self.shouldHandleMove:
+ return
+
+ self.originalPen.endPath()
+ self.innerPen.endPath()
+ self.outerPen.endPath()
+
+ innerContour = self.innerGlyph[-1]
+ outerContour = self.outerGlyph[-1]
+
+ innerContour.reverse()
+
+ innerContour[0].segmentType = "line"
+ outerContour[0].segmentType = "line"
+
+ self.buildCap(outerContour, innerContour)
+
+ for point in innerContour:
+ outerContour.addPoint((point.x, point.y), segmentType=point.segmentType, smooth=point.smooth)
+
+ self.innerGlyph.removeContour(innerContour)
+
+ def _endPath(self):
+ # The current way glyph outlines are processed means that _endPath() would not be called
+ # _closePath() is used instead
+ pass
+
+ def addComponent(self, glyphName, transform):
+ self.components.append((glyphName, transform))
+
+ # thickness
+
+ def getThickness(self, angle):
+ a2 = angle + pi * .5
+ f = abs(sin(a2 + radians(self.contrastAngle)))
+ f = f ** 5
+ return self.offset + self.contrast * f
+
+ # connections
+
+ def buildConnection(self, close=False):
+ if not checkSmooth(self.prevAngle, self.currentAngle):
+ if checkInnerOuter(self.prevAngle, self.currentAngle):
+ self.connectionCallback(self.outerPrevPoint, self.outerCurrentPoint, self.outerPen, close)
+ self.connectionInnerCorner(self.innerPrevPoint, self.innerCurrentPoint, self.innerPen, close)
+ else:
+ self.connectionCallback(self.innerPrevPoint, self.innerCurrentPoint, self.innerPen, close)
+ self.connectionInnerCorner(self.outerPrevPoint, self.outerCurrentPoint, self.outerPen, close)
+
+ def connectionRound(self, first, last, pen, close):
+ angle_1 = radians(degrees(self.prevAngle)+90)
+ angle_2 = radians(degrees(self.currentAngle)+90)
+
+ tempFirst = first - self.pointClass(cos(angle_1), sin(angle_1)) * self.miterLimit
+ tempLast = last + self.pointClass(cos(angle_2), sin(angle_2)) * self.miterLimit
+
+ newPoint = interSect((first, tempFirst), (last, tempLast))
+ if newPoint is None:
+ pen.lineTo(last)
+ return
+ distance1 = newPoint.distance(first)
+ distance2 = newPoint.distance(last)
+ if roundFloat(distance1) > self.miterLimit + self.contrast:
+ distance1 = self.miterLimit + tempFirst.distance(tempLast) * .7
+ if roundFloat(distance2) > self.miterLimit + self.contrast:
+ distance2 = self.miterLimit + tempFirst.distance(tempLast) * .7
+
+ distance1 *= self.magicCurve
+ distance2 *= self.magicCurve
+
+ bcp1 = first - self.pointClass(cos(angle_1), sin(angle_1)) * distance1
+ bcp2 = last + self.pointClass(cos(angle_2), sin(angle_2)) * distance2
+ pen.curveTo(bcp1, bcp2, last)
+
+ def connectionInnerCorner(self, first, last, pen, close):
+ if not close:
+ pen.lineTo(last)
+
+ # caps
+
+ def buildCap(self, firstContour, lastContour):
+ first = firstContour[-1]
+ last = lastContour[0]
+ first = self.pointClass(first.x, first.y)
+ last = self.pointClass(last.x, last.y)
+
+ self.capCallback(firstContour, lastContour, first, last, self.prevAngle)
+
+ first = lastContour[-1]
+ last = firstContour[0]
+ first = self.pointClass(first.x, first.y)
+ last = self.pointClass(last.x, last.y)
+
+ angle = radians(degrees(self.firstAngle)+180)
+ self.capCallback(lastContour, firstContour, first, last, angle)
+
+ def capRound(self, firstContour, lastContour, first, last, angle):
+ hookedAngle = radians(degrees(angle)+90)
+
+ p1 = first - self.pointClass(cos(hookedAngle), sin(hookedAngle)) * self.offset
+
+ p2 = last - self.pointClass(cos(hookedAngle), sin(hookedAngle)) * self.offset
+
+ oncurve = p1 + (p2-p1)*.5
+
+ roundness = .54
+
+ h1 = first - self.pointClass(cos(hookedAngle), sin(hookedAngle)) * self.offset * roundness
+ h2 = oncurve + self.pointClass(cos(angle), sin(angle)) * self.offset * roundness
+
+ firstContour[-1].smooth = True
+
+ firstContour.addPoint((h1.x, h1.y))
+ firstContour.addPoint((h2.x, h2.y))
+ firstContour.addPoint((oncurve.x, oncurve.y), smooth=True, segmentType="curve")
+
+ h1 = oncurve - self.pointClass(cos(angle), sin(angle)) * self.offset * roundness
+ h2 = last - self.pointClass(cos(hookedAngle), sin(hookedAngle)) * self.offset * roundness
+
+ firstContour.addPoint((h1.x, h1.y))
+ firstContour.addPoint((h2.x, h2.y))
+
+ lastContour[0].segmentType = "curve"
+ lastContour[0].smooth = True
+
+ def drawSettings(self, drawOriginal=False, drawInner=False, drawOuter=True):
+ self.drawOriginal = drawOriginal
+ self.drawInner = drawInner
+ self.drawOuter = drawOuter
+
+ def drawPoints(self, pointPen):
+ if self.drawInner:
+ reversePen = ReverseContourPointPen(pointPen)
+ self.innerGlyph.drawPoints(CleanPointPen(reversePen))
+ if self.drawOuter:
+ self.outerGlyph.drawPoints(CleanPointPen(pointPen))
+
+ if self.drawOriginal:
+ if self.drawOuter:
+ pointPen = ReverseContourPointPen(pointPen)
+ self.originalGlyph.drawPoints(CleanPointPen(pointPen))
+
+ for glyphName, transform in self.components:
+ pointPen.addComponent(glyphName, transform)
+
+ def draw(self, pen):
+ pointPen = PointToSegmentPen(pen)
+ self.drawPoints(pointPen)
+
+ def getGlyph(self):
+ glyph = Glyph()
+ pointPen = glyph.getPointPen()
+ self.drawPoints(pointPen)
+ return glyph
+
+# The following functions have been decoupled from the outlinerRoboFontExtension and
+# effectively de-parameterized, with built-in assumptions
+
+def calculate(glyph, strokewidth):
+ tickness = strokewidth
+ contrast = 0
+ contrastAngle = 0
+ keepBounds = False
+ optimizeCurve = True
+ miterLimit = None #assumed
+
+ corner = "round" #assumed - other options not supported
+ cap = "round" #assumed - other options not supported
+
+ drawOriginal = False
+ drawInner = True
+ drawOuter = True
+
+ pen = OutlinePen(glyph.getParent(),
+ tickness,
+ contrast,
+ contrastAngle,
+ connection=corner,
+ cap=cap,
+ miterLimit=miterLimit,
+ optimizeCurve=optimizeCurve)
+
+ glyph.draw(pen)
+
+ pen.drawSettings(drawOriginal=drawOriginal,
+ drawInner=drawInner,
+ drawOuter=drawOuter)
+
+ result = pen.getGlyph()
+
+ return result
+
+
+def expandGlyph(glyph, strokewidth):
+ defconGlyph = glyph
+ outline = calculate(defconGlyph, strokewidth)
+
+ glyph.clearContours()
+ outline.drawPoints(glyph.getPointPen())
+
+ glyph.round()
+
+def expandFont(targetfont, strokewidth):
+ font = targetfont
+ for glyph in font:
+ expandGlyph(glyph, strokewidth)
+
+def doit(args):
+ infont = OpenFont(args.ifont)
+ outfont = args.ofont
+ # add try to catch bad input
+ strokewidth = int(args.thickness)
+ expandFont(infont, strokewidth)
+ infont.save(outfont)
+
+ return infont
+
+def cmd() : execute(None,doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/psfexportnamesunicodesfp.py b/examples/psfexportnamesunicodesfp.py
new file mode 100644
index 0000000..f5dacd1
--- /dev/null
+++ b/examples/psfexportnamesunicodesfp.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+'''Outputs an unsorted csv file containing the names of all the glyphs in the default layer
+and their primary unicode values. Format name,usv'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2018 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'Victor Gaultney'
+
+from silfont.core import execute
+
+suffix = "_namesunicodes"
+
+argspec = [
+ ('ifont', {'help': 'Input font file'}, {'type': 'infont'}),
+ ('-o','--output',{'help': 'Output csv file'}, {'type': 'outfile', 'def': suffix+'.csv'})]
+
+def doit(args) :
+ font = args.ifont
+ outfile = args.output
+
+ for glyph in font:
+ unival = ""
+ if glyph.unicode:
+ unival = str.upper(hex(glyph.unicode))[2:7].zfill(4)
+ outfile.write(glyph.name + "," + unival + "\n")
+
+ print("Done")
+
+def cmd() : execute("FP",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/psfgenftml.py b/examples/psfgenftml.py
new file mode 100644
index 0000000..2807e45
--- /dev/null
+++ b/examples/psfgenftml.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+'''
+Example script to generate ftml document from glyph_data.csv and UFO.
+
+To try this with the Harmattan font project:
+ 1) clone and build Harmattan:
+ clone https://github.com/silnrsi/font-harmattan
+ cd font-harmattan
+ smith configure
+ smith build ftml
+ 2) run psfgenftml as follows:
+ python3 psfgenftml.py \
+ -t "AllChars" \
+ --ap "_?dia[AB]$" \
+ --xsl ../tools/lib/ftml.xsl \
+ --scale 200 \
+ -i source/glyph_data.csv \
+ -s "url(../references/Harmattan-Regular-v1.ttf)=ver 1" \
+ -s "url(../results/Harmattan-Regular.ttf)=Reg-GR" \
+ -s "url(../results/tests/ftml/fonts/Harmattan-Regular_ot_arab.ttf)=Reg-OT" \
+ source/Harmattan-Regular.ufo tests/AllChars-dev.ftml
+ 3) launch resulting output file, tests/AllChars-dev.ftml, in a browser.
+ (see https://silnrsi.github.io/FDBP/en-US/Browsers%20as%20a%20font%20test%20platform.html)
+ NB: Using Firefox will allow simultaneous display of both Graphite and OpenType rendering
+ 4) As above but substitute:
+ -t "Diac Test" for the -t parameter
+ tests/DiacTest-dev.ftml for the final parameter
+ and launch tests/DiacTest-dev.ftml in a browser.
+'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2018,2021 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'Bob Hallissy'
+
+import re
+from silfont.core import execute
+import silfont.ftml_builder as FB
+
+argspec = [
+ ('ifont', {'help': 'Input UFO'}, {'type': 'infont'}),
+ ('output', {'help': 'Output file ftml in XML format', 'nargs': '?'}, {'type': 'outfile', 'def': '_out.ftml'}),
+ ('-i','--input', {'help': 'Glyph info csv file'}, {'type': 'incsv', 'def': 'glyph_data.csv'}),
+ ('-f','--fontcode', {'help': 'letter to filter for glyph_data'},{}),
+ ('-l','--log', {'help': 'Set log file name'}, {'type': 'outfile', 'def': '_ftml.log'}),
+ ('--langs', {'help':'List of bcp47 language tags', 'default': None}, {}),
+ ('--rtl', {'help': 'enable right-to-left features', 'action': 'store_true'}, {}),
+ ('--norendercheck', {'help': 'do not include the RenderingUnknown check', 'action': 'store_true'}, {}),
+ ('-t', '--test', {'help': 'name of the test to generate', 'default': None}, {}),
+ ('-s','--fontsrc', {'help': 'font source: "url()" or "local()" optionally followed by "=label"', 'action': 'append'}, {}),
+ ('--scale', {'help': 'percentage to scale rendered text (default 100)'}, {}),
+ ('--ap', {'help': 'regular expression describing APs to examine', 'default': '.'}, {}),
+ ('-w', '--width', {'help': 'total width of all <string> column (default automatic)'}, {}),
+ ('--xsl', {'help': 'XSL stylesheet to use'}, {}),
+]
+
+
+def doit(args):
+ logger = args.logger
+
+ # Read input csv
+ builder = FB.FTMLBuilder(logger, incsv=args.input, fontcode=args.fontcode, font=args.ifont, ap=args.ap,
+ rtlenable=True, langs=args.langs)
+
+ # Override default base (25CC) for displaying combining marks:
+ builder.diacBase = 0x0628 # beh
+
+ # Initialize FTML document:
+ # Default name for test: AllChars or something based on the csvdata file:
+ test = args.test or 'AllChars (NG)'
+ widths = None
+ if args.width:
+ try:
+ width, units = re.match(r'(\d+)(.*)$', args.width).groups()
+ if len(args.fontsrc):
+ width = int(round(int(width)/len(args.fontsrc)))
+ widths = {'string': f'{width}{units}'}
+ logger.log(f'width: {args.width} --> {widths["string"]}', 'I')
+ except:
+ logger.log(f'Unable to parse width argument "{args.width}"', 'W')
+ # split labels from fontsource parameter
+ fontsrc = []
+ labels = []
+ for sl in args.fontsrc:
+ try:
+ s, l = sl.split('=',1)
+ fontsrc.append(s)
+ labels.append(l)
+ except ValueError:
+ fontsrc.append(sl)
+ labels.append(None)
+ ftml = FB.FTML(test, logger, rendercheck=not args.norendercheck, fontscale=args.scale,
+ widths=widths, xslfn=args.xsl, fontsrc=fontsrc, fontlabel=labels, defaultrtl=args.rtl)
+
+ if test.lower().startswith("allchars"):
+ # all chars that should be in the font:
+ ftml.startTestGroup('Encoded characters')
+ for uid in sorted(builder.uids()):
+ if uid < 32: continue
+ c = builder.char(uid)
+ # iterate over all permutations of feature settings that might affect this character:
+ for featlist in builder.permuteFeatures(uids = (uid,)):
+ ftml.setFeatures(featlist)
+ builder.render((uid,), ftml)
+ # Don't close test -- collect consecutive encoded chars in a single row
+ ftml.clearFeatures()
+ for langID in sorted(c.langs):
+ ftml.setLang(langID)
+ builder.render((uid,), ftml)
+ ftml.clearLang()
+
+ # Add unencoded specials and ligatures -- i.e., things with a sequence of USVs in the glyph_data:
+ ftml.startTestGroup('Specials & ligatures from glyph_data')
+ for basename in sorted(builder.specials()):
+ special = builder.special(basename)
+ # iterate over all permutations of feature settings that might affect this special
+ for featlist in builder.permuteFeatures(uids = special.uids):
+ ftml.setFeatures(featlist)
+ builder.render(special.uids, ftml)
+ # close test so each special is on its own row:
+ ftml.closeTest()
+ ftml.clearFeatures()
+ if len(special.langs):
+ for langID in sorted(special.langs):
+ ftml.setLang(langID)
+ builder.render(special.uids, ftml)
+ ftml.closeTest()
+ ftml.clearLang()
+
+ # Add Lam-Alef data manually
+ ftml.startTestGroup('Lam-Alef')
+ # generate list of lam and alef characters that should be in the font:
+ lamlist = list(filter(lambda x: x in builder.uids(), (0x0644, 0x06B5, 0x06B6, 0x06B7, 0x06B8, 0x076A, 0x08A6)))
+ aleflist = list(filter(lambda x: x in builder.uids(), (0x0627, 0x0622, 0x0623, 0x0625, 0x0671, 0x0672, 0x0673, 0x0675, 0x0773, 0x0774)))
+ # iterate over all combinations:
+ for lam in lamlist:
+ for alef in aleflist:
+ for featlist in builder.permuteFeatures(uids = (lam, alef)):
+ ftml.setFeatures(featlist)
+ builder.render((lam,alef), ftml)
+ # close test so each combination is on its own row:
+ ftml.closeTest()
+ ftml.clearFeatures()
+
+ if test.lower().startswith("diac"):
+ # Diac attachment:
+
+ # Representative base and diac chars:
+ repDiac = list(filter(lambda x: x in builder.uids(), (0x064E, 0x0650, 0x065E, 0x0670, 0x0616, 0x06E3, 0x08F0, 0x08F2)))
+ repBase = list(filter(lambda x: x in builder.uids(), (0x0627, 0x0628, 0x062B, 0x0647, 0x064A, 0x77F, 0x08AC)))
+
+ ftml.startTestGroup('Representative diacritics on all bases that take diacritics')
+ for uid in sorted(builder.uids()):
+ # ignore some I don't care about:
+ if uid < 32 or uid in (0xAA, 0xBA): continue
+ c = builder.char(uid)
+ # Always process Lo, but others only if that take marks:
+ if c.general == 'Lo' or c.isBase:
+ for diac in repDiac:
+ for featlist in builder.permuteFeatures(uids = (uid,diac)):
+ ftml.setFeatures(featlist)
+ # Don't automatically separate connecting or mirrored forms into separate lines:
+ builder.render((uid,diac), ftml, addBreaks = False)
+ ftml.clearFeatures()
+ ftml.closeTest()
+
+ ftml.startTestGroup('All diacritics on representative bases')
+ for uid in sorted(builder.uids()):
+ # ignore non-ABS marks
+ if uid < 0x600 or uid in range(0xFE00, 0xFE10): continue
+ c = builder.char(uid)
+ if c.general == 'Mn':
+ for base in repBase:
+ for featlist in builder.permuteFeatures(uids = (uid,base)):
+ ftml.setFeatures(featlist)
+ builder.render((base,uid), ftml, keyUID = uid, addBreaks = False)
+ ftml.clearFeatures()
+ ftml.closeTest()
+
+ ftml.startTestGroup('Special cases')
+ builder.render((0x064A, 0x065E), ftml, comment="Yeh + Fatha should keep dots")
+ builder.render((0x064A, 0x0654), ftml, comment="Yeh + Hamza should loose dots")
+ ftml.closeTest()
+
+ # Write the output ftml file
+ ftml.writeFile(args.output)
+
+
+def cmd() : execute("UFO",doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/psftidyfontlabufo.py b/examples/psftidyfontlabufo.py
new file mode 100755
index 0000000..d69f16a
--- /dev/null
+++ b/examples/psftidyfontlabufo.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+__doc__ = '''Make changes to a backup UFO to match some changes made to another UFO by FontLab
+When a UFO is first round-tripped through Fontlab 7, many changes are made including adding 'smooth="yes"' to many points
+in glifs and removing it from others. Also if components are after contours in a glif, then they get moved to before them.
+These changes make initial comparisons hard and can mask other changes.
+This script takes the backup of the original font that Fontlab made and writes out a new version with contours changed
+to match those in the round-tripped UFO so a diff can then be done to look for other differences.
+A glif is only changed if there are no other changes to contours.
+If also moves components to match.
+'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2021 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute, splitfn
+from xml.etree import ElementTree as ET
+from silfont.ufo import Ufont
+import os, glob
+from difflib import ndiff
+
+argspec = [
+ ('ifont',{'help': 'post-fontlab ufo'}, {'type': 'infont'}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_tidyfontlab.log'})]
+
+def doit(args) :
+
+ flfont = args.ifont
+ logger = args.logger
+ params = args.paramsobj
+ fontname = args.ifont.ufodir
+
+ # Locate the oldest backup
+ (path, base, ext) = splitfn(fontname)
+ backuppath = os.path.join(path, base + ".*-*" + ext) # Backup has date/time added in format .yymmdd-hhmm
+ backups = glob.glob(backuppath)
+ if len(backups) == 0:
+ logger.log("No backups found matching %s so aborting..." % backuppath, "P")
+ return
+ backupname = sorted(backups)[0] # Choose the oldest backup - date/time format sorts alphabetically
+ logger.log(f"Opening backup font {backupname}", "P")
+ bfont = Ufont(backupname, params=params)
+ outufoname = os.path.join(path, base + ".tidied.ufo")
+
+ fllayers = {} # Dictionary of flfont layers by layer name
+ for layer in flfont.layers: fllayers[layer.layername] = layer
+
+ for layer in bfont.layers:
+ if layer.layername not in fllayers:
+ logger.log(f"layer {layer.layername} missing", "E")
+ continue
+ fllayer = fllayers[layer.layername]
+ glifchangecount = 0
+ smoothchangecount = 0
+ duplicatenodecount = 0
+ compchangecount = 0
+ for gname in layer:
+ glif = layer[gname]
+ glifchange = False
+ flglif = fllayer[gname]
+ if "outline" in glif and "outline" in flglif:
+ changestomake = []
+ otherchange = False
+ outline = glif["outline"]
+ floutline = flglif["outline"]
+ contours = outline.contours
+ if len(contours) != len(floutline.contours): break # Different number so can't all be identical!
+ flcontours = iter(floutline.contours)
+ for contour in contours:
+ flc = next(flcontours)
+ points = contour["point"]
+ flpoints = flc["point"]
+ duplicatenode = False
+ smoothchanges = True
+ if len(points) != len(flpoints): # Contours must be different!
+ if len(flpoints) - len(points) == 1: # Look for duplicate node issue
+ (different, plus, minus) = sdiff(str(ET.tostring(points[0]).strip()), str(ET.tostring(flpoints[0]).strip()))
+ if ET.tostring(points[0]).strip() == ET.tostring(flpoints[-1]).strip(): # With duplicate node issue first point is appended to the end
+ if plus == "lin" and minus == "curv": # On first point curve changed to line.
+ duplicatenode = True # Also still need check all the remaining points are the same
+ break # but next check does that
+ otherchange = True # Duplicate node issue above is only case where contour count can be different
+ break
+
+ firstpoint = True
+ for point in points:
+ flp = flpoints.pop(0)
+ if firstpoint and duplicatenode: # Ignore the first point since that will be different
+ firstpoint = False
+ continue
+ firstpoint = False
+ (different, plus, minus) = sdiff(str(ET.tostring(point).strip()), str(ET.tostring(flp).strip()))
+ if different: # points are different
+ if plus.strip() + minus.strip() == 'smooth="yes"':
+ smoothchanges = True # Only difference is addition or removal of smooth="yes"
+ else: # Other change to glif,so can't safely make changes
+ otherchange = True
+
+ if (smoothchanges or duplicatenode) and not otherchange: # Only changes to contours in glif are known issues that should be reset
+ flcontours = iter(floutline.contours)
+ for contour in list(contours):
+ flcontour = next(flcontours)
+ outline.replaceobject(contour, flcontour, "contour")
+ if smoothchanges:
+ logger.log(f'Smooth changes made to {gname}', "I")
+ smoothchangecount += 1
+ if duplicatenode:
+ logger.log(f'Duplicate node changes made to {gname}', "I")
+ duplicatenodecount += 1
+ glifchange = True
+
+ # Now need to move components to the front...
+ components = outline.components
+ if len(components) > 0 and len(contours) > 0 and list(outline)[0] == "contour":
+ oldcontours = list(contours) # Easiest way to 'move' components is to delete contours then append back at the end
+ for contour in oldcontours: outline.removeobject(contour, "contour")
+ for contour in oldcontours: outline.appendobject(contour, "contour")
+ logger.log(f'Component position changes made to {gname}', "I")
+ compchangecount += 1
+ glifchange = True
+ if glifchange: glifchangecount += 1
+
+ logger.log(f'{layer.layername}: {glifchangecount} glifs changed', 'P')
+ logger.log(f'{layer.layername}: {smoothchangecount} changes due to smooth, {duplicatenodecount} due to duplicate nodes and {compchangecount} due to components position', "P")
+
+ bfont.write(outufoname)
+ return
+
+def sdiff(before, after): # Returns strings with the differences between the supplited strings
+ if before == after: return(False,"","") # First returned value is True if the strings are different
+ diff = ndiff(before, after)
+ plus = "" # Plus will have the extra characters that are only in after
+ minus = "" # Minus will have the characters missing from after
+ for d in diff:
+ if d[0] == "+": plus += d[2]
+ if d[0] == "-": minus += d[2]
+ return(True, plus, minus)
+
+def cmd() : execute("UFO",doit, argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/psftoneletters.py b/examples/psftoneletters.py
new file mode 100644
index 0000000..b6f7b7a
--- /dev/null
+++ b/examples/psftoneletters.py
@@ -0,0 +1,327 @@
+#!/usr/bin/env python3
+from __future__ import unicode_literals
+'''Creates Latin script tone letters (pitch contours)'''
+__url__ = 'https://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2017 SIL International (https://www.sil.org)'
+__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
+__author__ = 'Victor Gaultney'
+
+# Usage: psftoneletters ifont ofont
+# Assumption is that the named tone letters already exist in the font,
+# so this script is only to update (rebuild) them. New tone letter spaces
+# in the font can be created with psfbuildcomp.py
+
+# To Do
+# Get parameters from lib.plist org.sil.lcg.toneLetters
+
+# main input, output, and execution handled by pysilfont framework
+from silfont.core import execute
+import silfont.ufo as UFO
+
+from robofab.world import OpenFont
+
+from math import tan, radians, sqrt
+
+suffix = '_toneletters'
+argspec = [
+ ('ifont',{'help': 'Input font file'}, {'type': 'filename'}),
+ ('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'filename', 'def': "_"+suffix}),
+ ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': suffix+'log'})]
+
+
+def getParameters(font):
+ global glyphHeight, marginFlatLeft, marginPointLeft, marginFlatRight, marginPointRight, contourWidth, marginDotLeft, marginDotRight, dotSpacing, italicAngle, radius, strokeHeight, strokeDepth, contourGap, fakeBottom, dotRadius, dotBCP, contourGapDot, fakeBottomDot, anchorHeight, anchorOffset
+
+ source = font.lib.getval("org.sil.lcg.toneLetters")
+
+ strokeThickness = int(source["strokeThickness"]) # total width of stroke (ideally an even number)
+ glyphHeight = int(source["glyphHeight"]) # height, including overshoot
+ glyphDepth = int(source["glyphDepth"]) # depth - essentially overshoot (typically negative)
+ marginFlatLeft = int(source["marginFlatLeft"]) # left sidebearing for straight bar
+ marginPointLeft = int(source["marginPointLeft"]) # left sidebearing for endpoints
+ marginFlatRight = int(source["marginFlatRight"]) # left sidebearing for straight bar
+ marginPointRight = int(source["marginPointRight"]) # left sidebearing for endpoints
+ contourWidth = int(source["contourWidth"]) # this is how wide the contour portions are, from the middle
+ # of one end to the other, in the horizontal axis. The actual
+ # bounding box of the contours would then be this plus the
+ # strokeThickness.
+ marginDotLeft = int(source["marginDotLeft"]) # left sidebearing for dots
+ marginDotRight = int(source["marginDotRight"]) # right sidebearing for dots
+ dotSize = int(source["dotSize"]) # the diameter of the dot, normally 150% of the stroke weight
+ # (ideally an even number)
+ dotSpacing = int(source["dotSpacing"]) # the space between the edge of the dot and the
+ # edge of the expanded stroke
+ italicAngle = float(source["italicAngle"]) # angle of italic slant, 0 for upright
+
+ radius = round(strokeThickness / 2)
+ strokeHeight = glyphHeight - radius # for the unexpanded stroke
+ strokeDepth = glyphDepth + radius
+ strokeLength = strokeHeight - strokeDepth
+ contourGap = round(strokeLength / 4) # gap between contour levels
+ fakeBottom = strokeDepth - contourGap # a false 'bottom' for building contours
+
+ dotRadius = round(dotSize / 2) # this gets redefined during nine tone process
+ dotBCP = round((dotSize / 2) * .55) # this gets redefined during nine tone process
+ contourGapDot = round(( (glyphHeight - dotRadius) - (glyphDepth + dotRadius) ) / 4)
+ fakeBottomDot = (glyphDepth + dotRadius) - contourGapDot
+
+ anchorHeight = [ 0 , strokeDepth , (strokeDepth + contourGap) , (strokeDepth + contourGap * 2) , (strokeHeight - contourGap) , strokeHeight ]
+ anchorOffset = 20 # hardcoded for now
+
+# drawing functions
+
+def drawLine(glyph,startX,startY,endX,endY):
+
+ dx = (endX - startX) # dx of original stroke
+ dy = (endY - startY) # dy of original stroke
+ len = sqrt( dx * dx + dy * dy ) # length of original stroke
+ opp = round(dy * (radius / len)) # offsets for on-curve points
+ adj = round(dx * (radius / len))
+ oppOff = round(opp * .55) # offsets for off-curve from on-curve
+ adjOff = round(adj * .55)
+
+ glyph.clearContours()
+
+ pen = glyph.getPen()
+
+ # print startX + opp, startY - adj
+
+ pen.moveTo((startX + opp, startY - adj))
+ pen.lineTo((endX + opp, endY - adj)) # first straight line
+
+ bcp1x = endX + opp + adjOff
+ bcp1y = endY - adj + oppOff
+ bcp2x = endX + adj + oppOff
+ bcp2y = endY + opp - adjOff
+ pen.curveTo((bcp1x, bcp1y), (bcp2x, bcp2y), (endX + adj, endY + opp))
+
+ bcp1x = endX + adj - oppOff
+ bcp1y = endY + opp + adjOff
+ bcp2x = endX - opp + adjOff
+ bcp2y = endY + adj + oppOff
+ pen.curveTo((bcp1x, bcp1y), (bcp2x, bcp2y), (endX - opp, endY + adj))
+
+ pen.lineTo((startX - opp, startY + adj)) # second straight line
+
+ bcp1x = startX - opp - adjOff
+ bcp1y = startY + adj - oppOff
+ bcp2x = startX - adj - oppOff
+ bcp2y = startY - opp + adjOff
+ pen.curveTo((bcp1x, bcp1y), (bcp2x, bcp2y), (startX - adj, startY - opp))
+
+ bcp1x = startX - adj + oppOff
+ bcp1y = startY - opp - adjOff
+ bcp2x = startX + opp - adjOff
+ bcp2y = startY - adj - oppOff
+ pen.curveTo((bcp1x, bcp1y), (bcp2x, bcp2y), (startX + opp, startY - adj))
+ # print startX + opp, startY - adj
+
+ pen.closePath()
+
+
+def drawDot(glyph,dotX,dotY):
+
+ glyph.clearContours()
+
+ pen = glyph.getPen()
+
+ pen.moveTo((dotX, dotY - dotRadius))
+ pen.curveTo((dotX + dotBCP, dotY - dotRadius), (dotX + dotRadius, dotY - dotBCP), (dotX + dotRadius, dotY))
+ pen.curveTo((dotX + dotRadius, dotY + dotBCP), (dotX + dotBCP, dotY + dotRadius), (dotX, dotY + dotRadius))
+ pen.curveTo((dotX - dotBCP, dotY + dotRadius), (dotX - dotRadius, dotY + dotBCP), (dotX - dotRadius, dotY))
+ pen.curveTo((dotX - dotRadius, dotY - dotBCP), (dotX - dotBCP, dotY - dotRadius), (dotX, dotY - dotRadius))
+ pen.closePath()
+
+
+def adjItalX(aiX,aiY):
+ newX = aiX + round(tan(radians(italicAngle)) * aiY)
+ return newX
+
+
+def buildComp(f,g,pieces,ancLevelLeft,ancLevelMidLeft,ancLevelMidRight,ancLevelRight):
+
+ g.clear()
+ g.width = 0
+
+ for p in pieces:
+ g.appendComponent(p, (g.width, 0))
+ g.width += f[p].width
+
+ if ancLevelLeft > 0:
+ anc_nm = "_TL"
+ anc_x = adjItalX(0,anchorHeight[ancLevelLeft])
+ if g.name[0:7] == 'TnStaff':
+ anc_x = anc_x - anchorOffset
+ anc_y = anchorHeight[ancLevelLeft]
+ g.appendAnchor(anc_nm, (anc_x, anc_y))
+
+ if ancLevelMidLeft > 0:
+ anc_nm = "_TL"
+ anc_x = adjItalX(marginPointLeft + radius,anchorHeight[ancLevelMidLeft])
+ anc_y = anchorHeight[ancLevelMidLeft]
+ g.appendAnchor(anc_nm, (anc_x, anc_y))
+
+ if ancLevelMidRight > 0:
+ anc_nm = "TL"
+ anc_x = adjItalX(g.width - marginPointRight - radius,anchorHeight[ancLevelMidRight])
+ anc_y = anchorHeight[ancLevelMidRight]
+ g.appendAnchor(anc_nm, (anc_x, anc_y))
+
+ if ancLevelRight > 0:
+ anc_nm = "TL"
+ anc_x = adjItalX(g.width,anchorHeight[ancLevelRight])
+ if g.name[0:7] == 'TnStaff':
+ anc_x = anc_x + anchorOffset
+ anc_y = anchorHeight[ancLevelRight]
+ g.appendAnchor(anc_nm, (anc_x, anc_y))
+
+
+# updating functions
+
+def updateTLPieces(targetfont):
+
+ f = targetfont
+
+ # set spacer widths
+ f["TnLtrSpcFlatLeft"].width = marginFlatLeft + radius
+ f["TnLtrSpcPointLeft"].width = marginPointLeft + radius - 1 # -1 corrects final sidebearing
+ f["TnLtrSpcFlatRight"].width = marginFlatRight + radius
+ f["TnLtrSpcPointRight"].width = marginPointRight + radius - 1 # -1 corrects final sidebearing
+ f["TnLtrSpcDotLeft"].width = marginDotLeft + dotRadius
+ f["TnLtrSpcDotMiddle"].width = dotRadius + dotSpacing + radius
+ f["TnLtrSpcDotRight"].width = dotRadius + marginDotRight
+
+ # redraw bar
+ g = f["TnLtrBar"]
+ drawLine(g,adjItalX(0,strokeDepth),strokeDepth,adjItalX(0,strokeHeight),strokeHeight)
+ g.width = 0
+
+ # redraw contours
+ namePre = 'TnLtrSeg'
+ for i in range(1,6):
+ for j in range(1,6):
+
+ nameFull = namePre + str(i) + str(j)
+
+ if i == 5: # this deals with round off errors
+ startLevel = strokeHeight
+ else:
+ startLevel = fakeBottom + i * contourGap
+ if j == 5:
+ endLevel = strokeHeight
+ else:
+ endLevel = fakeBottom + j * contourGap
+
+ g = f[nameFull]
+ g.width = contourWidth
+ drawLine(g,adjItalX(1,startLevel),startLevel,adjItalX(contourWidth-1,endLevel),endLevel)
+
+
+ # redraw dots
+ namePre = 'TnLtrDot'
+ for i in range(1,6):
+
+ nameFull = namePre + str(i)
+
+ if i == 5: # this deals with round off errors
+ dotLevel = glyphHeight - dotRadius
+ else:
+ dotLevel = fakeBottomDot + i * contourGapDot
+
+ g = f[nameFull]
+ drawDot(g,adjItalX(0,dotLevel),dotLevel)
+
+
+def rebuildTLComps(targetfont):
+
+ f = targetfont
+
+ # staff right
+ for i in range(1,6):
+ nameFull = 'TnStaffRt' + str(i)
+ buildComp(f,f[nameFull],['TnLtrBar','TnLtrSpcFlatRight'],i,0,0,0)
+
+ # staff right no outline
+ for i in range(1,6):
+ nameFull = 'TnStaffRt' + str(i) + 'no'
+ buildComp(f,f[nameFull],['TnLtrSpcFlatRight'],i,0,0,0)
+
+ # staff left
+ for i in range(1,6):
+ nameFull = 'TnStaffLft' + str(i)
+ buildComp(f,f[nameFull],['TnLtrSpcFlatLeft','TnLtrBar'],0,0,0,i)
+
+ # staff left no outline
+ for i in range(1,6):
+ nameFull = 'TnStaffLft' + str(i) + 'no'
+ buildComp(f,f[nameFull],['TnLtrSpcFlatLeft'],0,0,0,i)
+
+ # contours right
+ for i in range(1,6):
+ for j in range(1,6):
+ nameFull = 'TnContRt' + str(i) + str(j)
+ segment = 'TnLtrSeg' + str(i) + str(j)
+ buildComp(f,f[nameFull],['TnLtrSpcPointLeft',segment],0,i,0,j)
+
+ # contours left
+ for i in range(1,6):
+ for j in range(1,6):
+ nameFull = 'TnContLft' + str(i) + str(j)
+ segment = 'TnLtrSeg' + str(i) + str(j)
+ buildComp(f,f[nameFull],[segment,'TnLtrSpcPointRight'],i,0,j,0)
+
+ # basic tone letters
+ for i in range(1,6):
+ nameFull = 'TnLtr' + str(i)
+ segment = 'TnLtrSeg' + str(i) + str(i)
+ buildComp(f,f[nameFull],['TnLtrSpcPointLeft',segment,'TnLtrBar','TnLtrSpcFlatRight'],0,0,0,0)
+
+ # basic tone letters no outline
+ for i in range(1,6):
+ nameFull = 'TnLtr' + str(i) + 'no'
+ segment = 'TnLtrSeg' + str(i) + str(i)
+ buildComp(f,f[nameFull],['TnLtrSpcPointLeft',segment,'TnLtrSpcFlatRight'],0,i,0,0)
+
+ # left stem tone letters
+ for i in range(1,6):
+ nameFull = 'LftStemTnLtr' + str(i)
+ segment = 'TnLtrSeg' + str(i) + str(i)
+ buildComp(f,f[nameFull],['TnLtrSpcFlatLeft','TnLtrBar',segment,'TnLtrSpcPointRight'],0,0,0,0)
+
+ # left stem tone letters no outline
+ for i in range(1,6):
+ nameFull = 'LftStemTnLtr' + str(i) + 'no'
+ segment = 'TnLtrSeg' + str(i) + str(i)
+ buildComp(f,f[nameFull],['TnLtrSpcFlatLeft',segment,'TnLtrSpcPointRight'],0,0,i,0)
+
+ # dotted tone letters
+ for i in range(1,6):
+ nameFull = 'DotTnLtr' + str(i)
+ dot = 'TnLtrDot' + str(i)
+ buildComp(f,f[nameFull],['TnLtrSpcDotLeft',dot,'TnLtrSpcDotMiddle','TnLtrBar','TnLtrSpcFlatRight'],0,0,0,0)
+
+ # dotted left stem tone letters
+ for i in range(1,6):
+ nameFull = 'DotLftStemTnLtr' + str(i)
+ dot = 'TnLtrDot' + str(i)
+ buildComp(f,f[nameFull],['TnLtrSpcFlatLeft','TnLtrBar','TnLtrSpcDotMiddle',dot,'TnLtrSpcDotRight'],0,0,0,0)
+
+
+def doit(args):
+
+ psffont = UFO.Ufont(args.ifont, params = args.paramsobj)
+ rffont = OpenFont(args.ifont)
+ outfont = args.ofont
+
+ getParameters(psffont)
+
+ updateTLPieces(rffont)
+ rebuildTLComps(rffont)
+
+
+ rffont.save(outfont)
+
+ return
+
+def cmd() : execute(None,doit,argspec)
+if __name__ == "__main__": cmd()
diff --git a/examples/xmlDemo.py b/examples/xmlDemo.py
new file mode 100755
index 0000000..33459b6
--- /dev/null
+++ b/examples/xmlDemo.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+'Demo script for use of ETWriter'
+__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)'
+__author__ = 'David Raymond'
+
+from silfont.core import execute
+import silfont.etutil as etutil
+from xml.etree import cElementTree as ET
+
+argspec = [('outfile1',{'help': 'output file 1','default': './xmlDemo.xml','nargs': '?'}, {'type': 'outfile'}),
+ ('outfile2',{'help': 'output file 2','nargs': '?'}, {'type': 'outfile', 'def':'_2.xml'}),
+ ('outfile3',{'help': 'output file 3','nargs': '?'}, {'type': 'outfile', 'def':'_3.xml'})]
+
+def doit(args) :
+ ofile1 = args.outfile1
+ ofile2 = args.outfile2
+ ofile3 = args.outfile3
+
+ xmlstring = "<item>\n<subitem hello='world'>\n<subsub name='moon'>\n<value>lunar</value>\n</subsub>\n</subitem>"
+ xmlstring += "<subitem hello='jupiter'>\n<subsub name='moon'>\n<value>IO</value>\n</subsub>\n</subitem>\n</item>"
+
+ # Using etutil's xmlitem class
+
+ xmlobj = etutil.xmlitem()
+ xmlobj.etree = ET.fromstring(xmlstring)
+
+ etwobj = etutil.ETWriter(xmlobj.etree)
+ xmlobj.outxmlstr = etwobj.serialize_xml()
+
+ ofile1.write(xmlobj.outxmlstr)
+
+ # Just using ETWriter
+
+ etwobj = etutil.ETWriter( ET.fromstring(xmlstring) )
+ xmlstr = etwobj.serialize_xml()
+ ofile2.write(xmlstr)
+ # Changing parameters
+
+ etwobj = etutil.ETWriter( ET.fromstring(xmlstring) )
+ etwobj.indentIncr = " "
+ etwobj.indentFirst = ""
+ xmlstr = etwobj.serialize_xml()
+ ofile3.write(xmlstr)
+
+ # Close files and exit
+ ofile1.close()
+ ofile2.close()
+ ofile3.close()
+ return
+
+def cmd() : execute("",doit,argspec)
+if __name__ == "__main__": cmd()