~ubuntu-branches/ubuntu/trusty/python-enable/trusty

« back to all changes in this revision

Viewing changes to enthought/kiva/fonttools/fontTools/ttLib/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-04-05 21:54:28 UTC
  • mfrom: (1.1.5 upstream)
  • mto: (8.2.1 sid)
  • mto: This revision was merged to the branch mainline in revision 10.
  • Revision ID: james.westby@ubuntu.com-20110405215428-1x2wtubz3ok2kxaq
Tags: upstream-3.4.1
ImportĀ upstreamĀ versionĀ 3.4.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
"""fontTools.ttLib -- a package for dealing with TrueType fonts.
2
2
 
3
 
This package offers translators to convert TrueType fonts to Python 
 
3
This package offers translators to convert TrueType fonts to Python
4
4
objects and vice versa, and additionally from Python to TTX (an XML-based
5
5
text format) and vice versa.
6
6
 
37
37
>>> tt2.importXML("afont.ttx")
38
38
>>> tt2['maxp'].numGlyphs
39
39
242
40
 
>>> 
 
40
>>>
41
41
 
42
42
"""
43
43
 
54
54
 
55
55
 
56
56
class TTFont:
57
 
        
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.
62
 
        """
63
 
        
64
 
        def __init__(self, file=None, res_name_or_index=None,
65
 
                        sfntVersion="\000\001\000\000", checkChecksums=0,
66
 
                        verbose=0, recalcBBoxes=1):
67
 
                
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. 
71
 
                
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
76
 
                will be read!)
77
 
                
78
 
                The 'checkChecksums' argument is used to specify how sfnt
79
 
                checksums are treated upon reading a file from disk:
80
 
            
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.
84
 
                
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.
88
 
                
89
 
                If the recalcBBoxes argument is false, a number of things will *not*
90
 
                be recalculated upon save/compile:
91
 
 
92
 
                        1. glyph bounding boxes
93
 
                        2. maxp font bounding box
94
 
                        3. hhea min/max values
95
 
            
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.
101
 
                """
102
 
                
103
 
                import sfnt
104
 
                self.verbose = verbose
105
 
                self.recalcBBoxes = recalcBBoxes
106
 
                self.tables = {}
107
 
                self.reader = None
108
 
                if not file:
109
 
                        self.sfntVersion = sfntVersion
110
 
                        return
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
114
 
                                import macUtils
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)
119
 
                                        else:
120
 
                                                file = open(file, "rb")
121
 
                                else:
122
 
                                        file = macUtils.SFNTResourceReader(file, res_name_or_index)
123
 
                        else:
124
 
                                file = open(file, "rb")
125
 
                else:
126
 
                        pass # assume "file" is a readable file object
127
 
                self.reader = sfnt.SFNTReader(file, checkChecksums)
128
 
                self.sfntVersion = self.reader.sfntVersion
129
 
        
130
 
        def close(self):
131
 
                """If we still have a reader object, close it."""
132
 
                if self.reader is not None:
133
 
                        self.reader.close()
134
 
        
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
138
 
                file object.
139
 
                
140
 
                On the Mac, if makeSuitcase is true, a suitcase (resource fork)
141
 
                file will we made instead of a flat .ttf file. 
142
 
                """
143
 
                from enthought.kiva.fonttools.fontTools.ttLib import sfnt
144
 
                if isinstance(file, basestring):
145
 
                        closeStream = 1
146
 
                        if os.name == "mac" and makeSuitcase:
147
 
                                import macUtils
148
 
                                file = macUtils.SFNTResourceWriter(file, self)
149
 
                        else:
150
 
                                file = open(file, "wb")
151
 
                                if os.name == "mac":
152
 
                                        import macfs
153
 
                                        fss = macfs.FSSpec(file.name)
154
 
                                        fss.SetCreatorType('mdos', 'BINA')
155
 
                else:
156
 
                        # assume "file" is a writable file object
157
 
                        closeStream = 0
158
 
                
159
 
                tags = self.keys()
160
 
                tags.remove("GlyphOrder")
161
 
                numTables = len(tags)
162
 
                writer = sfnt.SFNTWriter(file, numTables, self.sfntVersion)
163
 
                
164
 
                done = []
165
 
                for tag in tags:
166
 
                        self._writeTable(tag, writer, done)
167
 
                
168
 
                writer.close(closeStream)
169
 
        
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.
178
 
                """
179
 
                from enthought.kiva.fonttools.fontTools import version
180
 
                import xmlWriter
181
 
                
182
 
                self.disassembleInstructions = disassembleInstructions
183
 
                if not tables:
184
 
                        tables = self.keys()
185
 
                        if skipTables:
186
 
                                for tag in skipTables:
187
 
                                        if tag in tables:
