1 // texturefont.cpp
2 //
3 // Copyright (C) 2001, Chris Laurel <claurel@shatters.net>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9
10 #include <cassert>
11 #include <cstring>
12 #include <fstream>
13
14 #ifndef _WIN32
15 #ifndef TARGET_OS_MAC
16 #include <config.h>
17 #endif /* TARGET_OS_MAC */
18 #endif /* _WIN32 */
19
20 #include <celutil/debug.h>
21 #include <celutil/bytes.h>
22 #include <celutil/utf8.h>
23 #include <celutil/util.h>
24 #include <celengine/gl.h>
25 #include "texturefont.h"
26
27 using namespace std;
28
29
TextureFont()30 TextureFont::TextureFont() :
31 maxAscent(0),
32 maxDescent(0),
33 maxWidth(0),
34 texWidth(0),
35 texHeight(0),
36 fontImage(NULL),
37 texName(0),
38 glyphLookup(NULL),
39 glyphLookupTableSize(0)
40 {
41 }
42
43
~TextureFont()44 TextureFont::~TextureFont()
45 {
46 if (texName != 0)
47 glDeleteTextures(1, (const GLuint*) &texName);
48 if (fontImage != NULL)
49 delete[] fontImage;
50 if (glyphLookup != NULL)
51 delete[] glyphLookup;
52 }
53
54
render(wchar_t ch) const55 void TextureFont::render(wchar_t ch) const
56 {
57 const Glyph* glyph = getGlyph(ch);
58 if (glyph == NULL) glyph = getGlyph((wchar_t)'?');
59 if (glyph != NULL)
60 {
61 glBegin(GL_QUADS);
62 glTexCoord2f(glyph->texCoords[0].u, glyph->texCoords[0].v);
63 glVertex2f(glyph->xoff, glyph->yoff);
64 glTexCoord2f(glyph->texCoords[1].u, glyph->texCoords[1].v);
65 glVertex2f(glyph->xoff + glyph->width, glyph->yoff);
66 glTexCoord2f(glyph->texCoords[2].u, glyph->texCoords[2].v);
67 glVertex2f(glyph->xoff + glyph->width, glyph->yoff + glyph->height);
68 glTexCoord2f(glyph->texCoords[3].u, glyph->texCoords[3].v);
69 glVertex2f(glyph->xoff, glyph->yoff + glyph->height);
70 glEnd();
71 glTranslatef(glyph->advance, 0.0f, 0.0f);
72 }
73 }
74
75
render(const string & s) const76 void TextureFont::render(const string& s) const
77 {
78 int len = s.length();
79 bool validChar = true;
80 int i = 0;
81
82 while (i < len && validChar) {
83 wchar_t ch = 0;
84 validChar = UTF8Decode(s, i, ch);
85 i += UTF8EncodedSize(ch);
86
87 render(ch);
88 }
89 }
90
91
getWidth(const string & s) const92 int TextureFont::getWidth(const string& s) const
93 {
94 int width = 0;
95 int len = s.length();
96 bool validChar = true;
97 int i = 0;
98
99 while (i < len && validChar)
100 {
101 wchar_t ch = 0;
102 validChar = UTF8Decode(s, i, ch);
103 i += UTF8EncodedSize(ch);
104
105 const Glyph* g = getGlyph(ch);
106 if (g != NULL)
107 width += g->advance;
108 }
109
110 return width;
111 }
112
113
getHeight() const114 int TextureFont::getHeight() const
115 {
116 return maxAscent + maxDescent;
117 }
118
getMaxWidth() const119 int TextureFont::getMaxWidth() const
120 {
121 return maxWidth;
122 }
123
getMaxAscent() const124 int TextureFont::getMaxAscent() const
125 {
126 return maxAscent;
127 }
128
setMaxAscent(int _maxAscent)129 void TextureFont::setMaxAscent(int _maxAscent)
130 {
131 maxAscent = _maxAscent;
132 }
133
getMaxDescent() const134 int TextureFont::getMaxDescent() const
135 {
136 return maxDescent;
137 }
138
setMaxDescent(int _maxDescent)139 void TextureFont::setMaxDescent(int _maxDescent)
140 {
141 maxDescent = _maxDescent;
142 }
143
144
getTextureName() const145 int TextureFont::getTextureName() const
146 {
147 return texName;
148 }
149
150
bind()151 void TextureFont::bind()
152 {
153 if (texName != 0)
154 glBindTexture(GL_TEXTURE_2D, texName);
155 }
156
157
addGlyph(const TextureFont::Glyph & g)158 void TextureFont::addGlyph(const TextureFont::Glyph& g)
159 {
160 glyphs.insert(glyphs.end(), g);
161 if (g.width > maxWidth)
162 maxWidth = g.width;
163 }
164
165
getGlyph(wchar_t ch) const166 const TextureFont::Glyph* TextureFont::getGlyph(wchar_t ch) const
167 {
168 if (ch >= (wchar_t)glyphLookupTableSize)
169 return NULL;
170 else
171 return glyphLookup[ch];
172 }
173
174
buildTexture()175 bool TextureFont::buildTexture()
176 {
177 assert(fontImage != NULL);
178
179 if (texName != 0)
180 glDeleteTextures(1, (const GLuint*) &texName);
181 glGenTextures(1, (GLuint*) &texName);
182 if (texName == 0)
183 {
184 DPRINTF(0, "Failed to allocate texture object for font.\n");
185 return false;
186 }
187
188 glBindTexture(GL_TEXTURE_2D, texName);
189
190 // Don't build mipmaps . . . should really make them an option.
191 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
192 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
193 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA,
194 texWidth, texHeight,
195 0,
196 GL_ALPHA, GL_UNSIGNED_BYTE,
197 fontImage);
198
199 return true;
200 }
201
202
rebuildGlyphLookupTable()203 void TextureFont::rebuildGlyphLookupTable()
204 {
205 if (glyphs.size() == 0)
206 return;
207
208 // Find the largest glyph id
209 int maxID = glyphs[0].__id;
210 vector<Glyph>::const_iterator iter;
211 for (iter = glyphs.begin(); iter != glyphs.end(); iter++)
212 {
213 if (iter->__id > maxID)
214 maxID = iter->__id;
215 }
216
217 // If there was already a lookup table, delete it.
218 if (glyphLookup != NULL)
219 delete[] glyphLookup;
220
221 DPRINTF(1, "texturefont: allocating glyph lookup table with %d entries.\n",
222 maxID + 1);
223 glyphLookup = new const Glyph*[maxID + 1];
224 for (int i = 0; i <= maxID; i++)
225 glyphLookup[i] = NULL;
226
227 // Fill the table with glyph pointers
228 for (iter = glyphs.begin(); iter != glyphs.end(); iter++)
229 glyphLookup[iter->__id] = &(*iter);
230 glyphLookupTableSize = (unsigned int) maxID + 1;
231 }
232
233
readUint32(istream & in,bool swap)234 static uint32 readUint32(istream& in, bool swap)
235 {
236 uint32 x;
237 in.read(reinterpret_cast<char*>(&x), sizeof x);
238 return swap ? bswap_32(x) : x;
239 }
240
readUint16(istream & in,bool swap)241 static uint16 readUint16(istream& in, bool swap)
242 {
243 uint16 x;
244 in.read(reinterpret_cast<char*>(&x), sizeof x);
245 return swap ? bswap_16(x) : x;
246 }
247
readUint8(istream & in)248 static uint8 readUint8(istream& in)
249 {
250 uint8 x;
251 in.read(reinterpret_cast<char*>(&x), sizeof x);
252 return x;
253 }
254
255 /* Not currently used
256 static int32 readInt32(istream& in, bool swap)
257 {
258 int32 x;
259 in.read(reinterpret_cast<char*>(&x), sizeof x);
260 return swap ? static_cast<int32>(bswap_32(static_cast<uint32>(x))) : x;
261 }*/
262
readInt16(istream & in,bool swap)263 static int16 readInt16(istream& in, bool swap)
264 {
265 int16 x;
266 in.read(reinterpret_cast<char*>(&x), sizeof x);
267 return swap ? static_cast<int16>(bswap_16(static_cast<uint16>(x))) : x;
268 }
269
readInt8(istream & in)270 static int8 readInt8(istream& in)
271 {
272 int8 x;
273 in.read(reinterpret_cast<char*>(&x), sizeof x);
274 return x;
275 }
276
277
load(istream & in)278 TextureFont* TextureFont::load(istream& in)
279 {
280 char header[4];
281
282 in.read(header, sizeof header);
283 if (!in.good() || strncmp(header, "\377txf", 4) != 0)
284 {
285 DPRINTF(0, "Stream is not a texture font!.\n");
286 return NULL;
287 }
288
289 uint32 endiannessTest = 0;
290 in.read(reinterpret_cast<char*>(&endiannessTest), sizeof endiannessTest);
291 if (!in.good())
292 {
293 DPRINTF(0, "Error reading endianness bytes in txf header.\n");
294 return NULL;
295 }
296
297 bool byteSwap;
298 if (endiannessTest == 0x78563412)
299 byteSwap = true;
300 else if (endiannessTest == 0x12345678)
301 byteSwap = false;
302 else
303 {
304 DPRINTF(0, "Stream is not a texture font!.\n");
305 return NULL;
306 }
307
308 int format = readUint32(in, byteSwap);
309 unsigned int texWidth = readUint32(in, byteSwap);
310 unsigned int texHeight = readUint32(in, byteSwap);
311 unsigned int maxAscent = readUint32(in, byteSwap);
312 unsigned int maxDescent = readUint32(in, byteSwap);
313 unsigned int nGlyphs = readUint32(in, byteSwap);
314
315 if (!in)
316 {
317 DPRINTF(0, "Texture font stream is incomplete");
318 return NULL;
319 }
320
321 DPRINTF(1, "Font contains %d glyphs.\n", nGlyphs);
322
323 TextureFont* font = new TextureFont();
324 assert(font != NULL);
325
326 font->setMaxAscent(maxAscent);
327 font->setMaxDescent(maxDescent);
328
329 float dx = 0.5f / texWidth;
330 float dy = 0.5f / texHeight;
331
332 for (unsigned int i = 0; i < nGlyphs; i++)
333 {
334 uint16 __id = readUint16(in, byteSwap);
335 TextureFont::Glyph glyph(__id);
336
337 glyph.width = readUint8(in);
338 glyph.height = readUint8(in);
339 glyph.xoff = readInt8(in);
340 glyph.yoff = readInt8(in);
341 glyph.advance = readInt8(in);
342 readInt8(in);
343 glyph.x = readInt16(in, byteSwap);
344 glyph.y = readInt16(in, byteSwap);
345
346 if (!in)
347 {
348 DPRINTF(0, "Error reading glyph %ud from texture font stream.\n", i);
349 delete font;
350 return NULL;
351 }
352
353 float fWidth = texWidth;
354 float fHeight = texHeight;
355 glyph.texCoords[0].u = glyph.x / fWidth + dx;
356 glyph.texCoords[0].v = glyph.y / fHeight + dy;
357 glyph.texCoords[1].u = (glyph.x + glyph.width) / fWidth + dx;
358 glyph.texCoords[1].v = glyph.y / fHeight + dy;
359 glyph.texCoords[2].u = (glyph.x + glyph.width) / fWidth + dx;
360 glyph.texCoords[2].v = (glyph.y + glyph.height) / fHeight + dy;
361 glyph.texCoords[3].u = glyph.x / fWidth + dx;
362 glyph.texCoords[3].v = (glyph.y + glyph.height) / fHeight + dy;
363
364 font->addGlyph(glyph);
365 }
366
367 font->texWidth = texWidth;
368 font->texHeight = texHeight;
369 if (format == TxfByte)
370 {
371 unsigned char* fontImage = new unsigned char[texWidth * texHeight];
372 if (fontImage == NULL)
373 {
374 DPRINTF(0, "Not enough memory for font bitmap.\n");
375 delete font;
376 return NULL;
377 }
378
379 DPRINTF(1, "Reading %d x %d 8-bit font image.\n", texWidth, texHeight);
380
381 in.read(reinterpret_cast<char*>(fontImage), texWidth * texHeight);
382 if (in.gcount() != (signed)(texWidth * texHeight))
383 {
384 DPRINTF(0, "Missing bitmap data in font stream.\n");
385 delete font;
386 delete[] fontImage;
387 return NULL;
388 }
389
390 font->fontImage = fontImage;
391 }
392 else
393 {
394 int rowBytes = (texWidth + 7) >> 3;
395 unsigned char* fontBits = new unsigned char[rowBytes * texHeight];
396 unsigned char* fontImage = new unsigned char[texWidth * texHeight];
397 if (fontImage == NULL || fontBits == NULL)
398 {
399 DPRINTF(0, "Not enough memory for font bitmap.\n");
400 delete font;
401 if (fontBits != NULL)
402 delete[] fontBits;
403 if (fontImage != NULL)
404 delete[] fontImage;
405 return NULL;
406 }
407
408 DPRINTF(1, "Reading %d x %d 1-bit font image.\n", texWidth, texHeight);
409
410 in.read(reinterpret_cast<char*>(fontBits), rowBytes * texHeight);
411 if (in.gcount() != (signed)(rowBytes * texHeight))
412 {
413 DPRINTF(0, "Missing bitmap data in font stream.\n");
414 delete font;
415 return NULL;
416 }
417
418 for (unsigned int y = 0; y < texHeight; y++)
419 {
420 for (unsigned int x = 0; x < texWidth; x++)
421 {
422 if ((fontBits[y * rowBytes + (x >> 3)] & (1 << (x & 0x7))) != 0)
423 fontImage[y * texWidth + x] = 0xff;
424 else
425 fontImage[y * texWidth + x] = 0x0;
426 }
427 }
428
429 font->fontImage = fontImage;
430 delete[] fontBits;
431 }
432
433 font->rebuildGlyphLookupTable();
434
435 return font;
436 }
437
438
LoadTextureFont(const string & filename)439 TextureFont* LoadTextureFont(const string& filename)
440 {
441 string localeFilename = LocaleFilename(filename);
442 ifstream in(localeFilename.c_str(), ios::in | ios::binary);
443 if (!in.good())
444 {
445 DPRINTF(0, "Could not open font file %s\n", filename.c_str());
446 return NULL;
447 }
448
449 return TextureFont::load(in);
450 }
451