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