blob: c834fc52011d241f007e2b6d45b29b7c267ab4b2 [file] [log] [blame]
import struct
from fontTools.misc import sstruct
import string
import types
# FontRec header
nfntHeaderFormat = """
> # big endian
fontType: h # font type
firstChar: h # ASCII code of first character
lastChar: h # ASCII code of last character
widMax: h # maximum character width
kernMax: h # negative of maximum character kern
nDescent: h # negative of descent
fRectWidth: h # width of font rectangle
fRectHeight: h # height of font rectangle
owTLoc: H # offset to offset/width table (in words from _this_ point)
ascent: h # ascent
descent: h # descent
leading: h # leading
rowWords: h # row width of bit image / 2
"""
headerSize = sstruct.calcsize(nfntHeaderFormat)
assert headerSize == 26
class NFNT:
def __init__(self, data=None):
if data is not None:
self.decompile(data)
def decompile(self, data):
# header; FontRec
sstruct.unpack(nfntHeaderFormat, data[:headerSize], self)
#assert self.fRectHeight == (self.ascent + self.descent)
# rest
tableSize = 2 * (self.lastChar - self.firstChar + 3)
bitmapSize = 2 * self.rowWords * self.fRectHeight
self.bits = data[headerSize:headerSize + bitmapSize]
# XXX deal with self.nDescent being a positive number
assert (headerSize + bitmapSize + tableSize - 16) / 2 == self.owTLoc # ugh...
locTable = data[headerSize + bitmapSize:headerSize + bitmapSize + tableSize]
if len(locTable) <> tableSize:
raise ValueError, 'invalid NFNT format'
owTable = data[headerSize + bitmapSize + tableSize:headerSize + bitmapSize + 2 * tableSize]
if len(owTable) <> tableSize:
raise ValueError, 'invalid NFNT format'
# fill tables
self.offsetTable = []
self.widthTable = []
self.locTable = []
for i in range(0, tableSize, 2):
self.offsetTable.append(ord(owTable[i]))
self.widthTable.append(ord(owTable[i+1]))
loc, = struct.unpack("h", locTable[i:i+2])
self.locTable.append(loc)
def compile(self):
header = sstruct.pack(nfntHeaderFormat, self)
nEntries = len(self.widthTable)
owTable = [None] * nEntries
locTable = [None] * nEntries
for i in range(nEntries):
owTable[i] = chr(self.offsetTable[i]) + chr(self.widthTable[i])
locTable[i] = struct.pack("h", self.locTable[i])
owTable = string.join(owTable, "")
locTable = string.join(locTable, "")
assert len(locTable) == len(owTable) == 2 * (self.lastChar - self.firstChar + 3)
return header + self.bits + locTable + owTable
def unpackGlyphs(self):
import numpy
nGlyphs = len(self.locTable) - 1
self.glyphs = [None] * nGlyphs
rowBytes = self.rowWords * 2
imageWidth = self.rowWords * 16
imageHeight = self.fRectHeight
bits = self.bits
bitImage = numpy.zeros((imageWidth, imageHeight), numpy.int8)
for y in range(imageHeight):
for xByte in range(rowBytes):
byte = bits[y * rowBytes + xByte]
for xBit in range(8):
x = 8 * xByte + xBit
bit = (ord(byte) >> (7 - xBit)) & 0x01
bitImage[x, y] = bit
for i in range(nGlyphs):
width = self.widthTable[i]
offset = self.offsetTable[i]
if width == 255 and offset == 255:
self.glyphs[i] = None
else:
imageL = self.locTable[i]
imageR = self.locTable[i+1]
imageWidth = imageR - imageL
offset = offset + self.kernMax
self.glyphs[i] = glyph = Glyph(width, offset, bitImage[imageL:imageR])
def packGlyphs(self):
import numpy
imageWidth = 0
kernMax = 0
imageHeight = None
widMax = 0
fRectWidth = 0
for glyph in self.glyphs:
if glyph is None:
continue
if imageHeight is None:
imageHeight = glyph.pixels.shape[1]
else:
assert imageHeight == glyph.pixels.shape[1]
imageWidth = imageWidth + glyph.pixels.shape[0]
kernMax = min(kernMax, glyph.offset)
widMax = max(widMax, glyph.width)
fRectWidth = max(fRectWidth, glyph.pixels.shape[0] + glyph.offset)
fRectWidth = fRectWidth - kernMax
imageWidth = 16 * ((imageWidth - 1) / 16 + 1)
rowBytes = imageWidth / 8
rowWords = rowBytes / 2
bitImage = numpy.zeros((imageWidth, imageHeight), numpy.int8)
locTable = []
widthTable = []
offsetTable = []
loc = 0
for glyph in self.glyphs:
locTable.append(loc)
if glyph is None:
widthTable.append(255)
offsetTable.append(255)
continue
widthTable.append(glyph.width)
offsetTable.append(glyph.offset - kernMax)
imageWidth = glyph.pixels.shape[0]
bitImage[loc:loc+imageWidth] = glyph.pixels
loc = loc + imageWidth
locTable.append(loc)
widthTable.append(255)
offsetTable.append(255)
bits = []
for y in range(imageHeight):
for xByte in range(rowBytes):
byte = 0
for x in range(8):
byte = byte | ((bitImage[8 * xByte + x, y] & 0x01) << (7 - x))
bits.append(chr(byte))
bits = string.join(bits, "")
# assign values
self.fontType = 0x9000
self.lastChar = self.firstChar + len(self.glyphs) - 2
self.widMax = widMax
self.kernMax = kernMax
self.descent = imageHeight - self.ascent
self.nDescent = -self.descent
self.fRectWidth = fRectWidth
self.fRectHeight = imageHeight
self.rowWords = rowWords
tableSize = 2 * (self.lastChar - self.firstChar + 3)
self.owTLoc = (headerSize + len(bits) + tableSize - 16) / 2
self.bits = bits
self.locTable = locTable
self.widthTable = widthTable
self.offsetTable = offsetTable
def getMissing(self):
return self.glyphs[-1]
def __getitem__(self, charNum):
if charNum > self.lastChar or charNum < 0:
raise IndexError, "no such character"
index = charNum - self.firstChar
if index < 0:
return None
return self.glyphs[index]
def __setitem__(self, charNum, glyph):
if charNum > self.lastChar or charNum < 0:
raise IndexError, "no such character"
index = charNum - self.firstChar
if index < 0:
raise IndexError, "no such character"
self.glyphs[index] = glyph
def __len__(self):
return len(self.locTable) - 2 + self.firstChar
#
# XXX old cruft
#
def createQdBitImage(self):
import Qd
self.bitImage = Qd.BitMap(self.bits, 2 * self.rowWords, (0, 0, self.rowWords * 16, self.fRectHeight))
def drawstring(self, astring, destbits, xOffset=0, yOffset=0):
drawchar = self.drawchar
for ch in astring:
xOffset = drawchar(ch, destbits, xOffset, yOffset)
return xOffset
def drawchar(self, ch, destbits, xOffset, yOffset=0):
import Qd
width, bounds, destbounds = self.getcharbounds(ch)
destbounds = Qd.OffsetRect(destbounds, xOffset, yOffset)
Qd.CopyBits(self.bitImage, destbits, bounds, destbounds, 1, None)
return xOffset + width
def stringwidth(self, astring):
charwidth = self.charwidth
width = 0
for ch in astring:
width = width + charwidth(ch)
return width
def charwidth(self, ch):
cindex = ord(ch) - self.firstChar
if cindex > self.lastChar or \
(self.offsetTable[cindex] == 255 and self.widthTable[cindex] == 255):
cindex = -2 # missing char
return self.widthTable[cindex]
def getcharbounds(self, ch):
cindex = ord(ch) - self.firstChar
if cindex > self.lastChar or \
(self.offsetTable[cindex] == 255 and self.widthTable[cindex] == 255):
return self.getcharboundsindex(-2) # missing char
return self.getcharboundsindex(cindex)
def getcharboundsindex(self, cindex):
offset = self.offsetTable[cindex]
width = self.widthTable[cindex]
if offset == 255 and width == 255:
raise ValueError, "character not defined"
location0 = self.locTable[cindex]
location1 = self.locTable[cindex + 1]
srcbounds = (location0, 0, location1, self.fRectHeight)
destbounds = ( offset + self.kernMax,
0,
offset + self.kernMax + location1 - location0,
self.fRectHeight )
return width, srcbounds, destbounds
class Glyph:
def __init__(self, width, offset, pixels=None, pixelDepth=1):
self.width = width
self.offset = offset
self.pixelDepth = pixelDepth
self.pixels = pixels
def dataFromFile(pathOrFSSpec, nameOrID="", resType='NFNT'):
from Carbon import Res
resref = Res.FSOpenResFile(pathOrFSSpec, 1) # readonly
try:
Res.UseResFile(resref)
if not nameOrID:
# just take the first in the file
res = Res.Get1IndResource(resType, 1)
elif type(nameOrID) == types.IntType:
res = Res.Get1Resource(resType, nameOrID)
else:
res = Res.Get1NamedResource(resType, nameOrID)
theID, theType, name = res.GetResInfo()
data = res.data
finally:
Res.CloseResFile(resref)
return data
def fromFile(pathOrFSSpec, nameOrID="", resType='NFNT'):
data = dataFromFile(pathOrFSSpec, nameOrID, resType)
return NFNT(data)
if __name__ == "__main__":
import EasyDialogs
path = EasyDialogs.AskFileForOpen()
if path:
data = dataFromFile(path)
font = NFNT(data)
font.unpackGlyphs()
font.packGlyphs()
data2 = font.compile()
print "xxxxx", data == data2, len(data) == len(data2)