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