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