188
 
                                                tables.remove(tag)
189
 
                numTables = len(tables)
190
 
                numGlyphs = self['maxp'].numGlyphs
191
 
                if progress:
192
 
                        progress.set(0, numTables)
193
 
                        idlefunc = getattr(progress, "idle", None)
194
 
                else:
195
 
                        idlefunc = None
196
 
                
197
 
                writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc)
198
 
                writer.begintag("ttFont", sfntVersion=`self.sfntVersion`[1:-1], 
199
 
                                ttLibVersion=version)
200
 
                writer.newline()
201
 
                
202
 
                if not splitTables:
203
 
                        writer.newline()
204
 
                else:
205
 
                        # 'fileOrPath' must now be a path
206
 
                        path, ext = os.path.splitext(fileOrPath)
207
 
                        fileNameTemplate = path + ".%s" + ext
208
 
                
209
 
                for i in range(numTables):
210
 
                        if progress:
211
 
                                progress.set(i)
212
 
                        tag = tables[i]
213
 
                        if splitTables:
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))
220
 
                                writer.newline()
221
 
                        else:
222
 
                                tableWriter = writer
223
 
                        self._tableToXML(tableWriter, tag, progress)
224
 
                        if splitTables:
225
 
                                tableWriter.endtag("ttFont")
226
 
                                tableWriter.newline()
227
 
                                tableWriter.close()
228
 
                if progress:
229
 
                        progress.set((i + 1))
230
 
                writer.endtag("ttFont")
231
 
                writer.newline()
232
 
                writer.close()
233
 
                if self.verbose:
234
 
                        debugmsg("Done dumping TTX")
235
 
        
236
 
        def _tableToXML(self, writer, tag, progress):
237
 
                if self.has_key(tag):
238
 
                        table = self[tag]
239
 
                        report = "Dumping '%s' table..." % tag
240
 
                else:
241
 
                        report = "No '%s' table found." % tag
242
 
                if progress:
243
 
                        progress.setLabel(report)
244
 
                elif self.verbose:
245
 
                        debugmsg(report)
246
 
                else:
247
 
                        print report
248
 
                if not self.has_key(tag):
249
 
                        return
250
 
                xmlTag = tagToXML(tag)
251
 
                if hasattr(table, "ERROR"):
252
 
                        writer.begintag(xmlTag, ERROR="decompilation error")
253
 
                else:
254
 
                        writer.begintag(xmlTag)
255
 
                writer.newline()
256
 
                if tag in ("glyf", "CFF "):
257
 
                        table.toXML(writer, self, progress)
258
 
                else:
259
 
                        table.toXML(writer, self)
260
 
                writer.endtag(xmlTag)
261
 
                writer.newline()
262
 
                writer.newline()
263
 
        
264
 
        def importXML(self, file, progress=None):
265
 
                """Import a TTX file (an XML-based text format), so as to recreate
266
 
                a font object.
267
 
                """
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 ').
273
 
                        self.getGlyphOrder()
274
 
                import xmlImport
275
 
                xmlImport.importXML(self, file, progress)
276
 
        
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)
281
 
        
282
 
        def has_key(self, tag):
283
 
                if self.isLoaded(tag):
284
 
                        return 1
285
 
                elif self.reader and self.reader.has_key(tag):
286
 
                        return 1
287
 
                elif tag == "GlyphOrder":
288
 
                        return 1
289
 
                else:
290
 
                        return 0
291
 
        
292
 
        def keys(self):
293
 
                keys = self.tables.keys()
294
 
                if self.reader:
295
 
                        for key in self.reader.keys():
296
 
                                if key not in keys:
297
 
                                        keys.append(key)
298
 
                keys.sort()
299
 
                if "GlyphOrder" in keys:
300
 
                        keys.remove("GlyphOrder")
301
 
                return ["GlyphOrder"] + keys
302
 
        
303
 
        def __len__(self):
304
 
                return len(self.keys())
305
 
        
306
 
        def __getitem__(self, tag):
307
 
                try:
308
 
                        return self.tables[tag]
309
 
                except KeyError:
310
 
                        if tag == "GlyphOrder":
311
 
                                table = GlyphOrder(tag)
312
 
                                self.tables[tag] = table
313
 
                                return table
314
 
                        if self.reader is not None:
315
 
                                import traceback
316
 
                                if self.verbose:
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
322
 
                                if self.verbose:
323
 
                                        debugmsg("Decompiling '%s' table" % tag)
324
 
                                try:
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
329
 
                                        import StringIO
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)
336
 
                                return table
337
 
                        else:
338
 
                                raise KeyError, "'%s' table not found" % tag
339
 
        
340
 
        def __setitem__(self, tag, table):
341
 
                self.tables[tag] = table
