1 /* sdltext.c: Font-rendering functions for SDL.
2  *
3  * Copyright (C) 2001-2006 by Brian Raiter, under the GNU General Public
4  * License. No warranty. See COPYING for details.
5  */
6 
7 #include	<stdio.h>
8 #include	<stdlib.h>
9 #include	<string.h>
10 #include	<ctype.h>
11 #include	"SDL.h"
12 #include	"sdlgen.h"
13 #include	"../err.h"
14 
15 /* Accept a bitmap as an 8-bit SDL surface and from it extract the
16  * glyphs of a font. (See the documentation included in the Tile World
17  * distribution for specifics regarding the bitmap layout.)
18  */
makefontfromsurface(fontinfo * pf,SDL_Surface * surface)19 static int makefontfromsurface(fontinfo *pf, SDL_Surface *surface)
20 {
21     char		brk[267];
22     unsigned char      *p;
23     unsigned char      *dest;
24     Uint8		foregnd, bkgnd;
25     int			pitch, wsum;
26     int			count, ch;
27     int			x, y, x0, y0, w;
28 
29     if (surface->format->BytesPerPixel != 1)
30 	return FALSE;
31 
32     if (SDL_MUSTLOCK(surface))
33 	SDL_LockSurface(surface);
34 
35     pitch = surface->pitch;
36     p = surface->pixels;
37     foregnd = p[0];
38     bkgnd = p[pitch];
39     for (y = 1, p += pitch ; y < surface->h && *p == bkgnd ; ++y, p += pitch) ;
40     pf->h = y - 1;
41 
42     wsum = 0;
43     ch = 32;
44     memset(pf->w, 0, sizeof pf->w);
45     memset(brk, 0, sizeof brk);
46     for (y = 0 ; y + pf->h < surface->h && ch < 256 ; y += pf->h + 1) {
47 	p = surface->pixels;
48 	p += y * pitch;
49 	x0 = 1;
50 	for (x = 1 ; x < surface->w ; ++x) {
51 	    if (p[x] == bkgnd)
52 		continue;
53 	    w = x - x0;
54 	    x0 = x + 1;
55 	    pf->w[ch] = w;
56 	    wsum += w;
57 	    ++ch;
58 	    if (ch == 127)
59 		ch = 144;
60 	    else if (ch == 154)
61 		ch = 160;
62 	    else if (ch == 256)
63 		break;
64 	}
65 	brk[ch] = 1;
66     }
67 
68     count = ch;
69     if (!(pf->memory = calloc(wsum, pf->h)))
70 	memerrexit();
71 
72     x0 = 1;
73     y0 = 1;
74     dest = pf->memory;
75     for (ch = 0 ; ch < 256 ; ++ch) {
76 	pf->bits[ch] = dest;
77 	if (pf->w[ch] == 0)
78 	    continue;
79 	if (brk[ch]) {
80 	    x0 = 1;
81 	    y0 += pf->h + 1;
82 	}
83 	p = surface->pixels;
84 	p += y0 * pitch + x0;
85 	for (y = 0 ; y < pf->h ; ++y, p += pitch)
86 	    for (x = 0 ; x < pf->w[ch] ; ++x, ++dest)
87 		*dest = p[x] == bkgnd ? 0 : p[x] == foregnd ? 2 : 1;
88 	x0 += pf->w[ch] + 1;
89     }
90 
91     if (SDL_MUSTLOCK(surface))
92 	SDL_UnlockSurface(surface);
93 
94     return TRUE;
95 }
96 
97 /* Given a text and a maximum horizontal space to occupy, return
98  * the amount of vertial space needed to render the entire text with
99  * word-wrapping.
100  */
measuremltext(unsigned char const * text,int len,int maxwidth)101 static int measuremltext(unsigned char const *text, int len, int maxwidth)
102 {
103     int	brk, w, h, n;
104 
105     if (len < 0)
106 	len = strlen((char const*)text);
107     h = 0;
108     brk = 0;
109     for (n = 0, w = 0 ; n < len ; ++n) {
110 	w += sdlg.font.w[text[n]];
111 	if (isspace(text[n])) {
112 	    brk = w;
113 	} else if (w > maxwidth) {
114 	    h += sdlg.font.h;
115 	    if (brk) {
116 		w -= brk;
117 		brk = 0;
118 	    } else {
119 		w = sdlg.font.w[text[n]];
120 		brk = 0;
121 	    }
122 	}
123     }
124     if (w)
125 	h += sdlg.font.h;
126     return h;
127 }
128 
129 /*
130  * Render a single line of pixels of the given text to a locked
131  * surface at scanline. w specifies the total number of pixels to
132  * render. (Any pixels remaining after the last glyph has been
133  * rendered are set to the background color.) y specifies the vertical
134  * coordinate of the line to render relative to the font glyphs. A
135  * separate function is supplied for each possible surface depth.
136  */
137 
drawtextscanline8(Uint8 * scanline,int w,int y,Uint32 * clr,unsigned char const * text,int len)138 static void *drawtextscanline8(Uint8 *scanline, int w, int y, Uint32 *clr,
139 			       unsigned char const *text, int len)
140 {
141     unsigned char const	       *glyph;
142     int				n, x;
143 
144     for (n = 0 ; n < len ; ++n) {
145 	glyph = sdlg.font.bits[text[n]];
146 	glyph += y * sdlg.font.w[text[n]];
147 	for (x = 0 ; w && x < sdlg.font.w[text[n]] ; ++x, --w)
148 	    scanline[x] = (Uint8)clr[glyph[x]];
149 	scanline += x;
150     }
151     while (w--)
152 	*scanline++ = (Uint8)clr[0];
153     return scanline;
154 }
155 
drawtextscanline16(Uint16 * scanline,int w,int y,Uint32 * clr,unsigned char const * text,int len)156 static void *drawtextscanline16(Uint16 *scanline, int w, int y, Uint32 *clr,
157 				unsigned char const *text, int len)
158 {
159     unsigned char const	       *glyph;
160     int				n, x;
161 
162     for (n = 0 ; n < len ; ++n) {
163 	glyph = sdlg.font.bits[text[n]];
164 	glyph += y * sdlg.font.w[text[n]];
165 	for (x = 0 ; w && x < sdlg.font.w[text[n]] ; ++x, --w)
166 	    scanline[x] = (Uint16)clr[glyph[x]];
167 	scanline += x;
168     }
169     while (w--)
170 	*scanline++ = (Uint16)clr[0];
171     return scanline;
172 }
173 
drawtextscanline24(Uint8 * scanline,int w,int y,Uint32 * clr,unsigned char const * text,int len)174 static void *drawtextscanline24(Uint8 *scanline, int w, int y, Uint32 *clr,
175 				unsigned char const *text, int len)
176 {
177     unsigned char const	       *glyph;
178     Uint32			c;
179     int				n, x;
180 
181     for (n = 0 ; n < len ; ++n) {
182 	glyph = sdlg.font.bits[text[n]];
183 	glyph += y * sdlg.font.w[text[n]];
184 	for (x = 0 ; w && x < sdlg.font.w[text[n]] ; ++x, --w) {
185 	    c = clr[glyph[x]];
186 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
187 	    *scanline++ = (Uint8)(c >> 16);
188 	    *scanline++ = (Uint8)(c >> 8);
189 	    *scanline++ = (Uint8)c;
190 #else
191 	    *scanline++ = (Uint8)c;
192 	    *scanline++ = (Uint8)(c >> 8);
193 	    *scanline++ = (Uint8)(c >> 16);
194 #endif
195 	}
196     }
197     c = clr[0];
198     while (w--) {
199 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
200 	*scanline++ = (Uint8)(c >> 16);
201 	*scanline++ = (Uint8)(c >> 8);
202 	*scanline++ = (Uint8)c;
203 #else
204 	*scanline++ = (Uint8)c;
205 	*scanline++ = (Uint8)(c >> 8);
206 	*scanline++ = (Uint8)(c >> 16);
207 #endif
208     }
209     return scanline;
210 }
211 
drawtextscanline32(Uint32 * scanline,int w,int y,Uint32 * clr,unsigned char const * text,int len)212 static void *drawtextscanline32(Uint32 *scanline, int w, int y, Uint32 *clr,
213 				unsigned char const *text, int len)
214 {
215     unsigned char const	       *glyph;
216     int				n, x;
217 
218     for (n = 0 ; n < len ; ++n) {
219 	glyph = sdlg.font.bits[text[n]];
220 	glyph += y * sdlg.font.w[text[n]];
221 	for (x = 0 ; w && x < sdlg.font.w[text[n]] ; ++x, --w)
222 	    scanline[x] = clr[glyph[x]];
223 	scanline += x;
224     }
225     while (w--)
226 	*scanline++ = clr[0];
227     return scanline;
228 }
229 
230 /*
231  * The main font-rendering functions.
232  */
233 
234 /* Draw a single line of text to the screen at the position given by
235  * rect. The bitflags in the final argument control the placement of
236  * text within rect and what colors to use.
237  */
drawtext(SDL_Rect * rect,unsigned char const * text,int len,int flags)238 static void drawtext(SDL_Rect *rect, unsigned char const *text,
239 		     int len, int flags)
240 {
241     Uint32     *clr;
242     void       *p;
243     void       *q;
244     int		l, r;
245     int		pitch, bpp, n, w, y;
246 
247     if (len < 0)
248 	len = text ? strlen((char const*)text) : 0;
249 
250     w = 0;
251     for (n = 0 ; n < len ; ++n)
252 	w += sdlg.font.w[text[n]];
253     if (flags & PT_CALCSIZE) {
254 	rect->h = sdlg.font.h;
255 	rect->w = w;
256 	return;
257     }
258     if (w >= rect->w) {
259 	w = rect->w;
260 	l = r = 0;
261     } else if (flags & PT_RIGHT) {
262 	l = rect->w - w;
263 	r = 0;
264     } else if (flags & PT_CENTER) {
265 	l = (rect->w - w) / 2;
266 	r = (rect->w - w) - l;
267     } else {
268 	l = 0;
269 	r = rect->w - w;
270     }
271 
272     if (flags & PT_DIM)
273 	clr = sdlg.dimtextclr.c;
274     else if (flags & PT_HILIGHT)
275 	clr = sdlg.hilightclr.c;
276     else
277 	clr = sdlg.textclr.c;
278 
279     pitch = sdlg.screen->pitch;
280     bpp = sdlg.screen->format->BytesPerPixel;
281     p = (unsigned char*)sdlg.screen->pixels + rect->y * pitch + rect->x * bpp;
282     for (y = 0 ; y < sdlg.font.h && y < rect->h ; ++y) {
283 	switch (bpp) {
284 	  case 1:
285 	    q = drawtextscanline8(p, l, y, clr, NULL, 0);
286 	    q = drawtextscanline8(q, w, y, clr, text, len);
287 	    q = drawtextscanline8(q, r, y, clr, NULL, 0);
288 	    break;
289 	  case 2:
290 	    q = drawtextscanline16(p, l, y, clr, NULL, 0);
291 	    q = drawtextscanline16(q, w, y, clr, text, len);
292 	    q = drawtextscanline16(q, r, y, clr, NULL, 0);
293 	    break;
294 	  case 3:
295 	    q = drawtextscanline24(p, l, y, clr, NULL, 0);
296 	    q = drawtextscanline24(q, w, y, clr, text, len);
297 	    q = drawtextscanline24(q, r, y, clr, NULL, 0);
298 	    break;
299 	  case 4:
300 	    q = drawtextscanline32(p, l, y, clr, NULL, 0);
301 	    q = drawtextscanline32(q, w, y, clr, text, len);
302 	    q = drawtextscanline32(q, r, y, clr, NULL, 0);
303 	    break;
304 	}
305 	p = (unsigned char*)p + pitch;
306     }
307 
308     if (flags & PT_UPDATERECT) {
309 	rect->y += y;
310 	rect->h -= y;
311     }
312 }
313 
314 /* Draw one or more lines of text to the screen at the position given by
315  * rect. The text is broken up on whitespace whenever possible.
316  */
drawmultilinetext(SDL_Rect * rect,unsigned char const * text,int len,int flags)317 static void drawmultilinetext(SDL_Rect *rect, unsigned char const *text,
318 			      int len, int flags)
319 {
320     SDL_Rect	area;
321     int		index, brkw, brkn;
322     int		w, n;
323 
324     if (flags & PT_CALCSIZE) {
325 	rect->h = measuremltext(text, len, rect->w);
326 	return;
327     }
328 
329     if (len < 0)
330 	len = strlen((char const*)text);
331 
332     area = *rect;
333     brkw = brkn = 0;
334     index = 0;
335     for (n = 0, w = 0 ; n < len ; ++n) {
336 	w += sdlg.font.w[text[n]];
337 	if (isspace(text[n])) {
338 	    brkn = n;
339 	    brkw = w;
340 	} else if (w > rect->w) {
341 	    if (brkw) {
342 		drawtext(&area, text + index, brkn - index,
343 				 flags | PT_UPDATERECT);
344 		index = brkn + 1;
345 		w -= brkw;
346 	    } else {
347 		drawtext(&area, text + index, n - index,
348 				 flags | PT_UPDATERECT);
349 		index = n;
350 		w = sdlg.font.w[text[n]];
351 	    }
352 	    brkw = 0;
353 	}
354     }
355     if (w)
356 	drawtext(&area, text + index, len - index, flags | PT_UPDATERECT);
357     if (flags & PT_UPDATERECT) {
358 	*rect = area;
359     } else {
360 	while (area.h)
361 	    drawtext(&area, NULL, 0, PT_UPDATERECT);
362     }
363 }
364 
365 /*
366  * The exported functions.
367  */
368 
369 /* Render a string of text.
370  */
_puttext(SDL_Rect * rect,char const * text,int len,int flags)371 static void _puttext(SDL_Rect *rect, char const *text, int len, int flags)
372 {
373     if (!sdlg.font.h)
374 	die("no font available! (how did I get this far?)");
375 
376     if (len < 0)
377 	len = text ? strlen(text) : 0;
378 
379     if (SDL_MUSTLOCK(sdlg.screen))
380 	SDL_LockSurface(sdlg.screen);
381 
382     if (flags & PT_MULTILINE)
383 	drawmultilinetext(rect, (unsigned char const*)text, len, flags);
384     else
385 	drawtext(rect, (unsigned char const*)text, len, flags);
386 
387     if (SDL_MUSTLOCK(sdlg.screen))
388 	SDL_UnlockSurface(sdlg.screen);
389 }
390 
391 /* Lay out the columns of the given table so that the entire table
392  * fits within area (horizontally; no attempt is made to make it fit
393  * vertically). Return an array of rectangles, one per column. This
394  * function is essentially the same algorithm used within printtable()
395  * in tworld.c
396  */
_measuretable(SDL_Rect const * area,tablespec const * table)397 static SDL_Rect *_measuretable(SDL_Rect const *area, tablespec const *table)
398 {
399     SDL_Rect		       *colsizes;
400     unsigned char const	       *p;
401     int				sep, mlindex, mlwidth, diff;
402     int				i, j, n, i0, c, w, x;
403 
404     if (!(colsizes = malloc(table->cols * sizeof *colsizes)))
405 	memerrexit();
406     for (i = 0 ; i < table->cols ; ++i) {
407 	colsizes[i].x = 0;
408 	colsizes[i].y = area->y;
409 	colsizes[i].w = 0;
410 	colsizes[i].h = area->h;
411     }
412 
413     mlindex = -1;
414     mlwidth = 0;
415     n = 0;
416     for (j = 0 ; j < table->rows ; ++j) {
417 	for (i = 0 ; i < table->cols ; ++n) {
418 	    c = table->items[n][0] - '0';
419 	    if (c == 1) {
420 		w = 0;
421 		p = (unsigned char const*)table->items[n];
422 		for (p += 2 ; *p ; ++p)
423 		    w += sdlg.font.w[*p];
424 		if (table->items[n][1] == '!') {
425 		    if (w > mlwidth || mlindex != i)
426 			mlwidth = w;
427 		    mlindex = i;
428 		} else {
429 		    if (w > colsizes[i].w)
430 			colsizes[i].w = w;
431 		}
432 	    }
433 	    i += c;
434 	}
435     }
436 
437     sep = sdlg.font.w[' '] * table->sep;
438     w = -sep;
439     for (i = 0 ; i < table->cols ; ++i)
440 	w += colsizes[i].w + sep;
441     diff = area->w - w;
442     if (diff < 0 && table->collapse >= 0) {
443 	w = -diff;
444 	if (colsizes[table->collapse].w < w)
445 	    w = colsizes[table->collapse].w - sdlg.font.w[' '];
446 	colsizes[table->collapse].w -= w;
447 	diff += w;
448     }
449 
450     if (diff > 0) {
451 	n = 0;
452 	for (j = 0 ; j < table->rows && diff > 0 ; ++j) {
453 	    for (i = 0 ; i < table->cols ; ++n) {
454 		c = table->items[n][0] - '0';
455 		if (c > 1 && table->items[n][1] != '!') {
456 		    w = sep;
457 		    p = (unsigned char const*)table->items[n];
458 		    for (p += 2 ; *p ; ++p)
459 			w += sdlg.font.w[*p];
460 		    for (i0 = i ; i0 < i + c ; ++i0)
461 			w -= colsizes[i0].w + sep;
462 		    if (w > 0) {
463 			if (table->collapse >= i && table->collapse < i + c)
464 			    i0 = table->collapse;
465 			else if (mlindex >= i && mlindex < i + c)
466 			    i0 = mlindex;
467 			else
468 			    i0 = i + c - 1;
469 			if (w > diff)
470 			    w = diff;
471 			colsizes[i0].w += w;
472 			diff -= w;
473 			if (diff == 0)
474 			    break;
475 		    }
476 		}
477 		i += c;
478 	    }
479 	}
480     }
481     if (diff > 0 && mlindex >= 0 && colsizes[mlindex].w < mlwidth) {
482 	mlwidth -= colsizes[mlindex].w;
483 	w = mlwidth < diff ? mlwidth : diff;
484 	colsizes[mlindex].w += w;
485 	diff -= w;
486     }
487 
488     x = 0;
489     for (i = 0 ; i < table->cols && x < area->w ; ++i) {
490 	colsizes[i].x = area->x + x;
491 	x += colsizes[i].w + sep;
492 	if (x >= area->w)
493 	    colsizes[i].w = area->x + area->w - colsizes[i].x;
494     }
495     for ( ; i < table->cols ; ++i) {
496 	colsizes[i].x = area->x + area->w;
497 	colsizes[i].w = 0;
498     }
499 
500     return colsizes;
501 }
502 
503 /* Render a single row of a table to the screen, using cols to locate
504  * the entries in the individual columns.
505  */
_drawtablerow(tablespec const * table,SDL_Rect * cols,int * row,int flags)506 static int _drawtablerow(tablespec const *table, SDL_Rect *cols,
507 			 int *row, int flags)
508 {
509     SDL_Rect			rect;
510     unsigned char const	       *p;
511     int				c, f, n, i, y;
512 
513     if (!cols) {
514 	for (i = 0 ; i < table->cols ; i += table->items[(*row)++][0] - '0') ;
515 	return TRUE;
516     }
517 
518     if (SDL_MUSTLOCK(sdlg.screen))
519 	SDL_LockSurface(sdlg.screen);
520 
521     y = cols[0].y;
522     n = *row;
523     for (i = 0 ; i < table->cols ; ++n) {
524 	p = (unsigned char const*)table->items[n];
525 	c = p[0] - '0';
526 	rect = cols[i];
527 	i += c;
528 	if (c > 1)
529 	    rect.w = cols[i - 1].x + cols[i - 1].w - rect.x;
530 	f = flags | PT_UPDATERECT;
531 	if (p[1] == '+')
532 	    f |= PT_RIGHT;
533 	else if (p[1] == '.')
534 	    f |= PT_CENTER;
535 	if (p[1] == '!')
536 	    drawmultilinetext(&rect, p + 2, -1, f);
537 	else
538 	    drawtext(&rect, p + 2, -1, f);
539 	if (rect.y > y)
540 	    y = rect.y;
541     }
542 
543     if (SDL_MUSTLOCK(sdlg.screen))
544 	SDL_UnlockSurface(sdlg.screen);
545 
546     *row = n;
547     for (i = 0 ; i < table->cols ; ++i) {
548 	cols[i].h -= y - cols[i].y;
549 	cols[i].y = y;
550     }
551 
552     return TRUE;
553 }
554 
555 /* Free the resources associated with a font.
556  */
freefont(void)557 void freefont(void)
558 {
559     if (sdlg.font.h) {
560 	free(sdlg.font.memory);
561 	sdlg.font.memory = NULL;
562 	sdlg.font.h = 0;
563     }
564 }
565 
566 /* Load the font contained in the given bitmap file. Error messages
567  * will be displayed if complain is TRUE. The return value is TRUE if
568  * the font was successfully retrieved.
569  */
loadfontfromfile(char const * filename,int complain)570 int loadfontfromfile(char const *filename, int complain)
571 {
572     SDL_Surface	       *bmp;
573     fontinfo		font;
574 
575     bmp = SDL_LoadBMP(filename);
576     if (!bmp) {
577 	if (complain)
578 	    errmsg(filename, "can't load font bitmap: %s", SDL_GetError());
579 	return FALSE;
580     }
581     if (!makefontfromsurface(&font, bmp)) {
582 	if (complain)
583 	    errmsg(filename, "invalid font file");
584 	return FALSE;
585     }
586     SDL_FreeSurface(bmp);
587     freefont();
588     sdlg.font = font;
589     return TRUE;
590 }
591 
592 /* Initialize the module.
593  */
_sdltextinitialize(void)594 int _sdltextinitialize(void)
595 {
596     sdlg.font.h = 0;
597     sdlg.puttextfunc = _puttext;
598     sdlg.measuretablefunc = _measuretable;
599     sdlg.drawtablerowfunc = _drawtablerow;
600     return TRUE;
601 }
602