summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--DOCUMENTATION72
-rw-r--r--Makefile.am14
-rwxr-xr-xsrc/compile-unifont.py13
-rw-r--r--test/test-example.c276
-rw-r--r--test/test-glyphs.c219
6 files changed, 334 insertions, 262 deletions
diff --git a/.gitignore b/.gitignore
index f36c03d..ae3989b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,5 +26,5 @@ src/mmap-unifont.bin
src/mmap-unifont.cmp
src/mmap-unifont.pc
stamp-h1
-test-glyphs
+test-example
test-valgrind
diff --git a/DOCUMENTATION b/DOCUMENTATION
index cc84848..c72c99b 100644
--- a/DOCUMENTATION
+++ b/DOCUMENTATION
@@ -14,13 +14,18 @@ and to retrieve configuration options. It includes the following definition
The second file is the actual pre-compiled font. To access it, you're highly
recommended to read the 'pkgdatadir' variable from the pkg-config file. The font
file can then be accessed via:
- $pkgdatadir/mmap-unifont.bin
+ ${pkgdatadir}/mmap-unifont.bin
The usual way to access the file is:
fd = open(PATH, O_RDONLY | O_CLOEXEC);
fstat(fd, &st);
p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+For a full forward- and backward-compatible example, see:
+ ./test/test-example.c
+It contains a _very_ small API that retrieves font-information efficiently from
+by mmap()ing the font file.
+
Binary Format
-------------
@@ -65,16 +70,20 @@ number of bytes. This allows direct access to any glyph in the array.
required. Its size can be inquired by reading the preceding header-size
field.
The header consists of a list of fields in a well-defined order. If the
- header-size is to small to contain a specific field, the caller has to
- assume it's set to the default value as specified below. For instance, if
- the header has size 3, it's too small to contain the glyph-size field,
- therefore, glyph-size is 33. But it's also too small to contain any
- following field (as the field-order is fixed), therefore, all fields get the
- default value.
+ header-size is too small to contain a specific field, the caller has to
+ assume it's set to the default value (as specified below). For instance, if
+ the header has size 3, it's too small to contain the glyph-header-size
+ field, therefore, glyph-header-size is 1. But it's also too small to contain
+ any following field (as the field-order is fixed), therefore, all fields get
+ the default value.
+ If the header-size is 8, both glyph-header-size and glyph-data-size are
+ present and can be parsed in that order.
Currently, the header is defined as:
+--------------------+
- | GLYPH SIZE | 4 bytes
+ | GLYPH HEADER SIZE | 4 bytes
+ +--------------------+
+ | GLYPH DATA SIZE | 4 bytes
+--------------------+
| UNKNOWN | x bytes
~ ~
@@ -82,8 +91,10 @@ number of bytes. This allows direct access to any glyph in the array.
+--------------------+
Defined fields are:
- glyph-size: 4byte unsigned integer which defines the size of each glyph
- in bytes.
+ glyph-header-size: 4byte unsigned integer which defines the size of the
+ header of each glyph in bytes. Default is 1.
+ glyph-data-size: 4byte unsigned integer which defines the size of the
+ data-body of each glyph in bytes. Default is 32.
No other fields are defined and any following bytes are undefined.
@@ -91,9 +102,10 @@ number of bytes. This allows direct access to any glyph in the array.
Glyphs
------
- After the header, the file contains an array of glyphs. Each glyph has the
- size as defined in the glyph-size field in the header. This size is not
- restricted by any alignment. So the array is packed.
+ After the header, the file contains an array of glyphs. Each glyph consists
+ of a header, followed by the data-body. The size of both fields is specified
+ in the font-header. The overall size of a single glyph is the sum of both.
+ This size is not restricted by any alignment and the array is packed.
The array is ordered by Unicode codepoints. Therefore, the n-th entry is the
glyph for the n-th Unicode codepoint.
@@ -102,28 +114,28 @@ number of bytes. This allows direct access to any glyph in the array.
+--------------------+
| GLYPH WIDTH | 1 byte
+--------------------+
- | DATA | 16*n bytes
+ ~ PADDING ~ x bytes (depending on glyph-header-size)
~ ~
+--------------------+
- | UNKNOWN | x bytes
+ | DATA | 16*n bytes
~ ~
~ ~
+--------------------+
+ ~ PADDING ~ x bytes (depending on glyph-data-size)
+ ~ ~
+ +--------------------+
+
+ The glyph-width specifies the character-width of the glyph. Default value is
+ 1, which means, a normal glyph occupies 8x16 pixels. If the width is 'n',
+ the glyph will occupy "(n*8)x16" pixels. The height is fixed to 16 lines.
+
+ If the global glyph-data-size parameter is smaller than required for a
+ glyph, you are supposed to down-scale the glyph-width until it fits. You may
+ also consider the data corrupted and abort.
- Thus, a glyph occupies at least 17bytes. Any following bytes have to be
- ignored. The glyph-width is a 1byte unsigned integer that defines the
- character-width of the glyph. Normal ASCII characters have a width of 1,
- wide-characters (eg., CJK) have a width of 2. More widths might be required
- for future characters.
- The data section contains the packed glyph representation. 1 bit per pixel
- is available, which defines whether the pixel should be black or white (on
- or off, ..). The upper-most / most-significant bit defines the left-most
- pixel. The lower-most / least-significant bit defines the right-most pixel.
-
- Every glyph is encoded in "8 * width" horizontal pixels and 16 vertical
- pixels. A single-width char thus occupies 8x16 pixels, which requires 1 byte
- per line, thus 16 bytes.
- A double-width / wide character requires 8 * 2 == 16 horizontal pixels and
- 16 vertical pixels, thus 2 bytes per line and 32 bytes in total.
+ The actual data section contains a 1-bit alpha-mask for the glyph. Format is
+ packed A1, which means, a single byte contains 8 packed alpha-masks for 8
+ horizontal pixels. The left-most pixel is encoded in the MSB, the right-most
+ pixel is encoded in the LSB.
Any following bytes have to be ignored.
diff --git a/Makefile.am b/Makefile.am
index 494c048..39bfb42 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -77,7 +77,7 @@ src/mmap-unifont.bin: src/compile-unifont.py src/unifont.hex
src/mmap-unifont.cmp: src/compile-unifont.py src/mmap-unifont.bin
$(AM_V_GEN)cat $(top_srcdir)/src/mmap-unifont.bin | $(PYTHON) $< verify >$@
-update-unifont: src/mmap-unifont.bin src/mmap-unifont.cmp
+test-unifont: src/mmap-unifont.bin src/mmap-unifont.cmp
@RET=`diff -u src/unifont.hex src/mmap-unifont.cmp | wc -l` ; \
if test "x$$?" != "x0" -o "x$$RET" != "x0" ; then \
echo "Generated Unifont-file differs from original; generator probably broken" ; \
@@ -97,7 +97,7 @@ update-unifont: src/mmap-unifont.bin src/mmap-unifont.cmp
if BUILD_HAVE_CHECK
MEMTESTS += \
- test-glyphs
+ test-example
check_PROGRAMS += \
$(MEMTESTS) \
test-valgrind
@@ -116,10 +116,10 @@ test_cflags = \
test_lflags = \
$(AM_LDFLAGS)
-test_glyphs_SOURCES = test/test-glyphs.c $(test_sources)
-test_glyphs_CPPFLAGS = $(test_cflags)
-test_glyphs_LDADD = $(test_libs)
-test_glyphs_LDFLAGS = $(test_lflags)
+test_example_SOURCES = test/test-example.c $(test_sources)
+test_example_CPPFLAGS = $(test_cflags)
+test_example_LDADD = $(test_libs)
+test_example_LDFLAGS = $(test_lflags)
test_valgrind_SOURCES = test/test-valgrind.c $(test_sources)
test_valgrind_CPPFLAGS = $(test_cflags)
@@ -145,7 +145,7 @@ memcheck: memcheck-verify
TPHONY += memcheck memcheck-verify
-distcheck-hook: memcheck
+distcheck-hook: test-unifont memcheck
#
# Phony targets
diff --git a/src/compile-unifont.py b/src/compile-unifont.py
index b4b8aca..4cc085d 100755
--- a/src/compile-unifont.py
+++ b/src/compile-unifont.py
@@ -29,10 +29,11 @@ def write_bin_entry(entry):
def write_bin(bits):
# write header-size
- sys.stdout.buffer.write(struct.pack('<I', 4))
+ sys.stdout.buffer.write(struct.pack('<I', 8))
# write header
- sys.stdout.buffer.write(struct.pack('<I', 33))
+ sys.stdout.buffer.write(struct.pack('<I', 1))
+ sys.stdout.buffer.write(struct.pack('<I', 32))
# write glyphs
for idx in range(len(bits)):
@@ -99,7 +100,8 @@ def parse_bin_entry(bits, chunk):
def parse_bin():
bits = []
- glyph_size = 33
+ glyph_header_size = 1
+ glyph_data_size = 32
# read header-size
chunk = sys.stdin.buffer.read(4)
@@ -114,11 +116,12 @@ def parse_bin():
if not chunk:
sys.exit("error: corrupted header");
- glyph_size = struct.unpack('<I', chunk[0:4])[0]
+ glyph_header_size = struct.unpack('<I', chunk[0:4])[0]
+ glyph_data_size = struct.unpack('<I', chunk[4:8])[0]
# read glyphs
while True:
- chunk = sys.stdin.buffer.read(glyph_size)
+ chunk = sys.stdin.buffer.read(glyph_header_size + glyph_data_size)
if chunk:
parse_bin_entry(bits, chunk)
else:
diff --git a/test/test-example.c b/test/test-example.c
new file mode 100644
index 0000000..9daf68c
--- /dev/null
+++ b/test/test-example.c
@@ -0,0 +1,276 @@
+#include "test-common.h"
+
+struct mmfont {
+ unsigned long ref;
+
+ int fd;
+ void *map;
+ size_t map_size;
+
+ uint32_t glyph_header_size;
+ uint32_t glyph_data_size;
+ uint32_t glyph_size;
+
+ const uint8_t *glyphs;
+ size_t n_glyphs;
+};
+
+int mmfont_new(struct mmfont **out);
+struct mmfont *mmfont_ref(struct mmfont *f);
+struct mmfont *mmfont_unref(struct mmfont *f);
+
+static void mmfont_init(struct mmfont *f)
+{
+ const uint8_t *p = f->map;
+ size_t size = f->map_size;
+ uint32_t header_size, t32;
+
+ if (size < sizeof(uint32_t))
+ return;
+
+ /* parse 4-byte header-size */
+ t32 = *(uint32_t*)p;
+ header_size = le32toh(t32);
+ p += sizeof(uint32_t);
+ size -= sizeof(uint32_t);
+ if (header_size > size)
+ header_size = size;
+
+ /* parse header */
+ switch (header_size) {
+ default:
+ /* fallthrough */
+ case 8:
+ t32 = *(uint32_t*)(p + 4);
+ f->glyph_data_size = le32toh(t32);
+ /* fallthrough */
+ case 4 ... 7:
+ t32 = *(uint32_t*)p;
+ f->glyph_header_size = le32toh(t32);
+ /* fallthrough */
+ case 0 ... 3:
+ break;
+ }
+
+ t32 = f->glyph_header_size + f->glyph_data_size;
+ if (!t32 || t32 < f->glyph_header_size) {
+ f->glyph_size = 0;
+ f->n_glyphs = 0;
+ f->glyphs = NULL;
+ } else {
+ f->glyph_size = t32;
+ f->n_glyphs = size / t32;
+ f->glyphs = p + header_size;
+ }
+}
+
+int mmfont_new(struct mmfont **out)
+{
+ struct mmfont *f;
+ struct stat st;
+ int r;
+
+ if (!out)
+ return -EINVAL;
+
+ f = calloc(1, sizeof(*f));
+ if (!f)
+ return -ENOMEM;
+
+ f->ref = 1;
+ f->fd = -1;
+ f->map = MAP_FAILED;
+
+ f->glyph_header_size = 1;
+ f->glyph_data_size = 32;
+
+ f->fd = open(PKGDATADIR "/mmap-unifont.bin", O_RDONLY | O_CLOEXEC);
+ if (f->fd < 0) {
+ r = -errno;
+ goto error;
+ }
+
+ r = fstat(f->fd, &st);
+ if (r < 0) {
+ r = -errno;
+ goto error;
+ }
+ f->map_size = st.st_size;
+
+ f->map = mmap(NULL, f->map_size, PROT_READ, MAP_PRIVATE, f->fd, 0);
+ if (f->map == MAP_FAILED) {
+ r = -errno;
+ goto error;
+ }
+
+ mmfont_init(f);
+
+ *out = f;
+ return 0;
+
+error:
+ mmfont_unref(f);
+ return r;
+}
+
+struct mmfont *mmfont_ref(struct mmfont *f)
+{
+ if (!f || !f->ref)
+ return NULL;
+
+ ++f->ref;
+ return f;
+}
+
+struct mmfont *mmfont_unref(struct mmfont *f)
+{
+ if (!f || !f->ref || --f->ref)
+ return NULL;
+
+ if (f->map != MAP_FAILED)
+ munmap(f->map, f->map_size);
+ if (f->fd >= 0)
+ close(f->fd);
+ free(f);
+
+ return NULL;
+}
+
+int mmfont_lookup(struct mmfont *f,
+ const uint8_t **out_data,
+ size_t *out_width,
+ uint32_t codepoint)
+{
+ const uint8_t *p;
+ size_t width, t;
+
+ if (!f)
+ return -EINVAL;
+
+ if (codepoint >= f->n_glyphs)
+ return -ENOENT;
+
+ p = f->glyphs + codepoint * f->glyph_size;
+
+ if (out_data)
+ *out_data = p + f->glyph_header_size;
+
+ if (out_width) {
+ if (f->glyph_header_size >= 1)
+ width = p[0];
+ else
+ width = 1;
+
+ /* verify the given width fits into the data-segment */
+ t = f->glyph_data_size / 16;
+ if (width > t)
+ width = t;
+
+ *out_width = width;
+ }
+
+ return 0;
+}
+
+static void render(char *w, const uint8_t *glyph, size_t width)
+{
+ unsigned int i, j;
+
+ for (j = 0; j < 16; ++j) {
+ for (i = 0; i < 8 * width; ++i) {
+ if (glyph[i / 8] & (1 << (7 - i % 8)))
+ *w++ = '#';
+ else
+ *w++ = ' ';
+ }
+ *w++ = '\n';
+ glyph += 1 * width;
+ }
+
+ *w++ = 0;
+}
+
+START_TEST(test_misc_draw)
+{
+ char buf[1024];
+ const uint8_t *data;
+ struct mmfont *f;
+ size_t width;
+ int r;
+
+ f = TEST_INVALID_PTR;
+ r = mmfont_new(&f);
+ ck_assert_int_ge(r, 0);
+ ck_assert_ptr_ne(f, TEST_INVALID_PTR);
+ ck_assert_ptr_ne(f, NULL);
+
+ /* access glyph 'A' */
+ r = mmfont_lookup(f, &data, &width, 'A');
+ ck_assert_int_ge(r, 0);
+ ck_assert_int_eq(width, 1);
+
+ /* draw glyph 'A' */
+ render(buf, data, width);
+ ck_assert(!strcmp(buf,
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " ## \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " ###### \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " \n"
+ " \n"
+ ));
+
+ /* access wide glyph 0x4ec0 ('什') */
+ r = mmfont_lookup(f, &data, &width, 0x4ec0);
+ ck_assert_int_ge(r, 0);
+ ck_assert_int_eq(width, 2);
+
+ /* draw wide glyph */
+ render(buf, data, width);
+ ck_assert(!strcmp(buf,
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " ## # \n"
+ " ## ########## \n"
+ " # # # \n"
+ "# # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ " # # \n"
+ ));
+
+ f = mmfont_unref(f);
+ ck_assert_ptr_eq(f, NULL);
+}
+END_TEST
+
+TEST_DEFINE_CASE(misc)
+ TEST(test_misc_draw)
+TEST_END_CASE
+
+int main(int argc, char **argv)
+{
+ if (access(PKGDATADIR "/mmap-unifont.bin", F_OK))
+ return 77;
+
+ return test_run_suite(TEST_SUITE(example,
+ TEST_CASE(misc),
+ TEST_END));
+}
diff --git a/test/test-glyphs.c b/test/test-glyphs.c
deleted file mode 100644
index 46e4529..0000000
--- a/test/test-glyphs.c
+++ /dev/null
@@ -1,219 +0,0 @@
-#include "test-common.h"
-
-static int font_open(void)
-{
- int fd;
-
- fd = open(PKGDATADIR "/mmap-unifont.bin", O_RDONLY | O_CLOEXEC);
- ck_assert_int_ge(fd, 0);
-
- return fd;
-}
-
-static void font_close(int fd)
-{
- int r;
-
- r = close(fd);
- ck_assert_int_ge(r, 0);
-}
-
-static void *font_mmap(int fd, size_t *out_size)
-{
- struct stat st;
- void *p;
- int r;
-
- r = fstat(fd, &st);
- ck_assert_int_ge(r, 0);
-
- p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
- ck_assert_ptr_ne(p, MAP_FAILED);
-
- if (out_size)
- *out_size = st.st_size;
-
- return p;
-}
-
-static void font_unmap(void *p, size_t size)
-{
- int r;
-
- r = munmap(p, size);
- ck_assert_int_ge(r, 0);
-}
-
-static int font_fd = -1;
-static void *font_p = NULL;
-static size_t font_size = 0;
-
-static void font_init(void)
-{
- font_fd = font_open();
- font_p = font_mmap(font_fd, &font_size);
-}
-
-static void font_destroy(void)
-{
- if (font_fd < 0)
- return;
-
- font_unmap(font_p, font_size);
- font_close(font_fd);
- font_fd = -1;
-}
-
-START_TEST(test_misc_setup)
-{
- int fd;
- void *p;
- size_t size;
-
- fd = font_open();
- p = font_mmap(fd, &size);
- font_unmap(p, size);
- font_close(fd);
-
- font_init();
- ck_assert_int_ge(font_fd, 0);
- font_destroy();
- ck_assert_int_lt(font_fd, 0);
- font_destroy();
- ck_assert_int_lt(font_fd, 0);
-}
-END_TEST
-
-static void font_render(char *w, const uint8_t *glyph, size_t width)
-{
- unsigned int i, j;
-
- for (j = 0; j < 16; ++j) {
- for (i = 0; i < 8 * width; ++i) {
- if (glyph[i / 8] & (1 << (7 - i % 8)))
- *w++ = '#';
- else
- *w++ = ' ';
- }
- *w++ = '\n';
- glyph += 1 * width;
- }
-
- *w++ = 0;
-}
-
-START_TEST(test_misc_draw)
-{
- char buf[1024];
- const uint8_t *p, *end, *glyph;
- size_t size, width;
- uint32_t header_size, glyph_size;
-
- font_init();
-
- p = font_p;
- size = font_size;
- end = p + font_size;
-
- /* parse 4-byte header-size */
- {
- ck_assert_int_ge(size, 4);
- header_size = *(uint32_t*)p;
- header_size = le32toh(header_size);
- p += sizeof(uint32_t);
- }
-
- /* parse header */
- {
- glyph_size = 33;
-
- switch (header_size) {
- default:
- /* fallthrough */
- case 4:
- glyph_size = *(uint32_t*)p;
- glyph_size = le32toh(glyph_size);
- /* fallthrough */
- case 0 ... 3:
- break;
- }
-
- ck_assert_int_ge(glyph_size, 33);
- }
-
- /* forward @p to end of header */
- p += header_size;
-
- /* access glyph 'A' */
- glyph = p + glyph_size * 'A';
- ck_assert(end >= glyph + glyph_size);
- width = *glyph++;
- ck_assert_int_eq(width, 1);
-
- /* draw glyph 'A' */
- font_render(buf, glyph, width);
- ck_assert(!strcmp(buf,
- " \n"
- " \n"
- " \n"
- " \n"
- " ## \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " ###### \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " \n"
- " \n"
- ));
-
- /* access wide glyph 0x4ec0 ('什') */
- glyph = p + glyph_size * 0x4ec0;
- ck_assert(end >= glyph + glyph_size);
- width = *glyph++;
- ck_assert_int_eq(width, 2);
-
- /* draw wide glyph */
- font_render(buf, glyph, width);
- ck_assert(!strcmp(buf,
- " # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " ## # \n"
- " ## ########## \n"
- " # # # \n"
- "# # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- " # # \n"
- ));
-
- /* close font */
- font_destroy();
-}
-END_TEST
-
-TEST_DEFINE_CASE(misc)
- TEST(test_misc_setup)
- TEST(test_misc_draw)
-TEST_END_CASE
-
-int main(int argc, char **argv)
-{
- if (access(PKGDATADIR "/mmap-unifont.bin", F_OK))
- return 77;
-
- return test_run_suite(TEST_SUITE(glyphs,
- TEST_CASE(misc),
- TEST_END));
-}