342
 
        
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):
347
 
                        del self.tables[tag]
348
 
                if self.reader and self.reader.has_key(tag):
349
 
                        del self.reader[tag]
350
 
        
351
 
        def setGlyphOrder(self, glyphOrder):
352
 
                self.glyphOrder = glyphOrder
353
 
        
354
 
        def getGlyphOrder(self):
355
 
                try:
356
 
                        return self.glyphOrder
357
 
                except AttributeError:
358
 
                        pass
359
 
                if self.has_key('CFF '):
360
 
                        cff = self['CFF ']
361
 
                        if cff.haveGlyphNames():
362
 
                                self.glyphOrder = cff.getGlyphOrder()
363
 
                        else:
364
 
                                # CID-keyed font, use cmap
365
 
                                self._getGlyphNamesFromCmap()
366
 
                elif self.has_key('post'):
367
 
                        # TrueType font
368
 
                        glyphOrder = self['post'].getGlyphOrder()
369
 
                        if glyphOrder is None:
370
 
                                #
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).
374
 
                                #
375
 
                                self._getGlyphNamesFromCmap()
376
 
                        else:
377
 
                                self.glyphOrder = glyphOrder
378
 
                else:
379
 
                        self._getGlyphNamesFromCmap()
380
 
                return self.glyphOrder
381
 
        
382
 
        def _getGlyphNamesFromCmap(self):
383
 
                #
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,
387
 
                #   or none at all).
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
394
 
                #
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
400
 
                        # restore it later.
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']
405
 
                else:
406
 
                        cmapLoading = None
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
409
 
                # cmap.
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
423
 
                        cmap = tempcmap.cmap
424
 
                        # create a reverse cmap dict
425
 
                        reversecmap = {}
426
 
                        for unicode, name in cmap.items():
427
 
                                reversecmap[name] = unicode
428
 
                        allNames = {}
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]
436
 
                                        else:
437
 
                                                # create uni<CODE> name
438
 
                                                glyphName = "uni" + string.upper(string.zfill(
439
 
                                                                hex(unicode)[2:], 4))
440
 
                                        tempName = glyphName
441
 
                                        n = 1
442
 
                                        while allNames.has_key(tempName):
443
 
                                                tempName = glyphName + "#" + `n`
444
 
                                                n = n + 1
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']
450
 
                else:
451
 
                        pass # no unicode cmap available, stick with the invented names
452
 
                self.glyphOrder = glyphOrder
453
 
                if cmapLoading:
454
 
                        # restore partially loaded cmap, so it can continue loading
455
 
                        # using the proper names.
456
 
                        self.tables['cmap'] = cmapLoading
457
 
        
458
 
        def getGlyphNames(self):
459
 
                """Get a list of glyph names, sorted alphabetically."""
460
 
                glyphNames = self.getGlyphOrder()[:]
461
 
                glyphNames.sort()
462
 
                return glyphNames
463
 
        
464
 
        def getGlyphNames2(self):
465
 
                """Get a list of glyph names, sorted alphabetically, 
466
 
                but not case sensitive.
467
 
                """
468
 
                from enthought.kiva.fonttools.fontTools.misc import textTools
469
 
                return textTools.caselessSort(self.getGlyphOrder())
470
 
        
471
 
        def getGlyphName(self, glyphID):
472
 
                try:
473
 
                        return self.getGlyphOrder()[glyphID]
474
 
                except IndexError:
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
478
 
        
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)
488
 
                        else:
489
 
                                raise KeyError, glyphName
490
 
                glyphID = d[glyphName]
491
 
                if glyphName <> glyphOrder[glyphID]:
492
 
                        self._buildReverseGlyphOrderDict()
493
 
                        return self.getGlyphID(glyphName)
494
 
                return glyphID
495
 
        
496
 
        def _buildReverseGlyphOrderDict(self):
497
 
                self._reverseGlyphOrderDict = d = {}
498
 
                glyphOrder = self.getGlyphOrder()
499
 
                for glyphID in range(len(glyphOrder)):
500
 
                        d[glyphOrder[glyphID]] = glyphID
501
 
        
502
 
        def _writeTable(self, tag, writer, done):
503
 
                """Internal helper function for self.save(). Keeps track of 
504
 
                inter-table dependencies.
505
 
                """
506
 
                if tag in done:
507
 
                        return
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)
513
 
                                else:
514
 
                                        done.append(masterTable)
515
 
                tabledata = self.getTableData(tag)
516
 
                if self.verbose:
517
 
                        debugmsg("writing '%s' table to disk" % tag)
518
 
                writer[tag] = tabledata
519
 
                done.append(tag)
520
 
        
521
 
        def getTableData(self, tag):
522
 
                """Returns raw table data, whether compiled or directly read from disk.
523
 
                """
