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