From 513ee4f15567048b619848edb092cdc4f33fe1c0 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Fri, 13 Aug 2021 16:57:18 +0200 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 8 + LICENSE | 15 ++ Makefile | 45 ++++++ README | 1 + config.mk | 8 + demo.c | 203 +++++++++++++++++++++++++ libparsepsf.c | 470 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ libparsepsf.h | 27 ++++ 8 files changed, 777 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 config.mk create mode 100644 demo.c create mode 100644 libparsepsf.c create mode 100644 libparsepsf.h 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 + +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 +#include +#include +#include + + +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 +#include +#include +#include +#include + +#include + + +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 +#include + + +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 -- cgit v1.2.3-70-g09d2