• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
2
3from collections import namedtuple
4from fontTools.misc import sstruct
5from fontTools import ttLib
6from fontTools import version
7from fontTools.misc.textTools import tostr, safeEval, pad
8from fontTools.misc.arrayTools import calcIntBounds, pointInRect
9from fontTools.misc.bezierTools import calcQuadraticBounds
10from fontTools.misc.fixedTools import (
11	fixedToFloat as fi2fl,
12	floatToFixed as fl2fi,
13	floatToFixedToStr as fl2str,
14	strToFixedToFloat as str2fl,
15	otRound,
16)
17from numbers import Number
18from . import DefaultTable
19from . import ttProgram
20import sys
21import struct
22import array
23import logging
24import os
25from fontTools.misc import xmlWriter
26from fontTools.misc.filenames import userNameToFileName
27from fontTools.misc.loggingTools import deprecateFunction
28
29log = logging.getLogger(__name__)
30
31# We compute the version the same as is computed in ttlib/__init__
32# so that we can write 'ttLibVersion' attribute of the glyf TTX files
33# when glyf is written to separate files.
34version = ".".join(version.split('.')[:2])
35
36#
37# The Apple and MS rasterizers behave differently for
38# scaled composite components: one does scale first and then translate
39# and the other does it vice versa. MS defined some flags to indicate
40# the difference, but it seems nobody actually _sets_ those flags.
41#
42# Funny thing: Apple seems to _only_ do their thing in the
43# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
44# (eg. Charcoal)...
45#
46SCALE_COMPONENT_OFFSET_DEFAULT = 0   # 0 == MS, 1 == Apple
47
48
49class table__g_l_y_f(DefaultTable.DefaultTable):
50	"""Glyph Data Table
51
52	This class represents the `glyf <https://docs.microsoft.com/en-us/typography/opentype/spec/glyf>`_
53 	table, which contains outlines for glyphs in TrueType format. In many cases,
54 	it is easier to access and manipulate glyph outlines through the ``GlyphSet``
55 	object returned from :py:meth:`fontTools.ttLib.ttFont.getGlyphSet`::
56
57 			>> from fontTools.pens.boundsPen import BoundsPen
58 			>> glyphset = font.getGlyphSet()
59			>> bp = BoundsPen(glyphset)
60			>> glyphset["A"].draw(bp)
61			>> bp.bounds
62			(19, 0, 633, 716)
63
64	However, this class can be used for low-level access to the ``glyf`` table data.
65	Objects of this class support dictionary-like access, mapping glyph names to
66	:py:class:`Glyph` objects::
67
68			>> glyf = font["glyf"]
69			>> len(glyf["Aacute"].components)
70			2
71
72	Note that when adding glyphs to the font via low-level access to the ``glyf``
73	table, the new glyphs must also be added to the ``hmtx``/``vmtx`` table::
74
75			>> font["glyf"]["divisionslash"] = Glyph()
76			>> font["hmtx"]["divisionslash"] = (640, 0)
77
78	"""
79
80	# this attribute controls the amount of padding applied to glyph data upon compile.
81	# Glyph lenghts are aligned to multiples of the specified value.
82	# Allowed values are (0, 1, 2, 4). '0' means no padding; '1' (default) also means
83	# no padding, except for when padding would allow to use short loca offsets.
84	padding = 1
85
86	def decompile(self, data, ttFont):
87		loca = ttFont['loca']
88		pos = int(loca[0])
89		nextPos = 0
90		noname = 0
91		self.glyphs = {}
92		self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
93		for i in range(0, len(loca)-1):
94			try:
95				glyphName = glyphOrder[i]
96			except IndexError:
97				noname = noname + 1
98				glyphName = 'ttxautoglyph%s' % i
99			nextPos = int(loca[i+1])
100			glyphdata = data[pos:nextPos]
101			if len(glyphdata) != (nextPos - pos):
102				raise ttLib.TTLibError("not enough 'glyf' table data")
103			glyph = Glyph(glyphdata)
104			self.glyphs[glyphName] = glyph
105			pos = nextPos
106		if len(data) - nextPos >= 4:
107			log.warning(
108				"too much 'glyf' table data: expected %d, received %d bytes",
109				nextPos, len(data))
110		if noname:
111			log.warning('%s glyphs have no name', noname)
112		if ttFont.lazy is False: # Be lazy for None and True
113			self.ensureDecompiled()
114
115	def ensureDecompiled(self, recurse=False):
116		# The recurse argument is unused, but part of the signature of
117		# ensureDecompiled across the library.
118		for glyph in self.glyphs.values():
119			glyph.expand(self)
120
121	def compile(self, ttFont):
122		if not hasattr(self, "glyphOrder"):
123			self.glyphOrder = ttFont.getGlyphOrder()
124		padding = self.padding
125		assert padding in (0, 1, 2, 4)
126		locations = []
127		currentLocation = 0
128		dataList = []
129		recalcBBoxes = ttFont.recalcBBoxes
130		for glyphName in self.glyphOrder:
131			glyph = self.glyphs[glyphName]
132			glyphData = glyph.compile(self, recalcBBoxes)
133			if padding > 1:
134				glyphData = pad(glyphData, size=padding)
135			locations.append(currentLocation)
136			currentLocation = currentLocation + len(glyphData)
137			dataList.append(glyphData)
138		locations.append(currentLocation)
139
140		if padding == 1 and currentLocation < 0x20000:
141			# See if we can pad any odd-lengthed glyphs to allow loca
142			# table to use the short offsets.
143			indices = [i for i,glyphData in enumerate(dataList) if len(glyphData) % 2 == 1]
144			if indices and currentLocation + len(indices) < 0x20000:
145				# It fits.  Do it.
146				for i in indices:
147					dataList[i] += b'\0'
148				currentLocation = 0
149				for i,glyphData in enumerate(dataList):
150					locations[i] = currentLocation
151					currentLocation += len(glyphData)
152				locations[len(dataList)] = currentLocation
153
154		data = b''.join(dataList)
155		if 'loca' in ttFont:
156			ttFont['loca'].set(locations)
157		if 'maxp' in ttFont:
158			ttFont['maxp'].numGlyphs = len(self.glyphs)
159		if not data:
160		# As a special case when all glyph in the font are empty, add a zero byte
161		# to the table, so that OTS doesn’t reject it, and to make the table work
162		# on Windows as well.
163		# See https://github.com/khaledhosny/ots/issues/52
164			data = b"\0"
165		return data
166
167	def toXML(self, writer, ttFont, splitGlyphs=False):
168		notice = (
169			"The xMin, yMin, xMax and yMax values\n"
170			"will be recalculated by the compiler.")
171		glyphNames = ttFont.getGlyphNames()
172		if not splitGlyphs:
173			writer.newline()
174			writer.comment(notice)
175			writer.newline()
176			writer.newline()
177		numGlyphs = len(glyphNames)
178		if splitGlyphs:
179			path, ext = os.path.splitext(writer.file.name)
180			existingGlyphFiles = set()
181		for glyphName in glyphNames:
182			glyph = self.get(glyphName)
183			if glyph is None:
184				log.warning("glyph '%s' does not exist in glyf table", glyphName)
185				continue
186			if glyph.numberOfContours:
187				if splitGlyphs:
188					glyphPath = userNameToFileName(
189						tostr(glyphName, 'utf-8'),
190						existingGlyphFiles,
191						prefix=path + ".",
192						suffix=ext)
193					existingGlyphFiles.add(glyphPath.lower())
194					glyphWriter = xmlWriter.XMLWriter(
195						glyphPath, idlefunc=writer.idlefunc,
196						newlinestr=writer.newlinestr)
197					glyphWriter.begintag("ttFont", ttLibVersion=version)
198					glyphWriter.newline()
199					glyphWriter.begintag("glyf")
200					glyphWriter.newline()
201					glyphWriter.comment(notice)
202					glyphWriter.newline()
203					writer.simpletag("TTGlyph", src=os.path.basename(glyphPath))
204				else:
205					glyphWriter = writer
206				glyphWriter.begintag('TTGlyph', [
207							("name", glyphName),
208							("xMin", glyph.xMin),
209							("yMin", glyph.yMin),
210							("xMax", glyph.xMax),
211							("yMax", glyph.yMax),
212							])
213				glyphWriter.newline()
214				glyph.toXML(glyphWriter, ttFont)
215				glyphWriter.endtag('TTGlyph')
216				glyphWriter.newline()
217				if splitGlyphs:
218					glyphWriter.endtag("glyf")
219					glyphWriter.newline()
220					glyphWriter.endtag("ttFont")
221					glyphWriter.newline()
222					glyphWriter.close()
223			else:
224				writer.simpletag('TTGlyph', name=glyphName)
225				writer.comment("contains no outline data")
226				if not splitGlyphs:
227					writer.newline()
228			writer.newline()
229
230	def fromXML(self, name, attrs, content, ttFont):
231		if name != "TTGlyph":
232			return
233		if not hasattr(self, "glyphs"):
234			self.glyphs = {}
235		if not hasattr(self, "glyphOrder"):
236			self.glyphOrder = ttFont.getGlyphOrder()
237		glyphName = attrs["name"]
238		log.debug("unpacking glyph '%s'", glyphName)
239		glyph = Glyph()
240		for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
241			setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
242		self.glyphs[glyphName] = glyph
243		for element in content:
244			if not isinstance(element, tuple):
245				continue
246			name, attrs, content = element
247			glyph.fromXML(name, attrs, content, ttFont)
248		if not ttFont.recalcBBoxes:
249			glyph.compact(self, 0)
250
251	def setGlyphOrder(self, glyphOrder):
252		"""Sets the glyph order
253
254		Args:
255			glyphOrder ([str]): List of glyph names in order.
256		"""
257		self.glyphOrder = glyphOrder
258
259	def getGlyphName(self, glyphID):
260		"""Returns the name for the glyph with the given ID.
261
262		Raises a ``KeyError`` if the glyph name is not found in the font.
263		"""
264		return self.glyphOrder[glyphID]
265
266	def getGlyphID(self, glyphName):
267		"""Returns the ID of the glyph with the given name.
268
269		Raises a ``ValueError`` if the glyph is not found in the font.
270		"""
271		# XXX optimize with reverse dict!!!
272		return self.glyphOrder.index(glyphName)
273
274	def removeHinting(self):
275		"""Removes TrueType hints from all glyphs in the glyphset.
276
277		See :py:meth:`Glyph.removeHinting`.
278		"""
279		for glyph in self.glyphs.values():
280			glyph.removeHinting()
281
282	def keys(self):
283		return self.glyphs.keys()
284
285	def has_key(self, glyphName):
286		return glyphName in self.glyphs
287
288	__contains__ = has_key
289
290	def get(self, glyphName, default=None):
291		glyph = self.glyphs.get(glyphName, default)
292		if glyph is not None:
293			glyph.expand(self)
294		return glyph
295
296	def __getitem__(self, glyphName):
297		glyph = self.glyphs[glyphName]
298		glyph.expand(self)
299		return glyph
300
301	def __setitem__(self, glyphName, glyph):
302		self.glyphs[glyphName] = glyph
303		if glyphName not in self.glyphOrder:
304			self.glyphOrder.append(glyphName)
305
306	def __delitem__(self, glyphName):
307		del self.glyphs[glyphName]
308		self.glyphOrder.remove(glyphName)
309
310	def __len__(self):
311		assert len(self.glyphOrder) == len(self.glyphs)
312		return len(self.glyphs)
313
314	def _getPhantomPoints(self, glyphName, hMetrics, vMetrics=None):
315		"""Compute the four "phantom points" for the given glyph from its bounding box
316		and the horizontal and vertical advance widths and sidebearings stored in the
317		ttFont's "hmtx" and "vmtx" tables.
318
319		'hMetrics' should be ttFont['hmtx'].metrics.
320
321		'vMetrics' should be ttFont['vmtx'].metrics if there is "vmtx" or None otherwise.
322		If there is no vMetrics passed in, vertical phantom points are set to the zero coordinate.
323
324		https://docs.microsoft.com/en-us/typography/opentype/spec/tt_instructing_glyphs#phantoms
325		"""
326		glyph = self[glyphName]
327		if not hasattr(glyph, 'xMin'):
328			glyph.recalcBounds(self)
329
330		horizontalAdvanceWidth, leftSideBearing = hMetrics[glyphName]
331		leftSideX = glyph.xMin - leftSideBearing
332		rightSideX = leftSideX + horizontalAdvanceWidth
333
334		if vMetrics:
335			verticalAdvanceWidth, topSideBearing = vMetrics[glyphName]
336			topSideY = topSideBearing + glyph.yMax
337			bottomSideY = topSideY - verticalAdvanceWidth
338		else:
339			bottomSideY = topSideY = 0
340
341		return [
342			(leftSideX, 0),
343			(rightSideX, 0),
344			(0, topSideY),
345			(0, bottomSideY),
346		]
347
348	def _getCoordinatesAndControls(self, glyphName, hMetrics, vMetrics=None):
349		"""Return glyph coordinates and controls as expected by "gvar" table.
350
351		The coordinates includes four "phantom points" for the glyph metrics,
352		as mandated by the "gvar" spec.
353
354		The glyph controls is a namedtuple with the following attributes:
355			- numberOfContours: -1 for composite glyphs.
356			- endPts: list of indices of end points for each contour in simple
357			glyphs, or component indices in composite glyphs (used for IUP
358			optimization).
359			- flags: array of contour point flags for simple glyphs (None for
360			composite glyphs).
361			- components: list of base glyph names (str) for each component in
362			composite glyphs (None for simple glyphs).
363
364		The "hMetrics" and vMetrics are used to compute the "phantom points" (see
365		the "_getPhantomPoints" method).
366
367		Return None if the requested glyphName is not present.
368		"""
369		glyph = self.get(glyphName)
370		if glyph is None:
371			return None
372		if glyph.isComposite():
373			coords = GlyphCoordinates(
374				[(getattr(c, 'x', 0), getattr(c, 'y', 0)) for c in glyph.components]
375			)
376			controls = _GlyphControls(
377				numberOfContours=glyph.numberOfContours,
378				endPts=list(range(len(glyph.components))),
379				flags=None,
380				components=[c.glyphName for c in glyph.components],
381			)
382		else:
383			coords, endPts, flags = glyph.getCoordinates(self)
384			coords = coords.copy()
385			controls = _GlyphControls(
386				numberOfContours=glyph.numberOfContours,
387				endPts=endPts,
388				flags=flags,
389				components=None,
390			)
391		# Add phantom points for (left, right, top, bottom) positions.
392		phantomPoints = self._getPhantomPoints(glyphName, hMetrics, vMetrics)
393		coords.extend(phantomPoints)
394		return coords, controls
395
396	def _setCoordinates(self, glyphName, coord, hMetrics, vMetrics=None):
397		"""Set coordinates and metrics for the given glyph.
398
399		"coord" is an array of GlyphCoordinates which must include the "phantom
400		points" as the last four coordinates.
401
402		Both the horizontal/vertical advances and left/top sidebearings in "hmtx"
403		and "vmtx" tables (if any) are updated from four phantom points and
404		the glyph's bounding boxes.
405
406		The "hMetrics" and vMetrics are used to propagate "phantom points"
407		into "hmtx" and "vmtx" tables if desired.  (see the "_getPhantomPoints"
408		method).
409		"""
410		glyph = self[glyphName]
411
412		# Handle phantom points for (left, right, top, bottom) positions.
413		assert len(coord) >= 4
414		leftSideX = coord[-4][0]
415		rightSideX = coord[-3][0]
416		topSideY = coord[-2][1]
417		bottomSideY = coord[-1][1]
418
419		coord = coord[:-4]
420
421		if glyph.isComposite():
422			assert len(coord) == len(glyph.components)
423			for p, comp in zip(coord, glyph.components):
424				if hasattr(comp, 'x'):
425					comp.x, comp.y = p
426		elif glyph.numberOfContours == 0:
427			assert len(coord) == 0
428		else:
429			assert len(coord) == len(glyph.coordinates)
430			glyph.coordinates = GlyphCoordinates(coord)
431
432		glyph.recalcBounds(self)
433
434		horizontalAdvanceWidth = otRound(rightSideX - leftSideX)
435		if horizontalAdvanceWidth < 0:
436			# unlikely, but it can happen, see:
437			# https://github.com/fonttools/fonttools/pull/1198
438			horizontalAdvanceWidth = 0
439		leftSideBearing = otRound(glyph.xMin - leftSideX)
440		hMetrics[glyphName] = horizontalAdvanceWidth, leftSideBearing
441
442		if vMetrics is not None:
443			verticalAdvanceWidth = otRound(topSideY - bottomSideY)
444			if verticalAdvanceWidth < 0:  # unlikely but do the same as horizontal
445				verticalAdvanceWidth = 0
446			topSideBearing = otRound(topSideY - glyph.yMax)
447			vMetrics[glyphName] = verticalAdvanceWidth, topSideBearing
448
449
450	# Deprecated
451
452	def _synthesizeVMetrics(self, glyphName, ttFont, defaultVerticalOrigin):
453		"""This method is wrong and deprecated.
454		For rationale see:
455		https://github.com/fonttools/fonttools/pull/2266/files#r613569473
456		"""
457		vMetrics = getattr(ttFont.get('vmtx'), 'metrics', None)
458		if vMetrics is None:
459			verticalAdvanceWidth = ttFont["head"].unitsPerEm
460			topSideY = getattr(ttFont.get('hhea'), 'ascent', None)
461			if topSideY is None:
462				if defaultVerticalOrigin is not None:
463					topSideY = defaultVerticalOrigin
464				else:
465					topSideY = verticalAdvanceWidth
466			glyph = self[glyphName]
467			glyph.recalcBounds(self)
468			topSideBearing = otRound(topSideY - glyph.yMax)
469			vMetrics = {glyphName: (verticalAdvanceWidth, topSideBearing)}
470		return vMetrics
471
472	@deprecateFunction("use '_getPhantomPoints' instead", category=DeprecationWarning)
473	def getPhantomPoints(self, glyphName, ttFont, defaultVerticalOrigin=None):
474		"""Old public name for self._getPhantomPoints().
475		See: https://github.com/fonttools/fonttools/pull/2266"""
476		hMetrics = ttFont['hmtx'].metrics
477		vMetrics = self._synthesizeVMetrics(glyphName, ttFont, defaultVerticalOrigin)
478		return self._getPhantomPoints(glyphName, hMetrics, vMetrics)
479
480	@deprecateFunction("use '_getCoordinatesAndControls' instead", category=DeprecationWarning)
481	def getCoordinatesAndControls(self, glyphName, ttFont, defaultVerticalOrigin=None):
482		"""Old public name for self._getCoordinatesAndControls().
483		See: https://github.com/fonttools/fonttools/pull/2266"""
484		hMetrics = ttFont['hmtx'].metrics
485		vMetrics = self._synthesizeVMetrics(glyphName, ttFont, defaultVerticalOrigin)
486		return self._getCoordinatesAndControls(glyphName, hMetrics, vMetrics)
487
488	@deprecateFunction("use '_setCoordinates' instead", category=DeprecationWarning)
489	def setCoordinates(self, glyphName, ttFont):
490		"""Old public name for self._setCoordinates().
491		See: https://github.com/fonttools/fonttools/pull/2266"""
492		hMetrics = ttFont['hmtx'].metrics
493		vMetrics = getattr(ttFont.get('vmtx'), 'metrics', None)
494		self._setCoordinates(glyphName, hMetrics, vMetrics)
495
496
497_GlyphControls = namedtuple(
498	"_GlyphControls", "numberOfContours endPts flags components"
499)
500
501
502glyphHeaderFormat = """
503		>	# big endian
504		numberOfContours:	h
505		xMin:				h
506		yMin:				h
507		xMax:				h
508		yMax:				h
509"""
510
511# flags
512flagOnCurve = 0x01
513flagXShort = 0x02
514flagYShort = 0x04
515flagRepeat = 0x08
516flagXsame =  0x10
517flagYsame = 0x20
518flagOverlapSimple = 0x40
519flagReserved = 0x80
520
521# These flags are kept for XML output after decompiling the coordinates
522keepFlags = flagOnCurve + flagOverlapSimple
523
524_flagSignBytes = {
525	0: 2,
526	flagXsame: 0,
527	flagXShort|flagXsame: +1,
528	flagXShort: -1,
529	flagYsame: 0,
530	flagYShort|flagYsame: +1,
531	flagYShort: -1,
532}
533
534def flagBest(x, y, onCurve):
535	"""For a given x,y delta pair, returns the flag that packs this pair
536	most efficiently, as well as the number of byte cost of such flag."""
537
538	flag = flagOnCurve if onCurve else 0
539	cost = 0
540	# do x
541	if x == 0:
542		flag = flag | flagXsame
543	elif -255 <= x <= 255:
544		flag = flag | flagXShort
545		if x > 0:
546			flag = flag | flagXsame
547		cost += 1
548	else:
549		cost += 2
550	# do y
551	if y == 0:
552		flag = flag | flagYsame
553	elif -255 <= y <= 255:
554		flag = flag | flagYShort
555		if y > 0:
556			flag = flag | flagYsame
557		cost += 1
558	else:
559		cost += 2
560	return flag, cost
561
562def flagFits(newFlag, oldFlag, mask):
563	newBytes = _flagSignBytes[newFlag & mask]
564	oldBytes = _flagSignBytes[oldFlag & mask]
565	return newBytes == oldBytes or abs(newBytes) > abs(oldBytes)
566
567def flagSupports(newFlag, oldFlag):
568	return ((oldFlag & flagOnCurve) == (newFlag & flagOnCurve) and
569		flagFits(newFlag, oldFlag, flagXsame|flagXShort) and
570		flagFits(newFlag, oldFlag, flagYsame|flagYShort))
571
572def flagEncodeCoord(flag, mask, coord, coordBytes):
573	byteCount = _flagSignBytes[flag & mask]
574	if byteCount == 1:
575		coordBytes.append(coord)
576	elif byteCount == -1:
577		coordBytes.append(-coord)
578	elif byteCount == 2:
579		coordBytes.extend(struct.pack('>h', coord))
580
581def flagEncodeCoords(flag, x, y, xBytes, yBytes):
582	flagEncodeCoord(flag, flagXsame|flagXShort, x, xBytes)
583	flagEncodeCoord(flag, flagYsame|flagYShort, y, yBytes)
584
585
586ARG_1_AND_2_ARE_WORDS		= 0x0001  # if set args are words otherwise they are bytes
587ARGS_ARE_XY_VALUES		= 0x0002  # if set args are xy values, otherwise they are points
588ROUND_XY_TO_GRID		= 0x0004  # for the xy values if above is true
589WE_HAVE_A_SCALE			= 0x0008  # Sx = Sy, otherwise scale == 1.0
590NON_OVERLAPPING			= 0x0010  # set to same value for all components (obsolete!)
591MORE_COMPONENTS			= 0x0020  # indicates at least one more glyph after this one
592WE_HAVE_AN_X_AND_Y_SCALE	= 0x0040  # Sx, Sy
593WE_HAVE_A_TWO_BY_TWO		= 0x0080  # t00, t01, t10, t11
594WE_HAVE_INSTRUCTIONS		= 0x0100  # instructions follow
595USE_MY_METRICS			= 0x0200  # apply these metrics to parent glyph
596OVERLAP_COMPOUND		= 0x0400  # used by Apple in GX fonts
597SCALED_COMPONENT_OFFSET		= 0x0800  # composite designed to have the component offset scaled (designed for Apple)
598UNSCALED_COMPONENT_OFFSET	= 0x1000  # composite designed not to have the component offset scaled (designed for MS)
599
600
601CompositeMaxpValues = namedtuple('CompositeMaxpValues', ['nPoints', 'nContours', 'maxComponentDepth'])
602
603
604class Glyph(object):
605	"""This class represents an individual TrueType glyph.
606
607	TrueType glyph objects come in two flavours: simple and composite. Simple
608	glyph objects contain contours, represented via the ``.coordinates``,
609	``.flags``, ``.numberOfContours``, and ``.endPtsOfContours`` attributes;
610	composite glyphs contain components, available through the ``.components``
611	attributes.
612
613	Because the ``.coordinates`` attribute (and other simple glyph attributes mentioned
614	above) is only set on simple glyphs and the ``.components`` attribute is only
615	set on composite glyphs, it is necessary to use the :py:meth:`isComposite`
616	method to test whether a glyph is simple or composite before attempting to
617	access its data.
618
619	For a composite glyph, the components can also be accessed via array-like access::
620
621		>> assert(font["glyf"]["Aacute"].isComposite())
622		>> font["glyf"]["Aacute"][0]
623		<fontTools.ttLib.tables._g_l_y_f.GlyphComponent at 0x1027b2ee0>
624
625	"""
626
627	def __init__(self, data=b""):
628		if not data:
629			# empty char
630			self.numberOfContours = 0
631			return
632		self.data = data
633
634	def compact(self, glyfTable, recalcBBoxes=True):
635		data = self.compile(glyfTable, recalcBBoxes)
636		self.__dict__.clear()
637		self.data = data
638
639	def expand(self, glyfTable):
640		if not hasattr(self, "data"):
641			# already unpacked
642			return
643		if not self.data:
644			# empty char
645			del self.data
646			self.numberOfContours = 0
647			return
648		dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
649		del self.data
650		# Some fonts (eg. Neirizi.ttf) have a 0 for numberOfContours in
651		# some glyphs; decompileCoordinates assumes that there's at least
652		# one, so short-circuit here.
653		if self.numberOfContours == 0:
654			return
655		if self.isComposite():
656			self.decompileComponents(data, glyfTable)
657		else:
658			self.decompileCoordinates(data)
659
660	def compile(self, glyfTable, recalcBBoxes=True):
661		if hasattr(self, "data"):
662			if recalcBBoxes:
663				# must unpack glyph in order to recalculate bounding box
664				self.expand(glyfTable)
665			else:
666				return self.data
667		if self.numberOfContours == 0:
668			return b''
669		if recalcBBoxes:
670			self.recalcBounds(glyfTable)
671		data = sstruct.pack(glyphHeaderFormat, self)
672		if self.isComposite():
673			data = data + self.compileComponents(glyfTable)
674		else:
675			data = data + self.compileCoordinates()
676		return data
677
678	def toXML(self, writer, ttFont):
679		if self.isComposite():
680			for compo in self.components:
681				compo.toXML(writer, ttFont)
682			haveInstructions = hasattr(self, "program")
683		else:
684			last = 0
685			for i in range(self.numberOfContours):
686				writer.begintag("contour")
687				writer.newline()
688				for j in range(last, self.endPtsOfContours[i] + 1):
689					attrs = [
690							("x", self.coordinates[j][0]),
691							("y", self.coordinates[j][1]),
692							("on", self.flags[j] & flagOnCurve),
693						]
694					if self.flags[j] & flagOverlapSimple:
695						# Apple's rasterizer uses flagOverlapSimple in the first contour/first pt to flag glyphs that contain overlapping contours
696						attrs.append(("overlap", 1))
697					writer.simpletag("pt", attrs)
698					writer.newline()
699				last = self.endPtsOfContours[i] + 1
700				writer.endtag("contour")
701				writer.newline()
702			haveInstructions = self.numberOfContours > 0
703		if haveInstructions:
704			if self.program:
705				writer.begintag("instructions")
706				writer.newline()
707				self.program.toXML(writer, ttFont)
708				writer.endtag("instructions")
709			else:
710				writer.simpletag("instructions")
711			writer.newline()
712
713	def fromXML(self, name, attrs, content, ttFont):
714		if name == "contour":
715			if self.numberOfContours < 0:
716				raise ttLib.TTLibError("can't mix composites and contours in glyph")
717			self.numberOfContours = self.numberOfContours + 1
718			coordinates = GlyphCoordinates()
719			flags = bytearray()
720			for element in content:
721				if not isinstance(element, tuple):
722					continue
723				name, attrs, content = element
724				if name != "pt":
725					continue  # ignore anything but "pt"
726				coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"])))
727				flag = bool(safeEval(attrs["on"]))
728				if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
729					flag |= flagOverlapSimple
730				flags.append(flag)
731			if not hasattr(self, "coordinates"):
732				self.coordinates = coordinates
733				self.flags = flags
734				self.endPtsOfContours = [len(coordinates)-1]
735			else:
736				self.coordinates.extend (coordinates)
737				self.flags.extend(flags)
738				self.endPtsOfContours.append(len(self.coordinates)-1)
739		elif name == "component":
740			if self.numberOfContours > 0:
741				raise ttLib.TTLibError("can't mix composites and contours in glyph")
742			self.numberOfContours = -1
743			if not hasattr(self, "components"):
744				self.components = []
745			component = GlyphComponent()
746			self.components.append(component)
747			component.fromXML(name, attrs, content, ttFont)
748		elif name == "instructions":
749			self.program = ttProgram.Program()
750			for element in content:
751				if not isinstance(element, tuple):
752					continue
753				name, attrs, content = element
754				self.program.fromXML(name, attrs, content, ttFont)
755
756	def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
757		assert self.isComposite()
758		nContours = 0
759		nPoints = 0
760		initialMaxComponentDepth = maxComponentDepth
761		for compo in self.components:
762			baseGlyph = glyfTable[compo.glyphName]
763			if baseGlyph.numberOfContours == 0:
764				continue
765			elif baseGlyph.numberOfContours > 0:
766				nP, nC = baseGlyph.getMaxpValues()
767			else:
768				nP, nC, componentDepth = baseGlyph.getCompositeMaxpValues(
769						glyfTable, initialMaxComponentDepth + 1)
770				maxComponentDepth = max(maxComponentDepth, componentDepth)
771			nPoints = nPoints + nP
772			nContours = nContours + nC
773		return CompositeMaxpValues(nPoints, nContours, maxComponentDepth)
774
775	def getMaxpValues(self):
776		assert self.numberOfContours > 0
777		return len(self.coordinates), len(self.endPtsOfContours)
778
779	def decompileComponents(self, data, glyfTable):
780		self.components = []
781		more = 1
782		haveInstructions = 0
783		while more:
784			component = GlyphComponent()
785			more, haveInstr, data = component.decompile(data, glyfTable)
786			haveInstructions = haveInstructions | haveInstr
787			self.components.append(component)
788		if haveInstructions:
789			numInstructions, = struct.unpack(">h", data[:2])
790			data = data[2:]
791			self.program = ttProgram.Program()
792			self.program.fromBytecode(data[:numInstructions])
793			data = data[numInstructions:]
794			if len(data) >= 4:
795				log.warning(
796					"too much glyph data at the end of composite glyph: %d excess bytes",
797					len(data))
798
799	def decompileCoordinates(self, data):
800		endPtsOfContours = array.array("h")
801		endPtsOfContours.frombytes(data[:2*self.numberOfContours])
802		if sys.byteorder != "big": endPtsOfContours.byteswap()
803		self.endPtsOfContours = endPtsOfContours.tolist()
804
805		pos = 2*self.numberOfContours
806		instructionLength, = struct.unpack(">h", data[pos:pos+2])
807		self.program = ttProgram.Program()
808		self.program.fromBytecode(data[pos+2:pos+2+instructionLength])
809		pos += 2 + instructionLength
810		nCoordinates = self.endPtsOfContours[-1] + 1
811		flags, xCoordinates, yCoordinates = \
812				self.decompileCoordinatesRaw(nCoordinates, data, pos)
813
814		# fill in repetitions and apply signs
815		self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates)
816		xIndex = 0
817		yIndex = 0
818		for i in range(nCoordinates):
819			flag = flags[i]
820			# x coordinate
821			if flag & flagXShort:
822				if flag & flagXsame:
823					x = xCoordinates[xIndex]
824				else:
825					x = -xCoordinates[xIndex]
826				xIndex = xIndex + 1
827			elif flag & flagXsame:
828				x = 0
829			else:
830				x = xCoordinates[xIndex]
831				xIndex = xIndex + 1
832			# y coordinate
833			if flag & flagYShort:
834				if flag & flagYsame:
835					y = yCoordinates[yIndex]
836				else:
837					y = -yCoordinates[yIndex]
838				yIndex = yIndex + 1
839			elif flag & flagYsame:
840				y = 0
841			else:
842				y = yCoordinates[yIndex]
843				yIndex = yIndex + 1
844			coordinates[i] = (x, y)
845		assert xIndex == len(xCoordinates)
846		assert yIndex == len(yCoordinates)
847		coordinates.relativeToAbsolute()
848		# discard all flags except "keepFlags"
849		for i in range(len(flags)):
850			flags[i] &= keepFlags
851		self.flags = flags
852
853	def decompileCoordinatesRaw(self, nCoordinates, data, pos=0):
854		# unpack flags and prepare unpacking of coordinates
855		flags = bytearray(nCoordinates)
856		# Warning: deep Python trickery going on. We use the struct module to unpack
857		# the coordinates. We build a format string based on the flags, so we can
858		# unpack the coordinates in one struct.unpack() call.
859		xFormat = ">" # big endian
860		yFormat = ">" # big endian
861		j = 0
862		while True:
863			flag = data[pos]
864			pos += 1
865			repeat = 1
866			if flag & flagRepeat:
867				repeat = data[pos] + 1
868				pos += 1
869			for k in range(repeat):
870				if flag & flagXShort:
871					xFormat = xFormat + 'B'
872				elif not (flag & flagXsame):
873					xFormat = xFormat + 'h'
874				if flag & flagYShort:
875					yFormat = yFormat + 'B'
876				elif not (flag & flagYsame):
877					yFormat = yFormat + 'h'
878				flags[j] = flag
879				j = j + 1
880			if j >= nCoordinates:
881				break
882		assert j == nCoordinates, "bad glyph flags"
883		# unpack raw coordinates, krrrrrr-tching!
884		xDataLen = struct.calcsize(xFormat)
885		yDataLen = struct.calcsize(yFormat)
886		if len(data) - pos - (xDataLen + yDataLen) >= 4:
887			log.warning(
888				"too much glyph data: %d excess bytes", len(data) - pos - (xDataLen + yDataLen))
889		xCoordinates = struct.unpack(xFormat, data[pos:pos+xDataLen])
890		yCoordinates = struct.unpack(yFormat, data[pos+xDataLen:pos+xDataLen+yDataLen])
891		return flags, xCoordinates, yCoordinates
892
893	def compileComponents(self, glyfTable):
894		data = b""
895		lastcomponent = len(self.components) - 1
896		more = 1
897		haveInstructions = 0
898		for i in range(len(self.components)):
899			if i == lastcomponent:
900				haveInstructions = hasattr(self, "program")
901				more = 0
902			compo = self.components[i]
903			data = data + compo.compile(more, haveInstructions, glyfTable)
904		if haveInstructions:
905			instructions = self.program.getBytecode()
906			data = data + struct.pack(">h", len(instructions)) + instructions
907		return data
908
909	def compileCoordinates(self):
910		assert len(self.coordinates) == len(self.flags)
911		data = []
912		endPtsOfContours = array.array("h", self.endPtsOfContours)
913		if sys.byteorder != "big": endPtsOfContours.byteswap()
914		data.append(endPtsOfContours.tobytes())
915		instructions = self.program.getBytecode()
916		data.append(struct.pack(">h", len(instructions)))
917		data.append(instructions)
918
919		deltas = self.coordinates.copy()
920		deltas.toInt()
921		deltas.absoluteToRelative()
922
923		# TODO(behdad): Add a configuration option for this?
924		deltas = self.compileDeltasGreedy(self.flags, deltas)
925		#deltas = self.compileDeltasOptimal(self.flags, deltas)
926
927		data.extend(deltas)
928		return b''.join(data)
929
930	def compileDeltasGreedy(self, flags, deltas):
931		# Implements greedy algorithm for packing coordinate deltas:
932		# uses shortest representation one coordinate at a time.
933		compressedFlags = bytearray()
934		compressedXs = bytearray()
935		compressedYs = bytearray()
936		lastflag = None
937		repeat = 0
938		for flag,(x,y) in zip(flags, deltas):
939			# Oh, the horrors of TrueType
940			# do x
941			if x == 0:
942				flag = flag | flagXsame
943			elif -255 <= x <= 255:
944				flag = flag | flagXShort
945				if x > 0:
946					flag = flag | flagXsame
947				else:
948					x = -x
949				compressedXs.append(x)
950			else:
951				compressedXs.extend(struct.pack('>h', x))
952			# do y
953			if y == 0:
954				flag = flag | flagYsame
955			elif -255 <= y <= 255:
956				flag = flag | flagYShort
957				if y > 0:
958					flag = flag | flagYsame
959				else:
960					y = -y
961				compressedYs.append(y)
962			else:
963				compressedYs.extend(struct.pack('>h', y))
964			# handle repeating flags
965			if flag == lastflag and repeat != 255:
966				repeat = repeat + 1
967				if repeat == 1:
968					compressedFlags.append(flag)
969				else:
970					compressedFlags[-2] = flag | flagRepeat
971					compressedFlags[-1] = repeat
972			else:
973				repeat = 0
974				compressedFlags.append(flag)
975			lastflag = flag
976		return (compressedFlags, compressedXs, compressedYs)
977
978	def compileDeltasOptimal(self, flags, deltas):
979		# Implements optimal, dynaic-programming, algorithm for packing coordinate
980		# deltas.  The savings are negligible :(.
981		candidates = []
982		bestTuple = None
983		bestCost = 0
984		repeat = 0
985		for flag,(x,y) in zip(flags, deltas):
986			# Oh, the horrors of TrueType
987			flag, coordBytes = flagBest(x, y, flag)
988			bestCost += 1 + coordBytes
989			newCandidates = [(bestCost, bestTuple, flag, coordBytes),
990							(bestCost+1, bestTuple, (flag|flagRepeat), coordBytes)]
991			for lastCost,lastTuple,lastFlag,coordBytes in candidates:
992				if lastCost + coordBytes <= bestCost + 1 and (lastFlag & flagRepeat) and (lastFlag < 0xff00) and flagSupports(lastFlag, flag):
993					if (lastFlag & 0xFF) == (flag|flagRepeat) and lastCost == bestCost + 1:
994						continue
995					newCandidates.append((lastCost + coordBytes, lastTuple, lastFlag+256, coordBytes))
996			candidates = newCandidates
997			bestTuple = min(candidates, key=lambda t:t[0])
998			bestCost = bestTuple[0]
999
1000		flags = []
1001		while bestTuple:
1002			cost, bestTuple, flag, coordBytes = bestTuple
1003			flags.append(flag)
1004		flags.reverse()
1005
1006		compressedFlags = bytearray()
1007		compressedXs = bytearray()
1008		compressedYs = bytearray()
1009		coords = iter(deltas)
1010		ff = []
1011		for flag in flags:
1012			repeatCount, flag = flag >> 8, flag & 0xFF
1013			compressedFlags.append(flag)
1014			if flag & flagRepeat:
1015				assert(repeatCount > 0)
1016				compressedFlags.append(repeatCount)
1017			else:
1018				assert(repeatCount == 0)
1019			for i in range(1 + repeatCount):
1020				x,y = next(coords)
1021				flagEncodeCoords(flag, x, y, compressedXs, compressedYs)
1022				ff.append(flag)
1023		try:
1024			next(coords)
1025			raise Exception("internal error")
1026		except StopIteration:
1027			pass
1028
1029		return (compressedFlags, compressedXs, compressedYs)
1030
1031	def recalcBounds(self, glyfTable):
1032		"""Recalculates the bounds of the glyph.
1033
1034		Each glyph object stores its bounding box in the
1035		``xMin``/``yMin``/``xMax``/``yMax`` attributes. These bounds must be
1036		recomputed when the ``coordinates`` change. The ``table__g_l_y_f`` bounds
1037		must be provided to resolve component bounds.
1038		"""
1039		coords, endPts, flags = self.getCoordinates(glyfTable)
1040		self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(coords)
1041
1042	def isComposite(self):
1043		"""Test whether a glyph has components"""
1044		if hasattr(self, "data") and self.data:
1045			return struct.unpack(">h", self.data[:2])[0] == -1
1046		else:
1047			return self.numberOfContours == -1
1048
1049	def __getitem__(self, componentIndex):
1050		if not self.isComposite():
1051			raise ttLib.TTLibError("can't use glyph as sequence")
1052		return self.components[componentIndex]
1053
1054	def getCoordinates(self, glyfTable):
1055		"""Return the coordinates, end points and flags
1056
1057		This method returns three values: A :py:class:`GlyphCoordinates` object,
1058		a list of the indexes of the final points of each contour (allowing you
1059		to split up the coordinates list into contours) and a list of flags.
1060
1061		On simple glyphs, this method returns information from the glyph's own
1062		contours; on composite glyphs, it "flattens" all components recursively
1063		to return a list of coordinates representing all the components involved
1064		in the glyph.
1065
1066		To interpret the flags for each point, see the "Simple Glyph Flags"
1067		section of the `glyf table specification <https://docs.microsoft.com/en-us/typography/opentype/spec/glyf#simple-glyph-description>`.
1068		"""
1069
1070		if self.numberOfContours > 0:
1071			return self.coordinates, self.endPtsOfContours, self.flags
1072		elif self.isComposite():
1073			# it's a composite
1074			allCoords = GlyphCoordinates()
1075			allFlags = bytearray()
1076			allEndPts = []
1077			for compo in self.components:
1078				g = glyfTable[compo.glyphName]
1079				try:
1080					coordinates, endPts, flags = g.getCoordinates(glyfTable)
1081				except RecursionError:
1082					raise ttLib.TTLibError("glyph '%s' contains a recursive component reference" % compo.glyphName)
1083				coordinates = GlyphCoordinates(coordinates)
1084				if hasattr(compo, "firstPt"):
1085					# component uses two reference points: we apply the transform _before_
1086					# computing the offset between the points
1087					if hasattr(compo, "transform"):
1088						coordinates.transform(compo.transform)
1089					x1,y1 = allCoords[compo.firstPt]
1090					x2,y2 = coordinates[compo.secondPt]
1091					move = x1-x2, y1-y2
1092					coordinates.translate(move)
1093				else:
1094					# component uses XY offsets
1095					move = compo.x, compo.y
1096					if not hasattr(compo, "transform"):
1097						coordinates.translate(move)
1098					else:
1099						apple_way = compo.flags & SCALED_COMPONENT_OFFSET
1100						ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
1101						assert not (apple_way and ms_way)
1102						if not (apple_way or ms_way):
1103							scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT  # see top of this file
1104						else:
1105							scale_component_offset = apple_way
1106						if scale_component_offset:
1107							# the Apple way: first move, then scale (ie. scale the component offset)
1108							coordinates.translate(move)
1109							coordinates.transform(compo.transform)
1110						else:
1111							# the MS way: first scale, then move
1112							coordinates.transform(compo.transform)
1113							coordinates.translate(move)
1114				offset = len(allCoords)
1115				allEndPts.extend(e + offset for e in endPts)
1116				allCoords.extend(coordinates)
1117				allFlags.extend(flags)
1118			return allCoords, allEndPts, allFlags
1119		else:
1120			return GlyphCoordinates(), [], bytearray()
1121
1122	def getComponentNames(self, glyfTable):
1123		"""Returns a list of names of component glyphs used in this glyph
1124
1125		This method can be used on simple glyphs (in which case it returns an
1126		empty list) or composite glyphs.
1127		"""
1128		if not hasattr(self, "data"):
1129			if self.isComposite():
1130				return [c.glyphName for c in self.components]
1131			else:
1132				return []
1133
1134		# Extract components without expanding glyph
1135
1136		if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
1137			return []  # Not composite
1138
1139		data = self.data
1140		i = 10
1141		components = []
1142		more = 1
1143		while more:
1144			flags, glyphID = struct.unpack(">HH", data[i:i+4])
1145			i += 4
1146			flags = int(flags)
1147			components.append(glyfTable.getGlyphName(int(glyphID)))
1148
1149			if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1150			else: i += 2
1151			if flags & WE_HAVE_A_SCALE: i += 2
1152			elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1153			elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1154			more = flags & MORE_COMPONENTS
1155
1156		return components
1157
1158	def trim(self, remove_hinting=False):
1159		""" Remove padding and, if requested, hinting, from a glyph.
1160			This works on both expanded and compacted glyphs, without
1161			expanding it."""
1162		if not hasattr(self, "data"):
1163			if remove_hinting:
1164				if self.isComposite():
1165					if hasattr(self, "program"):
1166						del self.program
1167				else:
1168					self.program = ttProgram.Program()
1169					self.program.fromBytecode([])
1170			# No padding to trim.
1171			return
1172		if not self.data:
1173			return
1174		numContours = struct.unpack(">h", self.data[:2])[0]
1175		data = bytearray(self.data)
1176		i = 10
1177		if numContours >= 0:
1178			i += 2 * numContours # endPtsOfContours
1179			nCoordinates = ((data[i-2] << 8) | data[i-1]) + 1
1180			instructionLen = (data[i] << 8) | data[i+1]
1181			if remove_hinting:
1182				# Zero instruction length
1183				data[i] = data [i+1] = 0
1184				i += 2
1185				if instructionLen:
1186					# Splice it out
1187					data = data[:i] + data[i+instructionLen:]
1188				instructionLen = 0
1189			else:
1190				i += 2 + instructionLen
1191
1192			coordBytes = 0
1193			j = 0
1194			while True:
1195				flag = data[i]
1196				i = i + 1
1197				repeat = 1
1198				if flag & flagRepeat:
1199					repeat = data[i] + 1
1200					i = i + 1
1201				xBytes = yBytes = 0
1202				if flag & flagXShort:
1203					xBytes = 1
1204				elif not (flag & flagXsame):
1205					xBytes = 2
1206				if flag & flagYShort:
1207					yBytes = 1
1208				elif not (flag & flagYsame):
1209					yBytes = 2
1210				coordBytes += (xBytes + yBytes) * repeat
1211				j += repeat
1212				if j >= nCoordinates:
1213					break
1214			assert j == nCoordinates, "bad glyph flags"
1215			i += coordBytes
1216			# Remove padding
1217			data = data[:i]
1218		else:
1219			more = 1
1220			we_have_instructions = False
1221			while more:
1222				flags =(data[i] << 8) | data[i+1]
1223				if remove_hinting:
1224					flags &= ~WE_HAVE_INSTRUCTIONS
1225				if flags & WE_HAVE_INSTRUCTIONS:
1226					we_have_instructions = True
1227				data[i+0] = flags >> 8
1228				data[i+1] = flags & 0xFF
1229				i += 4
1230				flags = int(flags)
1231
1232				if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1233				else: i += 2
1234				if flags & WE_HAVE_A_SCALE: i += 2
1235				elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1236				elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1237				more = flags & MORE_COMPONENTS
1238			if we_have_instructions:
1239				instructionLen = (data[i] << 8) | data[i+1]
1240				i += 2 + instructionLen
1241			# Remove padding
1242			data = data[:i]
1243
1244		self.data = data
1245
1246	def removeHinting(self):
1247		"""Removes TrueType hinting instructions from the glyph."""
1248		self.trim (remove_hinting=True)
1249
1250	def draw(self, pen, glyfTable, offset=0):
1251		"""Draws the glyph using the supplied pen object.
1252
1253		Arguments:
1254			pen: An object conforming to the pen protocol.
1255			glyfTable: A :py:class:`table__g_l_y_f` object, to resolve components.
1256			offset (int): A horizontal offset. If provided, all coordinates are
1257				translated by this offset.
1258		"""
1259
1260		if self.isComposite():
1261			for component in self.components:
1262				glyphName, transform = component.getComponentInfo()
1263				pen.addComponent(glyphName, transform)
1264			return
1265
1266		coordinates, endPts, flags = self.getCoordinates(glyfTable)
1267		if offset:
1268			coordinates = coordinates.copy()
1269			coordinates.translate((offset, 0))
1270		start = 0
1271		for end in endPts:
1272			end = end + 1
1273			contour = coordinates[start:end]
1274			cFlags = [flagOnCurve & f for f in flags[start:end]]
1275			start = end
1276			if 1 not in cFlags:
1277				# There is not a single on-curve point on the curve,
1278				# use pen.qCurveTo's special case by specifying None
1279				# as the on-curve point.
1280				contour.append(None)
1281				pen.qCurveTo(*contour)
1282			else:
1283				# Shuffle the points so that contour the is guaranteed
1284				# to *end* in an on-curve point, which we'll use for
1285				# the moveTo.
1286				firstOnCurve = cFlags.index(1) + 1
1287				contour = contour[firstOnCurve:] + contour[:firstOnCurve]
1288				cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
1289				pen.moveTo(contour[-1])
1290				while contour:
1291					nextOnCurve = cFlags.index(1) + 1
1292					if nextOnCurve == 1:
1293						# Skip a final lineTo(), as it is implied by
1294						# pen.closePath()
1295						if len(contour) > 1:
1296							pen.lineTo(contour[0])
1297					else:
1298						pen.qCurveTo(*contour[:nextOnCurve])
1299					contour = contour[nextOnCurve:]
1300					cFlags = cFlags[nextOnCurve:]
1301			pen.closePath()
1302
1303	def drawPoints(self, pen, glyfTable, offset=0):
1304		"""Draw the glyph using the supplied pointPen. As opposed to Glyph.draw(),
1305		this will not change the point indices.
1306		"""
1307
1308		if self.isComposite():
1309			for component in self.components:
1310				glyphName, transform = component.getComponentInfo()
1311				pen.addComponent(glyphName, transform)
1312			return
1313
1314		coordinates, endPts, flags = self.getCoordinates(glyfTable)
1315		if offset:
1316			coordinates = coordinates.copy()
1317			coordinates.translate((offset, 0))
1318		start = 0
1319		for end in endPts:
1320			end = end + 1
1321			contour = coordinates[start:end]
1322			cFlags = flags[start:end]
1323			start = end
1324			pen.beginPath()
1325			# Start with the appropriate segment type based on the final segment
1326			segmentType = "line" if cFlags[-1] == 1 else "qcurve"
1327			for i, pt in enumerate(contour):
1328				if cFlags[i] & flagOnCurve == 1:
1329					pen.addPoint(pt, segmentType=segmentType)
1330					segmentType = "line"
1331				else:
1332					pen.addPoint(pt)
1333					segmentType = "qcurve"
1334			pen.endPath()
1335
1336	def __eq__(self, other):
1337		if type(self) != type(other):
1338			return NotImplemented
1339		return self.__dict__ == other.__dict__
1340
1341	def __ne__(self, other):
1342		result = self.__eq__(other)
1343		return result if result is NotImplemented else not result
1344
1345class GlyphComponent(object):
1346	"""Represents a component within a composite glyph.
1347
1348	The component is represented internally with four attributes: ``glyphName``,
1349	``x``, ``y`` and ``transform``. If there is no "two-by-two" matrix (i.e
1350	no scaling, reflection, or rotation; only translation), the ``transform``
1351	attribute is not present.
1352	"""
1353	# The above documentation is not *completely* true, but is *true enough* because
1354	# the rare firstPt/lastPt attributes are not totally supported and nobody seems to
1355	# mind - see below.
1356
1357	def __init__(self):
1358		pass
1359
1360	def getComponentInfo(self):
1361		"""Return information about the component
1362
1363		This method returns a tuple of two values: the glyph name of the component's
1364		base glyph, and a transformation matrix. As opposed to accessing the attributes
1365		directly, ``getComponentInfo`` always returns a six-element tuple of the
1366		component's transformation matrix, even when the two-by-two ``.transform``
1367		matrix is not present.
1368		"""
1369		# XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
1370		# something equivalent in fontTools.objects.glyph (I'd rather not
1371		# convert it to an absolute offset, since it is valuable information).
1372		# This method will now raise "AttributeError: x" on glyphs that use
1373		# this TT feature.
1374		if hasattr(self, "transform"):
1375			[[xx, xy], [yx, yy]] = self.transform
1376			trans = (xx, xy, yx, yy, self.x, self.y)
1377		else:
1378			trans = (1, 0, 0, 1, self.x, self.y)
1379		return self.glyphName, trans
1380
1381	def decompile(self, data, glyfTable):
1382		flags, glyphID = struct.unpack(">HH", data[:4])
1383		self.flags = int(flags)
1384		glyphID = int(glyphID)
1385		self.glyphName = glyfTable.getGlyphName(int(glyphID))
1386		data = data[4:]
1387
1388		if self.flags & ARG_1_AND_2_ARE_WORDS:
1389			if self.flags & ARGS_ARE_XY_VALUES:
1390				self.x, self.y = struct.unpack(">hh", data[:4])
1391			else:
1392				x, y = struct.unpack(">HH", data[:4])
1393				self.firstPt, self.secondPt = int(x), int(y)
1394			data = data[4:]
1395		else:
1396			if self.flags & ARGS_ARE_XY_VALUES:
1397				self.x, self.y = struct.unpack(">bb", data[:2])
1398			else:
1399				x, y = struct.unpack(">BB", data[:2])
1400				self.firstPt, self.secondPt = int(x), int(y)
1401			data = data[2:]
1402
1403		if self.flags & WE_HAVE_A_SCALE:
1404			scale, = struct.unpack(">h", data[:2])
1405			self.transform = [[fi2fl(scale,14), 0], [0, fi2fl(scale,14)]]  # fixed 2.14
1406			data = data[2:]
1407		elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
1408			xscale, yscale = struct.unpack(">hh", data[:4])
1409			self.transform = [[fi2fl(xscale,14), 0], [0, fi2fl(yscale,14)]]  # fixed 2.14
1410			data = data[4:]
1411		elif self.flags & WE_HAVE_A_TWO_BY_TWO:
1412			(xscale, scale01,
1413					scale10, yscale) = struct.unpack(">hhhh", data[:8])
1414			self.transform = [[fi2fl(xscale,14), fi2fl(scale01,14)],
1415							[fi2fl(scale10,14), fi2fl(yscale,14)]] # fixed 2.14
1416			data = data[8:]
1417		more = self.flags & MORE_COMPONENTS
1418		haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
1419		self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
1420				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
1421				NON_OVERLAPPING | OVERLAP_COMPOUND)
1422		return more, haveInstructions, data
1423
1424	def compile(self, more, haveInstructions, glyfTable):
1425		data = b""
1426
1427		# reset all flags we will calculate ourselves
1428		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
1429				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
1430				NON_OVERLAPPING | OVERLAP_COMPOUND)
1431		if more:
1432			flags = flags | MORE_COMPONENTS
1433		if haveInstructions:
1434			flags = flags | WE_HAVE_INSTRUCTIONS
1435
1436		if hasattr(self, "firstPt"):
1437			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
1438				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
1439			else:
1440				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
1441				flags = flags | ARG_1_AND_2_ARE_WORDS
1442		else:
1443			x = otRound(self.x)
1444			y = otRound(self.y)
1445			flags = flags | ARGS_ARE_XY_VALUES
1446			if (-128 <= x <= 127) and (-128 <= y <= 127):
1447				data = data + struct.pack(">bb", x, y)
1448			else:
1449				data = data + struct.pack(">hh", x, y)
1450				flags = flags | ARG_1_AND_2_ARE_WORDS
1451
1452		if hasattr(self, "transform"):
1453			transform = [[fl2fi(x,14) for x in row] for row in self.transform]
1454			if transform[0][1] or transform[1][0]:
1455				flags = flags | WE_HAVE_A_TWO_BY_TWO
1456				data = data + struct.pack(">hhhh",
1457						transform[0][0], transform[0][1],
1458						transform[1][0], transform[1][1])
1459			elif transform[0][0] != transform[1][1]:
1460				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
1461				data = data + struct.pack(">hh",
1462						transform[0][0], transform[1][1])
1463			else:
1464				flags = flags | WE_HAVE_A_SCALE
1465				data = data + struct.pack(">h",
1466						transform[0][0])
1467
1468		glyphID = glyfTable.getGlyphID(self.glyphName)
1469		return struct.pack(">HH", flags, glyphID) + data
1470
1471	def toXML(self, writer, ttFont):
1472		attrs = [("glyphName", self.glyphName)]
1473		if not hasattr(self, "firstPt"):
1474			attrs = attrs + [("x", self.x), ("y", self.y)]
1475		else:
1476			attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
1477
1478		if hasattr(self, "transform"):
1479			transform = self.transform
1480			if transform[0][1] or transform[1][0]:
1481				attrs = attrs + [
1482					("scalex", fl2str(transform[0][0], 14)),
1483					("scale01", fl2str(transform[0][1], 14)),
1484					("scale10", fl2str(transform[1][0], 14)),
1485					("scaley", fl2str(transform[1][1], 14)),
1486				]
1487			elif transform[0][0] != transform[1][1]:
1488				attrs = attrs + [
1489					("scalex", fl2str(transform[0][0], 14)),
1490					("scaley", fl2str(transform[1][1], 14)),
1491				]
1492			else:
1493				attrs = attrs + [("scale", fl2str(transform[0][0], 14))]
1494		attrs = attrs + [("flags", hex(self.flags))]
1495		writer.simpletag("component", attrs)
1496		writer.newline()
1497
1498	def fromXML(self, name, attrs, content, ttFont):
1499		self.glyphName = attrs["glyphName"]
1500		if "firstPt" in attrs:
1501			self.firstPt = safeEval(attrs["firstPt"])
1502			self.secondPt = safeEval(attrs["secondPt"])
1503		else:
1504			self.x = safeEval(attrs["x"])
1505			self.y = safeEval(attrs["y"])
1506		if "scale01" in attrs:
1507			scalex = str2fl(attrs["scalex"], 14)
1508			scale01 = str2fl(attrs["scale01"], 14)
1509			scale10 = str2fl(attrs["scale10"], 14)
1510			scaley = str2fl(attrs["scaley"], 14)
1511			self.transform = [[scalex, scale01], [scale10, scaley]]
1512		elif "scalex" in attrs:
1513			scalex = str2fl(attrs["scalex"], 14)
1514			scaley = str2fl(attrs["scaley"], 14)
1515			self.transform = [[scalex, 0], [0, scaley]]
1516		elif "scale" in attrs:
1517			scale = str2fl(attrs["scale"], 14)
1518			self.transform = [[scale, 0], [0, scale]]
1519		self.flags = safeEval(attrs["flags"])
1520
1521	def __eq__(self, other):
1522		if type(self) != type(other):
1523			return NotImplemented
1524		return self.__dict__ == other.__dict__
1525
1526	def __ne__(self, other):
1527		result = self.__eq__(other)
1528		return result if result is NotImplemented else not result
1529
1530class GlyphCoordinates(object):
1531	"""A list of glyph coordinates.
1532
1533	Unlike an ordinary list, this is a numpy-like matrix object which supports
1534	matrix addition, scalar multiplication and other operations described below.
1535	"""
1536	def __init__(self, iterable=[]):
1537		self._a = array.array('d')
1538		self.extend(iterable)
1539
1540	@property
1541	def array(self):
1542		"""Returns the underlying array of coordinates"""
1543		return self._a
1544
1545	@staticmethod
1546	def zeros(count):
1547		"""Creates a new ``GlyphCoordinates`` object with all coordinates set to (0,0)"""
1548		g = GlyphCoordinates()
1549		g._a.frombytes(bytes(count * 2 * g._a.itemsize))
1550		return g
1551
1552	def copy(self):
1553		"""Creates a new ``GlyphCoordinates`` object which is a copy of the current one."""
1554		c = GlyphCoordinates()
1555		c._a.extend(self._a)
1556		return c
1557
1558	def __len__(self):
1559		"""Returns the number of coordinates in the array."""
1560		return len(self._a) // 2
1561
1562	def __getitem__(self, k):
1563		"""Returns a two element tuple (x,y)"""
1564		if isinstance(k, slice):
1565			indices = range(*k.indices(len(self)))
1566			return [self[i] for i in indices]
1567		a = self._a
1568		x = a[2*k]
1569		y = a[2*k+1]
1570		return (int(x) if x.is_integer() else x,
1571			int(y) if y.is_integer() else y)
1572
1573	def __setitem__(self, k, v):
1574		"""Sets a point's coordinates to a two element tuple (x,y)"""
1575		if isinstance(k, slice):
1576			indices = range(*k.indices(len(self)))
1577			# XXX This only works if len(v) == len(indices)
1578			for j,i in enumerate(indices):
1579				self[i] = v[j]
1580			return
1581		self._a[2*k],self._a[2*k+1] = v
1582
1583	def __delitem__(self, i):
1584		"""Removes a point from the list"""
1585		i = (2*i) % len(self._a)
1586		del self._a[i]
1587		del self._a[i]
1588
1589	def __repr__(self):
1590		return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])'
1591
1592	def append(self, p):
1593		self._a.extend(tuple(p))
1594
1595	def extend(self, iterable):
1596		for p in iterable:
1597			self._a.extend(p)
1598
1599	def toInt(self, *, round=otRound):
1600		a = self._a
1601		for i in range(len(a)):
1602			a[i] = round(a[i])
1603
1604	def relativeToAbsolute(self):
1605		a = self._a
1606		x,y = 0,0
1607		for i in range(0, len(a), 2):
1608			a[i  ] = x = a[i  ] + x
1609			a[i+1] = y = a[i+1] + y
1610
1611	def absoluteToRelative(self):
1612		a = self._a
1613		x,y = 0,0
1614		for i in range(0, len(a), 2):
1615			nx = a[i  ]
1616			ny = a[i+1]
1617			a[i]   = nx - x
1618			a[i+1] = ny - y
1619			x = nx
1620			y = ny
1621
1622	def translate(self, p):
1623		"""
1624		>>> GlyphCoordinates([(1,2)]).translate((.5,0))
1625		"""
1626		x,y = p
1627		if x == 0 and y == 0:
1628			return
1629		a = self._a
1630		for i in range(0, len(a), 2):
1631			a[i]   += x
1632			a[i+1] += y
1633
1634	def scale(self, p):
1635		"""
1636		>>> GlyphCoordinates([(1,2)]).scale((.5,0))
1637		"""
1638		x,y = p
1639		if x == 1 and y == 1:
1640			return
1641		a = self._a
1642		for i in range(0, len(a), 2):
1643			a[i]   *= x
1644			a[i+1] *= y
1645
1646	def transform(self, t):
1647		"""
1648		>>> GlyphCoordinates([(1,2)]).transform(((.5,0),(.2,.5)))
1649		"""
1650		a = self._a
1651		for i in range(0, len(a), 2):
1652			x = a[i  ]
1653			y = a[i+1]
1654			px = x * t[0][0] + y * t[1][0]
1655			py = x * t[0][1] + y * t[1][1]
1656			a[i]   = px
1657			a[i+1] = py
1658
1659	def __eq__(self, other):
1660		"""
1661		>>> g = GlyphCoordinates([(1,2)])
1662		>>> g2 = GlyphCoordinates([(1.0,2)])
1663		>>> g3 = GlyphCoordinates([(1.5,2)])
1664		>>> g == g2
1665		True
1666		>>> g == g3
1667		False
1668		>>> g2 == g3
1669		False
1670		"""
1671		if type(self) != type(other):
1672			return NotImplemented
1673		return self._a == other._a
1674
1675	def __ne__(self, other):
1676		"""
1677		>>> g = GlyphCoordinates([(1,2)])
1678		>>> g2 = GlyphCoordinates([(1.0,2)])
1679		>>> g3 = GlyphCoordinates([(1.5,2)])
1680		>>> g != g2
1681		False
1682		>>> g != g3
1683		True
1684		>>> g2 != g3
1685		True
1686		"""
1687		result = self.__eq__(other)
1688		return result if result is NotImplemented else not result
1689
1690	# Math operations
1691
1692	def __pos__(self):
1693		"""
1694		>>> g = GlyphCoordinates([(1,2)])
1695		>>> g
1696		GlyphCoordinates([(1, 2)])
1697		>>> g2 = +g
1698		>>> g2
1699		GlyphCoordinates([(1, 2)])
1700		>>> g2.translate((1,0))
1701		>>> g2
1702		GlyphCoordinates([(2, 2)])
1703		>>> g
1704		GlyphCoordinates([(1, 2)])
1705		"""
1706		return self.copy()
1707	def __neg__(self):
1708		"""
1709		>>> g = GlyphCoordinates([(1,2)])
1710		>>> g
1711		GlyphCoordinates([(1, 2)])
1712		>>> g2 = -g
1713		>>> g2
1714		GlyphCoordinates([(-1, -2)])
1715		>>> g
1716		GlyphCoordinates([(1, 2)])
1717		"""
1718		r = self.copy()
1719		a = r._a
1720		for i in range(len(a)):
1721			a[i] = -a[i]
1722		return r
1723	def __round__(self, *, round=otRound):
1724		r = self.copy()
1725		r.toInt(round=round)
1726		return r
1727
1728	def __add__(self, other): return self.copy().__iadd__(other)
1729	def __sub__(self, other): return self.copy().__isub__(other)
1730	def __mul__(self, other): return self.copy().__imul__(other)
1731	def __truediv__(self, other): return self.copy().__itruediv__(other)
1732
1733	__radd__ = __add__
1734	__rmul__ = __mul__
1735	def __rsub__(self, other): return other + (-self)
1736
1737	def __iadd__(self, other):
1738		"""
1739		>>> g = GlyphCoordinates([(1,2)])
1740		>>> g += (.5,0)
1741		>>> g
1742		GlyphCoordinates([(1.5, 2)])
1743		>>> g2 = GlyphCoordinates([(3,4)])
1744		>>> g += g2
1745		>>> g
1746		GlyphCoordinates([(4.5, 6)])
1747		"""
1748		if isinstance(other, tuple):
1749			assert len(other) ==  2
1750			self.translate(other)
1751			return self
1752		if isinstance(other, GlyphCoordinates):
1753			other = other._a
1754			a = self._a
1755			assert len(a) == len(other)
1756			for i in range(len(a)):
1757				a[i] += other[i]
1758			return self
1759		return NotImplemented
1760
1761	def __isub__(self, other):
1762		"""
1763		>>> g = GlyphCoordinates([(1,2)])
1764		>>> g -= (.5,0)
1765		>>> g
1766		GlyphCoordinates([(0.5, 2)])
1767		>>> g2 = GlyphCoordinates([(3,4)])
1768		>>> g -= g2
1769		>>> g
1770		GlyphCoordinates([(-2.5, -2)])
1771		"""
1772		if isinstance(other, tuple):
1773			assert len(other) ==  2
1774			self.translate((-other[0],-other[1]))
1775			return self
1776		if isinstance(other, GlyphCoordinates):
1777			other = other._a
1778			a = self._a
1779			assert len(a) == len(other)
1780			for i in range(len(a)):
1781				a[i] -= other[i]
1782			return self
1783		return NotImplemented
1784
1785	def __imul__(self, other):
1786		"""
1787		>>> g = GlyphCoordinates([(1,2)])
1788		>>> g *= (2,.5)
1789		>>> g *= 2
1790		>>> g
1791		GlyphCoordinates([(4, 2)])
1792		>>> g = GlyphCoordinates([(1,2)])
1793		>>> g *= 2
1794		>>> g
1795		GlyphCoordinates([(2, 4)])
1796		"""
1797		if isinstance(other, tuple):
1798			assert len(other) ==  2
1799			self.scale(other)
1800			return self
1801		if isinstance(other, Number):
1802			if other == 1:
1803				return self
1804			a = self._a
1805			for i in range(len(a)):
1806				a[i] *= other
1807			return self
1808		return NotImplemented
1809
1810	def __itruediv__(self, other):
1811		"""
1812		>>> g = GlyphCoordinates([(1,3)])
1813		>>> g /= (.5,1.5)
1814		>>> g /= 2
1815		>>> g
1816		GlyphCoordinates([(1, 1)])
1817		"""
1818		if isinstance(other, Number):
1819			other = (other, other)
1820		if isinstance(other, tuple):
1821			if other == (1,1):
1822				return self
1823			assert len(other) ==  2
1824			self.scale((1./other[0],1./other[1]))
1825			return self
1826		return NotImplemented
1827
1828	def __bool__(self):
1829		"""
1830		>>> g = GlyphCoordinates([])
1831		>>> bool(g)
1832		False
1833		>>> g = GlyphCoordinates([(0,0), (0.,0)])
1834		>>> bool(g)
1835		True
1836		>>> g = GlyphCoordinates([(0,0), (1,0)])
1837		>>> bool(g)
1838		True
1839		>>> g = GlyphCoordinates([(0,.5), (0,0)])
1840		>>> bool(g)
1841		True
1842		"""
1843		return bool(self._a)
1844
1845	__nonzero__ = __bool__
1846
1847
1848if __name__ == "__main__":
1849	import doctest, sys
1850	sys.exit(doctest.testmod().failed)
1851