58
"""The main font object. It manages file input and output, and offers
59
a convenient way of accessing tables.
60
Tables will be only decompiled when neccesary, ie. when they're actually
61
accessed. This means that simple operations can be extremely fast.
64
def __init__(self, file=None, res_name_or_index=None,
65
sfntVersion="\000\001\000\000", checkChecksums=0,
66
verbose=0, recalcBBoxes=1):
68
"""The constructor can be called with a few different arguments.
69
When reading a font from disk, 'file' should be either a pathname
70
pointing to a file, or a readable file object.
72
It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt
73
resource name or an sfnt resource index number or zero. The latter
74
case will cause TTLib to autodetect whether the file is a flat file
75
or a suitcase. (If it's a suitcase, only the first 'sfnt' resource
78
The 'checkChecksums' argument is used to specify how sfnt
79
checksums are treated upon reading a file from disk:
81
- 0: don't check (default)
82
- 1: check, print warnings if a wrong checksum is found
83
- 2: check, raise an exception if a wrong checksum is found.
85
The TTFont constructor can also be called without a 'file'
86
argument: this is the way to create a new empty font.
87
In this case you can optionally supply the 'sfntVersion' argument.
89
If the recalcBBoxes argument is false, a number of things will *not*
90
be recalculated upon save/compile:
92
1. glyph bounding boxes
93
2. maxp font bounding box
94
3. hhea min/max values
96
(1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-).
97
Additionally, upon importing an TTX file, this option cause glyphs
98
to be compiled right away. This should reduce memory consumption
99
greatly, and therefore should have some impact on the time needed
100
to parse/compile large fonts.
104
self.verbose = verbose
105
self.recalcBBoxes = recalcBBoxes
109
self.sfntVersion = sfntVersion
111
if isinstance(file, basestring):
112
if os.name == "mac" and res_name_or_index is not None:
113
# on the mac, we deal with sfnt resources as well as flat files
115
if res_name_or_index == 0:
116
if macUtils.getSFNTResIndices(file):
117
# get the first available sfnt font.
118
file = macUtils.SFNTResourceReader(file, 1)
120
file = open(file, "rb")
122
file = macUtils.SFNTResourceReader(file, res_name_or_index)
124
file = open(file, "rb")
126
pass # assume "file" is a readable file object
127
self.reader = sfnt.SFNTReader(file, checkChecksums)
128
self.sfntVersion = self.reader.sfntVersion
131
"""If we still have a reader object, close it."""
132
if self.reader is not None:
135
def save(self, file, makeSuitcase=0):
136
"""Save the font to disk. Similarly to the constructor,
137
the 'file' argument can be either a pathname or a writable
140
On the Mac, if makeSuitcase is true, a suitcase (resource fork)
141
file will we made instead of a flat .ttf file.
143
from enthought.kiva.fonttools.fontTools.ttLib import sfnt
144
if isinstance(file, basestring):
146
if os.name == "mac" and makeSuitcase:
148
file = macUtils.SFNTResourceWriter(file, self)
150
file = open(file, "wb")
153
fss = macfs.FSSpec(file.name)
154
fss.SetCreatorType('mdos', 'BINA')
156
# assume "file" is a writable file object
160
tags.remove("GlyphOrder")
161
numTables = len(tags)
162
writer = sfnt.SFNTWriter(file, numTables, self.sfntVersion)
166
self._writeTable(tag, writer, done)
168
writer.close(closeStream)
170
def saveXML(self, fileOrPath, progress=None,
171
tables=None, skipTables=None, splitTables=0, disassembleInstructions=1):
172
"""Export the font as TTX (an XML-based text file), or as a series of text
173
files when splitTables is true. In the latter case, the 'fileOrPath'
174
argument should be a path to a directory.
175
The 'tables' argument must either be false (dump all tables) or a
176
list of tables to dump. The 'skipTables' argument may be a list of tables
177
to skip, but only when the 'tables' argument is false.
179
from enthought.kiva.fonttools.fontTools import version
182
self.disassembleInstructions = disassembleInstructions
186
for tag in skipTables:
189
numTables = len(tables)
190
numGlyphs = self['maxp'].numGlyphs
192
progress.set(0, numTables)
193
idlefunc = getattr(progress, "idle", None)
197
writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc)
198
writer.begintag("ttFont", sfntVersion=`self.sfntVersion`[1:-1],
199
ttLibVersion=version)
205
# 'fileOrPath' must now be a path
206
path, ext = os.path.splitext(fileOrPath)
207
fileNameTemplate = path + ".%s" + ext
209
for i in range(numTables):
214
tablePath = fileNameTemplate % tagToIdentifier(tag)
215
tableWriter = xmlWriter.XMLWriter(tablePath, idlefunc=idlefunc)
216
tableWriter.begintag("ttFont", ttLibVersion=version)
217
tableWriter.newline()
218
tableWriter.newline()
219
writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath))
223
self._tableToXML(tableWriter, tag, progress)
225
tableWriter.endtag("ttFont")
226
tableWriter.newline()
229
progress.set((i + 1))
230
writer.endtag("ttFont")
234
debugmsg("Done dumping TTX")
236
def _tableToXML(self, writer, tag, progress):
237
if self.has_key(tag):
239
report = "Dumping '%s' table..." % tag
241
report = "No '%s' table found." % tag
243
progress.setLabel(report)
248
if not self.has_key(tag):
250
xmlTag = tagToXML(tag)
251
if hasattr(table, "ERROR"):
252
writer.begintag(xmlTag, ERROR="decompilation error")
254
writer.begintag(xmlTag)
256
if tag in ("glyf", "CFF "):
257
table.toXML(writer, self, progress)
259
table.toXML(writer, self)
260
writer.endtag(xmlTag)
264
def importXML(self, file, progress=None):
265
"""Import a TTX file (an XML-based text format), so as to recreate
268
if self.has_key("maxp") and self.has_key("post"):
269
# Make sure the glyph order is loaded, as it otherwise gets
270
# lost if the XML doesn't contain the glyph order, yet does
271
# contain the table which was originally used to extract the
272
# glyph names from (ie. 'post', 'cmap' or 'CFF ').
275
xmlImport.importXML(self, file, progress)
277
def isLoaded(self, tag):
278
"""Return true if the table identified by 'tag' has been
279
decompiled and loaded into memory."""
280
return self.tables.has_key(tag)
282
def has_key(self, tag):
283
if self.isLoaded(tag):
285
elif self.reader and self.reader.has_key(tag):
287
elif tag == "GlyphOrder":
293
keys = self.tables.keys()
295
for key in self.reader.keys():
299
if "GlyphOrder" in keys:
300
keys.remove("GlyphOrder")
301
return ["GlyphOrder"] + keys
304
return len(self.keys())
306
def __getitem__(self, tag):
308
return self.tables[tag]
310
if tag == "GlyphOrder":
311
table = GlyphOrder(tag)
312
self.tables[tag] = table
314
if self.reader is not None:
317
debugmsg("Reading '%s' table from disk" % tag)
318
data = self.reader[tag]
319
tableClass = getTableClass(tag)
320
table = tableClass(tag)
321
self.tables[tag] = table
323
debugmsg("Decompiling '%s' table" % tag)
325
table.decompile(data, self)
326
except "_ _ F O O _ _": # dummy exception to disable exception catching
327
print "An exception occurred during the decompilation of the '%s' table" % tag
328
from tables.DefaultTable import DefaultTable
330
file = StringIO.StringIO()
331
traceback.print_exc(file=file)
332
table = DefaultTable(tag)
333
table.ERROR = file.getvalue()
334
self.tables[tag] = table
335
table.decompile(data, self)
338
raise KeyError, "'%s' table not found" % tag
340
def __setitem__(self, tag, table):
341
self.tables[tag] = table
343
def __delitem__(self, tag):
344
if not self.has_key(tag):
345
raise KeyError, "'%s' table not found" % tag
346
if self.tables.has_key(tag):
348
if self.reader and self.reader.has_key(tag):
351
def setGlyphOrder(self, glyphOrder):
352
self.glyphOrder = glyphOrder
354
def getGlyphOrder(self):
356
return self.glyphOrder
357
except AttributeError:
359
if self.has_key('CFF '):
361
if cff.haveGlyphNames():
362
self.glyphOrder = cff.getGlyphOrder()
364
# CID-keyed font, use cmap
365
self._getGlyphNamesFromCmap()
366
elif self.has_key('post'):
368
glyphOrder = self['post'].getGlyphOrder()
369
if glyphOrder is None:
371
# No names found in the 'post' table.
372
# Try to create glyph names from the unicode cmap (if available)
373
# in combination with the Adobe Glyph List (AGL).
375
self._getGlyphNamesFromCmap()
377
self.glyphOrder = glyphOrder
379
self._getGlyphNamesFromCmap()
380
return self.glyphOrder
382
def _getGlyphNamesFromCmap(self):
384
# This is rather convoluted, but then again, it's an interesting problem:
385
# - we need to use the unicode values found in the cmap table to
386
# build glyph names (eg. because there is only a minimal post table,
388
# - but the cmap parser also needs glyph names to work with...
389
# So here's what we do:
390
# - make up glyph names based on glyphID
391
# - load a temporary cmap table based on those names
392
# - extract the unicode values, build the "real" glyph names
393
# - unload the temporary cmap table
395
if self.isLoaded("cmap"):
396
# Bootstrapping: we're getting called by the cmap parser
397
# itself. This means self.tables['cmap'] contains a partially
398
# loaded cmap, making it impossible to get at a unicode
399
# subtable here. We remove the partially loaded cmap and
401
# This only happens if the cmap table is loaded before any
402
# other table that does f.getGlyphOrder() or f.getGlyphName().
403
cmapLoading = self.tables['cmap']
404
del self.tables['cmap']
407
# Make up glyph names based on glyphID, which will be used by the
408
# temporary cmap and by the real cmap in case we don't find a unicode
410
numGlyphs = int(self['maxp'].numGlyphs)
411
glyphOrder = [None] * numGlyphs
412
glyphOrder[0] = ".notdef"
413
for i in range(1, numGlyphs):
414
glyphOrder[i] = "glyph%.5d" % i
415
# Set the glyph order, so the cmap parser has something
416
# to work with (so we don't get called recursively).
417
self.glyphOrder = glyphOrder
418
# Get a (new) temporary cmap (based on the just invented names)
419
tempcmap = self['cmap'].getcmap(3, 1)
420
if tempcmap is not None:
421
# we have a unicode cmap
422
from enthought.kiva.fonttools.fontTools import agl
424
# create a reverse cmap dict
426
for unicode, name in cmap.items():
427
reversecmap[name] = unicode
429
for i in range(numGlyphs):
430
tempName = glyphOrder[i]
431
if reversecmap.has_key(tempName):
432
unicode = reversecmap[tempName]
433
if agl.UV2AGL.has_key(unicode):
434
# get name from the Adobe Glyph List
435
glyphName = agl.UV2AGL[unicode]
437
# create uni<CODE> name
438
glyphName = "uni" + string.upper(string.zfill(
439
hex(unicode)[2:], 4))
442
while allNames.has_key(tempName):
443
tempName = glyphName + "#" + `n`
445
glyphOrder[i] = tempName
446
allNames[tempName] = 1
447
# Delete the temporary cmap table from the cache, so it can
448
# be parsed again with the right names.
449
del self.tables['cmap']
451
pass # no unicode cmap available, stick with the invented names
452
self.glyphOrder = glyphOrder
454
# restore partially loaded cmap, so it can continue loading
455
# using the proper names.
456
self.tables['cmap'] = cmapLoading
458
def getGlyphNames(self):
459
"""Get a list of glyph names, sorted alphabetically."""
460
glyphNames = self.getGlyphOrder()[:]
464
def getGlyphNames2(self):
465
"""Get a list of glyph names, sorted alphabetically,
466
but not case sensitive.
468
from enthought.kiva.fonttools.fontTools.misc import textTools
469
return textTools.caselessSort(self.getGlyphOrder())
471
def getGlyphName(self, glyphID):
473
return self.getGlyphOrder()[glyphID]
475
# XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in
476
# the cmap table than there are glyphs. I don't think it's legal...
477
return "glyph%.5d" % glyphID
479
def getGlyphID(self, glyphName):
480
if not hasattr(self, "_reverseGlyphOrderDict"):
481
self._buildReverseGlyphOrderDict()
482
glyphOrder = self.getGlyphOrder()
483
d = self._reverseGlyphOrderDict
484
if not d.has_key(glyphName):
485
if glyphName in glyphOrder:
486
self._buildReverseGlyphOrderDict()
487
return self.getGlyphID(glyphName)
489
raise KeyError, glyphName
490
glyphID = d[glyphName]
491
if glyphName <> glyphOrder[glyphID]:
492
self._buildReverseGlyphOrderDict()
493
return self.getGlyphID(glyphName)
496
def _buildReverseGlyphOrderDict(self):
497
self._reverseGlyphOrderDict = d = {}
498
glyphOrder = self.getGlyphOrder()
499
for glyphID in range(len(glyphOrder)):
500
d[glyphOrder[glyphID]] = glyphID
502
def _writeTable(self, tag, writer, done):
503
"""Internal helper function for self.save(). Keeps track of
504
inter-table dependencies.
508
tableClass = getTableClass(tag)
509
for masterTable in tableClass.dependencies:
510
if masterTable not in done:
511
if self.has_key(masterTable):
512
self._writeTable(masterTable, writer, done)
514
done.append(masterTable)
515
tabledata = self.getTableData(tag)
517
debugmsg("writing '%s' table to disk" % tag)
518
writer[tag] = tabledata
521
def getTableData(self, tag):
522
"""Returns raw table data, whether compiled or directly read from disk.
524
if self.isLoaded(tag):
526
debugmsg("compiling '%s' table" % tag)
527
return self.tables[tag].compile(self)
528
elif self.reader and self.reader.has_key(tag):
530
debugmsg("Reading '%s' table from disk" % tag)
531
return self.reader[tag]
58
"""The main font object. It manages file input and output, and offers
59
a convenient way of accessing tables.
60
Tables will be only decompiled when neccesary, ie. when they're actually
61
accessed. This means that simple operations can be extremely fast.
64
def __init__(self, file=None, res_name_or_index=None,
65
sfntVersion="\000\001\000\000", checkChecksums=0,
66
verbose=0, recalcBBoxes=1):
68
"""The constructor can be called with a few different arguments.
69
When reading a font from disk, 'file' should be either a pathname
70
pointing to a file, or a readable file object.
72
It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt
73
resource name or an sfnt resource index number or zero. The latter
74
case will cause TTLib to autodetect whether the file is a flat file
75
or a suitcase. (If it's a suitcase, only the first 'sfnt' resource
78
The 'checkChecksums' argument is used to specify how sfnt
79
checksums are treated upon reading a file from disk:
81
- 0: don't check (default)
82
- 1: check, print warnings if a wrong checksum is found
83
- 2: check, raise an exception if a wrong checksum is found.
85
The TTFont constructor can also be called without a 'file'
86
argument: this is the way to create a new empty font.
87
In this case you can optionally supply the 'sfntVersion' argument.
89
If the recalcBBoxes argument is false, a number of things will *not*
90
be recalculated upon save/compile:
92
1. glyph bounding boxes
93
2. maxp font bounding box
94
3. hhea min/max values
96
(1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-).
97
Additionally, upon importing an TTX file, this option cause glyphs
98
to be compiled right away. This should reduce memory consumption
99
greatly, and therefore should have some impact on the time needed
100
to parse/compile large fonts.
104
self.verbose = verbose
105
self.recalcBBoxes = recalcBBoxes
109
self.sfntVersion = sfntVersion
111
if isinstance(file, basestring):
112
if os.name == "mac" and res_name_or_index is not None:
113
# on the mac, we deal with sfnt resources as well as flat files
115
if res_name_or_index == 0:
116
if macUtils.getSFNTResIndices(file):
117
# get the first available sfnt font.
118
file = macUtils.SFNTResourceReader(file, 1)
120
file = open(file, "rb")
122
file = macUtils.SFNTResourceReader(file, res_name_or_index)
124
file = open(file, "rb")
126
pass # assume "file" is a readable file object
127
self.reader = sfnt.SFNTReader(file, checkChecksums)
128
self.sfntVersion = self.reader.sfntVersion
131
"""If we still have a reader object, close it."""
132
if self.reader is not None:
135
def save(self, file, makeSuitcase=0):
136
"""Save the font to disk. Similarly to the constructor,
137
the 'file' argument can be either a pathname or a writable
140
On the Mac, if makeSuitcase is true, a suitcase (resource fork)
141
file will we made instead of a flat .ttf file.
143
from enthought.kiva.fonttools.fontTools.ttLib import sfnt
144
if isinstance(file, basestring):
146
if os.name == "mac" and makeSuitcase:
148
file = macUtils.SFNTResourceWriter(file, self)
150
file = open(file, "wb")
153
fss = macfs.FSSpec(file.name)
154
fss.SetCreatorType('mdos', 'BINA')
156
# assume "file" is a writable file object
160
tags.remove("GlyphOrder")
161
numTables = len(tags)
162
writer = sfnt.SFNTWriter(file, numTables, self.sfntVersion)
166
self._writeTable(tag, writer, done)
168
writer.close(closeStream)
170
def saveXML(self, fileOrPath, progress=None,
171
tables=None, skipTables=None, splitTables=0, disassembleInstructions=1):
172
"""Export the font as TTX (an XML-based text file), or as a series of text
173
files when splitTables is true. In the latter case, the 'fileOrPath'
174
argument should be a path to a directory.
175
The 'tables' argument must either be false (dump all tables) or a
176
list of tables to dump. The 'skipTables' argument may be a list of tables
177
to skip, but only when the 'tables' argument is false.
179
from enthought.kiva.fonttools.fontTools import version
182
self.disassembleInstructions = disassembleInstructions
186
for tag in skipTables:
189
numTables = len(tables)
190
numGlyphs = self['maxp'].numGlyphs
192
progress.set(0, numTables)
193
idlefunc = getattr(progress, "idle", None)
197
writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc)
198
writer.begintag("ttFont", sfntVersion=`self.sfntVersion`[1:-1],
199
ttLibVersion=version)
205
# 'fileOrPath' must now be a path
206
path, ext = os.path.splitext(fileOrPath)
207
fileNameTemplate = path + ".%s" + ext
209
for i in range(numTables):
214
tablePath = fileNameTemplate % tagToIdentifier(tag)
215
tableWriter = xmlWriter.XMLWriter(tablePath, idlefunc=idlefunc)
216
tableWriter.begintag("ttFont", ttLibVersion=version)
217
tableWriter.newline()
218
tableWriter.newline()
219
writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath))
223
self._tableToXML(tableWriter, tag, progress)
225
tableWriter.endtag("ttFont")
226
tableWriter.newline()
229
progress.set((i + 1))
230
writer.endtag("ttFont")
234
debugmsg("Done dumping TTX")
236
def _tableToXML(self, writer, tag, progress):
237
if self.has_key(tag):
239
report = "Dumping '%s' table..." % tag
241
report = "No '%s' table found." % tag
243
progress.setLabel(report)
248
if not self.has_key(tag):
250
xmlTag = tagToXML(tag)
251
if hasattr(table, "ERROR"):
252
writer.begintag(xmlTag, ERROR="decompilation error")
254
writer.begintag(xmlTag)
256
if tag in ("glyf", "CFF "):
257
table.toXML(writer, self, progress)
259
table.toXML(writer, self)
260
writer.endtag(xmlTag)
264
def importXML(self, file, progress=None):
265
"""Import a TTX file (an XML-based text format), so as to recreate
268
if self.has_key("maxp") and self.has_key("post"):
269
# Make sure the glyph order is loaded, as it otherwise gets
270
# lost if the XML doesn't contain the glyph order, yet does
271
# contain the table which was originally used to extract the
272
# glyph names from (ie. 'post', 'cmap' or 'CFF ').
275
xmlImport.importXML(self, file, progress)
277
def isLoaded(self, tag):
278
"""Return true if the table identified by 'tag' has been
279
decompiled and loaded into memory."""
280
return self.tables.has_key(tag)
282
def has_key(self, tag):
283
if self.isLoaded(tag):
285
elif self.reader and self.reader.has_key(tag):
287
elif tag == "GlyphOrder":
293
keys = self.tables.keys()
295
for key in self.reader.keys():
299
if "GlyphOrder" in keys:
300
keys.remove("GlyphOrder")
301
return ["GlyphOrder"] + keys
304
return len(self.keys())
306
def __getitem__(self, tag):
308
return self.tables[tag]
310
if tag == "GlyphOrder":
311
table = GlyphOrder(tag)
312
self.tables[tag] = table
314
if self.reader is not None:
317
debugmsg("Reading '%s' table from disk" % tag)
318
data = self.reader[tag]
319
tableClass = getTableClass(tag)
320
table = tableClass(tag)
321
self.tables[tag] = table
323
debugmsg("Decompiling '%s' table" % tag)
325
table.decompile(data, self)
326
except "_ _ F O O _ _": # dummy exception to disable exception catching
327
print "An exception occurred during the decompilation of the '%s' table" % tag
328
from tables.DefaultTable import DefaultTable
330
file = StringIO.StringIO()
331
traceback.print_exc(file=file)
332
table = DefaultTable(tag)
333
table.ERROR = file.getvalue()
334
self.tables[tag] = table
335
table.decompile(data, self)
338
raise KeyError, "'%s' table not found" % tag
340
def __setitem__(self, tag, table):
341
self.tables[tag] = table
343
def __delitem__(self, tag):
344
if not self.has_key(tag):
345
raise KeyError, "'%s' table not found" % tag
346
if self.tables.has_key(tag):
348
if self.reader and self.reader.has_key(tag):
351
def setGlyphOrder(self, glyphOrder):
352
self.glyphOrder = glyphOrder
354
def getGlyphOrder(self):
356
return self.glyphOrder
357
except AttributeError:
359
if self.has_key('CFF '):
361
if cff.haveGlyphNames():
362
self.glyphOrder = cff.getGlyphOrder()
364
# CID-keyed font, use cmap
365
self._getGlyphNamesFromCmap()
366
elif self.has_key('post'):
368
glyphOrder = self['post'].getGlyphOrder()
369
if glyphOrder is None:
371
# No names found in the 'post' table.
372
# Try to create glyph names from the unicode cmap (if available)
373
# in combination with the Adobe Glyph List (AGL).
375
self._getGlyphNamesFromCmap()
377
self.glyphOrder = glyphOrder
379
self._getGlyphNamesFromCmap()
380
return self.glyphOrder
382
def _getGlyphNamesFromCmap(self):
384
# This is rather convoluted, but then again, it's an interesting problem:
385
# - we need to use the unicode values found in the cmap table to
386
# build glyph names (eg. because there is only a minimal post table,
388
# - but the cmap parser also needs glyph names to work with...
389
# So here's what we do:
390
# - make up glyph names based on glyphID
391
# - load a temporary cmap table based on those names
392
# - extract the unicode values, build the "real" glyph names
393
# - unload the temporary cmap table
395
if self.isLoaded("cmap"):
396
# Bootstrapping: we're getting called by the cmap parser
397
# itself. This means self.tables['cmap'] contains a partially
398
# loaded cmap, making it impossible to get at a unicode
399
# subtable here. We remove the partially loaded cmap and
401
# This only happens if the cmap table is loaded before any
402
# other table that does f.getGlyphOrder() or f.getGlyphName().
403
cmapLoading = self.tables['cmap']
404
del self.tables['cmap']
407
# Make up glyph names based on glyphID, which will be used by the
408
# temporary cmap and by the real cmap in case we don't find a unicode
410
numGlyphs = int(self['maxp'].numGlyphs)
411
glyphOrder = [None] * numGlyphs
412
glyphOrder[0] = ".notdef"
413
for i in range(1, numGlyphs):
414
glyphOrder[i] = "glyph%.5d" % i
415
# Set the glyph order, so the cmap parser has something
416
# to work with (so we don't get called recursively).
417
self.glyphOrder = glyphOrder
418
# Get a (new) temporary cmap (based on the just invented names)
419
tempcmap = self['cmap'].getcmap(3, 1)
420
if tempcmap is not None:
421
# we have a unicode cmap
422
from enthought.kiva.fonttools.fontTools import agl
424
# create a reverse cmap dict
426
for unicode, name in cmap.items():
427
reversecmap[name] = unicode
429
for i in range(numGlyphs):
430
tempName = glyphOrder[i]
431
if reversecmap.has_key(tempName):
432
unicode = reversecmap[tempName]
433
if agl.UV2AGL.has_key(unicode):
434
# get name from the Adobe Glyph List
435
glyphName = agl.UV2AGL[unicode]
437
# create uni<CODE> name
438
glyphName = "uni" + string.upper(string.zfill(
439
hex(unicode)[2:], 4))
442
while allNames.has_key(tempName):
443
tempName = glyphName + "#" + `n`
445
glyphOrder[i] = tempName
446
allNames[tempName] = 1
447
# Delete the temporary cmap table from the cache, so it can
448
# be parsed again with the right names.
449
del self.tables['cmap']
451
pass # no unicode cmap available, stick with the invented names
452
self.glyphOrder = glyphOrder
454
# restore partially loaded cmap, so it can continue loading
455
# using the proper names.
456
self.tables['cmap'] = cmapLoading
458
def getGlyphNames(self):
459
"""Get a list of glyph names, sorted alphabetically."""
460
glyphNames = self.getGlyphOrder()[:]
464
def getGlyphNames2(self):
465
"""Get a list of glyph names, sorted alphabetically,
466
but not case sensitive.
468
from enthought.kiva.fonttools.fontTools.misc import textTools
469
return textTools.caselessSort(self.getGlyphOrder())
471
def getGlyphName(self, glyphID):
473
return self.getGlyphOrder()[glyphID]
475
# XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in
476
# the cmap table than there are glyphs. I don't think it's legal...
477
return "glyph%.5d" % glyphID
479
def getGlyphID(self, glyphName):
480
if not hasattr(self, "_reverseGlyphOrderDict"):
481
self._buildReverseGlyphOrderDict()
482
glyphOrder = self.getGlyphOrder()
483
d = self._reverseGlyphOrderDict
484
if not d.has_key(glyphName):
485
if glyphName in glyphOrder:
486
self._buildReverseGlyphOrderDict()
487
return self.getGlyphID(glyphName)
489
raise KeyError, glyphName
490
glyphID = d[glyphName]
491
if glyphName <> glyphOrder[glyphID]:
492
self._buildReverseGlyphOrderDict()
493
return self.getGlyphID(glyphName)
496
def _buildReverseGlyphOrderDict(self):
497
self._reverseGlyphOrderDict = d = {}
498
glyphOrder = self.getGlyphOrder()
499
for glyphID in range(len(glyphOrder)):
500
d[glyphOrder[glyphID]] = glyphID
502
def _writeTable(self, tag, writer, done):
503
"""Internal helper function for self.save(). Keeps track of
504
inter-table dependencies.
508
tableClass = getTableClass(tag)
509
for masterTable in tableClass.dependencies:
510
if masterTable not in done:
511
if self.has_key(masterTable):
512
self._writeTable(masterTable, writer, done)
514
done.append(masterTable)
515
tabledata = self.getTableData(tag)
517
debugmsg("writing '%s' table to disk" % tag)
518
writer[tag] = tabledata
521
def getTableData(self, tag):
522
"""Returns raw table data, whether compiled or directly read from disk.
524
if self.isLoaded(tag):
526
debugmsg("compiling '%s' table" % tag)
527
return self.tables[tag].compile(self)
528
elif self.reader and self.reader.has_key(tag):
530
debugmsg("Reading '%s' table from disk" % tag)
531
return self.reader[tag]
536
536
class GlyphOrder:
538
"""A pseudo table. The glyph order isn't in the font as a separate
539
table, but it's nice to present it as such in the TTX format.
542
def __init__(self, tag):
545
def toXML(self, writer, ttFont):
546
glyphOrder = ttFont.getGlyphOrder()
547
writer.comment("The 'id' attribute is only for humans; "
548
"it is ignored when parsed.")
550
for i in range(len(glyphOrder)):
551
glyphName = glyphOrder[i]
552
writer.simpletag("GlyphID", id=i, name=glyphName)
555
def fromXML(self, (name, attrs, content), ttFont):
556
if not hasattr(self, "glyphOrder"):
558
ttFont.setGlyphOrder(self.glyphOrder)
559
if name == "GlyphID":
560
self.glyphOrder.append(attrs["name"])
538
"""A pseudo table. The glyph order isn't in the font as a separate
539
table, but it's nice to present it as such in the TTX format.
542
def __init__(self, tag):
545
def toXML(self, writer, ttFont):
546
glyphOrder = ttFont.getGlyphOrder()
547
writer.comment("The 'id' attribute is only for humans; "
548
"it is ignored when parsed.")
550
for i in range(len(glyphOrder)):
551
glyphName = glyphOrder[i]
552
writer.simpletag("GlyphID", id=i, name=glyphName)
555
def fromXML(self, (name, attrs, content), ttFont):
556
if not hasattr(self, "glyphOrder"):
558
ttFont.setGlyphOrder(self.glyphOrder)
559
if name == "GlyphID":
560
self.glyphOrder.append(attrs["name"])
563
563
def _test_endianness():
564
"""Test the endianness of the machine. This is crucial to know
565
since TrueType data is always big endian, even on little endian
566
machines. There are quite a few situations where we explicitly
567
need to swap some bytes.
570
data = struct.pack("h", 0x01)
571
if data == "\000\001":
573
elif data == "\001\000":
576
assert 0, "endian confusion!"
564
"""Test the endianness of the machine. This is crucial to know
565
since TrueType data is always big endian, even on little endian
566
machines. There are quite a few situations where we explicitly
567
need to swap some bytes.
570
data = struct.pack("h", 0x01)
571
if data == "\000\001":
573
elif data == "\001\000":
576
assert 0, "endian confusion!"
578
578
endian = _test_endianness()
581
581
def getTableModule(tag):
582
"""Fetch the packer/unpacker module for a table.
583
Return None when no module is found.
586
pyTag = tagToIdentifier(tag)
588
module = __import__("enthought.kiva.fonttools.fontTools.ttLib.tables." + pyTag)
592
return getattr(tables, pyTag)
582
"""Fetch the packer/unpacker module for a table.
583
Return None when no module is found.
586
pyTag = tagToIdentifier(tag)
588
module = __import__("enthought.kiva.fonttools.fontTools.ttLib.tables." + pyTag)
592
return getattr(tables, pyTag)
595
595
def getTableClass(tag):
596
"""Fetch the packer/unpacker class for a table.
597
Return None when no class is found.
599
module = getTableModule(tag)
601
from tables.DefaultTable import DefaultTable
603
pyTag = tagToIdentifier(tag)
604
tableClass = getattr(module, "table_" + pyTag)
596
"""Fetch the packer/unpacker class for a table.
597
Return None when no class is found.
599
module = getTableModule(tag)
601
from tables.DefaultTable import DefaultTable
603
pyTag = tagToIdentifier(tag)
604
tableClass = getattr(module, "table_" + pyTag)
608
608
def newTable(tag):
609
"""Return a new instance of a table."""
610
tableClass = getTableClass(tag)
611
return tableClass(tag)
609
"""Return a new instance of a table."""
610
tableClass = getTableClass(tag)
611
return tableClass(tag)
614
614
def _escapechar(c):
615
"""Helper function for tagToIdentifier()"""
617
if re.match("[a-z0-9]", c):
619
elif re.match("[A-Z]", c):
622
return hex(ord(c))[2:]
615
"""Helper function for tagToIdentifier()"""
617
if re.match("[a-z0-9]", c):
619
elif re.match("[A-Z]", c):
622
return hex(ord(c))[2:]
625
625
def tagToIdentifier(tag):
626
"""Convert a table tag to a valid (but UGLY) python identifier,
627
as well as a filename that's guaranteed to be unique even on a
628
caseless file system. Each character is mapped to two characters.
629
Lowercase letters get an underscore before the letter, uppercase
630
letters get an underscore after the letter. Trailing spaces are
631
trimmed. Illegal characters are escaped as two hex bytes. If the
632
result starts with a number (as the result of a hex escape), an
633
extra underscore is prepended. Examples::
640
if tag == "GlyphOrder":
642
assert len(tag) == 4, "tag should be 4 characters long"
643
while len(tag) > 1 and tag[-1] == ' ':
647
ident = ident + _escapechar(c)
648
if re.match("[0-9]", ident):
626
"""Convert a table tag to a valid (but UGLY) python identifier,
627
as well as a filename that's guaranteed to be unique even on a
628
caseless file system. Each character is mapped to two characters.
629
Lowercase letters get an underscore before the letter, uppercase
630
letters get an underscore after the letter. Trailing spaces are
631
trimmed. Illegal characters are escaped as two hex bytes. If the
632
result starts with a number (as the result of a hex escape), an
633
extra underscore is prepended. Examples::
640
if tag == "GlyphOrder":
642
assert len(tag) == 4, "tag should be 4 characters long"
643
while len(tag) > 1 and tag[-1] == ' ':
647
ident = ident + _escapechar(c)
648
if re.match("[0-9]", ident):
653
653
def identifierToTag(ident):
654
"""the opposite of tagToIdentifier()"""
655
if ident == "GlyphOrder":
657
if len(ident) % 2 and ident[0] == "_":
659
assert not (len(ident) % 2)
661
for i in range(0, len(ident), 2):
663
tag = tag + ident[i+1]
664
elif ident[i+1] == "_":
668
tag = tag + chr(string.atoi(ident[i:i+2], 16))
669
# append trailing spaces
670
tag = tag + (4 - len(tag)) * ' '
654
"""the opposite of tagToIdentifier()"""
655
if ident == "GlyphOrder":
657
if len(ident) % 2 and ident[0] == "_":
659
assert not (len(ident) % 2)
661
for i in range(0, len(ident), 2):
663
tag = tag + ident[i+1]
664
elif ident[i+1] == "_":
668
tag = tag + chr(string.atoi(ident[i:i+2], 16))
669
# append trailing spaces
670
tag = tag + (4 - len(tag)) * ' '
674
674
def tagToXML(tag):
675
"""Similarly to tagToIdentifier(), this converts a TT tag
676
to a valid XML element name. Since XML element names are
677
case sensitive, this is a fairly simple/readable translation.
682
elif tag == "GlyphOrder":
684
if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
685
return string.strip(tag)
687
return tagToIdentifier(tag)
675
"""Similarly to tagToIdentifier(), this converts a TT tag
676
to a valid XML element name. Since XML element names are
677
case sensitive, this is a fairly simple/readable translation.
682
elif tag == "GlyphOrder":
684
if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
685
return string.strip(tag)
687
return tagToIdentifier(tag)
690
690
def xmlToTag(tag):
691
"""The opposite of tagToXML()"""
695
return identifierToTag(tag)
697
return tag + " " * (4 - len(tag))
691
"""The opposite of tagToXML()"""
695
return identifierToTag(tag)
697
return tag + " " * (4 - len(tag))
701
701
def debugmsg(msg):
703
print msg + time.strftime(" (%H:%M:%S)", time.localtime(time.time()))
703
print msg + time.strftime(" (%H:%M:%S)", time.localtime(time.time()))