source: toilet/trunk/src/figlet.c @ 2263

Last change on this file since 2263 was 1461, checked in by Sam Hocevar, 16 years ago
  • Bwarf, typo in the no warranty clause.
  • Property svn:keywords set to Id
File size: 12.9 KB
Line 
1/*
2 *  TOIlet        The Other Implementation’s letters
3 *  Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
4 *                All Rights Reserved
5 *
6 *  $Id: figlet.c 1461 2006-12-12 01:50:48Z sam $
7 *
8 *  This program is free software. It comes without any warranty, to
9 *  the extent permitted by applicable law. You can redistribute it
10 *  and/or modify it under the terms of the Do What The Fuck You Want
11 *  To Public License, Version 2, as published by Sam Hocevar. See
12 *  http://sam.zoy.org/wtfpl/COPYING for more details.
13 */
14
15/*
16 * This file contains functions for handling FIGlet fonts.
17 */
18
19#include "config.h"
20
21#if defined(HAVE_INTTYPES_H)
22#   include <inttypes.h>
23#endif
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <cucul.h>
28
29#include "toilet.h"
30#include "render.h"
31#include "io.h"
32
33#define STD_GLYPHS (127 - 32)
34#define EXT_GLYPHS (STD_GLYPHS + 7)
35
36static int feed_figlet(context_t *, uint32_t, uint32_t);
37static int flush_figlet(context_t *);
38static int end_figlet(context_t *);
39
40static int open_font(context_t *cx);
41static uint32_t hsmush(uint32_t, uint32_t, unsigned int);
42
43int init_figlet(context_t *cx)
44{
45    if(open_font(cx))
46        return -1;
47
48    if(cx->full_layout & 0x3f)
49        cx->hsmushrule = cx->full_layout & 0x3f;
50    else if(cx->old_layout > 0)
51        cx->hsmushrule = cx->old_layout;
52
53    switch(cx->hmode)
54    {
55    case H_DEFAULT:
56        if(cx->old_layout == -1)
57            cx->hmode = H_NONE;
58        else if(cx->old_layout == 0 && (cx->full_layout & 0xc0) == 0x40)
59            cx->hmode = H_KERN;
60        else if((cx->old_layout & 0x3f) && (cx->full_layout & 0x3f)
61                 && (cx->full_layout & 0x80))
62        {
63            cx->hmode = H_SMUSH;
64            cx->hsmushrule = cx->full_layout & 0x3f;
65        }
66        else if(cx->old_layout == 0 && (cx->full_layout & 0xbf) == 0x80)
67        {
68            cx->hmode = H_SMUSH;
69            cx->hsmushrule = 0x3f;
70        }
71        else
72            cx->hmode = H_OVERLAP;
73        break;
74    default:
75        break;
76    }
77
78    cx->charcv = cucul_create_canvas(cx->max_length - 2, cx->height);
79
80    cx->left = malloc(cx->height * sizeof(int));
81    cx->right = malloc(cx->height * sizeof(int));
82
83    cx->feed = feed_figlet;
84    cx->flush = flush_figlet;
85    cx->end = end_figlet;
86
87    return 0;
88}
89
90static int feed_figlet(context_t *cx, uint32_t ch, uint32_t attr)
91{
92    unsigned int c, w, h, x, y, overlap, extra, xleft, xright;
93
94    switch(ch)
95    {
96        case (uint32_t)'\r':
97            return 0;
98        case (uint32_t)'\n':
99            cx->x = 0;
100            cx->y += cx->height;
101            return 0;
102        /* FIXME: handle '\t' */
103    }
104
105    /* Look whether our glyph is available */
106    for(c = 0; c < cx->glyphs; c++)
107        if(cx->lookup[c * 2] == ch)
108            break;
109
110    if(c == cx->glyphs)
111        return 0;
112
113    w = cx->lookup[c * 2 + 1];
114    h = cx->height;
115
116    cucul_set_canvas_handle(cx->fontcv, 0, c * cx->height);
117    cucul_blit(cx->charcv, 0, 0, cx->fontcv, NULL);
118
119    /* Check whether we reached the end of the screen */
120    if(cx->x && cx->x + w > cx->term_width)
121    {
122        cx->x = 0;
123        cx->y += h;
124    }
125
126    /* Compute how much the next character will overlap */
127    switch(cx->hmode)
128    {
129    case H_SMUSH:
130    case H_KERN:
131    case H_OVERLAP:
132        extra = (cx->hmode == H_OVERLAP);
133        overlap = w;
134        for(y = 0; y < h; y++)
135        {
136            /* Compute how much spaces we can eat from the new glyph */
137            for(xright = 0; xright < overlap; xright++)
138                if(cucul_get_char(cx->charcv, xright, y) != ' ')
139                    break;
140
141            /* Compute how much spaces we can eat from the previous glyph */
142            for(xleft = 0; xright + xleft < overlap && xleft < cx->x; xleft++)
143                if(cucul_get_char(cx->cv, cx->x - 1 - xleft, cx->y + y) != ' ')
144                    break;
145
146            /* Handle overlapping */
147            if(cx->hmode == H_OVERLAP && xleft < cx->x)
148                xleft++;
149
150            /* Handle smushing */
151            if(cx->hmode == H_SMUSH)
152            {
153                if(xleft < cx->x &&
154                   hsmush(cucul_get_char(cx->cv, cx->x - 1 - xleft, cx->y + y),
155                          cucul_get_char(cx->charcv, xright, y),
156                          cx->hsmushrule))
157                    xleft++;
158            }
159 
160            if(xleft + xright < overlap)
161                overlap = xleft + xright;
162        }
163        break;
164    case H_NONE:
165        overlap = 0;
166        break;
167    default:
168        return -1;
169    }
170
171    /* Check whether the current canvas is large enough */
172    if(cx->x + w - overlap > cx->w)
173        cx->w = cx->x + w - overlap < cx->term_width
174              ? cx->x + w - overlap : cx->term_width;
175
176    if(cx->y + h > cx->h)
177        cx->h = cx->y + h;
178
179    if(attr)
180        cucul_set_attr(cx->cv, attr);
181    cucul_set_canvas_size(cx->cv, cx->w, cx->h);
182
183    /* Render our char (FIXME: create a rect-aware cucul_blit_canvas?) */
184    for(y = 0; y < h; y++)
185        for(x = 0; x < w; x++)
186    {
187        uint32_t ch1, ch2;
188        //uint32_t tmpat = cucul_get_attr(cx->fontcv, x, y + c * cx->height);
189        ch2 = cucul_get_char(cx->charcv, x, y);
190        if(ch2 == ' ')
191            continue;
192        ch1 = cucul_get_char(cx->cv, cx->x + x - overlap, cx->y + y);
193        /* FIXME: this could be changed to cucul_put_attr() when the
194         * function is fixed in libcucul */
195        //cucul_set_attr(cx->cv, tmpat);
196        if(ch1 == ' ' || cx->hmode != H_SMUSH)
197            cucul_put_char(cx->cv, cx->x + x - overlap, cx->y + y, ch2);
198        else
199            cucul_put_char(cx->cv, cx->x + x - overlap, cx->y + y,
200                           hsmush(ch1, ch2, cx->hsmushrule));
201        //cucul_put_attr(cx->cv, cx->x + x, cx->y + y, tmpat);
202    }
203
204    /* Advance cursor */
205    cx->x += w - overlap;
206
207    return 0;
208}
209
210static int flush_figlet(context_t *cx)
211{
212    unsigned int x, y;
213
214    cx->torender = cx->cv;
215    cucul_set_canvas_size(cx->torender, cx->w, cx->h);
216
217    /* FIXME: do this somewhere else, or record hardblank positions */
218    for(y = 0; y < cx->h; y++)
219        for(x = 0; x < cx->w; x++)
220            if(cucul_get_char(cx->torender, x, y) == 0xa0)
221            {
222                uint32_t attr = cucul_get_attr(cx->torender, x, y);
223                cucul_put_char(cx->torender, x, y, ' ');
224                cucul_put_attr(cx->torender, x, y, attr);
225            }
226
227    cx->x = cx->y = 0;
228    cx->w = cx->h = 0;
229    cx->cv = cucul_create_canvas(1, 1);
230
231    return 0;
232}
233
234static int end_figlet(context_t *cx)
235{
236    free(cx->left);
237    free(cx->right);
238    cucul_free_canvas(cx->charcv);
239    cucul_free_canvas(cx->fontcv);
240    free(cx->lookup);
241
242    return 0;
243}
244
245static int open_font(context_t *cx)
246{
247    char *data = NULL;
248    char path[2048];
249    char buf[2048];
250    char hardblank[10];
251    TOIFILE *f;
252    unsigned int i, j, size, comment_lines;
253
254    /* Open font: try .tlf, then .flf */
255    snprintf(path, 2047, "%s/%s.tlf", cx->dir, cx->font);
256    path[2047] = '\0';
257    f = toiopen(path, "r");
258    if(!f)
259    {
260        snprintf(path, 2047, "%s/%s.flf", cx->dir, cx->font);
261        path[2047] = '\0';
262        f = toiopen(path, "r");
263        if(!f)
264        {
265            fprintf(stderr, "font `%s' not found\n", path);
266            return -1;
267        }
268    }
269
270    /* Read header */
271    cx->print_direction = 0;
272    cx->full_layout = 0;
273    cx->codetag_count = 0;
274    toigets(buf, 2048, f);
275    if(sscanf(buf, "%*[ft]lf2a%6s %u %u %u %i %u %u %u %u\n", hardblank,
276              &cx->height, &cx->baseline, &cx->max_length,
277              &cx->old_layout, &comment_lines, &cx->print_direction,
278              &cx->full_layout, &cx->codetag_count) < 6)
279    {
280        fprintf(stderr, "font `%s' has invalid header: %s\n", path, buf);
281        toiclose(f);
282        return -1;
283    }
284
285    if(cx->old_layout < -1 || cx->old_layout > 63 || cx->full_layout > 32767
286        || ((cx->full_layout & 0x80) && (cx->full_layout & 0x3f) == 0
287            && cx->old_layout))
288    {
289        fprintf(stderr, "font `%s' has invalid layout %i/%u\n",
290                path, cx->old_layout, cx->full_layout);
291        toiclose(f);
292        return -1;
293    }
294
295    cx->hardblank = cucul_utf8_to_utf32(hardblank, NULL);
296
297    /* Skip comment lines */
298    for(i = 0; i < comment_lines; i++)
299        toigets(buf, 2048, f);
300
301    /* Read mandatory characters (32-127, 196, 214, 220, 228, 246, 252, 223)
302     * then read additional characters. */
303    cx->glyphs = 0;
304    cx->lookup = NULL;
305
306    for(i = 0, size = 0; !toieof(f); cx->glyphs++)
307    {
308        if((cx->glyphs % 2048) == 0)
309            cx->lookup = realloc(cx->lookup,
310                                   (cx->glyphs + 2048) * 2 * sizeof(int));
311
312        if(cx->glyphs < STD_GLYPHS)
313        {
314            cx->lookup[cx->glyphs * 2] = 32 + cx->glyphs;
315        }
316        else if(cx->glyphs < EXT_GLYPHS)
317        {
318            static int const tab[7] = { 196, 214, 220, 228, 246, 252, 223 };
319            cx->lookup[cx->glyphs * 2] = tab[cx->glyphs - STD_GLYPHS];
320        }
321        else
322        {
323            if(toigets(buf, 2048, f) == NULL)
324                break;
325
326            /* Ignore blank lines, as in jacky.flf */
327            if(buf[0] == '\n' || buf[0] == '\r')
328                continue;
329
330            /* Ignore negative indices for now, as in ivrit.flf */
331            if(buf[0] == '-')
332            {
333                for(j = 0; j < cx->height; j++)
334                    toigets(buf, 2048, f);
335                continue;
336            }
337
338            if(!buf[0] || buf[0] < '0' || buf[0] > '9')
339            {
340                free(data);
341                free(cx->lookup);
342                fprintf(stderr, "read error at glyph #%u in `%s'\n",
343                                cx->glyphs, path);
344                return -1;
345            }
346
347            if(buf[1] == 'x')
348                sscanf(buf, "%x", &cx->lookup[cx->glyphs * 2]);
349            else
350                sscanf(buf, "%u", &cx->lookup[cx->glyphs * 2]);
351        }
352
353        cx->lookup[cx->glyphs * 2 + 1] = 0;
354
355        for(j = 0; j < cx->height; j++)
356        {
357            if(i + 2048 >= size)
358                data = realloc(data, size += 2048);
359
360            toigets(data + i, 2048, f);
361            i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data;
362        }
363    }
364
365    toiclose(f);
366
367    if(cx->glyphs < EXT_GLYPHS)
368    {
369        free(data);
370        free(cx->lookup);
371        fprintf(stderr, "only %u glyphs in `%s', expected at least %u\n",
372                        cx->glyphs, path, EXT_GLYPHS);
373        return -1;
374    }
375
376    /* Import buffer into canvas */
377    cx->fontcv = cucul_create_canvas(0, 0);
378    cucul_import_memory(cx->fontcv, data, i, "utf8");
379    free(data);
380
381    /* Remove EOL characters. For now we ignore hardblanks, don’t do any
382     * smushing, nor any kind of error checking. */
383    for(j = 0; j < cx->height * cx->glyphs; j++)
384    {
385        unsigned long int ch, oldch = 0;
386
387        for(i = cx->max_length; i--;)
388        {
389            ch = cucul_get_char(cx->fontcv, i, j);
390
391            /* Replace hardblanks with U+00A0 NO-BREAK SPACE */
392            if(ch == cx->hardblank)
393                cucul_put_char(cx->fontcv, i, j, ch = 0xa0);
394
395            if(oldch && ch != oldch)
396            {
397                if(!cx->lookup[j / cx->height * 2 + 1])
398                    cx->lookup[j / cx->height * 2 + 1] = i + 1;
399            }
400            else if(oldch && ch == oldch)
401                cucul_put_char(cx->fontcv, i, j, ' ');
402            else if(ch != ' ')
403            {
404                oldch = ch;
405                cucul_put_char(cx->fontcv, i, j, ' ');
406            }
407        }
408    }
409
410    return 0;
411}
412
413static uint32_t hsmush(uint32_t ch1, uint32_t ch2, unsigned int rule)
414{
415    /* Rule 1 */
416    if((rule & 0x01) && ch1 == ch2 && ch1 != 0xa0)
417        return ch2;
418
419    if(ch1 < 0x80 && ch2 < 0x80)
420    {
421        char const charlist[] = "|/\\[]{}()<>";
422        char *tmp1, *tmp2;
423
424        /* Rule 2 */
425        if(rule & 0x02)
426        {
427            if(ch1 == '_' && strchr(charlist, ch2))
428                return ch2;
429
430            if(ch2 == '_' && strchr(charlist, ch1))
431                return ch1;
432        }
433
434        /* Rule 3 */
435        if((rule & 0x04) &&
436           (tmp1 = strchr(charlist, ch1)) && (tmp2 = strchr(charlist, ch2)))
437        {
438            int cl1 = (tmp1 + 1 - charlist) / 2;
439            int cl2 = (tmp2 + 1 - charlist) / 2;
440
441            if(cl1 < cl2)
442                return ch2;
443            if(cl1 > cl2)
444                return ch1;
445        }
446
447        /* Rule 4 */
448        if(rule & 0x08)
449        {
450            uint16_t s = ch1 + ch2;
451            uint16_t p = ch1 * ch2;
452
453            if(p == 15375 /* '{' * '}' */
454                || p == 8463 /* '[' * ']' */
455                || (p == 1640 && s == 81)) /* '(' *|+ ')' */
456                return '|';
457        }
458
459        /* Rule 5 */
460        if(rule & 0x10)
461        {
462            switch((ch1 << 8) | ch2)
463            {
464                case 0x2f5c: return '|'; /* /\ */
465                case 0x5c2f: return 'Y'; /* \/ */
466                case 0x3e3c: return 'X'; /* >< */
467            }
468        }
469
470        /* Rule 6 */
471        if((rule & 0x20) && ch1 == ch2 && ch1 == 0xa0)
472            return 0xa0;
473    }
474
475    return 0;
476}
477
Note: See TracBrowser for help on using the repository browser.