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