[systemd-devel] [RFC 05/12] gfx: add sd-gfx library with unifont section

David Herrmann dh.herrmann at gmail.com
Wed Nov 27 10:48:40 PST 2013


If we want to interact with a user in the initrd, during
emergency-situations, in single-user mode, or in any other rather limited
situation, we currently rely on the kernel to do input and graphics access
for us. More precisely, we rely on the VT layer to do keyboard parsing and
font rendering. Or in other words: The *kernel* provides our UI!

This is bad for the same reasons we don't put any other UI into the
kernel. However, there are a few reasons to keep a minimal UI in the
kernel:
 1) show panic-screen with kernel oops message
 2) show log-screen during early boot
 3) allow kernel-debugging via kdb
While people using kdb are encouraged to keep VTs, for 1) and 2) there is
a replacement via fblog/drmlog.

So we run out of reasons to keep a UI in the kernel. However, to allow
moving the UI handling to userspace, we need a bunch of helpers:
 - keyboard handling: convert keycodes into keysyms and modifiers/..
 - modesetting: program the gfx pipeline and provide a rendering
                infrastructure
 - fonts: to render text, we need some basic fonts
 - hotplugging: device detection and assignment during runtime
(Note that all these are implemented (often quite rudimentary) in the
kernel to allow a basic UI.)

This patch introduces sd-gfx, a systemd-internal library dealing with all
these things. Note that it is designed to be exported some day, but for
now we keep it internal.
While the library itself will be kept small and almost self-contained, it
is designed to be extensible and can be used in rather complex graphics
applications. For systemd, we plan to add a basic emergency-console, an
initrd password-query, kernel-log-screen and a fallback login-screen.
These allow booting without CONFIG_VT and provide a basic system for
seats without VTs (either CONFIT_VT=n or seats != seat0).

As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont. It contains glyphs
for the *whole* Base-Multilingual-Plane of Unicode and thus allows
internationalized text.

The "make-unifont.py" script is used by the "make update-unifont" custom
target to regenerate the unifont files. As this can take quite some time,
we check the result into git so only maintainers need to run this.

The binary file contains all glyphs in a compressed 1-bit-per-pixel format
for all 8x16 and 16x16 glyphs. It is linked directly into libsystemd-gfx
via the *.bin makefile target. Binary size is 2.1MB, but thanks to paging,
the kernel only loads required pages into memory. A ASCII-only screen thus
only needs 40k VIRT mem.
---
Hi

I removed the unifont-data from this patch so this will not apply cleanly.
However, the ML would reject the huge patch otherwise. If anyone is interested
in the raw patch, the series is available on:
  http://cgit.freedesktop.org/~dvdhrm/systemd/log/?h=console

Thanks
David

 Makefile.am                      |    31 +
 configure.ac                     |    10 +
 make-unifont.py                  |   138 +
 src/libsystemd-gfx/.gitignore    |     1 +
 src/libsystemd-gfx/Makefile      |     1 +
 src/libsystemd-gfx/gfx-unifont.c |   273 +
 src/libsystemd-gfx/unifont.bin   |   Bin 0 -> 2162688 bytes
 src/libsystemd-gfx/unifont.hex   | 63488 +++++++++++++++++++++++++++++++++++++
 src/systemd/sd-gfx.h             |    65 +
 9 files changed, 64007 insertions(+)
 create mode 100755 make-unifont.py
 create mode 100644 src/libsystemd-gfx/.gitignore
 create mode 120000 src/libsystemd-gfx/Makefile
 create mode 100644 src/libsystemd-gfx/gfx-unifont.c
 create mode 100644 src/libsystemd-gfx/unifont.bin
 create mode 100644 src/libsystemd-gfx/unifont.hex
 create mode 100644 src/systemd/sd-gfx.h

diff --git a/Makefile.am b/Makefile.am
index ce27e82..2edb091 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3853,6 +3853,37 @@ EXTRA_DIST += \
 endif
 
 # ------------------------------------------------------------------------------