524
 
                if self.isLoaded(tag):
525
 
                        if self.verbose:
526
 
                                debugmsg("compiling '%s' table" % tag)
527
 
                        return self.tables[tag].compile(self)
528
 
                elif self.reader and self.reader.has_key(tag):
529
 
                        if self.verbose:
530
 
                                debugmsg("Reading '%s' table from disk" % tag)
531
 
                        return self.reader[tag]
532
 
                else:
533
 
                        raise KeyError, tag
 
57
 
 
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.
 
62
        """
 
63
 
 
64
        def __init__(self, file=None, res_name_or_index=None,
 
65
                        sfntVersion="\000\001\000\000", checkChecksums=0,
 
66
                        verbose=0, recalcBBoxes=1):
 
67
 
 
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.
 
71
 
 
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
 
76
                will be read!)
 
77
 
 
78
                The 'checkChecksums' argument is used to specify how sfnt
 
79
                checksums are treated upon reading a file from disk:
 
80
 
 
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.
 
84
 
 
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.
 
88
 
 
89
                If the recalcBBoxes argument is false, a number of things will *not*
 
90
                be recalculated upon save/compile:
 
91
 
 
92
                        1. glyph bounding boxes
 
93
                        2. maxp font bounding box
 
94
                        3. hhea min/max values
 
95
 
 
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.
 
101
                """
 
102
 
 
103
                import sfnt
 
104
                self.verbose = verbose
 
105
                self.recalcBBoxes = recalcBBoxes
 
106
                self.tables = {}
 
107
                self.reader = None
 
108
                if not file:
 
109
                        self.sfntVersion = sfntVersion
 
110
                        return
 
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
 
114
                                import macUtils
 
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)
 
119
                                        else:
 
120
                                                file = open(file, "rb")
 
121
                                else:
 
122
                                        file = macUtils.SFNTResourceReader(file, res_name_or_index)
 
123
                        else:
 
124
                                file = open(file, "rb")
 
125
                else:
 
126
                        pass # assume "file" is a readable file object
 
127
                self.reader = sfnt.SFNTReader(file, checkChecksums)
 
128
                self.sfntVersion = self.reader.sfntVersion
 
129
 
 
130
        def close(self):
 
131
                """If we still have a reader object, close it."""
 
132
                if self.reader is not None:
 
133
                        self.reader.close()
 
134
 
 
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
 
138
                file object.
 
139
 
 
140
                On the Mac, if makeSuitcase is true, a suitcase (resource fork)
 
141
                file will we made instead of a flat .ttf file.
 
142
                """
 
143
                from enthought.kiva.fonttools.fontTools.ttLib import sfnt
 
144
                if isinstance(file, basestring):
 
145
                        closeStream = 1
 
146
                        if os.name == "mac" and makeSuitcase:
 
147
                                import macUtils
 
148
                                file = macUtils.SFNTResourceWriter(file, self)
 
149
                        else:
 
150
                                file = open(file, "wb")
 
151
                                if os.name == "mac":
 
152
                                        import macfs
 
153
                                        fss = macfs.FSSpec(file.name)
 
154
                                        fss.SetCreatorType('mdos', 'BINA')
 
155
                else:
 
156
                        # assume "file" is a writable file object
 
157
                        closeStream = 0
 
158
 
 
159
                tags = self.keys()
 
160
                tags.remove("GlyphOrder")
 
161
                numTables = len(tags)
 
162
                writer = sfnt.SFNTWriter(file, numTables, self.sfntVersion)
 
163
 
 
164
                done = []
 
165
                for tag in tags:
 
166
                        self._writeTable(tag, writer, done)
 
167
 
 
168
                writer.close(closeStream)
 
169
 
 
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.
 
178
                """
 
179
                from enthought.kiva.fonttools.fontTools import version
 
180
                import xmlWriter
 
181
 
 
182
                self.disassembleInstructions = disassembleInstructions
 
183
                if not tables:
 
184
                        tables = self.keys()
 
185
                        if skipTables:
 
186
                                for tag in skipTables:
 
187
                                        if tag in tables:
 
188
                                                tables.remove(tag)
 
189
                numTables = len(tables)
 
190
                numGlyphs = self['maxp'].numGlyphs
 
191
                if progress:
 
192
                        progress.set(0, numTables)
 
193
                        idlefunc = getattr(progress, "idle", None)
 
194
                else:
 
195
                        idlefunc = None
 
196
 
 
197
                writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc)
 
198
                writer.begintag("ttFont", sfntVersion=`self.sfntVersion`[1:-1],
 
199
                                ttLibVersion=version)
 
200
                writer.newline()
 
201
 
 
202
                if not splitTables:
 
203
                        writer.newline()
 
204
                else:
 
