Behdad Esfahbod | 8413c10 | 2013-09-17 16:59:39 -0400 | [diff] [blame] | 1 | import struct |
| 2 | from fontTools.misc import sstruct |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 3 | import string |
| 4 | import types |
| 5 | |
| 6 | |
| 7 | # FontRec header |
| 8 | nfntHeaderFormat = """ |
| 9 | > # big endian |
| 10 | fontType: h # font type |
| 11 | firstChar: h # ASCII code of first character |
| 12 | lastChar: h # ASCII code of last character |
| 13 | widMax: h # maximum character width |
| 14 | kernMax: h # negative of maximum character kern |
| 15 | nDescent: h # negative of descent |
| 16 | fRectWidth: h # width of font rectangle |
| 17 | fRectHeight: h # height of font rectangle |
| 18 | owTLoc: H # offset to offset/width table (in words from _this_ point) |
| 19 | ascent: h # ascent |
| 20 | descent: h # descent |
| 21 | leading: h # leading |
| 22 | rowWords: h # row width of bit image / 2 |
| 23 | """ |
| 24 | headerSize = sstruct.calcsize(nfntHeaderFormat) |
| 25 | assert headerSize == 26 |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 26 | |
| 27 | |
| 28 | class NFNT: |
| 29 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 30 | def __init__(self, data=None): |
| 31 | if data is not None: |
| 32 | self.decompile(data) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 33 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 34 | def decompile(self, data): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 35 | # header; FontRec |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 36 | sstruct.unpack(nfntHeaderFormat, data[:headerSize], self) |
| 37 | |
| 38 | #assert self.fRectHeight == (self.ascent + self.descent) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 39 | |
| 40 | # rest |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 41 | tableSize = 2 * (self.lastChar - self.firstChar + 3) |
| 42 | bitmapSize = 2 * self.rowWords * self.fRectHeight |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 43 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 44 | self.bits = data[headerSize:headerSize + bitmapSize] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 45 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 46 | # XXX deal with self.nDescent being a positive number |
| 47 | assert (headerSize + bitmapSize + tableSize - 16) / 2 == self.owTLoc # ugh... |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 48 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 49 | locTable = data[headerSize + bitmapSize:headerSize + bitmapSize + tableSize] |
| 50 | if len(locTable) <> tableSize: |
| 51 | raise ValueError, 'invalid NFNT format' |
| 52 | |
| 53 | owTable = data[headerSize + bitmapSize + tableSize:headerSize + bitmapSize + 2 * tableSize] |
| 54 | if len(owTable) <> tableSize: |
| 55 | raise ValueError, 'invalid NFNT format' |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 56 | |
| 57 | # fill tables |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 58 | self.offsetTable = [] |
| 59 | self.widthTable = [] |
| 60 | self.locTable = [] |
| 61 | for i in range(0, tableSize, 2): |
| 62 | self.offsetTable.append(ord(owTable[i])) |
| 63 | self.widthTable.append(ord(owTable[i+1])) |
| 64 | loc, = struct.unpack("h", locTable[i:i+2]) |
| 65 | self.locTable.append(loc) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 66 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 67 | def compile(self): |
| 68 | header = sstruct.pack(nfntHeaderFormat, self) |
| 69 | nEntries = len(self.widthTable) |
| 70 | owTable = [None] * nEntries |
| 71 | locTable = [None] * nEntries |
| 72 | for i in range(nEntries): |
| 73 | owTable[i] = chr(self.offsetTable[i]) + chr(self.widthTable[i]) |
| 74 | locTable[i] = struct.pack("h", self.locTable[i]) |
| 75 | owTable = string.join(owTable, "") |
| 76 | locTable = string.join(locTable, "") |
| 77 | assert len(locTable) == len(owTable) == 2 * (self.lastChar - self.firstChar + 3) |
| 78 | return header + self.bits + locTable + owTable |
| 79 | |
| 80 | def unpackGlyphs(self): |
jvr | 1b7d54f | 2008-03-04 15:25:27 +0000 | [diff] [blame] | 81 | import numpy |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 82 | nGlyphs = len(self.locTable) - 1 |
| 83 | self.glyphs = [None] * nGlyphs |
| 84 | |
| 85 | rowBytes = self.rowWords * 2 |
| 86 | imageWidth = self.rowWords * 16 |
| 87 | imageHeight = self.fRectHeight |
| 88 | bits = self.bits |
jvr | 1b7d54f | 2008-03-04 15:25:27 +0000 | [diff] [blame] | 89 | bitImage = numpy.zeros((imageWidth, imageHeight), numpy.int8) |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 90 | |
| 91 | for y in range(imageHeight): |
| 92 | for xByte in range(rowBytes): |
| 93 | byte = bits[y * rowBytes + xByte] |
| 94 | for xBit in range(8): |
| 95 | x = 8 * xByte + xBit |
| 96 | bit = (ord(byte) >> (7 - xBit)) & 0x01 |
| 97 | bitImage[x, y] = bit |
| 98 | |
| 99 | for i in range(nGlyphs): |
| 100 | width = self.widthTable[i] |
| 101 | offset = self.offsetTable[i] |
| 102 | if width == 255 and offset == 255: |
| 103 | self.glyphs[i] = None |
| 104 | else: |
| 105 | imageL = self.locTable[i] |
| 106 | imageR = self.locTable[i+1] |
| 107 | imageWidth = imageR - imageL |
| 108 | offset = offset + self.kernMax |
| 109 | self.glyphs[i] = glyph = Glyph(width, offset, bitImage[imageL:imageR]) |
| 110 | |
| 111 | def packGlyphs(self): |
jvr | 1b7d54f | 2008-03-04 15:25:27 +0000 | [diff] [blame] | 112 | import numpy |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 113 | imageWidth = 0 |
| 114 | kernMax = 0 |
| 115 | imageHeight = None |
| 116 | widMax = 0 |
| 117 | fRectWidth = 0 |
| 118 | for glyph in self.glyphs: |
| 119 | if glyph is None: |
| 120 | continue |
| 121 | if imageHeight is None: |
| 122 | imageHeight = glyph.pixels.shape[1] |
| 123 | else: |
| 124 | assert imageHeight == glyph.pixels.shape[1] |
| 125 | imageWidth = imageWidth + glyph.pixels.shape[0] |
| 126 | kernMax = min(kernMax, glyph.offset) |
| 127 | widMax = max(widMax, glyph.width) |
| 128 | fRectWidth = max(fRectWidth, glyph.pixels.shape[0] + glyph.offset) |
| 129 | |
jvr | 5149e18 | 2003-08-22 18:53:29 +0000 | [diff] [blame] | 130 | fRectWidth = fRectWidth - kernMax |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 131 | imageWidth = 16 * ((imageWidth - 1) / 16 + 1) |
| 132 | rowBytes = imageWidth / 8 |
| 133 | rowWords = rowBytes / 2 |
jvr | 1b7d54f | 2008-03-04 15:25:27 +0000 | [diff] [blame] | 134 | bitImage = numpy.zeros((imageWidth, imageHeight), numpy.int8) |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 135 | locTable = [] |
| 136 | widthTable = [] |
| 137 | offsetTable = [] |
| 138 | loc = 0 |
| 139 | for glyph in self.glyphs: |
| 140 | locTable.append(loc) |
| 141 | if glyph is None: |
| 142 | widthTable.append(255) |
| 143 | offsetTable.append(255) |
| 144 | continue |
| 145 | widthTable.append(glyph.width) |
| 146 | offsetTable.append(glyph.offset - kernMax) |
| 147 | imageWidth = glyph.pixels.shape[0] |
| 148 | bitImage[loc:loc+imageWidth] = glyph.pixels |
| 149 | loc = loc + imageWidth |
| 150 | |
| 151 | locTable.append(loc) |
| 152 | widthTable.append(255) |
| 153 | offsetTable.append(255) |
| 154 | |
| 155 | bits = [] |
| 156 | for y in range(imageHeight): |
| 157 | for xByte in range(rowBytes): |
| 158 | byte = 0 |
| 159 | for x in range(8): |
| 160 | byte = byte | ((bitImage[8 * xByte + x, y] & 0x01) << (7 - x)) |
| 161 | bits.append(chr(byte)) |
| 162 | bits = string.join(bits, "") |
| 163 | |
| 164 | # assign values |
| 165 | self.fontType = 0x9000 |
| 166 | self.lastChar = self.firstChar + len(self.glyphs) - 2 |
| 167 | self.widMax = widMax |
| 168 | self.kernMax = kernMax |
| 169 | self.descent = imageHeight - self.ascent |
| 170 | self.nDescent = -self.descent |
| 171 | self.fRectWidth = fRectWidth |
| 172 | self.fRectHeight = imageHeight |
| 173 | self.rowWords = rowWords |
| 174 | |
| 175 | tableSize = 2 * (self.lastChar - self.firstChar + 3) |
| 176 | self.owTLoc = (headerSize + len(bits) + tableSize - 16) / 2 |
| 177 | |
| 178 | self.bits = bits |
| 179 | self.locTable = locTable |
| 180 | self.widthTable = widthTable |
| 181 | self.offsetTable = offsetTable |
| 182 | |
| 183 | def getMissing(self): |
| 184 | return self.glyphs[-1] |
| 185 | |
| 186 | def __getitem__(self, charNum): |
| 187 | if charNum > self.lastChar or charNum < 0: |
| 188 | raise IndexError, "no such character" |
| 189 | index = charNum - self.firstChar |
| 190 | if index < 0: |
| 191 | return None |
| 192 | return self.glyphs[index] |
| 193 | |
| 194 | def __setitem__(self, charNum, glyph): |
| 195 | if charNum > self.lastChar or charNum < 0: |
| 196 | raise IndexError, "no such character" |
| 197 | index = charNum - self.firstChar |
| 198 | if index < 0: |
| 199 | raise IndexError, "no such character" |
| 200 | self.glyphs[index] = glyph |
| 201 | |
| 202 | def __len__(self): |
| 203 | return len(self.locTable) - 2 + self.firstChar |
| 204 | |
| 205 | # |
| 206 | # XXX old cruft |
| 207 | # |
| 208 | |
| 209 | def createQdBitImage(self): |
| 210 | import Qd |
| 211 | self.bitImage = Qd.BitMap(self.bits, 2 * self.rowWords, (0, 0, self.rowWords * 16, self.fRectHeight)) |
| 212 | |
| 213 | def drawstring(self, astring, destbits, xOffset=0, yOffset=0): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 214 | drawchar = self.drawchar |
| 215 | for ch in astring: |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 216 | xOffset = drawchar(ch, destbits, xOffset, yOffset) |
| 217 | return xOffset |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 218 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 219 | def drawchar(self, ch, destbits, xOffset, yOffset=0): |
| 220 | import Qd |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 221 | width, bounds, destbounds = self.getcharbounds(ch) |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 222 | destbounds = Qd.OffsetRect(destbounds, xOffset, yOffset) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 223 | Qd.CopyBits(self.bitImage, destbits, bounds, destbounds, 1, None) |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 224 | return xOffset + width |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 225 | |
| 226 | def stringwidth(self, astring): |
| 227 | charwidth = self.charwidth |
| 228 | width = 0 |
| 229 | for ch in astring: |
| 230 | width = width + charwidth(ch) |
| 231 | return width |
| 232 | |
| 233 | def charwidth(self, ch): |
| 234 | cindex = ord(ch) - self.firstChar |
| 235 | if cindex > self.lastChar or \ |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 236 | (self.offsetTable[cindex] == 255 and self.widthTable[cindex] == 255): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 237 | cindex = -2 # missing char |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 238 | return self.widthTable[cindex] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 239 | |
| 240 | def getcharbounds(self, ch): |
| 241 | cindex = ord(ch) - self.firstChar |
| 242 | if cindex > self.lastChar or \ |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 243 | (self.offsetTable[cindex] == 255 and self.widthTable[cindex] == 255): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 244 | return self.getcharboundsindex(-2) # missing char |
| 245 | return self.getcharboundsindex(cindex) |
| 246 | |
| 247 | def getcharboundsindex(self, cindex): |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 248 | offset = self.offsetTable[cindex] |
| 249 | width = self.widthTable[cindex] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 250 | if offset == 255 and width == 255: |
| 251 | raise ValueError, "character not defined" |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 252 | location0 = self.locTable[cindex] |
| 253 | location1 = self.locTable[cindex + 1] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 254 | srcbounds = (location0, 0, location1, self.fRectHeight) |
| 255 | destbounds = ( offset + self.kernMax, |
| 256 | 0, |
| 257 | offset + self.kernMax + location1 - location0, |
| 258 | self.fRectHeight ) |
| 259 | return width, srcbounds, destbounds |
| 260 | |
| 261 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 262 | class Glyph: |
| 263 | |
| 264 | def __init__(self, width, offset, pixels=None, pixelDepth=1): |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 265 | self.width = width |
| 266 | self.offset = offset |
| 267 | self.pixelDepth = pixelDepth |
| 268 | self.pixels = pixels |
| 269 | |
| 270 | |
| 271 | def dataFromFile(pathOrFSSpec, nameOrID="", resType='NFNT'): |
jvr | 192655c | 2006-10-21 13:16:51 +0000 | [diff] [blame] | 272 | from Carbon import Res |
jvr | 91bca42 | 2012-10-18 12:49:22 +0000 | [diff] [blame] | 273 | resref = Res.FSOpenResFile(pathOrFSSpec, 1) # readonly |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 274 | try: |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 275 | Res.UseResFile(resref) |
| 276 | if not nameOrID: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 277 | # just take the first in the file |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 278 | res = Res.Get1IndResource(resType, 1) |
| 279 | elif type(nameOrID) == types.IntType: |
| 280 | res = Res.Get1Resource(resType, nameOrID) |
| 281 | else: |
| 282 | res = Res.Get1NamedResource(resType, nameOrID) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 283 | theID, theType, name = res.GetResInfo() |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 284 | data = res.data |
| 285 | finally: |
| 286 | Res.CloseResFile(resref) |
| 287 | return data |
| 288 | |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 289 | |
| 290 | def fromFile(pathOrFSSpec, nameOrID="", resType='NFNT'): |
| 291 | data = dataFromFile(pathOrFSSpec, nameOrID, resType) |
| 292 | return NFNT(data) |
| 293 | |
| 294 | |
| 295 | if __name__ == "__main__": |
jvr | 91bca42 | 2012-10-18 12:49:22 +0000 | [diff] [blame] | 296 | import EasyDialogs |
| 297 | path = EasyDialogs.AskFileForOpen() |
| 298 | if path: |
| 299 | data = dataFromFile(path) |
Just | ad6bb5a | 2001-03-09 12:42:25 +0000 | [diff] [blame] | 300 | font = NFNT(data) |
| 301 | font.unpackGlyphs() |
| 302 | font.packGlyphs() |
| 303 | data2 = font.compile() |
| 304 | print "xxxxx", data == data2, len(data) == len(data2) |