+if HAVE_GFX
+noinst_LTLIBRARIES += \
+	libsystemd-gfx.la
+
+libsystemd_gfx_la_SOURCES = \
+	src/libsystemd-gfx/sd-gfx.h \
+	src/libsystemd-gfx/gfx-unifont.c
+
+libsystemd_gfx_la_CFLAGS = \
+	$(AM_CFLAGS)
+
+libsystemd_gfx_la_LIBADD = \
+	libsystemd-shared.la \
+	src/libsystemd-gfx/unifont.bin.lo
+
+src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
+	$(AM_V_GEN)cat $(top_srcdir)/src/libsystemd-gfx/unifont.hex | $(PYTHON) $< >$@
+
+src/libsystemd-gfx/unifont.cmp: make-unifont.py src/libsystemd-gfx/unifont.bin
+	$(AM_V_GEN)cat $(top_srcdir)/src/libsystemd-gfx/unifont.bin | $(PYTHON) $< verify >$@
+
+update-unifont: src/libsystemd-gfx/unifont.bin src/libsystemd-gfx/unifont.cmp
+	@RET=`diff -u src/libsystemd-gfx/unifont.hex src/libsystemd-gfx/unifont.cmp | wc -l` ; \
+		if test "x$$?" != "x0" -o "x$$RET" != "x0" ; then \
+			echo "Generated Unifont-file differs from original; generator probably broken" ; \
+			exit 1 ; \
+		fi
+	@echo "unifont.bin has been regenerated"
+endif
+
+# ------------------------------------------------------------------------------
 if ENABLE_NETWORKD
 rootlibexec_PROGRAMS += \
 	systemd-networkd
diff --git a/configure.ac b/configure.ac
index 3fd05da..354673a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -291,6 +291,15 @@ fi
 AM_CONDITIONAL(HAVE_KMOD, [test "$have_kmod" = "yes"])
 
 # ------------------------------------------------------------------------------
+have_gfx=no
+AC_ARG_ENABLE(gfx, AS_HELP_STRING([--disable-gfx], [disable sd-gfx graphics and input support]))
+if test "x$enable_gfx" != "xno"; then
+        AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built])
+        have_gfx=yes
+fi
+AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"])
+
+# ------------------------------------------------------------------------------
 have_blkid=no
 AC_ARG_ENABLE(blkid, AS_HELP_STRING([--disable-blkid], [disable blkid support]))
 if test "x$enable_blkid" != "xno"; then
