diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2014-05-14 15:31:03 +0200 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2014-05-14 15:31:03 +0200 |
commit | 3ccb60ed95b2e70137212fb7ce68f1f94b9ce421 (patch) | |
tree | 2d6c6ed900248d0a1d42c4ae511d303fdec3517a | |
parent | a214453f2d31fde9d17c0b32f04722d143fa030a (diff) |
Similar to a global header, we now add a header to each glyph. This makes
extending single glyphs much easier and we don't have to append it at the
end.
Also replace the previous test with a more advanced one.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | DOCUMENTATION | 72 | ||||
-rw-r--r-- | Makefile.am | 14 | ||||
-rwxr-xr-x | src/compile-unifont.py | 13 | ||||
-rw-r--r-- | test/test-example.c | 276 | ||||
-rw-r--r-- | test/test-glyphs.c | 219 |
6 files changed, 334 insertions, 262 deletions
@@ -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)); -} |