205
                        # 'fileOrPath' must now be a path
 
206
                        path, ext = os.path.splitext(fileOrPath)
 
207
                        fileNameTemplate = path + ".%s" + ext
 
208
 
 
209
                for i in range(numTables):
 
210
                        if progress:
 
211
                                progress.set(i)
 
212
                        tag = tables[i]
 
213
                        if splitTables:
 
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))
 
220
                                writer.newline()
 
221
                        else:
 
222
                                tableWriter = writer
 
223
                        self._tableToXML(tableWriter, tag, progress)
 
224
                        if splitTables:
 
225
                                tableWriter.endtag("ttFont")
 
226
                                tableWriter.newline()
 
227
                                tableWriter.close()
 
228
                if progress:
 
229
                        progress.set((i + 1))
 
230
                writer.endtag("ttFont")
 
231
                writer.newline()
 
232
                writer.close()
 
233
                if self.verbose:
 
234
                        debugmsg("Done dumping TTX")
 
235
 
 
236
        def _tableToXML(self, writer, tag, progress):
 
237
                if self.has_key(tag):
 
238
                        table = self[tag]
 
239
                        report = "Dumping '%s' table..." % tag
 
240
                else:
 
241
                        report = "No '%s' table found." % tag
 
242
                if progress:
 
243
                        progress.setLabel(report)
 
244
                elif self.verbose:
 
245
                        debugmsg(report)
 
246
                else:
 
247
                        print report
 
248
                if not self.has_key(tag):
 
249
                        return
 
250
                xmlTag = tagToXML(tag)
 
251
                if hasattr(table, "ERROR"):
 
252
                        writer.begintag(xmlTag, ERROR="decompilation error")
 
253
                else:
 
254
                        writer.begintag(xmlTag)
 
255
                writer.newline()
 
256
                if tag in ("glyf", "CFF "):
 
257
                        table.toXML(writer, self, progress)
 
258
                else:
 
259
                        table.toXML(writer, self)
 
260
                writer.endtag(xmlTag)
 
261
                writer.newline()
 
262
                writer.newline()
 
263
 
 
264
        def importXML(self, file, progress=None):
 
265
                """Import a TTX file (an XML-based text format), so as to recreate
 
266
                a font object.
 
267
                """
 
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 ').
 
273
                        self.getGlyphOrder()
 
274
                import xmlImport
 
275
                xmlImport.importXML(self, file, progress)
 
276
 
 
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)
 
281
 
 
282
        def has_key(self, tag):
 
283
                if self.isLoaded(tag):
 
284
                        return 1
 
285
                elif self.reader and self.reader.has_key(tag):
 
286
                        return 1
 
287
                elif tag == "GlyphOrder":
 
288
                        return 1
 
289
                else:
 
290
                        return 0
 
291
 
 
292
        def keys(self):
 
293
                keys = self.tables.keys()
 
294
                if self.reader:
 
295
                        for key in self.reader.keys():
 
296
                                if key not in keys:
 
297
                                        keys.append(key)
 
298
                keys.sort()
 
299
                if "GlyphOrder" in keys:
 
300
                        keys.remove("GlyphOrder")
 
301
                return ["GlyphOrder"] + keys
 
302
 
 
303
        def __len__(self):
 
304
                return len(self.keys())
 
305
 
 
306
        def __getitem__(self, tag):
 
307
                try:
 
308
                        return self.tables[tag]
 
309
                except KeyError:
 
310
                        if tag == "GlyphOrder":
 
311
                                table = GlyphOrder(tag)
 
312
                                self.tables[tag] = table
 
313
                                return table
 
314
                        if self.reader is not None:
 
315
                                import traceback
 
316
                                if self.verbose:
 
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
 
322
                                if self.verbose:
 
323
                                        debugmsg("Decompiling '%s' table" % tag)
 
324
                                try:
 
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
 
329
                                        import StringIO
 
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)
 
336
                                return table
 
337
                        else:
 
338
                                raise KeyError, "'%s' table not found" % tag
 
339
 
 
340
        def __setitem__(self, tag, table):
 
341
                self.tables[tag] = table
 
342
 
 
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):
 
347
                        del self.tables[tag]
 
348
                if self.reader and self.reader.has_key(tag):
 
349
                        del self.reader[tag]
 
350
 
 
351
        def setGlyphOrder(self, glyphOrder):
 
352
                self.glyphOrder = glyphOrder
 
353
 
 
354
        def getGlyphOrder(self):
 
355
                try:
 
356
                        return self.glyphOrder
 
357
                except AttributeError:
 
358
                        pass
 
359
                if self.has_key('CFF '):
 
360
                        cff = self['CFF ']
 
361
                        if cff.haveGlyphNames():
 
362
                                self.glyphOrder = cff.getGlyphOrder()
 
