- added support for composite info
- write attributes in a decent order


git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@132 4cde692c-a291-49d1-8350-778aa11640f8
diff --git a/Lib/fontTools/afmLib.py b/Lib/fontTools/afmLib.py
index a725e83..5cf5c8a 100644
--- a/Lib/fontTools/afmLib.py
+++ b/Lib/fontTools/afmLib.py
@@ -8,7 +8,7 @@
 import string
 import types
 
-__version__ = "$Id: afmLib.py,v 1.1 1999-12-16 21:34:51 Just Exp $"
+__version__ = "$Id: afmLib.py,v 1.2 2001-04-30 14:40:17 Just Exp $"
 
 
 # every single line starts with a "word"
@@ -18,7 +18,7 @@
 charRE = re.compile(
 		"(-?\d+)"			# charnum
 		"\s*;\s*WX\s+"		# ; WX 
-		"(\d+)"			# width
+		"(\d+)"				# width
 		"\s*;\s*N\s+"		# ; N 
 		"(\.?[A-Za-z0-9_]+)"	# charname
 		"\s*;\s*B\s+"		# ; B 
@@ -42,7 +42,46 @@
 		"\s*"				# 
 		)
 
-error = "AFM.error"
+# regular expressions to parse composite info lines of the form:
+# Aacute 2 ; PCC A 0 0 ; PCC acute 182 211 ;
+compositeRE = re.compile(
+		"([.A-Za-z0-9_]+)"	# char name
+		"\s+"				# 
+		"(\d+)"				# number of parts
+		"\s*;\s*"			# 
+		)
+componentRE = re.compile(
+		"PCC\s+"			# PPC
+		"([.A-Za-z0-9_]+)"	# base char name
+		"\s+"				# 
+		"(-?\d+)"			# x offset
+		"\s+"				# 
+		"(-?\d+)"			# y offset
+		"\s*;\s*"			# 
+		)
+
+preferredAttributeOrder = [
+		"FontName",
+		"FullName",
+		"FamilyName",
+		"Weight",
+		"ItalicAngle",
+		"IsFixedPitch",
+		"FontBBox",
+		"UnderlinePosition",
+		"UnderlineThickness",
+		"Version",
+		"Notice",
+		"EncodingScheme",
+		"CapHeight",
+		"XHeight",
+		"Ascender",
+		"Descender",
+]
+
+
+class error(Exception): pass
+
 
 class AFM:
 	
@@ -53,14 +92,18 @@
 			'StartKernData',
 			'StartKernPairs',
 			'EndKernPairs',
-			'EndKernData', ]
+			'EndKernData',
+			'StartComposites',
+			'EndComposites',
+			]
 	
-	def __init__(self, path = None):
+	def __init__(self, path=None):
 		self._attrs = {}
 		self._chars = {}
 		self._kerning = {}
 		self._index = {}
 		self._comments = []
+		self._composites = {}
 		if path is not None:
 			self.read(path)
 	
@@ -78,10 +121,12 @@
 			rest = string.strip(line[pos:])
 			if word in self._keywords:
 				continue
-			if word == 'C':
+			if word == "C":
 				self.parsechar(rest)
 			elif word == "KPX":
 				self.parsekernpair(rest)
+			elif word == "CC":
+				self.parsecomposite(rest)
 			else:
 				self.parseattr(word, rest)
 	
@@ -122,6 +167,28 @@
 			else:
 				self._attrs[word] = value
 	
+	def parsecomposite(self, rest):
+		m = compositeRE.match(rest)
+		if m is None:
+			raise error, "syntax error in AFM file: " + `rest`
+		charname = m.group(1)
+		ncomponents = int(m.group(2))
+		rest = rest[m.regs[0][1]:]
+		components = []
+		while 1:
+			m = componentRE.match(rest)
+			if m is None:
+				raise error, "syntax error in AFM file: " + `rest`
+			basechar = m.group(1)
+			xoffset = int(m.group(2))
+			yoffset = int(m.group(3))
+			components.append((basechar, xoffset, yoffset))
+			rest = rest[m.regs[0][1]:]
+			if not rest:
+				break
+		assert len(components) == ncomponents
+		self._composites[charname] = components
+	
 	def write(self, path, sep = '\r'):
 		import time
 		lines = [	"StartFontMetrics 2.0",
@@ -130,12 +197,27 @@
 						time.strftime("%m/%d/%Y %H:%M:%S", 
 						time.localtime(time.time())))]
 		
-		# write attributes
-		items = self._attrs.items()
-		items.sort()		# XXX proper ordering???
+		# write comments, assuming (possibly wrongly!) they should
+		# all appear at the top
+		for comment in self._comments:
+			lines.append("Comment " + comment)
+		
+		# write attributes, first the ones we know about, in
+		# a preferred order
+		attrs = self._attrs
+		for attr in preferredAttributeOrder:
+			if attrs.has_key(attr):
+				value = attrs[attr]
+				if attr == "FontBBox":
+					value = "%s %s %s %s" % value
+				lines.append(attr + " " + str(value))
+		# then write the attributes we don't know about,
+		# in alphabetical order
+		items = attrs.items()
+		items.sort()
 		for attr, value in items:
-			if attr == "FontBBox":
-				value = string.join(map(str, value), " ")
+			if attr in preferredAttributeOrder:
+				continue
 			lines.append(attr + " " + str(value))
 		
 		# write char metrics
@@ -166,9 +248,20 @@
 		items.sort()		# XXX is order important?
 		for (leftchar, rightchar), value in items:
 			lines.append("KPX %s %s %d" % (leftchar, rightchar, value))
-		
 		lines.append("EndKernPairs")
 		lines.append("EndKernData")
+		
+		if self._composites:
+			composites = self._composites.items()
+			composites.sort()
+			lines.append("StartComposites %s" % len(self._composites))
+			for charname, components in composites:
+				line = "CC %s %s ;" % (charname, len(components))
+				for basechar, xoffset, yoffset in components:
+					line = line + " PCC %s %s %s ;" % (basechar, xoffset, yoffset)
+				lines.append(line)
+			lines.append("EndComposites")
+		
 		lines.append("EndFontMetrics")
 		
 		writelines(path, lines, sep)
@@ -234,7 +327,7 @@
 		sep = sep + '\n'	# unix or dos
 	return string.split(data, sep)
 
-def writelines(path, lines, sep = '\r'):
+def writelines(path, lines, sep='\r'):
 	f = open(path, 'wb')
 	for line in lines:
 		f.write(line + sep)
@@ -261,5 +354,5 @@
 		#print afm.chars()
 		#print afm.kernpairs()
 		print afm
-		afm.write(path + ".xxx")
+		afm.write(path + ".muck")