664 lines
17 KiB
C
664 lines
17 KiB
C
/*
|
||
* libcaca Colour ASCII-Art library
|
||
* Copyright © 2002—2018 Sam Hocevar <sam@hocevar.net>
|
||
* All Rights Reserved
|
||
*
|
||
* This library is free software. It comes without any warranty, to
|
||
* the extent permitted by applicable law. You can redistribute it
|
||
* and/or modify it under the terms of the Do What the Fuck You Want
|
||
* to Public License, Version 2, as published by Sam Hocevar. See
|
||
* http://www.wtfpl.net/ for more details.
|
||
*/
|
||
|
||
/*
|
||
* This file contains FIGlet and TOIlet font handling functions.
|
||
*/
|
||
|
||
/*
|
||
* FIXME: this file needs huge cleanup to be usable
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#if !defined(__KERNEL__)
|
||
# include <stdio.h>
|
||
# include <stdlib.h>
|
||
# include <string.h>
|
||
#endif
|
||
|
||
#include "caca.h"
|
||
#include "caca_internals.h"
|
||
|
||
#if defined _WIN32 && defined __GNUC__ && __GNUC__ >= 3
|
||
# if !HAVE_SPRINTF_S
|
||
int sprintf_s(char *s, size_t n, const char *fmt, ...) CACA_WEAK;
|
||
# endif
|
||
# if !HAVE_VSNPRINTF
|
||
int vsnprintf(char *s, size_t n, const char *fmt, va_list ap) CACA_WEAK;
|
||
# endif
|
||
#endif
|
||
|
||
struct caca_charfont
|
||
{
|
||
int term_width;
|
||
int x, y, w, h, lines;
|
||
|
||
enum { H_DEFAULT, H_KERN, H_SMUSH, H_NONE, H_OVERLAP } hmode;
|
||
int hsmushrule;
|
||
uint32_t hardblank;
|
||
int height, baseline, max_length;
|
||
int old_layout;
|
||
int print_direction, full_layout, codetag_count;
|
||
int glyphs;
|
||
caca_canvas_t *fontcv, *charcv;
|
||
int *left, *right; /* Unused yet */
|
||
uint32_t *lookup;
|
||
};
|
||
|
||
static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule);
|
||
static caca_charfont_t * open_charfont(char const *);
|
||
static int free_charfont(caca_charfont_t *);
|
||
static void update_figfont_settings(caca_canvas_t *cv);
|
||
|
||
/** \brief load a figfont and attach it to a canvas */
|
||
int caca_canvas_set_figfont(caca_canvas_t *cv, char const *path)
|
||
{
|
||
caca_charfont_t *ff = NULL;
|
||
|
||
if (path)
|
||
{
|
||
ff = open_charfont(path);
|
||
if (!ff)
|
||
return -1;
|
||
}
|
||
|
||
if (cv->ff)
|
||
{
|
||
caca_free_canvas(cv->ff->charcv);
|
||
free(cv->ff->left);
|
||
free(cv->ff->right);
|
||
free_charfont(cv->ff);
|
||
}
|
||
|
||
cv->ff = ff;
|
||
|
||
if (!path)
|
||
return 0;
|
||
|
||
/* from TOIlet’s main.c -- can be overriden by user */
|
||
ff->term_width = 80;
|
||
ff->hmode = H_DEFAULT;
|
||
|
||
/* from TOIlet’s render.c */
|
||
ff->x = ff->y = 0;
|
||
ff->w = ff->h = 0;
|
||
ff->lines = 0;
|
||
caca_set_canvas_size(cv, 0, 0); /* XXX */
|
||
|
||
cv->ff = ff;
|
||
|
||
update_figfont_settings(cv);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/** \brief set the width of the figfont rendering */
|
||
int caca_set_figfont_width(caca_canvas_t *cv, int width)
|
||
{
|
||
caca_charfont_t *ff = cv->ff;
|
||
|
||
if (!cv->ff)
|
||
return 0;
|
||
|
||
ff->term_width = width;
|
||
|
||
update_figfont_settings(cv);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/** \brief set the smushing mode of the figfont rendering */
|
||
int caca_set_figfont_smush(caca_canvas_t *cv, char const *mode)
|
||
{
|
||
caca_charfont_t *ff = cv->ff;
|
||
|
||
if (!cv->ff)
|
||
return 0;
|
||
|
||
if (!strcasecmp(mode, "default"))
|
||
ff->hmode = H_DEFAULT;
|
||
else if (!strcasecmp(mode, "kern"))
|
||
ff->hmode = H_KERN;
|
||
else if (!strcasecmp(mode, "smush"))
|
||
ff->hmode = H_SMUSH;
|
||
else if (!strcasecmp(mode, "none"))
|
||
ff->hmode = H_NONE;
|
||
else if (!strcasecmp(mode, "overlap"))
|
||
ff->hmode = H_OVERLAP;
|
||
else
|
||
ff->hmode = H_DEFAULT;
|
||
|
||
update_figfont_settings(cv);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/** \brief paste a character using the current figfont */
|
||
int caca_put_figchar(caca_canvas_t *cv, uint32_t ch)
|
||
{
|
||
caca_charfont_t *ff = cv->ff;
|
||
int c, w, h, x, y, overlap, extra, xleft, xright;
|
||
|
||
if (!ff)
|
||
return -1;
|
||
|
||
switch(ch)
|
||
{
|
||
case (uint32_t)'\r':
|
||
return 0;
|
||
case (uint32_t)'\n':
|
||
ff->x = 0;
|
||
ff->y += ff->height;
|
||
return 0;
|
||
/* FIXME: handle '\t' */
|
||
}
|
||
|
||
/* Look whether our glyph is available */
|
||
for(c = 0; c < ff->glyphs; c++)
|
||
if(ff->lookup[c * 2] == ch)
|
||
break;
|
||
|
||
if(c == ff->glyphs)
|
||
return 0;
|
||
|
||
w = ff->lookup[c * 2 + 1];
|
||
h = ff->height;
|
||
|
||
caca_set_canvas_handle(ff->fontcv, 0, c * ff->height);
|
||
caca_blit(ff->charcv, 0, 0, ff->fontcv, NULL);
|
||
|
||
/* Check whether we reached the end of the screen */
|
||
if(ff->x && ff->x + w > ff->term_width)
|
||
{
|
||
ff->x = 0;
|
||
ff->y += h;
|
||
}
|
||
|
||
/* Compute how much the next character will overlap */
|
||
switch(ff->hmode)
|
||
{
|
||
case H_SMUSH:
|
||
case H_KERN:
|
||
case H_OVERLAP:
|
||
extra = (ff->hmode == H_OVERLAP);
|
||
overlap = w;
|
||
for(y = 0; y < h; y++)
|
||
{
|
||
/* Compute how much spaces we can eat from the new glyph */
|
||
for(xright = 0; xright < overlap; xright++)
|
||
if(caca_get_char(ff->charcv, xright, y) != ' ')
|
||
break;
|
||
|
||
/* Compute how much spaces we can eat from the previous glyph */
|
||
for(xleft = 0; xright + xleft < overlap && xleft < ff->x; xleft++)
|
||
if(caca_get_char(cv, ff->x - 1 - xleft, ff->y + y) != ' ')
|
||
break;
|
||
|
||
/* Handle overlapping */
|
||
if(ff->hmode == H_OVERLAP && xleft < ff->x)
|
||
xleft++;
|
||
|
||
/* Handle smushing */
|
||
if(ff->hmode == H_SMUSH)
|
||
{
|
||
if(xleft < ff->x &&
|
||
hsmush(caca_get_char(cv, ff->x - 1 - xleft, ff->y + y),
|
||
caca_get_char(ff->charcv, xright, y),
|
||
ff->hsmushrule))
|
||
xleft++;
|
||
}
|
||
|
||
if(xleft + xright < overlap)
|
||
overlap = xleft + xright;
|
||
}
|
||
break;
|
||
case H_NONE:
|
||
overlap = 0;
|
||
break;
|
||
default:
|
||
return -1;
|
||
}
|
||
|
||
/* Check whether the current canvas is large enough */
|
||
if(ff->x + w - overlap > ff->w)
|
||
ff->w = ff->x + w - overlap < ff->term_width
|
||
? ff->x + w - overlap : ff->term_width;
|
||
|
||
if(ff->y + h > ff->h)
|
||
ff->h = ff->y + h;
|
||
|
||
#if 0 /* deactivated for libcaca insertion */
|
||
if(attr)
|
||
caca_set_attr(cv, attr);
|
||
#endif
|
||
caca_set_canvas_size(cv, ff->w, ff->h);
|
||
|
||
/* Render our char (FIXME: create a rect-aware caca_blit_canvas?) */
|
||
for(y = 0; y < h; y++)
|
||
for(x = 0; x < w; x++)
|
||
{
|
||
uint32_t ch1, ch2;
|
||
uint32_t tmpat = caca_get_attr(ff->fontcv, x, y + c * ff->height);
|
||
ch2 = caca_get_char(ff->charcv, x, y);
|
||
if(ch2 == ' ')
|
||
continue;
|
||
ch1 = caca_get_char(cv, ff->x + x - overlap, ff->y + y);
|
||
if(ch1 == ' ' || ff->hmode != H_SMUSH)
|
||
caca_put_char(cv, ff->x + x - overlap, ff->y + y, ch2);
|
||
else
|
||
caca_put_char(cv, ff->x + x - overlap, ff->y + y,
|
||
hsmush(ch1, ch2, ff->hsmushrule));
|
||
caca_put_attr(cv, ff->x + x, ff->y + y, tmpat);
|
||
}
|
||
|
||
/* Advance cursor */
|
||
ff->x += w - overlap;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/** \brief flush the figlet context */
|
||
int caca_flush_figlet(caca_canvas_t *cv)
|
||
{
|
||
caca_charfont_t *ff = cv->ff;
|
||
int x, y;
|
||
|
||
if (!ff)
|
||
return -1;
|
||
|
||
//ff->torender = cv;
|
||
//caca_set_canvas_size(ff->torender, ff->w, ff->h);
|
||
caca_set_canvas_size(cv, ff->w, ff->h);
|
||
|
||
/* FIXME: do this somewhere else, or record hardblank positions */
|
||
for(y = 0; y < ff->h; y++)
|
||
for(x = 0; x < ff->w; x++)
|
||
if(caca_get_char(cv, x, y) == 0xa0)
|
||
{
|
||
uint32_t attr = caca_get_attr(cv, x, y);
|
||
caca_put_char(cv, x, y, ' ');
|
||
caca_put_attr(cv, x, y, attr);
|
||
}
|
||
|
||
ff->x = ff->y = 0;
|
||
ff->w = ff->h = 0;
|
||
|
||
//cv = caca_create_canvas(1, 1); /* XXX */
|
||
|
||
/* from render.c */
|
||
ff->lines += caca_get_canvas_height(cv);
|
||
|
||
return 0;
|
||
}
|
||
|
||
#define STD_GLYPHS (127 - 32)
|
||
#define EXT_GLYPHS (STD_GLYPHS + 7)
|
||
|
||
static caca_charfont_t * open_charfont(char const *path)
|
||
{
|
||
char buf[2048];
|
||
char hardblank[10];
|
||
caca_charfont_t *ff;
|
||
char *data = NULL;
|
||
caca_file_t *f;
|
||
#if !defined __KERNEL__ && (defined HAVE_SNPRINTF || defined HAVE_SPRINTF_S)
|
||
int const pathlen = 2048;
|
||
char *altpath = NULL;
|
||
#endif
|
||
int i, j, size, comment_lines;
|
||
|
||
ff = malloc(sizeof(caca_charfont_t));
|
||
if(!ff)
|
||
{
|
||
seterrno(ENOMEM);
|
||
return NULL;
|
||
}
|
||
|
||
/* Open font: if not found, try .tlf, then .flf */
|
||
f = caca_file_open(path, "r");
|
||
#if !defined __KERNEL__ && (defined HAVE_SNPRINTF || defined HAVE_SPRINTF_S)
|
||
if(!f)
|
||
altpath = malloc(pathlen);
|
||
if(!f)
|
||
{
|
||
#if defined HAVE_SPRINTF_S
|
||
sprintf_s(altpath, pathlen - 1, "%s.tlf", path);
|
||
#else
|
||
snprintf(altpath, pathlen - 1, "%s.tlf", path);
|
||
#endif
|
||
altpath[pathlen - 1] = '\0';
|
||
f = caca_file_open(altpath, "r");
|
||
}
|
||
if(!f)
|
||
{
|
||
#if defined HAVE_SPRINTF_S
|
||
sprintf_s(altpath, pathlen - 1, "%s.flf", path);
|
||
#else
|
||
snprintf(altpath, pathlen - 1, "%s.flf", path);
|
||
#endif
|
||
altpath[pathlen - 1] = '\0';
|
||
f = caca_file_open(altpath, "r");
|
||
}
|
||
if (altpath)
|
||
free(altpath);
|
||
#endif
|
||
if(!f)
|
||
{
|
||
free(ff);
|
||
seterrno(ENOENT);
|
||
return NULL;
|
||
}
|
||
|
||
/* Read header */
|
||
ff->print_direction = 0;
|
||
ff->full_layout = 0;
|
||
ff->codetag_count = 0;
|
||
caca_file_gets(f, buf, 2048);
|
||
if(sscanf(buf, "%*[ft]lf2a%6s %u %u %u %i %u %u %u %u\n", hardblank,
|
||
&ff->height, &ff->baseline, &ff->max_length,
|
||
&ff->old_layout, &comment_lines, &ff->print_direction,
|
||
&ff->full_layout, &ff->codetag_count) < 6)
|
||
{
|
||
debug("figfont error: `%s' has invalid header: %s", path, buf);
|
||
caca_file_close(f);
|
||
free(ff);
|
||
seterrno(EINVAL);
|
||
return NULL;
|
||
}
|
||
|
||
if(ff->old_layout < -1 || ff->old_layout > 63 || ff->full_layout > 32767
|
||
|| ((ff->full_layout & 0x80) && (ff->full_layout & 0x3f) == 0
|
||
&& ff->old_layout))
|
||
{
|
||
debug("figfont error: `%s' has invalid layout %i/%u",
|
||
path, ff->old_layout, ff->full_layout);
|
||
caca_file_close(f);
|
||
free(ff);
|
||
seterrno(EINVAL);
|
||
return NULL;
|
||
}
|
||
|
||
ff->hardblank = caca_utf8_to_utf32(hardblank, NULL);
|
||
|
||
/* Skip comment lines */
|
||
for(i = 0; i < comment_lines; i++)
|
||
caca_file_gets(f, buf, 2048);
|
||
|
||
/* Read mandatory characters (32-127, 196, 214, 220, 228, 246, 252, 223)
|
||
* then read additional characters. */
|
||
ff->glyphs = 0;
|
||
ff->lookup = NULL;
|
||
|
||
for(i = 0, size = 0; !caca_file_eof(f); ff->glyphs++)
|
||
{
|
||
if((ff->glyphs % 2048) == 0)
|
||
ff->lookup = realloc(ff->lookup,
|
||
(ff->glyphs + 2048) * 2 * sizeof(int));
|
||
|
||
if(ff->glyphs < STD_GLYPHS)
|
||
{
|
||
ff->lookup[ff->glyphs * 2] = 32 + ff->glyphs;
|
||
}
|
||
else if(ff->glyphs < EXT_GLYPHS)
|
||
{
|
||
static int const tab[7] = { 196, 214, 220, 228, 246, 252, 223 };
|
||
ff->lookup[ff->glyphs * 2] = tab[ff->glyphs - STD_GLYPHS];
|
||
}
|
||
else
|
||
{
|
||
unsigned int tmp;
|
||
|
||
if(caca_file_gets(f, buf, 2048) == NULL)
|
||
break;
|
||
|
||
/* Ignore blank lines, as in jacky.flf */
|
||
if(buf[0] == '\n' || buf[0] == '\r')
|
||
continue;
|
||
|
||
/* Ignore negative indices for now, as in ivrit.flf */
|
||
if(buf[0] == '-')
|
||
{
|
||
for(j = 0; j < ff->height; j++)
|
||
caca_file_gets(f, buf, 2048);
|
||
continue;
|
||
}
|
||
|
||
if(!buf[0] || buf[0] < '0' || buf[0] > '9')
|
||
{
|
||
debug("figfont error: glyph #%u in `%s'", ff->glyphs, path);
|
||
free(data);
|
||
free(ff->lookup);
|
||
free(ff);
|
||
seterrno(EINVAL);
|
||
return NULL;
|
||
}
|
||
|
||
sscanf(buf, buf[1] == 'x' ? "%x" : "%u", &tmp);
|
||
ff->lookup[ff->glyphs * 2] = tmp;
|
||
}
|
||
|
||
ff->lookup[ff->glyphs * 2 + 1] = 0;
|
||
|
||
for(j = 0; j < ff->height; j++)
|
||
{
|
||
if(i + 2048 >= size)
|
||
data = realloc(data, size += 2048);
|
||
|
||
caca_file_gets(f, data + i, 2048);
|
||
i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data;
|
||
}
|
||
}
|
||
|
||
caca_file_close(f);
|
||
|
||
if(ff->glyphs < EXT_GLYPHS)
|
||
{
|
||
debug("figfont error: only %u glyphs in `%s', expected at least %u",
|
||
ff->glyphs, path, EXT_GLYPHS);
|
||
free(data);
|
||
free(ff->lookup);
|
||
free(ff);
|
||
seterrno(EINVAL);
|
||
return NULL;
|
||
}
|
||
|
||
/* Remaining initialisation */
|
||
ff->charcv = NULL;
|
||
ff->left = NULL;
|
||
ff->right = NULL;
|
||
|
||
/* Import buffer into canvas */
|
||
ff->fontcv = caca_create_canvas(0, 0);
|
||
caca_import_canvas_from_memory(ff->fontcv, data, i, "utf8");
|
||
free(data);
|
||
|
||
/* Remove EOL characters. For now we ignore hardblanks, don’t do any
|
||
* smushing, nor any kind of error checking. */
|
||
for(j = 0; j < ff->height * ff->glyphs; j++)
|
||
{
|
||
uint32_t ch, oldch = 0;
|
||
|
||
for(i = ff->max_length; i--;)
|
||
{
|
||
ch = caca_get_char(ff->fontcv, i, j);
|
||
|
||
/* Replace hardblanks with U+00A0 NO-BREAK SPACE */
|
||
if(ch == ff->hardblank)
|
||
caca_put_char(ff->fontcv, i, j, ch = 0xa0);
|
||
|
||
if(oldch && ch != oldch)
|
||
{
|
||
if(!ff->lookup[j / ff->height * 2 + 1])
|
||
ff->lookup[j / ff->height * 2 + 1] = i + 1;
|
||
}
|
||
else if(oldch && ch == oldch)
|
||
caca_put_char(ff->fontcv, i, j, ' ');
|
||
else if(ch != ' ')
|
||
{
|
||
oldch = ch;
|
||
caca_put_char(ff->fontcv, i, j, ' ');
|
||
}
|
||
}
|
||
}
|
||
|
||
return ff;
|
||
}
|
||
|
||
int free_charfont(caca_charfont_t *ff)
|
||
{
|
||
caca_free_canvas(ff->fontcv);
|
||
free(ff->lookup);
|
||
free(ff);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void update_figfont_settings(caca_canvas_t *cv)
|
||
{
|
||
caca_charfont_t *ff = cv->ff;
|
||
|
||
if (!cv->ff)
|
||
return;
|
||
|
||
/* from TOIlet’s figlet.c */
|
||
if (ff->full_layout & 0x3f)
|
||
ff->hsmushrule = ff->full_layout & 0x3f;
|
||
else if (ff->old_layout > 0)
|
||
ff->hsmushrule = ff->old_layout;
|
||
|
||
switch (ff->hmode)
|
||
{
|
||
case H_DEFAULT:
|
||
if (ff->old_layout == -1)
|
||
ff->hmode = H_NONE;
|
||
else if (ff->old_layout == 0 && (ff->full_layout & 0xc0) == 0x40)
|
||
ff->hmode = H_KERN;
|
||
else if ((ff->old_layout & 0x3f) && (ff->full_layout & 0x3f)
|
||
&& (ff->full_layout & 0x80))
|
||
{
|
||
ff->hmode = H_SMUSH;
|
||
ff->hsmushrule = ff->full_layout & 0x3f;
|
||
}
|
||
else if (ff->old_layout == 0 && (ff->full_layout & 0xbf) == 0x80)
|
||
{
|
||
ff->hmode = H_SMUSH;
|
||
ff->hsmushrule = 0x3f;
|
||
}
|
||
else
|
||
ff->hmode = H_OVERLAP;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if (ff->charcv)
|
||
caca_free_canvas(ff->charcv);
|
||
ff->charcv = caca_create_canvas(ff->max_length - 2, ff->height);
|
||
|
||
free(ff->left);
|
||
free(ff->right);
|
||
ff->left = malloc(ff->height * sizeof(int));
|
||
ff->right = malloc(ff->height * sizeof(int));
|
||
}
|
||
|
||
static uint32_t hsmush(uint32_t ch1, uint32_t ch2, int rule)
|
||
{
|
||
/* Rule 1 */
|
||
if((rule & 0x01) && ch1 == ch2 && ch1 != 0xa0)
|
||
return ch2;
|
||
|
||
if(ch1 < 0x80 && ch2 < 0x80)
|
||
{
|
||
char const charlist[] = "|/\\[]{}()<>";
|
||
char *tmp1, *tmp2;
|
||
|
||
/* Rule 2 */
|
||
if(rule & 0x02)
|
||
{
|
||
if(ch1 == '_' && strchr(charlist, ch2))
|
||
return ch2;
|
||
|
||
if(ch2 == '_' && strchr(charlist, ch1))
|
||
return ch1;
|
||
}
|
||
|
||
/* Rule 3 */
|
||
if((rule & 0x04) &&
|
||
(tmp1 = strchr(charlist, ch1)) && (tmp2 = strchr(charlist, ch2)))
|
||
{
|
||
int cl1 = (tmp1 + 1 - charlist) / 2;
|
||
int cl2 = (tmp2 + 1 - charlist) / 2;
|
||
|
||
if(cl1 < cl2)
|
||
return ch2;
|
||
if(cl1 > cl2)
|
||
return ch1;
|
||
}
|
||
|
||
/* Rule 4 */
|
||
if(rule & 0x08)
|
||
{
|
||
uint16_t s = ch1 + ch2;
|
||
uint16_t p = ch1 * ch2;
|
||
|
||
if(p == 15375 /* '{' * '}' */
|
||
|| p == 8463 /* '[' * ']' */
|
||
|| (p == 1640 && s == 81)) /* '(' *|+ ')' */
|
||
return '|';
|
||
}
|
||
|
||
/* Rule 5 */
|
||
if(rule & 0x10)
|
||
{
|
||
switch((ch1 << 8) | ch2)
|
||
{
|
||
case 0x2f5c: return '|'; /* /\ */
|
||
case 0x5c2f: return 'Y'; /* \/ */
|
||
case 0x3e3c: return 'X'; /* >< */
|
||
}
|
||
}
|
||
|
||
/* Rule 6 */
|
||
if((rule & 0x20) && ch1 == ch2 && ch1 == 0xa0)
|
||
return 0xa0;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Functions for the mingw32 runtime
|
||
*/
|
||
|
||
#if defined _WIN32 && defined __GNUC__ && __GNUC__ >= 3
|
||
# if !HAVE_SPRINTF_S
|
||
int sprintf_s(char *s, size_t n, const char *fmt, ...)
|
||
{
|
||
va_list args;
|
||
int ret;
|
||
va_start(args, fmt);
|
||
ret = vsnprintf(s, n, fmt, args);
|
||
va_end(args);
|
||
return ret;
|
||
}
|
||
# endif
|
||
|
||
# if !HAVE_VSNPRINTF
|
||
int vsnprintf(char *s, size_t n, const char *fmt, va_list ap)
|
||
{
|
||
return 0;
|
||
}
|
||
# endif
|
||
#endif
|
||
|