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.
This commit is contained in:
David Herrmann 2014-07-18 17:34:03 +02:00
parent 545149a2fc
commit 86db5dfb6d
9 changed files with 64159 additions and 2 deletions

1
.gitignore vendored
View File

@ -225,6 +225,7 @@
/test-time
/test-tmpfiles
/test-udev
/test-unifont
/test-unit-file
/test-unit-name
/test-utf8

View File

@ -2838,9 +2838,15 @@ noinst_LTLIBRARIES += \
noinst_PROGRAMS += \
systemd-subterm
unifontdatadir=$(datadir)/unifont
dist_unifontdata_DATA = \
src/libsystemd-terminal/unifont-glyph-array.bin
tests += \
test-term-page \
test-term-parser
test-term-parser \
test-unifont
endif
libsystemd_terminal_la_CFLAGS = \
@ -2852,7 +2858,9 @@ libsystemd_terminal_la_SOURCES = \
src/libsystemd-terminal/term-page.c \
src/libsystemd-terminal/term-parser.c \
src/libsystemd-terminal/term-screen.c \
src/libsystemd-terminal/term-wcwidth.c
src/libsystemd-terminal/term-wcwidth.c \
src/libsystemd-terminal/unifont-internal.h \
src/libsystemd-terminal/unifont.c
libsystemd_terminal_la_LIBADD = \
libsystemd-internal.la \
@ -2882,6 +2890,20 @@ test_term_parser_LDADD = \
libsystemd-internal.la \
libsystemd-shared.la
test_unifont_SOURCES = \
src/libsystemd-terminal/test-unifont.c
test_unifont_LDADD = \
libsystemd-terminal.la \
libsystemd-internal.la \
libsystemd-shared.la
update-unifont:
$(AM_V_GEN)cat $(top_srcdir)/src/libsystemd-terminal/unifont.hex | $(PYTHON) $(top_srcdir)/tools/compile-unifont.py >$(top_srcdir)/src/libsystemd-terminal/unifont-glyph-array.bin
@echo "unifont-glyph-array.bin has been regenerated"
.PHONY: update-unifont
# ------------------------------------------------------------------------------
if ENABLE_GTK_DOC
SUBDIRS += \

View File

@ -0,0 +1,128 @@
/*-*- 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/>.
***/
/*
* Test Unifont Helper
* This tries opening the binary unifont glyph-array and renders some glyphs.
* The glyphs are then compared to hard-coded glyphs.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "macro.h"
#include "unifont-def.h"
#include "unifont-internal.h"
#include "util.h"
static void render(char *w, const unifont_glyph *g) {
unsigned int i, j;
const uint8_t *d = g->data;
for (j = 0; j < 16; ++j) {
for (i = 0; i < 8 * g->cwidth; ++i) {
if (d[i / 8] & (1 << (7 - i % 8)))
*w++ = '#';
else
*w++ = ' ';
}
*w++ = '\n';
d += g->stride;
}
*w++ = 0;
}
static void test_unifont(void) {
char buf[4096];
unifont_glyph g;
unifont *u;
assert_se(unifont_new(&u) >= 0);
/* lookup invalid font */
assert_se(unifont_lookup(u, &g, 0xffffffffU) < 0);
/* lookup and render 'A' */
assert_se(unifont_lookup(u, &g, 'A') >= 0);
assert_se(g.width == 8);
assert_se(g.height == 16);
assert_se(g.stride >= 1);
assert_se(g.cwidth == 1);
assert_se(g.data != NULL);
render(buf, &g);
assert_se(!strcmp(buf,
" \n"
" \n"
" \n"
" \n"
" ## \n"
" # # \n"
" # # \n"
" # # \n"
" # # \n"
" ###### \n"
" # # \n"
" # # \n"
" # # \n"
" # # \n"
" \n"
" \n"
));
/* lookup and render '什' */
assert_se(unifont_lookup(u, &g, 0x4ec0) >= 0);
assert_se(g.width == 16);
assert_se(g.height == 16);
assert_se(g.stride >= 2);
assert_se(g.cwidth == 2);
assert_se(g.data != NULL);
render(buf, &g);
assert_se(!strcmp(buf,
" # # \n"
" # # \n"
" # # \n"
" # # \n"
" # # \n"
" ## # \n"
" ## ########## \n"
" # # # \n"
"# # # \n"
" # # \n"
" # # \n"
" # # \n"
" # # \n"
" # # \n"
" # # \n"
" # # \n"
));
unifont_unref(u);
}
int main(int argc, char **argv) {
if (access(UNIFONT_PATH, F_OK))
return 77;
test_unifont();
return 0;
}

