• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
2
3
4from __future__ import print_function, division, absolute_import
5from fontTools.misc.py23 import *
6from fontTools.misc import sstruct
7from fontTools import ttLib
8from fontTools.misc.textTools import safeEval
9from fontTools.misc.arrayTools import calcBounds, calcIntBounds, pointInRect
10from fontTools.misc.bezierTools import calcQuadraticBounds
11from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
12from . import DefaultTable
13from . import ttProgram
14import sys
15import struct
16import array
17import warnings
18
19#
20# The Apple and MS rasterizers behave differently for
21# scaled composite components: one does scale first and then translate
22# and the other does it vice versa. MS defined some flags to indicate
23# the difference, but it seems nobody actually _sets_ those flags.
24#
25# Funny thing: Apple seems to _only_ do their thing in the
26# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
27# (eg. Charcoal)...
28#
29SCALE_COMPONENT_OFFSET_DEFAULT = 0   # 0 == MS, 1 == Apple
30
31
32class table__g_l_y_f(DefaultTable.DefaultTable):
33
34	def decompile(self, data, ttFont):
35		loca = ttFont['loca']
36		last = int(loca[0])
37		noname = 0
38		self.glyphs = {}
39		self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
40		for i in range(0, len(loca)-1):
41			try:
42				glyphName = glyphOrder[i]
43			except IndexError:
44				noname = noname + 1
45				glyphName = 'ttxautoglyph%s' % i
46			next = int(loca[i+1])
47			glyphdata = data[last:next]
48			if len(glyphdata) != (next - last):
49				raise ttLib.TTLibError("not enough 'glyf' table data")
50			glyph = Glyph(glyphdata)
51			self.glyphs[glyphName] = glyph
52			last = next
53		if len(data) > next:
54			warnings.warn("too much 'glyf' table data")
55		if noname:
56			warnings.warn('%s glyphs have no name' % i)
57		if not ttFont.lazy:
58			for glyph in self.glyphs.values():
59				glyph.expand(self)
60
61	def compile(self, ttFont):
62		if not hasattr(self, "glyphOrder"):
63			self.glyphOrder = ttFont.getGlyphOrder()
64		locations = []
65		currentLocation = 0
66		dataList = []
67		recalcBBoxes = ttFont.recalcBBoxes
68		for glyphName in self.glyphOrder:
69			glyph = self.glyphs[glyphName]
70			glyphData = glyph.compile(self, recalcBBoxes)
71			locations.append(currentLocation)
72			currentLocation = currentLocation + len(glyphData)
73			dataList.append(glyphData)
74		locations.append(currentLocation)
75		data = bytesjoin(dataList)
76		if 'loca' in ttFont:
77			ttFont['loca'].set(locations)
78		ttFont['maxp'].numGlyphs = len(self.glyphs)
79		return data
80
81	def toXML(self, writer, ttFont, progress=None):
82		writer.newline()
83		glyphNames = ttFont.getGlyphNames()
84		writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.")
85		writer.newline()
86		writer.newline()
87		counter = 0
88		progressStep = 10
89		numGlyphs = len(glyphNames)
90		for glyphName in glyphNames:
91			if not counter % progressStep and progress is not None:
92				progress.setLabel("Dumping 'glyf' table... (%s)" % glyphName)
93				progress.increment(progressStep / numGlyphs)
94			counter = counter + 1
95			glyph = self[glyphName]
96			if glyph.numberOfContours:
97				writer.begintag('TTGlyph', [
98						("name", glyphName),
99						("xMin", glyph.xMin),
100						("yMin", glyph.yMin),
101						("xMax", glyph.xMax),
102						("yMax", glyph.yMax),
103						])
104				writer.newline()
105				glyph.toXML(writer, ttFont)
106				writer.endtag('TTGlyph')
107				writer.newline()
108			else:
109				writer.simpletag('TTGlyph', name=glyphName)
110				writer.comment("contains no outline data")
111				writer.newline()
112			writer.newline()
113
114	def fromXML(self, name, attrs, content, ttFont):
115		if name != "TTGlyph":
116			return
117		if not hasattr(self, "glyphs"):
118			self.glyphs = {}
119		if not hasattr(self, "glyphOrder"):
120			self.glyphOrder = ttFont.getGlyphOrder()
121		glyphName = attrs["name"]
122		if ttFont.verbose:
123			ttLib.debugmsg("unpacking glyph '%s'" % glyphName)
124		glyph = Glyph()
125		for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
126			setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
127		self.glyphs[glyphName] = glyph
128		for element in content:
129			if not isinstance(element, tuple):
130				continue
131			name, attrs, content = element
132			glyph.fromXML(name, attrs, content, ttFont)
133		if not ttFont.recalcBBoxes:
134			glyph.compact(self, 0)
135
136	def setGlyphOrder(self, glyphOrder):
137		self.glyphOrder = glyphOrder
138
139	def getGlyphName(self, glyphID):
140		return self.glyphOrder[glyphID]
141
142	def getGlyphID(self, glyphName):
143		# XXX optimize with reverse dict!!!
144		return self.glyphOrder.index(glyphName)
145
146	def keys(self):
147		return self.glyphs.keys()
148
149	def has_key(self, glyphName):
150		return glyphName in self.glyphs
151
152	__contains__ = has_key
153
154	def __getitem__(self, glyphName):
155		glyph = self.glyphs[glyphName]
156		glyph.expand(self)
157		return glyph
158
159	def __setitem__(self, glyphName, glyph):
160		self.glyphs[glyphName] = glyph
161		if glyphName not in self.glyphOrder:
162			self.glyphOrder.append(glyphName)
163
164	def __delitem__(self, glyphName):
165		del self.glyphs[glyphName]
166		self.glyphOrder.remove(glyphName)
167
168	def __len__(self):
169		assert len(self.glyphOrder) == len(self.glyphs)
170		return len(self.glyphs)
171
172
173glyphHeaderFormat = """
174		>	# big endian
175		numberOfContours:	h
176		xMin:				h
177		yMin:				h
178		xMax:				h
179		yMax:				h
180"""
181
182# flags
183flagOnCurve = 0x01
184flagXShort = 0x02
185flagYShort = 0x04
186flagRepeat = 0x08
187flagXsame =  0x10
188flagYsame = 0x20
189flagReserved1 = 0x40
190flagReserved2 = 0x80
191
192
193ARG_1_AND_2_ARE_WORDS      = 0x0001  # if set args are words otherwise they are bytes
194ARGS_ARE_XY_VALUES         = 0x0002  # if set args are xy values, otherwise they are points
195ROUND_XY_TO_GRID           = 0x0004  # for the xy values if above is true
196WE_HAVE_A_SCALE            = 0x0008  # Sx = Sy, otherwise scale == 1.0
197NON_OVERLAPPING            = 0x0010  # set to same value for all components (obsolete!)
198MORE_COMPONENTS            = 0x0020  # indicates at least one more glyph after this one
199WE_HAVE_AN_X_AND_Y_SCALE   = 0x0040  # Sx, Sy
200WE_HAVE_A_TWO_BY_TWO       = 0x0080  # t00, t01, t10, t11
201WE_HAVE_INSTRUCTIONS       = 0x0100  # instructions follow
202USE_MY_METRICS             = 0x0200  # apply these metrics to parent glyph
203OVERLAP_COMPOUND           = 0x0400  # used by Apple in GX fonts
204SCALED_COMPONENT_OFFSET    = 0x0800  # composite designed to have the component offset scaled (designed for Apple)
205UNSCALED_COMPONENT_OFFSET  = 0x1000  # composite designed not to have the component offset scaled (designed for MS)
206
207
208class Glyph(object):
209
210	def __init__(self, data=""):
211		if not data:
212			# empty char
213			self.numberOfContours = 0
214			return
215		self.data = data
216
217	def compact(self, glyfTable, recalcBBoxes=True):
218		data = self.compile(glyfTable, recalcBBoxes)
219		self.__dict__.clear()
220		self.data = data
221
222	def expand(self, glyfTable):
223		if not hasattr(self, "data"):
224			# already unpacked
225			return
226		if not self.data:
227			# empty char
228			self.numberOfContours = 0
229			return
230		dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
231		del self.data
232		if self.isComposite():
233			self.decompileComponents(data, glyfTable)
234		else:
235			self.decompileCoordinates(data)
236
237	def compile(self, glyfTable, recalcBBoxes=True):
238		if hasattr(self, "data"):
239			return self.data
240		if self.numberOfContours == 0:
241			return ""
242		if recalcBBoxes:
243			self.recalcBounds(glyfTable)
244		data = sstruct.pack(glyphHeaderFormat, self)
245		if self.isComposite():
246			data = data + self.compileComponents(glyfTable)
247		else:
248			data = data + self.compileCoordinates()
249		# From the spec: "Note that the local offsets should be word-aligned"
250		# From a later MS spec: "Note that the local offsets should be long-aligned"
251		# Let's be modern and align on 4-byte boundaries.
252		if len(data) % 4:
253			# add pad bytes
254			nPadBytes = 4 - (len(data) % 4)
255			data = data + b"\0" * nPadBytes
256		return data
257
258	def toXML(self, writer, ttFont):
259		if self.isComposite():
260			for compo in self.components:
261				compo.toXML(writer, ttFont)
262			if hasattr(self, "program"):
263				writer.begintag("instructions")
264				self.program.toXML(writer, ttFont)
265				writer.endtag("instructions")
266				writer.newline()
267		else:
268			last = 0
269			for i in range(self.numberOfContours):
270				writer.begintag("contour")
271				writer.newline()
272				for j in range(last, self.endPtsOfContours[i] + 1):
273					writer.simpletag("pt", [
274							("x", self.coordinates[j][0]),
275							("y", self.coordinates[j][1]),
276							("on", self.flags[j] & flagOnCurve)])
277					writer.newline()
278				last = self.endPtsOfContours[i] + 1
279				writer.endtag("contour")
280				writer.newline()
281			if self.numberOfContours:
282				writer.begintag("instructions")
283				self.program.toXML(writer, ttFont)
284				writer.endtag("instructions")
285				writer.newline()
286
287	def fromXML(self, name, attrs, content, ttFont):
288		if name == "contour":
289			if self.numberOfContours < 0:
290				raise ttLib.TTLibError("can't mix composites and contours in glyph")
291			self.numberOfContours = self.numberOfContours + 1
292			coordinates = GlyphCoordinates()
293			flags = []
294			for element in content:
295				if not isinstance(element, tuple):
296					continue
297				name, attrs, content = element
298				if name != "pt":
299					continue  # ignore anything but "pt"
300				coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"])))
301				flags.append(not not safeEval(attrs["on"]))
302			flags = array.array("B", flags)
303			if not hasattr(self, "coordinates"):
304				self.coordinates = coordinates
305				self.flags = flags
306				self.endPtsOfContours = [len(coordinates)-1]
307			else:
308				self.coordinates.extend (coordinates)
309				self.flags.extend(flags)
310				self.endPtsOfContours.append(len(self.coordinates)-1)
311		elif name == "component":
312			if self.numberOfContours > 0:
313				raise ttLib.TTLibError("can't mix composites and contours in glyph")
314			self.numberOfContours = -1
315			if not hasattr(self, "components"):
316				self.components = []
317			component = GlyphComponent()
318			self.components.append(component)
319			component.fromXML(name, attrs, content, ttFont)
320		elif name == "instructions":
321			self.program = ttProgram.Program()
322			for element in content:
323				if not isinstance(element, tuple):
324					continue
325				name, attrs, content = element
326				self.program.fromXML(name, attrs, content, ttFont)
327
328	def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
329		assert self.isComposite()
330		nContours = 0
331		nPoints = 0
332		for compo in self.components:
333			baseGlyph = glyfTable[compo.glyphName]
334			if baseGlyph.numberOfContours == 0:
335				continue
336			elif baseGlyph.numberOfContours > 0:
337				nP, nC = baseGlyph.getMaxpValues()
338			else:
339				nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
340						glyfTable, maxComponentDepth + 1)
341			nPoints = nPoints + nP
342			nContours = nContours + nC
343		return nPoints, nContours, maxComponentDepth
344
345	def getMaxpValues(self):
346		assert self.numberOfContours > 0
347		return len(self.coordinates), len(self.endPtsOfContours)
348
349	def decompileComponents(self, data, glyfTable):
350		self.components = []
351		more = 1
352		haveInstructions = 0
353		while more:
354			component = GlyphComponent()
355			more, haveInstr, data = component.decompile(data, glyfTable)
356			haveInstructions = haveInstructions | haveInstr
357			self.components.append(component)
358		if haveInstructions:
359			numInstructions, = struct.unpack(">h", data[:2])
360			data = data[2:]
361			self.program = ttProgram.Program()
362			self.program.fromBytecode(data[:numInstructions])
363			data = data[numInstructions:]
364			assert len(data) < 4, "bad composite data"
365
366	def decompileCoordinates(self, data):
367		endPtsOfContours = array.array("h")
368		endPtsOfContours.fromstring(data[:2*self.numberOfContours])
369		if sys.byteorder != "big":
370			endPtsOfContours.byteswap()
371		self.endPtsOfContours = endPtsOfContours.tolist()
372
373		data = data[2*self.numberOfContours:]
374
375		instructionLength, = struct.unpack(">h", data[:2])
376		data = data[2:]
377		self.program = ttProgram.Program()
378		self.program.fromBytecode(data[:instructionLength])
379		data = data[instructionLength:]
380		nCoordinates = self.endPtsOfContours[-1] + 1
381		flags, xCoordinates, yCoordinates = \
382				self.decompileCoordinatesRaw(nCoordinates, data)
383
384		# fill in repetitions and apply signs
385		self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates)
386		xIndex = 0
387		yIndex = 0
388		for i in range(nCoordinates):
389			flag = flags[i]
390			# x coordinate
391			if flag & flagXShort:
392				if flag & flagXsame:
393					x = xCoordinates[xIndex]
394				else:
395					x = -xCoordinates[xIndex]
396				xIndex = xIndex + 1
397			elif flag & flagXsame:
398				x = 0
399			else:
400				x = xCoordinates[xIndex]
401				xIndex = xIndex + 1
402			# y coordinate
403			if flag & flagYShort:
404				if flag & flagYsame:
405					y = yCoordinates[yIndex]
406				else:
407					y = -yCoordinates[yIndex]
408				yIndex = yIndex + 1
409			elif flag & flagYsame:
410				y = 0
411			else:
412				y = yCoordinates[yIndex]
413				yIndex = yIndex + 1
414			coordinates[i] = (x, y)
415		assert xIndex == len(xCoordinates)
416		assert yIndex == len(yCoordinates)
417		coordinates.relativeToAbsolute()
418		# discard all flags but for "flagOnCurve"
419		self.flags = array.array("B", (f & flagOnCurve for f in flags))
420
421	def decompileCoordinatesRaw(self, nCoordinates, data):
422		# unpack flags and prepare unpacking of coordinates
423		flags = array.array("B", [0] * nCoordinates)
424		# Warning: deep Python trickery going on. We use the struct module to unpack
425		# the coordinates. We build a format string based on the flags, so we can
426		# unpack the coordinates in one struct.unpack() call.
427		xFormat = ">" # big endian
428		yFormat = ">" # big endian
429		i = j = 0
430		while True:
431			flag = byteord(data[i])
432			i = i + 1
433			repeat = 1
434			if flag & flagRepeat:
435				repeat = byteord(data[i]) + 1
436				i = i + 1
437			for k in range(repeat):
438				if flag & flagXShort:
439					xFormat = xFormat + 'B'
440				elif not (flag & flagXsame):
441					xFormat = xFormat + 'h'
442				if flag & flagYShort:
443					yFormat = yFormat + 'B'
444				elif not (flag & flagYsame):
445					yFormat = yFormat + 'h'
446				flags[j] = flag
447				j = j + 1
448			if j >= nCoordinates:
449				break
450		assert j == nCoordinates, "bad glyph flags"
451		data = data[i:]
452		# unpack raw coordinates, krrrrrr-tching!
453		xDataLen = struct.calcsize(xFormat)
454		yDataLen = struct.calcsize(yFormat)
455		if len(data) - (xDataLen + yDataLen) >= 4:
456			warnings.warn("too much glyph data: %d excess bytes" % (len(data) - (xDataLen + yDataLen)))
457		xCoordinates = struct.unpack(xFormat, data[:xDataLen])
458		yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
459		return flags, xCoordinates, yCoordinates
460
461	def compileComponents(self, glyfTable):
462		data = b""
463		lastcomponent = len(self.components) - 1
464		more = 1
465		haveInstructions = 0
466		for i in range(len(self.components)):
467			if i == lastcomponent:
468				haveInstructions = hasattr(self, "program")
469				more = 0
470			compo = self.components[i]
471			data = data + compo.compile(more, haveInstructions, glyfTable)
472		if haveInstructions:
473			instructions = self.program.getBytecode()
474			data = data + struct.pack(">h", len(instructions)) + instructions
475		return data
476
477
478	def compileCoordinates(self):
479		assert len(self.coordinates) == len(self.flags)
480		data = b""
481		endPtsOfContours = array.array("h", self.endPtsOfContours)
482		if sys.byteorder != "big":
483			endPtsOfContours.byteswap()
484		data = data + endPtsOfContours.tostring()
485		instructions = self.program.getBytecode()
486		data = data + struct.pack(">h", len(instructions)) + instructions
487		nCoordinates = len(self.coordinates)
488
489		coordinates = self.coordinates.copy()
490		coordinates.absoluteToRelative()
491		flags = self.flags
492		compressedflags = []
493		xPoints = []
494		yPoints = []
495		xFormat = ">"
496		yFormat = ">"
497		lastflag = None
498		repeat = 0
499		for i in range(len(coordinates)):
500			# Oh, the horrors of TrueType
501			flag = flags[i]
502			x, y = coordinates[i]
503			# do x
504			if x == 0:
505				flag = flag | flagXsame
506			elif -255 <= x <= 255:
507				flag = flag | flagXShort
508				if x > 0:
509					flag = flag | flagXsame
510				else:
511					x = -x
512				xPoints.append(x)
513				xFormat = xFormat + 'B'
514			else:
515				xPoints.append(x)
516				xFormat = xFormat + 'h'
517			# do y
518			if y == 0:
519				flag = flag | flagYsame
520			elif -255 <= y <= 255:
521				flag = flag | flagYShort
522				if y > 0:
523					flag = flag | flagYsame
524				else:
525					y = -y
526				yPoints.append(y)
527				yFormat = yFormat + 'B'
528			else:
529				yPoints.append(y)
530				yFormat = yFormat + 'h'
531			# handle repeating flags
532			if flag == lastflag and repeat != 255:
533				repeat = repeat + 1
534				if repeat == 1:
535					compressedflags.append(flag)
536				else:
537					compressedflags[-2] = flag | flagRepeat
538					compressedflags[-1] = repeat
539			else:
540				repeat = 0
541				compressedflags.append(flag)
542			lastflag = flag
543		data = data + array.array("B", compressedflags).tostring()
544		if coordinates.isFloat():
545			# Warn?
546			xPoints = [int(round(x)) for x in xPoints]
547			yPoints = [int(round(y)) for y in xPoints]
548		data = data + struct.pack(*(xFormat,)+tuple(xPoints))
549		data = data + struct.pack(*(yFormat,)+tuple(yPoints))
550		return data
551
552	def recalcBounds(self, glyfTable):
553		coords, endPts, flags = self.getCoordinates(glyfTable)
554		if len(coords) > 0:
555			if 0:
556				# This branch calculates exact glyph outline bounds
557				# analytically, handling cases without on-curve
558				# extremas, etc.  However, the glyf table header
559				# simply says that the bounds should be min/max x/y
560				# "for coordinate data", so I suppose that means no
561				# fancy thing here, just get extremas of all coord
562				# points (on and off).  As such, this branch is
563				# disabled.
564
565				# Collect on-curve points
566				onCurveCoords = [coords[j] for j in range(len(coords))
567						 if flags[j] & flagOnCurve]
568				# Add implicit on-curve points
569				start = 0
570				for end in endPts:
571					last = end
572					for j in range(start, end + 1):
573						if not ((flags[j] | flags[last]) & flagOnCurve):
574							x = (coords[last][0] + coords[j][0]) / 2
575							y = (coords[last][1] + coords[j][1]) / 2
576							onCurveCoords.append((x,y))
577						last = j
578					start = end + 1
579				# Add bounds for curves without an explicit extrema
580				start = 0
581				for end in endPts:
582					last = end
583					for j in range(start, end + 1):
584						if not (flags[j] & flagOnCurve):
585							next = j + 1 if j < end else start
586							bbox = calcBounds([coords[last], coords[next]])
587							if not pointInRect(coords[j], bbox):
588								# Ouch!
589								warnings.warn("Outline has curve with implicit extrema.")
590								# Ouch!  Find analytical curve bounds.
591								pthis = coords[j]
592								plast = coords[last]
593								if not (flags[last] & flagOnCurve):
594									plast = ((pthis[0]+plast[0])/2, (pthis[1]+plast[1])/2)
595								pnext = coords[next]
596								if not (flags[next] & flagOnCurve):
597									pnext = ((pthis[0]+pnext[0])/2, (pthis[1]+pnext[1])/2)
598								bbox = calcQuadraticBounds(plast, pthis, pnext)
599								onCurveCoords.append((bbox[0],bbox[1]))
600								onCurveCoords.append((bbox[2],bbox[3]))
601						last = j
602					start = end + 1
603
604				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(onCurveCoords)
605			else:
606				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(coords)
607		else:
608			self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0)
609
610	def isComposite(self):
611		"""Can be called on compact or expanded glyph."""
612		if hasattr(self, "data"):
613			return struct.unpack(">h", self.data[:2])[0] == -1
614		else:
615			return self.numberOfContours == -1
616
617	def __getitem__(self, componentIndex):
618		if not self.isComposite():
619			raise ttLib.TTLibError("can't use glyph as sequence")
620		return self.components[componentIndex]
621
622	def getCoordinates(self, glyfTable):
623		if self.numberOfContours > 0:
624			return self.coordinates, self.endPtsOfContours, self.flags
625		elif self.isComposite():
626			# it's a composite
627			allCoords = GlyphCoordinates()
628			allFlags = array.array("B")
629			allEndPts = []
630			for compo in self.components:
631				g = glyfTable[compo.glyphName]
632				coordinates, endPts, flags = g.getCoordinates(glyfTable)
633				if hasattr(compo, "firstPt"):
634					# move according to two reference points
635					x1,y1 = allCoords[compo.firstPt]
636					x2,y2 = coordinates[compo.secondPt]
637					move = x1-x2, y1-y2
638				else:
639					move = compo.x, compo.y
640
641				coordinates = GlyphCoordinates(coordinates)
642				if not hasattr(compo, "transform"):
643					coordinates.translate(move)
644				else:
645					apple_way = compo.flags & SCALED_COMPONENT_OFFSET
646					ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
647					assert not (apple_way and ms_way)
648					if not (apple_way or ms_way):
649						scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT  # see top of this file
650					else:
651						scale_component_offset = apple_way
652					if scale_component_offset:
653						# the Apple way: first move, then scale (ie. scale the component offset)
654						coordinates.translate(move)
655						coordinates.transform(compo.transform)
656					else:
657						# the MS way: first scale, then move
658						coordinates.transform(compo.transform)
659						coordinates.translate(move)
660				offset = len(allCoords)
661				allEndPts.extend(e + offset for e in endPts)
662				allCoords.extend(coordinates)
663				allFlags.extend(flags)
664			return allCoords, allEndPts, allFlags
665		else:
666			return GlyphCoordinates(), [], array.array("B")
667
668	def getComponentNames(self, glyfTable):
669		if not hasattr(self, "data"):
670			if self.isComposite():
671				return [c.glyphName for c in self.components]
672			else:
673				return []
674
675		# Extract components without expanding glyph
676
677		if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
678			return []  # Not composite
679
680		data = self.data
681		i = 10
682		components = []
683		more = 1
684		while more:
685			flags, glyphID = struct.unpack(">HH", data[i:i+4])
686			i += 4
687			flags = int(flags)
688			components.append(glyfTable.getGlyphName(int(glyphID)))
689
690			if flags & ARG_1_AND_2_ARE_WORDS: i += 4
691			else: i += 2
692			if flags & WE_HAVE_A_SCALE: i += 2
693			elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
694			elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
695			more = flags & MORE_COMPONENTS
696
697		return components
698
699	def removeHinting(self):
700		if not hasattr(self, "data"):
701			self.program = ttProgram.Program()
702			self.program.fromBytecode([])
703			return
704
705		# Remove instructions without expanding glyph
706
707		if not self.data:
708			return
709		numContours = struct.unpack(">h", self.data[:2])[0]
710		data = array.array("B", self.data)
711		i = 10
712		if numContours >= 0:
713			i += 2 * numContours # endPtsOfContours
714			nCoordinates = ((data[i-2] << 8) | data[i-1]) + 1
715			instructionLen = (data[i] << 8) | data[i+1]
716			# Zero it
717			data[i] = data [i+1] = 0
718			i += 2
719			if instructionLen:
720				# Splice it out
721				data = data[:i] + data[i+instructionLen:]
722				if instructionLen % 4:
723					# We now have to go ahead and drop
724					# the old padding.  Otherwise with
725					# padding we have to add, we may
726					# end up with more than 3 bytes of
727					# padding.
728					coordBytes = 0
729					j = 0
730					while True:
731						flag = data[i]
732						i = i + 1
733						repeat = 1
734						if flag & flagRepeat:
735							repeat = data[i] + 1
736							i = i + 1
737						xBytes = yBytes = 0
738						if flag & flagXShort:
739							xBytes = 1
740						elif not (flag & flagXsame):
741							xBytes = 2
742						if flag & flagYShort:
743							yBytes = 1
744						elif not (flag & flagYsame):
745							yBytes = 2
746						coordBytes += (xBytes + yBytes) * repeat
747						j += repeat
748						if j >= nCoordinates:
749							break
750					assert j == nCoordinates, "bad glyph flags"
751					data = data[:i + coordBytes]
752		else:
753			more = 1
754			while more:
755				flags =(data[i] << 8) | data[i+1]
756				# Turn instruction flag off
757				flags &= ~WE_HAVE_INSTRUCTIONS
758				data[i+0] = flags >> 8
759				data[i+1] = flags & 0xFF
760				i += 4
761				flags = int(flags)
762
763				if flags & ARG_1_AND_2_ARE_WORDS: i += 4
764				else: i += 2
765				if flags & WE_HAVE_A_SCALE: i += 2
766				elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
767				elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
768				more = flags & MORE_COMPONENTS
769
770			# Cut off
771			data = data[:i]
772
773		data = data.tostring()
774
775		if len(data) % 4:
776			# add pad bytes
777			nPadBytes = 4 - (len(data) % 4)
778			data = data + b"\0" * nPadBytes
779
780		self.data = data
781
782	def __ne__(self, other):
783		return not self.__eq__(other)
784	def __eq__(self, other):
785		if type(self) != type(other):
786			return NotImplemented
787		return self.__dict__ == other.__dict__
788
789
790class GlyphComponent(object):
791
792	def __init__(self):
793		pass
794
795	def getComponentInfo(self):
796		"""Return the base glyph name and a transform."""
797		# XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
798		# something equivalent in fontTools.objects.glyph (I'd rather not
799		# convert it to an absolute offset, since it is valuable information).
800		# This method will now raise "AttributeError: x" on glyphs that use
801		# this TT feature.
802		if hasattr(self, "transform"):
803			[[xx, xy], [yx, yy]] = self.transform
804			trans = (xx, xy, yx, yy, self.x, self.y)
805		else:
806			trans = (1, 0, 0, 1, self.x, self.y)
807		return self.glyphName, trans
808
809	def decompile(self, data, glyfTable):
810		flags, glyphID = struct.unpack(">HH", data[:4])
811		self.flags = int(flags)
812		glyphID = int(glyphID)
813		self.glyphName = glyfTable.getGlyphName(int(glyphID))
814		#print ">>", reprflag(self.flags)
815		data = data[4:]
816
817		if self.flags & ARG_1_AND_2_ARE_WORDS:
818			if self.flags & ARGS_ARE_XY_VALUES:
819				self.x, self.y = struct.unpack(">hh", data[:4])
820			else:
821				x, y = struct.unpack(">HH", data[:4])
822				self.firstPt, self.secondPt = int(x), int(y)
823			data = data[4:]
824		else:
825			if self.flags & ARGS_ARE_XY_VALUES:
826				self.x, self.y = struct.unpack(">bb", data[:2])
827			else:
828				x, y = struct.unpack(">BB", data[:2])
829				self.firstPt, self.secondPt = int(x), int(y)
830			data = data[2:]
831
832		if self.flags & WE_HAVE_A_SCALE:
833			scale, = struct.unpack(">h", data[:2])
834			self.transform = [[fi2fl(scale,14), 0], [0, fi2fl(scale,14)]]  # fixed 2.14
835			data = data[2:]
836		elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
837			xscale, yscale = struct.unpack(">hh", data[:4])
838			self.transform = [[fi2fl(xscale,14), 0], [0, fi2fl(yscale,14)]]  # fixed 2.14
839			data = data[4:]
840		elif self.flags & WE_HAVE_A_TWO_BY_TWO:
841			(xscale, scale01,
842					scale10, yscale) = struct.unpack(">hhhh", data[:8])
843			self.transform = [[fi2fl(xscale,14), fi2fl(scale01,14)],
844					  [fi2fl(scale10,14), fi2fl(yscale,14)]] # fixed 2.14
845			data = data[8:]
846		more = self.flags & MORE_COMPONENTS
847		haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
848		self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
849				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
850				NON_OVERLAPPING)
851		return more, haveInstructions, data
852
853	def compile(self, more, haveInstructions, glyfTable):
854		data = b""
855
856		# reset all flags we will calculate ourselves
857		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
858				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
859				NON_OVERLAPPING)
860		if more:
861			flags = flags | MORE_COMPONENTS
862		if haveInstructions:
863			flags = flags | WE_HAVE_INSTRUCTIONS
864
865		if hasattr(self, "firstPt"):
866			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
867				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
868			else:
869				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
870				flags = flags | ARG_1_AND_2_ARE_WORDS
871		else:
872			flags = flags | ARGS_ARE_XY_VALUES
873			if (-128 <= self.x <= 127) and (-128 <= self.y <= 127):
874				data = data + struct.pack(">bb", self.x, self.y)
875			else:
876				data = data + struct.pack(">hh", self.x, self.y)
877				flags = flags | ARG_1_AND_2_ARE_WORDS
878
879		if hasattr(self, "transform"):
880			transform = [[fl2fi(x,14) for x in row] for row in self.transform]
881			if transform[0][1] or transform[1][0]:
882				flags = flags | WE_HAVE_A_TWO_BY_TWO
883				data = data + struct.pack(">hhhh",
884						transform[0][0], transform[0][1],
885						transform[1][0], transform[1][1])
886			elif transform[0][0] != transform[1][1]:
887				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
888				data = data + struct.pack(">hh",
889						transform[0][0], transform[1][1])
890			else:
891				flags = flags | WE_HAVE_A_SCALE
892				data = data + struct.pack(">h",
893						transform[0][0])
894
895		glyphID = glyfTable.getGlyphID(self.glyphName)
896		return struct.pack(">HH", flags, glyphID) + data
897
898	def toXML(self, writer, ttFont):
899		attrs = [("glyphName", self.glyphName)]
900		if not hasattr(self, "firstPt"):
901			attrs = attrs + [("x", self.x), ("y", self.y)]
902		else:
903			attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
904
905		if hasattr(self, "transform"):
906			transform = self.transform
907			if transform[0][1] or transform[1][0]:
908				attrs = attrs + [
909						("scalex", transform[0][0]), ("scale01", transform[0][1]),
910						("scale10", transform[1][0]), ("scaley", transform[1][1]),
911						]
912			elif transform[0][0] != transform[1][1]:
913				attrs = attrs + [
914						("scalex", transform[0][0]), ("scaley", transform[1][1]),
915						]
916			else:
917				attrs = attrs + [("scale", transform[0][0])]
918		attrs = attrs + [("flags", hex(self.flags))]
919		writer.simpletag("component", attrs)
920		writer.newline()
921
922	def fromXML(self, name, attrs, content, ttFont):
923		self.glyphName = attrs["glyphName"]
924		if "firstPt" in attrs:
925			self.firstPt = safeEval(attrs["firstPt"])
926			self.secondPt = safeEval(attrs["secondPt"])
927		else:
928			self.x = safeEval(attrs["x"])
929			self.y = safeEval(attrs["y"])
930		if "scale01" in attrs:
931			scalex = safeEval(attrs["scalex"])
932			scale01 = safeEval(attrs["scale01"])
933			scale10 = safeEval(attrs["scale10"])
934			scaley = safeEval(attrs["scaley"])
935			self.transform = [[scalex, scale01], [scale10, scaley]]
936		elif "scalex" in attrs:
937			scalex = safeEval(attrs["scalex"])
938			scaley = safeEval(attrs["scaley"])
939			self.transform = [[scalex, 0], [0, scaley]]
940		elif "scale" in attrs:
941			scale = safeEval(attrs["scale"])
942			self.transform = [[scale, 0], [0, scale]]
943		self.flags = safeEval(attrs["flags"])
944
945	def __ne__(self, other):
946		return not self.__eq__(other)
947	def __eq__(self, other):
948		if type(self) != type(other):
949			return NotImplemented
950		return self.__dict__ == other.__dict__
951
952class GlyphCoordinates(object):
953
954	def __init__(self, iterable=[]):
955		self._a = array.array("h")
956		self.extend(iterable)
957
958	def isFloat(self):
959		return self._a.typecode == 'f'
960
961	def _ensureFloat(self):
962		if self.isFloat():
963			return
964		self._a = array.array("f", self._a)
965
966	def _checkFloat(self, p):
967		if any(isinstance(v, float) for v in p):
968			p = [int(v) if int(v) == v else v for v in p]
969			if any(isinstance(v, float) for v in p):
970				self._ensureFloat()
971		return p
972
973	@staticmethod
974	def zeros(count):
975		return GlyphCoordinates([(0,0)] * count)
976
977	def copy(self):
978		c = GlyphCoordinates()
979		c._a.extend(self._a)
980		return c
981
982	def __len__(self):
983		return len(self._a) // 2
984
985	def __getitem__(self, k):
986		if isinstance(k, slice):
987			indices = range(*k.indices(len(self)))
988			return [self[i] for i in indices]
989		return self._a[2*k],self._a[2*k+1]
990
991	def __setitem__(self, k, v):
992		if isinstance(k, slice):
993			indices = range(*k.indices(len(self)))
994			# XXX This only works if len(v) == len(indices)
995			# TODO Implement __delitem__
996			for j,i in enumerate(indices):
997				self[i] = v[j]
998			return
999		v = self._checkFloat(v)
1000		self._a[2*k],self._a[2*k+1] = v
1001
1002	def __repr__(self):
1003		return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])'
1004
1005	def append(self, p):
1006		p = self._checkFloat(p)
1007		self._a.extend(tuple(p))
1008
1009	def extend(self, iterable):
1010		for p in iterable:
1011			p = self._checkFloat(p)
1012			self._a.extend(p)
1013
1014	def relativeToAbsolute(self):
1015		a = self._a
1016		x,y = 0,0
1017		for i in range(len(a) // 2):
1018			a[2*i  ] = x = a[2*i  ] + x
1019			a[2*i+1] = y = a[2*i+1] + y
1020
1021	def absoluteToRelative(self):
1022		a = self._a
1023		x,y = 0,0
1024		for i in range(len(a) // 2):
1025			dx = a[2*i  ] - x
1026			dy = a[2*i+1] - y
1027			x = a[2*i  ]
1028			y = a[2*i+1]
1029			a[2*i  ] = dx
1030			a[2*i+1] = dy
1031
1032	def translate(self, p):
1033		(x,y) = p
1034		a = self._a
1035		for i in range(len(a) // 2):
1036			a[2*i  ] += x
1037			a[2*i+1] += y
1038
1039	def transform(self, t):
1040		a = self._a
1041		for i in range(len(a) // 2):
1042			x = a[2*i  ]
1043			y = a[2*i+1]
1044			px = x * t[0][0] + y * t[1][0]
1045			py = x * t[0][1] + y * t[1][1]
1046			self[i] = (px, py)
1047
1048	def __ne__(self, other):
1049		return not self.__eq__(other)
1050	def __eq__(self, other):
1051		if type(self) != type(other):
1052			return NotImplemented
1053		return self._a == other._a
1054
1055
1056def reprflag(flag):
1057	bin = ""
1058	if isinstance(flag, str):
1059		flag = byteord(flag)
1060	while flag:
1061		if flag & 0x01:
1062			bin = "1" + bin
1063		else:
1064			bin = "0" + bin
1065		flag = flag >> 1
1066	bin = (14 - len(bin)) * "0" + bin
1067	return bin
1068
1069