@@ -1079,6 +1088,7 @@ AC_MSG_RESULT([
         polkit:                  ${have_polkit}
         efi:                     ${have_efi}
         kmod:                    ${have_kmod}
+        sd-gfx:                  ${have_gfx}
         blkid:                   ${have_blkid}
         nss-myhostname:          ${have_myhostname}
         gudev:                   ${enable_gudev}
diff --git a/make-unifont.py b/make-unifont.py
new file mode 100755
index 0000000..375c8a4
--- /dev/null
+++ b/make-unifont.py
@@ -0,0 +1,138 @@
+#  -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
+#
+#  This file is part of systemd.
+#
+#  Copyright 2013 David Herrmann <dh.herrmann at 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 we use in
+# our gfx applications. Can also do the reverse to verify correctness.
+#
+
+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)))
+
+    for i in range(0, 64, 2):
+        c = int(entry[i:i+2], 16)
+        sys.stdout.buffer.write(struct.pack('B', c))
+
+def write_bin(bits):
+    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
+
+#
+# Write "bits" array as text-file line-by-line to stdout.
+#
+
+def write_hex_line(idx, entry):
+    if entry:
+        print("%04X:%s" % (idx, entry))
+
+def write_hex(bits):
+    for idx, entry in enumerate(bits):
+        if not entry:
+            continue
+
+        write_hex_line(idx, entry)
+
+#
+# Parse a binary file into "bits" so we can verify the correctness of a given
+# binary file.
+#
+
+def parse_bin_entry(bits, chunk):
+    entry = ""
+    l = struct.unpack('B', chunk[0:1])[0]
+    for c in chunk[1:l*16+1]:
+        entry += format(c, "02X")
+
+    bits.append(entry)
+
+def parse_bin():
+    bits = []
+
+    while True:
+        chunk = sys.stdin.buffer.read(33)
+        if chunk:
+            parse_bin_entry(bits, chunk)
+        else:
+            break
+
+    return bits
+
+#
+# In normal mode we simply read line by line from standard-input (or first
+# argument) and write the binary-file to standard-output.
+#
+# In verify-mode, we read the binary-file from standard-input and write the
+# text-file line-by-line to standard-output.
+#
+
+if __name__ == "__main__":
+    if len(sys.argv) > 1 and sys.argv[1] == "verify":
+        bits = parse_bin()
+        write_hex(bits)
+    else:
+        bits = parse_hex()
+        write_bin(bits)
diff --git a/src/libsystemd-gfx/.gitignore b/src/libsystemd-gfx/.gitignore
new file mode 100644
index 0000000..a792d8f
--- /dev/null
+++ b/src/libsystemd-gfx/.gitignore
@@ -0,0 +1 @@
+/unifont.cmp
diff --git a/src/libsystemd-gfx/Makefile b/src/libsystemd-gfx/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/libsystemd-gfx/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/libsystemd-gfx/gfx-unifont.c b/src/libsystemd-gfx/gfx-unifont.c
new file mode 100644
index 0000000..c2fc879
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-unifont.c
@@ -0,0 +1,273 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at 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/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "def.h"
+#include "hashmap.h"
+#include "log.h"
+#include "macro.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+/* Glyphs are linked as binary data. The data layout is a size-byte followed by
+ * 32 data bytes. The data bytes are padded with 0 if the size is smaller than
+ * 32. The size-byte specifies the size of a single line. Each glyph always
+ * consists of 16 lines.
+ * Currently, size==1 is used for single-width glyphs and size==2 for
+ * double-width glyphs. */
+
+struct unifont_data {
+        uint8_t cells;
+        uint8_t data[32];
+} _packed_;
+
+extern const struct unifont_data _binary_src_libsystemd_gfx_unifont_bin_start[];
+extern const struct unifont_data _binary_src_libsystemd_gfx_unifont_bin_end[];
+static const unsigned int unifont_width = 8;
+static const unsigned int unifont_stride = 1;
+static const unsigned int unifont_max_cells = 2;
+static const unsigned int unifont_height = 16;
+
+static const struct unifont_data unifont_fallback = {
+        .cells = 1,
+        .data = "\x00\x00\x00\x7E\x66\x5A\x5A\x7A\x76\x76\x7E\x76\x76\x7E\x00\x00",
+};
+
+static const struct unifont_data *unifont_get(uint32_t ucs4) {
+        const struct unifont_data *begin, *end, *g;
+
+        begin = _binary_src_libsystemd_gfx_unifont_bin_start;
+        end = _binary_src_libsystemd_gfx_unifont_bin_end;
+
+        g = &begin[ucs4];
+        if (g >= end)
+                return &unifont_fallback;
+        if (g->cells == 0 || g->cells > unifont_max_cells)
+                return &unifont_fallback;
+
+        return g;
+}
+
+/*
+ * Fonts
+ * We provide a built-in static font. This can be used to render text in any
+ * situation without depending on huge text-pipelines like pango. The font is
+ * based on "GNU Unifont" which provides fixed-size glyphs for the whole
+ * Unicode Basic Multilingual Plane.
+ * This can be used in initrds, emergency-consoles and other situations where a
+ * full text-pipeline would be overkill.
+ *
+ * Note that we provide some enhanced features to properly support all systems.
+ * This currently includes:
+ *  - scaling: You can specify a "ppi" value to scale the static fonts. This
+ *             can only be done with integer-scaling and should only be used
+ *             as reading-aid or for high-DPI screens.
+ *  - combining: Glyph-combining (eg., for unicode combining characters) is
+ *               implemented in the renderer to correctly draw combined
+ *               characters for non-latin scripts. Character-combining is left
+ *               to the caller, we only allow drawing a set of characters as a
+ *               single glyph.
+ *  - caching: To support fast lookups we cache modified glyphs.
+ */
+
+typedef struct gfx_glyph gfx_glyph;
+
+struct gfx_glyph {
+        sd_gfx_buffer buf;
+};
+
+struct sd_gfx_font {
+        unsigned int ppi;
+        unsigned int width;
+        unsigned int height;
+        Hashmap *glyphs;
+
+        gfx_glyph fallback;
+};
+
+static void gfx_glyph_blend(gfx_glyph *g, unsigned int ppi, const struct unifont_data *u) {
+        unsigned int i, j;
+        const uint8_t *src;
+        uint8_t *dst;
+
+        /*
+         * TODO: scale glyph according to @ppi
+         */
+
+        src = u->data;
+        dst = g->buf.data;
+
+        for (i = 0; i < unifont_height; ++i) {
+                for (j = 0; j < u->cells; ++j)
+                        dst[j] |= src[j];
+
+                src += u->cells * unifont_stride;
+                dst += g->buf.stride;
+        }
+}
+
+static int gfx_glyph_render(gfx_glyph *g, unsigned int ppi, const struct unifont_data *u) {
+        g->buf.width = u->cells * unifont_width;
+        g->buf.height = unifont_height;
+        g->buf.stride = u->cells * unifont_stride;
+        g->buf.format = SD_GFX_BUFFER_FORMAT_A1;
+
+        g->buf.data = calloc(unifont_height, unifont_max_cells * unifont_stride);
+        if (!g->buf.data)
+                return -ENOMEM;
+
+        gfx_glyph_blend(g, ppi, u);
+        return 0;
+}
+
+int sd_gfx_font_new(sd_gfx_font **out, unsigned int ppi) {
+        sd_gfx_font *font;
+        int r;
+
+        ppi = CLAMP(ppi, 10U, 1000U);
+
+        font = calloc(1, sizeof(*font));
+        if (!font)
+                return log_oom();
+
+        font->ppi = ppi;
+        font->width = unifont_width;
+        font->height = unifont_height;
+
+        font->glyphs = hashmap_new(trivial_hash_func, trivial_compare_func);
+        if (!font->glyphs) {
+                free(font);
+                return log_oom();
+        }
+
+        r = gfx_glyph_render(&font->fallback, font->ppi, &unifont_fallback);
+        if (r < 0) {
+                hashmap_free(font->glyphs);
+                free(font);
+                return log_oom();
+        }
+
+        *out = font;
+        return 0;
+}
+
+static void gfx_glyph_free(gfx_glyph *g) {
+        free(g->buf.data);
+        free(g);
+}
+
+void sd_gfx_font_free(sd_gfx_font *font) {
+        gfx_glyph *g;
+
+        if (!font)
+                return;
+
+        while ((g = hashmap_steal_first(font->glyphs)))
+                gfx_glyph_free(g);
+
+        hashmap_free(font->glyphs);
+        free(font);
+}
+
+unsigned int sd_gfx_font_get_ppi(sd_gfx_font *font) {
+        return font->ppi;
+}
+
+unsigned int sd_gfx_font_get_width(sd_gfx_font *font) {
+        return font->width;
+}
+
+unsigned int sd_gfx_font_get_height(sd_gfx_font *font) {
+        return font->height;
+}
+
+/*
+ * Render a glyph
+ * This first tries to look up the glyph in the cache. If found, it is returned,
+ * otherwise the glyph is rendered and put into the cache.
+ *
+ * This function never fails. If the glyph cannot be rendered, a fallback glyph
+ * is returned. Note that this function can render multiple glyphs into a single
+ * buffer. You simply have to pass multiple codepoints via @ucs4 / @len.
+ * However, this should only be used for combining-characters.
+ *
+ * Whenever you render a glyph, this functions needs a unique ID to identify it.
+ * You have to pass it via @id. If you render only a single codepoint, simply
+ * pass the codepoint as @id (or pass 0 in which case the first codepoint is
+ * automatically taken as id). But if you render combined codepoints, you need
+ * to allocate a unique ID for them. See libtsm for an example how to do that.
+ *
+ * The lifetime of the returned buffer is bound to @font. You don't have to free
+ * it yourself.
+ */
+void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out) {
+        const struct unifont_data *u;
+        gfx_glyph *g;
+
+        if (!len)
+                goto error;
+
+        if (!id)
+                id = *ucs4;
+
+        g = hashmap_get(font->glyphs, INT_TO_PTR(id));
+        if (g)
+                goto out;
+
+        g = calloc(1, sizeof(*g));
+        if (!g) {
+                log_oom();
+                goto error;
+        }
+
+        u = unifont_get(*ucs4);
+        if (gfx_glyph_render(g, font->ppi, u) < 0) {
+                log_oom();
+                free(g);
+                goto error;
+        }
+
+        while (--len) {
+                u = unifont_get(*++ucs4);
+                gfx_glyph_blend(g, font->ppi, u);
+        }
+
+        if (hashmap_put(font->glyphs, INT_TO_PTR(id), g) < 0) {
+                log_oom();
+                free(g->buf.data);
+                free(g);
+                goto error;
+        }
+
+        goto out;
+
+error:
+        g = &font->fallback;
+out:
+        *out = &g->buf;
+}
diff --git a/src/libsystemd-gfx/unifont.bin b/src/libsystemd-gfx/unifont.bin
new file mode 100644
index 0000000000000000000000000000000000000000..0b32d80222dd465d0612188915ef67355dc73e4d
GIT binary patch
literal 2162688
z9F*YJIJ$6q>;c^VB)T=i&<gm^zxN+SppMS|-OpAY!~fZzt8D^U-P?cj(n~*i<Be_n

[... skipped for ML ...]

h<$hpdYOEeTz{h7Yzz4==k?}x2v&LpIyNUqW`UBL12zCGf

literal 0
HcmV?d00001

diff --git a/src/libsystemd-gfx/unifont.hex b/src/libsystemd-gfx/unifont.hex
new file mode 100644
index 0000000..90672a8
--- /dev/null
+++ b/src/libsystemd-gfx/unifont.hex
@@ -0,0 +1,63488 @@
+0000:AAAA00018000000180004A51EA505A51C99E0001800000018000000180005555
+0001:AAAA00018000000180003993C252325F8A527193800000018000000180005555

[... skipped for ML ...]

+FFFE:FFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFFE187EFBFE38FEFBFEF87FFFFFFFF
+FFFF:FFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFF
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
new file mode 100644
index 0000000..c46fbd4
--- /dev/null
+++ b/src/systemd/sd-gfx.h
@@ -0,0 +1,65 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosdgfxhfoo
+#define foosdgfxhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at 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/>.
+***/
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <systemd/_sd-common.h>
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_gfx_buffer sd_gfx_buffer;
+typedef struct sd_gfx_font sd_gfx_font;
+
+/* memory buffer */
+
+enum {
+        SD_GFX_BUFFER_FORMAT_A1,
+        SD_GFX_BUFFER_FORMAT_A8,
+        SD_GFX_BUFFER_FORMAT_XRGB8888,
+};
+
+struct sd_gfx_buffer {
+        unsigned int width;
+        unsigned int height;
+        int stride;
+        unsigned int format;
+        void *data;
+};
+
+/* unifont */
+
+int sd_gfx_font_new(sd_gfx_font **out, unsigned int ppi);
+void sd_gfx_font_free(sd_gfx_font *font);
+
+unsigned int sd_gfx_font_get_ppi(sd_gfx_font *font);
+unsigned int sd_gfx_font_get_width(sd_gfx_font *font);
+unsigned int sd_gfx_font_get_height(sd_gfx_font *font);
+
+void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out);
+
+_SD_END_DECLARATIONS;
+
+#endif
-- 
1.8.4.2



More information about the systemd-devel mailing list