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