/* * libcaca Colour ASCII-Art library * Copyright © 2002—2018 Sam Hocevar * 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 # include # include #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