blob: 74ae13be8fe8463e7766989ac1080aef85ad3f22 [file] [log] [blame]
import os
import struct
from fontTools.misc import sstruct
import string
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 "<FontFamily instance of %s>" % 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 + apply(struct.pack, (">9h",) + self.ffProperty)
header = header + apply(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 = self.boundingBoxes.items()
boxes.sort()
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 = self.widthTables.items()
tables.sort()
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 = self.kernTables.items()
tables.sort()
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 = string.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 + apply(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 = nameparts.keys()
nameparts.sort()
items = splitnames.items()
items.sort()
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 = self.glyphEncoding.items()
items.sort()
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 = fond.psNames.values()
psNames.sort()
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]])