afmLib.py revision dc7e6f3e5563a853477ebe26166b002c158dbe8b
17842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""Module for reading and writing AFM files.""" 27842e56b97ce677b83bdab09cda48bc2d89ac75aJust 37842e56b97ce677b83bdab09cda48bc2d89ac75aJust# XXX reads AFM's generated by Fog, not tested with much else. 47842e56b97ce677b83bdab09cda48bc2d89ac75aJust# It does not implement the full spec (Adobe Technote 5004, Adobe Font Metrics 57842e56b97ce677b83bdab09cda48bc2d89ac75aJust# File Format Specification). Still, it should read most "common" AFM files. 67842e56b97ce677b83bdab09cda48bc2d89ac75aJust 77842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport re 87842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport string 97842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport types 107842e56b97ce677b83bdab09cda48bc2d89ac75aJust 115910f971461cd3df60be89acd73672a22b62cba1jvr__version__ = "$Id: afmLib.py,v 1.6 2003-05-24 12:50:47 jvr Exp $" 127842e56b97ce677b83bdab09cda48bc2d89ac75aJust 137842e56b97ce677b83bdab09cda48bc2d89ac75aJust 147842e56b97ce677b83bdab09cda48bc2d89ac75aJust# every single line starts with a "word" 157842e56b97ce677b83bdab09cda48bc2d89ac75aJustidentifierRE = re.compile("^([A-Za-z]+).*") 167842e56b97ce677b83bdab09cda48bc2d89ac75aJust 177842e56b97ce677b83bdab09cda48bc2d89ac75aJust# regular expression to parse char lines 187842e56b97ce677b83bdab09cda48bc2d89ac75aJustcharRE = re.compile( 197842e56b97ce677b83bdab09cda48bc2d89ac75aJust "(-?\d+)" # charnum 207842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s*;\s*WX\s+" # ; WX 2131ad351b75c56d7bf0d2d059e156baff14eafe68jvr "(-?\d+)" # width 227842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s*;\s*N\s+" # ; N 233c3c32c5a4db928ab702d54adddb32e9a3cf2beajvr "([.A-Za-z0-9_]+)" # charname 247842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s*;\s*B\s+" # ; B 257842e56b97ce677b83bdab09cda48bc2d89ac75aJust "(-?\d+)" # left 267842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s+" # 277842e56b97ce677b83bdab09cda48bc2d89ac75aJust "(-?\d+)" # bottom 287842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s+" # 297842e56b97ce677b83bdab09cda48bc2d89ac75aJust "(-?\d+)" # right 307842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s+" # 317842e56b97ce677b83bdab09cda48bc2d89ac75aJust "(-?\d+)" # top 327842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s*;\s*" # ; 337842e56b97ce677b83bdab09cda48bc2d89ac75aJust ) 347842e56b97ce677b83bdab09cda48bc2d89ac75aJust 357842e56b97ce677b83bdab09cda48bc2d89ac75aJust# regular expression to parse kerning lines 367842e56b97ce677b83bdab09cda48bc2d89ac75aJustkernRE = re.compile( 377842e56b97ce677b83bdab09cda48bc2d89ac75aJust "([.A-Za-z0-9_]+)" # leftchar 387842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s+" # 397842e56b97ce677b83bdab09cda48bc2d89ac75aJust "([.A-Za-z0-9_]+)" # rightchar 407842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s+" # 417842e56b97ce677b83bdab09cda48bc2d89ac75aJust "(-?\d+)" # value 427842e56b97ce677b83bdab09cda48bc2d89ac75aJust "\s*" # 437842e56b97ce677b83bdab09cda48bc2d89ac75aJust ) 447842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4532f868492ce3f4a06aaf97fac1e4655c281c233aJust# regular expressions to parse composite info lines of the form: 4632f868492ce3f4a06aaf97fac1e4655c281c233aJust# Aacute 2 ; PCC A 0 0 ; PCC acute 182 211 ; 4732f868492ce3f4a06aaf97fac1e4655c281c233aJustcompositeRE = re.compile( 4832f868492ce3f4a06aaf97fac1e4655c281c233aJust "([.A-Za-z0-9_]+)" # char name 4932f868492ce3f4a06aaf97fac1e4655c281c233aJust "\s+" # 5032f868492ce3f4a06aaf97fac1e4655c281c233aJust "(\d+)" # number of parts 5132f868492ce3f4a06aaf97fac1e4655c281c233aJust "\s*;\s*" # 5232f868492ce3f4a06aaf97fac1e4655c281c233aJust ) 5332f868492ce3f4a06aaf97fac1e4655c281c233aJustcomponentRE = re.compile( 5432f868492ce3f4a06aaf97fac1e4655c281c233aJust "PCC\s+" # PPC 5532f868492ce3f4a06aaf97fac1e4655c281c233aJust "([.A-Za-z0-9_]+)" # base char name 5632f868492ce3f4a06aaf97fac1e4655c281c233aJust "\s+" # 5732f868492ce3f4a06aaf97fac1e4655c281c233aJust "(-?\d+)" # x offset 5832f868492ce3f4a06aaf97fac1e4655c281c233aJust "\s+" # 5932f868492ce3f4a06aaf97fac1e4655c281c233aJust "(-?\d+)" # y offset 6032f868492ce3f4a06aaf97fac1e4655c281c233aJust "\s*;\s*" # 6132f868492ce3f4a06aaf97fac1e4655c281c233aJust ) 6232f868492ce3f4a06aaf97fac1e4655c281c233aJust 6332f868492ce3f4a06aaf97fac1e4655c281c233aJustpreferredAttributeOrder = [ 6432f868492ce3f4a06aaf97fac1e4655c281c233aJust "FontName", 6532f868492ce3f4a06aaf97fac1e4655c281c233aJust "FullName", 6632f868492ce3f4a06aaf97fac1e4655c281c233aJust "FamilyName", 6732f868492ce3f4a06aaf97fac1e4655c281c233aJust "Weight", 6832f868492ce3f4a06aaf97fac1e4655c281c233aJust "ItalicAngle", 6932f868492ce3f4a06aaf97fac1e4655c281c233aJust "IsFixedPitch", 7032f868492ce3f4a06aaf97fac1e4655c281c233aJust "FontBBox", 7132f868492ce3f4a06aaf97fac1e4655c281c233aJust "UnderlinePosition", 7232f868492ce3f4a06aaf97fac1e4655c281c233aJust "UnderlineThickness", 7332f868492ce3f4a06aaf97fac1e4655c281c233aJust "Version", 7432f868492ce3f4a06aaf97fac1e4655c281c233aJust "Notice", 7532f868492ce3f4a06aaf97fac1e4655c281c233aJust "EncodingScheme", 7632f868492ce3f4a06aaf97fac1e4655c281c233aJust "CapHeight", 7732f868492ce3f4a06aaf97fac1e4655c281c233aJust "XHeight", 7832f868492ce3f4a06aaf97fac1e4655c281c233aJust "Ascender", 7932f868492ce3f4a06aaf97fac1e4655c281c233aJust "Descender", 8032f868492ce3f4a06aaf97fac1e4655c281c233aJust] 8132f868492ce3f4a06aaf97fac1e4655c281c233aJust 8232f868492ce3f4a06aaf97fac1e4655c281c233aJust 8332f868492ce3f4a06aaf97fac1e4655c281c233aJustclass error(Exception): pass 8432f868492ce3f4a06aaf97fac1e4655c281c233aJust 857842e56b97ce677b83bdab09cda48bc2d89ac75aJust 867842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass AFM: 877842e56b97ce677b83bdab09cda48bc2d89ac75aJust 885910f971461cd3df60be89acd73672a22b62cba1jvr _attrs = None 895910f971461cd3df60be89acd73672a22b62cba1jvr 907842e56b97ce677b83bdab09cda48bc2d89ac75aJust _keywords = ['StartFontMetrics', 917842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'EndFontMetrics', 927842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'StartCharMetrics', 937842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'EndCharMetrics', 947842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'StartKernData', 957842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'StartKernPairs', 967842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'EndKernPairs', 9732f868492ce3f4a06aaf97fac1e4655c281c233aJust 'EndKernData', 9832f868492ce3f4a06aaf97fac1e4655c281c233aJust 'StartComposites', 9932f868492ce3f4a06aaf97fac1e4655c281c233aJust 'EndComposites', 10032f868492ce3f4a06aaf97fac1e4655c281c233aJust ] 1017842e56b97ce677b83bdab09cda48bc2d89ac75aJust 10232f868492ce3f4a06aaf97fac1e4655c281c233aJust def __init__(self, path=None): 1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._attrs = {} 1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._chars = {} 1057842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._kerning = {} 1067842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._index = {} 1077842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._comments = [] 10832f868492ce3f4a06aaf97fac1e4655c281c233aJust self._composites = {} 1097842e56b97ce677b83bdab09cda48bc2d89ac75aJust if path is not None: 1107842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.read(path) 1117842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1127842e56b97ce677b83bdab09cda48bc2d89ac75aJust def read(self, path): 1137842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines = readlines(path) 1147842e56b97ce677b83bdab09cda48bc2d89ac75aJust for line in lines: 1157842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not string.strip(line): 1167842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 1177842e56b97ce677b83bdab09cda48bc2d89ac75aJust m = identifierRE.match(line) 1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust if m is None: 119dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod raise error("syntax error in AFM file: " + repr(line)) 1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1217842e56b97ce677b83bdab09cda48bc2d89ac75aJust pos = m.regs[1][1] 1227842e56b97ce677b83bdab09cda48bc2d89ac75aJust word = line[:pos] 1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust rest = string.strip(line[pos:]) 1247842e56b97ce677b83bdab09cda48bc2d89ac75aJust if word in self._keywords: 1257842e56b97ce677b83bdab09cda48bc2d89ac75aJust continue 12632f868492ce3f4a06aaf97fac1e4655c281c233aJust if word == "C": 1277842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.parsechar(rest) 1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust elif word == "KPX": 1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.parsekernpair(rest) 13032f868492ce3f4a06aaf97fac1e4655c281c233aJust elif word == "CC": 13132f868492ce3f4a06aaf97fac1e4655c281c233aJust self.parsecomposite(rest) 1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.parseattr(word, rest) 1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust def parsechar(self, rest): 1367842e56b97ce677b83bdab09cda48bc2d89ac75aJust m = charRE.match(rest) 1377842e56b97ce677b83bdab09cda48bc2d89ac75aJust if m is None: 138dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod raise error("syntax error in AFM file: " + repr(rest)) 1397842e56b97ce677b83bdab09cda48bc2d89ac75aJust things = [] 1407842e56b97ce677b83bdab09cda48bc2d89ac75aJust for fr, to in m.regs[1:]: 1417842e56b97ce677b83bdab09cda48bc2d89ac75aJust things.append(rest[fr:to]) 1427842e56b97ce677b83bdab09cda48bc2d89ac75aJust charname = things[2] 1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust del things[2] 1447842e56b97ce677b83bdab09cda48bc2d89ac75aJust charnum, width, l, b, r, t = map(string.atoi, things) 1457842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._chars[charname] = charnum, width, (l, b, r, t) 1467842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1477842e56b97ce677b83bdab09cda48bc2d89ac75aJust def parsekernpair(self, rest): 1487842e56b97ce677b83bdab09cda48bc2d89ac75aJust m = kernRE.match(rest) 1497842e56b97ce677b83bdab09cda48bc2d89ac75aJust if m is None: 150dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod raise error("syntax error in AFM file: " + repr(rest)) 1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust things = [] 1527842e56b97ce677b83bdab09cda48bc2d89ac75aJust for fr, to in m.regs[1:]: 1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust things.append(rest[fr:to]) 1547842e56b97ce677b83bdab09cda48bc2d89ac75aJust leftchar, rightchar, value = things 1557842e56b97ce677b83bdab09cda48bc2d89ac75aJust value = string.atoi(value) 1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._kerning[(leftchar, rightchar)] = value 1577842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1587842e56b97ce677b83bdab09cda48bc2d89ac75aJust def parseattr(self, word, rest): 1597842e56b97ce677b83bdab09cda48bc2d89ac75aJust if word == "FontBBox": 1607842e56b97ce677b83bdab09cda48bc2d89ac75aJust l, b, r, t = map(string.atoi, string.split(rest)) 1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._attrs[word] = l, b, r, t 1627842e56b97ce677b83bdab09cda48bc2d89ac75aJust elif word == "Comment": 1637842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._comments.append(rest) 1647842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1657842e56b97ce677b83bdab09cda48bc2d89ac75aJust try: 1667842e56b97ce677b83bdab09cda48bc2d89ac75aJust value = string.atoi(rest) 1677842e56b97ce677b83bdab09cda48bc2d89ac75aJust except (ValueError, OverflowError): 1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._attrs[word] = rest 1697842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._attrs[word] = value 1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust 17232f868492ce3f4a06aaf97fac1e4655c281c233aJust def parsecomposite(self, rest): 17332f868492ce3f4a06aaf97fac1e4655c281c233aJust m = compositeRE.match(rest) 17432f868492ce3f4a06aaf97fac1e4655c281c233aJust if m is None: 175dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod raise error("syntax error in AFM file: " + repr(rest)) 17632f868492ce3f4a06aaf97fac1e4655c281c233aJust charname = m.group(1) 17732f868492ce3f4a06aaf97fac1e4655c281c233aJust ncomponents = int(m.group(2)) 17832f868492ce3f4a06aaf97fac1e4655c281c233aJust rest = rest[m.regs[0][1]:] 17932f868492ce3f4a06aaf97fac1e4655c281c233aJust components = [] 18032f868492ce3f4a06aaf97fac1e4655c281c233aJust while 1: 18132f868492ce3f4a06aaf97fac1e4655c281c233aJust m = componentRE.match(rest) 18232f868492ce3f4a06aaf97fac1e4655c281c233aJust if m is None: 183dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod raise error("syntax error in AFM file: " + repr(rest)) 18432f868492ce3f4a06aaf97fac1e4655c281c233aJust basechar = m.group(1) 18532f868492ce3f4a06aaf97fac1e4655c281c233aJust xoffset = int(m.group(2)) 18632f868492ce3f4a06aaf97fac1e4655c281c233aJust yoffset = int(m.group(3)) 18732f868492ce3f4a06aaf97fac1e4655c281c233aJust components.append((basechar, xoffset, yoffset)) 18832f868492ce3f4a06aaf97fac1e4655c281c233aJust rest = rest[m.regs[0][1]:] 18932f868492ce3f4a06aaf97fac1e4655c281c233aJust if not rest: 19032f868492ce3f4a06aaf97fac1e4655c281c233aJust break 19132f868492ce3f4a06aaf97fac1e4655c281c233aJust assert len(components) == ncomponents 19232f868492ce3f4a06aaf97fac1e4655c281c233aJust self._composites[charname] = components 19332f868492ce3f4a06aaf97fac1e4655c281c233aJust 1946175debd674004b3e88a4fea1338a090fc4b9da8Just def write(self, path, sep='\r'): 1957842e56b97ce677b83bdab09cda48bc2d89ac75aJust import time 1967842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines = [ "StartFontMetrics 2.0", 1977842e56b97ce677b83bdab09cda48bc2d89ac75aJust "Comment Generated by afmLib, version %s; at %s" % 1987842e56b97ce677b83bdab09cda48bc2d89ac75aJust (string.split(__version__)[2], 1997842e56b97ce677b83bdab09cda48bc2d89ac75aJust time.strftime("%m/%d/%Y %H:%M:%S", 2007842e56b97ce677b83bdab09cda48bc2d89ac75aJust time.localtime(time.time())))] 2017842e56b97ce677b83bdab09cda48bc2d89ac75aJust 20232f868492ce3f4a06aaf97fac1e4655c281c233aJust # write comments, assuming (possibly wrongly!) they should 20332f868492ce3f4a06aaf97fac1e4655c281c233aJust # all appear at the top 20432f868492ce3f4a06aaf97fac1e4655c281c233aJust for comment in self._comments: 20532f868492ce3f4a06aaf97fac1e4655c281c233aJust lines.append("Comment " + comment) 20632f868492ce3f4a06aaf97fac1e4655c281c233aJust 20732f868492ce3f4a06aaf97fac1e4655c281c233aJust # write attributes, first the ones we know about, in 20832f868492ce3f4a06aaf97fac1e4655c281c233aJust # a preferred order 20932f868492ce3f4a06aaf97fac1e4655c281c233aJust attrs = self._attrs 21032f868492ce3f4a06aaf97fac1e4655c281c233aJust for attr in preferredAttributeOrder: 211bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if attr in attrs: 21232f868492ce3f4a06aaf97fac1e4655c281c233aJust value = attrs[attr] 21332f868492ce3f4a06aaf97fac1e4655c281c233aJust if attr == "FontBBox": 21432f868492ce3f4a06aaf97fac1e4655c281c233aJust value = "%s %s %s %s" % value 21532f868492ce3f4a06aaf97fac1e4655c281c233aJust lines.append(attr + " " + str(value)) 21632f868492ce3f4a06aaf97fac1e4655c281c233aJust # then write the attributes we don't know about, 21732f868492ce3f4a06aaf97fac1e4655c281c233aJust # in alphabetical order 21832f868492ce3f4a06aaf97fac1e4655c281c233aJust items = attrs.items() 21932f868492ce3f4a06aaf97fac1e4655c281c233aJust items.sort() 2207842e56b97ce677b83bdab09cda48bc2d89ac75aJust for attr, value in items: 22132f868492ce3f4a06aaf97fac1e4655c281c233aJust if attr in preferredAttributeOrder: 22232f868492ce3f4a06aaf97fac1e4655c281c233aJust continue 2237842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines.append(attr + " " + str(value)) 2247842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2257842e56b97ce677b83bdab09cda48bc2d89ac75aJust # write char metrics 226dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod lines.append("StartCharMetrics " + repr(len(self._chars))) 2277842e56b97ce677b83bdab09cda48bc2d89ac75aJust items = map(lambda (charname, (charnum, width, box)): 2287842e56b97ce677b83bdab09cda48bc2d89ac75aJust (charnum, (charname, width, box)), 2297842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._chars.items()) 2307842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2317842e56b97ce677b83bdab09cda48bc2d89ac75aJust def myCmp(a, b): 2327842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Custom compare function to make sure unencoded chars (-1) 2337842e56b97ce677b83bdab09cda48bc2d89ac75aJust end up at the end of the list after sorting.""" 2347842e56b97ce677b83bdab09cda48bc2d89ac75aJust if a[0] == -1: 2357842e56b97ce677b83bdab09cda48bc2d89ac75aJust a = (0xffff,) + a[1:] # 0xffff is an arbitrary large number 2367842e56b97ce677b83bdab09cda48bc2d89ac75aJust if b[0] == -1: 2377842e56b97ce677b83bdab09cda48bc2d89ac75aJust b = (0xffff,) + b[1:] 2387842e56b97ce677b83bdab09cda48bc2d89ac75aJust return cmp(a, b) 2397842e56b97ce677b83bdab09cda48bc2d89ac75aJust items.sort(myCmp) 2407842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2417842e56b97ce677b83bdab09cda48bc2d89ac75aJust for charnum, (charname, width, (l, b, r, t)) in items: 2427842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines.append("C %d ; WX %d ; N %s ; B %d %d %d %d ;" % 2437842e56b97ce677b83bdab09cda48bc2d89ac75aJust (charnum, width, charname, l, b, r, t)) 2447842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines.append("EndCharMetrics") 2457842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2467842e56b97ce677b83bdab09cda48bc2d89ac75aJust # write kerning info 2477842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines.append("StartKernData") 248dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod lines.append("StartKernPairs " + repr(len(self._kerning))) 2497842e56b97ce677b83bdab09cda48bc2d89ac75aJust items = self._kerning.items() 2507842e56b97ce677b83bdab09cda48bc2d89ac75aJust items.sort() # XXX is order important? 2517842e56b97ce677b83bdab09cda48bc2d89ac75aJust for (leftchar, rightchar), value in items: 2527842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines.append("KPX %s %s %d" % (leftchar, rightchar, value)) 2537842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines.append("EndKernPairs") 2547842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines.append("EndKernData") 25532f868492ce3f4a06aaf97fac1e4655c281c233aJust 25632f868492ce3f4a06aaf97fac1e4655c281c233aJust if self._composites: 25732f868492ce3f4a06aaf97fac1e4655c281c233aJust composites = self._composites.items() 25832f868492ce3f4a06aaf97fac1e4655c281c233aJust composites.sort() 25932f868492ce3f4a06aaf97fac1e4655c281c233aJust lines.append("StartComposites %s" % len(self._composites)) 26032f868492ce3f4a06aaf97fac1e4655c281c233aJust for charname, components in composites: 26132f868492ce3f4a06aaf97fac1e4655c281c233aJust line = "CC %s %s ;" % (charname, len(components)) 26232f868492ce3f4a06aaf97fac1e4655c281c233aJust for basechar, xoffset, yoffset in components: 26332f868492ce3f4a06aaf97fac1e4655c281c233aJust line = line + " PCC %s %s %s ;" % (basechar, xoffset, yoffset) 26432f868492ce3f4a06aaf97fac1e4655c281c233aJust lines.append(line) 26532f868492ce3f4a06aaf97fac1e4655c281c233aJust lines.append("EndComposites") 26632f868492ce3f4a06aaf97fac1e4655c281c233aJust 2677842e56b97ce677b83bdab09cda48bc2d89ac75aJust lines.append("EndFontMetrics") 2687842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2697842e56b97ce677b83bdab09cda48bc2d89ac75aJust writelines(path, lines, sep) 2707842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2717842e56b97ce677b83bdab09cda48bc2d89ac75aJust def has_kernpair(self, pair): 272bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod return pair in self._kerning 2737842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2747842e56b97ce677b83bdab09cda48bc2d89ac75aJust def kernpairs(self): 2757842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self._kerning.keys() 2767842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2777842e56b97ce677b83bdab09cda48bc2d89ac75aJust def has_char(self, char): 278bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod return char in self._chars 2797842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2807842e56b97ce677b83bdab09cda48bc2d89ac75aJust def chars(self): 2817842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self._chars.keys() 2827842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2837842e56b97ce677b83bdab09cda48bc2d89ac75aJust def comments(self): 2847842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self._comments 2857842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2866175debd674004b3e88a4fea1338a090fc4b9da8Just def addComment(self, comment): 2876175debd674004b3e88a4fea1338a090fc4b9da8Just self._comments.append(comment) 2886175debd674004b3e88a4fea1338a090fc4b9da8Just 2896175debd674004b3e88a4fea1338a090fc4b9da8Just def addComposite(self, glyphName, components): 2906175debd674004b3e88a4fea1338a090fc4b9da8Just self._composites[glyphName] = components 2916175debd674004b3e88a4fea1338a090fc4b9da8Just 2927842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __getattr__(self, attr): 293bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if attr in self._attrs: 2947842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self._attrs[attr] 2957842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 296cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 2977842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2987842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __setattr__(self, attr, value): 2997842e56b97ce677b83bdab09cda48bc2d89ac75aJust # all attrs *not* starting with "_" are consider to be AFM keywords 3007842e56b97ce677b83bdab09cda48bc2d89ac75aJust if attr[:1] == "_": 3017842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.__dict__[attr] = value 3027842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 3037842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._attrs[attr] = value 3047842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3056175debd674004b3e88a4fea1338a090fc4b9da8Just def __delattr__(self, attr): 3066175debd674004b3e88a4fea1338a090fc4b9da8Just # all attrs *not* starting with "_" are consider to be AFM keywords 3076175debd674004b3e88a4fea1338a090fc4b9da8Just if attr[:1] == "_": 3086175debd674004b3e88a4fea1338a090fc4b9da8Just try: 3096175debd674004b3e88a4fea1338a090fc4b9da8Just del self.__dict__[attr] 3106175debd674004b3e88a4fea1338a090fc4b9da8Just except KeyError: 311cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 3126175debd674004b3e88a4fea1338a090fc4b9da8Just else: 3136175debd674004b3e88a4fea1338a090fc4b9da8Just try: 3146175debd674004b3e88a4fea1338a090fc4b9da8Just del self._attrs[attr] 3156175debd674004b3e88a4fea1338a090fc4b9da8Just except KeyError: 316cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise AttributeError(attr) 3176175debd674004b3e88a4fea1338a090fc4b9da8Just 3187842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __getitem__(self, key): 3197842e56b97ce677b83bdab09cda48bc2d89ac75aJust if type(key) == types.TupleType: 3207842e56b97ce677b83bdab09cda48bc2d89ac75aJust # key is a tuple, return the kernpair 3216175debd674004b3e88a4fea1338a090fc4b9da8Just return self._kerning[key] 3227842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 3237842e56b97ce677b83bdab09cda48bc2d89ac75aJust # return the metrics instead 3246175debd674004b3e88a4fea1338a090fc4b9da8Just return self._chars[key] 3256175debd674004b3e88a4fea1338a090fc4b9da8Just 3266175debd674004b3e88a4fea1338a090fc4b9da8Just def __setitem__(self, key, value): 3276175debd674004b3e88a4fea1338a090fc4b9da8Just if type(key) == types.TupleType: 3286175debd674004b3e88a4fea1338a090fc4b9da8Just # key is a tuple, set kernpair 3296175debd674004b3e88a4fea1338a090fc4b9da8Just self._kerning[key] = value 3306175debd674004b3e88a4fea1338a090fc4b9da8Just else: 3316175debd674004b3e88a4fea1338a090fc4b9da8Just # set char metrics 3326175debd674004b3e88a4fea1338a090fc4b9da8Just self._chars[key] = value 3336175debd674004b3e88a4fea1338a090fc4b9da8Just 3346175debd674004b3e88a4fea1338a090fc4b9da8Just def __delitem__(self, key): 3356175debd674004b3e88a4fea1338a090fc4b9da8Just if type(key) == types.TupleType: 3366175debd674004b3e88a4fea1338a090fc4b9da8Just # key is a tuple, del kernpair 3376175debd674004b3e88a4fea1338a090fc4b9da8Just del self._kerning[key] 3386175debd674004b3e88a4fea1338a090fc4b9da8Just else: 3396175debd674004b3e88a4fea1338a090fc4b9da8Just # del char metrics 3406175debd674004b3e88a4fea1338a090fc4b9da8Just del self._chars[key] 3417842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3427842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __repr__(self): 3437842e56b97ce677b83bdab09cda48bc2d89ac75aJust if hasattr(self, "FullName"): 3447842e56b97ce677b83bdab09cda48bc2d89ac75aJust return '<AFM object for %s>' % self.FullName 3457842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 3467842e56b97ce677b83bdab09cda48bc2d89ac75aJust return '<AFM object at %x>' % id(self) 3477842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3487842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3497842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef readlines(path): 3507842e56b97ce677b83bdab09cda48bc2d89ac75aJust f = open(path, 'rb') 3517842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = f.read() 3527842e56b97ce677b83bdab09cda48bc2d89ac75aJust f.close() 3537842e56b97ce677b83bdab09cda48bc2d89ac75aJust # read any text file, regardless whether it's formatted for Mac, Unix or Dos 3547842e56b97ce677b83bdab09cda48bc2d89ac75aJust sep = "" 3557842e56b97ce677b83bdab09cda48bc2d89ac75aJust if '\r' in data: 3567842e56b97ce677b83bdab09cda48bc2d89ac75aJust sep = sep + '\r' # mac or dos 3577842e56b97ce677b83bdab09cda48bc2d89ac75aJust if '\n' in data: 3587842e56b97ce677b83bdab09cda48bc2d89ac75aJust sep = sep + '\n' # unix or dos 3597842e56b97ce677b83bdab09cda48bc2d89ac75aJust return string.split(data, sep) 3607842e56b97ce677b83bdab09cda48bc2d89ac75aJust 36132f868492ce3f4a06aaf97fac1e4655c281c233aJustdef writelines(path, lines, sep='\r'): 3627842e56b97ce677b83bdab09cda48bc2d89ac75aJust f = open(path, 'wb') 3637842e56b97ce677b83bdab09cda48bc2d89ac75aJust for line in lines: 3647842e56b97ce677b83bdab09cda48bc2d89ac75aJust f.write(line + sep) 3657842e56b97ce677b83bdab09cda48bc2d89ac75aJust f.close() 3667842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3677842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3687842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3697842e56b97ce677b83bdab09cda48bc2d89ac75aJustif __name__ == "__main__": 37091bca4244286fb519c93fe92329da96b0e6f32eejvr import EasyDialogs 37191bca4244286fb519c93fe92329da96b0e6f32eejvr path = EasyDialogs.AskFileForOpen() 37291bca4244286fb519c93fe92329da96b0e6f32eejvr if path: 3737842e56b97ce677b83bdab09cda48bc2d89ac75aJust afm = AFM(path) 3747842e56b97ce677b83bdab09cda48bc2d89ac75aJust char = 'A' 3757842e56b97ce677b83bdab09cda48bc2d89ac75aJust if afm.has_char(char): 3767842e56b97ce677b83bdab09cda48bc2d89ac75aJust print afm[char] # print charnum, width and boundingbox 3777842e56b97ce677b83bdab09cda48bc2d89ac75aJust pair = ('A', 'V') 3787842e56b97ce677b83bdab09cda48bc2d89ac75aJust if afm.has_kernpair(pair): 3797842e56b97ce677b83bdab09cda48bc2d89ac75aJust print afm[pair] # print kerning value for pair 3807842e56b97ce677b83bdab09cda48bc2d89ac75aJust print afm.Version # various other afm entries have become attributes 3817842e56b97ce677b83bdab09cda48bc2d89ac75aJust print afm.Weight 3827842e56b97ce677b83bdab09cda48bc2d89ac75aJust # afm.comments() returns a list of all Comment lines found in the AFM 3837842e56b97ce677b83bdab09cda48bc2d89ac75aJust print afm.comments() 3847842e56b97ce677b83bdab09cda48bc2d89ac75aJust #print afm.chars() 3857842e56b97ce677b83bdab09cda48bc2d89ac75aJust #print afm.kernpairs() 3867842e56b97ce677b83bdab09cda48bc2d89ac75aJust print afm 38732f868492ce3f4a06aaf97fac1e4655c281c233aJust afm.write(path + ".muck") 3887842e56b97ce677b83bdab09cda48bc2d89ac75aJust 389