Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 1 | """Module for reading and writing AFM files.""" |
| 2 | |
| 3 | # XXX reads AFM's generated by Fog, not tested with much else. |
| 4 | # It does not implement the full spec (Adobe Technote 5004, Adobe Font Metrics |
| 5 | # File Format Specification). Still, it should read most "common" AFM files. |
| 6 | |
Behdad Esfahbod | 1ae2959 | 2014-01-14 15:07:50 +0800 | [diff] [blame] | 7 | from __future__ import print_function, division, absolute_import |
Behdad Esfahbod | 7ed91ec | 2013-11-27 15:16:28 -0500 | [diff] [blame] | 8 | from fontTools.misc.py23 import * |
Behdad Esfahbod | 30e691e | 2013-11-27 17:27:45 -0500 | [diff] [blame] | 9 | import re |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 10 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 11 | # every single line starts with a "word" |
| 12 | identifierRE = re.compile("^([A-Za-z]+).*") |
| 13 | |
| 14 | # regular expression to parse char lines |
| 15 | charRE = re.compile( |
| 16 | "(-?\d+)" # charnum |
| 17 | "\s*;\s*WX\s+" # ; WX |
jvr | 31ad351 | 2002-11-26 14:09:52 +0000 | [diff] [blame] | 18 | "(-?\d+)" # width |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 19 | "\s*;\s*N\s+" # ; N |
jvr | 3c3c32c | 2002-03-12 14:34:43 +0000 | [diff] [blame] | 20 | "([.A-Za-z0-9_]+)" # charname |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 21 | "\s*;\s*B\s+" # ; B |
| 22 | "(-?\d+)" # left |
| 23 | "\s+" # |
| 24 | "(-?\d+)" # bottom |
| 25 | "\s+" # |
| 26 | "(-?\d+)" # right |
| 27 | "\s+" # |
| 28 | "(-?\d+)" # top |
| 29 | "\s*;\s*" # ; |
| 30 | ) |
| 31 | |
| 32 | # regular expression to parse kerning lines |
| 33 | kernRE = re.compile( |
| 34 | "([.A-Za-z0-9_]+)" # leftchar |
| 35 | "\s+" # |
| 36 | "([.A-Za-z0-9_]+)" # rightchar |
| 37 | "\s+" # |
| 38 | "(-?\d+)" # value |
| 39 | "\s*" # |
| 40 | ) |
| 41 | |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 42 | # regular expressions to parse composite info lines of the form: |
| 43 | # Aacute 2 ; PCC A 0 0 ; PCC acute 182 211 ; |
| 44 | compositeRE = re.compile( |
| 45 | "([.A-Za-z0-9_]+)" # char name |
| 46 | "\s+" # |
| 47 | "(\d+)" # number of parts |
| 48 | "\s*;\s*" # |
| 49 | ) |
| 50 | componentRE = re.compile( |
| 51 | "PCC\s+" # PPC |
| 52 | "([.A-Za-z0-9_]+)" # base char name |
| 53 | "\s+" # |
| 54 | "(-?\d+)" # x offset |
| 55 | "\s+" # |
| 56 | "(-?\d+)" # y offset |
| 57 | "\s*;\s*" # |
| 58 | ) |
| 59 | |
| 60 | preferredAttributeOrder = [ |
| 61 | "FontName", |
| 62 | "FullName", |
| 63 | "FamilyName", |
| 64 | "Weight", |
| 65 | "ItalicAngle", |
| 66 | "IsFixedPitch", |
| 67 | "FontBBox", |
| 68 | "UnderlinePosition", |
| 69 | "UnderlineThickness", |
| 70 | "Version", |
| 71 | "Notice", |
| 72 | "EncodingScheme", |
| 73 | "CapHeight", |
| 74 | "XHeight", |
| 75 | "Ascender", |
| 76 | "Descender", |
| 77 | ] |
| 78 | |
| 79 | |
| 80 | class error(Exception): pass |
| 81 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 82 | |
Behdad Esfahbod | e388db5 | 2013-11-28 14:26:58 -0500 | [diff] [blame] | 83 | class AFM(object): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 84 | |
jvr | 5910f97 | 2003-05-24 12:50:47 +0000 | [diff] [blame] | 85 | _attrs = None |
| 86 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 87 | _keywords = ['StartFontMetrics', |
| 88 | 'EndFontMetrics', |
| 89 | 'StartCharMetrics', |
| 90 | 'EndCharMetrics', |
| 91 | 'StartKernData', |
| 92 | 'StartKernPairs', |
| 93 | 'EndKernPairs', |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 94 | 'EndKernData', |
| 95 | 'StartComposites', |
| 96 | 'EndComposites', |
| 97 | ] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 98 | |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 99 | def __init__(self, path=None): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 100 | self._attrs = {} |
| 101 | self._chars = {} |
| 102 | self._kerning = {} |
| 103 | self._index = {} |
| 104 | self._comments = [] |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 105 | self._composites = {} |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 106 | if path is not None: |
| 107 | self.read(path) |
| 108 | |
| 109 | def read(self, path): |
| 110 | lines = readlines(path) |
| 111 | for line in lines: |
Behdad Esfahbod | 14fb031 | 2013-11-27 05:47:34 -0500 | [diff] [blame] | 112 | if not line.strip(): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 113 | continue |
| 114 | m = identifierRE.match(line) |
| 115 | if m is None: |
Behdad Esfahbod | dc7e6f3 | 2013-11-27 02:44:56 -0500 | [diff] [blame] | 116 | raise error("syntax error in AFM file: " + repr(line)) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 117 | |
| 118 | pos = m.regs[1][1] |
| 119 | word = line[:pos] |
Behdad Esfahbod | 14fb031 | 2013-11-27 05:47:34 -0500 | [diff] [blame] | 120 | rest = line[pos:].strip() |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 121 | if word in self._keywords: |
| 122 | continue |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 123 | if word == "C": |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 124 | self.parsechar(rest) |
| 125 | elif word == "KPX": |
| 126 | self.parsekernpair(rest) |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 127 | elif word == "CC": |
| 128 | self.parsecomposite(rest) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 129 | else: |
| 130 | self.parseattr(word, rest) |
| 131 | |
| 132 | def parsechar(self, rest): |
| 133 | m = charRE.match(rest) |
| 134 | if m is None: |
Behdad Esfahbod | dc7e6f3 | 2013-11-27 02:44:56 -0500 | [diff] [blame] | 135 | raise error("syntax error in AFM file: " + repr(rest)) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 136 | things = [] |
| 137 | for fr, to in m.regs[1:]: |
| 138 | things.append(rest[fr:to]) |
| 139 | charname = things[2] |
| 140 | del things[2] |
Behdad Esfahbod | 14fb031 | 2013-11-27 05:47:34 -0500 | [diff] [blame] | 141 | charnum, width, l, b, r, t = (int(thing) for thing in things) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 142 | self._chars[charname] = charnum, width, (l, b, r, t) |
| 143 | |
| 144 | def parsekernpair(self, rest): |
| 145 | m = kernRE.match(rest) |
| 146 | if m is None: |
Behdad Esfahbod | dc7e6f3 | 2013-11-27 02:44:56 -0500 | [diff] [blame] | 147 | raise error("syntax error in AFM file: " + repr(rest)) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 148 | things = [] |
| 149 | for fr, to in m.regs[1:]: |
| 150 | things.append(rest[fr:to]) |
| 151 | leftchar, rightchar, value = things |
Behdad Esfahbod | 14fb031 | 2013-11-27 05:47:34 -0500 | [diff] [blame] | 152 | value = int(value) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 153 | self._kerning[(leftchar, rightchar)] = value |
| 154 | |
| 155 | def parseattr(self, word, rest): |
| 156 | if word == "FontBBox": |
Behdad Esfahbod | 14fb031 | 2013-11-27 05:47:34 -0500 | [diff] [blame] | 157 | l, b, r, t = [int(thing) for thing in rest.split()] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 158 | self._attrs[word] = l, b, r, t |
| 159 | elif word == "Comment": |
| 160 | self._comments.append(rest) |
| 161 | else: |
| 162 | try: |
Behdad Esfahbod | 14fb031 | 2013-11-27 05:47:34 -0500 | [diff] [blame] | 163 | value = int(rest) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 164 | except (ValueError, OverflowError): |
| 165 | self._attrs[word] = rest |
| 166 | else: |
| 167 | self._attrs[word] = value |
| 168 | |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 169 | def parsecomposite(self, rest): |
| 170 | m = compositeRE.match(rest) |
| 171 | if m is None: |
Behdad Esfahbod | dc7e6f3 | 2013-11-27 02:44:56 -0500 | [diff] [blame] | 172 | raise error("syntax error in AFM file: " + repr(rest)) |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 173 | charname = m.group(1) |
| 174 | ncomponents = int(m.group(2)) |
| 175 | rest = rest[m.regs[0][1]:] |
| 176 | components = [] |
Behdad Esfahbod | ac1b435 | 2013-11-27 04:15:34 -0500 | [diff] [blame] | 177 | while True: |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 178 | m = componentRE.match(rest) |
| 179 | if m is None: |
Behdad Esfahbod | dc7e6f3 | 2013-11-27 02:44:56 -0500 | [diff] [blame] | 180 | raise error("syntax error in AFM file: " + repr(rest)) |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 181 | basechar = m.group(1) |
| 182 | xoffset = int(m.group(2)) |
| 183 | yoffset = int(m.group(3)) |
| 184 | components.append((basechar, xoffset, yoffset)) |
| 185 | rest = rest[m.regs[0][1]:] |
| 186 | if not rest: |
| 187 | break |
| 188 | assert len(components) == ncomponents |
| 189 | self._composites[charname] = components |
| 190 | |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 191 | def write(self, path, sep='\r'): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 192 | import time |
| 193 | lines = [ "StartFontMetrics 2.0", |
Khaled Hosny | ea48dba | 2013-12-04 08:46:43 +0200 | [diff] [blame] | 194 | "Comment Generated by afmLib; at %s" % ( |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 195 | time.strftime("%m/%d/%Y %H:%M:%S", |
| 196 | time.localtime(time.time())))] |
| 197 | |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 198 | # write comments, assuming (possibly wrongly!) they should |
| 199 | # all appear at the top |
| 200 | for comment in self._comments: |
| 201 | lines.append("Comment " + comment) |
| 202 | |
| 203 | # write attributes, first the ones we know about, in |
| 204 | # a preferred order |
| 205 | attrs = self._attrs |
| 206 | for attr in preferredAttributeOrder: |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 207 | if attr in attrs: |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 208 | value = attrs[attr] |
| 209 | if attr == "FontBBox": |
| 210 | value = "%s %s %s %s" % value |
| 211 | lines.append(attr + " " + str(value)) |
| 212 | # then write the attributes we don't know about, |
| 213 | # in alphabetical order |
Behdad Esfahbod | ac1b435 | 2013-11-27 04:15:34 -0500 | [diff] [blame] | 214 | items = sorted(attrs.items()) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 215 | for attr, value in items: |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 216 | if attr in preferredAttributeOrder: |
| 217 | continue |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 218 | lines.append(attr + " " + str(value)) |
| 219 | |
| 220 | # write char metrics |
Behdad Esfahbod | dc7e6f3 | 2013-11-27 02:44:56 -0500 | [diff] [blame] | 221 | lines.append("StartCharMetrics " + repr(len(self._chars))) |
Behdad Esfahbod | e5ca796 | 2013-11-27 04:38:16 -0500 | [diff] [blame] | 222 | items = [(charnum, (charname, width, box)) for charname, (charnum, width, box) in self._chars.items()] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 223 | |
Behdad Esfahbod | b7fd2e1 | 2013-11-27 18:58:45 -0500 | [diff] [blame] | 224 | def myKey(a): |
| 225 | """Custom key function to make sure unencoded chars (-1) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 226 | end up at the end of the list after sorting.""" |
| 227 | if a[0] == -1: |
| 228 | a = (0xffff,) + a[1:] # 0xffff is an arbitrary large number |
Behdad Esfahbod | b7fd2e1 | 2013-11-27 18:58:45 -0500 | [diff] [blame] | 229 | return a |
| 230 | items.sort(key=myKey) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 231 | |
| 232 | for charnum, (charname, width, (l, b, r, t)) in items: |
| 233 | lines.append("C %d ; WX %d ; N %s ; B %d %d %d %d ;" % |
| 234 | (charnum, width, charname, l, b, r, t)) |
| 235 | lines.append("EndCharMetrics") |
| 236 | |
| 237 | # write kerning info |
| 238 | lines.append("StartKernData") |
Behdad Esfahbod | dc7e6f3 | 2013-11-27 02:44:56 -0500 | [diff] [blame] | 239 | lines.append("StartKernPairs " + repr(len(self._kerning))) |
Behdad Esfahbod | c2297cd | 2013-11-27 06:26:55 -0500 | [diff] [blame] | 240 | items = sorted(self._kerning.items()) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 241 | for (leftchar, rightchar), value in items: |
| 242 | lines.append("KPX %s %s %d" % (leftchar, rightchar, value)) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 243 | lines.append("EndKernPairs") |
| 244 | lines.append("EndKernData") |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 245 | |
| 246 | if self._composites: |
Behdad Esfahbod | ac1b435 | 2013-11-27 04:15:34 -0500 | [diff] [blame] | 247 | composites = sorted(self._composites.items()) |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 248 | lines.append("StartComposites %s" % len(self._composites)) |
| 249 | for charname, components in composites: |
| 250 | line = "CC %s %s ;" % (charname, len(components)) |
| 251 | for basechar, xoffset, yoffset in components: |
| 252 | line = line + " PCC %s %s %s ;" % (basechar, xoffset, yoffset) |
| 253 | lines.append(line) |
| 254 | lines.append("EndComposites") |
| 255 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 256 | lines.append("EndFontMetrics") |
| 257 | |
| 258 | writelines(path, lines, sep) |
| 259 | |
| 260 | def has_kernpair(self, pair): |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 261 | return pair in self._kerning |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 262 | |
| 263 | def kernpairs(self): |
Behdad Esfahbod | c2297cd | 2013-11-27 06:26:55 -0500 | [diff] [blame] | 264 | return list(self._kerning.keys()) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 265 | |
| 266 | def has_char(self, char): |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 267 | return char in self._chars |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 268 | |
| 269 | def chars(self): |
Behdad Esfahbod | c2297cd | 2013-11-27 06:26:55 -0500 | [diff] [blame] | 270 | return list(self._chars.keys()) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 271 | |
| 272 | def comments(self): |
| 273 | return self._comments |
| 274 | |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 275 | def addComment(self, comment): |
| 276 | self._comments.append(comment) |
| 277 | |
| 278 | def addComposite(self, glyphName, components): |
| 279 | self._composites[glyphName] = components |
| 280 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 281 | def __getattr__(self, attr): |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 282 | if attr in self._attrs: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 283 | return self._attrs[attr] |
| 284 | else: |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 285 | raise AttributeError(attr) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 286 | |
| 287 | def __setattr__(self, attr, value): |
| 288 | # all attrs *not* starting with "_" are consider to be AFM keywords |
| 289 | if attr[:1] == "_": |
| 290 | self.__dict__[attr] = value |
| 291 | else: |
| 292 | self._attrs[attr] = value |
| 293 | |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 294 | def __delattr__(self, attr): |
| 295 | # all attrs *not* starting with "_" are consider to be AFM keywords |
| 296 | if attr[:1] == "_": |
| 297 | try: |
| 298 | del self.__dict__[attr] |
| 299 | except KeyError: |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 300 | raise AttributeError(attr) |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 301 | else: |
| 302 | try: |
| 303 | del self._attrs[attr] |
| 304 | except KeyError: |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 305 | raise AttributeError(attr) |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 306 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 307 | def __getitem__(self, key): |
Behdad Esfahbod | 002c32f | 2013-11-27 04:48:20 -0500 | [diff] [blame] | 308 | if isinstance(key, tuple): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 309 | # key is a tuple, return the kernpair |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 310 | return self._kerning[key] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 311 | else: |
| 312 | # return the metrics instead |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 313 | return self._chars[key] |
| 314 | |
| 315 | def __setitem__(self, key, value): |
Behdad Esfahbod | 002c32f | 2013-11-27 04:48:20 -0500 | [diff] [blame] | 316 | if isinstance(key, tuple): |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 317 | # key is a tuple, set kernpair |
| 318 | self._kerning[key] = value |
| 319 | else: |
| 320 | # set char metrics |
| 321 | self._chars[key] = value |
| 322 | |
| 323 | def __delitem__(self, key): |
Behdad Esfahbod | 002c32f | 2013-11-27 04:48:20 -0500 | [diff] [blame] | 324 | if isinstance(key, tuple): |
Just | 6175deb | 2001-06-24 15:11:31 +0000 | [diff] [blame] | 325 | # key is a tuple, del kernpair |
| 326 | del self._kerning[key] |
| 327 | else: |
| 328 | # del char metrics |
| 329 | del self._chars[key] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 330 | |
| 331 | def __repr__(self): |
| 332 | if hasattr(self, "FullName"): |
| 333 | return '<AFM object for %s>' % self.FullName |
| 334 | else: |
| 335 | return '<AFM object at %x>' % id(self) |
| 336 | |
| 337 | |
| 338 | def readlines(path): |
| 339 | f = open(path, 'rb') |
| 340 | data = f.read() |
| 341 | f.close() |
| 342 | # read any text file, regardless whether it's formatted for Mac, Unix or Dos |
| 343 | sep = "" |
| 344 | if '\r' in data: |
| 345 | sep = sep + '\r' # mac or dos |
| 346 | if '\n' in data: |
| 347 | sep = sep + '\n' # unix or dos |
Behdad Esfahbod | 14fb031 | 2013-11-27 05:47:34 -0500 | [diff] [blame] | 348 | return data.split(sep) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 349 | |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 350 | def writelines(path, lines, sep='\r'): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 351 | f = open(path, 'wb') |
| 352 | for line in lines: |
| 353 | f.write(line + sep) |
| 354 | f.close() |
| 355 | |
| 356 | |
| 357 | |
| 358 | if __name__ == "__main__": |
jvr | 91bca42 | 2012-10-18 12:49:22 +0000 | [diff] [blame] | 359 | import EasyDialogs |
| 360 | path = EasyDialogs.AskFileForOpen() |
| 361 | if path: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 362 | afm = AFM(path) |
| 363 | char = 'A' |
| 364 | if afm.has_char(char): |
Behdad Esfahbod | 3ec6a25 | 2013-11-27 04:57:33 -0500 | [diff] [blame] | 365 | print(afm[char]) # print charnum, width and boundingbox |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 366 | pair = ('A', 'V') |
| 367 | if afm.has_kernpair(pair): |
Behdad Esfahbod | 3ec6a25 | 2013-11-27 04:57:33 -0500 | [diff] [blame] | 368 | print(afm[pair]) # print kerning value for pair |
| 369 | print(afm.Version) # various other afm entries have become attributes |
| 370 | print(afm.Weight) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 371 | # afm.comments() returns a list of all Comment lines found in the AFM |
Behdad Esfahbod | 3ec6a25 | 2013-11-27 04:57:33 -0500 | [diff] [blame] | 372 | print(afm.comments()) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 373 | #print afm.chars() |
| 374 | #print afm.kernpairs() |
Behdad Esfahbod | 3ec6a25 | 2013-11-27 04:57:33 -0500 | [diff] [blame] | 375 | print(afm) |
Just | 32f8684 | 2001-04-30 14:40:17 +0000 | [diff] [blame] | 376 | afm.write(path + ".muck") |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 377 | |