View File

@ -0,0 +1,137 @@
/*-*- 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/>.
***/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "sparse-endian.h"
#include "util.h"
typedef struct unifont_header unifont_header;
typedef struct unifont_glyph_header unifont_glyph_header;
/*
* Unifont: On-disk data
* Conventional font-formats have the problem that you have to pre-render each
* glyph before you can use it. If you just need one glyph, you have to parse
* the font-file until you found that glyph.
* GNU-Unifont is a bitmap font with very good Unicode coverage. All glyphs are
* (n*8)x16 bitmaps. Our on-disk data stores all those glyphs pre-compiled with
* fixed offsets. Therefore, the font-file can be mmap()ed and all glyphs can
* be accessed in O(1) (because all glyphs have the same size and thus their
* offsets can be easily computed). This guarantees, that the kernel only loads
* the pages that are really accessed. Thus, we have a far lower overhead than
* traditional font-formats like BDF. Furthermore, the backing file is read-only
* and can be shared in memory between multiple users.
*
* The binary-format starts with a fixed header:
*
* | 2bytes | 2bytes | 2bytes | 2bytes |
*
* +-----------------------------------+
* | SIGNATURE | 8 bytes
* +-----------------+-----------------+
* | COMPAT FLAGS | INCOMPAT FLAGS | 8 bytes
* +-----------------+--------+--------+
* | HEADER SIZE |GH-SIZE |G-STRIDE| 8 bytes
* +-----------------+--------+--------+
* | GLYPH BODY SIZE | 8 bytes
* +-----------------------------------+
*
* * The 8 bytes signature must be set to the ASCII string "DVDHRMUF".
* * The 4 bytes compatible-flags field contains flags for new features that
* might be added in the future and which are compatible to older parsers.
* * The 4 bytes incompatible-flags field contains flags for new features that
* might be added in the future and which are incompatible to old parses.
* Thus, if you encounter an unknown bit set, you must abort!
* * The 4 bytes header-size field contains the size of the header in bytes. It
* must be at least 32 (the size of this fixed header). If new features are
* added, it might be increased. It can also be used to add padding to the
* end of the header.
* * The 2 bytes glyph-header-size field specifies the size of each glyph
* header in bytes (see below).
* * The 2 bytes glyph-stride field specifies the stride of each line of glyph
* data in "bytes per line".
* * The 8 byte glyph-body-size field defines the size of each glyph body in
* bytes.
*
* After the header, the file can contain padding bytes, depending on the
* header-size field. Everything beyond the header+padding is treated as a big
* array of glyphs. Each glyph looks like this:
*
* | 1 byte |
*
* +-----------------------------------+
* | WIDTH | 1 byte
* +-----------------------------------+
* ~ PADDING ~
* +-----------------------------------+
* ~ ~
* ~ ~
* ~ DATA ~
* ~ ~
* ~ ~
* +-----------------------------------+
*
* * The first byte specifies the width of the glyph. If it is 0, the glyph
* must be treated as non-existant.
* All glyphs are "8*n" pixels wide and "16" pixels high. The width-field
* specifies the width multiplier "n".
* * After the width field padding might be added. This depends on the global
* glyph-header-size field. It defines the total size of each glyph-header.
* After the glyph-header+padding, the data-field starts.
* * The data-field contains a byte-array of bitmap data. The array is always
* as big as specified in the global glyph-body-size header field. This might
* include padding.
* The array contains all 16 lines of bitmap information for that glyph. The
* stride is given in the global glyph-stride header field. This can be used
* to add padding after each line.
* Each line is encoded as 1 bit per pixel bitmap. That is, each byte encodes
* data for 8 pixels (left most pixel is encoded in the LSB, right most pixel
* in the MSB). The width field defines the number of bytes valid per line.
* For width==1, you need 1 byte to encode the 8 pixels. The stride defines
* where the encoding of the next line starts.
* Any data beyond the 16th line is padding and must be ignored.
*/
/* path to binary file */
#define UNIFONT_PATH "/usr/share/unifont/unifont-glyph-array.bin"
/* header-size of version 1 */
#define UNIFONT_HEADER_SIZE_MIN 32
struct unifont_header {
/* fields available in version 1 */
uint8_t signature[8];
le32_t compatible_flags;
le32_t incompatible_flags;
le32_t header_size;
le16_t glyph_header_size;
le16_t glyph_stride;
le64_t glyph_body_size;
} _packed_;
struct unifont_glyph_header {
/* fields available in version 1 */
uint8_t width;
} _packed_;

