import os import struct from fontTools.misc import sstruct try: from Carbon import Res except ImportError: import Res error = "fondLib.error" DEBUG = 0 headerformat = """ > ffFlags: h ffFamID: h ffFirstChar: h ffLastChar: h ffAscent: h ffDescent: h ffLeading: h ffWidMax: h ffWTabOff: l ffKernOff: l ffStylOff: l """ FONDheadersize = 52 class FontFamily: def __init__(self, theRes, mode = 'r'): self.ID, type, self.name = theRes.GetResInfo() if type != 'FOND': raise ValueError("FOND resource required") self.FOND = theRes self.mode = mode self.changed = 0 if DEBUG: self.parsedthings = [] def parse(self): self._getheader() self._getfontassociationtable() self._getoffsettable() self._getboundingboxtable() self._getglyphwidthtable() self._getstylemappingtable() self._getglyphencodingsubtable() self._getkerningtables() def minimalparse(self): self._getheader() self._getglyphwidthtable() self._getstylemappingtable() def __repr__(self): return "" % self.name def getflags(self): return self.fondClass def setflags(self, flags): self.changed = 1 self.fondClass = flags def save(self, destresfile = None): if self.mode != 'w': raise error("can't save font: no write permission") self._buildfontassociationtable() self._buildoffsettable() self._buildboundingboxtable() self._buildglyphwidthtable() self._buildkerningtables() self._buildstylemappingtable() self._buildglyphencodingsubtable() rawnames = [ "_rawheader", "_rawfontassociationtable", "_rawoffsettable", "_rawglyphwidthtable", "_rawstylemappingtable", "_rawglyphencodingsubtable", "_rawkerningtables" ] for name in rawnames[1:]: # skip header data = getattr(self, name) if len(data) & 1: setattr(self, name, data + '\0') self.ffWTabOff = FONDheadersize + len(self._rawfontassociationtable) + len(self._rawoffsettable) self.ffStylOff = self.ffWTabOff + len(self._rawglyphwidthtable) self.ffKernOff = self.ffStylOff + len(self._rawstylemappingtable) + len(self._rawglyphencodingsubtable) self.glyphTableOffset = len(self._rawstylemappingtable) if not self._rawglyphwidthtable: self.ffWTabOff = 0 if not self._rawstylemappingtable: self.ffStylOff = 0 if not self._rawglyphencodingsubtable: self.glyphTableOffset = 0 if not self._rawkerningtables: self.ffKernOff = 0 self._buildheader() # glyphTableOffset has only just been calculated self._updatestylemappingtable() newdata = "" for name in rawnames: newdata = newdata + getattr(self, name) if destresfile is None: self.FOND.data = newdata self.FOND.ChangedResource() self.FOND.WriteResource() else: ID, type, name = self.FOND.GetResInfo() self.FOND.DetachResource() self.FOND.data = newdata saveref = Res.CurResFile() Res.UseResFile(destresfile) self.FOND.AddResource(type, ID, name) Res.UseResFile(saveref) self.changed = 0 def _getheader(self): data = self.FOND.data sstruct.unpack(headerformat, data[:28], self) self.ffProperty = struct.unpack(">9h", data[28:46]) self.ffIntl = struct.unpack(">hh", data[46:50]) self.ffVersion, = struct.unpack(">h", data[50:FONDheadersize]) if DEBUG: self._rawheader = data[:FONDheadersize] self.parsedthings.append((0, FONDheadersize, 'header')) def _buildheader(self): header = sstruct.pack(headerformat, self) header = header + struct.pack(*(">9h",) + self.ffProperty) header = header + struct.pack(*(">hh",) + self.ffIntl) header = header + struct.pack(">h", self.ffVersion) if DEBUG: print("header is the same?", self._rawheader == header and 'yes.' or 'no.') if self._rawheader != header: print(len(self._rawheader), len(header)) self._rawheader = header def _getfontassociationtable(self): data = self.FOND.data offset = FONDheadersize numberofentries, = struct.unpack(">h", data[offset:offset+2]) numberofentries = numberofentries + 1 size = numberofentries * 6 self.fontAssoc = [] for i in range(offset + 2, offset + size, 6): self.fontAssoc.append(struct.unpack(">3h", data[i:i+6])) self._endoffontassociationtable = offset + size + 2 if DEBUG: self._rawfontassociationtable = data[offset:self._endoffontassociationtable] self.parsedthings.append((offset, self._endoffontassociationtable, 'fontassociationtable')) def _buildfontassociationtable(self): data = struct.pack(">h", len(self.fontAssoc) - 1) for size, stype, ID in self.fontAssoc: data = data + struct.pack(">3h", size, stype, ID) if DEBUG: print("font association table is the same?", self._rawfontassociationtable == data and 'yes.' or 'no.') if self._rawfontassociationtable != data: print(len(self._rawfontassociationtable), len(data)) self._rawfontassociationtable = data def _getoffsettable(self): if self.ffWTabOff == 0: self._rawoffsettable = "" return data = self.FOND.data # Quick'n'Dirty. What's the spec anyway? Can't find it... offset = self._endoffontassociationtable count = self.ffWTabOff self._rawoffsettable = data[offset:count] if DEBUG: self.parsedthings.append((offset, count, 'offsettable&bbtable')) def _buildoffsettable(self): if not hasattr(self, "_rawoffsettable"): self._rawoffsettable = "" def _getboundingboxtable(self): self.boundingBoxes = None if self._rawoffsettable[:6] != '\0\0\0\0\0\6': # XXX ???? return boxes = {} data = self._rawoffsettable[6:] numstyles = struct.unpack(">h", data[:2])[0] + 1 data = data[2:] for i in range(numstyles): style, l, b, r, t = struct.unpack(">hhhhh", data[:10]) boxes[style] = (l, b, r, t) data = data[10:] self.boundingBoxes = boxes def _buildboundingboxtable(self): if self.boundingBoxes and self._rawoffsettable[:6] == '\0\0\0\0\0\6': boxes = sorted(self.boundingBoxes.items()) data = '\0\0\0\0\0\6' + struct.pack(">h", len(boxes) - 1) for style, (l, b, r, t) in boxes: data = data + struct.pack(">hhhhh", style, l, b, r, t) self._rawoffsettable = data def _getglyphwidthtable(self): self.widthTables = {} if self.ffWTabOff == 0: return data = self.FOND.data offset = self.ffWTabOff numberofentries, = struct.unpack(">h", data[offset:offset+2]) numberofentries = numberofentries + 1 count = offset + 2 for i in range(numberofentries): stylecode, = struct.unpack(">h", data[count:count+2]) widthtable = self.widthTables[stylecode] = [] count = count + 2 for j in range(3 + self.ffLastChar - self.ffFirstChar): width, = struct.unpack(">h", data[count:count+2]) widthtable.append(width) count = count + 2 if DEBUG: self._rawglyphwidthtable = data[offset:count] self.parsedthings.append((offset, count, 'glyphwidthtable')) def _buildglyphwidthtable(self): if not self.widthTables: self._rawglyphwidthtable = "" return numberofentries = len(self.widthTables) data = struct.pack('>h', numberofentries - 1) tables = sorted(self.widthTables.items()) for stylecode, table in tables: data = data + struct.pack('>h', stylecode) if len(table) != (3 + self.ffLastChar - self.ffFirstChar): raise error("width table has wrong length") for width in table: data = data + struct.pack('>h', width) if DEBUG: print("glyph width table is the same?", self._rawglyphwidthtable == data and 'yes.' or 'no.') self._rawglyphwidthtable = data def _getkerningtables(self): self.kernTables = {} if self.ffKernOff == 0: return data = self.FOND.data offset = self.ffKernOff numberofentries, = struct.unpack(">h", data[offset:offset+2]) numberofentries = numberofentries + 1 count = offset + 2 for i in range(numberofentries): stylecode, = struct.unpack(">h", data[count:count+2]) count = count + 2 numberofpairs, = struct.unpack(">h", data[count:count+2]) count = count + 2 kerntable = self.kernTables[stylecode] = [] for j in range(numberofpairs): firstchar, secondchar, kerndistance = struct.unpack(">cch", data[count:count+4]) kerntable.append((ord(firstchar), ord(secondchar), kerndistance)) count = count + 4 if DEBUG: self._rawkerningtables = data[offset:count] self.parsedthings.append((offset, count, 'kerningtables')) def _buildkerningtables(self): if self.kernTables == {}: self._rawkerningtables = "" self.ffKernOff = 0 return numberofentries = len(self.kernTables) data = [struct.pack('>h', numberofentries - 1)] tables = sorted(self.kernTables.items()) for stylecode, table in tables: data.append(struct.pack('>h', stylecode)) data.append(struct.pack('>h', len(table))) # numberofpairs for firstchar, secondchar, kerndistance in table: data.append(struct.pack(">cch", chr(firstchar), chr(secondchar), kerndistance)) data = ''.join(data) if DEBUG: print("kerning table is the same?", self._rawkerningtables == data and 'yes.' or 'no.') if self._rawkerningtables != data: print(len(self._rawkerningtables), len(data)) self._rawkerningtables = data def _getstylemappingtable(self): offset = self.ffStylOff self.styleStrings = [] self.styleIndices = () self.glyphTableOffset = 0 self.fondClass = 0 if offset == 0: return data = self.FOND.data self.fondClass, self.glyphTableOffset, self.styleMappingReserved, = \ struct.unpack(">hll", data[offset:offset+10]) self.styleIndices = struct.unpack('>48b', data[offset + 10:offset + 58]) stringcount, = struct.unpack('>h', data[offset+58:offset+60]) count = offset + 60 for i in range(stringcount): str_len = ord(data[count]) self.styleStrings.append(data[count + 1:count + 1 + str_len]) count = count + 1 + str_len self._unpackstylestrings() data = data[offset:count] if len(data) % 2: data = data + '\0' if DEBUG: self._rawstylemappingtable = data self.parsedthings.append((offset, count, 'stylemappingtable')) def _buildstylemappingtable(self): if not self.styleIndices: self._rawstylemappingtable = "" return data = struct.pack(">hll", self.fondClass, self.glyphTableOffset, self.styleMappingReserved) self._packstylestrings() data = data + struct.pack(*(">48b",) + self.styleIndices) stringcount = len(self.styleStrings) data = data + struct.pack(">h", stringcount) for string in self.styleStrings: data = data + chr(len(string)) + string if len(data) % 2: data = data + '\0' if DEBUG: print("style mapping table is the same?", self._rawstylemappingtable == data and 'yes.' or 'no.') self._rawstylemappingtable = data def _unpackstylestrings(self): psNames = {} self.ffFamilyName = self.styleStrings[0] for i in self.widthTables.keys(): index = self.styleIndices[i] if index == 1: psNames[i] = self.styleStrings[0] else: style = self.styleStrings[0] codes = map(ord, self.styleStrings[index - 1]) for code in codes: style = style + self.styleStrings[code - 1] psNames[i] = style self.psNames = psNames def _packstylestrings(self): nameparts = {} splitnames = {} for style, name in self.psNames.items(): split = splitname(name, self.ffFamilyName) splitnames[style] = split for part in split: nameparts[part] = None del nameparts[self.ffFamilyName] nameparts = sorted(nameparts.keys()) items = sorted(splitnames.items()) numindices = 0 for style, split in items: if len(split) > 1: numindices = numindices + 1 numindices = max(numindices, max(self.styleIndices) - 1) styleStrings = [self.ffFamilyName] + numindices * [""] + nameparts # XXX the next bit goes wrong for MM fonts. for style, split in items: if len(split) == 1: continue indices = "" for part in split[1:]: indices = indices + chr(nameparts.index(part) + numindices + 2) styleStrings[self.styleIndices[style] - 1] = indices self.styleStrings = styleStrings def _updatestylemappingtable(self): # Update the glyphTableOffset field. # This is necessary since we have to build this table to # know what the glyphTableOffset will be. # And we don't want to build it twice, do we? data = self._rawstylemappingtable if not data: return data = data[:2] + struct.pack(">l", self.glyphTableOffset) + data[6:] self._rawstylemappingtable = data def _getglyphencodingsubtable(self): glyphEncoding = self.glyphEncoding = {} if not self.glyphTableOffset: return offset = self.ffStylOff + self.glyphTableOffset data = self.FOND.data numberofentries, = struct.unpack(">h", data[offset:offset+2]) count = offset + 2 for i in range(numberofentries): glyphcode = ord(data[count]) count = count + 1 strlen = ord(data[count]) count = count + 1 glyphname = data[count:count+strlen] glyphEncoding[glyphcode] = glyphname count = count + strlen if DEBUG: self._rawglyphencodingsubtable = data[offset:count] self.parsedthings.append((offset, count, 'glyphencodingsubtable')) def _buildglyphencodingsubtable(self): if not self.glyphEncoding: self._rawglyphencodingsubtable = "" return numberofentries = len(self.glyphEncoding) data = struct.pack(">h", numberofentries) items = sorted(self.glyphEncoding.items()) for glyphcode, glyphname in items: data = data + chr(glyphcode) + chr(len(glyphname)) + glyphname self._rawglyphencodingsubtable = data uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' def splitname(name, famname = None): # XXX this goofs up MM font names: but how should it be done?? if famname: if name[:len(famname)] != famname: raise error("first part of name should be same as family name") name = name[len(famname):] split = [famname] else: split = [] current = "" for c in name: if c == '-' or c in uppercase: if current: split.append(current) current = "" current = current + c if current: split.append(current) return split def makeLWFNfilename(name): split = splitname(name) lwfnname = split[0][:5] for part in split[1:]: if part != '-': lwfnname = lwfnname + part[:3] return lwfnname class BitmapFontFile: def __init__(self, path, mode='r'): if mode == 'r': permission = 1 # read only elif mode == 'w': permission = 3 # exclusive r/w else: raise error('mode should be either "r" or "w"') self.mode = mode self.resref = Res.FSOpenResFile(path, permission) Res.UseResFile(self.resref) self.path = path self.fonds = [] self.getFONDs() def getFONDs(self): FONDcount = Res.Count1Resources('FOND') for i in range(FONDcount): fond = FontFamily(Res.Get1IndResource('FOND', i + 1), self.mode) self.fonds.append(fond) def parse(self): self.fondsbyname = {} for fond in self.fonds: fond.parse() if hasattr(fond, "psNames") and fond.psNames: psNames = sorted(fond.psNames.values()) self.fondsbyname[psNames[0]] = fond def minimalparse(self): for fond in self.fonds: fond.minimalparse() def close(self): if self.resref != None: try: Res.CloseResFile(self.resref) except Res.Error: pass self.resref = None class FondSelector: def __init__(self, fondlist): import W if not fondlist: raise ValueError("expected at least one FOND entry") if len(fondlist) == 1: self.choice = 0 return fonds = [] for fond in fondlist: fonds.append(fond.name) self.w = W.ModalDialog((200, 200), "aaa") self.w.donebutton = W.Button((-70, -26, 60, 16), "Done", self.close) self.w.l = W.List((10, 10, -10, -36), fonds, self.listhit) self.w.setdefaultbutton(self.w.donebutton) self.w.l.setselection([0]) self.w.open() def close(self): self.checksel() sel = self.w.l.getselection() self.choice = sel[0] self.w.close() def listhit(self, isDbl): if isDbl: self.w.donebutton.push() else: self.checksel() def checksel(self): sel = self.w.l.getselection() if not sel: self.w.l.setselection([0]) elif len(sel) != 1: self.w.l.setselection([sel[0]])