diff options
-rw-r--r-- | .gitignore | 8 | ||||
-rw-r--r-- | LICENSE | 15 | ||||
-rw-r--r-- | Makefile | 45 | ||||
-rw-r--r-- | README | 1 | ||||
-rw-r--r-- | config.mk | 8 | ||||
-rw-r--r-- | demo.c | 203 | ||||
-rw-r--r-- | libparsepsf.c | 470 | ||||
-rw-r--r-- | libparsepsf.h | 27 |
8 files changed, 777 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9f3a77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*\#* +*~ +*.o +*.a +*.so +*.su +*.lo +/demo @@ -0,0 +1,15 @@ +ISC License + +© 2021 Mattias Andrée <maandree@kth.se> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..61b281b --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ + libparsepsf.o + +LIB_HDR =\ + libparsepsf.h + +HDR =\ + $(LIB_HDR) + +all: libparsepsf.a demo +$(OBJ): $(@:.o=.c) $(HDR) +libparsepsf.o: libparsepsf.c $(LIB_HDR) + +libparsepsf.a: $(OBJ) + $(AR) rc $@ $(OBJ) + $(AR) ts $@ > /dev/null + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +demo: demo.o libparsepsf.a + $(CC) -o $@ $@.o libparsepsf.a $(LDFLAGS) + +install: libparsepsf.a + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/include" + cp -- libparsepsf.a "$(DESTDIR)$(PREFIX)/lib" + cp -- libparsepsf.h "$(DESTDIR)$(PREFIX)/include" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libparsepsf.a" + -rm -f -- "$(DESTDIR)$(PREFIX)/include/libparsepsf.h" + +clean: + -rm -f -- *.o *.lo *.su *.a *.so *.so.* demo + +.SUFFIXES: +.SUFFIXES: .c .o + +.PHONY: all install uninstall clean @@ -0,0 +1 @@ +libparsepsf is a C library for interpreting PSF (PC Screen Font) files. diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..941bc29 --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = -std=c99 -Wall +LDFLAGS = -lgrapheme + +CC = cc @@ -0,0 +1,203 @@ +/* See LICENSE file for copyright and license details. */ +#include "libparsepsf.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +static size_t +readfile(int fd, char **datap, size_t *sizep) +{ + size_t len = 0; + ssize_t r; + for (;;) { + if (len == *sizep) { + *datap = realloc(*datap, *sizep += 4096); + if (!*datap) { + perror("realloc"); + exit(1); + } + } + r = read(fd, &(*datap)[len], *sizep - len); + if (r <= 0) { + if (!r) + break; + perror("read"); + exit(1); + } + len += (size_t)r; + } + return len; +} + + +static uint8_t * +genreplacement(const struct libparsepsf_font *font) +{ + size_t glyph, size, i, linesize, xoff, yoff; + int invert = 0, round = 0; + uint8_t *data, xbit; + + glyph = libparsepsf_get_glyph(font, "�", NULL, NULL); + if (!glyph) { + invert = 1; + round = 1; + glyph = libparsepsf_get_glyph(font, "?", NULL, NULL); + if (!glyph) { + round = 0; + glyph = libparsepsf_get_glyph(font, " ", NULL, NULL); + } + } + glyph -= (glyph ? 1 : 0); + + linesize = font->width / 8 + (font->width % 8 ? 1 : 0); + size = linesize * font->height; + data = malloc(size); + if (!data) { + perror("malloc"); + exit(1); + } + + memcpy(data, &font->glyph_data[glyph * size], size); + if (invert) + for (i = 0; i < size; i++) + data[i] ^= 0xFF; + if (round) { + yoff = (font->height - 1) * linesize; + xoff = (font->width - 1) / 8; + xbit = 0x80 >> ((font->width - 1) % 8); + data[0] ^= 0x80; + data[yoff] ^= 0x80; + data[xoff] ^= xbit; + data[yoff + xoff] ^= xbit; + } + + return data; +} + + +static void +printglyphrow(const struct libparsepsf_font *font, const uint8_t *gd, const char *bg, const char *fg) +{ + uint8_t bit; + size_t x; + + bit = 0x80; + for (x = 0; x < font->width; x++) { + printf("%s", (*gd & bit) ? fg : bg); + bit >>= 1; + if (!bit) { + bit = 0x80; + gd = &gd[1]; + } + } +} + + +static void +printglyph(const struct libparsepsf_font *font, size_t glyph, const char *bg, const char *fg) +{ + const uint8_t *gd; + size_t width = font->width / 8 + (font->width % 8 ? 1 : 0); + size_t glyphsize = width * font->height; + size_t y; + + if (glyph > font->num_glyphs) { + fprintf(stderr, "glyph does not exist!\n"); + exit(1); + } + + for (y = 0; y < font->height; y++) { + gd = &font->glyph_data[glyph * glyphsize + y * width]; + printglyphrow(font, gd, bg, fg); + printf("\033[0m\n"); + } +} + + +static void +printline(const struct libparsepsf_font *font, const char *line, size_t linelen) +{ + const char *c, *bg, *fg; + const uint8_t *gd; + size_t width = font->width / 8 + (font->width % 8 ? 1 : 0); + size_t glyphsize = width * font->height; + size_t glyph, rem, y; + uint8_t *replacement = genreplacement(font); + + for (y = 0; y < font->height; y++) { + rem = linelen; + for (c = line; rem;) { + glyph = libparsepsf_get_glyph(font, c, &rem, &c); + if (!glyph) { + do { + c = &c[1]; + rem -= 1; + } while ((*c & 0xC0) == 0x80); + bg = "\033[1;30;40m[]"; + fg = "\033[1;31;41m[]"; + gd = &replacement[y * width]; + } else { + glyph -= 1; + bg = "\033[1;30;40m[]"; + fg = "\033[1;37;47m[]"; + gd = &font->glyph_data[glyph * glyphsize + y * width]; + } + printglyphrow(font, gd, bg, fg); + } + printf("\033[0m\n"); + } + + free(replacement); +} + + +int +main(int argc, char *argv[]) +{ + unsigned long int glyph; + uint32_t uver; + char *data = NULL; + size_t len, size = 0; + struct libparsepsf_font font; + + if (argc) { + argc--; + argv++; + } + + len = readfile(STDIN_FILENO, &data, &size); + if (libparsepsf_parse_font(data, len, &font, &uver)) { + perror("libparsepsf_parse_font"); + free(data); + exit(1); + } + free(data); + + if (uver) { + fprintf(stderr, "WARNING: Font format version is not fully supported: " + "version 2.%lu\n", (unsigned long int)uver); + } + + printf("#glyphs = %zu\n", font.num_glyphs); + printf("width = %zu\n", font.width); + printf("height = %zu\n", font.height); + + if (!argc) { + printline(&font, "", 1); + } else if (argc == 2 && !strcmp(argv[0], "-g")) { + glyph = strtoul(argv[1], NULL, 0); + printglyph(&font, (size_t)glyph, "\033[1;30;40m[]", "\033[1;37;47m[]"); + } else if (argc == 2 && !strcmp(argv[0], "-x")) { + glyph = strtoul(argv[1], NULL, 16); + printglyph(&font, (size_t)glyph, "\033[1;30;40m[]", "\033[1;37;47m[]"); + } else { + for (; argc--; argv++) + printline(&font, *argv, strlen(*argv)); + } + + libparsepsf_destroy_font(&font); + return 0; +} diff --git a/libparsepsf.c b/libparsepsf.c new file mode 100644 index 0000000..7c7b655 --- /dev/null +++ b/libparsepsf.c @@ -0,0 +1,470 @@ +/* See LICENSE file for copyright and license details. */ +#include "libparsepsf.h" + +#include <errno.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <grapheme.h> + + +struct psf1_header { + uint8_t magic_x36; + uint8_t magic_x04; + uint8_t mode; +#define PSF1_MODE512 0x01 +#define PSF1_MODEHASTAB 0x02 +/* #define PSF1_MODEHASSEQ 0x04 // really used */ + uint8_t height; +}; +#define PSF1_SEPARATOR 0xFFFF +#define PSF1_STARTSEQ 0xFFFE + +struct psf2_header { + uint8_t magic_x72; + uint8_t magic_xb5; + uint8_t magic_x4a; + uint8_t magic_x86; + uint32_t version; +#define PSF2_MAXVERSION 0 + uint32_t header_size; + uint32_t flags; +#define PSF2_HAS_UNICODE_TABLE 0x01 + uint32_t num_glyphs; + uint32_t charsize; /* = height * ((width + 7) / 8) */ + uint32_t height; + uint32_t width; +}; +#define PSF2_SEPARATOR 0xFF +#define PSF2_STARTSEQ 0xFE + + +static void +free_map(struct libparsepsf_unimap *node) +{ + size_t i; + for (i = 0; i < sizeof(node->nonterminal) / sizeof(*node->nonterminal); i++) + if (node->nonterminal[i]) + free_map(node->nonterminal[i]); + free(node); +} + +void +libparsepsf_destroy_font(struct libparsepsf_font *font) +{ + free(font->glyph_data); + font->glyph_data = NULL; + if (font->map) { + free_map(font->map); + font->map = NULL; + } +} + + +static uint16_t +letoh16(const uint8_t *le) +{ + uint16_t b0 = (uint16_t)((uint16_t)le[0] << 0); + uint16_t b1 = (uint16_t)((uint16_t)le[1] << 8); + return (uint16_t)(b0 | b1); +} + + +static uint32_t +letoh32(uint32_t le) +{ + union { + uint32_t v; + uint8_t b[4]; + } u = {.v = le}; + uint32_t b0 = (uint32_t)((uint32_t)u.b[0] << 0); + uint32_t b1 = (uint32_t)((uint32_t)u.b[1] << 8); + uint32_t b2 = (uint32_t)((uint32_t)u.b[2] << 16); + uint32_t b3 = (uint32_t)((uint32_t)u.b[3] << 24); + return (uint32_t)(b0 | b1 | b2 | b3); +} + + +static uint32_t +desurrogate(uint16_t high, uint16_t low) +{ + /* high surrogate has lower value */ + uint32_t h = UINT32_C(0xD800) ^ (uint32_t)high; + uint32_t l = UINT32_C(0xDC00) ^ (uint32_t)low; + h <<= 10; + return (uint32_t)(h | l); +} + +static int +put_map_incomplete(struct libparsepsf_font *font, const uint8_t *seq, size_t seqlen, + uint8_t *savedp, struct libparsepsf_unimap **nodep) +{ + size_t i; + if (font->map == NULL) { + font->map = calloc(1, sizeof(*font->map)); + if (!font->map) + goto enomem; + } + if (!seqlen) + goto ebfont; + *nodep = font->map; + *savedp = seq[--seqlen]; + for (i = 0; i < seqlen; i++) { + if (!(*nodep)->nonterminal[seq[i]]) { + (*nodep)->nonterminal[seq[i]] = calloc(1, sizeof(*font->map)); + if (!(*nodep)->nonterminal[seq[i]]) + goto enomem; + } + *nodep = (*nodep)->nonterminal[seq[i]]; + } + + return 0; + +ebfont: + errno = EBFONT; + return -1; +enomem: + errno = ENOMEM; + return -1; +} + +static int +put_map_finalise(size_t index, uint8_t saved, struct libparsepsf_unimap *node) +{ + /* unfortunately this actually happens in the real world */ +#if 0 + if (node->terminal[saved]) { + return 0; +# if 0 + errno = EBFONT; + return -1; +# endif + } +#endif + + node->terminal[saved] = index + 1; + return 0; +} + +static int +put_map(struct libparsepsf_font *font, size_t index, const uint8_t *seq, size_t seqlen) +{ + uint8_t saved = 0xFF; + struct libparsepsf_unimap *node = NULL; + if (put_map_incomplete(font, seq, seqlen, &saved, &node)) + return -1; + return put_map_finalise(index, saved, node); +} + +static int +decode_utf8(const uint8_t *data, size_t size, size_t *np, uint32_t *cpp) +{ + uint8_t head; + uint32_t cp; + + head = *data; + *np = 1; + if (!(head & 0x80)) { + if (cpp) + *cpp = (uint32_t)head; + return 0; + } else if (!(head & 0x40)) { + return -1; + } + size--; + cp = (uint32_t)head; + head <<= 1; + + while (head & 0x80) { + head <<= 1; + if ((data[*np] & 0xC0) != 0x80) + return -1; + cp <<= 6; + cp |= (uint32_t)(data[*np] ^ 0x80); + *np += 1; + } + if (*np > 4) + return -1; + + cp &= (UINT32_C(1) << (*np * 5 + 1)) - 1; + if ((cp & UINT32_C(0xFFF800)) == UINT32_C(0xD800) || + cp > UINT32_C(0x10FFFF) || + cp < UINT32_C(1) << (*np == 2 ? 7 : *np * 5 - 4)) + return -1; + + if (cpp) + *cpp = cp; + return 0; +} + +int +libparsepsf_parse_font(const void *data, size_t size, struct libparsepsf_font *fontp, uint32_t *unrecognised_versionp) +{ + union { + struct psf1_header psf1; + struct psf2_header psf2; + } header; + const uint8_t *udata = data; + size_t glyphs_offset; + size_t charsize; + size_t off; + size_t i; + size_t n; + uint32_t u32; + uint16_t u16, u16b; + uint8_t u8, utf8[4], utf8_saved; + struct libparsepsf_unimap *utf8_node; + + *unrecognised_versionp = 0; + fontp->glyph_data = NULL; + fontp->map = NULL; + + if (size < 4) + goto ebfont; + + if (udata[0] == 0x36) { /* TODO untested */ + if (size < sizeof(header.psf1)) + goto ebfont; + memcpy(&header.psf1, udata, sizeof(header.psf1)); + if (header.psf1.magic_x36 != 0x36 || + header.psf1.magic_x04 != 0x04) + goto ebfont; + fontp->num_glyphs = (header.psf1.mode & PSF1_MODE512) ? 512 : 256; + fontp->height = (size_t)header.psf1.height; + fontp->width = 8; + glyphs_offset = sizeof(header.psf1); + charsize = fontp->height; + if (glyphs_offset > size || + !fontp->num_glyphs || + charsize > (size - glyphs_offset) / fontp->num_glyphs) + goto ebfont; + if (header.psf1.mode & PSF1_MODEHASTAB) { + off = glyphs_offset + fontp->num_glyphs * charsize; + for (i = 0; i < fontp->num_glyphs; i++) { + for (;;) { + if (off + 2 > size) + goto ebfont; + u16 = letoh16(&udata[off]); + off += 2; + if (u16 == PSF1_STARTSEQ) { + break; + } else if (u16 == PSF1_SEPARATOR) { + goto next_char_psf1; + } else if ((u16 & UINT32_C(0xF800)) == UINT32_C(0xD800)) { + if (off + 2 > size) + goto ebfont; + u16b = letoh16(&udata[off]); + off += 2; + if (((u16 ^ u16b) & 0xDC00) != 0x0400) + goto ebfont; + u32 = desurrogate(u16 < u16b ? u16 : u16b, + u16 < u16b ? u16b : u16); + } else { + u32 = (uint32_t)u16; + } + n = grapheme_cp_encode(u32, utf8, sizeof(utf8)); + if (n > sizeof(utf8)) + abort(); + if (put_map(fontp, i, utf8, n)) + goto fail; + } + utf8_saved = 0xFF; + utf8_node = NULL; + for (;;) { + if (off + 2 > size) + goto ebfont; + u16 = letoh16(&udata[off]); + off += 2; + if (u16 == PSF1_STARTSEQ || u16 == PSF1_SEPARATOR) { + if (put_map_finalise(i, utf8_saved, utf8_node)) + goto fail; + if (u16 == PSF1_SEPARATOR) + goto next_char_psf1; + utf8_saved = 0xFF; + utf8_node = NULL; + continue; + } else if ((u16 & UINT32_C(0xF800)) == UINT32_C(0xD800)) { + if (off + 2 > size) + goto ebfont; + u16b = letoh16(&udata[off]); + off += 2; + if (((u16 ^ u16b) & 0xDC00) != 0x0400) + goto ebfont; + u32 = desurrogate(u16 > u16b ? u16 : u16b, + u16 > u16b ? u16b : u16); + } else { + u32 = (uint32_t)u16; + } + n = grapheme_cp_encode(u32, utf8, sizeof(utf8)); + if (n > sizeof(utf8)) + abort(); + if (put_map_incomplete(fontp, utf8, n, &utf8_saved, &utf8_node)) + goto fail; + } + next_char_psf1:; + } + } + + } else { + if (size < sizeof(header.psf2)) + goto ebfont; + memcpy(&header.psf2, udata, sizeof(header.psf2)); + if (header.psf2.magic_x72 != 0x72 || + header.psf2.magic_xb5 != 0xb5 || + header.psf2.magic_x4a != 0x4a || + header.psf2.magic_x86 != 0x86) + goto ebfont; + header.psf2.version = letoh32(header.psf2.version); + header.psf2.header_size = letoh32(header.psf2.header_size); + header.psf2.flags = letoh32(header.psf2.flags); + header.psf2.num_glyphs = letoh32(header.psf2.num_glyphs); + header.psf2.charsize = letoh32(header.psf2.charsize); + header.psf2.height = letoh32(header.psf2.height); + header.psf2.width = letoh32(header.psf2.width); + if (header.psf2.height * ((header.psf2.width + 7) / 8) != header.psf2.charsize) + goto ebfont; + if (header.psf2.version > PSF2_MAXVERSION) + *unrecognised_versionp = 0; + fontp->num_glyphs = (size_t)header.psf2.num_glyphs; + fontp->height = (size_t)header.psf2.height; + fontp->width = (size_t)header.psf2.width; + glyphs_offset = (size_t)header.psf2.header_size; + charsize = (size_t)header.psf2.charsize; + if (glyphs_offset > size || + !fontp->num_glyphs || + charsize > (size - glyphs_offset) / fontp->num_glyphs) + goto ebfont; + if (header.psf2.flags & PSF2_HAS_UNICODE_TABLE) { + off = glyphs_offset + fontp->num_glyphs * charsize; + for (i = 0; i < fontp->num_glyphs; i++) { + for (;;) { + if (off == size) + goto ebfont; + u8 = udata[off]; + if (u8 == PSF2_STARTSEQ) { + off += 1; + break; + } else if (u8 == PSF2_SEPARATOR) { + off += 1; + goto next_char_psf2; + } + if (decode_utf8(&udata[off], size - off, &n, NULL)) + goto ebfont; + if (put_map(fontp, i, &udata[off], n)) + goto fail; + off += n; + } + utf8_saved = 0xFF; + utf8_node = NULL; + for (;;) { + if (off == size) + goto ebfont; + u8 = udata[off]; + if (u8 == 0xFE || u8 == 0xFF) { + if (put_map_finalise(i, utf8_saved, utf8_node)) + goto fail; + off += 1; + if (u8 == 0xFF) + goto next_char_psf2; + utf8_saved = 0xFF; + utf8_node = NULL; + } else { + if (decode_utf8(&udata[off], size - off, &n, NULL)) + goto ebfont; + if (put_map_incomplete(fontp, &udata[off], n, &utf8_saved, &utf8_node)) + goto fail; + off += n; + } + } + next_char_psf2:; + } + } + } + + if (charsize) { + fontp->glyph_data = malloc(fontp->num_glyphs * charsize); + if (!fontp->glyph_data) + goto enomem; + } + memcpy(fontp->glyph_data, &udata[glyphs_offset], fontp->num_glyphs * charsize); + + return 0; + +enomem: + errno = ENOMEM; + goto fail; +ebfont: + errno = EBFONT; +fail: + libparsepsf_destroy_font(fontp); + return -1; +} + + +size_t +libparsepsf_get_glyph(const struct libparsepsf_font *font, const char *c, size_t *remp, const char **next_cp) +{ + size_t glyph = 0, rem = 0, n; + uint32_t cp; + struct libparsepsf_unimap *node = font->map; + + if (!node) { + if (!remp && !*c) + return 0; + if (decode_utf8((const uint8_t *)c, remp ? *remp : SIZE_MAX, &n, &cp)) { + errno = EILSEQ; + return 0; + } + if (remp) + rem = *remp - n; + c = &c[n]; + glyph = (size_t)cp; + if (glyph >= font->num_glyphs) + return 0; + glyph -= 1; + goto out; + + } else if (remp) { + rem = *remp; + if (!rem) + return 0; + for (; rem > 1; c = &c[1], rem -= 1) { + if (node->terminal[*(const uint8_t *)c]) { + glyph = node->terminal[*(const uint8_t *)c]; + if (next_cp) + *next_cp = &c[1]; + if (remp) + *remp = rem - 1; + } + node = node->nonterminal[*(const uint8_t *)c]; + if (!node) + return glyph; + } + + } else { + if (!c[0]) + return 0; + for (; c[1]; c = &c[1]) { + if (node->terminal[*(const uint8_t *)c]) { + glyph = node->terminal[*(const uint8_t *)c]; + if (next_cp) + *next_cp = &c[1]; + } + node = node->nonterminal[*(const uint8_t *)c]; + if (!node) + return glyph; + } + } + + glyph = node->terminal[*(const uint8_t *)c]; +out: + if (glyph) { + if (next_cp) + *next_cp = &c[1]; + if (remp) + *remp = rem - 1; + } + return glyph; +} diff --git a/libparsepsf.h b/libparsepsf.h new file mode 100644 index 0000000..b1984b4 --- /dev/null +++ b/libparsepsf.h @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef LIBPARSEPSF_H +#define LIBPARSEPSF_H + +#include <stdint.h> +#include <stddef.h> + + +struct libparsepsf_unimap { + struct libparsepsf_unimap *nonterminal[256]; + size_t terminal[256]; /* index + 1, 0 if not used */ +}; + +struct libparsepsf_font { + size_t num_glyphs; + size_t height; + size_t width; + uint8_t *glyph_data; + struct libparsepsf_unimap *map; +}; + + +void libparsepsf_destroy_font(struct libparsepsf_font *font); +int libparsepsf_parse_font(const void *data, size_t size, struct libparsepsf_font *fontp, uint32_t *unrecognised_versionp); +size_t libparsepsf_get_glyph(const struct libparsepsf_font *font, const char *c, size_t *remp, const char **next_cp); + +#endif |