1 /*
2  * Copyright (c) 2001-2012 Hypertriton, Inc. <http://hypertriton.com/>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23  * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 /*
27  * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002  Sam Lantinga
28  * All rights reserved.
29  *
30  * Redistribution and use in source and binary forms, with or without
31  * modification, are permitted provided that the following conditions
32  * are met:
33  * 1. Redistributions of source code must retain the above copyright
34  *    notice, this list of conditions and the following disclaimer.
35  * 2. Redistributions in binary form must reproduce the above copyright
36  *    notice, this list of conditions and the following disclaimer in the
37  *    documentation and/or other materials provided with the distribution.
38  * 3. Neither the name of the author, nor the names of its contributors
39  *    may be used to endorse or promote products derived from this
40  *    software without specific prior written permission.
41  *
42  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
43  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
45  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
46  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
47  * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
48  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
49  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
51  * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52  */
53 
54 /*
55  * Rendering/sizing of UCS-4 or UTF-8 text using FreeType (if available) or
56  * an internal bitmap font engine.
57  *
58  * TextSizeFT() and TextRenderFT_Blended() are based on code from SDL_ttf
59  * (http://libsdl.org/projects/SDL_ttf/), placed under BSD license with
60  * kind permission from Sam Lantinga.
61  */
62 
63 #include <agar/config/have_freetype.h>
64 #include <agar/config/have_fontconfig.h>
65 #include <agar/config/ttfdir.h>
66 
67 #include <agar/core/core.h>
68 #include <agar/core/config.h>
69 #include <agar/core/win32.h>
70 
71 #ifdef HAVE_FREETYPE
72 # include <agar/gui/ttf.h>
73 #endif
74 #ifdef HAVE_FONTCONFIG
75 # include <fontconfig/fontconfig.h>
76 #endif
77 
78 #include <agar/gui/window.h>
79 #include <agar/gui/vbox.h>
80 #include <agar/gui/box.h>
81 #include <agar/gui/label.h>
82 #include <agar/gui/textbox.h>
83 #include <agar/gui/button.h>
84 #include <agar/gui/ucombo.h>
85 #include <agar/gui/numerical.h>
86 #include <agar/gui/keymap.h>
87 #include <agar/gui/checkbox.h>
88 #include <agar/gui/load_xcf.h>
89 #include <agar/gui/iconmgr.h>
90 #include <agar/gui/icons.h>
91 #include <agar/gui/fonts.h>
92 #include <agar/gui/fonts_data.h>
93 #include <agar/gui/packedpixel.h>
94 
95 #include <string.h>
96 #include <stdarg.h>
97 #include <ctype.h>
98 
99 /* Default fonts */
100 const char *agDefaultFaceFT = "_agFontVera";
101 const char *agDefaultFaceBitmap = "_agFontMinimal";
102 
103 /* Statically compiled fonts */
104 AG_StaticFont *agBuiltinFonts[] = {
105 	&agFontVera,
106 	&agFontMinimal
107 };
108 const int agBuiltinFontCount = sizeof(agBuiltinFonts)/sizeof(agBuiltinFonts[0]);
109 
110 int agTextFontHeight = 0;		/* Default font height (px) */
111 int agTextFontAscent = 0;		/* Default font ascent (px) */
112 int agTextFontDescent = 0;		/* Default font descent (px) */
113 int agTextFontLineSkip = 0;		/* Default font line skip (px) */
114 int agGlyphGC = 0;			/* Enable glyph garbage collector */
115 int agFreetypeInited = 0;		/* Initialized Freetype library */
116 int agFontconfigInited = 0;		/* Initialized Fontconfig library */
117 int agRTL = 0;				/* Right-to-left mode */
118 
119 static int agTextInitedSubsystem = 0;
120 static AG_TextState states[AG_TEXT_STATES_MAX];
121 static Uint curState = 0;
122 AG_TextState *agTextState;
123 
124 /* #define SYMBOLS */		/* Escape $(x) type symbols */
125 
126 static const char *agTextMsgTitles[] = {
127 	N_("Error"),
128 	N_("Warning"),
129 	N_("Information")
130 };
131 
132 AG_Mutex agTextLock;
133 static TAILQ_HEAD(ag_fontq, ag_font) fonts;
134 AG_Font *agDefaultFont = NULL;
135 
136 /* Load an individual glyph from a bitmap font file. */
137 static void
LoadBitmapGlyph(AG_Surface * su,const char * lbl,void * p)138 LoadBitmapGlyph(AG_Surface *su, const char *lbl, void *p)
139 {
140 	AG_Font *font = p;
141 
142 	if (font->nglyphs == 0) {
143 		Strlcpy(font->bspec, lbl, sizeof(font->bspec));
144 	}
145 	font->bglyphs = Realloc(font->bglyphs,
146 	                        (font->nglyphs+1)*sizeof(AG_Surface *));
147 	font->bglyphs[font->nglyphs++] = su;
148 }
149 
150 static void
FontInit(void * obj)151 FontInit(void *obj)
152 {
153 	AG_Font *font = obj;
154 
155 	font->spec.type = AG_FONT_VECTOR;
156 	font->spec.sourceType = AG_FONT_SOURCE_FILE;
157 	font->spec.source.file[0] = '\0';
158 	font->spec.index = 0;
159 	font->spec.size = 0.0;
160 	font->spec.matrix.xx = 1.0;
161 	font->spec.matrix.yy = 1.0;
162 	font->spec.matrix.xy = 0.0;
163 	font->spec.matrix.yx = 0.0;
164 
165 	font->flags = 0;
166 	font->c0 = 0;
167 	font->c1 = 0;
168 	font->height = 0;
169 	font->ascent = 0;
170 	font->descent = 0;
171 	font->lineskip = 0;
172 	font->ttf = NULL;
173 	font->nRefs = 0;
174 }
175 
176 static void
FontDestroy(void * obj)177 FontDestroy(void *obj)
178 {
179 	AG_Font *font = obj;
180 	int i;
181 
182 	switch (font->spec.type) {
183 #ifdef HAVE_FREETYPE
184 	case AG_FONT_VECTOR:
185 		AG_TTFCloseFont(font);
186 		break;
187 #endif
188 	case AG_FONT_BITMAP:
189 		for (i = 0; i < font->nglyphs; i++) {
190 			AG_SurfaceFree(font->bglyphs[i]);
191 		}
192 		Free(font->bglyphs);
193 		break;
194 	}
195 }
196 
197 static int
GetFontTypeFromSignature(const char * path,enum ag_font_type * pType)198 GetFontTypeFromSignature(const char *path, enum ag_font_type *pType)
199 {
200 	char buf[13];
201 	FILE *f;
202 
203 	if ((f = fopen(path, "rb")) == NULL) {
204 		AG_SetError(_("Unable to open %s"), path);
205 		return (-1);
206 	}
207 	if (fread(buf, 13, 1, f) == 13) {
208 		if (strncmp(buf, "gimp xcf file", 13) == 0) {
209 			*pType = AG_FONT_BITMAP;
210 		} else {
211 			*pType = AG_FONT_VECTOR;
212 		}
213 	} else {
214 		*pType = AG_FONT_VECTOR;
215 	}
216 	fclose(f);
217 	return (0);
218 }
219 
220 /* Load a bitmap font. */
221 static int
OpenBitmapFont(AG_Font * font)222 OpenBitmapFont(AG_Font *font)
223 {
224 	char *s;
225 	char *msig, *c0, *c1;
226 	AG_DataSource *ds;
227 
228 	switch (font->spec.sourceType) {
229 	case AG_FONT_SOURCE_FILE:
230 		ds = AG_OpenFile(font->spec.source.file, "rb");
231 		break;
232 	case AG_FONT_SOURCE_MEMORY:
233 		ds = AG_OpenConstCore(font->spec.source.mem.data,
234 		                      font->spec.source.mem.size);
235 		break;
236 	default:
237 		ds = NULL;
238 		break;
239 	}
240 	if (ds == NULL)
241 		return (-1);
242 
243 	/* Allocate the glyph array. */
244 	if ((font->bglyphs = TryMalloc(sizeof(AG_Surface *))) == NULL) {
245 		AG_CloseDataSource(ds);
246 		return (-1);
247 	}
248 	font->nglyphs = 0;
249 
250 	/* Load the glyphs from the XCF layers. */
251 	if (AG_XCFLoad(ds, 0, LoadBitmapGlyph, font) == -1) {
252 		AG_CloseDataSource(ds);
253 		return (-1);
254 	}
255 	AG_CloseDataSource(ds);
256 
257 	/* Get the range of characters from the "MAP:x-y" string. */
258 	s = font->bspec;
259 	msig = AG_Strsep(&s, ":");
260 	c0 = AG_Strsep(&s, "-");
261 	c1 = AG_Strsep(&s, "-");
262 	if (font->nglyphs < 1 ||
263 	    msig == NULL || strcmp(msig, "MAP") != 0 ||
264 	    c0 == NULL || c1 == NULL ||
265 	    c0[0] == '\0' || c1[0] == '\0') {
266 		AG_SetError("XCF is missing the \"MAP:x-y\" string");
267 		return (-1);
268 	}
269 	font->c0 = (Uint32)strtol(c0, NULL, 10);
270 	font->c1 = (Uint32)strtol(c1, NULL, 10);
271 	if (font->nglyphs < (font->c1 - font->c0)) {
272 		AG_SetError("XCF has inconsistent bitmap fontspec");
273 		return (-1);
274 	}
275 	font->height = font->bglyphs[0]->h;
276 	font->ascent = font->height;
277 	font->descent = 0;
278 	font->lineskip = font->height+2;
279 	return (0);
280 }
281 
282 /*
283  * Load the given font (or return a pointer to an existing one), with the
284  * given specifications.
285  */
286 AG_Font *
AG_FetchFont(const char * pname,int psize,int pflags)287 AG_FetchFont(const char *pname, int psize, int pflags)
288 {
289 	AG_Config *cfg = AG_ConfigObject();
290 	char name[AG_OBJECT_NAME_MAX];
291 	int ptsize = (psize >= 0) ? psize : AG_GetInt(cfg,"font.size");
292 	Uint flags = (pflags >= 0) ? pflags : AG_GetUint(cfg,"font.flags");
293 	AG_StaticFont *builtin = NULL;
294 	AG_Font *font;
295 	AG_FontSpec *spec;
296 	int i;
297 
298 	if (pname != NULL) {
299 		Strlcpy(name, pname, sizeof(name));
300 	} else {
301 		AG_GetString(cfg, "font.face", name, sizeof(name));
302 	}
303 
304 	AG_MutexLock(&agTextLock);
305 
306 	TAILQ_FOREACH(font, &fonts, fonts) {
307 		if (font->spec.size == (double)ptsize &&
308 		    font->flags == flags &&
309 		    strcmp(OBJECT(font)->name, name) == 0)
310 			break;
311 	}
312 	if (font != NULL)
313 		goto out;
314 
315 	if ((font = TryMalloc(sizeof(AG_Font))) == NULL) {
316 		AG_MutexUnlock(&agTextLock);
317 		return (NULL);
318 	}
319 	AG_ObjectInit(font, &agFontClass);
320 	AG_ObjectSetNameS(font, name);
321 	spec = &font->spec;
322 	spec->size = (double)ptsize;
323 	font->flags = flags;
324 
325 	if (name[0] == '_') {
326 		for (i = 0; i < agBuiltinFontCount; i++) {
327 			if (strcmp(agBuiltinFonts[i]->name, &name[1]) == 0)
328 				break;
329 		}
330 		if (i == agBuiltinFontCount) {
331 			AG_SetError(_("No such builtin font: %s"), name);
332 			goto fail;
333 		}
334 		builtin = agBuiltinFonts[i];
335 		spec->type = builtin->type;
336 		spec->sourceType = AG_FONT_SOURCE_MEMORY;
337 		spec->source.mem.data = builtin->data;
338 		spec->source.mem.size = builtin->size;
339 	} else {
340 #ifdef HAVE_FONTCONFIG
341 		if (agFontconfigInited) {
342 			FcPattern *pattern, *fpat;
343 			FcResult fres = FcResultMatch;
344 			char *nameIn;
345 			FcChar8 *filename;
346 			FcMatrix *mat;
347 
348 			Asprintf(&nameIn, "%s-%u", name, ptsize);
349 			if ((pattern = FcNameParse((FcChar8 *)nameIn)) == NULL ||
350 			    !FcConfigSubstitute(NULL, pattern, FcMatchPattern)) {
351 				AG_SetError(_("Fontconfig failed to parse: %s"), name);
352 				free(nameIn);
353 				goto fail;
354 			}
355 			free(nameIn);
356 
357 			FcDefaultSubstitute(pattern);
358 			if ((fpat = FcFontMatch(NULL, pattern, &fres)) == NULL ||
359 			    fres != FcResultMatch) {
360 				AG_SetError(_("Fontconfig failed to match: %s"), name);
361 				goto fail;
362 			}
363 			if (FcPatternGetString(fpat, FC_FILE, 0,
364 			    &filename) != FcResultMatch) {
365 				AG_SetError("Fontconfig FC_FILE missing");
366 				goto fail;
367 			}
368 			Strlcpy(spec->source.file, (const char *)filename,
369 			    sizeof(spec->source.file));
370 
371 			if (FcPatternGetInteger(fpat, FC_INDEX, 0, &spec->index)
372 			    != FcResultMatch) {
373 				AG_SetError("Fontconfig FC_INDEX missing");
374 				goto fail;
375 			}
376 			if (FcPatternGetDouble(fpat, FC_SIZE, 0, &spec->size)
377 			    != FcResultMatch) {
378 				AG_SetError("Fontconfig FC_SIZE missing");
379 				goto fail;
380 			}
381 			if (FcPatternGetMatrix(fpat, FC_MATRIX, 0, &mat) == FcResultMatch) {
382 				spec->matrix.xx = mat->xx;
383 				spec->matrix.yy = mat->yy;
384 				spec->matrix.xy = mat->xy;
385 				spec->matrix.yx = mat->yx;
386 			}
387 			spec->type = AG_FONT_VECTOR;
388 			FcPatternDestroy(fpat);
389 			FcPatternDestroy(pattern);
390 		} else
391 #endif /* HAVE_FONTCONFIG */
392 		{
393 			if (AG_ConfigFile("font-path", name, NULL,
394 			    spec->source.file, sizeof(spec->source.file)) == -1) {
395 				if (AG_ConfigFile("font-path", name, "ttf",
396 				    spec->source.file, sizeof(spec->source.file)) == -1)
397 					goto fail;
398 			}
399 			if (GetFontTypeFromSignature(spec->source.file,
400 			   &spec->type) == -1)
401 				goto fail;
402 		}
403 		spec->sourceType = AG_FONT_SOURCE_FILE;
404 	}
405 
406 	switch (spec->type) {
407 #ifdef HAVE_FREETYPE
408 	case AG_FONT_VECTOR:
409 		if (!agFreetypeInited) {
410 			AG_SetError("FreeType is not initialized");
411 			goto fail;
412 		}
413 		if (AG_TTFOpenFont(font) == -1) {
414 			goto fail;
415 		}
416 		break;
417 #endif
418 	case AG_FONT_BITMAP:
419 		if (OpenBitmapFont(font) == -1) {
420 			goto fail;
421 		}
422 		break;
423 	default:
424 		AG_SetError("Unsupported font type");
425 		goto fail;
426 	}
427 	TAILQ_INSERT_HEAD(&fonts, font, fonts);
428 out:
429 	font->nRefs++;
430 	AG_MutexUnlock(&agTextLock);
431 	return (font);
432 fail:
433 	AG_MutexUnlock(&agTextLock);
434 	AG_ObjectDestroy(font);
435 	return (NULL);
436 }
437 
438 /* Decrement reference count on a font, delete if it reaches zero. */
439 void
AG_UnusedFont(AG_Font * font)440 AG_UnusedFont(AG_Font *font)
441 {
442 	AG_MutexLock(&agTextLock);
443 	if (font != agDefaultFont) {
444 		if (--font->nRefs == 0) {
445 			TAILQ_REMOVE(&fonts, font, fonts);
446 			AG_ObjectDestroy(font);
447 		}
448 	}
449 	AG_MutexUnlock(&agTextLock);
450 }
451 
452 void
AG_SetDefaultFont(AG_Font * font)453 AG_SetDefaultFont(AG_Font *font)
454 {
455 	AG_Config *cfg;
456 
457 	AG_MutexLock(&agTextLock);
458 	agDefaultFont = font;
459 	agTextFontHeight = font->height;
460 	agTextFontAscent = font->ascent;
461 	agTextFontDescent = font->descent;
462 	agTextFontLineSkip = font->lineskip;
463 	agTextState->font = font;
464 	cfg = AG_ConfigObject();
465 	AG_SetString(cfg, "font.face", OBJECT(font)->name);
466 	AG_SetInt(cfg, "font.size", font->spec.size);
467 	AG_SetInt(cfg, "font.flags", font->flags);
468 	AG_MutexUnlock(&agTextLock);
469 }
470 
471 void
AG_SetRTL(int enable)472 AG_SetRTL(int enable)
473 {
474 	AG_MutexLock(&agTextLock);
475 	agRTL = enable;
476 	AG_MutexUnlock(&agTextLock);
477 }
478 
479 static Uint32
TextTmsgExpire(AG_Timer * to,AG_Event * event)480 TextTmsgExpire(AG_Timer *to, AG_Event *event)
481 {
482 	AG_Window *win = AG_PTR(1);
483 
484 	AG_ObjectDetach(win);
485 	return (0);
486 }
487 
488 static void
InitTextState(void)489 InitTextState(void)
490 {
491 	agTextState->font = agDefaultFont;
492 	agTextState->color = AG_ColorRGB(255,255,255);
493 	agTextState->colorBG = AG_ColorRGBA(0,0,0,0);
494 	agTextState->justify = AG_TEXT_LEFT;
495 	agTextState->valign = AG_TEXT_TOP;
496 	agTextState->tabWd = agTextTabWidth;
497 }
498 
499 /* Initialize the font engine and configure the default font. */
500 int
AG_InitTextSubsystem(void)501 AG_InitTextSubsystem(void)
502 {
503 	AG_Config *cfg = AG_ConfigObject();
504 
505 	if (agTextInitedSubsystem++ > 0)
506 		return (0);
507 
508 	AG_MutexInitRecursive(&agTextLock);
509 	TAILQ_INIT(&fonts);
510 
511 	/* Set the default font search path. */
512 	AG_ObjectLock(cfg);
513 	if (!AG_Defined(cfg,"font-path")) {
514 		char fontPath[AG_PATHNAME_MAX];
515 		AG_User *sysUser = AG_GetRealUser();
516 		size_t len;
517 
518 		fontPath[0] ='\0';
519 #if !defined(_WIN32)
520 		if (strcmp(TTFDIR, "NONE") != 0) {
521 			Strlcat(fontPath, TTFDIR, sizeof(fontPath));
522 			Strlcat(fontPath, AG_PATHSEPMULTI, sizeof(fontPath));
523 		}
524 #endif
525 #if defined(__APPLE__)
526 		if (sysUser != NULL && sysUser->home != NULL) {
527 			Strlcat(fontPath, sysUser->home, sizeof(fontPath));
528 			Strlcat(fontPath, "/Library/Fonts", sizeof(fontPath));
529 			Strlcat(fontPath, AG_PATHSEPMULTI, sizeof(fontPath));
530 		}
531 		Strlcat(fontPath, "/Library/Fonts", sizeof(fontPath));
532 		Strlcat(fontPath, AG_PATHSEPMULTI, sizeof(fontPath));
533 		Strlcat(fontPath, "/System/Library/Fonts", sizeof(fontPath));
534 		Strlcat(fontPath, AG_PATHSEPMULTI, sizeof(fontPath));
535 #elif defined(_WIN32)
536 		{
537 			char windir[AG_PATHNAME_MAX];
538 
539 			if (sysUser != NULL && sysUser->home != NULL) {
540 				Strlcat(fontPath, sysUser->home, sizeof(fontPath));
541 				Strlcat(fontPath, "\\Fonts", sizeof(fontPath));
542 				Strlcat(fontPath, AG_PATHSEPMULTI, sizeof(fontPath));
543 			}
544 			if (GetWindowsDirectoryA(windir, sizeof(windir)) > 0) {
545 				Strlcat(fontPath, windir, sizeof(fontPath));
546 				Strlcat(fontPath, "\\Fonts", sizeof(fontPath));
547 				Strlcat(fontPath, AG_PATHSEPMULTI, sizeof(fontPath));
548 			}
549 		}
550 #else /* !WIN32 & !APPLE */
551 		if (sysUser != NULL && sysUser->home != NULL) {
552 			Strlcat(fontPath, sysUser->home, sizeof(fontPath));
553 			Strlcat(fontPath, AG_PATHSEP, sizeof(fontPath));
554 			Strlcat(fontPath, ".fonts", sizeof(fontPath));
555 			Strlcat(fontPath, AG_PATHSEPMULTI, sizeof(fontPath));
556 		}
557 #endif
558 		if ((len = strlen(fontPath)) > 0) {
559 			if (fontPath[len-1] == ':') {
560 				fontPath[len-1] = '\0';
561 			}
562 			AG_SetString(cfg, "font-path", fontPath);
563 		}
564 
565 		if (sysUser != NULL)
566 			AG_UserFree(sysUser);
567 	}
568 
569 	/* Initialize FreeType if available. */
570 #ifdef HAVE_FREETYPE
571 	if (AG_TTFInit() == 0) {
572 		agFreetypeInited = 1;
573 	} else {
574 		AG_Verbose("Failed to initialize FreeType (%s); falling back "
575 		           "to monospace font engine\n", AG_GetError());
576 	}
577 #endif
578 #ifdef HAVE_FONTCONFIG
579 	if (FcInit()) {
580 		agFontconfigInited = 1;
581 	} else {
582 		AG_Verbose("Failed to initialize fontconfig; ignoring\n");
583 	}
584 #endif
585 	/* Load the default font. */
586 	if (!AG_Defined(cfg,"font.face")) {
587 		AG_SetString(cfg, "font.face",
588 		    agFreetypeInited ? agDefaultFaceFT : agDefaultFaceBitmap);
589 	}
590 	if (!AG_Defined(cfg,"font.size")) {
591 		AG_SetInt(cfg, "font.size", 12);
592 	}
593 	if (!AG_Defined(cfg,"font.flags")) {
594 		AG_SetUint(cfg, "font.flags", 0);
595 	}
596 	AG_ObjectUnlock(cfg);
597 
598 	if ((agDefaultFont = AG_FetchFont(NULL, -1, -1)) == NULL) {
599 		goto fail;
600 	}
601 	agTextFontHeight = agDefaultFont->height;
602 	agTextFontAscent = agDefaultFont->ascent;
603 	agTextFontDescent = agDefaultFont->descent;
604 	agTextFontLineSkip = agDefaultFont->lineskip;
605 
606 	/* Initialize the rendering state. */
607 	curState = 0;
608 	agTextState = &states[0];
609 	InitTextState();
610 	return (0);
611 fail:
612 #ifdef HAVE_FREETYPE
613 	if (agFreetypeInited) {
614 		AG_TTFDestroy();
615 		agFreetypeInited = 0;
616 	}
617 #endif
618 	return (-1);
619 }
620 
621 void
AG_DestroyTextSubsystem(void)622 AG_DestroyTextSubsystem(void)
623 {
624 	AG_Font *font, *fontNext;
625 
626 	if (--agTextInitedSubsystem > 0) {
627 		return;
628 	}
629 	for (font = TAILQ_FIRST(&fonts);
630 	     font != TAILQ_END(&fonts);
631 	     font = fontNext) {
632 		fontNext = TAILQ_NEXT(font, fonts);
633 		AG_ObjectDestroy(font);
634 	}
635 #ifdef HAVE_FREETYPE
636 	if (agFreetypeInited) {
637 		AG_TTFDestroy();
638 		agFreetypeInited = 0;
639 	}
640 #endif
641 #ifdef HAVE_FONTCONFIG
642 	if (agFontconfigInited) {
643 		FcFini();
644 		agFontconfigInited = 0;
645 	}
646 #endif
647 	AG_MutexDestroy(&agTextLock);
648 }
649 
650 /* Initialize the glyph cache. */
651 void
AG_TextInitGlyphCache(AG_Driver * drv)652 AG_TextInitGlyphCache(AG_Driver *drv)
653 {
654 	Uint i;
655 
656 	drv->glyphCache = Malloc(AG_GLYPH_NBUCKETS*sizeof(AG_GlyphCache));
657 	for (i = 0; i < AG_GLYPH_NBUCKETS; i++)
658 		SLIST_INIT(&drv->glyphCache[i].glyphs);
659 }
660 
661 /* Clear the glyph cache. */
662 void
AG_TextClearGlyphCache(AG_Driver * drv)663 AG_TextClearGlyphCache(AG_Driver *drv)
664 {
665 	int i;
666 	AG_Glyph *gl, *ngl;
667 
668 	for (i = 0; i < AG_GLYPH_NBUCKETS; i++) {
669 		for (gl = SLIST_FIRST(&drv->glyphCache[i].glyphs);
670 		     gl != SLIST_END(&drv->glyphCache[i].glyphs);
671 		     gl = ngl) {
672 			ngl = SLIST_NEXT(gl, glyphs);
673 			AG_SurfaceFree(gl->su);
674 			Free(gl);
675 		}
676 		SLIST_INIT(&drv->glyphCache[i].glyphs);
677 	}
678 }
679 
680 void
AG_TextDestroyGlyphCache(AG_Driver * drv)681 AG_TextDestroyGlyphCache(AG_Driver *drv)
682 {
683 	AG_TextClearGlyphCache(drv);
684 	free(drv->glyphCache);
685 	drv->glyphCache = NULL;
686 }
687 
688 /* Render a glyph following a cache miss; called from AG_TextRenderGlyph(). */
689 AG_Glyph *
AG_TextRenderGlyphMiss(AG_Driver * drv,Uint32 ch)690 AG_TextRenderGlyphMiss(AG_Driver *drv, Uint32 ch)
691 {
692 	AG_Glyph *gl;
693 	Uint32 ucs[2];
694 
695 	gl = Malloc(sizeof(AG_Glyph));
696 	gl->font = agTextState->font;
697 	gl->color = agTextState->color;
698 	gl->ch = ch;
699 	ucs[0] = ch;
700 	ucs[1] = '\0';
701 	gl->su = AG_TextRenderUCS4(ucs);
702 
703 	switch (agTextState->font->spec.type) {
704 #ifdef HAVE_FREETYPE
705 	case AG_FONT_VECTOR:
706 		{
707 			AG_TTFGlyph *gt;
708 
709 			if (AG_TTFFindGlyph(agTextState->font->ttf, ch,
710 			    TTF_CACHED_METRICS|TTF_CACHED_BITMAP) == 0) {
711 				gt = ((AG_TTFFont *)agTextState->font->ttf)->current;
712 				gl->advance = gt->advance;
713 			} else {
714 				gl->advance = gl->su->w;
715 			}
716 		}
717 		break;
718 #endif
719 	case AG_FONT_BITMAP:
720 		gl->advance = gl->su->w;
721 		break;
722 	}
723 	AGDRIVER_CLASS(drv)->updateGlyph(drv, gl);
724 	return (gl);
725 }
726 
727 /* Save the current text rendering state. */
728 void
AG_PushTextState(void)729 AG_PushTextState(void)
730 {
731 	AG_MutexLock(&agTextLock);
732 	if ((curState+1) >= AG_TEXT_STATES_MAX) {
733 		AG_FatalError("Text state stack overflow");
734 	}
735 	agTextState = &states[++curState];
736 	InitTextState();
737 	AG_MutexUnlock(&agTextLock);
738 }
739 
740 /* Restore the previous rendering state. */
741 void
AG_PopTextState(void)742 AG_PopTextState(void)
743 {
744 	AG_MutexLock(&agTextLock);
745 	if (curState == 0) {
746 		AG_FatalError("No text state to pop");
747 	}
748 	agTextState = &states[--curState];
749 	AG_MutexUnlock(&agTextLock);
750 }
751 
752 /* Select the font face to use in rendering text. */
753 AG_Font *
AG_TextFontLookup(const char * face,int size,Uint flags)754 AG_TextFontLookup(const char *face, int size, Uint flags)
755 {
756 	AG_Font *newFont;
757 
758 	AG_MutexLock(&agTextLock);
759 	if ((newFont = AG_FetchFont(face, size, flags)) == NULL) {
760 		goto fail;
761 	}
762 	agTextState->font = newFont;
763 	AG_MutexUnlock(&agTextLock);
764 	return (newFont);
765 fail:
766 	AG_MutexUnlock(&agTextLock);
767 	return (NULL);
768 }
769 
770 /* Set font size in points. */
771 AG_Font *
AG_TextFontPts(int pts)772 AG_TextFontPts(int pts)
773 {
774 	AG_Font *font, *newFont;
775 
776 	AG_MutexLock(&agTextLock);
777 	font = agTextState->font;
778 	newFont = AG_FetchFont(OBJECT(font)->name, pts, font->flags);
779 	if (newFont == NULL) {
780 		goto fail;
781 	}
782 	agTextState->font = newFont;
783 	AG_MutexUnlock(&agTextLock);
784 	return (agTextState->font);
785 fail:
786 	AG_MutexUnlock(&agTextLock);
787 	return (NULL);
788 }
789 
790 /* Set font size as % of the current font size. */
791 AG_Font *
AG_TextFontPct(int pct)792 AG_TextFontPct(int pct)
793 {
794 	AG_Font *font, *newFont;
795 
796 	AG_MutexLock(&agTextLock);
797 	font = agTextState->font;
798 	newFont = AG_FetchFont(OBJECT(font)->name, font->spec.size*pct/100,
799 	    font->flags);
800 	if (newFont == NULL) {
801 		goto fail;
802 	}
803 	agTextState->font = newFont;
804 	AG_MutexUnlock(&agTextLock);
805 	return (agTextState->font);
806 fail:
807 	AG_MutexUnlock(&agTextLock);
808 	return (NULL);
809 }
810 
811 /* Varargs variant of TextRender(). */
812 AG_Surface *
AG_TextRenderf(const char * fmt,...)813 AG_TextRenderf(const char *fmt, ...)
814 {
815 	char *s;
816 	va_list args;
817 	AG_Surface *su;
818 
819 	va_start(args, fmt);
820 	Vasprintf(&s, fmt, args);
821 	va_end(args);
822 
823 	su = AG_TextRender(s);
824 	free(s);
825 	return (su);
826 }
827 #ifdef SYMBOLS
828 static __inline__ AG_Surface *
GetSymbolSurface(Uint32 ch)829 GetSymbolSurface(Uint32 ch)
830 {
831 	switch (ch) {
832 	case 'L': return agIconLeftButton.s;
833 	case 'M': return agIconMidButton.s;
834 	case 'R': return agIconRightButton.s;
835 	case 'C': return agIconCtrlKey.s;
836 	default: return (NULL);
837 	}
838 }
839 #endif /* SYMBOLS */
840 
841 static __inline__ void
InitMetrics(AG_TextMetrics * tm)842 InitMetrics(AG_TextMetrics *tm)
843 {
844 	tm->w = 0;
845 	tm->h = 0;
846 	tm->wLines = NULL;
847 	tm->nLines = 0;
848 }
849 
850 static __inline__ void
FreeMetrics(AG_TextMetrics * tm)851 FreeMetrics(AG_TextMetrics *tm)
852 {
853 	Free(tm->wLines);
854 }
855 
856 #ifdef HAVE_FREETYPE
857 /*
858  * Compute the rendered size of UCS-4 text with a FreeType font. If the
859  * string is multiline and nLines is non-NULL, the width of individual lines
860  * is returned into wLines, and the number of lines into nLines.
861  */
862 static void
TextSizeFT(const Uint32 * ucs,AG_TextMetrics * tm,int extended)863 TextSizeFT(const Uint32 *ucs, AG_TextMetrics *tm, int extended)
864 {
865 	AG_Font *font = agTextState->font;
866 	AG_TTFFont *ftFont = font->ttf;
867 	AG_TTFGlyph *glyph;
868 	const Uint32 *ch;
869 	int xMin=0, xMax=0, yMin=0, yMax;
870 	int xMinLine=0, xMaxLine=0;
871 	int x, z;
872 
873 	/* Compute the sum of the bounding box of the characters. */
874 	yMax = font->height;
875 	x = 0;
876 	for (ch = &ucs[0]; *ch != '\0'; ch++) {
877 		if (*ch == '\n') {
878 			if (extended) {
879 				tm->wLines = Realloc(tm->wLines,
880 				    (tm->nLines+2)*sizeof(Uint));
881 				tm->wLines[tm->nLines++] = (xMaxLine-xMinLine);
882 				xMinLine = 0;
883 				xMaxLine = 0;
884 			}
885 			yMax += font->lineskip;
886 			x = 0;
887 			continue;
888 		}
889 		if (*ch == '\t') {
890 			x += agTextState->tabWd;
891 			continue;
892 		}
893 		if (AG_TTFFindGlyph(ftFont, *ch, TTF_CACHED_METRICS) != 0) {
894 			continue;
895 		}
896 		glyph = ftFont->current;
897 
898 		z = x + glyph->minx;
899 		if (xMin > z) { xMin = z; }
900 		if (xMinLine > z) { xMinLine = z; }
901 
902 		if (ftFont->style & AG_TTF_STYLE_BOLD) {
903 			x += ftFont->glyph_overhang;
904 		}
905 		z = x + MAX(glyph->advance,glyph->maxx);
906 		if (xMax < z) { xMax = z; }
907 		if (xMaxLine < z) { xMaxLine = z; }
908 		x += glyph->advance;
909 
910 		if (glyph->miny < yMin) { yMin = glyph->miny; }
911 		if (glyph->maxy > yMax) { yMax = glyph->maxy; }
912 	}
913 	if (*ch != '\n' && extended) {
914 		if (tm->nLines > 0) {
915 			tm->wLines = Realloc(tm->wLines,
916 			    (tm->nLines+2)*sizeof(Uint));
917 			tm->wLines[tm->nLines] = (xMaxLine-xMinLine);
918 		}
919 		tm->nLines++;
920 	}
921 	tm->w = (xMax-xMin);
922 	tm->h = (yMax-yMin);
923 }
924 
925 # ifdef SYMBOLS
926 
927 static int
TextRenderSymbol(Uint ch,AG_Surface * su,int x,int y)928 TextRenderSymbol(Uint ch, AG_Surface *su, int x, int y)
929 {
930 	AG_Surface *sym;
931 	int row;
932 
933 	if ((sym = GetSymbolSurface(ch)) == NULL) {
934 		return (0);
935 	}
936 	for (row = 0; row < sym->h; row++) {
937 		Uint8 *dst = (Uint8 *)su->pixels + (y+row)*su->pitch +
938 		                                   (x+2);
939 		Uint8 *src = (Uint8 *)sym->pixels + row*sym->pitch;
940 		int col;
941 
942 		for (col = 0; col < sym->w; col++) {
943 			if (AG_GET_PIXEL(sym,src) != sym->format->colorkey) {
944 				*dst = 1;
945 			}
946 			src += sym->format->BytesPerPixel;
947 			dst++;
948 		}
949 	}
950 	return (sym->w + 4);
951 }
952 
953 static int
TextRenderSymbol_Blended(Uint ch,AG_Surface * su,int x,int y)954 TextRenderSymbol_Blended(Uint ch, AG_Surface *su, int x, int y)
955 {
956 	AG_Surface *sym;
957 	Uint32 alpha;
958 	int row;
959 
960 	if ((sym = GetSymbolSurface(ch)) == NULL) {
961 		return (0);
962 	}
963 	for (row = 0; row < sym->h; row++) {
964 		Uint8 *dst = (Uint8 *)su->pixels + (y+row)*(su->pitch/4) + (x+2);
965 		Uint8 *src = (Uint8 *)sym->pixels + row*sym->pitch;
966 		int col;
967 
968 		for (col = 0; col < sym->w; col++) {
969 			alpha = *src;
970 			if (AG_GET_PIXEL(sym,src) != sym->format->colorkey) {
971 				dst[0] = 0xff;
972 				dst[1] = 0xff;
973 				dst[2] = 0xff;
974 			}
975 			src += sym->format->BytesPerPixel;
976 			dst += 3;
977 		}
978 	}
979 	return (sym->w + 4);
980 }
981 # endif /* SYMBOLS */
982 
983 /* Underline rendered text. */
984 /* XXX does not handle multiline/alignment properly */
985 static void
TextRenderFT_Blended_Underline(AG_TTFFont * ftFont,AG_Surface * su,int nLines)986 TextRenderFT_Blended_Underline(AG_TTFFont *ftFont, AG_Surface *su, int nLines)
987 {
988 	AG_Color C = agTextState->color;
989 	Uint32 pixel;
990 	Uint8 *pDst;
991 	int x, y, line;
992 
993 	pixel = AG_MapPixelRGBA(su->format, C.r, C.g, C.b, 255);
994 	for (line = 0; line < nLines; line++) {
995 		y = ftFont->ascent - ftFont->underline_offset - 1;
996 		y *= (line+1);
997 		if (y >= su->h) {
998 			y = (su->h - 1) - ftFont->underline_height;
999 		}
1000 		pDst = (Uint8 *)su->pixels + y*su->pitch;
1001 		for (y = 0; y < ftFont->underline_height; y++) {
1002 			for (x = 0; x < su->w; x++) {
1003 				AG_PACKEDPIXEL_PUT(su->format->BytesPerPixel,
1004 				    pDst, pixel);
1005 				pDst += su->format->BytesPerPixel;
1006 			}
1007 		}
1008 	}
1009 }
1010 
1011 static AG_Surface *
TextRenderFT_Blended(const Uint32 * ucs)1012 TextRenderFT_Blended(const Uint32 *ucs)
1013 {
1014 	AG_TextMetrics tm;
1015 	AG_Font *font = agTextState->font;
1016 	AG_Color C = agTextState->color;
1017 	AG_TTFFont *ftFont = font->ttf;
1018 	AG_TTFGlyph *glyph;
1019 	const Uint32 *ch;
1020 	int xStart, yStart;
1021 	int line;
1022 	AG_Surface *su;
1023 	Uint32 pixel;
1024 	Uint8 *src, *dst;
1025 	int x, y;
1026 	FT_UInt prev_index = 0;
1027 	int w;
1028 
1029 	InitMetrics(&tm);
1030 	TextSizeFT(ucs, &tm, 1);
1031 	if (tm.w <= 0 || tm.h <= 0)
1032 		goto empty;
1033 
1034 	if ((su = AG_SurfaceStdRGBA(tm.w, tm.h)) == NULL) {
1035 		Verbose("TextRenderFT_Blended: %s\n", AG_GetError());
1036 		goto empty;
1037 	}
1038 
1039 	/* Load and render each character */
1040  	line = 0;
1041  	xStart = (tm.nLines > 1) ? AG_TextJustifyOffset(tm.w, tm.wLines[0]) : 0;
1042  	yStart = 0;
1043 
1044 	AG_FillRect(su, NULL, agTextState->colorBG);
1045 	if (agTextState->colorBG.a == AG_ALPHA_TRANSPARENT)
1046 		AG_SurfaceSetColorKey(su, AG_SRCCOLORKEY,
1047 		    AG_MapColorRGBA(su->format, agTextState->colorBG));
1048 
1049 	for (ch = &ucs[0]; *ch != '\0'; ch++) {
1050 		if (*ch == '\n') {
1051 			yStart += font->lineskip;
1052 			xStart = AG_TextJustifyOffset(tm.w, tm.wLines[++line]);
1053 			continue;
1054 		}
1055 		if (*ch == '\t') {
1056 			xStart += agTextState->tabWd;
1057 			continue;
1058 		}
1059 #ifdef SYMBOLS
1060 		if (ch[0] == '$' && agTextSymbols &&
1061 		    ch[1] == '(' && ch[2] != '\0' && ch[3] == ')') {
1062 			xStart += TextRenderSymbol_Blended(ch[2], su, xStart,
1063 			    yStart);
1064 			ch += 3;
1065 			continue;
1066 		}
1067 #endif
1068 		if (AG_TTFFindGlyph(ftFont, *ch,
1069 		    TTF_CACHED_METRICS|TTF_CACHED_PIXMAP)) {
1070 			AG_SurfaceFree(su);
1071 			goto empty;
1072 		}
1073 		glyph = ftFont->current;
1074 		/*
1075 		 * Ensure the width of the pixmap is correct. On some cases,
1076 		 * freetype may report a larger pixmap than possible.
1077 		 * XXX is this test necessary?
1078 		 */
1079 		w = glyph->pixmap.width;
1080 		if (w > glyph->maxx - glyph->minx) {
1081 			w = glyph->maxx - glyph->minx;
1082 		}
1083 		if (FT_HAS_KERNING(ftFont->face) && prev_index &&
1084 		    glyph->index) {
1085 			FT_Vector delta;
1086 
1087 			FT_Get_Kerning(ftFont->face, prev_index, glyph->index,
1088 			    ft_kerning_default, &delta);
1089 			xStart += delta.x >> 6;
1090 		}
1091 
1092 		/* Prevent texture wrapping with first glyph. */
1093 		if ((ch == &ucs[0]) && (glyph->minx < 0))
1094 			xStart -= glyph->minx;
1095 
1096 		for (y = 0; y < glyph->pixmap.rows; y++) {
1097 			if (y+glyph->yoffset < 0 ||
1098 			    y+glyph->yoffset >= su->h) {
1099 				continue;
1100 			}
1101 			dst = (Uint8 *)su->pixels +
1102 			    (yStart + y + glyph->yoffset)*su->pitch +
1103 			    (xStart + glyph->minx)*su->format->BytesPerPixel;
1104 
1105 			/* Adjust src for pixmaps to account for pitch. */
1106 			src = (Uint8 *)(glyph->pixmap.buffer +
1107 			                glyph->pixmap.pitch*y);
1108 
1109 			if (agTextState->colorBG.a == AG_ALPHA_TRANSPARENT) {
1110 				for (x = 0; x < w; x++) {
1111 					Uint32 alpha = *src++;
1112 
1113 					pixel = AG_MapPixelRGBA(su->format,
1114 					    C.r, C.g, C.b, alpha);
1115 					AG_PACKEDPIXEL_PUT(
1116 					    su->format->BytesPerPixel,
1117 					    dst, pixel);
1118 					dst += su->format->BytesPerPixel;
1119 				}
1120 			} else {
1121 				for (x = 0; x < w; x++) {
1122 					Uint32 alpha = *src++;
1123 
1124 					AG_BLEND_RGBA(su, dst,
1125 					    C.r, C.g, C.b, alpha,
1126 					    AG_ALPHA_SRC);
1127 					dst += su->format->BytesPerPixel;
1128 				}
1129 			}
1130 		}
1131 		xStart += glyph->advance;
1132 		if (ftFont->style & AG_TTF_STYLE_BOLD) {
1133 			xStart += ftFont->glyph_overhang;
1134 		}
1135 		prev_index = glyph->index;
1136 	}
1137 	if (ftFont->style & AG_TTF_STYLE_UNDERLINE) {
1138 		TextRenderFT_Blended_Underline(ftFont, su, tm.nLines);
1139 	}
1140 	FreeMetrics(&tm);
1141 	return (su);
1142 empty:
1143 	FreeMetrics(&tm);
1144 	return AG_SurfaceEmpty();
1145 }
1146 
1147 #endif /* HAVE_FREETYPE */
1148 
1149 static __inline__ AG_Surface *
GetBitmapGlyph(AG_Font * font,Uint32 c)1150 GetBitmapGlyph(AG_Font *font, Uint32 c)
1151 {
1152 	if ((font->flags & AG_FONT_UPPERCASE) &&
1153 	    (isalpha(c) && islower(c))) {
1154 		c = toupper(c);
1155 	}
1156 	if (c < font->c0 || c > font->c1) {
1157 		return (agTextState->font->bglyphs[0]);
1158 	}
1159 	return (font->bglyphs[c - font->c0 + 1]);
1160 }
1161 
1162 /* Compute the rendered size of UCS-4 text with a bitmap font. */
1163 static __inline__ void
TextSizeBitmap(const Uint32 * ucs,AG_TextMetrics * tm,int extended)1164 TextSizeBitmap(const Uint32 *ucs, AG_TextMetrics *tm, int extended)
1165 {
1166 	AG_Font *font = agTextState->font;
1167 	const Uint32 *c;
1168 	AG_Surface *sGlyph;
1169 	int wLine = 0;
1170 
1171 	for (c = &ucs[0]; *c != '\0'; c++) {
1172 		sGlyph = GetBitmapGlyph(font, *c);
1173 		if (*c == '\n') {
1174 			if (extended) {
1175 				tm->wLines = Realloc(tm->wLines,
1176 				    (tm->nLines+2)*sizeof(Uint));
1177 				tm->wLines[tm->nLines++] = wLine;
1178 				wLine = 0;
1179 			}
1180 			tm->h += agTextState->font->lineskip;
1181 			continue;
1182 		}
1183 		if (*c == '\t') {
1184 			wLine += agTextState->tabWd;
1185 			tm->w += agTextState->tabWd;
1186 			continue;
1187 		}
1188 		wLine += sGlyph->w;
1189 		tm->w += sGlyph->w;
1190 		tm->h = MAX(tm->h, sGlyph->h);
1191 	}
1192 	if (*c != '\n' && extended) {
1193 		if (tm->nLines > 0) {
1194 			tm->wLines = Realloc(tm->wLines,
1195 			    (tm->nLines+2)*sizeof(Uint));
1196 			tm->wLines[tm->nLines] = wLine;
1197 		}
1198 		tm->nLines++;
1199 	}
1200 }
1201 
1202 /* Render UCS-4 text to a new surface using a bitmap font. */
1203 /* TODO: blend colors */
1204 static AG_Surface *
TextRenderBitmap(const Uint32 * ucs)1205 TextRenderBitmap(const Uint32 *ucs)
1206 {
1207 	AG_TextMetrics tm;
1208 	AG_Font *font = agTextState->font;
1209 	AG_Rect rd;
1210 	int line;
1211 	const Uint32 *c;
1212 	AG_Surface *sGlyph, *su;
1213 
1214 	InitMetrics(&tm);
1215 	TextSizeBitmap(ucs, &tm, 1);
1216 
1217 	if ((su = AG_SurfaceStdRGBA(tm.w, tm.h)) == NULL) {
1218 		AG_FatalError(NULL);
1219 	}
1220 	AG_FillRect(su, NULL, AG_ColorRGBA(0,0,0,0));
1221 
1222 	line = 0;
1223 	rd.x = (tm.nLines > 1) ? AG_TextJustifyOffset(tm.w, tm.wLines[0]) : 0;
1224 	rd.y = 0;
1225 
1226 	for (c = &ucs[0]; *c != '\0'; c++) {
1227 		if (*c == '\n') {
1228 			rd.y += font->lineskip;
1229 			rd.x = AG_TextJustifyOffset(tm.w, tm.wLines[++line]);
1230 			continue;
1231 		}
1232 		if (*c == '\t') {
1233 			rd.x += agTextState->tabWd;
1234 			continue;
1235 		}
1236 		sGlyph = GetBitmapGlyph(font, *c);
1237 		if (*c != ' ') {
1238 			AG_SurfaceBlit(sGlyph, NULL, su, rd.x, rd.y);
1239 		}
1240 		rd.x += sGlyph->w;
1241 	}
1242 	AG_SurfaceSetColorKey(su, AG_SRCCOLORKEY,
1243 	    AG_MapPixelRGBA(su->format, 0,0,0,0));
1244 	AG_SurfaceSetAlpha(su, AG_SRCALPHA, font->bglyphs[0]->format->alpha);
1245 
1246 	FreeMetrics(&tm);
1247 	return (su);
1248 }
1249 
1250 /* Render an UCS-4 text string onto a new 8-bit surface. */
1251 AG_Surface *
AG_TextRenderUCS4(const Uint32 * text)1252 AG_TextRenderUCS4(const Uint32 *text)
1253 {
1254 	switch (agTextState->font->spec.type) {
1255 #ifdef HAVE_FREETYPE
1256 	case AG_FONT_VECTOR:
1257 		return TextRenderFT_Blended(text);
1258 #endif
1259 	case AG_FONT_BITMAP:
1260 		return TextRenderBitmap(text);
1261 	default:
1262 		return AG_SurfaceEmpty();
1263 	}
1264 }
1265 
1266 /* Return the rendered size in pixels of a UCS4-encoded string. */
1267 void
AG_TextSizeUCS4(const Uint32 * ucs4,int * w,int * h)1268 AG_TextSizeUCS4(const Uint32 *ucs4, int *w, int *h)
1269 {
1270 	AG_TextMetrics tm;
1271 
1272 	InitMetrics(&tm);
1273 	switch (agTextState->font->spec.type) {
1274 #ifdef HAVE_FREETYPE
1275 	case AG_FONT_VECTOR:
1276 		TextSizeFT(ucs4, &tm, 0);
1277 		break;
1278 #endif
1279 	case AG_FONT_BITMAP:
1280 		TextSizeBitmap(ucs4, &tm, 0);
1281 		break;
1282 	}
1283 	if (w != NULL) { *w = tm.w; }
1284 	if (h != NULL) { *h = tm.h; }
1285 	FreeMetrics(&tm);
1286 }
1287 
1288 /*
1289  * Return the rendered size in pixels of a UCS4-encoded string, along with
1290  * a line count and the width of each line in an array.
1291  */
1292 void
AG_TextSizeMultiUCS4(const Uint32 * ucs4,int * w,int * h,Uint ** wLines,Uint * nLines)1293 AG_TextSizeMultiUCS4(const Uint32 *ucs4, int *w, int *h, Uint **wLines,
1294     Uint *nLines)
1295 {
1296 	AG_TextMetrics tm;
1297 
1298 	InitMetrics(&tm);
1299 	switch (agTextState->font->spec.type) {
1300 #ifdef HAVE_FREETYPE
1301 	case AG_FONT_VECTOR:
1302 		TextSizeFT(ucs4, &tm, 1);
1303 		break;
1304 #endif
1305 	case AG_FONT_BITMAP:
1306 		TextSizeBitmap(ucs4, &tm, 1);
1307 		break;
1308 	}
1309 	if (w != NULL) { *w = tm.w; }
1310 	if (h != NULL) { *h = tm.h; }
1311 
1312 	if (tm.nLines == 1) {
1313 		tm.wLines = Realloc(tm.wLines, sizeof(Uint));
1314 		tm.wLines[0] = tm.w;
1315 	}
1316 	if (wLines != NULL) { *wLines = tm.wLines; }
1317 	if (nLines != NULL) { *nLines = tm.nLines; }
1318 }
1319 
1320 /*
1321  * Return the expected size in pixels of a rendered C string.
1322  * The string may contain UTF-8 sequences.
1323  */
1324 void
AG_TextSize(const char * text,int * w,int * h)1325 AG_TextSize(const char *text, int *w, int *h)
1326 {
1327 	Uint32 *ucs;
1328 
1329 	if (text == NULL || text[0] == '\0') {
1330 		if (w != NULL) { *w = 0; }
1331 		if (h != NULL) { *h = 0; }
1332 		return;
1333 	}
1334 	if ((ucs = AG_ImportUnicode("UTF-8", text, NULL, NULL)) != NULL) {
1335 		AG_TextSizeUCS4(ucs, w, h);
1336 		Free(ucs);
1337 	} else {
1338 		*w = 0;
1339 		*h = 0;
1340 	}
1341 }
1342 
1343 /*
1344  * Return the expected size in pixels of a rendered C string, along with
1345  * the line count and width of each line. The string may contain UTF-8
1346  * sequences.
1347  */
1348 void
AG_TextSizeMulti(const char * text,int * w,int * h,Uint ** wLines,Uint * nLines)1349 AG_TextSizeMulti(const char *text, int *w, int *h, Uint **wLines, Uint *nLines)
1350 {
1351 	Uint32 *ucs;
1352 
1353 	if ((ucs = AG_ImportUnicode("UTF-8", text, NULL, NULL)) != NULL) {
1354 		AG_TextSizeMultiUCS4(ucs, w, h, wLines, nLines);
1355 		Free(ucs);
1356 	} else {
1357 		*w = 0;
1358 		*h = 0;
1359 		*nLines = 0;
1360 	}
1361 }
1362 
1363 /*
1364  * Parse a command-line font specification and set the default font.
1365  * The format is <face>,<size>,<flags>. Acceptable flags include 'b'
1366  * (bold), 'i' (italic) and 'U' (uppercase).
1367  */
1368 void
AG_TextParseFontSpec(const char * fontspec)1369 AG_TextParseFontSpec(const char *fontspec)
1370 {
1371 	AG_Config *cfg = AG_ConfigObject();
1372 	char buf[128];
1373 	char *fs, *s, *c;
1374 
1375 	Strlcpy(buf, fontspec, sizeof(buf));
1376 	fs = &buf[0];
1377 
1378 	if ((s = AG_Strsep(&fs, ":,/")) != NULL &&
1379 	    s[0] != '\0') {
1380 		AG_SetString(cfg, "font.face", s);
1381 	}
1382 	if ((s = AG_Strsep(&fs, ":,/")) != NULL &&
1383 	    s[0] != '\0') {
1384 		AG_SetInt(cfg, "font.size", atoi(s));
1385 	}
1386 	if ((s = AG_Strsep(&fs, ":,/")) != NULL &&
1387 	    s[0] != '\0') {
1388 		Uint flags = 0;
1389 
1390 		for (c = &s[0]; *c != '\0'; c++) {
1391 			switch (*c) {
1392 			case 'b': flags |= AG_FONT_BOLD;	break;
1393 			case 'i': flags |= AG_FONT_ITALIC;	break;
1394 			case 'U': flags |= AG_FONT_UPPERCASE;	break;
1395 			}
1396 		}
1397 		AG_SetUint(cfg, "font.flags", flags);
1398 	}
1399 }
1400 
1401 /*
1402  * Canned notification and dialog windows.
1403  */
1404 
1405 /* Display an informational message window. */
1406 void
AG_TextMsg(enum ag_text_msg_title title,const char * fmt,...)1407 AG_TextMsg(enum ag_text_msg_title title, const char *fmt, ...)
1408 {
1409 	va_list ap;
1410 	char *s;
1411 
1412 	va_start(ap, fmt);
1413 	Vasprintf(&s, fmt, ap);
1414 	va_end(ap);
1415 
1416 	AG_TextMsgS(title, s);
1417 	free(s);
1418 }
1419 void
AG_TextMsgS(enum ag_text_msg_title title,const char * s)1420 AG_TextMsgS(enum ag_text_msg_title title, const char *s)
1421 {
1422 	AG_Window *win;
1423 	AG_VBox *vb;
1424 	AG_Button *btnOK;
1425 
1426 	win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NORESIZE|AG_WINDOW_NOCLOSE|
1427 	                   AG_WINDOW_NOMINIMIZE|AG_WINDOW_NOMAXIMIZE);
1428 	if (win == NULL)
1429 		return;
1430 
1431 	win->wmType = AG_WINDOW_WM_NOTIFICATION;
1432 	AG_WindowSetCaptionS(win, _(agTextMsgTitles[title]));
1433 	AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
1434 
1435 	vb = AG_VBoxNew(win, 0);
1436 	AG_LabelNewS(vb, 0, s);
1437 
1438 	vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
1439 	btnOK = AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
1440 
1441 	AG_WidgetFocus(btnOK);
1442 	AG_WindowShow(win);
1443 }
1444 
1445 /* Display a message for a given period of time (format string). */
1446 void
AG_TextTmsg(enum ag_text_msg_title title,Uint32 expire,const char * fmt,...)1447 AG_TextTmsg(enum ag_text_msg_title title, Uint32 expire, const char *fmt, ...)
1448 {
1449 	va_list ap;
1450 	char *s;
1451 
1452 	va_start(ap, fmt);
1453 	Vasprintf(&s, fmt, ap);
1454 	va_end(ap);
1455 
1456 	AG_TextTmsgS(title, expire, s);
1457 	free(s);
1458 }
1459 void
AG_TextTmsgS(enum ag_text_msg_title title,Uint32 ticks,const char * s)1460 AG_TextTmsgS(enum ag_text_msg_title title, Uint32 ticks, const char *s)
1461 {
1462 	AG_Window *win;
1463 	AG_VBox *vb;
1464 	AG_Timer *to;
1465 
1466 	win = AG_WindowNew(AG_WINDOW_NORESIZE|AG_WINDOW_NOCLOSE|
1467 	                   AG_WINDOW_NOMINIMIZE|AG_WINDOW_NOMAXIMIZE);
1468 	if (win == NULL) {
1469 		return;
1470 	}
1471 	win->wmType = AG_WINDOW_WM_NOTIFICATION;
1472 	AG_WindowSetCaptionS(win, _(agTextMsgTitles[title]));
1473 	AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
1474 
1475 	vb = AG_VBoxNew(win, 0);
1476 	AG_LabelNewS(vb, 0, s);
1477 	AG_WindowShow(win);
1478 
1479 	to = AG_AddTimerAuto(NULL, ticks, TextTmsgExpire, "%p", win);
1480 	if (to != NULL)
1481 		Strlcpy(to->name, "textTmsg", sizeof(to->name));
1482 }
1483 
1484 /*
1485  * Display an informational message with a "Don't tell me again" option.
1486  * The user preference is preserved in a persistent table. Unlike warnings,
1487  * the dialog window is not modal (format string).
1488  */
1489 void
AG_TextInfo(const char * key,const char * fmt,...)1490 AG_TextInfo(const char *key, const char *fmt, ...)
1491 {
1492 	va_list ap;
1493 	char *s;
1494 
1495 	va_start(ap, fmt);
1496 	Vasprintf(&s, fmt, ap);
1497 	va_end(ap);
1498 
1499 	AG_TextInfoS(key, s);
1500 	free(s);
1501 }
1502 void
AG_TextInfoS(const char * key,const char * s)1503 AG_TextInfoS(const char *key, const char *s)
1504 {
1505 	AG_Config *cfg = AG_ConfigObject();
1506 	char disableSw[64];
1507 	AG_Window *win;
1508 	AG_VBox *vb;
1509 	AG_Checkbox *cb;
1510 	AG_Button *btnOK;
1511 	AG_Variable *Vdisable;
1512 
1513 	Strlcpy(disableSw, "info.", sizeof(disableSw));
1514 	Strlcat(disableSw, key, sizeof(disableSw));
1515 
1516 	AG_ObjectLock(cfg);
1517 
1518 	if (AG_Defined(cfg,disableSw) &&
1519 	    AG_GetInt(cfg,disableSw) == 1)
1520 		goto out;
1521 
1522 	win = AG_WindowNew(AG_WINDOW_NORESIZE|AG_WINDOW_NOCLOSE|
1523 	                   AG_WINDOW_NOMINIMIZE|AG_WINDOW_NOMAXIMIZE);
1524 	win->wmType = AG_WINDOW_WM_DIALOG;
1525 	AG_WindowSetCaptionS(win, _(agTextMsgTitles[AG_MSG_INFO]));
1526 	AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
1527 
1528 	vb = AG_VBoxNew(win, 0);
1529 	AG_LabelNewS(vb, 0, s);
1530 
1531 	vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
1532 	btnOK = AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
1533 
1534 	cb = AG_CheckboxNewS(win, AG_CHECKBOX_HFILL, _("Don't tell me again"));
1535 	Vdisable = AG_SetInt(cfg,disableSw,0);
1536 	AG_BindInt(cb, "state", &Vdisable->data.i);
1537 
1538 	AG_WidgetFocus(btnOK);
1539 	AG_WindowShow(win);
1540 out:
1541 	AG_ObjectUnlock(cfg);
1542 }
1543 
1544 /*
1545  * Display a warning message with a "Don't tell me again" option.
1546  * The user preference is preserved in a persistent table.
1547  */
1548 void
AG_TextWarning(const char * key,const char * fmt,...)1549 AG_TextWarning(const char *key, const char *fmt, ...)
1550 {
1551 	va_list ap;
1552 	char *s;
1553 
1554 	va_start(ap, fmt);
1555 	Vasprintf(&s, fmt, ap);
1556 	va_end(ap);
1557 
1558 	AG_TextWarningS(key, s);
1559 	free(s);
1560 }
1561 void
AG_TextWarningS(const char * key,const char * s)1562 AG_TextWarningS(const char *key, const char *s)
1563 {
1564 	AG_Config *cfg = AG_ConfigObject();
1565 	char disableSw[64];
1566 	AG_Window *win;
1567 	AG_VBox *vb;
1568 	AG_Checkbox *cb;
1569 	AG_Button *btnOK;
1570 	AG_Variable *Vdisable;
1571 
1572 	Strlcpy(disableSw, "warn.", sizeof(disableSw));
1573 	Strlcat(disableSw, key, sizeof(disableSw));
1574 
1575 	AG_ObjectLock(cfg);
1576 
1577 	if (AG_Defined(cfg,disableSw) &&
1578 	    AG_GetInt(cfg,disableSw) == 1)
1579 		goto out;
1580 
1581 	win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NORESIZE|
1582 	                   AG_WINDOW_NOCLOSE|AG_WINDOW_NOMINIMIZE|
1583 			   AG_WINDOW_NOMAXIMIZE);
1584 	win->wmType = AG_WINDOW_WM_DIALOG;
1585 	AG_WindowSetCaptionS(win, _(agTextMsgTitles[AG_MSG_WARNING]));
1586 	AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
1587 
1588 	vb = AG_VBoxNew(win, 0);
1589 	AG_LabelNewS(vb, 0, s);
1590 
1591 	vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
1592 	btnOK = AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
1593 
1594 	cb = AG_CheckboxNewS(win, AG_CHECKBOX_HFILL, _("Don't tell me again"));
1595 	Vdisable = AG_SetInt(cfg,disableSw,0);
1596 	AG_BindInt(cb, "state", &Vdisable->data.i);
1597 
1598 	AG_WidgetFocus(btnOK);
1599 	AG_WindowShow(win);
1600 out:
1601 	AG_ObjectUnlock(cfg);
1602 }
1603 
1604 /* Display an error message. */
1605 void
AG_TextError(const char * fmt,...)1606 AG_TextError(const char *fmt, ...)
1607 {
1608 	va_list ap;
1609 	char *s;
1610 
1611 	va_start(ap, fmt);
1612 	Vasprintf(&s, fmt, ap);
1613 	va_end(ap);
1614 
1615 	AG_TextErrorS(s);
1616 	free(s);
1617 }
1618 void
AG_TextErrorS(const char * s)1619 AG_TextErrorS(const char *s)
1620 {
1621 	AG_Window *win;
1622 	AG_VBox *vb;
1623 	AG_Button *btnOK;
1624 
1625 	win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NORESIZE|
1626 	                   AG_WINDOW_NOCLOSE|AG_WINDOW_NOMINIMIZE|
1627 			   AG_WINDOW_NOMAXIMIZE);
1628 	win->wmType = AG_WINDOW_WM_DIALOG;
1629 	AG_WindowSetCaptionS(win, _(agTextMsgTitles[AG_MSG_ERROR]));
1630 	AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
1631 
1632 	vb = AG_VBoxNew(win, 0);
1633 	AG_LabelNewS(vb, 0, s);
1634 
1635 	vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
1636 	btnOK = AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
1637 
1638 	AG_WidgetFocus(btnOK);
1639 	AG_WindowShow(win);
1640 }
1641 
1642 /* Prompt the user with a choice of options. */
1643 AG_Window *
AG_TextPromptOptions(AG_Button ** bOpts,Uint nbOpts,const char * fmt,...)1644 AG_TextPromptOptions(AG_Button **bOpts, Uint nbOpts, const char *fmt, ...)
1645 {
1646 	char text[AG_LABEL_MAX];
1647 	AG_Window *win;
1648 	AG_Box *bo;
1649 	va_list ap;
1650 	Uint i;
1651 
1652 	va_start(ap, fmt);
1653 	Vsnprintf(text, sizeof(text), fmt, ap);
1654 	va_end(ap);
1655 
1656 	win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NOTITLE|AG_WINDOW_NORESIZE);
1657 	win->wmType = AG_WINDOW_WM_DIALOG;
1658 	AG_WindowSetPosition(win, AG_WINDOW_CENTER, 0);
1659 	AG_WindowSetSpacing(win, 8);
1660 
1661 	AG_LabelNewS(win, 0, text);
1662 
1663 	bo = AG_BoxNew(win, AG_BOX_HORIZ, AG_BOX_HOMOGENOUS|AG_BOX_HFILL);
1664 	for (i = 0; i < nbOpts; i++) {
1665 		bOpts[i] = AG_ButtonNewS(bo, 0, "XXXXXXXXXXX");
1666 	}
1667 	AG_WindowShow(win);
1668 	return (win);
1669 }
1670 
1671 /* Prompt the user for a floating-point value. */
1672 void
AG_TextEditFloat(double * fp,double min,double max,const char * unit,const char * format,...)1673 AG_TextEditFloat(double *fp, double min, double max, const char *unit,
1674     const char *format, ...)
1675 {
1676 	char msg[AG_LABEL_MAX];
1677 	AG_Window *win;
1678 	AG_VBox *vb;
1679 	va_list args;
1680 	AG_Numerical *num;
1681 
1682 	va_start(args, format);
1683 	Vsnprintf(msg, sizeof(msg), format, args);
1684 	va_end(args);
1685 
1686 	win = AG_WindowNew(AG_WINDOW_MODAL);
1687 	win->wmType = AG_WINDOW_WM_DIALOG;
1688 	AG_WindowSetCaptionS(win, _("Enter real number"));
1689 	AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
1690 
1691 	vb = AG_VBoxNew(win, AG_VBOX_HFILL);
1692 	AG_LabelNewS(vb, 0, msg);
1693 
1694 	vb = AG_VBoxNew(win, AG_VBOX_HFILL);
1695 	{
1696 		num = AG_NumericalNewDblR(vb, 0, unit, _("Number: "),
1697 		    fp, min, max);
1698 		AG_SetEvent(num, "numerical-return", AGWINDETACH(win));
1699 	}
1700 
1701 	vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
1702 	AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
1703 
1704 	/* TODO test type */
1705 
1706 	AG_WidgetFocus(num);
1707 	AG_WindowShow(win);
1708 }
1709 
1710 /* Create a dialog to edit a string value. */
1711 void
AG_TextEditString(char * sp,size_t len,const char * msgfmt,...)1712 AG_TextEditString(char *sp, size_t len, const char *msgfmt, ...)
1713 {
1714 	char msg[AG_LABEL_MAX];
1715 	AG_Window *win;
1716 	AG_VBox *vb;
1717 	va_list args;
1718 	AG_Textbox *tb;
1719 
1720 	va_start(args, msgfmt);
1721 	Vsnprintf(msg, sizeof(msg), msgfmt, args);
1722 	va_end(args);
1723 
1724 	win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NOTITLE);
1725 	win->wmType = AG_WINDOW_WM_DIALOG;
1726 	AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
1727 
1728 	vb = AG_VBoxNew(win, AG_VBOX_HFILL);
1729 	AG_LabelNewS(vb, 0, msg);
1730 
1731 	vb = AG_VBoxNew(win, AG_VBOX_EXPAND);
1732 	tb = AG_TextboxNewS(vb, AG_TEXTBOX_EXCL|AG_TEXTBOX_MULTILINE, NULL);
1733 	AG_Expand(tb);
1734 	AG_TextboxBindUTF8(tb, sp, len);
1735 	AG_SetEvent(tb, "textbox-return", AGWINDETACH(win));
1736 
1737 	vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL);
1738 	AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
1739 
1740 	AG_WidgetFocus(tb);
1741 	AG_WindowShow(win);
1742 }
1743 
1744 /* Align a text surface inside a given space. */
1745 void
AG_TextAlign(int * x,int * y,int wArea,int hArea,int wText,int hText,int lPad,int rPad,int tPad,int bPad,enum ag_text_justify justify,enum ag_text_valign valign)1746 AG_TextAlign(int *x, int *y, int wArea, int hArea, int wText, int hText,
1747     int lPad, int rPad, int tPad, int bPad, enum ag_text_justify justify,
1748     enum ag_text_valign valign)
1749 {
1750 	switch (justify) {
1751 	case AG_TEXT_LEFT:
1752 		*x = lPad;
1753 		break;
1754 	case AG_TEXT_CENTER:
1755 		*x = (wArea + lPad + rPad)/2 - wText/2;
1756 		break;
1757 	case AG_TEXT_RIGHT:
1758 		*x = wArea - rPad - wText;
1759 		break;
1760 	}
1761 	switch (valign) {
1762 	case AG_TEXT_TOP:
1763 		*y = tPad;
1764 		break;
1765 	case AG_TEXT_MIDDLE:
1766 		*y = (hArea + tPad + bPad)/2 - hText/2;
1767 		break;
1768 	case AG_TEXT_BOTTOM:
1769 		*y = hArea - bPad - wText;
1770 		break;
1771 	}
1772 }
1773 
1774 AG_ObjectClass agFontClass = {
1775 	"AG_Font",
1776 	sizeof(AG_Font),
1777 	{ 0, 0 },
1778 	FontInit,
1779 	NULL,		/* free */
1780 	FontDestroy,
1781 	NULL,		/* load */
1782 	NULL,		/* save */
1783 	NULL,		/* edit */
1784 };
1785