363
                        else:
 
364
                                # CID-keyed font, use cmap
 
365
                                self._getGlyphNamesFromCmap()
 
366
                elif self.has_key('post'):
 
367
                        # TrueType font
 
368
                        glyphOrder = self['post'].getGlyphOrder()
 
369
                        if glyphOrder is None:
 
370
                                #
 
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).
 
374
                                #
 
375
                                self._getGlyphNamesFromCmap()
 
376
                        else:
 
377
                                self.glyphOrder = glyphOrder
 
378
                else:
 
379
                        self._getGlyphNamesFromCmap()
 
380
                return self.glyphOrder
 
381
 
 
382
        def _getGlyphNamesFromCmap(self):
 
383
                #
 
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,
 
387
                #   or none at all).
 
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
 
394
                #
 
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
 
400
                        # restore it later.
 
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']
 
405
                else:
 
406
                        cmapLoading = None
 
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
 
409
                # cmap.
 
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
 
423
                        cmap = tempcmap.cmap
 
424
                        # create a reverse cmap dict
 
425
                        reversecmap = {}
 
426
                        for unicode, name in cmap.items():
 
427
                                reversecmap[name] = unicode
 
428
                        allNames = {}
 
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]
 
436
                                        else:
 
437
                                                # create uni<CODE> name
 
438
                                                glyphName = "uni" + string.upper(string.zfill(
 
439
                                                                hex(unicode)[2:], 4))
 
440
                                        tempName = glyphName
 
441
                                        n = 1
 
442
                                        while allNames.has_key(tempName):
 
443
                                                tempName = glyphName + "#" + `n`
 
444
                                                n = n + 1
 
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']
 
450
                else:
 
451
                        pass # no unicode cmap available, stick with the invented names
 
452
                self.glyphOrder = glyphOrder
 
453
                if cmapLoading:
 
454
                        # restore partially loaded cmap, so it can continue loading
 
455
                        # using the proper names.
 
456
                        self.tables['cmap'] = cmapLoading
 
457
 
 
458
        def getGlyphNames(self):
 
459
                """Get a list of glyph names, sorted alphabetically."""
 
460
                glyphNames = self.getGlyphOrder()[:]
 
461
                glyphNames.sort()
 
462
                return glyphNames
 
463
 
 
464
        def getGlyphNames2(self):
 
465
                """Get a list of glyph names, sorted alphabetically,
 
466
                but not case sensitive.
 
467
                """
 
468
                from enthought.kiva.fonttools.fontTools.misc import textTools
 
469
                return textTools.caselessSort(self.getGlyphOrder())
 
470
 
 
471
        def getGlyphName(self, glyphID):
 
472
                try:
 
473
                        return self.getGlyphOrder()[glyphID]
 
474
                except IndexError:
 
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
 
478
 
 
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)
 
488
                        else:
 
489
                                raise KeyError, glyphName
 
490
                glyphID = d[glyphName]
 
491
                if glyphName <> glyphOrder[glyphID]:
 
492
                        self._buildReverseGlyphOrderDict()
 
493
                        return self.getGlyphID(glyphName)
 
494
                return glyphID
 
495
 
 
496
        def _buildReverseGlyphOrderDict(self):
 
497
                self._reverseGlyphOrderDict = d = {}
 
498
                glyphOrder = self.getGlyphOrder()
 
499
                for glyphID in range(len(glyphOrder)):
 
500
                        d[glyphOrder[glyphID]] = glyphID
 
501
 
 
502
        def _writeTable(self, tag, writer, done):
 
503
                """Internal helper function for self.save(). Keeps track of
 
504
                inter-table dependencies.
 
505
                """
 
506
                if tag in done:
 
507
                        return
 
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)
 
513
                                else:
 
514
                                        done.append(masterTable)
 
515
                tabledata = self.getTableData(tag)
 
516
                if self.verbose:
 
517
                        debugmsg("writing '%s' table to disk" % tag)
 
518
                writer[tag] = tabledata
 
519
                done.append(tag)
 
520
 
 
521
        def getTableData(self, tag):
 
522
                """Returns raw table data, whether compiled or directly read from disk.
 
523
                """
 
524
                if self.isLoaded(tag):
 
525
                        if self.verbose:
 
526
                                debugmsg("compiling '%s' table" % tag)
 
527
                        return self.tables[tag].compile(self)
 
528
                elif self.reader and self.reader.has_key(tag):
 
529
                        if self.verbose:
 
530
                                debugmsg("Reading '%s' table from disk" % tag)
 
531
                        return self.reader[tag]
 
532
                else:
 
533
                        raise KeyError, tag
534
534
 
535
535
 
