blob: 74ae13be8fe8463e7766989ac1080aef85ad3f22 [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001import os
Behdad Esfahbod8413c102013-09-17 16:59:39 -04002import struct
3from fontTools.misc import sstruct
Just7842e561999-12-16 21:34:53 +00004import string
jvrb4e94d92002-10-29 15:49:25 +00005try:
6 from Carbon import Res
7except ImportError:
8 import Res
Just7842e561999-12-16 21:34:53 +00009
Just7842e561999-12-16 21:34:53 +000010
11error = "fondLib.error"
12
13DEBUG = 0
14
15headerformat = """
jvre895dce2005-06-24 09:35:16 +000016 >
Just7842e561999-12-16 21:34:53 +000017 ffFlags: h
18 ffFamID: h
19 ffFirstChar: h
20 ffLastChar: h
21 ffAscent: h
22 ffDescent: h
23 ffLeading: h
24 ffWidMax: h
25 ffWTabOff: l
26 ffKernOff: l
27 ffStylOff: l
28"""
29
30FONDheadersize = 52
31
32class FontFamily:
33
34 def __init__(self, theRes, mode = 'r'):
35 self.ID, type, self.name = theRes.GetResInfo()
36 if type <> 'FOND':
37 raise ValueError, "FOND resource required"
38 self.FOND = theRes
39 self.mode = mode
40 self.changed = 0
41
42 if DEBUG:
43 self.parsedthings = []
44
45 def parse(self):
46 self._getheader()
47 self._getfontassociationtable()
48 self._getoffsettable()
49 self._getboundingboxtable()
50 self._getglyphwidthtable()
51 self._getstylemappingtable()
52 self._getglyphencodingsubtable()
53 self._getkerningtables()
54
55 def minimalparse(self):
56 self._getheader()
57 self._getglyphwidthtable()
58 self._getstylemappingtable()
59
60 def __repr__(self):
61 return "<FontFamily instance of %s>" % self.name
62
63 def getflags(self):
64 return self.fondClass
65
66 def setflags(self, flags):
67 self.changed = 1
68 self.fondClass = flags
69
70 def save(self, destresfile = None):
71 if self.mode <> 'w':
72 raise error, "can't save font: no write permission"
73 self._buildfontassociationtable()
74 self._buildoffsettable()
75 self._buildboundingboxtable()
76 self._buildglyphwidthtable()
77 self._buildkerningtables()
78 self._buildstylemappingtable()
79 self._buildglyphencodingsubtable()
80 rawnames = [ "_rawheader",
81 "_rawfontassociationtable",
82 "_rawoffsettable",
83 "_rawglyphwidthtable",
84 "_rawstylemappingtable",
85 "_rawglyphencodingsubtable",
86 "_rawkerningtables"
87 ]
88 for name in rawnames[1:]: # skip header
89 data = getattr(self, name)
90 if len(data) & 1:
91 setattr(self, name, data + '\0')
92
93 self.ffWTabOff = FONDheadersize + len(self._rawfontassociationtable) + len(self._rawoffsettable)
94 self.ffStylOff = self.ffWTabOff + len(self._rawglyphwidthtable)
95 self.ffKernOff = self.ffStylOff + len(self._rawstylemappingtable) + len(self._rawglyphencodingsubtable)
96 self.glyphTableOffset = len(self._rawstylemappingtable)
97
98 if not self._rawglyphwidthtable:
99 self.ffWTabOff = 0
100 if not self._rawstylemappingtable:
101 self.ffStylOff = 0
102 if not self._rawglyphencodingsubtable:
103 self.glyphTableOffset = 0
104 if not self._rawkerningtables:
105 self.ffKernOff = 0
106
107 self._buildheader()
108
109 # glyphTableOffset has only just been calculated
110 self._updatestylemappingtable()
111
112 newdata = ""
113 for name in rawnames:
114 newdata = newdata + getattr(self, name)
115 if destresfile is None:
116 self.FOND.data = newdata
117 self.FOND.ChangedResource()
118 self.FOND.WriteResource()
119 else:
120 ID, type, name = self.FOND.GetResInfo()
121 self.FOND.DetachResource()
122 self.FOND.data = newdata
123 saveref = Res.CurResFile()
124 Res.UseResFile(destresfile)
125 self.FOND.AddResource(type, ID, name)
126 Res.UseResFile(saveref)
127 self.changed = 0
128
129 def _getheader(self):
130 data = self.FOND.data
131 sstruct.unpack(headerformat, data[:28], self)
jvre895dce2005-06-24 09:35:16 +0000132 self.ffProperty = struct.unpack(">9h", data[28:46])
133 self.ffIntl = struct.unpack(">hh", data[46:50])
134 self.ffVersion, = struct.unpack(">h", data[50:FONDheadersize])
Just7842e561999-12-16 21:34:53 +0000135
136 if DEBUG:
137 self._rawheader = data[:FONDheadersize]
138 self.parsedthings.append((0, FONDheadersize, 'header'))
139
140 def _buildheader(self):
141 header = sstruct.pack(headerformat, self)
jvre895dce2005-06-24 09:35:16 +0000142 header = header + apply(struct.pack, (">9h",) + self.ffProperty)
143 header = header + apply(struct.pack, (">hh",) + self.ffIntl)
144 header = header + struct.pack(">h", self.ffVersion)
Just7842e561999-12-16 21:34:53 +0000145 if DEBUG:
146 print "header is the same?", self._rawheader == header and 'yes.' or 'no.'
147 if self._rawheader <> header:
148 print len(self._rawheader), len(header)
149 self._rawheader = header
150
151 def _getfontassociationtable(self):
152 data = self.FOND.data
153 offset = FONDheadersize
jvre895dce2005-06-24 09:35:16 +0000154 numberofentries, = struct.unpack(">h", data[offset:offset+2])
Just7842e561999-12-16 21:34:53 +0000155 numberofentries = numberofentries + 1
156 size = numberofentries * 6
157 self.fontAssoc = []
158 for i in range(offset + 2, offset + size, 6):
jvre895dce2005-06-24 09:35:16 +0000159 self.fontAssoc.append(struct.unpack(">3h", data[i:i+6]))
Just7842e561999-12-16 21:34:53 +0000160
161 self._endoffontassociationtable = offset + size + 2
162 if DEBUG:
163 self._rawfontassociationtable = data[offset:self._endoffontassociationtable]
164 self.parsedthings.append((offset, self._endoffontassociationtable, 'fontassociationtable'))
165
166 def _buildfontassociationtable(self):
jvre895dce2005-06-24 09:35:16 +0000167 data = struct.pack(">h", len(self.fontAssoc) - 1)
Just7842e561999-12-16 21:34:53 +0000168 for size, stype, ID in self.fontAssoc:
jvre895dce2005-06-24 09:35:16 +0000169 data = data + struct.pack(">3h", size, stype, ID)
Just7842e561999-12-16 21:34:53 +0000170
171 if DEBUG:
172 print "font association table is the same?", self._rawfontassociationtable == data and 'yes.' or 'no.'
173 if self._rawfontassociationtable <> data:
174 print len(self._rawfontassociationtable), len(data)
175 self._rawfontassociationtable = data
176
177 def _getoffsettable(self):
178 if self.ffWTabOff == 0:
179 self._rawoffsettable = ""
180 return
181 data = self.FOND.data
182 # Quick'n'Dirty. What's the spec anyway? Can't find it...
183 offset = self._endoffontassociationtable
184 count = self.ffWTabOff
185 self._rawoffsettable = data[offset:count]
186 if DEBUG:
187 self.parsedthings.append((offset, count, 'offsettable&bbtable'))
188
189 def _buildoffsettable(self):
190 if not hasattr(self, "_rawoffsettable"):
191 self._rawoffsettable = ""
192
193 def _getboundingboxtable(self):
194 self.boundingBoxes = None
195 if self._rawoffsettable[:6] <> '\0\0\0\0\0\6': # XXX ????
196 return
197 boxes = {}
198 data = self._rawoffsettable[6:]
jvre895dce2005-06-24 09:35:16 +0000199 numstyles = struct.unpack(">h", data[:2])[0] + 1
Just7842e561999-12-16 21:34:53 +0000200 data = data[2:]
201 for i in range(numstyles):
jvre895dce2005-06-24 09:35:16 +0000202 style, l, b, r, t = struct.unpack(">hhhhh", data[:10])
Just7842e561999-12-16 21:34:53 +0000203 boxes[style] = (l, b, r, t)
204 data = data[10:]
205 self.boundingBoxes = boxes
206
207 def _buildboundingboxtable(self):
208 if self.boundingBoxes and self._rawoffsettable[:6] == '\0\0\0\0\0\6':
209 boxes = self.boundingBoxes.items()
210 boxes.sort()
jvre895dce2005-06-24 09:35:16 +0000211 data = '\0\0\0\0\0\6' + struct.pack(">h", len(boxes) - 1)
Just7842e561999-12-16 21:34:53 +0000212 for style, (l, b, r, t) in boxes:
jvre895dce2005-06-24 09:35:16 +0000213 data = data + struct.pack(">hhhhh", style, l, b, r, t)
Just7842e561999-12-16 21:34:53 +0000214 self._rawoffsettable = data
215
216 def _getglyphwidthtable(self):
217 self.widthTables = {}
218 if self.ffWTabOff == 0:
219 return
220 data = self.FOND.data
221 offset = self.ffWTabOff
jvre895dce2005-06-24 09:35:16 +0000222 numberofentries, = struct.unpack(">h", data[offset:offset+2])
Just7842e561999-12-16 21:34:53 +0000223 numberofentries = numberofentries + 1
224 count = offset + 2
225 for i in range(numberofentries):
jvre895dce2005-06-24 09:35:16 +0000226 stylecode, = struct.unpack(">h", data[count:count+2])
Just7842e561999-12-16 21:34:53 +0000227 widthtable = self.widthTables[stylecode] = []
228 count = count + 2
229 for j in range(3 + self.ffLastChar - self.ffFirstChar):
jvre895dce2005-06-24 09:35:16 +0000230 width, = struct.unpack(">h", data[count:count+2])
Just7842e561999-12-16 21:34:53 +0000231 widthtable.append(width)
232 count = count + 2
233
234 if DEBUG:
235 self._rawglyphwidthtable = data[offset:count]
236 self.parsedthings.append((offset, count, 'glyphwidthtable'))
237
238 def _buildglyphwidthtable(self):
239 if not self.widthTables:
240 self._rawglyphwidthtable = ""
241 return
242 numberofentries = len(self.widthTables)
jvre895dce2005-06-24 09:35:16 +0000243 data = struct.pack('>h', numberofentries - 1)
Just7842e561999-12-16 21:34:53 +0000244 tables = self.widthTables.items()
245 tables.sort()
246 for stylecode, table in tables:
jvre895dce2005-06-24 09:35:16 +0000247 data = data + struct.pack('>h', stylecode)
Just7842e561999-12-16 21:34:53 +0000248 if len(table) <> (3 + self.ffLastChar - self.ffFirstChar):
249 raise error, "width table has wrong length"
250 for width in table:
jvre895dce2005-06-24 09:35:16 +0000251 data = data + struct.pack('>h', width)
Just7842e561999-12-16 21:34:53 +0000252 if DEBUG:
253 print "glyph width table is the same?", self._rawglyphwidthtable == data and 'yes.' or 'no.'
254 self._rawglyphwidthtable = data
255
256 def _getkerningtables(self):
257 self.kernTables = {}
258 if self.ffKernOff == 0:
259 return
260 data = self.FOND.data
261 offset = self.ffKernOff
jvre895dce2005-06-24 09:35:16 +0000262 numberofentries, = struct.unpack(">h", data[offset:offset+2])
Just7842e561999-12-16 21:34:53 +0000263 numberofentries = numberofentries + 1
264 count = offset + 2
265 for i in range(numberofentries):
jvre895dce2005-06-24 09:35:16 +0000266 stylecode, = struct.unpack(">h", data[count:count+2])
Just7842e561999-12-16 21:34:53 +0000267 count = count + 2
jvre895dce2005-06-24 09:35:16 +0000268 numberofpairs, = struct.unpack(">h", data[count:count+2])
Just7842e561999-12-16 21:34:53 +0000269 count = count + 2
270 kerntable = self.kernTables[stylecode] = []
271 for j in range(numberofpairs):
jvre895dce2005-06-24 09:35:16 +0000272 firstchar, secondchar, kerndistance = struct.unpack(">cch", data[count:count+4])
Just7842e561999-12-16 21:34:53 +0000273 kerntable.append((ord(firstchar), ord(secondchar), kerndistance))
274 count = count + 4
275
276 if DEBUG:
277 self._rawkerningtables = data[offset:count]
278 self.parsedthings.append((offset, count, 'kerningtables'))
279
280 def _buildkerningtables(self):
281 if self.kernTables == {}:
282 self._rawkerningtables = ""
283 self.ffKernOff = 0
284 return
285 numberofentries = len(self.kernTables)
jvre895dce2005-06-24 09:35:16 +0000286 data = [struct.pack('>h', numberofentries - 1)]
Just7842e561999-12-16 21:34:53 +0000287 tables = self.kernTables.items()
288 tables.sort()
289 for stylecode, table in tables:
jvre895dce2005-06-24 09:35:16 +0000290 data.append(struct.pack('>h', stylecode))
291 data.append(struct.pack('>h', len(table))) # numberofpairs
Just7842e561999-12-16 21:34:53 +0000292 for firstchar, secondchar, kerndistance in table:
jvre895dce2005-06-24 09:35:16 +0000293 data.append(struct.pack(">cch", chr(firstchar), chr(secondchar), kerndistance))
Just7842e561999-12-16 21:34:53 +0000294
295 data = string.join(data, '')
296
297 if DEBUG:
298 print "kerning table is the same?", self._rawkerningtables == data and 'yes.' or 'no.'
299 if self._rawkerningtables <> data:
300 print len(self._rawkerningtables), len(data)
301 self._rawkerningtables = data
302
303 def _getstylemappingtable(self):
304 offset = self.ffStylOff
305 self.styleStrings = []
306 self.styleIndices = ()
307 self.glyphTableOffset = 0
308 self.fondClass = 0
309 if offset == 0:
310 return
311 data = self.FOND.data
312 self.fondClass, self.glyphTableOffset, self.styleMappingReserved, = \
jvre895dce2005-06-24 09:35:16 +0000313 struct.unpack(">hll", data[offset:offset+10])
314 self.styleIndices = struct.unpack('>48b', data[offset + 10:offset + 58])
315 stringcount, = struct.unpack('>h', data[offset+58:offset+60])
Just7842e561999-12-16 21:34:53 +0000316
317 count = offset + 60
318 for i in range(stringcount):
319 str_len = ord(data[count])
320 self.styleStrings.append(data[count + 1:count + 1 + str_len])
321 count = count + 1 + str_len
322
323 self._unpackstylestrings()
324
325 data = data[offset:count]
326 if len(data) % 2:
327 data = data + '\0'
328 if DEBUG:
329 self._rawstylemappingtable = data
330 self.parsedthings.append((offset, count, 'stylemappingtable'))
331
332 def _buildstylemappingtable(self):
333 if not self.styleIndices:
334 self._rawstylemappingtable = ""
335 return
jvre895dce2005-06-24 09:35:16 +0000336 data = struct.pack(">hll", self.fondClass, self.glyphTableOffset,
Just7842e561999-12-16 21:34:53 +0000337 self.styleMappingReserved)
338
339 self._packstylestrings()
jvre895dce2005-06-24 09:35:16 +0000340 data = data + apply(struct.pack, (">48b",) + self.styleIndices)
Just7842e561999-12-16 21:34:53 +0000341
342 stringcount = len(self.styleStrings)
jvre895dce2005-06-24 09:35:16 +0000343 data = data + struct.pack(">h", stringcount)
Just7842e561999-12-16 21:34:53 +0000344 for string in self.styleStrings:
345 data = data + chr(len(string)) + string
346
347 if len(data) % 2:
348 data = data + '\0'
349
350 if DEBUG:
351 print "style mapping table is the same?", self._rawstylemappingtable == data and 'yes.' or 'no.'
352 self._rawstylemappingtable = data
353
354 def _unpackstylestrings(self):
355 psNames = {}
356 self.ffFamilyName = self.styleStrings[0]
357 for i in self.widthTables.keys():
358 index = self.styleIndices[i]
359 if index == 1:
360 psNames[i] = self.styleStrings[0]
361 else:
362 style = self.styleStrings[0]
363 codes = map(ord, self.styleStrings[index - 1])
364 for code in codes:
365 style = style + self.styleStrings[code - 1]
366 psNames[i] = style
367 self.psNames = psNames
368
369 def _packstylestrings(self):
370 nameparts = {}
371 splitnames = {}
372 for style, name in self.psNames.items():
373 split = splitname(name, self.ffFamilyName)
374 splitnames[style] = split
375 for part in split:
376 nameparts[part] = None
377 del nameparts[self.ffFamilyName]
378 nameparts = nameparts.keys()
379 nameparts.sort()
380 items = splitnames.items()
381 items.sort()
382 numindices = 0
383 for style, split in items:
384 if len(split) > 1:
385 numindices = numindices + 1
Just0d517072001-06-24 15:10:02 +0000386 numindices = max(numindices, max(self.styleIndices) - 1)
Justdccbd312000-10-03 10:34:34 +0000387 styleStrings = [self.ffFamilyName] + numindices * [""] + nameparts
Just7842e561999-12-16 21:34:53 +0000388 # XXX the next bit goes wrong for MM fonts.
389 for style, split in items:
390 if len(split) == 1:
391 continue
392 indices = ""
393 for part in split[1:]:
394 indices = indices + chr(nameparts.index(part) + numindices + 2)
395 styleStrings[self.styleIndices[style] - 1] = indices
396 self.styleStrings = styleStrings
397
398 def _updatestylemappingtable(self):
399 # Update the glyphTableOffset field.
pabs31344bc92010-01-09 09:12:11 +0000400 # This is necessary since we have to build this table to
Just7842e561999-12-16 21:34:53 +0000401 # know what the glyphTableOffset will be.
402 # And we don't want to build it twice, do we?
403 data = self._rawstylemappingtable
404 if not data:
405 return
jvre895dce2005-06-24 09:35:16 +0000406 data = data[:2] + struct.pack(">l", self.glyphTableOffset) + data[6:]
Just7842e561999-12-16 21:34:53 +0000407 self._rawstylemappingtable = data
408
409 def _getglyphencodingsubtable(self):
410 glyphEncoding = self.glyphEncoding = {}
411 if not self.glyphTableOffset:
412 return
413 offset = self.ffStylOff + self.glyphTableOffset
414 data = self.FOND.data
jvre895dce2005-06-24 09:35:16 +0000415 numberofentries, = struct.unpack(">h", data[offset:offset+2])
Just7842e561999-12-16 21:34:53 +0000416 count = offset + 2
417 for i in range(numberofentries):
418 glyphcode = ord(data[count])
419 count = count + 1
420 strlen = ord(data[count])
421 count = count + 1
422 glyphname = data[count:count+strlen]
423 glyphEncoding[glyphcode] = glyphname
424 count = count + strlen
425
426 if DEBUG:
427 self._rawglyphencodingsubtable = data[offset:count]
428 self.parsedthings.append((offset, count, 'glyphencodingsubtable'))
429
430 def _buildglyphencodingsubtable(self):
431 if not self.glyphEncoding:
432 self._rawglyphencodingsubtable = ""
433 return
434 numberofentries = len(self.glyphEncoding)
jvre895dce2005-06-24 09:35:16 +0000435 data = struct.pack(">h", numberofentries)
Just7842e561999-12-16 21:34:53 +0000436 items = self.glyphEncoding.items()
437 items.sort()
438 for glyphcode, glyphname in items:
439 data = data + chr(glyphcode) + chr(len(glyphname)) + glyphname
440 self._rawglyphencodingsubtable = data
441
442
443uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
444
445def splitname(name, famname = None):
446 # XXX this goofs up MM font names: but how should it be done??
447 if famname:
448 if name[:len(famname)] <> famname:
449 raise error, "first part of name should be same as family name"
450 name = name[len(famname):]
451 split = [famname]
452 else:
453 split = []
454 current = ""
455 for c in name:
456 if c == '-' or c in uppercase:
457 if current:
458 split.append(current)
459 current = ""
460 current = current + c
461 if current:
462 split.append(current)
463 return split
464
465def makeLWFNfilename(name):
466 split = splitname(name)
467 lwfnname = split[0][:5]
468 for part in split[1:]:
469 if part <> '-':
470 lwfnname = lwfnname + part[:3]
471 return lwfnname
472
473class BitmapFontFile:
474
Just0d517072001-06-24 15:10:02 +0000475 def __init__(self, path, mode='r'):
Just7842e561999-12-16 21:34:53 +0000476 if mode == 'r':
477 permission = 1 # read only
478 elif mode == 'w':
479 permission = 3 # exclusive r/w
480 else:
481 raise error, 'mode should be either "r" or "w"'
482 self.mode = mode
jvr91bca422012-10-18 12:49:22 +0000483 self.resref = Res.FSOpenResFile(path, permission)
Just7842e561999-12-16 21:34:53 +0000484 Res.UseResFile(self.resref)
485 self.path = path
486 self.fonds = []
487 self.getFONDs()
488
489 def getFONDs(self):
490 FONDcount = Res.Count1Resources('FOND')
491 for i in range(FONDcount):
492 fond = FontFamily(Res.Get1IndResource('FOND', i + 1), self.mode)
493 self.fonds.append(fond)
494
495 def parse(self):
496 self.fondsbyname = {}
497 for fond in self.fonds:
498 fond.parse()
499 if hasattr(fond, "psNames") and fond.psNames:
500 psNames = fond.psNames.values()
501 psNames.sort()
502 self.fondsbyname[psNames[0]] = fond
503
504 def minimalparse(self):
505 for fond in self.fonds:
506 fond.minimalparse()
507
508 def close(self):
509 if self.resref <> None:
510 try:
511 Res.CloseResFile(self.resref)
512 except Res.Error:
513 pass
514 self.resref = None
515
516
517class FondSelector:
518
519 def __init__(self, fondlist):
520 import W
521 if not fondlist:
522 raise ValueError, "expected at least one FOND entry"
523 if len(fondlist) == 1:
524 self.choice = 0
525 return
526 fonds = []
527 for fond in fondlist:
528 fonds.append(fond.name)
529 self.w = W.ModalDialog((200, 200), "aaa")
530 self.w.donebutton = W.Button((-70, -26, 60, 16), "Done", self.close)
531 self.w.l = W.List((10, 10, -10, -36), fonds, self.listhit)
532 self.w.setdefaultbutton(self.w.donebutton)
533 self.w.l.setselection([0])
534 self.w.open()
535
536 def close(self):
537 self.checksel()
538 sel = self.w.l.getselection()
539 self.choice = sel[0]
540 self.w.close()
541
542 def listhit(self, isDbl):
543 if isDbl:
544 self.w.donebutton.push()
545 else:
546 self.checksel()
547
548 def checksel(self):
549 sel = self.w.l.getselection()
550 if not sel:
551 self.w.l.setselection([0])
552 elif len(sel) <> 1:
553 self.w.l.setselection([sel[0]])
554