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