aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore8
-rw-r--r--LICENSE15
-rw-r--r--Makefile45
-rw-r--r--README1
-rw-r--r--config.mk8
-rw-r--r--demo.c203
-rw-r--r--libparsepsf.c470
-rw-r--r--libparsepsf.h27
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c44b2d9
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/README b/README
new file mode 100644
index 0000000..96de825
--- /dev/null
+++ b/README
@@ -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
diff --git a/demo.c b/demo.c
new file mode 100644
index 0000000..dcefc36
--- /dev/null
+++ b/demo.c
@@ -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