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