blob: c834fc52011d241f007e2b6d45b29b7c267ab4b2 [file] [log] [blame]
Behdad Esfahbod8413c102013-09-17 16:59:39 -04001import struct
2from fontTools.misc import sstruct
Justad6bb5a2001-03-09 12:42:25 +00003import string
4import types
5
6
7# FontRec header
8nfntHeaderFormat = """
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"""
24headerSize = sstruct.calcsize(nfntHeaderFormat)
25assert headerSize == 26
Just7842e561999-12-16 21:34:53 +000026
27
28class NFNT:
29
Justad6bb5a2001-03-09 12:42:25 +000030 def __init__(self, data=None):
31 if data is not None:
32 self.decompile(data)
Just7842e561999-12-16 21:34:53 +000033
Justad6bb5a2001-03-09 12:42:25 +000034 def decompile(self, data):
Just7842e561999-12-16 21:34:53 +000035 # header; FontRec
Justad6bb5a2001-03-09 12:42:25 +000036 sstruct.unpack(nfntHeaderFormat, data[:headerSize], self)
37
38 #assert self.fRectHeight == (self.ascent + self.descent)
Just7842e561999-12-16 21:34:53 +000039
40 # rest
Justad6bb5a2001-03-09 12:42:25 +000041 tableSize = 2 * (self.lastChar - self.firstChar + 3)
42 bitmapSize = 2 * self.rowWords * self.fRectHeight
Just7842e561999-12-16 21:34:53 +000043
Justad6bb5a2001-03-09 12:42:25 +000044 self.bits = data[headerSize:headerSize + bitmapSize]
Just7842e561999-12-16 21:34:53 +000045
Justad6bb5a2001-03-09 12:42:25 +000046 # XXX deal with self.nDescent being a positive number
47 assert (headerSize + bitmapSize + tableSize - 16) / 2 == self.owTLoc # ugh...
Just7842e561999-12-16 21:34:53 +000048
Justad6bb5a2001-03-09 12:42:25 +000049 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'
Just7842e561999-12-16 21:34:53 +000056
57 # fill tables
Justad6bb5a2001-03-09 12:42:25 +000058 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)
Just7842e561999-12-16 21:34:53 +000066
Justad6bb5a2001-03-09 12:42:25 +000067 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):
jvr1b7d54f2008-03-04 15:25:27 +000081 import numpy
Justad6bb5a2001-03-09 12:42:25 +000082 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
jvr1b7d54f2008-03-04 15:25:27 +000089 bitImage = numpy.zeros((imageWidth, imageHeight), numpy.int8)
Justad6bb5a2001-03-09 12:42:25 +000090
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):
jvr1b7d54f2008-03-04 15:25:27 +0000112 import numpy
Justad6bb5a2001-03-09 12:42:25 +0000113 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
jvr5149e182003-08-22 18:53:29 +0000130 fRectWidth = fRectWidth - kernMax
Justad6bb5a2001-03-09 12:42:25 +0000131 imageWidth = 16 * ((imageWidth - 1) / 16 + 1)
132 rowBytes = imageWidth / 8
133 rowWords = rowBytes / 2
jvr1b7d54f2008-03-04 15:25:27 +0000134 bitImage = numpy.zeros((imageWidth, imageHeight), numpy.int8)
Justad6bb5a2001-03-09 12:42:25 +0000135 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):
Just7842e561999-12-16 21:34:53 +0000214 drawchar = self.drawchar
215 for ch in astring:
Justad6bb5a2001-03-09 12:42:25 +0000216 xOffset = drawchar(ch, destbits, xOffset, yOffset)
217 return xOffset
Just7842e561999-12-16 21:34:53 +0000218
Justad6bb5a2001-03-09 12:42:25 +0000219 def drawchar(self, ch, destbits, xOffset, yOffset=0):
220 import Qd
Just7842e561999-12-16 21:34:53 +0000221 width, bounds, destbounds = self.getcharbounds(ch)
Justad6bb5a2001-03-09 12:42:25 +0000222 destbounds = Qd.OffsetRect(destbounds, xOffset, yOffset)
Just7842e561999-12-16 21:34:53 +0000223 Qd.CopyBits(self.bitImage, destbits, bounds, destbounds, 1, None)
Justad6bb5a2001-03-09 12:42:25 +0000224 return xOffset + width
Just7842e561999-12-16 21:34:53 +0000225
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 \
Justad6bb5a2001-03-09 12:42:25 +0000236 (self.offsetTable[cindex] == 255 and self.widthTable[cindex] == 255):
Just7842e561999-12-16 21:34:53 +0000237 cindex = -2 # missing char
Justad6bb5a2001-03-09 12:42:25 +0000238 return self.widthTable[cindex]
Just7842e561999-12-16 21:34:53 +0000239
240 def getcharbounds(self, ch):
241 cindex = ord(ch) - self.firstChar
242 if cindex > self.lastChar or \
Justad6bb5a2001-03-09 12:42:25 +0000243 (self.offsetTable[cindex] == 255 and self.widthTable[cindex] == 255):
Just7842e561999-12-16 21:34:53 +0000244 return self.getcharboundsindex(-2) # missing char
245 return self.getcharboundsindex(cindex)
246
247 def getcharboundsindex(self, cindex):
Justad6bb5a2001-03-09 12:42:25 +0000248 offset = self.offsetTable[cindex]
249 width = self.widthTable[cindex]
Just7842e561999-12-16 21:34:53 +0000250 if offset == 255 and width == 255:
251 raise ValueError, "character not defined"
Justad6bb5a2001-03-09 12:42:25 +0000252 location0 = self.locTable[cindex]
253 location1 = self.locTable[cindex + 1]
Just7842e561999-12-16 21:34:53 +0000254 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
Justad6bb5a2001-03-09 12:42:25 +0000262class Glyph:
263
264 def __init__(self, width, offset, pixels=None, pixelDepth=1):
Justad6bb5a2001-03-09 12:42:25 +0000265 self.width = width
266 self.offset = offset
267 self.pixelDepth = pixelDepth
268 self.pixels = pixels
269
270
271def dataFromFile(pathOrFSSpec, nameOrID="", resType='NFNT'):
jvr192655c2006-10-21 13:16:51 +0000272 from Carbon import Res
jvr91bca422012-10-18 12:49:22 +0000273 resref = Res.FSOpenResFile(pathOrFSSpec, 1) # readonly
Just7842e561999-12-16 21:34:53 +0000274 try:
Justad6bb5a2001-03-09 12:42:25 +0000275 Res.UseResFile(resref)
276 if not nameOrID:
Just7842e561999-12-16 21:34:53 +0000277 # just take the first in the file
Justad6bb5a2001-03-09 12:42:25 +0000278 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)
Just7842e561999-12-16 21:34:53 +0000283 theID, theType, name = res.GetResInfo()
Just7842e561999-12-16 21:34:53 +0000284 data = res.data
285 finally:
286 Res.CloseResFile(resref)
287 return data
288
Justad6bb5a2001-03-09 12:42:25 +0000289
290def fromFile(pathOrFSSpec, nameOrID="", resType='NFNT'):
291 data = dataFromFile(pathOrFSSpec, nameOrID, resType)
292 return NFNT(data)
293
294
295if __name__ == "__main__":
jvr91bca422012-10-18 12:49:22 +0000296 import EasyDialogs
297 path = EasyDialogs.AskFileForOpen()
298 if path:
299 data = dataFromFile(path)
Justad6bb5a2001-03-09 12:42:25 +0000300 font = NFNT(data)
301 font.unpackGlyphs()
302 font.packGlyphs()
303 data2 = font.compile()
304 print "xxxxx", data == data2, len(data) == len(data2)