__init__.py revision d8b32bf6e208a18421cfd4705801d6237e281f93
1"""fontTools.ttLib -- a package for dealing with TrueType fonts.
2
3This package offers translators to convert TrueType fonts to Python
4objects and vice versa, and additionally from Python to TTX (an XML-based
5text format) and vice versa.
6
7Example interactive session:
8
9Python 1.5.2c1 (#43, Mar  9 1999, 13:06:43)  [CW PPC w/GUSI w/MSL]
10Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
11>>> from fontTools import ttLib
12>>> tt = ttLib.TTFont("afont.ttf")
13>>> tt['maxp'].numGlyphs
14242
15>>> tt['OS/2'].achVendID
16'B&H\000'
17>>> tt['head'].unitsPerEm
182048
19>>> tt.saveXML("afont.ttx")
20Dumping 'LTSH' table...
21Dumping 'OS/2' table...
22Dumping 'VDMX' table...
23Dumping 'cmap' table...
24Dumping 'cvt ' table...
25Dumping 'fpgm' table...
26Dumping 'glyf' table...
27Dumping 'hdmx' table...
28Dumping 'head' table...
29Dumping 'hhea' table...
30Dumping 'hmtx' table...
31Dumping 'loca' table...
32Dumping 'maxp' table...
33Dumping 'name' table...
34Dumping 'post' table...
35Dumping 'prep' table...
36>>> tt2 = ttLib.TTFont()
37>>> tt2.importXML("afont.ttx")
38>>> tt2['maxp'].numGlyphs
39242
40>>>
41
42"""
43
44#
45# $Id: __init__.py,v 1.51 2009-02-22 08:55:00 pabs3 Exp $
46#
47
48import sys
49import os
50import string
51
52haveMacSupport = 0
53if sys.platform == "mac":
54	haveMacSupport = 1
55elif sys.platform == "darwin" and sys.version_info[:3] != (2, 2, 0):
56	# Python 2.2's Mac support is broken, so don't enable it there.
57	haveMacSupport = 1
58
59
60class TTLibError(Exception): pass
61
62
63class TTFont:
64
65	"""The main font object. It manages file input and output, and offers
66	a convenient way of accessing tables.
67	Tables will be only decompiled when necessary, ie. when they're actually
68	accessed. This means that simple operations can be extremely fast.
69	"""
70
71	def __init__(self, file=None, res_name_or_index=None,
72			sfntVersion="\000\001\000\000", flavor=None, checkChecksums=0,
73			verbose=0, recalcBBoxes=1, allowVID=0, ignoreDecompileErrors=False,
74			fontNumber=-1):
75
76		"""The constructor can be called with a few different arguments.
77		When reading a font from disk, 'file' should be either a pathname
78		pointing to a file, or a readable file object.
79
80		It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt
81		resource name or an sfnt resource index number or zero. The latter
82		case will cause TTLib to autodetect whether the file is a flat file
83		or a suitcase. (If it's a suitcase, only the first 'sfnt' resource
84		will be read!)
85
86		The 'checkChecksums' argument is used to specify how sfnt
87		checksums are treated upon reading a file from disk:
88			0: don't check (default)
89			1: check, print warnings if a wrong checksum is found
90			2: check, raise an exception if a wrong checksum is found.
91
92		The TTFont constructor can also be called without a 'file'
93		argument: this is the way to create a new empty font.
94		In this case you can optionally supply the 'sfntVersion' argument,
95		and a 'flavor' which can be None, or 'woff'.
96
97		If the recalcBBoxes argument is false, a number of things will *not*
98		be recalculated upon save/compile:
99			1) glyph bounding boxes
100			2) maxp font bounding box
101			3) hhea min/max values
102		(1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-).
103		Additionally, upon importing an TTX file, this option cause glyphs
104		to be compiled right away. This should reduce memory consumption
105		greatly, and therefore should have some impact on the time needed
106		to parse/compile large fonts.
107
108		If the allowVID argument is set to true, then virtual GID's are
109		supported. Asking for a glyph ID with a glyph name or GID that is not in
110		the font will return a virtual GID.   This is valid for GSUB and cmap
111		tables. For SING glyphlets, the cmap table is used to specify Unicode
112		values for virtual GI's used in GSUB/GPOS rules. If the gid Nis requested
113		and does not exist in the font, or the glyphname has the form glyphN
114		and does not exist in the font, then N is used as the virtual GID.
115		Else, the first virtual GID is assigned as 0x1000 -1; for subsequent new
116		virtual GIDs, the next is one less than the previous.
117
118		If ignoreDecompileErrors is set to True, exceptions raised in
119		individual tables during decompilation will be ignored, falling
120		back to the DefaultTable implementation, which simply keeps the
121		binary data.
122		"""
123
124		from fontTools.ttLib import sfnt
125		self.verbose = verbose
126		self.recalcBBoxes = recalcBBoxes
127		self.tables = {}
128		self.reader = None
129
130		# Permit the user to reference glyphs that are not int the font.
131		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.
132		self.reverseVIDDict = {}
133		self.VIDDict = {}
134		self.allowVID = allowVID
135		self.ignoreDecompileErrors = ignoreDecompileErrors
136
137		if not file:
138			self.sfntVersion = sfntVersion
139			self.flavor = flavor
140			self.flavorData = None
141			return
142		if not hasattr(file, "read"):
143			# assume file is a string
144			if haveMacSupport and res_name_or_index is not None:
145				# on the mac, we deal with sfnt resources as well as flat files
146				import macUtils
147				if res_name_or_index == 0:
148					if macUtils.getSFNTResIndices(file):
149						# get the first available sfnt font.
150						file = macUtils.SFNTResourceReader(file, 1)
151					else:
152						file = open(file, "rb")
153				else:
154					file = macUtils.SFNTResourceReader(file, res_name_or_index)
155			else:
156				file = open(file, "rb")
157		else:
158			pass # assume "file" is a readable file object
159		self.reader = sfnt.SFNTReader(file, checkChecksums, fontNumber=fontNumber)
160		self.sfntVersion = self.reader.sfntVersion
161		self.flavor = self.reader.flavor
162		self.flavorData = self.reader.flavorData
163
164	def close(self):
165		"""If we still have a reader object, close it."""
166		if self.reader is not None:
167			self.reader.close()
168
169	def save(self, file, makeSuitcase=0, reorderTables=1):
170		"""Save the font to disk. Similarly to the constructor,
171		the 'file' argument can be either a pathname or a writable
172		file object.
173
174		On the Mac, if makeSuitcase is true, a suitcase (resource fork)
175		file will we made instead of a flat .ttf file.
176		"""
177		from fontTools.ttLib import sfnt
178		if not hasattr(file, "write"):
179			closeStream = 1
180			if os.name == "mac" and makeSuitcase:
181				import macUtils
182				file = macUtils.SFNTResourceWriter(file, self)
183			else:
184				file = open(file, "wb")
185				if os.name == "mac":
186					from fontTools.misc.macCreator import setMacCreatorAndType
187					setMacCreatorAndType(file.name, 'mdos', 'BINA')
188		else:
189			# assume "file" is a writable file object
190			closeStream = 0
191
192		tags = self.keys()
193		if "GlyphOrder" in tags:
194			tags.remove("GlyphOrder")
195		numTables = len(tags)
196		if reorderTables:
197			import tempfile
198			tmp = tempfile.TemporaryFile(prefix="ttx-fonttools")
199		else:
200			tmp = file
201		writer = sfnt.SFNTWriter(tmp, numTables, self.sfntVersion, self.flavor, self.flavorData)
202
203		done = []
204		for tag in tags:
205			self._writeTable(tag, writer, done)
206
207		writer.close()
208
209		if reorderTables:
210			tmp.flush()
211			tmp.seek(0)
212			reorderFontTables(tmp, file)
213			tmp.close()
214
215		if closeStream:
216			file.close()
217
218	def saveXML(self, fileOrPath, progress=None,
219			tables=None, skipTables=None, splitTables=0, disassembleInstructions=1,
220			bitmapGlyphDataFormat='raw'):
221		"""Export the font as TTX (an XML-based text file), or as a series of text
222		files when splitTables is true. In the latter case, the 'fileOrPath'
223		argument should be a path to a directory.
224		The 'tables' argument must either be false (dump all tables) or a
225		list of tables to dump. The 'skipTables' argument may be a list of tables
226		to skip, but only when the 'tables' argument is false.
227		"""
228		from fontTools import version
229		import xmlWriter
230
231		self.disassembleInstructions = disassembleInstructions
232		self.bitmapGlyphDataFormat = bitmapGlyphDataFormat
233		if not tables:
234			tables = self.keys()
235			if "GlyphOrder" not in tables:
236				tables = ["GlyphOrder"] + tables
237			if skipTables:
238				for tag in skipTables:
239					if tag in tables:
240						tables.remove(tag)
241		numTables = len(tables)
242		if progress:
243			progress.set(0, numTables)
244			idlefunc = getattr(progress, "idle", None)
245		else:
246			idlefunc = None
247
248		writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc)
249		writer.begintag("ttFont", sfntVersion=`self.sfntVersion`[1:-1],
250				ttLibVersion=version)
251		writer.newline()
252
253		if not splitTables:
254			writer.newline()
255		else:
256			# 'fileOrPath' must now be a path
257			path, ext = os.path.splitext(fileOrPath)
258			fileNameTemplate = path + ".%s" + ext
259
260		for i in range(numTables):
261			if progress:
262				progress.set(i)
263			tag = tables[i]
264			if splitTables:
265				tablePath = fileNameTemplate % tagToIdentifier(tag)
266				tableWriter = xmlWriter.XMLWriter(tablePath, idlefunc=idlefunc)
267				tableWriter.begintag("ttFont", ttLibVersion=version)
268				tableWriter.newline()
269				tableWriter.newline()
270				writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath))
271				writer.newline()
272			else:
273				tableWriter = writer
274			self._tableToXML(tableWriter, tag, progress)
275			if splitTables:
276				tableWriter.endtag("ttFont")
277				tableWriter.newline()
278				tableWriter.close()
279		if progress:
280			progress.set((i + 1))
281		writer.endtag("ttFont")
282		writer.newline()
283		writer.close()
284		if self.verbose:
285			debugmsg("Done dumping TTX")
286
287	def _tableToXML(self, writer, tag, progress):
288		if self.has_key(tag):
289			table = self[tag]
290			report = "Dumping '%s' table..." % tag
291		else:
292			report = "No '%s' table found." % tag
293		if progress:
294			progress.setLabel(report)
295		elif self.verbose:
296			debugmsg(report)
297		else:
298			print report
299		if not self.has_key(tag):
300			return
301		xmlTag = tagToXML(tag)
302		if hasattr(table, "ERROR"):
303			writer.begintag(xmlTag, ERROR="decompilation error")
304		else:
305			writer.begintag(xmlTag)
306		writer.newline()
307		if tag in ("glyf", "CFF "):
308			table.toXML(writer, self, progress)
309		else:
310			table.toXML(writer, self)
311		writer.endtag(xmlTag)
312		writer.newline()
313		writer.newline()
314
315	def importXML(self, file, progress=None):
316		"""Import a TTX file (an XML-based text format), so as to recreate
317		a font object.
318		"""
319		if self.has_key("maxp") and self.has_key("post"):
320			# Make sure the glyph order is loaded, as it otherwise gets
321			# lost if the XML doesn't contain the glyph order, yet does
322			# contain the table which was originally used to extract the
323			# glyph names from (ie. 'post', 'cmap' or 'CFF ').
324			self.getGlyphOrder()
325		import xmlImport
326		xmlImport.importXML(self, file, progress)
327
328	def isLoaded(self, tag):
329		"""Return true if the table identified by 'tag' has been
330		decompiled and loaded into memory."""
331		return self.tables.has_key(tag)
332
333	def has_key(self, tag):
334		if self.isLoaded(tag):
335			return 1
336		elif self.reader and self.reader.has_key(tag):
337			return 1
338		elif tag == "GlyphOrder":
339			return 1
340		else:
341			return 0
342
343	__contains__ = has_key
344
345	def keys(self):
346		keys = self.tables.keys()
347		if self.reader:
348			for key in self.reader.keys():
349				if key not in keys:
350					keys.append(key)
351
352		if "GlyphOrder" in keys:
353			keys.remove("GlyphOrder")
354		keys = sortedTagList(keys)
355		return ["GlyphOrder"] + keys
356
357	def __len__(self):
358		return len(self.keys())
359
360	def __getitem__(self, tag):
361		try:
362			return self.tables[tag]
363		except KeyError:
364			if tag == "GlyphOrder":
365				table = GlyphOrder(self)
366				self.tables[tag] = table
367				return table
368			if self.reader is not None:
369				import traceback
370				if self.verbose:
371					debugmsg("Reading '%s' table from disk" % tag)
372				data = self.reader[tag]
373				tableClass = getTableClass(tag)
374				table = tableClass(tag)
375				self.tables[tag] = table
376				if self.verbose:
377					debugmsg("Decompiling '%s' table" % tag)
378				try:
379					table.decompile(data, self)
380				except:
381					if not self.ignoreDecompileErrors:
382						raise
383					# fall back to DefaultTable, retaining the binary table data
384					print "An exception occurred during the decompilation of the '%s' table" % tag
385					from tables.DefaultTable import DefaultTable
386					import StringIO
387					file = StringIO.StringIO()
388					traceback.print_exc(file=file)
389					table = DefaultTable(tag)
390					table.ERROR = file.getvalue()
391					self.tables[tag] = table
392					table.decompile(data, self)
393				return table
394			else:
395				raise KeyError, "'%s' table not found" % tag
396
397	def __setitem__(self, tag, table):
398		self.tables[tag] = table
399
400	def __delitem__(self, tag):
401		if not self.has_key(tag):
402			raise KeyError, "'%s' table not found" % tag
403		if self.tables.has_key(tag):
404			del self.tables[tag]
405		if self.reader and self.reader.has_key(tag):
406			del self.reader[tag]
407
408	def setGlyphOrder(self, glyphOrder):
409		self.glyphOrder = glyphOrder
410
411	def getGlyphOrder(self):
412		try:
413			return self.glyphOrder
414		except AttributeError:
415			pass
416		if self.has_key('CFF '):
417			cff = self['CFF ']
418			self.glyphOrder = cff.getGlyphOrder()
419		elif self.has_key('post'):
420			# TrueType font
421			glyphOrder = self['post'].getGlyphOrder()
422			if glyphOrder is None:
423				#
424				# No names found in the 'post' table.
425				# Try to create glyph names from the unicode cmap (if available)
426				# in combination with the Adobe Glyph List (AGL).
427				#
428				self._getGlyphNamesFromCmap()
429			else:
430				self.glyphOrder = glyphOrder
431		else:
432			self._getGlyphNamesFromCmap()
433		return self.glyphOrder
434
435	def _getGlyphNamesFromCmap(self):
436		#
437		# This is rather convoluted, but then again, it's an interesting problem:
438		# - we need to use the unicode values found in the cmap table to
439		#   build glyph names (eg. because there is only a minimal post table,
440		#   or none at all).
441		# - but the cmap parser also needs glyph names to work with...
442		# So here's what we do:
443		# - make up glyph names based on glyphID
444		# - load a temporary cmap table based on those names
445		# - extract the unicode values, build the "real" glyph names
446		# - unload the temporary cmap table
447		#
448		if self.isLoaded("cmap"):
449			# Bootstrapping: we're getting called by the cmap parser
450			# itself. This means self.tables['cmap'] contains a partially
451			# loaded cmap, making it impossible to get at a unicode
452			# subtable here. We remove the partially loaded cmap and
453			# restore it later.
454			# This only happens if the cmap table is loaded before any
455			# other table that does f.getGlyphOrder()  or f.getGlyphName().
456			cmapLoading = self.tables['cmap']
457			del self.tables['cmap']
458		else:
459			cmapLoading = None
460		# Make up glyph names based on glyphID, which will be used by the
461		# temporary cmap and by the real cmap in case we don't find a unicode
462		# cmap.
463		numGlyphs = int(self['maxp'].numGlyphs)
464		glyphOrder = [None] * numGlyphs
465		glyphOrder[0] = ".notdef"
466		for i in range(1, numGlyphs):
467			glyphOrder[i] = "glyph%.5d" % i
468		# Set the glyph order, so the cmap parser has something
469		# to work with (so we don't get called recursively).
470		self.glyphOrder = glyphOrder
471		# Get a (new) temporary cmap (based on the just invented names)
472		tempcmap = self['cmap'].getcmap(3, 1)
473		if tempcmap is not None:
474			# we have a unicode cmap
475			from fontTools import agl
476			cmap = tempcmap.cmap
477			# create a reverse cmap dict
478			reversecmap = {}
479			for unicode, name in cmap.items():
480				reversecmap[name] = unicode
481			allNames = {}
482			for i in range(numGlyphs):
483				tempName = glyphOrder[i]
484				if reversecmap.has_key(tempName):
485					unicode = reversecmap[tempName]
486					if agl.UV2AGL.has_key(unicode):
487						# get name from the Adobe Glyph List
488						glyphName = agl.UV2AGL[unicode]
489					else:
490						# create uni<CODE> name
491						glyphName = "uni" + string.upper(string.zfill(
492								hex(unicode)[2:], 4))
493					tempName = glyphName
494					n = 1
495					while allNames.has_key(tempName):
496						tempName = glyphName + "#" + `n`
497						n = n + 1
498					glyphOrder[i] = tempName
499					allNames[tempName] = 1
500			# Delete the temporary cmap table from the cache, so it can
501			# be parsed again with the right names.
502			del self.tables['cmap']
503		else:
504			pass # no unicode cmap available, stick with the invented names
505		self.glyphOrder = glyphOrder
506		if cmapLoading:
507			# restore partially loaded cmap, so it can continue loading
508			# using the proper names.
509			self.tables['cmap'] = cmapLoading
510
511	def getGlyphNames(self):
512		"""Get a list of glyph names, sorted alphabetically."""
513		glyphNames = self.getGlyphOrder()[:]
514		glyphNames.sort()
515		return glyphNames
516
517	def getGlyphNames2(self):
518		"""Get a list of glyph names, sorted alphabetically,
519		but not case sensitive.
520		"""
521		from fontTools.misc import textTools
522		return textTools.caselessSort(self.getGlyphOrder())
523
524	def getGlyphName(self, glyphID, requireReal=0):
525		try:
526			return self.getGlyphOrder()[glyphID]
527		except IndexError:
528			if requireReal or not self.allowVID:
529				# XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in
530				# the cmap table than there are glyphs. I don't think it's legal...
531				return "glyph%.5d" % glyphID
532			else:
533				# user intends virtual GID support
534				try:
535					glyphName = self.VIDDict[glyphID]
536				except KeyError:
537					glyphName  ="glyph%.5d" % glyphID
538					self.last_vid = min(glyphID, self.last_vid )
539					self.reverseVIDDict[glyphName] = glyphID
540					self.VIDDict[glyphID] = glyphName
541				return glyphName
542
543	def getGlyphID(self, glyphName, requireReal = 0):
544		if not hasattr(self, "_reverseGlyphOrderDict"):
545			self._buildReverseGlyphOrderDict()
546		glyphOrder = self.getGlyphOrder()
547		d = self._reverseGlyphOrderDict
548		if not d.has_key(glyphName):
549			if glyphName in glyphOrder:
550				self._buildReverseGlyphOrderDict()
551				return self.getGlyphID(glyphName)
552			else:
553				if requireReal or not self.allowVID:
554					raise KeyError, glyphName
555				else:
556					# user intends virtual GID support
557					try:
558						glyphID = self.reverseVIDDict[glyphName]
559					except KeyError:
560						# if name is in glyphXXX format, use the specified name.
561						if glyphName[:5] == "glyph":
562							try:
563								glyphID = int(glyphName[5:])
564							except (NameError, ValueError):
565								glyphID = None
566						if glyphID == None:
567							glyphID = self.last_vid -1
568							self.last_vid = glyphID
569						self.reverseVIDDict[glyphName] = glyphID
570						self.VIDDict[glyphID] = glyphName
571					return glyphID
572
573		glyphID = d[glyphName]
574		if glyphName <> glyphOrder[glyphID]:
575			self._buildReverseGlyphOrderDict()
576			return self.getGlyphID(glyphName)
577		return glyphID
578
579	def getReverseGlyphMap(self, rebuild=0):
580		if rebuild or not hasattr(self, "_reverseGlyphOrderDict"):
581			self._buildReverseGlyphOrderDict()
582		return self._reverseGlyphOrderDict
583
584	def _buildReverseGlyphOrderDict(self):
585		self._reverseGlyphOrderDict = d = {}
586		glyphOrder = self.getGlyphOrder()
587		for glyphID in range(len(glyphOrder)):
588			d[glyphOrder[glyphID]] = glyphID
589
590	def _writeTable(self, tag, writer, done):
591		"""Internal helper function for self.save(). Keeps track of
592		inter-table dependencies.
593		"""
594		if tag in done:
595			return
596		tableClass = getTableClass(tag)
597		for masterTable in tableClass.dependencies:
598			if masterTable not in done:
599				if self.has_key(masterTable):
600					self._writeTable(masterTable, writer, done)
601				else:
602					done.append(masterTable)
603		tabledata = self.getTableData(tag)
604		if self.verbose:
605			debugmsg("writing '%s' table to disk" % tag)
606		writer[tag] = tabledata
607		done.append(tag)
608
609	def getTableData(self, tag):
610		"""Returns raw table data, whether compiled or directly read from disk.
611		"""
612		if self.isLoaded(tag):
613			if self.verbose:
614				debugmsg("compiling '%s' table" % tag)
615			return self.tables[tag].compile(self)
616		elif self.reader and self.reader.has_key(tag):
617			if self.verbose:
618				debugmsg("Reading '%s' table from disk" % tag)
619			return self.reader[tag]
620		else:
621			raise KeyError, tag
622
623	def getGlyphSet(self, preferCFF=1):
624		"""Return a generic GlyphSet, which is a dict-like object
625		mapping glyph names to glyph objects. The returned glyph objects
626		have a .draw() method that supports the Pen protocol, and will
627		have an attribute named 'width', but only *after* the .draw() method
628		has been called.
629
630		If the font is CFF-based, the outlines will be taken from the 'CFF '
631		table. Otherwise the outlines will be taken from the 'glyf' table.
632		If the font contains both a 'CFF ' and a 'glyf' table, you can use
633		the 'preferCFF' argument to specify which one should be taken.
634		"""
635		if preferCFF and self.has_key("CFF "):
636			return self["CFF "].cff.values()[0].CharStrings
637		if self.has_key("glyf"):
638			return _TTGlyphSet(self)
639		if self.has_key("CFF "):
640			return self["CFF "].cff.values()[0].CharStrings
641		raise TTLibError, "Font contains no outlines"
642
643
644class _TTGlyphSet:
645
646	"""Generic dict-like GlyphSet class, meant as a TrueType counterpart
647	to CFF's CharString dict. See TTFont.getGlyphSet().
648	"""
649
650	# This class is distinct from the 'glyf' table itself because we need
651	# access to the 'hmtx' table, which could cause a dependency problem
652	# there when reading from XML.
653
654	def __init__(self, ttFont):
655		self._ttFont = ttFont
656
657	def keys(self):
658		return self._ttFont["glyf"].keys()
659
660	def has_key(self, glyphName):
661		return self._ttFont["glyf"].has_key(glyphName)
662
663	__contains__ = has_key
664
665	def __getitem__(self, glyphName):
666		return _TTGlyph(glyphName, self._ttFont)
667
668	def get(self, glyphName, default=None):
669		try:
670			return self[glyphName]
671		except KeyError:
672			return default
673
674
675class _TTGlyph:
676
677	"""Wrapper for a TrueType glyph that supports the Pen protocol, meaning
678	that it has a .draw() method that takes a pen object as its only
679	argument. Additionally there is a 'width' attribute.
680	"""
681
682	def __init__(self, glyphName, ttFont):
683		self._glyphName = glyphName
684		self._ttFont = ttFont
685		self.width, self.lsb = self._ttFont['hmtx'][self._glyphName]
686
687	def draw(self, pen):
688		"""Draw the glyph onto Pen. See fontTools.pens.basePen for details
689		how that works.
690		"""
691		glyfTable = self._ttFont['glyf']
692		glyph = glyfTable[self._glyphName]
693		if hasattr(glyph, "xMin"):
694			offset = self.lsb - glyph.xMin
695		else:
696			offset = 0
697		if glyph.isComposite():
698			for component in glyph:
699				glyphName, transform = component.getComponentInfo()
700				pen.addComponent(glyphName, transform)
701		else:
702			coordinates, endPts, flags = glyph.getCoordinates(glyfTable)
703			if offset:
704				coordinates = coordinates + (offset, 0)
705			start = 0
706			for end in endPts:
707				end = end + 1
708				contour = coordinates[start:end].tolist()
709				cFlags = flags[start:end].tolist()
710				start = end
711				if 1 not in cFlags:
712					# There is not a single on-curve point on the curve,
713					# use pen.qCurveTo's special case by specifying None
714					# as the on-curve point.
715					contour.append(None)
716					pen.qCurveTo(*contour)
717				else:
718					# Shuffle the points so that contour the is guaranteed
719					# to *end* in an on-curve point, which we'll use for
720					# the moveTo.
721					firstOnCurve = cFlags.index(1) + 1
722					contour = contour[firstOnCurve:] + contour[:firstOnCurve]
723					cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
724					pen.moveTo(contour[-1])
725					while contour:
726						nextOnCurve = cFlags.index(1) + 1
727						if nextOnCurve == 1:
728							pen.lineTo(contour[0])
729						else:
730							pen.qCurveTo(*contour[:nextOnCurve])
731						contour = contour[nextOnCurve:]
732						cFlags = cFlags[nextOnCurve:]
733				pen.closePath()
734
735
736class GlyphOrder:
737
738	"""A pseudo table. The glyph order isn't in the font as a separate
739	table, but it's nice to present it as such in the TTX format.
740	"""
741
742	def __init__(self, ttFont):
743		self.glyphOrder = ttFont.getGlyphOrder()
744
745	def __len__(self):
746		return len(self.glyphOrder)
747
748	def __getitem__(self, i):
749		return self.glyphOrder[i]
750
751	def toXML(self, writer, ttFont):
752		writer.comment("The 'id' attribute is only for humans; "
753				"it is ignored when parsed.")
754		writer.newline()
755		for i in range(len(self.glyphOrder)):
756			glyphName = self.glyphOrder[i]
757			writer.simpletag("GlyphID", id=i, name=glyphName)
758			writer.newline()
759
760	def fromXML(self, (name, attrs, content), ttFont):
761		if not hasattr(self, "glyphOrder"):
762			self.glyphOrder = []
763			ttFont.setGlyphOrder(self.glyphOrder)
764		if name == "GlyphID":
765			self.glyphOrder.append(attrs["name"])
766
767
768def getTableModule(tag):
769	"""Fetch the packer/unpacker module for a table.
770	Return None when no module is found.
771	"""
772	import tables
773	pyTag = tagToIdentifier(tag)
774	try:
775		__import__("fontTools.ttLib.tables." + pyTag)
776	except ImportError, err:
777		# If pyTag is found in the ImportError message,
778		# means table is not implemented.  If it's not
779		# there, then some other module is missing, don't
780		# suppress the error.
781		if str(err).find(pyTag) >= 0:
782			return None
783		else:
784			raise err
785	else:
786		return getattr(tables, pyTag)
787
788
789def getTableClass(tag):
790	"""Fetch the packer/unpacker class for a table.
791	Return None when no class is found.
792	"""
793	module = getTableModule(tag)
794	if module is None:
795		from tables.DefaultTable import DefaultTable
796		return DefaultTable
797	pyTag = tagToIdentifier(tag)
798	tableClass = getattr(module, "table_" + pyTag)
799	return tableClass
800
801
802def newTable(tag):
803	"""Return a new instance of a table."""
804	tableClass = getTableClass(tag)
805	return tableClass(tag)
806
807
808def _escapechar(c):
809	"""Helper function for tagToIdentifier()"""
810	import re
811	if re.match("[a-z0-9]", c):
812		return "_" + c
813	elif re.match("[A-Z]", c):
814		return c + "_"
815	else:
816		return hex(ord(c))[2:]
817
818
819def tagToIdentifier(tag):
820	"""Convert a table tag to a valid (but UGLY) python identifier,
821	as well as a filename that's guaranteed to be unique even on a
822	caseless file system. Each character is mapped to two characters.
823	Lowercase letters get an underscore before the letter, uppercase
824	letters get an underscore after the letter. Trailing spaces are
825	trimmed. Illegal characters are escaped as two hex bytes. If the
826	result starts with a number (as the result of a hex escape), an
827	extra underscore is prepended. Examples:
828		'glyf' -> '_g_l_y_f'
829		'cvt ' -> '_c_v_t'
830		'OS/2' -> 'O_S_2f_2'
831	"""
832	import re
833	if tag == "GlyphOrder":
834		return tag
835	assert len(tag) == 4, "tag should be 4 characters long"
836	while len(tag) > 1 and tag[-1] == ' ':
837		tag = tag[:-1]
838	ident = ""
839	for c in tag:
840		ident = ident + _escapechar(c)
841	if re.match("[0-9]", ident):
842		ident = "_" + ident
843	return ident
844
845
846def identifierToTag(ident):
847	"""the opposite of tagToIdentifier()"""
848	if ident == "GlyphOrder":
849		return ident
850	if len(ident) % 2 and ident[0] == "_":
851		ident = ident[1:]
852	assert not (len(ident) % 2)
853	tag = ""
854	for i in range(0, len(ident), 2):
855		if ident[i] == "_":
856			tag = tag + ident[i+1]
857		elif ident[i+1] == "_":
858			tag = tag + ident[i]
859		else:
860			# assume hex
861			tag = tag + chr(int(ident[i:i+2], 16))
862	# append trailing spaces
863	tag = tag + (4 - len(tag)) * ' '
864	return tag
865
866
867def tagToXML(tag):
868	"""Similarly to tagToIdentifier(), this converts a TT tag
869	to a valid XML element name. Since XML element names are
870	case sensitive, this is a fairly simple/readable translation.
871	"""
872	import re
873	if tag == "OS/2":
874		return "OS_2"
875	elif tag == "GlyphOrder":
876		return tag
877	if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
878		return string.strip(tag)
879	else:
880		return tagToIdentifier(tag)
881
882
883def xmlToTag(tag):
884	"""The opposite of tagToXML()"""
885	if tag == "OS_2":
886		return "OS/2"
887	if len(tag) == 8:
888		return identifierToTag(tag)
889	else:
890		return tag + " " * (4 - len(tag))
891	return tag
892
893
894def debugmsg(msg):
895	import time
896	print msg + time.strftime("  (%H:%M:%S)", time.localtime(time.time()))
897
898
899# Table order as recommended in the OpenType specification 1.4
900TTFTableOrder = ["head", "hhea", "maxp", "OS/2", "hmtx", "LTSH", "VDMX",
901                  "hdmx", "cmap", "fpgm", "prep", "cvt ", "loca", "glyf",
902                  "kern", "name", "post", "gasp", "PCLT"]
903
904OTFTableOrder = ["head", "hhea", "maxp", "OS/2", "name", "cmap", "post",
905                  "CFF "]
906
907def sortedTagList(tagList, tableOrder=None):
908	"""Return a sorted copy of tagList, sorted according to the OpenType
909	specification, or according to a custom tableOrder. If given and not
910	None, tableOrder needs to be a list of tag names.
911	"""
912	tagList = list(tagList)
913	tagList.sort()
914	if tableOrder is None:
915		if "DSIG" in tagList:
916			# DSIG should be last (XXX spec reference?)
917			tagList.remove("DSIG")
918			tagList.append("DSIG")
919		if "CFF " in tagList:
920			tableOrder = OTFTableOrder
921		else:
922			tableOrder = TTFTableOrder
923	orderedTables = []
924	for tag in tableOrder:
925		if tag in tagList:
926			orderedTables.append(tag)
927			tagList.remove(tag)
928	orderedTables.extend(tagList)
929	return orderedTables
930
931
932def reorderFontTables(inFile, outFile, tableOrder=None, checkChecksums=0):
933	"""Rewrite a font file, ordering the tables as recommended by the
934	OpenType specification 1.4.
935	"""
936	from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
937	reader = SFNTReader(inFile, checkChecksums=checkChecksums)
938	writer = SFNTWriter(outFile, reader.numTables, reader.sfntVersion, reader.flavor, reader.flavorData)
939	tables = reader.keys()
940	for tag in sortedTagList(tables, tableOrder):
941		writer[tag] = reader[tag]
942	writer.close()
943