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

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