tdfiglet/tdfiglet.c

641 lines
12 KiB
C
Raw Permalink Normal View History

2018-07-14 21:46:44 +00:00
/* Copyright (c) 2018 Trollforge. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Trollforge's name may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*/
2018-06-23 06:44:33 +00:00
#include <ctype.h>
#include <dirent.h>
2018-06-23 06:44:33 +00:00
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <iconv.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/queue.h>
2018-06-23 06:44:33 +00:00
#include <sys/stat.h>
2018-08-24 07:20:33 +00:00
#include <sys/time.h>
2018-06-23 06:44:33 +00:00
#include <sys/types.h>
#ifdef DEBUG
#define DEBUG 1
#endif /* DEBUG */
#define OUTLN_FNT 0
#define BLOCK_FNT 1
#define COLOR_FNT 2
#define NUM_CHARS 94
/* 4 bytes + '\0' */
#define MAX_UTFSTR 5
#define LEFT_JUSTIFY 0
#define RIGHT_JUSTIFY 1
#define CENTER_JUSTIFY 2
#define DEFAULT_WIDTH 80
2018-06-24 04:21:29 +00:00
#define COLOR_ANSI 0
#define COLOR_MIRC 1
#define ENC_UNICODE 0
#define ENC_ANSI 1
2018-07-14 20:57:54 +00:00
#ifndef FONT_DIR
#define FONT_DIR "fonts"
#endif /* FONT_DIR */
#ifndef FONT_EXT
#define FONT_EXT "tdf"
#endif /* FONT_EXT */
2018-07-14 21:23:43 +00:00
#ifndef DEFAULT_FONT
#define DEFAULT_FONT "brndamgx" /* seems most complete */
#endif /* DEFAULT_FONT */
2018-06-23 06:44:33 +00:00
typedef struct opt_s {
uint8_t justify;
uint8_t width;
2018-06-24 04:21:29 +00:00
uint8_t color;
uint8_t encoding;
bool random;
2018-06-24 05:30:56 +00:00
bool info;
2018-06-23 06:44:33 +00:00
} opt_t;
typedef struct cell_s {
uint8_t color;
char utfchar[MAX_UTFSTR];
} cell_t;
typedef struct glyph_s {
uint8_t width;
uint8_t height;
cell_t *cell;
} glyph_t;
2018-06-27 04:00:22 +00:00
typedef struct font_s {
2018-06-23 06:44:33 +00:00
uint8_t namelen;
uint8_t *name;
uint8_t fonttype;
uint8_t spacing;
uint16_t blocksize;
uint16_t *charlist;
uint8_t *data;
glyph_t *glyphs[NUM_CHARS];
uint8_t height;
} font_t;
struct dirname_s {
char *str;
SLIST_ENTRY(dirname_s) stuff;
};
2018-06-27 04:00:22 +00:00
const char *charlist = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO"
"PQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
2018-06-23 06:44:33 +00:00
opt_t opt;
2018-06-23 06:44:33 +00:00
void usage(void);
font_t *loadfont(char *fn);
void readchar(int i, glyph_t *glyph, font_t *font);
void ibmtoutf8(char *a, char *u);
void printcolor(uint8_t color);
int lookupchar(char c, const font_t *font);
void printcell(char *utfchar, uint8_t color);
void printrow(const glyph_t *glyph, int row);
void printstr(const char *str, font_t *font);
void
usage(void)
{
fprintf(stderr, "usage: tdfiglet [options] input\n");
2018-06-23 06:44:33 +00:00
fprintf(stderr, "\n");
2018-07-14 21:23:43 +00:00
fprintf(stderr, " -f [font] Specify font file used.\n");
fprintf(stderr, " -j l|r|c Justify left, right, or center. Default is left.\n");
fprintf(stderr, " -w n Set screen width. Default is 80.\n");
fprintf(stderr, " -c a|m Color format ANSI or mirc. Default is ANSI.\n");
fprintf(stderr, " -e u|a Encode as unicode or ASCII. Default is unicode.\n");
fprintf(stderr, " -i Print font details.\n");
fprintf(stderr, " -r Use random font.\n");
2018-07-14 21:23:43 +00:00
fprintf(stderr, " -h Print usage.\n");
2018-06-24 04:21:29 +00:00
fprintf(stderr, "\n");
2018-06-23 06:44:33 +00:00
exit(EX_USAGE);
}
int
main(int argc, char *argv[])
{
font_t *font = NULL;
int o;
opt.justify = LEFT_JUSTIFY;
opt.width = 80;
2018-06-24 05:30:56 +00:00
opt.info = false;
opt.encoding = ENC_UNICODE;
opt.random = false;
2018-07-14 21:23:43 +00:00
char *fontfile = NULL;
2018-06-23 06:44:33 +00:00
2018-08-24 07:20:33 +00:00
struct timeval tv;
DIR *d;
struct dirent *dir;
SLIST_HEAD(, dirname_s) head = SLIST_HEAD_INITIALIZER(dirname_s);
SLIST_INIT(&head);
struct dirname_s *dp;
int r = 0;
int dll = 0;
while((o = getopt(argc, argv, "f:w:j:c:e:ir")) != -1) {
2018-06-23 06:44:33 +00:00
switch (o) {
2018-07-14 21:23:43 +00:00
case 'f':
fontfile = optarg;
break;
2018-06-23 06:44:33 +00:00
case 'w':
opt.width = atoi(optarg);
break;
case 'j':
switch (optarg[0]) {
case 'l':
opt.justify = LEFT_JUSTIFY;
break;
case 'r':
opt.justify = RIGHT_JUSTIFY;
break;
case 'c':
opt.justify = CENTER_JUSTIFY;
break;
default:
usage();
}
break;
2018-06-24 04:21:29 +00:00
case 'c':
switch (optarg[0]) {
case 'a':
opt.color = COLOR_ANSI;
break;
case 'm':
opt.color = COLOR_MIRC;
break;
default:
usage();
}
break;
case 'e':
switch (optarg[0]) {
case 'a':
opt.encoding = ENC_ANSI;
break;
case 'u':
opt.encoding = ENC_UNICODE;
break;
default:
usage();
}
break;
2018-06-24 05:30:56 +00:00
case 'i':
opt.info = true;
break;
case 'r':
opt.random = true;
break;
2018-07-14 21:03:50 +00:00
case 'h':
/* fallthrough */
2018-06-23 06:44:33 +00:00
default:
usage();
}
}
argc -= optind;
argv += optind;
2018-08-06 21:28:19 +00:00
if (argc < 1) {
usage();
}
2018-07-14 21:23:43 +00:00
if (!fontfile) {
if (!opt.random) {
fontfile = DEFAULT_FONT;
} else {
d = opendir(FONT_DIR);
if (!d) {
fprintf(stderr, "Error: unable to read %s\n",
FONT_DIR);
exit(1);
}
2018-07-14 21:23:43 +00:00
while ((dir = readdir(d))) {
if (strstr(dir->d_name, FONT_EXT)) {
dp = malloc(sizeof(struct dirname_s));
dp->str = calloc(1, 1024);
strcpy(dp->str, dir->d_name);
SLIST_INSERT_HEAD(&head, dp, stuff);
dll++;
}
}
closedir(d);
2018-08-24 07:20:33 +00:00
gettimeofday(&tv, NULL);
srand(tv.tv_usec);
r = dll ? rand() % dll : 0;
dp = SLIST_FIRST(&head);
for (int i = 0; i < r; i++) {
dp = SLIST_NEXT(dp, stuff);
}
2018-06-23 06:44:33 +00:00
if (dp && dp->str) {
fontfile = dp->str;
} else {
fontfile = DEFAULT_FONT;
}
}
2018-06-23 06:44:33 +00:00
}
font = loadfont(fontfile);
2018-06-23 06:44:33 +00:00
printf("\n");
2018-07-14 21:23:43 +00:00
for (int i = 0; i < argc; i++) {
2018-06-23 06:44:33 +00:00
printstr(argv[i], font);
printf("\n");
}
return(0);
}
font_t
*loadfont(char *fn_arg)
{
2018-06-23 06:44:33 +00:00
font_t *font;
uint8_t *map = NULL;
int fd;
struct stat st;
size_t len;
uint8_t *p;
2018-07-14 20:57:54 +00:00
char *fn = NULL;
2018-06-23 06:44:33 +00:00
2018-06-27 04:00:22 +00:00
const char *magic = "\x13TheDraw FONTS file\x1a";
2018-07-14 20:57:54 +00:00
if (!strchr(fn_arg, '/')) {
if (strchr(fn_arg, '.')) {
fn = malloc(strlen(FONT_DIR) + strlen(fn_arg) + 2);
2018-07-14 20:57:54 +00:00
sprintf(fn, "%s/%s", FONT_DIR, fn_arg);
} else {
fn = malloc(strlen(FONT_DIR) + strlen(fn_arg) + \
strlen(FONT_EXT) + 3);
2018-07-14 20:57:54 +00:00
sprintf(fn, "%s/%s.%s", FONT_DIR, fn_arg, FONT_EXT);
}
} else {
fn = malloc(strlen(fn_arg) + 1);
sprintf(fn, "%s", fn_arg);
}
2018-06-23 06:44:33 +00:00
2018-07-14 20:57:54 +00:00
if (fn == NULL) {
perror(NULL);
exit(EX_OSERR);
2018-06-24 05:30:56 +00:00
}
2018-06-23 06:44:33 +00:00
2018-07-14 20:57:54 +00:00
fd = open(fn, O_RDONLY);
2018-06-23 06:44:33 +00:00
if (fd < 0) {
perror(NULL);
exit(EX_NOINPUT);
}
2018-07-14 20:57:54 +00:00
if (opt.info) {
printf("file: %s\n", fn);
}
font = malloc(sizeof(font_t));
2018-08-06 21:28:19 +00:00
if (fstat(fd, &st)) {
2018-06-25 01:41:42 +00:00
perror(NULL);
exit(EX_OSERR);
}
2018-06-23 06:44:33 +00:00
len = st.st_size;
map = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);
2018-06-25 01:41:42 +00:00
if (!map) {
perror(NULL);
exit(EX_OSERR);
}
2018-06-23 06:44:33 +00:00
close(fd);
if (!font) {
perror(NULL);
exit(EX_OSERR);
}
font->namelen = map[24];
font->name = &map[25];
font->fonttype = map[41];
font->spacing = map[42];
font->blocksize = (uint16_t)map[43];
font->charlist = (uint16_t *)&map[45];
font->data = &map[233];
font->height = 0;
if (strncmp(magic, (const char *)map, strlen(magic)) || font->fonttype != COLOR_FNT) {
fprintf(stderr, "Invalid font file: %s\n", fn);
2018-06-23 06:44:33 +00:00
exit(EX_NOINPUT);
}
free(fn);
2018-06-24 05:30:56 +00:00
if (opt.info) {
printf("font: %s\nchar list: ", font->name);
}
2018-06-23 06:44:33 +00:00
2018-06-27 04:00:22 +00:00
for (int i = 0; i < NUM_CHARS; i++) {
/* check for invalid glyph addresses */
if (charlist[i] + &map[233] > map + st.st_size) {
perror(NULL);
exit(EX_NOINPUT);
}
2018-06-23 06:44:33 +00:00
if (lookupchar(charlist[i], font) > -1) {
2018-06-24 05:30:56 +00:00
if (opt.info) {
printf("%c", charlist[i]);
}
2018-06-23 06:44:33 +00:00
p = font->data + font->charlist[i] + 2;
if (*p > font->height) {
font->height = *p;
}
}
2018-06-27 04:00:22 +00:00
}
2018-06-23 06:44:33 +00:00
2018-06-24 05:30:56 +00:00
if (opt.info) {
printf("\n");
}
2018-06-23 06:44:33 +00:00
for (int i = 0; i < NUM_CHARS; i++) {
if (lookupchar(charlist[i], font) != -1) {
2018-06-23 06:44:33 +00:00
font->glyphs[i] = calloc(1, sizeof(glyph_t));
if (!font->glyphs[i]) {
perror(NULL);
exit(EX_OSERR);
}
readchar(i, font->glyphs[i], font);
} else {
font->glyphs[i] = NULL;
}
}
return font;
}
void
readchar(int i, glyph_t *glyph, font_t *font)
{
if (font->charlist[i] == 0xffff) {
printf("char not found\n");
return;
}
uint8_t *p = font->data + font->charlist[i];
uint8_t ch;
uint8_t color;
glyph->width = *p;
p++;
glyph->height = *p;
p++;
int row = 0;
int col = 0;
int width = glyph->width;
int height = glyph->height;
if (height > font->height) {
font->height = height;
}
glyph->cell = calloc(width * font->height, sizeof(cell_t));
if (!glyph->cell) {
perror(NULL);
exit(EX_OSERR);
}
for (int i = 0; i < width * font->height; i++) {
2018-06-23 07:34:02 +00:00
glyph->cell[i].utfchar[0] = ' ';
2018-06-23 06:44:33 +00:00
glyph->cell[i].color = 0;
}
while (*p) {
ch = *p;
p++;
if (ch == '\r') {
2018-06-24 08:27:18 +00:00
ch = ' ';
2018-06-23 06:44:33 +00:00
row++;
col = 0;
} else {
2018-06-24 08:27:18 +00:00
color = *p;
p++;
2018-06-23 06:44:33 +00:00
#ifdef DEBUG
if (ch == 0x09)
ch = 'T';
if (ch < 0x20)
ch = '?';
#else
if (ch < 0x20)
ch = ' ';
#endif /* DEBUG */
2018-06-24 04:21:29 +00:00
if (opt.encoding == ENC_UNICODE) {
ibmtoutf8((char *)&ch,
glyph->cell[row * width + col].utfchar);
} else {
glyph->cell[row * width + col].utfchar[0] = ch;
}
2018-06-23 06:44:33 +00:00
glyph->cell[row * width + col].color = color;
col++;
}
}
}
int
lookupchar(char c, const font_t *font)
{
for (int i = 0; i < NUM_CHARS; i++) {
if (charlist[i] == c && font->charlist[i] != 0xffff)
2018-06-23 06:44:33 +00:00
return i;
}
return -1;
}
void
ibmtoutf8(char *a, char *u)
{
static iconv_t conv = (iconv_t)0;
size_t inchsize = 1;
size_t outchsize = MAX_UTFSTR;
if (!conv) {
conv = iconv_open("UTF-8", "CP437");
}
iconv(conv, &a, &inchsize, &u, &outchsize);
return;
}
void
printcolor(uint8_t color)
{
2018-06-27 04:00:22 +00:00
2018-06-23 06:44:33 +00:00
uint8_t fg = color & 0x0f;
uint8_t bg = (color & 0xf0) >> 4;
2018-08-06 21:28:19 +00:00
/* thedraw colors BRT BRT BRT BRT BRT BRT BRT BRT */
/* thedraw colors BLK BLU GRN CYN RED MAG BRN GRY BLK BLU GRN CYN RED PNK YLW WHT */
2018-06-27 04:00:22 +00:00
uint8_t fgacolors[] = {30, 34, 32, 36, 31, 35, 33, 37, 90, 94, 92, 96, 91, 95, 93, 97};
uint8_t bgacolors[] = {40, 44, 42, 46, 41, 45, 43, 47};
uint8_t fgmcolors[] = { 1, 2, 3, 10, 5, 6, 7, 15, 14, 12, 9, 11, 4, 13, 8, 0};
uint8_t bgmcolors[] = { 1, 2, 3, 10, 5, 6, 7, 15, 14, 12, 9, 11, 4, 13, 8, 0};
2018-06-24 04:21:29 +00:00
if (opt.color == COLOR_ANSI) {
printf("\x1b[");
printf("%d;", fgacolors[fg]);
printf("%dm", bgacolors[bg]);
} else {
printf("\x03");
printf("%d,", fgmcolors[fg]);
printf("%d", bgmcolors[bg]);
}
2018-06-23 06:44:33 +00:00
}
void
printrow(const glyph_t *glyph, int row)
{
char *utfchar;
uint8_t color;
int i;
2018-06-24 11:39:33 +00:00
uint8_t lastcolor;
2018-06-24 04:21:29 +00:00
2018-06-23 06:44:33 +00:00
for (i = 0; i < glyph->width; i++) {
2018-08-06 21:28:19 +00:00
utfchar = glyph->cell[glyph->width * row + i].utfchar;
2018-06-23 06:44:33 +00:00
color = glyph->cell[glyph->width * row + i].color;
2018-06-24 11:39:33 +00:00
2018-06-24 23:25:58 +00:00
if (i == 0 || color != lastcolor) {
2018-06-24 11:39:33 +00:00
printcolor(color);
lastcolor = color;
}
2018-06-23 06:44:33 +00:00
printf("%s", utfchar);
}
2018-06-24 04:21:29 +00:00
if (opt.color == COLOR_ANSI) {
printf("\x1b[0m");
} else {
printf("\x03");
}
2018-06-23 06:44:33 +00:00
}
void
printstr(const char *str, font_t *font)
{
int maxheight = 0;
int linewidth = 0;
int len = strlen(str);
int padding = 0;
int n = 0;
2018-06-23 06:44:33 +00:00
for (int i = 0; i < len; i++) {
glyph_t *g;
n = lookupchar(str[i], font);
if (n == -1) {
continue;
}
g = font->glyphs[n];
2018-06-23 06:44:33 +00:00
if (g->height > maxheight) {
maxheight = g->height;
}
linewidth += g->width;
if (linewidth + 1 < len) {
linewidth += font->spacing;
}
}
if (opt.justify == CENTER_JUSTIFY) {
padding = (opt.width - linewidth) / 2;
} else if (opt.justify == RIGHT_JUSTIFY) {
padding = (opt.width - linewidth);
}
for (int i = 0; i < maxheight; i++) {
for (int i = 0; i < padding; ++i) {
printf(" ");
}
for (int c = 0; c < strlen(str); c++) {
n = lookupchar(str[c], font);
if (n == -1) {
continue;
}
glyph_t *g = font->glyphs[n];
2018-06-23 06:44:33 +00:00
printrow(g, i);
2018-06-24 04:21:29 +00:00
if (opt.color == COLOR_ANSI) {
printf("\x1b[0m");
} else {
printf("\x03");
}
2018-06-24 08:27:18 +00:00
for (int s = 0; s < font->spacing; s++) {
printf(" ");
}
2018-06-24 04:21:29 +00:00
}
2018-06-24 08:27:18 +00:00
2018-06-24 04:21:29 +00:00
if (opt.color == COLOR_ANSI) {
printf("\x1b[0m\n");
} else {
2018-06-24 11:39:33 +00:00
printf("\x03\r\n");
2018-06-23 06:44:33 +00:00
}
}
}