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