536
536
class GlyphOrder:
537
 
        
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.
540
 
        """
541
 
        
542
 
        def __init__(self, tag):
543
 
                pass
544
 
        
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.")
549
 
                writer.newline()
550
 
                for i in range(len(glyphOrder)):
551
 
                        glyphName = glyphOrder[i]
552
 
                        writer.simpletag("GlyphID", id=i, name=glyphName)
553
 
                        writer.newline()
554
 
        
555
 
        def fromXML(self, (name, attrs, content), ttFont):
556
 
                if not hasattr(self, "glyphOrder"):
557
 
                        self.glyphOrder = []
558
 
                        ttFont.setGlyphOrder(self.glyphOrder)
559
 
                if name == "GlyphID":
560
 
                        self.glyphOrder.append(attrs["name"])
 
537
 
 
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.
 
540
        """
 
541
 
 
542
        def __init__(self, tag):
 
543
                pass
 
544
 
 
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.")
 
549
                writer.newline()
 
550
                for i in range(len(glyphOrder)):
 
551
                        glyphName = glyphOrder[i]
 
552
                        writer.simpletag("GlyphID", id=i, name=glyphName)
 
553
                        writer.newline()
 
554
 
 
555
        def fromXML(self, (name, attrs, content), ttFont):
 
556
                if not hasattr(self, "glyphOrder"):
 
557
                        self.glyphOrder = []
 
558
                        ttFont.setGlyphOrder(self.glyphOrder)
 
559
                if name == "GlyphID":
 
560
                        self.glyphOrder.append(attrs["name"])
561
561
 
562
562
 
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.
568
 
        """
569
 
        import struct
570
 
        data = struct.pack("h", 0x01)
571
 
        if data == "\000\001":
572
 
                return "big"
573
 
        elif data == "\001\000":
574
 
                return "little"
575
 
        else:
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.
 
568
        """
 
569
        import struct
 
570
        data = struct.pack("h", 0x01)
 
571
        if data == "\000\001":
 
572
                return "big"
 
573
        elif data == "\001\000":
 
574
                return "little"
 
575
        else:
 
576
                assert 0, "endian confusion!"
577
577
 
578
578
endian = _test_endianness()
579
579
 
580
580
 
581
581
def getTableModule(tag):
582
 
        """Fetch the packer/unpacker module for a table. 
583
 
        Return None when no module is found.
584
 
        """
585
 
        import tables
586
 
        pyTag = tagToIdentifier(tag)
587
 
        try:
588
 
                module = __import__("enthought.kiva.fonttools.fontTools.ttLib.tables." + pyTag)
589
 
        except ImportError:
590
 
                return None
591
 
        else:
592
 
                return getattr(tables, pyTag)
 
582
        """Fetch the packer/unpacker module for a table.
 
583
        Return None when no module is found.
 
584
        """
 
585
        import tables
 
586
        pyTag = tagToIdentifier(tag)
 
587
        try:
 
588
                module = __import__("enthought.kiva.fonttools.fontTools.ttLib.tables." + pyTag)
 
589
        except ImportError:
 
590
                return None
 
591
        else:
 
592
                return getattr(tables, pyTag)
593
593
 
594
594
 
595
595
def getTableClass(tag):
596
 
        """Fetch the packer/unpacker class for a table. 
597
 
        Return None when no class is found.
598
 
        """
599
 
        module = getTableModule(tag)
600
 
        if module is None:
601
 
                from tables.DefaultTable import DefaultTable
602
 
                return DefaultTable
603
 
        pyTag = tagToIdentifier(tag)
604
 
        tableClass = getattr(module, "table_" + pyTag)
605
 
        return tableClass
 
596
        """Fetch the packer/unpacker class for a table.
 
597
        Return None when no class is found.
 
598
        """
 
599
        module = getTableModule(tag)
 
600
        if module is None:
 
601
                from tables.DefaultTable import DefaultTable
 
602
                return DefaultTable
 
603
        pyTag = tagToIdentifier(tag)
 
604
        tableClass = getattr(module, "table_" + pyTag)
 
605
        return tableClass
606
606
 
607
607
 
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)
612
612
 
613
613
 
614
614
def _escapechar(c):
615
 
        """Helper function for tagToIdentifier()"""
616
 
        import re
617
 
        if re.match("[a-z0-9]", c):
618
 
                return "_" + c
619
 
        elif re.match("[A-Z]", c):
620
 
                return c + "_"
621
 
        else:
622
 
                return hex(ord(c))[2:]
 
615
        """Helper function for tagToIdentifier()"""
 
616
        import re
 
617
        if re.match("[a-z0-9]", c):
 
618
                return "_" + c
 
619
        elif re.match("[A-Z]", c):
 
620
                return c + "_"
 
621
        else:
 
622
                return hex(ord(c))[2:]
623
623
 
