2000-01-17 18:58:46 +00:00
|
|
|
"""fontTools.ttLib -- a package for dealing with TrueType fonts.
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
This package offers translators to convert TrueType fonts to Python
|
2000-01-17 18:58:46 +00:00
|
|
|
objects and vice versa, and additionally from Python to TTX (an XML-based
|
|
|
|
text format) and vice versa.
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
Example interactive session:
|
|
|
|
|
|
|
|
Python 1.5.2c1 (#43, Mar 9 1999, 13:06:43) [CW PPC w/GUSI w/MSL]
|
|
|
|
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
|
|
|
|
>>> from fontTools import ttLib
|
|
|
|
>>> tt = ttLib.TTFont("afont.ttf")
|
|
|
|
>>> tt['maxp'].numGlyphs
|
|
|
|
242
|
|
|
|
>>> tt['OS/2'].achVendID
|
|
|
|
'B&H\000'
|
|
|
|
>>> tt['head'].unitsPerEm
|
|
|
|
2048
|
2000-01-17 18:58:46 +00:00
|
|
|
>>> tt.saveXML("afont.ttx")
|
1999-12-16 21:34:53 +00:00
|
|
|
Dumping 'LTSH' table...
|
|
|
|
Dumping 'OS/2' table...
|
|
|
|
Dumping 'VDMX' table...
|
|
|
|
Dumping 'cmap' table...
|
|
|
|
Dumping 'cvt ' table...
|
|
|
|
Dumping 'fpgm' table...
|
|
|
|
Dumping 'glyf' table...
|
|
|
|
Dumping 'hdmx' table...
|
|
|
|
Dumping 'head' table...
|
|
|
|
Dumping 'hhea' table...
|
|
|
|
Dumping 'hmtx' table...
|
|
|
|
Dumping 'loca' table...
|
|
|
|
Dumping 'maxp' table...
|
|
|
|
Dumping 'name' table...
|
|
|
|
Dumping 'post' table...
|
|
|
|
Dumping 'prep' table...
|
|
|
|
>>> tt2 = ttLib.TTFont()
|
2000-01-17 18:58:46 +00:00
|
|
|
>>> tt2.importXML("afont.ttx")
|
1999-12-16 21:34:53 +00:00
|
|
|
>>> tt2['maxp'].numGlyphs
|
|
|
|
242
|
|
|
|
>>>
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
1999-12-29 13:06:08 +00:00
|
|
|
#
|
2003-08-25 13:15:50 +00:00
|
|
|
# $Id: __init__.py,v 1.39 2003-08-25 13:15:50 jvr Exp $
|
1999-12-29 13:06:08 +00:00
|
|
|
#
|
|
|
|
|
2003-08-22 18:52:22 +00:00
|
|
|
import sys
|
1999-12-16 21:34:53 +00:00
|
|
|
import os
|
1999-12-29 13:06:08 +00:00
|
|
|
import string
|
2003-08-22 18:52:22 +00:00
|
|
|
haveMacSupport = sys.platform in ("mac", "darwin")
|
1999-12-16 21:34:53 +00:00
|
|
|
|
2002-05-02 15:23:25 +00:00
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
class TTLibError(Exception): pass
|
|
|
|
|
|
|
|
|
|
|
|
class TTFont:
|
|
|
|
|
|
|
|
"""The main font object. It manages file input and output, and offers
|
|
|
|
a convenient way of accessing tables.
|
|
|
|
Tables will be only decompiled when neccesary, ie. when they're actually
|
|
|
|
accessed. This means that simple operations can be extremely fast.
|
|
|
|
"""
|
|
|
|
|
2002-05-23 09:42:45 +00:00
|
|
|
def __init__(self, file=None, res_name_or_index=None,
|
|
|
|
sfntVersion="\000\001\000\000", checkChecksums=0,
|
1999-12-18 18:06:25 +00:00
|
|
|
verbose=0, recalcBBoxes=1):
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
"""The constructor can be called with a few different arguments.
|
|
|
|
When reading a font from disk, 'file' should be either a pathname
|
|
|
|
pointing to a file, or a readable file object.
|
|
|
|
|
|
|
|
It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt
|
|
|
|
resource name or an sfnt resource index number or zero. The latter
|
|
|
|
case will cause TTLib to autodetect whether the file is a flat file
|
|
|
|
or a suitcase. (If it's a suitcase, only the first 'sfnt' resource
|
|
|
|
will be read!)
|
|
|
|
|
2002-05-12 17:14:50 +00:00
|
|
|
The 'checkChecksums' argument is used to specify how sfnt
|
1999-12-16 21:34:53 +00:00
|
|
|
checksums are treated upon reading a file from disk:
|
|
|
|
0: don't check (default)
|
2002-05-23 09:42:45 +00:00
|
|
|
1: check, print warnings if a wrong checksum is found
|
1999-12-16 21:34:53 +00:00
|
|
|
2: check, raise an exception if a wrong checksum is found.
|
|
|
|
|
|
|
|
The TTFont constructor can also be called without a 'file'
|
|
|
|
argument: this is the way to create a new empty font.
|
|
|
|
In this case you can optionally supply the 'sfntVersion' argument.
|
1999-12-18 18:06:25 +00:00
|
|
|
|
1999-12-23 15:16:22 +00:00
|
|
|
If the recalcBBoxes argument is false, a number of things will *not*
|
1999-12-23 14:44:16 +00:00
|
|
|
be recalculated upon save/compile:
|
1999-12-23 15:16:22 +00:00
|
|
|
1) glyph bounding boxes
|
|
|
|
2) maxp font bounding box
|
|
|
|
3) hhea min/max values
|
|
|
|
(1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-).
|
2000-01-17 18:58:46 +00:00
|
|
|
Additionally, upon importing an TTX file, this option cause glyphs
|
1999-12-23 15:16:22 +00:00
|
|
|
to be compiled right away. This should reduce memory consumption
|
|
|
|
greatly, and therefore should have some impact on the time needed
|
|
|
|
to parse/compile large fonts.
|
1999-12-16 21:34:53 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
import sfnt
|
|
|
|
self.verbose = verbose
|
1999-12-18 18:06:25 +00:00
|
|
|
self.recalcBBoxes = recalcBBoxes
|
1999-12-16 21:34:53 +00:00
|
|
|
self.tables = {}
|
|
|
|
self.reader = None
|
|
|
|
if not file:
|
|
|
|
self.sfntVersion = sfntVersion
|
|
|
|
return
|
2003-08-22 18:52:22 +00:00
|
|
|
if not hasattr(file, "read"):
|
|
|
|
# assume file is a string
|
|
|
|
if haveMacSupport and res_name_or_index is not None:
|
1999-12-16 21:34:53 +00:00
|
|
|
# on the mac, we deal with sfnt resources as well as flat files
|
|
|
|
import macUtils
|
|
|
|
if res_name_or_index == 0:
|
|
|
|
if macUtils.getSFNTResIndices(file):
|
|
|
|
# get the first available sfnt font.
|
|
|
|
file = macUtils.SFNTResourceReader(file, 1)
|
|
|
|
else:
|
|
|
|
file = open(file, "rb")
|
|
|
|
else:
|
|
|
|
file = macUtils.SFNTResourceReader(file, res_name_or_index)
|
|
|
|
else:
|
|
|
|
file = open(file, "rb")
|
|
|
|
else:
|
|
|
|
pass # assume "file" is a readable file object
|
2002-05-12 17:14:50 +00:00
|
|
|
self.reader = sfnt.SFNTReader(file, checkChecksums)
|
1999-12-16 21:34:53 +00:00
|
|
|
self.sfntVersion = self.reader.sfntVersion
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
"""If we still have a reader object, close it."""
|
|
|
|
if self.reader is not None:
|
|
|
|
self.reader.close()
|
|
|
|
|
1999-12-18 18:06:25 +00:00
|
|
|
def save(self, file, makeSuitcase=0):
|
1999-12-16 21:34:53 +00:00
|
|
|
"""Save the font to disk. Similarly to the constructor,
|
|
|
|
the 'file' argument can be either a pathname or a writable
|
|
|
|
file object.
|
|
|
|
|
1999-12-18 18:06:25 +00:00
|
|
|
On the Mac, if makeSuitcase is true, a suitcase (resource fork)
|
|
|
|
file will we made instead of a flat .ttf file.
|
1999-12-16 21:34:53 +00:00
|
|
|
"""
|
2000-10-02 07:51:42 +00:00
|
|
|
from fontTools.ttLib import sfnt
|
2003-08-22 18:52:22 +00:00
|
|
|
if not hasattr(file, "write"):
|
2000-10-02 07:51:42 +00:00
|
|
|
closeStream = 1
|
1999-12-18 18:06:25 +00:00
|
|
|
if os.name == "mac" and makeSuitcase:
|
1999-12-16 21:34:53 +00:00
|
|
|
import macUtils
|
|
|
|
file = macUtils.SFNTResourceWriter(file, self)
|
|
|
|
else:
|
|
|
|
file = open(file, "wb")
|
|
|
|
if os.name == "mac":
|
|
|
|
import macfs
|
|
|
|
fss = macfs.FSSpec(file.name)
|
|
|
|
fss.SetCreatorType('mdos', 'BINA')
|
|
|
|
else:
|
2000-10-02 07:51:42 +00:00
|
|
|
# assume "file" is a writable file object
|
|
|
|
closeStream = 0
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
tags = self.keys()
|
2003-08-22 19:44:08 +00:00
|
|
|
if "GlyphOrder" in tags:
|
|
|
|
tags.remove("GlyphOrder")
|
1999-12-16 21:34:53 +00:00
|
|
|
numTables = len(tags)
|
|
|
|
writer = sfnt.SFNTWriter(file, numTables, self.sfntVersion)
|
|
|
|
|
|
|
|
done = []
|
|
|
|
for tag in tags:
|
|
|
|
self._writeTable(tag, writer, done)
|
|
|
|
|
2000-10-02 07:51:42 +00:00
|
|
|
writer.close(closeStream)
|
1999-12-16 21:34:53 +00:00
|
|
|
|
2000-01-05 20:43:36 +00:00
|
|
|
def saveXML(self, fileOrPath, progress=None,
|
2000-02-01 15:29:03 +00:00
|
|
|
tables=None, skipTables=None, splitTables=0, disassembleInstructions=1):
|
2000-01-17 18:58:46 +00:00
|
|
|
"""Export the font as TTX (an XML-based text file), or as a series of text
|
1999-12-29 13:06:08 +00:00
|
|
|
files when splitTables is true. In the latter case, the 'fileOrPath'
|
|
|
|
argument should be a path to a directory.
|
2000-01-05 20:43:36 +00:00
|
|
|
The 'tables' argument must either be false (dump all tables) or a
|
|
|
|
list of tables to dump. The 'skipTables' argument may be a list of tables
|
|
|
|
to skip, but only when the 'tables' argument is false.
|
1999-12-29 13:06:08 +00:00
|
|
|
"""
|
2002-05-02 15:23:25 +00:00
|
|
|
from fontTools import version
|
1999-12-16 21:34:53 +00:00
|
|
|
import xmlWriter
|
2000-02-01 15:29:03 +00:00
|
|
|
|
|
|
|
self.disassembleInstructions = disassembleInstructions
|
1999-12-16 21:34:53 +00:00
|
|
|
if not tables:
|
|
|
|
tables = self.keys()
|
2003-08-22 19:44:08 +00:00
|
|
|
if "GlyphOrder" not in tables:
|
|
|
|
tables = ["GlyphOrder"] + tables
|
2000-01-05 20:43:36 +00:00
|
|
|
if skipTables:
|
|
|
|
for tag in skipTables:
|
|
|
|
if tag in tables:
|
|
|
|
tables.remove(tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
numTables = len(tables)
|
|
|
|
if progress:
|
2002-07-23 16:44:25 +00:00
|
|
|
progress.set(0, numTables)
|
|
|
|
idlefunc = getattr(progress, "idle", None)
|
|
|
|
else:
|
|
|
|
idlefunc = None
|
2002-05-22 20:15:10 +00:00
|
|
|
|
2002-07-23 16:44:25 +00:00
|
|
|
writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc)
|
2002-05-22 20:15:10 +00:00
|
|
|
writer.begintag("ttFont", sfntVersion=`self.sfntVersion`[1:-1],
|
|
|
|
ttLibVersion=version)
|
|
|
|
writer.newline()
|
|
|
|
|
1999-12-27 19:48:21 +00:00
|
|
|
if not splitTables:
|
|
|
|
writer.newline()
|
|
|
|
else:
|
2002-05-11 21:18:12 +00:00
|
|
|
# 'fileOrPath' must now be a path
|
|
|
|
path, ext = os.path.splitext(fileOrPath)
|
|
|
|
fileNameTemplate = path + ".%s" + ext
|
1999-12-27 19:48:21 +00:00
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
for i in range(numTables):
|
2002-07-23 16:44:25 +00:00
|
|
|
if progress:
|
|
|
|
progress.set(i)
|
1999-12-16 21:34:53 +00:00
|
|
|
tag = tables[i]
|
1999-12-27 19:48:21 +00:00
|
|
|
if splitTables:
|
2002-05-13 16:21:51 +00:00
|
|
|
tablePath = fileNameTemplate % tagToIdentifier(tag)
|
2002-07-23 16:44:25 +00:00
|
|
|
tableWriter = xmlWriter.XMLWriter(tablePath, idlefunc=idlefunc)
|
2002-05-22 20:15:10 +00:00
|
|
|
tableWriter.begintag("ttFont", ttLibVersion=version)
|
|
|
|
tableWriter.newline()
|
|
|
|
tableWriter.newline()
|
2002-05-23 09:42:45 +00:00
|
|
|
writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath))
|
1999-12-27 19:48:21 +00:00
|
|
|
writer.newline()
|
1999-12-16 21:34:53 +00:00
|
|
|
else:
|
2002-05-22 20:15:10 +00:00
|
|
|
tableWriter = writer
|
2002-05-23 09:42:45 +00:00
|
|
|
self._tableToXML(tableWriter, tag, progress)
|
1999-12-27 19:48:21 +00:00
|
|
|
if splitTables:
|
2002-05-22 20:15:10 +00:00
|
|
|
tableWriter.endtag("ttFont")
|
|
|
|
tableWriter.newline()
|
|
|
|
tableWriter.close()
|
2002-07-23 16:44:25 +00:00
|
|
|
if progress:
|
|
|
|
progress.set((i + 1))
|
2002-05-22 20:15:10 +00:00
|
|
|
writer.endtag("ttFont")
|
|
|
|
writer.newline()
|
|
|
|
writer.close()
|
1999-12-16 21:34:53 +00:00
|
|
|
if self.verbose:
|
2000-01-17 18:58:46 +00:00
|
|
|
debugmsg("Done dumping TTX")
|
1999-12-16 21:34:53 +00:00
|
|
|
|
2002-05-23 09:42:45 +00:00
|
|
|
def _tableToXML(self, writer, tag, progress):
|
2002-05-22 20:15:10 +00:00
|
|
|
if self.has_key(tag):
|
|
|
|
table = self[tag]
|
|
|
|
report = "Dumping '%s' table..." % tag
|
|
|
|
else:
|
|
|
|
report = "No '%s' table found." % tag
|
|
|
|
if progress:
|
2002-07-23 16:44:25 +00:00
|
|
|
progress.setLabel(report)
|
2002-05-22 20:15:10 +00:00
|
|
|
elif self.verbose:
|
|
|
|
debugmsg(report)
|
|
|
|
else:
|
|
|
|
print report
|
|
|
|
if not self.has_key(tag):
|
|
|
|
return
|
2002-05-23 09:42:45 +00:00
|
|
|
xmlTag = tagToXML(tag)
|
2002-05-22 20:15:10 +00:00
|
|
|
if hasattr(table, "ERROR"):
|
|
|
|
writer.begintag(xmlTag, ERROR="decompilation error")
|
|
|
|
else:
|
|
|
|
writer.begintag(xmlTag)
|
|
|
|
writer.newline()
|
|
|
|
if tag in ("glyf", "CFF "):
|
|
|
|
table.toXML(writer, self, progress)
|
|
|
|
else:
|
|
|
|
table.toXML(writer, self)
|
|
|
|
writer.endtag(xmlTag)
|
|
|
|
writer.newline()
|
|
|
|
writer.newline()
|
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
def importXML(self, file, progress=None):
|
2002-05-01 21:06:11 +00:00
|
|
|
"""Import a TTX file (an XML-based text format), so as to recreate
|
1999-12-16 21:34:53 +00:00
|
|
|
a font object.
|
|
|
|
"""
|
2002-05-25 14:56:29 +00:00
|
|
|
if self.has_key("maxp") and self.has_key("post"):
|
2002-05-25 15:28:48 +00:00
|
|
|
# Make sure the glyph order is loaded, as it otherwise gets
|
|
|
|
# lost if the XML doesn't contain the glyph order, yet does
|
|
|
|
# contain the table which was originally used to extract the
|
|
|
|
# glyph names from (ie. 'post', 'cmap' or 'CFF ').
|
2002-05-25 14:56:29 +00:00
|
|
|
self.getGlyphOrder()
|
2002-05-01 21:06:11 +00:00
|
|
|
import xmlImport
|
|
|
|
xmlImport.importXML(self, file, progress)
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
def isLoaded(self, tag):
|
|
|
|
"""Return true if the table identified by 'tag' has been
|
|
|
|
decompiled and loaded into memory."""
|
|
|
|
return self.tables.has_key(tag)
|
|
|
|
|
|
|
|
def has_key(self, tag):
|
|
|
|
if self.isLoaded(tag):
|
|
|
|
return 1
|
|
|
|
elif self.reader and self.reader.has_key(tag):
|
|
|
|
return 1
|
2002-05-23 09:42:45 +00:00
|
|
|
elif tag == "GlyphOrder":
|
|
|
|
return 1
|
1999-12-16 21:34:53 +00:00
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
2003-08-25 13:15:50 +00:00
|
|
|
__contains__ = has_key
|
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
def keys(self):
|
|
|
|
keys = self.tables.keys()
|
|
|
|
if self.reader:
|
|
|
|
for key in self.reader.keys():
|
|
|
|
if key not in keys:
|
|
|
|
keys.append(key)
|
2003-08-22 19:44:08 +00:00
|
|
|
|
|
|
|
if "glyf" in keys:
|
|
|
|
tableSort = sortTTFFont
|
|
|
|
elif "CFF " in keys:
|
|
|
|
tableSort = sortOTFFont
|
|
|
|
else:
|
|
|
|
assert 0, "Font has neither glyf nor CFF table. Table list:" + str(keys)
|
|
|
|
keys.sort(tableSort)
|
|
|
|
return keys
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self.keys())
|
|
|
|
|
|
|
|
def __getitem__(self, tag):
|
|
|
|
try:
|
|
|
|
return self.tables[tag]
|
|
|
|
except KeyError:
|
2002-05-23 09:42:45 +00:00
|
|
|
if tag == "GlyphOrder":
|
|
|
|
table = GlyphOrder(tag)
|
|
|
|
self.tables[tag] = table
|
|
|
|
return table
|
1999-12-16 21:34:53 +00:00
|
|
|
if self.reader is not None:
|
2000-01-03 23:00:10 +00:00
|
|
|
import traceback
|
1999-12-16 21:34:53 +00:00
|
|
|
if self.verbose:
|
2002-07-23 16:44:25 +00:00
|
|
|
debugmsg("Reading '%s' table from disk" % tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
data = self.reader[tag]
|
2002-05-13 16:21:51 +00:00
|
|
|
tableClass = getTableClass(tag)
|
|
|
|
table = tableClass(tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
self.tables[tag] = table
|
|
|
|
if self.verbose:
|
2002-07-23 16:44:25 +00:00
|
|
|
debugmsg("Decompiling '%s' table" % tag)
|
2000-01-03 23:00:10 +00:00
|
|
|
try:
|
|
|
|
table.decompile(data, self)
|
2000-02-13 16:23:28 +00:00
|
|
|
except "_ _ F O O _ _": # dummy exception to disable exception catching
|
2000-01-05 20:43:36 +00:00
|
|
|
print "An exception occurred during the decompilation of the '%s' table" % tag
|
2000-01-03 23:00:10 +00:00
|
|
|
from tables.DefaultTable import DefaultTable
|
|
|
|
import StringIO
|
|
|
|
file = StringIO.StringIO()
|
|
|
|
traceback.print_exc(file=file)
|
|
|
|
table = DefaultTable(tag)
|
|
|
|
table.ERROR = file.getvalue()
|
|
|
|
self.tables[tag] = table
|
|
|
|
table.decompile(data, self)
|
1999-12-16 21:34:53 +00:00
|
|
|
return table
|
|
|
|
else:
|
|
|
|
raise KeyError, "'%s' table not found" % tag
|
|
|
|
|
|
|
|
def __setitem__(self, tag, table):
|
|
|
|
self.tables[tag] = table
|
|
|
|
|
|
|
|
def __delitem__(self, tag):
|
2002-05-04 22:04:02 +00:00
|
|
|
if not self.has_key(tag):
|
|
|
|
raise KeyError, "'%s' table not found" % tag
|
|
|
|
if self.tables.has_key(tag):
|
|
|
|
del self.tables[tag]
|
|
|
|
if self.reader and self.reader.has_key(tag):
|
|
|
|
del self.reader[tag]
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
def setGlyphOrder(self, glyphOrder):
|
|
|
|
self.glyphOrder = glyphOrder
|
|
|
|
|
|
|
|
def getGlyphOrder(self):
|
2002-05-05 09:48:31 +00:00
|
|
|
try:
|
|
|
|
return self.glyphOrder
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
if self.has_key('CFF '):
|
2002-05-13 11:26:38 +00:00
|
|
|
cff = self['CFF ']
|
2003-08-22 19:44:08 +00:00
|
|
|
self.glyphOrder = cff.getGlyphOrder()
|
2002-05-05 09:48:31 +00:00
|
|
|
elif self.has_key('post'):
|
|
|
|
# TrueType font
|
|
|
|
glyphOrder = self['post'].getGlyphOrder()
|
|
|
|
if glyphOrder is None:
|
|
|
|
#
|
|
|
|
# No names found in the 'post' table.
|
|
|
|
# Try to create glyph names from the unicode cmap (if available)
|
|
|
|
# in combination with the Adobe Glyph List (AGL).
|
|
|
|
#
|
2000-08-23 12:31:52 +00:00
|
|
|
self._getGlyphNamesFromCmap()
|
2002-05-05 09:48:31 +00:00
|
|
|
else:
|
|
|
|
self.glyphOrder = glyphOrder
|
|
|
|
else:
|
|
|
|
self._getGlyphNamesFromCmap()
|
1999-12-16 21:34:53 +00:00
|
|
|
return self.glyphOrder
|
|
|
|
|
|
|
|
def _getGlyphNamesFromCmap(self):
|
2002-05-05 11:29:33 +00:00
|
|
|
#
|
|
|
|
# This is rather convoluted, but then again, it's an interesting problem:
|
|
|
|
# - we need to use the unicode values found in the cmap table to
|
|
|
|
# build glyph names (eg. because there is only a minimal post table,
|
|
|
|
# or none at all).
|
|
|
|
# - but the cmap parser also needs glyph names to work with...
|
|
|
|
# So here's what we do:
|
|
|
|
# - make up glyph names based on glyphID
|
|
|
|
# - load a temporary cmap table based on those names
|
|
|
|
# - extract the unicode values, build the "real" glyph names
|
|
|
|
# - unload the temporary cmap table
|
|
|
|
#
|
|
|
|
if self.isLoaded("cmap"):
|
|
|
|
# Bootstrapping: we're getting called by the cmap parser
|
|
|
|
# itself. This means self.tables['cmap'] contains a partially
|
|
|
|
# loaded cmap, making it impossible to get at a unicode
|
|
|
|
# subtable here. We remove the partially loaded cmap and
|
|
|
|
# restore it later.
|
|
|
|
# This only happens if the cmap table is loaded before any
|
|
|
|
# other table that does f.getGlyphOrder() or f.getGlyphName().
|
|
|
|
cmapLoading = self.tables['cmap']
|
|
|
|
del self.tables['cmap']
|
|
|
|
else:
|
|
|
|
cmapLoading = None
|
|
|
|
# Make up glyph names based on glyphID, which will be used by the
|
|
|
|
# temporary cmap and by the real cmap in case we don't find a unicode
|
|
|
|
# cmap.
|
1999-12-16 21:34:53 +00:00
|
|
|
numGlyphs = int(self['maxp'].numGlyphs)
|
|
|
|
glyphOrder = [None] * numGlyphs
|
|
|
|
glyphOrder[0] = ".notdef"
|
|
|
|
for i in range(1, numGlyphs):
|
|
|
|
glyphOrder[i] = "glyph%.5d" % i
|
|
|
|
# Set the glyph order, so the cmap parser has something
|
2002-05-05 11:29:33 +00:00
|
|
|
# to work with (so we don't get called recursively).
|
1999-12-16 21:34:53 +00:00
|
|
|
self.glyphOrder = glyphOrder
|
2002-05-05 11:29:33 +00:00
|
|
|
# Get a (new) temporary cmap (based on the just invented names)
|
1999-12-16 21:34:53 +00:00
|
|
|
tempcmap = self['cmap'].getcmap(3, 1)
|
|
|
|
if tempcmap is not None:
|
|
|
|
# we have a unicode cmap
|
1999-12-29 13:06:08 +00:00
|
|
|
from fontTools import agl
|
1999-12-16 21:34:53 +00:00
|
|
|
cmap = tempcmap.cmap
|
|
|
|
# create a reverse cmap dict
|
|
|
|
reversecmap = {}
|
|
|
|
for unicode, name in cmap.items():
|
|
|
|
reversecmap[name] = unicode
|
2001-02-23 21:58:57 +00:00
|
|
|
allNames = {}
|
1999-12-16 21:34:53 +00:00
|
|
|
for i in range(numGlyphs):
|
|
|
|
tempName = glyphOrder[i]
|
|
|
|
if reversecmap.has_key(tempName):
|
|
|
|
unicode = reversecmap[tempName]
|
|
|
|
if agl.UV2AGL.has_key(unicode):
|
|
|
|
# get name from the Adobe Glyph List
|
2001-02-23 21:58:57 +00:00
|
|
|
glyphName = agl.UV2AGL[unicode]
|
1999-12-16 21:34:53 +00:00
|
|
|
else:
|
|
|
|
# create uni<CODE> name
|
2001-02-23 21:58:57 +00:00
|
|
|
glyphName = "uni" + string.upper(string.zfill(
|
|
|
|
hex(unicode)[2:], 4))
|
|
|
|
tempName = glyphName
|
|
|
|
n = 1
|
|
|
|
while allNames.has_key(tempName):
|
|
|
|
tempName = glyphName + "#" + `n`
|
|
|
|
n = n + 1
|
|
|
|
glyphOrder[i] = tempName
|
|
|
|
allNames[tempName] = 1
|
2002-05-05 11:29:33 +00:00
|
|
|
# Delete the temporary cmap table from the cache, so it can
|
|
|
|
# be parsed again with the right names.
|
1999-12-16 21:34:53 +00:00
|
|
|
del self.tables['cmap']
|
|
|
|
else:
|
|
|
|
pass # no unicode cmap available, stick with the invented names
|
|
|
|
self.glyphOrder = glyphOrder
|
2002-05-05 11:29:33 +00:00
|
|
|
if cmapLoading:
|
|
|
|
# restore partially loaded cmap, so it can continue loading
|
|
|
|
# using the proper names.
|
|
|
|
self.tables['cmap'] = cmapLoading
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
def getGlyphNames(self):
|
|
|
|
"""Get a list of glyph names, sorted alphabetically."""
|
|
|
|
glyphNames = self.getGlyphOrder()[:]
|
|
|
|
glyphNames.sort()
|
|
|
|
return glyphNames
|
|
|
|
|
|
|
|
def getGlyphNames2(self):
|
1999-12-17 12:54:19 +00:00
|
|
|
"""Get a list of glyph names, sorted alphabetically,
|
|
|
|
but not case sensitive.
|
|
|
|
"""
|
1999-12-16 21:34:53 +00:00
|
|
|
from fontTools.misc import textTools
|
|
|
|
return textTools.caselessSort(self.getGlyphOrder())
|
|
|
|
|
|
|
|
def getGlyphName(self, glyphID):
|
2002-05-13 11:26:38 +00:00
|
|
|
try:
|
|
|
|
return self.getGlyphOrder()[glyphID]
|
|
|
|
except IndexError:
|
|
|
|
# XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in
|
|
|
|
# the cmap table than there are glyphs. I don't think it's legal...
|
|
|
|
return "glyph%.5d" % glyphID
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
def getGlyphID(self, glyphName):
|
|
|
|
if not hasattr(self, "_reverseGlyphOrderDict"):
|
|
|
|
self._buildReverseGlyphOrderDict()
|
|
|
|
glyphOrder = self.getGlyphOrder()
|
|
|
|
d = self._reverseGlyphOrderDict
|
|
|
|
if not d.has_key(glyphName):
|
|
|
|
if glyphName in glyphOrder:
|
|
|
|
self._buildReverseGlyphOrderDict()
|
|
|
|
return self.getGlyphID(glyphName)
|
|
|
|
else:
|
|
|
|
raise KeyError, glyphName
|
|
|
|
glyphID = d[glyphName]
|
|
|
|
if glyphName <> glyphOrder[glyphID]:
|
|
|
|
self._buildReverseGlyphOrderDict()
|
|
|
|
return self.getGlyphID(glyphName)
|
|
|
|
return glyphID
|
|
|
|
|
|
|
|
def _buildReverseGlyphOrderDict(self):
|
|
|
|
self._reverseGlyphOrderDict = d = {}
|
|
|
|
glyphOrder = self.getGlyphOrder()
|
|
|
|
for glyphID in range(len(glyphOrder)):
|
|
|
|
d[glyphOrder[glyphID]] = glyphID
|
|
|
|
|
|
|
|
def _writeTable(self, tag, writer, done):
|
|
|
|
"""Internal helper function for self.save(). Keeps track of
|
|
|
|
inter-table dependencies.
|
|
|
|
"""
|
|
|
|
if tag in done:
|
|
|
|
return
|
2002-05-13 16:21:51 +00:00
|
|
|
tableClass = getTableClass(tag)
|
|
|
|
for masterTable in tableClass.dependencies:
|
1999-12-16 21:34:53 +00:00
|
|
|
if masterTable not in done:
|
|
|
|
if self.has_key(masterTable):
|
|
|
|
self._writeTable(masterTable, writer, done)
|
|
|
|
else:
|
|
|
|
done.append(masterTable)
|
2002-05-05 09:48:31 +00:00
|
|
|
tabledata = self.getTableData(tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
if self.verbose:
|
|
|
|
debugmsg("writing '%s' table to disk" % tag)
|
|
|
|
writer[tag] = tabledata
|
|
|
|
done.append(tag)
|
|
|
|
|
2002-05-05 09:48:31 +00:00
|
|
|
def getTableData(self, tag):
|
|
|
|
"""Returns raw table data, whether compiled or directly read from disk.
|
1999-12-16 21:34:53 +00:00
|
|
|
"""
|
|
|
|
if self.isLoaded(tag):
|
|
|
|
if self.verbose:
|
|
|
|
debugmsg("compiling '%s' table" % tag)
|
|
|
|
return self.tables[tag].compile(self)
|
|
|
|
elif self.reader and self.reader.has_key(tag):
|
|
|
|
if self.verbose:
|
2002-07-23 16:44:25 +00:00
|
|
|
debugmsg("Reading '%s' table from disk" % tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
return self.reader[tag]
|
|
|
|
else:
|
|
|
|
raise KeyError, tag
|
2003-08-25 13:15:50 +00:00
|
|
|
|
|
|
|
def getGlyphSet(self, preferCFF=1):
|
|
|
|
"""Return a generic GlyphSet, which is a dict-like object
|
|
|
|
mapping glyph names to glyph objects. The returned glyph objects
|
|
|
|
have a .draw() method that supports the Pen protocol, and will
|
|
|
|
have an attribute named 'width', but only *after* the .draw() method
|
|
|
|
has been called.
|
|
|
|
|
|
|
|
If the font is CFF-based, the outlines will be taken from the 'CFF '
|
|
|
|
table. Otherwise the outlines will be taken from the 'glyf' table.
|
|
|
|
If the font contains both a 'CFF ' and a 'glyf' table, you can use
|
|
|
|
the 'preferCFF' argument to specify which one should be taken.
|
|
|
|
"""
|
|
|
|
if preferCFF and self.has_key("CFF "):
|
|
|
|
return self["CFF "].cff.values()[0].CharStrings
|
|
|
|
if self.has_key("glyf"):
|
|
|
|
return _TTGlyphSet(self)
|
|
|
|
if not preferCFF and self.has_key("CFF "):
|
|
|
|
return self["CFF "].cff.values(0).CharStrings
|
|
|
|
raise TTLibError, "Font contains no outlines"
|
|
|
|
|
|
|
|
|
|
|
|
class _TTGlyphSet:
|
|
|
|
|
|
|
|
"""Generic dict-like GlyphSet class, meant as a TrueType counterpart
|
|
|
|
to CFF's CharString dict. See TTFont.getGlyphSet().
|
|
|
|
"""
|
|
|
|
|
|
|
|
# This class is distinct from the 'glyf' table itself because we need
|
|
|
|
# access to the 'hmtx' table, which could cause a dependency problem
|
|
|
|
# there when reading from XML.
|
|
|
|
|
|
|
|
def __init__(self, ttFont):
|
|
|
|
self._ttFont = ttFont
|
|
|
|
|
|
|
|
def keys(self):
|
|
|
|
return self._glyfTable.keys()
|
|
|
|
|
|
|
|
def has_key(self, glyphName):
|
|
|
|
return self._glyfTable.has_key(glyphName)
|
|
|
|
|
|
|
|
__contains__ = has_key
|
|
|
|
|
|
|
|
def __getitem__(self, glyphName):
|
|
|
|
return _TTGlyph(glyphName, self._ttFont)
|
|
|
|
|
|
|
|
|
|
|
|
class _TTGlyph:
|
|
|
|
|
|
|
|
"""Wrapper for a TrueType glyph that supports the Pen protocol.
|
|
|
|
Instances have an attribute named 'width', but only *after* the .draw()
|
|
|
|
method has been called.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, glyphName, ttFont):
|
|
|
|
self._glyphName = glyphName
|
|
|
|
self._ttFont = ttFont
|
|
|
|
|
|
|
|
def draw(self, pen):
|
|
|
|
glyfTable = self._ttFont['glyf']
|
|
|
|
glyph = glyfTable[self._glyphName]
|
|
|
|
self.width, lsb = self._ttFont['hmtx'][self._glyphName]
|
|
|
|
if hasattr(glyph, "xMin"):
|
|
|
|
offset = lsb - glyph.xMin
|
|
|
|
else:
|
|
|
|
offset = 0
|
|
|
|
if glyph.isComposite():
|
|
|
|
for component in glyph:
|
|
|
|
glyphName, transform = component.getComponentInfo()
|
|
|
|
pen.addComponent(glyphName, transform)
|
|
|
|
else:
|
|
|
|
coordinates, endPts, flags = glyph.getCoordinates(glyfTable)
|
|
|
|
if offset:
|
|
|
|
coordinates = coordinates + (offset, 0)
|
|
|
|
start = 0
|
|
|
|
for end in endPts:
|
|
|
|
end = end + 1
|
|
|
|
contour = coordinates[start:end].tolist()
|
|
|
|
cFlags = flags[start:end].tolist()
|
|
|
|
start = end
|
|
|
|
if 1 not in cFlags:
|
|
|
|
# There is not a single on-curve point on the curve,
|
|
|
|
# use pen.qCurveTo's special case by specifying None
|
|
|
|
# as the on-curve point.
|
|
|
|
contour.append(None)
|
|
|
|
pen.qCurveTo(*contour)
|
|
|
|
else:
|
|
|
|
# Shuffle the points so that contour is guaranteed to *end*
|
|
|
|
# in an on-curve point, which we'll use for the moveTo.
|
|
|
|
firstOnCurve = cFlags.index(1) + 1
|
|
|
|
contour = contour[firstOnCurve:] + contour[:firstOnCurve]
|
|
|
|
cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
|
|
|
|
pen.moveTo(contour[-1])
|
|
|
|
while contour:
|
|
|
|
nextOnCurve = cFlags.index(1) + 1
|
|
|
|
if nextOnCurve == 1:
|
|
|
|
pen.lineTo(contour[0])
|
|
|
|
else:
|
|
|
|
pen.qCurveTo(*contour[:nextOnCurve])
|
|
|
|
contour = contour[nextOnCurve:]
|
|
|
|
cFlags = cFlags[nextOnCurve:]
|
|
|
|
pen.closePath()
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
|
2002-05-23 09:42:45 +00:00
|
|
|
class GlyphOrder:
|
|
|
|
|
2002-05-25 14:56:29 +00:00
|
|
|
"""A pseudo table. The glyph order isn't in the font as a separate
|
|
|
|
table, but it's nice to present it as such in the TTX format.
|
2002-05-23 09:42:45 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, tag):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def toXML(self, writer, ttFont):
|
|
|
|
glyphOrder = ttFont.getGlyphOrder()
|
2002-05-24 09:58:04 +00:00
|
|
|
writer.comment("The 'id' attribute is only for humans; "
|
|
|
|
"it is ignored when parsed.")
|
2002-05-23 09:42:45 +00:00
|
|
|
writer.newline()
|
|
|
|
for i in range(len(glyphOrder)):
|
|
|
|
glyphName = glyphOrder[i]
|
|
|
|
writer.simpletag("GlyphID", id=i, name=glyphName)
|
|
|
|
writer.newline()
|
|
|
|
|
|
|
|
def fromXML(self, (name, attrs, content), ttFont):
|
|
|
|
if not hasattr(self, "glyphOrder"):
|
|
|
|
self.glyphOrder = []
|
|
|
|
ttFont.setGlyphOrder(self.glyphOrder)
|
|
|
|
if name == "GlyphID":
|
|
|
|
self.glyphOrder.append(attrs["name"])
|
|
|
|
|
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
def _test_endianness():
|
|
|
|
"""Test the endianness of the machine. This is crucial to know
|
|
|
|
since TrueType data is always big endian, even on little endian
|
|
|
|
machines. There are quite a few situations where we explicitly
|
|
|
|
need to swap some bytes.
|
|
|
|
"""
|
|
|
|
import struct
|
|
|
|
data = struct.pack("h", 0x01)
|
|
|
|
if data == "\000\001":
|
|
|
|
return "big"
|
|
|
|
elif data == "\001\000":
|
|
|
|
return "little"
|
|
|
|
else:
|
|
|
|
assert 0, "endian confusion!"
|
|
|
|
|
|
|
|
endian = _test_endianness()
|
|
|
|
|
|
|
|
|
|
|
|
def getTableModule(tag):
|
|
|
|
"""Fetch the packer/unpacker module for a table.
|
|
|
|
Return None when no module is found.
|
|
|
|
"""
|
|
|
|
import tables
|
2002-05-13 16:21:51 +00:00
|
|
|
pyTag = tagToIdentifier(tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
try:
|
2003-08-22 18:52:22 +00:00
|
|
|
__import__("fontTools.ttLib.tables." + pyTag)
|
1999-12-16 21:34:53 +00:00
|
|
|
except ImportError:
|
|
|
|
return None
|
|
|
|
else:
|
2002-05-13 16:21:51 +00:00
|
|
|
return getattr(tables, pyTag)
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def getTableClass(tag):
|
|
|
|
"""Fetch the packer/unpacker class for a table.
|
|
|
|
Return None when no class is found.
|
|
|
|
"""
|
|
|
|
module = getTableModule(tag)
|
|
|
|
if module is None:
|
|
|
|
from tables.DefaultTable import DefaultTable
|
|
|
|
return DefaultTable
|
2002-05-13 16:21:51 +00:00
|
|
|
pyTag = tagToIdentifier(tag)
|
|
|
|
tableClass = getattr(module, "table_" + pyTag)
|
|
|
|
return tableClass
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
|
2002-05-13 16:21:51 +00:00
|
|
|
def newTable(tag):
|
1999-12-16 21:34:53 +00:00
|
|
|
"""Return a new instance of a table."""
|
2002-05-13 16:21:51 +00:00
|
|
|
tableClass = getTableClass(tag)
|
|
|
|
return tableClass(tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _escapechar(c):
|
2002-05-13 16:21:51 +00:00
|
|
|
"""Helper function for tagToIdentifier()"""
|
1999-12-16 21:34:53 +00:00
|
|
|
import re
|
|
|
|
if re.match("[a-z0-9]", c):
|
|
|
|
return "_" + c
|
|
|
|
elif re.match("[A-Z]", c):
|
|
|
|
return c + "_"
|
|
|
|
else:
|
|
|
|
return hex(ord(c))[2:]
|
|
|
|
|
|
|
|
|
2002-05-13 16:21:51 +00:00
|
|
|
def tagToIdentifier(tag):
|
1999-12-16 21:34:53 +00:00
|
|
|
"""Convert a table tag to a valid (but UGLY) python identifier,
|
|
|
|
as well as a filename that's guaranteed to be unique even on a
|
|
|
|
caseless file system. Each character is mapped to two characters.
|
|
|
|
Lowercase letters get an underscore before the letter, uppercase
|
|
|
|
letters get an underscore after the letter. Trailing spaces are
|
|
|
|
trimmed. Illegal characters are escaped as two hex bytes. If the
|
|
|
|
result starts with a number (as the result of a hex escape), an
|
|
|
|
extra underscore is prepended. Examples:
|
|
|
|
'glyf' -> '_g_l_y_f'
|
|
|
|
'cvt ' -> '_c_v_t'
|
|
|
|
'OS/2' -> 'O_S_2f_2'
|
|
|
|
"""
|
|
|
|
import re
|
2002-05-25 08:22:22 +00:00
|
|
|
if tag == "GlyphOrder":
|
|
|
|
return tag
|
1999-12-16 21:34:53 +00:00
|
|
|
assert len(tag) == 4, "tag should be 4 characters long"
|
|
|
|
while len(tag) > 1 and tag[-1] == ' ':
|
|
|
|
tag = tag[:-1]
|
|
|
|
ident = ""
|
|
|
|
for c in tag:
|
|
|
|
ident = ident + _escapechar(c)
|
|
|
|
if re.match("[0-9]", ident):
|
|
|
|
ident = "_" + ident
|
|
|
|
return ident
|
|
|
|
|
|
|
|
|
2002-05-13 16:21:51 +00:00
|
|
|
def identifierToTag(ident):
|
|
|
|
"""the opposite of tagToIdentifier()"""
|
2002-05-25 08:22:22 +00:00
|
|
|
if ident == "GlyphOrder":
|
|
|
|
return ident
|
1999-12-16 21:34:53 +00:00
|
|
|
if len(ident) % 2 and ident[0] == "_":
|
|
|
|
ident = ident[1:]
|
|
|
|
assert not (len(ident) % 2)
|
|
|
|
tag = ""
|
|
|
|
for i in range(0, len(ident), 2):
|
|
|
|
if ident[i] == "_":
|
|
|
|
tag = tag + ident[i+1]
|
|
|
|
elif ident[i+1] == "_":
|
|
|
|
tag = tag + ident[i]
|
|
|
|
else:
|
|
|
|
# assume hex
|
2003-08-22 18:52:22 +00:00
|
|
|
tag = tag + chr(int(ident[i:i+2], 16))
|
1999-12-16 21:34:53 +00:00
|
|
|
# append trailing spaces
|
|
|
|
tag = tag + (4 - len(tag)) * ' '
|
|
|
|
return tag
|
|
|
|
|
|
|
|
|
2002-05-13 16:21:51 +00:00
|
|
|
def tagToXML(tag):
|
|
|
|
"""Similarly to tagToIdentifier(), this converts a TT tag
|
1999-12-16 21:34:53 +00:00
|
|
|
to a valid XML element name. Since XML element names are
|
|
|
|
case sensitive, this is a fairly simple/readable translation.
|
|
|
|
"""
|
1999-12-29 13:06:08 +00:00
|
|
|
import re
|
1999-12-16 21:34:53 +00:00
|
|
|
if tag == "OS/2":
|
|
|
|
return "OS_2"
|
2002-05-23 09:42:45 +00:00
|
|
|
elif tag == "GlyphOrder":
|
|
|
|
return "GlyphOrder"
|
1999-12-16 21:34:53 +00:00
|
|
|
if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
|
|
|
|
return string.strip(tag)
|
|
|
|
else:
|
2002-05-13 16:21:51 +00:00
|
|
|
return tagToIdentifier(tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
|
2002-05-13 16:21:51 +00:00
|
|
|
def xmlToTag(tag):
|
|
|
|
"""The opposite of tagToXML()"""
|
1999-12-16 21:34:53 +00:00
|
|
|
if tag == "OS_2":
|
|
|
|
return "OS/2"
|
|
|
|
if len(tag) == 8:
|
2002-05-13 16:21:51 +00:00
|
|
|
return identifierToTag(tag)
|
1999-12-16 21:34:53 +00:00
|
|
|
else:
|
|
|
|
return tag + " " * (4 - len(tag))
|
|
|
|
return tag
|
|
|
|
|
|
|
|
|
|
|
|
def debugmsg(msg):
|
|
|
|
import time
|
|
|
|
print msg + time.strftime(" (%H:%M:%S)", time.localtime(time.time()))
|
|
|
|
|
2003-08-22 19:44:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Table sorting algorithm pre recommendations in OpenType Spec v1.4
|
|
|
|
kTTFTableOrder = ["GlyphOrder", "head", "hhea", "maxp", "OS/2", "hmtx", "LTSH", "VDMX", "hdmx", "cmap", "fpgm", "prep", "cvt", "loca", "glyf", "kern", "name", "post", "gasp", "PCLT"]
|
|
|
|
kOTFTableOrder = ["GlyphOrder", "head", "hhea", "maxp", "OS/2", "name", "cmap", "post", "CFF "]
|
|
|
|
kNotInTableIndex = 10000 # an arbitrary value larger than will ever be a font.
|
|
|
|
def sortFontTables(tag1, tag2, tableOrder):
|
|
|
|
#No need to allow for two tags with the same name.
|
|
|
|
if tag1 == "DSIG":
|
|
|
|
ret = -1
|
|
|
|
elif tag2 == "DSIG":
|
|
|
|
ret = 1
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
i1 = tableOrder.index(tag1)
|
|
|
|
except ValueError:
|
|
|
|
i1 = kNotInTableIndex
|
|
|
|
try:
|
|
|
|
i2 = tableOrder.index(tag2)
|
|
|
|
except ValueError:
|
|
|
|
i2 = kNotInTableIndex
|
|
|
|
|
|
|
|
if i1 > i2:
|
|
|
|
ret = 1
|
|
|
|
elif i1 < i2:
|
|
|
|
ret = -1
|
|
|
|
else:
|
|
|
|
if tag1 < tag2:
|
|
|
|
ret = 1
|
|
|
|
elif tag1 < tag2:
|
|
|
|
ret = -1
|
|
|
|
else:
|
|
|
|
ret = 0
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def sortTTFFont(tag1, tag2):
|
|
|
|
return sortFontTables(tag1, tag2, kTTFTableOrder)
|
|
|
|
|
|
|
|
|
|
|
|
def sortOTFFont(tag1, tag2):
|
|
|
|
return sortFontTables(tag1, tag2, kOTFTableOrder)
|