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