Binary file not shown.

View File

@ -0,0 +1,54 @@
/*-*- 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/>.
***/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "util.h"
typedef struct unifont unifont;
typedef struct unifont_glyph unifont_glyph;
/*
* Unifont
* The unifont API provides a glyph-lookup for bitmap fonts which can be used
* as fallback if no system-font is available or if you don't want to deal with
* full font renderers.
*/
struct unifont_glyph {
unsigned int width;
unsigned int height;
unsigned int stride;
unsigned int cwidth;
const void *data; /* unaligned! */
};
int unifont_new(unifont **out);
unifont *unifont_ref(unifont *u);
unifont *unifont_unref(unifont *u);
DEFINE_TRIVIAL_CLEANUP_FUNC(unifont*, unifont_unref);
unsigned int unifont_get_stride(unifont *u);
int unifont_lookup(unifont *u, unifont_glyph *out, uint32_t ucs4);

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

116
tools/compile-unifont.py Executable file
View File

@ -0,0 +1,116 @@
# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
#
# This file is part of systemd.
#
# Copyright 2013-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/>.
#
# Parse a unifont.hex file and produce a compressed binary-format.
#
from __future__ import print_function
import re
import sys
import fileinput
import struct
#
# Write "bits" array as binary output.
#
def write_bin_entry(entry):
l = len(entry)
if l != 32 and l != 64:
entry = "0" * 64
l = 0
elif l < 64:
entry += "0" * (64 - l)
sys.stdout.buffer.write(struct.pack('B', int(l / 32))) # width
sys.stdout.buffer.write(struct.pack('B', 0)) # padding
sys.stdout.buffer.write(struct.pack('H', 0)) # padding
sys.stdout.buffer.write(struct.pack('I', 0)) # padding
i = 0
for j in range(0, 16):
for k in range(0, 2):
if l <= k * 16 * 2:
c = 0
else:
c = int(entry[i:i+2], 16)
i += 2
sys.stdout.buffer.write(struct.pack('B', c))
def write_bin(bits):
sys.stdout.buffer.write(struct.pack('B', 0x44)) # ASCII: 'D'
sys.stdout.buffer.write(struct.pack('B', 0x56)) # ASCII: 'V'
sys.stdout.buffer.write(struct.pack('B', 0x44)) # ASCII: 'D'
sys.stdout.buffer.write(struct.pack('B', 0x48)) # ASCII: 'H'
sys.stdout.buffer.write(struct.pack('B', 0x52)) # ASCII: 'R'
sys.stdout.buffer.write(struct.pack('B', 0x4d)) # ASCII: 'M'
sys.stdout.buffer.write(struct.pack('B', 0x55)) # ASCII: 'U'
sys.stdout.buffer.write(struct.pack('B', 0x46)) # ASCII: 'F'
sys.stdout.buffer.write(struct.pack('<I', 0)) # compatible-flags
sys.stdout.buffer.write(struct.pack('<I', 0)) # incompatible-flags
sys.stdout.buffer.write(struct.pack('<I', 32)) # header-size
sys.stdout.buffer.write(struct.pack('<H', 8)) # glyph-header-size
sys.stdout.buffer.write(struct.pack('<H', 2)) # glyph-stride
sys.stdout.buffer.write(struct.pack('<Q', 32)) # glyph-body-size
# write glyphs
for idx in range(len(bits)):
write_bin_entry(bits[idx])
#
# Parse hex file into "bits" array
#
def parse_hex_line(bits, line):
m = re.match(r"^([0-9A-Fa-f]+):([0-9A-Fa-f]+)$", line)
if m == None:
return
idx = int(m.group(1), 16)
val = m.group(2)
# insert skipped lines
for i in range(len(bits), idx):
bits.append("")
bits.insert(idx, val)
def parse_hex():
bits = []
for line in sys.stdin:
if not line:
continue
if line.startswith("#"):
continue
parse_hex_line(bits, line)
return bits
#
# In normal mode we simply read line by line from standard-input and write the
# binary-file to standard-output.
#
if __name__ == "__main__":
bits = parse_hex()
write_bin(bits)