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