624
624
 
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::
634
 
        
635
 
                'glyf' -> '_g_l_y_f'
636
 
                'cvt ' -> '_c_v_t'
637
 
                'OS/2' -> 'O_S_2f_2'
638
 
        """
639
 
        import re
640
 
        if tag == "GlyphOrder":
641
 
                return tag
642
 
        assert len(tag) == 4, "tag should be 4 characters long"
643
 
        while len(tag) > 1 and tag[-1] == ' ':
644
 
                tag = tag[:-1]
645
 
        ident = ""
646
 
        for c in tag:
647
 
                ident = ident + _escapechar(c)
648
 
        if re.match("[0-9]", ident):
649
 
                ident = "_" + ident
650
 
        return 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::
 
634
 
 
635
                'glyf' -> '_g_l_y_f'
 
636
                'cvt ' -> '_c_v_t'
 
637
                'OS/2' -> 'O_S_2f_2'
 
638
        """
 
639
        import re
 
640
        if tag == "GlyphOrder":
 
641
                return tag
 
642
        assert len(tag) == 4, "tag should be 4 characters long"
 
643
        while len(tag) > 1 and tag[-1] == ' ':
 
644
                tag = tag[:-1]
 
645
        ident = ""
 
646
        for c in tag:
 
647
                ident = ident + _escapechar(c)
 
648
        if re.match("[0-9]", ident):
 
649
                ident = "_" + ident
 
650
        return ident
651
651
 
652
652
 
653
653
def identifierToTag(ident):
654
 
        """the opposite of tagToIdentifier()"""
655
 
        if ident == "GlyphOrder":
656
 
                return ident
657
 
        if len(ident) % 2 and ident[0] == "_":
658
 
                ident = ident[1:]
659
 
        assert not (len(ident) % 2)
660
 
        tag = ""
661
 
        for i in range(0, len(ident), 2):
662
 
                if ident[i] == "_":
663
 
                        tag = tag + ident[i+1]
664
 
                elif ident[i+1] == "_":
665
 
                        tag = tag + ident[i]
666
 
                else:
667
 
                        # assume hex
668
 
                        tag = tag + chr(string.atoi(ident[i:i+2], 16))
669
 
        # append trailing spaces
670
 
        tag = tag + (4 - len(tag)) * ' '
671
 
        return tag
 
654
        """the opposite of tagToIdentifier()"""
 
655
        if ident == "GlyphOrder":
 
656
                return ident
 
657
        if len(ident) % 2 and ident[0] == "_":
 
658
                ident = ident[1:]
 
659
        assert not (len(ident) % 2)
 
660
        tag = ""
 
661
        for i in range(0, len(ident), 2):
 
662
                if ident[i] == "_":
 
663
                        tag = tag + ident[i+1]
 
664
                elif ident[i+1] == "_":
 
665
                        tag = tag + ident[i]
 
666
                else:
 
667
                        # assume hex
 
668
                        tag = tag + chr(string.atoi(ident[i:i+2], 16))
 
669
        # append trailing spaces
 
670
        tag = tag + (4 - len(tag)) * ' '
 
671
        return tag
672
672
 
673
673
 
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.
678
 
        """
679
 
        import re
680
 
        if tag == "OS/2":
681
 
                return "OS_2"
682
 
        elif tag == "GlyphOrder":
683
 
                return "GlyphOrder"
684
 
        if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
685
 
                return string.strip(tag)
686
 
        else:
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.
 
678
        """
 
679
        import re
 
680
        if tag == "OS/2":
 
681
                return "OS_2"
 
682
        elif tag == "GlyphOrder":
 
683
                return "GlyphOrder"
 
684
        if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
 
685
                return string.strip(tag)
 
686
        else:
 
687
                return tagToIdentifier(tag)
688
688
 
689
689
 
690
690
def xmlToTag(tag):
691
 
        """The opposite of tagToXML()"""
692
 
        if tag == "OS_2":
693
 
                return "OS/2"
694
 
        if len(tag) == 8:
695
 
                return identifierToTag(tag)
696
 
        else:
697
 
                return tag + " " * (4 - len(tag))
698
 
        return tag
 
691
        """The opposite of tagToXML()"""
 
692
        if tag == "OS_2":
 
693
                return "OS/2"
 
694
        if len(tag) == 8:
 
695
                return identifierToTag(tag)
 
696
        else:
 
697
                return tag + " " * (4 - len(tag))
 
698
        return tag
699
699
 
700
700
 
701
701
def debugmsg(msg):
702
 
        import time
703
 
        print msg + time.strftime("  (%H:%M:%S)", time.localtime(time.time()))
 
702
        import time
 
703
        print msg + time.strftime("  (%H:%M:%S)", time.localtime(time.time()))
704
704