_g_l_y_f.py revision ff6a25cdb99c86ce7d60c6f6780178fdbfa8674b
1"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
2
3
4#
5# The Apple and MS rasterizers behave differently for
6# scaled composite components: one does scale first and then translate
7# and the other does it vice versa. MS defined some flags to indicate
8# the difference, but it seems nobody actually _sets_ those flags.
9#
10# Funny thing: Apple seems to _only_ do their thing in the
11# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
12# (eg. Charcoal)...
13#
14SCALE_COMPONENT_OFFSET_DEFAULT = 0   # 0 == MS, 1 == Apple
15
16
17import struct, sstruct
18import DefaultTable
19from fontTools import ttLib
20from fontTools.misc.textTools import safeEval, readHex
21import ttProgram
22import array
23import Numeric
24from types import StringType, TupleType
25
26
27class table__g_l_y_f(DefaultTable.DefaultTable):
28
29	def decompile(self, data, ttFont):
30		loca = ttFont['loca']
31		last = loca[0]
32		self.glyphs = {}
33		self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
34		for i in range(0, len(loca)-1):
35			glyphName = glyphOrder[i]
36			next = loca[i+1]
37			glyphdata = data[last:next]
38			if len(glyphdata) <> (next - last):
39				raise ttLib.TTLibError, "not enough 'glyf' table data"
40			glyph = Glyph(glyphdata)
41			self.glyphs[glyphName] = glyph
42			last = next
43		# this should become a warning:
44		#if len(data) > next:
45		#	raise ttLib.TTLibError, "too much 'glyf' table data"
46
47	def compile(self, ttFont):
48		if not hasattr(self, "glyphOrder"):
49			self.glyphOrder = ttFont.getGlyphOrder()
50		import string
51		locations = []
52		currentLocation = 0
53		dataList = []
54		recalcBBoxes = ttFont.recalcBBoxes
55		for glyphName in self.glyphOrder:
56			glyph = self.glyphs[glyphName]
57			glyphData = glyph.compile(self, recalcBBoxes)
58			locations.append(currentLocation)
59			currentLocation = currentLocation + len(glyphData)
60			dataList.append(glyphData)
61		locations.append(currentLocation)
62		data = string.join(dataList, "")
63		ttFont['loca'].set(locations)
64		ttFont['maxp'].numGlyphs = len(self.glyphs)
65		return data
66
67	def toXML(self, writer, ttFont, progress=None):
68		writer.newline()
69		glyphNames = ttFont.getGlyphNames()
70		writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.")
71		writer.newline()
72		writer.newline()
73		counter = 0
74		progressStep = 10
75		numGlyphs = len(glyphNames)
76		for glyphName in glyphNames:
77			if not counter % progressStep and progress is not None:
78				progress.setLabel("Dumping 'glyf' table... (%s)" % glyphName)
79				progress.increment(progressStep / float(numGlyphs))
80			counter = counter + 1
81			glyph = self[glyphName]
82			if glyph.numberOfContours:
83				writer.begintag('TTGlyph', [
84						("name", glyphName),
85						("xMin", glyph.xMin),
86						("yMin", glyph.yMin),
87						("xMax", glyph.xMax),
88						("yMax", glyph.yMax),
89						])
90				writer.newline()
91				glyph.toXML(writer, ttFont)
92				writer.endtag('TTGlyph')
93				writer.newline()
94			else:
95				writer.simpletag('TTGlyph', name=glyphName)
96				writer.comment("contains no outline data")
97				writer.newline()
98			writer.newline()
99
100	def fromXML(self, (name, attrs, content), ttFont):
101		if name <> "TTGlyph":
102			return
103		if not hasattr(self, "glyphs"):
104			self.glyphs = {}
105		if not hasattr(self, "glyphOrder"):
106			self.glyphOrder = ttFont.getGlyphOrder()
107		glyphName = attrs["name"]
108		if ttFont.verbose:
109			ttLib.debugmsg("unpacking glyph '%s'" % glyphName)
110		glyph = Glyph()
111		for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
112			setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
113		self.glyphs[glyphName] = glyph
114		for element in content:
115			if type(element) <> TupleType:
116				continue
117			glyph.fromXML(element, ttFont)
118		if not ttFont.recalcBBoxes:
119			glyph.compact(self, 0)
120
121	def setGlyphOrder(self, glyphOrder):
122		self.glyphOrder = glyphOrder
123
124	def getGlyphName(self, glyphID):
125		return self.glyphOrder[glyphID]
126
127	def getGlyphID(self, glyphName):
128		# XXX optimize with reverse dict!!!
129		return self.glyphOrder.index(glyphName)
130
131	def __getitem__(self, glyphName):
132		glyph = self.glyphs[glyphName]
133		glyph.expand(self)
134		return glyph
135
136	def __setitem__(self, glyphName, glyph):
137		self.glyphs[glyphName] = glyph
138		if glyphName not in self.glyphOrder:
139			self.glyphOrder.append(glyphName)
140
141	def __delitem__(self, glyphName):
142		del self.glyphs[glyphName]
143		self.glyphOrder.remove(glyphName)
144
145	def __len__(self):
146		assert len(self.glyphOrder) == len(self.glyphs)
147		return len(self.glyphs)
148
149
150glyphHeaderFormat = """
151		>	# big endian
152		numberOfContours:	h
153		xMin:				h
154		yMin:				h
155		xMax:				h
156		yMax:				h
157"""
158
159# flags
160flagOnCurve = 0x01
161flagXShort = 0x02
162flagYShort = 0x04
163flagRepeat = 0x08
164flagXsame =  0x10
165flagYsame = 0x20
166flagReserved1 = 0x40
167flagReserved2 = 0x80
168
169
170ARG_1_AND_2_ARE_WORDS      = 0x0001  # if set args are words otherwise they are bytes
171ARGS_ARE_XY_VALUES         = 0x0002  # if set args are xy values, otherwise they are points
172ROUND_XY_TO_GRID           = 0x0004  # for the xy values if above is true
173WE_HAVE_A_SCALE            = 0x0008  # Sx = Sy, otherwise scale == 1.0
174NON_OVERLAPPING            = 0x0010  # set to same value for all components (obsolete!)
175MORE_COMPONENTS            = 0x0020  # indicates at least one more glyph after this one
176WE_HAVE_AN_X_AND_Y_SCALE   = 0x0040  # Sx, Sy
177WE_HAVE_A_TWO_BY_TWO       = 0x0080  # t00, t01, t10, t11
178WE_HAVE_INSTRUCTIONS       = 0x0100  # instructions follow
179USE_MY_METRICS             = 0x0200  # apply these metrics to parent glyph
180OVERLAP_COMPOUND           = 0x0400  # used by Apple in GX fonts
181SCALED_COMPONENT_OFFSET    = 0x0800  # composite designed to have the component offset scaled (designed for Apple)
182UNSCALED_COMPONENT_OFFSET  = 0x1000  # composite designed not to have the component offset scaled (designed for MS)
183
184
185class Glyph:
186
187	def __init__(self, data=""):
188		if not data:
189			# empty char
190			self.numberOfContours = 0
191			return
192		self.data = data
193
194	def compact(self, glyfTable, recalcBBoxes=1):
195		data = self.compile(glyfTable, recalcBBoxes)
196		self.__dict__.clear()
197		self.data = data
198
199	def expand(self, glyfTable):
200		if not hasattr(self, "data"):
201			# already unpacked
202			return
203		if not self.data:
204			# empty char
205			self.numberOfContours = 0
206			return
207		dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
208		del self.data
209		if self.isComposite():
210			self.decompileComponents(data, glyfTable)
211		else:
212			self.decompileCoordinates(data)
213
214	def compile(self, glyfTable, recalcBBoxes=1):
215		if hasattr(self, "data"):
216			return self.data
217		if self.numberOfContours == 0:
218			return ""
219		if recalcBBoxes:
220			self.recalcBounds(glyfTable)
221		data = sstruct.pack(glyphHeaderFormat, self)
222		if self.isComposite():
223			data = data + self.compileComponents(glyfTable)
224		else:
225			data = data + self.compileCoordinates()
226		# From the spec: "Note that the local offsets should be word-aligned"
227		# From a later MS spec: "Note that the local offsets should be long-aligned"
228		# Let's be modern and align on 4-byte boundaries.
229		if len(data) % 4:
230			# add pad bytes
231			nPadBytes = 4 - (len(data) % 4)
232			data = data + "\0" * nPadBytes
233		return data
234
235	def toXML(self, writer, ttFont):
236		if self.isComposite():
237			for compo in self.components:
238				compo.toXML(writer, ttFont)
239			if hasattr(self, "program"):
240				writer.begintag("instructions")
241				self.program.toXML(writer, ttFont)
242				writer.endtag("instructions")
243				writer.newline()
244		else:
245			last = 0
246			for i in range(self.numberOfContours):
247				writer.begintag("contour")
248				writer.newline()
249				for j in range(last, self.endPtsOfContours[i] + 1):
250					writer.simpletag("pt", [
251							("x", self.coordinates[j][0]),
252							("y", self.coordinates[j][1]),
253							("on", self.flags[j] & flagOnCurve)])
254					writer.newline()
255				last = self.endPtsOfContours[i] + 1
256				writer.endtag("contour")
257				writer.newline()
258			if self.numberOfContours:
259				writer.begintag("instructions")
260				self.program.toXML(writer, ttFont)
261				writer.endtag("instructions")
262				writer.newline()
263
264	def fromXML(self, (name, attrs, content), ttFont):
265		if name == "contour":
266			self.numberOfContours = self.numberOfContours + 1
267			if self.numberOfContours < 0:
268				raise ttLib.TTLibError, "can't mix composites and contours in glyph"
269			coordinates = []
270			flags = []
271			for element in content:
272				if type(element) <> TupleType:
273					continue
274				name, attrs, content = element
275				if name <> "pt":
276					continue  # ignore anything but "pt"
277				coordinates.append([safeEval(attrs["x"]), safeEval(attrs["y"])])
278				flags.append(not not safeEval(attrs["on"]))
279			coordinates = Numeric.array(coordinates, Numeric.Int16)
280			flags = Numeric.array(flags, Numeric.Int8)
281			if not hasattr(self, "coordinates"):
282				self.coordinates = coordinates
283				self.flags = flags
284				self.endPtsOfContours = [len(coordinates)-1]
285			else:
286				self.coordinates = Numeric.concatenate((self.coordinates, coordinates))
287				self.flags = Numeric.concatenate((self.flags, flags))
288				self.endPtsOfContours.append(len(self.coordinates)-1)
289		elif name == "component":
290			if self.numberOfContours > 0:
291				raise ttLib.TTLibError, "can't mix composites and contours in glyph"
292			self.numberOfContours = -1
293			if not hasattr(self, "components"):
294				self.components = []
295			component = GlyphComponent()
296			self.components.append(component)
297			component.fromXML((name, attrs, content), ttFont)
298		elif name == "instructions":
299			self.program = ttProgram.Program()
300			for element in content:
301				if type(element) <> TupleType:
302					continue
303				self.program.fromXML(element, ttFont)
304
305	def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
306		assert self.isComposite()
307		nContours = 0
308		nPoints = 0
309		for compo in self.components:
310			baseGlyph = glyfTable[compo.glyphName]
311			if baseGlyph.numberOfContours == 0:
312				continue
313			elif baseGlyph.numberOfContours > 0:
314				nP, nC = baseGlyph.getMaxpValues()
315			else:
316				nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
317						glyfTable, maxComponentDepth + 1)
318			nPoints = nPoints + nP
319			nContours = nContours + nC
320		return nPoints, nContours, maxComponentDepth
321
322	def getMaxpValues(self):
323		assert self.numberOfContours > 0
324		return len(self.coordinates), len(self.endPtsOfContours)
325
326	def decompileComponents(self, data, glyfTable):
327		self.components = []
328		more = 1
329		haveInstructions = 0
330		while more:
331			component = GlyphComponent()
332			more, haveInstr, data = component.decompile(data, glyfTable)
333			haveInstructions = haveInstructions | haveInstr
334			self.components.append(component)
335		if haveInstructions:
336			numInstructions, = struct.unpack(">h", data[:2])
337			data = data[2:]
338			self.program = ttProgram.Program()
339			self.program.fromBytecode(data[:numInstructions])
340			data = data[numInstructions:]
341			assert len(data) < 4, "bad composite data"
342
343	def decompileCoordinates(self, data):
344		endPtsOfContours = array.array("h")
345		endPtsOfContours.fromstring(data[:2*self.numberOfContours])
346		if ttLib.endian <> "big":
347			endPtsOfContours.byteswap()
348		self.endPtsOfContours = endPtsOfContours.tolist()
349
350		data = data[2*self.numberOfContours:]
351
352		instructionLength, = struct.unpack(">h", data[:2])
353		data = data[2:]
354		self.program = ttProgram.Program()
355		self.program.fromBytecode(data[:instructionLength])
356		data = data[instructionLength:]
357		nCoordinates = self.endPtsOfContours[-1] + 1
358		flags, xCoordinates, yCoordinates = \
359				self.decompileCoordinatesRaw(nCoordinates, data)
360
361		# fill in repetitions and apply signs
362		coordinates = Numeric.zeros((nCoordinates, 2), Numeric.Int16)
363		xIndex = 0
364		yIndex = 0
365		for i in range(nCoordinates):
366			flag = flags[i]
367			# x coordinate
368			if flag & flagXShort:
369				if flag & flagXsame:
370					x = xCoordinates[xIndex]
371				else:
372					x = -xCoordinates[xIndex]
373				xIndex = xIndex + 1
374			elif flag & flagXsame:
375				x = 0
376			else:
377				x = xCoordinates[xIndex]
378				xIndex = xIndex + 1
379			# y coordinate
380			if flag & flagYShort:
381				if flag & flagYsame:
382					y = yCoordinates[yIndex]
383				else:
384					y = -yCoordinates[yIndex]
385				yIndex = yIndex + 1
386			elif flag & flagYsame:
387				y = 0
388			else:
389				y = yCoordinates[yIndex]
390				yIndex = yIndex + 1
391			coordinates[i] = (x, y)
392		assert xIndex == len(xCoordinates)
393		assert yIndex == len(yCoordinates)
394		# convert relative to absolute coordinates
395		self.coordinates = Numeric.add.accumulate(coordinates)
396		# discard all flags but for "flagOnCurve"
397		if hasattr(Numeric, "__version__"):
398			self.flags = Numeric.bitwise_and(flags, flagOnCurve).astype(Numeric.Int8)
399		else:
400			self.flags = Numeric.boolean_and(flags, flagOnCurve).astype(Numeric.Int8)
401
402	def decompileCoordinatesRaw(self, nCoordinates, data):
403		# unpack flags and prepare unpacking of coordinates
404		flags = Numeric.array([0] * nCoordinates, Numeric.Int8)
405		# Warning: deep Python trickery going on. We use the struct module to unpack
406		# the coordinates. We build a format string based on the flags, so we can
407		# unpack the coordinates in one struct.unpack() call.
408		xFormat = ">" # big endian
409		yFormat = ">" # big endian
410		i = j = 0
411		while 1:
412			flag = ord(data[i])
413			i = i + 1
414			repeat = 1
415			if flag & flagRepeat:
416				repeat = ord(data[i]) + 1
417				i = i + 1
418			for k in range(repeat):
419				if flag & flagXShort:
420					xFormat = xFormat + 'B'
421				elif not (flag & flagXsame):
422					xFormat = xFormat + 'h'
423				if flag & flagYShort:
424					yFormat = yFormat + 'B'
425				elif not (flag & flagYsame):
426					yFormat = yFormat + 'h'
427				flags[j] = flag
428				j = j + 1
429			if j >= nCoordinates:
430				break
431		assert j == nCoordinates, "bad glyph flags"
432		data = data[i:]
433		# unpack raw coordinates, krrrrrr-tching!
434		xDataLen = struct.calcsize(xFormat)
435		yDataLen = struct.calcsize(yFormat)
436		if not (0 <= (len(data) - (xDataLen + yDataLen)) < 4):
437			raise ttLib.TTLibError, "bad glyph record (leftover bytes: %s)" % (len(data) - (xDataLen + yDataLen))
438		xCoordinates = struct.unpack(xFormat, data[:xDataLen])
439		yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
440		return flags, xCoordinates, yCoordinates
441
442	def compileComponents(self, glyfTable):
443		data = ""
444		lastcomponent = len(self.components) - 1
445		more = 1
446		haveInstructions = 0
447		for i in range(len(self.components)):
448			if i == lastcomponent:
449				haveInstructions = hasattr(self, "program")
450				more = 0
451			compo = self.components[i]
452			data = data + compo.compile(more, haveInstructions, glyfTable)
453		if haveInstructions:
454			instructions = self.program.getBytecode()
455			data = data + struct.pack(">h", len(instructions)) + instructions
456		return data
457
458
459	def compileCoordinates(self):
460		assert len(self.coordinates) == len(self.flags)
461		data = ""
462		endPtsOfContours = array.array("h", self.endPtsOfContours)
463		if ttLib.endian <> "big":
464			endPtsOfContours.byteswap()
465		data = data + endPtsOfContours.tostring()
466		instructions = self.program.getBytecode()
467		data = data + struct.pack(">h", len(instructions)) + instructions
468		nCoordinates = len(self.coordinates)
469
470		# make a copy
471		coordinates = self.coordinates.astype(self.coordinates.typecode())
472		# absolute to relative coordinates
473		coordinates[1:] = Numeric.subtract(coordinates[1:], coordinates[:-1])
474		flags = self.flags
475		compressedflags = []
476		xPoints = []
477		yPoints = []
478		xFormat = ">"
479		yFormat = ">"
480		lastflag = None
481		repeat = 0
482		for i in range(len(coordinates)):
483			# Oh, the horrors of TrueType
484			flag = self.flags[i]
485			x, y = coordinates[i]
486			# do x
487			if x == 0:
488				flag = flag | flagXsame
489			elif -255 <= x <= 255:
490				flag = flag | flagXShort
491				if x > 0:
492					flag = flag | flagXsame
493				else:
494					x = -x
495				xPoints.append(x)
496				xFormat = xFormat + 'B'
497			else:
498				xPoints.append(x)
499				xFormat = xFormat + 'h'
500			# do y
501			if y == 0:
502				flag = flag | flagYsame
503			elif -255 <= y <= 255:
504				flag = flag | flagYShort
505				if y > 0:
506					flag = flag | flagYsame
507				else:
508					y = -y
509				yPoints.append(y)
510				yFormat = yFormat + 'B'
511			else:
512				yPoints.append(y)
513				yFormat = yFormat + 'h'
514			# handle repeating flags
515			if flag == lastflag:
516				repeat = repeat + 1
517				if repeat == 1:
518					compressedflags.append(flag)
519				elif repeat > 1:
520					compressedflags[-2] = flag | flagRepeat
521					compressedflags[-1] = repeat
522				else:
523					compressedflags[-1] = repeat
524			else:
525				repeat = 0
526				compressedflags.append(flag)
527			lastflag = flag
528		data = data + array.array("B", compressedflags).tostring()
529		data = data + apply(struct.pack, (xFormat,)+tuple(xPoints))
530		data = data + apply(struct.pack, (yFormat,)+tuple(yPoints))
531		return data
532
533	def recalcBounds(self, glyfTable):
534		coordinates, endPts, flags = self.getCoordinates(glyfTable)
535		if len(coordinates) > 0:
536			self.xMin, self.yMin = Numeric.minimum.reduce(coordinates)
537			self.xMax, self.yMax = Numeric.maximum.reduce(coordinates)
538		else:
539			self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0)
540
541	def isComposite(self):
542		return self.numberOfContours == -1
543
544	def __getitem__(self, componentIndex):
545		if not self.isComposite():
546			raise ttLib.TTLibError, "can't use glyph as sequence"
547		return self.components[componentIndex]
548
549	def getCoordinates(self, glyfTable):
550		if self.numberOfContours > 0:
551			return self.coordinates, self.endPtsOfContours, self.flags
552		elif self.isComposite():
553			# it's a composite
554			allCoords = None
555			allFlags = None
556			allEndPts = None
557			for compo in self.components:
558				g = glyfTable[compo.glyphName]
559				coordinates, endPts, flags = g.getCoordinates(glyfTable)
560				if hasattr(compo, "firstPt"):
561					# move according to two reference points
562					move = allCoords[compo.firstPt] - coordinates[compo.secondPt]
563				else:
564					move = compo.x, compo.y
565
566				if not hasattr(compo, "transform"):
567					if len(coordinates) > 0:
568						coordinates = coordinates + move  # I love NumPy!
569				else:
570					apple_way = compo.flags & SCALED_COMPONENT_OFFSET
571					ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
572					assert not (apple_way and ms_way)
573					if not (apple_way or ms_way):
574						scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT  # see top of this file
575					else:
576						scale_component_offset = apple_way
577					if scale_component_offset:
578						# the Apple way: first move, then scale (ie. scale the component offset)
579						coordinates = coordinates + move
580						coordinates = Numeric.dot(coordinates, compo.transform)
581					else:
582						# the MS way: first scale, then move
583						coordinates = Numeric.dot(coordinates, compo.transform)
584						coordinates = coordinates + move
585					# due to the transformation the coords. are now floats;
586					# round them off nicely, and cast to short
587					coordinates = Numeric.floor(coordinates + 0.5).astype(Numeric.Int16)
588				if allCoords is None or len(allCoords) == 0:
589					allCoords = coordinates
590					allEndPts = endPts
591					allFlags = flags
592				else:
593					allEndPts = allEndPts + (Numeric.array(endPts) + len(allCoords)).tolist()
594					if len(coordinates) > 0:
595						allCoords = Numeric.concatenate((allCoords, coordinates))
596						allFlags = Numeric.concatenate((allFlags, flags))
597			return allCoords, allEndPts, allFlags
598		else:
599			return Numeric.array([], Numeric.Int16), [], Numeric.array([], Numeric.Int8)
600
601	def __cmp__(self, other):
602		if self.numberOfContours <= 0:
603			return cmp(self.__dict__, other.__dict__)
604		else:
605			if cmp(len(self.coordinates), len(other.coordinates)):
606				return 1
607			ctest = Numeric.alltrue(Numeric.alltrue(Numeric.equal(self.coordinates, other.coordinates)))
608			ftest = Numeric.alltrue(Numeric.equal(self.flags, other.flags))
609			if not ctest or not ftest:
610				return 1
611			return (
612					cmp(self.endPtsOfContours, other.endPtsOfContours) or
613					cmp(self.program, other.instructions)
614				)
615
616
617class GlyphComponent:
618
619	def __init__(self):
620		pass
621
622	def getComponentInfo(self):
623		"""Return the base glyph name and a Transformation object."""
624		# XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
625		# something equivalent in fontTools.objects.glyph (I'd rather not
626		# convert it to an absolute offset, since it is valuable information).
627		# This method will now raise "AttributeError: x" on glyphs that use
628		# this TT feature.
629		if hasattr(self, "transform"):
630			[[xx, xy], [yx, yy]] = self.transform
631			trans = (xx, xy, yx, yy, self.x, self.y)
632		else:
633			trans = (1, 0, 0, 1, self.x, self.y)
634		return self.glyphName, trans
635
636	def decompile(self, data, glyfTable):
637		flags, glyphID = struct.unpack(">HH", data[:4])
638		self.flags = int(flags)
639		glyphID = int(glyphID)
640		self.glyphName = glyfTable.getGlyphName(int(glyphID))
641		#print ">>", reprflag(self.flags)
642		data = data[4:]
643
644		if self.flags & ARG_1_AND_2_ARE_WORDS:
645			if self.flags & ARGS_ARE_XY_VALUES:
646				self.x, self.y = struct.unpack(">hh", data[:4])
647			else:
648				x, y = struct.unpack(">HH", data[:4])
649				self.firstPt, self.secondPt = int(x), int(y)
650			data = data[4:]
651		else:
652			if self.flags & ARGS_ARE_XY_VALUES:
653				self.x, self.y = struct.unpack(">bb", data[:2])
654			else:
655				x, y = struct.unpack(">BB", data[:2])
656				self.firstPt, self.secondPt = int(x), int(y)
657			data = data[2:]
658
659		if self.flags & WE_HAVE_A_SCALE:
660			scale, = struct.unpack(">h", data[:2])
661			self.transform = Numeric.array(
662					[[scale, 0], [0, scale]]) / float(0x4000)  # fixed 2.14
663			data = data[2:]
664		elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
665			xscale, yscale = struct.unpack(">hh", data[:4])
666			self.transform = Numeric.array(
667					[[xscale, 0], [0, yscale]]) / float(0x4000)  # fixed 2.14
668			data = data[4:]
669		elif self.flags & WE_HAVE_A_TWO_BY_TWO:
670			(xscale, scale01,
671					scale10, yscale) = struct.unpack(">hhhh", data[:8])
672			self.transform = Numeric.array(
673					[[xscale, scale01], [scale10, yscale]]) / float(0x4000)  # fixed 2.14
674			data = data[8:]
675		more = self.flags & MORE_COMPONENTS
676		haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
677		self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
678				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
679				NON_OVERLAPPING)
680		return more, haveInstructions, data
681
682	def compile(self, more, haveInstructions, glyfTable):
683		data = ""
684
685		# reset all flags we will calculate ourselves
686		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
687				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
688				NON_OVERLAPPING)
689		if more:
690			flags = flags | MORE_COMPONENTS
691		if haveInstructions:
692			flags = flags | WE_HAVE_INSTRUCTIONS
693
694		if hasattr(self, "firstPt"):
695			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
696				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
697			else:
698				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
699				flags = flags | ARG_1_AND_2_ARE_WORDS
700		else:
701			flags = flags | ARGS_ARE_XY_VALUES
702			if (-128 <= self.x <= 127) and (-128 <= self.y <= 127):
703				data = data + struct.pack(">bb", self.x, self.y)
704			else:
705				data = data + struct.pack(">hh", self.x, self.y)
706				flags = flags | ARG_1_AND_2_ARE_WORDS
707
708		if hasattr(self, "transform"):
709			# XXX needs more testing
710			transform = Numeric.floor(self.transform * 0x4000 + 0.5)
711			if transform[0][1] or transform[1][0]:
712				flags = flags | WE_HAVE_A_TWO_BY_TWO
713				data = data + struct.pack(">hhhh",
714						transform[0][0], transform[0][1],
715						transform[1][0], transform[1][1])
716			elif transform[0][0] <> transform[1][1]:
717				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
718				data = data + struct.pack(">hh",
719						transform[0][0], transform[1][1])
720			else:
721				flags = flags | WE_HAVE_A_SCALE
722				data = data + struct.pack(">h",
723						transform[0][0])
724
725		glyphID = glyfTable.getGlyphID(self.glyphName)
726		return struct.pack(">HH", flags, glyphID) + data
727
728	def toXML(self, writer, ttFont):
729		attrs = [("glyphName", self.glyphName)]
730		if not hasattr(self, "firstPt"):
731			attrs = attrs + [("x", self.x), ("y", self.y)]
732		else:
733			attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
734
735		if hasattr(self, "transform"):
736			# XXX needs more testing
737			transform = self.transform
738			if transform[0][1] or transform[1][0]:
739				attrs = attrs + [
740						("scalex", transform[0][0]), ("scale01", transform[0][1]),
741						("scale10", transform[1][0]), ("scaley", transform[1][1]),
742						]
743			elif transform[0][0] <> transform[1][1]:
744				attrs = attrs + [
745						("scalex", transform[0][0]), ("scaley", transform[1][1]),
746						]
747			else:
748				attrs = attrs + [("scale", transform[0][0])]
749		attrs = attrs + [("flags", hex(self.flags))]
750		writer.simpletag("component", attrs)
751		writer.newline()
752
753	def fromXML(self, (name, attrs, content), ttFont):
754		self.glyphName = attrs["glyphName"]
755		if attrs.has_key("firstPt"):
756			self.firstPt = safeEval(attrs["firstPt"])
757			self.secondPt = safeEval(attrs["secondPt"])
758		else:
759			self.x = safeEval(attrs["x"])
760			self.y = safeEval(attrs["y"])
761		if attrs.has_key("scale01"):
762			scalex = safeEval(attrs["scalex"])
763			scale01 = safeEval(attrs["scale01"])
764			scale10 = safeEval(attrs["scale10"])
765			scaley = safeEval(attrs["scaley"])
766			self.transform = Numeric.array([[scalex, scale01], [scale10, scaley]])
767		elif attrs.has_key("scalex"):
768			scalex = safeEval(attrs["scalex"])
769			scaley = safeEval(attrs["scaley"])
770			self.transform = Numeric.array([[scalex, 0], [0, scaley]])
771		elif attrs.has_key("scale"):
772			scale = safeEval(attrs["scale"])
773			self.transform = Numeric.array([[scale, 0], [0, scale]])
774		self.flags = safeEval(attrs["flags"])
775
776	def __cmp__(self, other):
777		if hasattr(self, "transform"):
778			if Numeric.alltrue(Numeric.equal(self.transform, other.transform)):
779				selfdict = self.__dict__.copy()
780				otherdict = other.__dict__.copy()
781				del selfdict["transform"]
782				del otherdict["transform"]
783				return cmp(selfdict, otherdict)
784			else:
785				return 1
786		else:
787			return cmp(self.__dict__, other.__dict__)
788
789
790def reprflag(flag):
791	bin = ""
792	if type(flag) == StringType:
793		flag = ord(flag)
794	while flag:
795		if flag & 0x01:
796			bin = "1" + bin
797		else:
798			bin = "0" + bin
799		flag = flag >> 1
800	bin = (14 - len(bin)) * "0" + bin
801	return bin
802
803