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

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