diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2014-07-18 17:34:03 +0200 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2014-07-18 17:45:33 +0200 |
commit | 86db5dfb6d334e583ea4161191754522ce850eed (patch) | |
tree | 5abc777472cb907b954f63e8523e95dfa494e974 /src/libsystemd-terminal/unifont.c | |
parent | 545149a2fc3523bbcbf3703fae3ae1ca5312f8c8 (diff) |
terminal: add unifont font-handling
The unifont layer of libsystemd-terminal provides a fallback font for
situations where no system-fonts are available, or if you don't want to
deal with traditional font-formats for some reasons.
The unifont API mmaps a pre-compiled bitmap font that was generated out of
GNU-Unifont font-data. This guarantees, that all users of the font will
share the pages in memory. Furthermore, the layout of the binary file
allows accessing glyph data in O(1) without pre-rendering glyphs etc. That
is, the OS can skip loading pages for glyphs that we never access.
Note that this is currently a test-run and we want to include the binary
file in the GNU-Unifont package. However, until it was considered stable
and accepted by the maintainers, we will ship it as part of systemd. So
far it's only enabled with the experimental --enable-terminal, anyway.
Diffstat (limited to 'src/libsystemd-terminal/unifont.c')
-rw-r--r-- | src/libsystemd-terminal/unifont.c | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/src/libsystemd-terminal/unifont.c b/src/libsystemd-terminal/unifont.c new file mode 100644 index 0000000000..9e0f718665 --- /dev/null +++ b/src/libsystemd-terminal/unifont.c @@ -0,0 +1,211 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* + * Unifont + * This implements the unifont glyph-array parser and provides it via a simple + * API to the caller. No heavy transformations are performed so glyph-lookups + * stay as fast as possible. + */ + +#include <endian.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include "macro.h" +#include "unifont-def.h" +#include "unifont-internal.h" +#include "util.h" + +struct unifont { + unsigned long ref; + + int fd; + const uint8_t *map; + size_t size; + + unifont_header header; + const void *glyphs; /* unaligned! */ + size_t n_glyphs; + size_t glyphsize; +}; + +static int unifont_fetch_header(unifont *u) { + unifont_header h = { }; + uint64_t glyphsize; + + if (u->size < UNIFONT_HEADER_SIZE_MIN) + return -EBFONT; + + assert_cc(sizeof(h) >= UNIFONT_HEADER_SIZE_MIN); + memcpy(&h, u->map, UNIFONT_HEADER_SIZE_MIN); + + h.compatible_flags = le32toh(h.compatible_flags); + h.incompatible_flags = le32toh(h.incompatible_flags); + h.header_size = le32toh(h.header_size); + h.glyph_header_size = le16toh(h.glyph_header_size); + h.glyph_stride = le16toh(h.glyph_stride); + h.glyph_body_size = le64toh(h.glyph_body_size); + + if (memcmp(h.signature, "DVDHRMUF", 8)) + return -EBFONT; + if (h.incompatible_flags != 0) + return -EBFONT; + if (h.header_size < UNIFONT_HEADER_SIZE_MIN || h.header_size > u->size) + return -EBFONT; + if (h.glyph_header_size + h.glyph_body_size < h.glyph_header_size) + return -EBFONT; + if (h.glyph_stride * 16ULL > h.glyph_body_size) + return -EBFONT; + + glyphsize = h.glyph_header_size + h.glyph_body_size; + + if (glyphsize == 0 || glyphsize > u->size - h.header_size) { + u->n_glyphs = 0; + } else { + u->glyphs = u->map + h.header_size; + u->n_glyphs = (u->size - h.header_size) / glyphsize; + u->glyphsize = glyphsize; + } + + memcpy(&u->header, &h, sizeof(h)); + return 0; +} + +static int unifont_fetch_glyph(unifont *u, unifont_glyph_header *out_header, const void **out_body, uint32_t ucs4) { + unifont_glyph_header glyph_header = { }; + const void *glyph_body = NULL; + const uint8_t *p; + + if (ucs4 >= u->n_glyphs) + return -ENOENT; + + p = u->glyphs; + + /* copy glyph-header data */ + p += ucs4 * u->glyphsize; + memcpy(&glyph_header, p, MIN(sizeof(glyph_header), u->header.glyph_header_size)); + + /* copy glyph-body pointer */ + p += u->header.glyph_header_size; + glyph_body = p; + + if (glyph_header.width < 1) + return -ENOENT; + if (glyph_header.width > u->header.glyph_stride) + return -EBFONT; + + memcpy(out_header, &glyph_header, sizeof(glyph_header)); + *out_body = glyph_body; + return 0; +} + +int unifont_new(unifont **out) { + _cleanup_(unifont_unrefp) unifont *u = NULL; + struct stat st; + int r; + + assert_return(out, -EINVAL); + + u = new0(unifont, 1); + if (!u) + return -ENOMEM; + + u->ref = 1; + u->fd = -1; + u->map = MAP_FAILED; + + u->fd = open(UNIFONT_PATH, O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (u->fd < 0) + return -errno; + + r = fstat(u->fd, &st); + if (r < 0) + return -errno; + + u->size = st.st_size; + u->map = mmap(NULL, u->size, PROT_READ, MAP_PRIVATE, u->fd, 0); + if (u->map == MAP_FAILED) + return -errno; + + r = unifont_fetch_header(u); + if (r < 0) + return r; + + *out = u; + u = NULL; + return 0; +} + +unifont *unifont_ref(unifont *u) { + if (!u || !u->ref) + return NULL; + + ++u->ref; + + return u; +} + +unifont *unifont_unref(unifont *u) { + if (!u || !u->ref || --u->ref) + return NULL; + + if (u->map != MAP_FAILED) + munmap((void*)u->map, u->size); + u->fd = safe_close(u->fd); + free(u); + + return NULL; +} + +unsigned int unifont_get_stride(unifont *u) { + assert(u); + + return u->header.glyph_stride; +} + +int unifont_lookup(unifont *u, unifont_glyph *out, uint32_t ucs4) { + unifont_glyph_header h = { }; + const void *b = NULL; + unifont_glyph g = { }; + int r; + + assert_return(u, -EINVAL); + + r = unifont_fetch_glyph(u, &h, &b, ucs4); + if (r < 0) + return r; + + g.width = h.width * 8U; + g.height = 16U; + g.stride = u->header.glyph_stride; + g.cwidth = h.width; + g.data = b; + + if (out) + memcpy(out, &g, sizeof(g)); + return 0; +} |