fonttools/Lib/fontTools/afmLib.py
Just 7842e56b97 Created a new library directory called "FreeLib". All OpenSource RFMKII components will reside there, fontTools being the flagship.
git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@2 4cde692c-a291-49d1-8350-778aa11640f8
1999-12-16 21:34:53 +00:00

266 lines
6.6 KiB
Python

"""Module for reading and writing AFM files."""
# XXX reads AFM's generated by Fog, not tested with much else.
# It does not implement the full spec (Adobe Technote 5004, Adobe Font Metrics
# File Format Specification). Still, it should read most "common" AFM files.
import re
import string
import types
__version__ = "$Id: afmLib.py,v 1.1 1999-12-16 21:34:51 Just Exp $"
# every single line starts with a "word"
identifierRE = re.compile("^([A-Za-z]+).*")
# regular expression to parse char lines
charRE = re.compile(
"(-?\d+)" # charnum
"\s*;\s*WX\s+" # ; WX
"(\d+)" # width
"\s*;\s*N\s+" # ; N
"(\.?[A-Za-z0-9_]+)" # charname
"\s*;\s*B\s+" # ; B
"(-?\d+)" # left
"\s+" #
"(-?\d+)" # bottom
"\s+" #
"(-?\d+)" # right
"\s+" #
"(-?\d+)" # top
"\s*;\s*" # ;
)
# regular expression to parse kerning lines
kernRE = re.compile(
"([.A-Za-z0-9_]+)" # leftchar
"\s+" #
"([.A-Za-z0-9_]+)" # rightchar
"\s+" #
"(-?\d+)" # value
"\s*" #
)
error = "AFM.error"
class AFM:
_keywords = ['StartFontMetrics',
'EndFontMetrics',
'StartCharMetrics',
'EndCharMetrics',
'StartKernData',
'StartKernPairs',
'EndKernPairs',
'EndKernData', ]
def __init__(self, path = None):
self._attrs = {}
self._chars = {}
self._kerning = {}
self._index = {}
self._comments = []
if path is not None:
self.read(path)
def read(self, path):
lines = readlines(path)
for line in lines:
if not string.strip(line):
continue
m = identifierRE.match(line)
if m is None:
raise error, "syntax error in AFM file: " + `line`
pos = m.regs[1][1]
word = line[:pos]
rest = string.strip(line[pos:])
if word in self._keywords:
continue
if word == 'C':
self.parsechar(rest)
elif word == "KPX":
self.parsekernpair(rest)
else:
self.parseattr(word, rest)
def parsechar(self, rest):
m = charRE.match(rest)
if m is None:
raise error, "syntax error in AFM file: " + `rest`
things = []
for fr, to in m.regs[1:]:
things.append(rest[fr:to])
charname = things[2]
del things[2]
charnum, width, l, b, r, t = map(string.atoi, things)
self._chars[charname] = charnum, width, (l, b, r, t)
def parsekernpair(self, rest):
m = kernRE.match(rest)
if m is None:
raise error, "syntax error in AFM file: " + `rest`
things = []
for fr, to in m.regs[1:]:
things.append(rest[fr:to])
leftchar, rightchar, value = things
value = string.atoi(value)
self._kerning[(leftchar, rightchar)] = value
def parseattr(self, word, rest):
if word == "FontBBox":
l, b, r, t = map(string.atoi, string.split(rest))
self._attrs[word] = l, b, r, t
elif word == "Comment":
self._comments.append(rest)
else:
try:
value = string.atoi(rest)
except (ValueError, OverflowError):
self._attrs[word] = rest
else:
self._attrs[word] = value
def write(self, path, sep = '\r'):
import time
lines = [ "StartFontMetrics 2.0",
"Comment Generated by afmLib, version %s; at %s" %
(string.split(__version__)[2],
time.strftime("%m/%d/%Y %H:%M:%S",
time.localtime(time.time())))]
# write attributes
items = self._attrs.items()
items.sort() # XXX proper ordering???
for attr, value in items:
if attr == "FontBBox":
value = string.join(map(str, value), " ")
lines.append(attr + " " + str(value))
# write char metrics
lines.append("StartCharMetrics " + `len(self._chars)`)
items = map(lambda (charname, (charnum, width, box)):
(charnum, (charname, width, box)),
self._chars.items())
def myCmp(a, b):
"""Custom compare function to make sure unencoded chars (-1)
end up at the end of the list after sorting."""
if a[0] == -1:
a = (0xffff,) + a[1:] # 0xffff is an arbitrary large number
if b[0] == -1:
b = (0xffff,) + b[1:]
return cmp(a, b)
items.sort(myCmp)
for charnum, (charname, width, (l, b, r, t)) in items:
lines.append("C %d ; WX %d ; N %s ; B %d %d %d %d ;" %
(charnum, width, charname, l, b, r, t))
lines.append("EndCharMetrics")
# write kerning info
lines.append("StartKernData")
lines.append("StartKernPairs " + `len(self._kerning)`)
items = self._kerning.items()
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")
lines.append("EndFontMetrics")
writelines(path, lines, sep)
def has_kernpair(self, pair):
return self._kerning.has_key(pair)
def kernpairs(self):
return self._kerning.keys()
def has_char(self, char):
return self._chars.has_key(char)
def chars(self):
return self._chars.keys()
def comments(self):
return self._comments
def __getattr__(self, attr):
if self._attrs.has_key(attr):
return self._attrs[attr]
else:
raise AttributeError, attr
def __setattr__(self, attr, value):
# all attrs *not* starting with "_" are consider to be AFM keywords
if attr[:1] == "_":
self.__dict__[attr] = value
else:
self._attrs[attr] = value
def __getitem__(self, key):
if type(key) == types.TupleType:
# key is a tuple, return the kernpair
if self._kerning.has_key(key):
return self._kerning[key]
else:
raise KeyError, "no kerning pair: " + str(key)
else:
# return the metrics instead
if self._chars.has_key(key):
return self._chars[key]
else:
raise KeyError, "metrics index " + str(key) + " out of range"
def __repr__(self):
if hasattr(self, "FullName"):
return '<AFM object for %s>' % self.FullName
else:
return '<AFM object at %x>' % id(self)
def readlines(path):
f = open(path, 'rb')
data = f.read()
f.close()
# read any text file, regardless whether it's formatted for Mac, Unix or Dos
sep = ""
if '\r' in data:
sep = sep + '\r' # mac or dos
if '\n' in data:
sep = sep + '\n' # unix or dos
return string.split(data, sep)
def writelines(path, lines, sep = '\r'):
f = open(path, 'wb')
for line in lines:
f.write(line + sep)
f.close()
if __name__ == "__main__":
import macfs
fss, ok = macfs.StandardGetFile('TEXT')
if ok:
path = fss.as_pathname()
afm = AFM(path)
char = 'A'
if afm.has_char(char):
print afm[char] # print charnum, width and boundingbox
pair = ('A', 'V')
if afm.has_kernpair(pair):
print afm[pair] # print kerning value for pair
print afm.Version # various other afm entries have become attributes
print afm.Weight
# afm.comments() returns a list of all Comment lines found in the AFM
print afm.comments()
#print afm.chars()
#print afm.kernpairs()
print afm
afm.write(path + ".xxx")