fonttools/Lib/fontTools/fondLib.py
Behdad Esfahbod 8413c108d2 Move sstruct under fontTools.misc
Our footprint in the Python module namespace is all under
fontTools now.  User code importing sstruct should be updated
to say "from fontTools.misc import sstruct".
2013-09-17 16:59:39 -04:00

555 lines
16 KiB
Python

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]])