diff options
author | Daniel Baumann <daniel@debian.org> | 2024-11-21 15:00:40 +0100 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-11-21 15:00:40 +0100 |
commit | 012d9cb5faed22cb9b4151569d30cc08563b02d1 (patch) | |
tree | fd901b9c231aeb8afa713851f23369fa4a1af2b3 /examples | |
parent | Initial commit. (diff) | |
download | pysilfont-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 'examples')
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() |