diff options
35 files changed, 4489 insertions, 623 deletions
@@ -1,6 +1,6 @@ # Component settings COMPONENT := rufl -COMPONENT_VERSION := 0.0.3 +COMPONENT_VERSION := 0.1.0 # Default to a static library COMPONENT_TYPE ?= lib-static @@ -9,12 +9,15 @@ PREFIX ?= /opt/netsurf NSSHARED ?= $(PREFIX)/share/netsurf-buildsystem include $(NSSHARED)/makefiles/Makefile.tools -TESTRUNNER := $(ECHO) +TESTRUNNER := $(PERL) $(NSTESTTOOLS)/testrunner.pl # Toolchain flags WARNFLAGS := -Wall -W -Wundef -Wpointer-arith -Wcast-align \ -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes \ - -Wmissing-declarations -Wnested-externs -pedantic + -Wmissing-declarations -Wnested-externs +ifeq ($(findstring -riscos,$(HOST)),-riscos) + WARNFLAGS := $(WARNFLAGS) -pedantic +endif # BeOS/Haiku/AmigaOS4 standard library headers create warnings ifneq ($(BUILD),i586-pc-haiku) ifneq ($(findstring amigaos,$(BUILD)),amigaos) @@ -28,12 +31,20 @@ else # __inline__ is a GCCism CFLAGS := $(CFLAGS) -Dinline="__inline__" endif +CFLAGS := $(CFLAGS) -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=500 # OSLib ifneq ($(findstring clean,$(MAKECMDGOALS)),clean) - ifeq ($(BUILD),arm-unknown-riscos) + ifeq ($(findstring -riscos,$(HOST)),-riscos) CFLAGS := $(CFLAGS) -I$(PREFIX)/include LDFLAGS := $(LDFLAGS) -lOSLib32 + TESTLDFLAGS := $(TESTLDFLAGS) -static + else + # Regardless of the host platform we're building for, we + # still need the RISC OS build environment because we need the + # OSLib headers. + # XXX: is there a way to avoid this path being hard-coded? + CFLAGS := $(CFLAGS) -I/opt/netsurf/arm-unknown-riscos/env/include endif endif diff --git a/include/rufl.h b/include/rufl.h index 767022e..e7d0987 100644 --- a/include/rufl.h +++ b/include/rufl.h @@ -8,6 +8,7 @@ #ifndef RUFL_H #define RUFL_H +#include <inttypes.h> #include <stdbool.h> #include <stdlib.h> #include "oslib/os.h" @@ -53,7 +54,7 @@ extern os_error *rufl_fm_error; /** List of available font families. */ extern const char **rufl_family_list; /** Number of entries in rufl_family_list. */ -extern unsigned int rufl_family_list_entries; +extern size_t rufl_family_list_entries; /** Menu of font families. */ extern void *rufl_family_menu; @@ -124,7 +125,7 @@ rufl_code rufl_split(const char *font_family, rufl_style font_style, /** Type of callback function for rufl_paint_callback(). */ typedef void (*rufl_callback_t)(void *context, const char *font_name, unsigned int font_size, - const char *s8, unsigned short *s16, unsigned int n, + const uint8_t *s8, const uint32_t *s32, unsigned int n, int x, int y); @@ -154,10 +155,10 @@ rufl_code rufl_decompose_glyph(const char *font_family, */ rufl_code rufl_font_metrics(const char *font_family, rufl_style font_style, - os_box *bbox, int *xkern, int *ykern, int *italic, - int *ascent, int *descent, - int *xheight, int *cap_height, - signed char *uline_position, unsigned char *uline_thickness); + os_box *bbox, int32_t *xkern, int32_t *ykern, int32_t *italic, + int32_t *ascent, int32_t *descent, + int32_t *xheight, int32_t *cap_height, + int8_t *uline_position, uint8_t *uline_thickness); /** @@ -167,9 +168,9 @@ rufl_code rufl_font_metrics(const char *font_family, rufl_style font_style, rufl_code rufl_glyph_metrics(const char *font_family, rufl_style font_style, unsigned int font_size, const char *string, size_t length, - int *x_bearing, int *y_bearing, - int *width, int *height, - int *x_advance, int *y_advance); + int32_t *x_bearing, int32_t *y_bearing, + int32_t *width, int32_t *height, + int32_t *x_advance, int32_t *y_advance); /** @@ -177,15 +178,14 @@ rufl_code rufl_glyph_metrics(const char *font_family, */ rufl_code rufl_font_bbox(const char *font_family, rufl_style font_style, - unsigned int font_size, - int *bbox); + unsigned int font_size, os_box *bbox); /** * Dump the internal library state to stdout. */ -void rufl_dump_state(void); +void rufl_dump_state(bool verbose); /** diff --git a/src/Makefile b/src/Makefile index 7592083..22df8df 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,8 @@ # Sources DIR_SOURCES := rufl_character_set_test.c rufl_decompose.c rufl_dump_state.c \ rufl_find.c rufl_init.c rufl_invalidate_cache.c \ - rufl_metrics.c rufl_paint.c rufl_quit.c + rufl_metrics.c rufl_paint.c rufl_substitution_table.c \ + rufl_quit.c ifeq ($(toolchain),norcroft) DIR_SOURCES := $(DIR_SOURCES) strfuncs.c diff --git a/src/rufl_character_set_test.c b/src/rufl_character_set_test.c index 45fbcaf..2e97894 100644 --- a/src/rufl_character_set_test.c +++ b/src/rufl_character_set_test.c @@ -12,18 +12,28 @@ * Test if a character set contains a character. * * \param charset character set - * \param c character code + * \param u Unicode codepoint * \return true if present, false if absent */ -bool rufl_character_set_test(struct rufl_character_set *charset, - unsigned int c) +bool rufl_character_set_test(const struct rufl_character_set *charset, + uint32_t u) { - unsigned int block = c >> 8; - unsigned int byte = (c >> 3) & 31; - unsigned int bit = c & 7; + unsigned int plane = u >> 16; + unsigned int block = (u >> 8) & 0xff; + unsigned int byte = (u >> 3) & 31; + unsigned int bit = u & 7; - if (256 <= block) + if (17 <= plane) + return false; + + /* Look for the plane we want */ + while (PLANE_ID(charset->metadata) != plane && + EXTENSION_FOLLOWS(charset->metadata)) { + charset = (void *)(((uint8_t *)charset) + + PLANE_SIZE(charset->metadata)); + } + if (PLANE_ID(charset->metadata) != plane) return false; if (charset->index[block] == BLOCK_EMPTY) @@ -31,7 +41,7 @@ bool rufl_character_set_test(struct rufl_character_set *charset, else if (charset->index[block] == BLOCK_FULL) return true; else { - unsigned char z = charset->block[charset->index[block]][byte]; + uint8_t z = charset->block[charset->index[block]][byte]; return z & (1 << bit); } } diff --git a/src/rufl_decompose.c b/src/rufl_decompose.c index edf9748..2085e8f 100644 --- a/src/rufl_decompose.c +++ b/src/rufl_decompose.c @@ -8,7 +8,7 @@ #include <assert.h> #include <stdio.h> -#include "oslib/font.h" +#include <oslib/font.h> #include "rufl_internal.h" diff --git a/src/rufl_dump_state.c b/src/rufl_dump_state.c index 06a1f22..f466333 100644 --- a/src/rufl_dump_state.c +++ b/src/rufl_dump_state.c @@ -9,16 +9,16 @@ #include "rufl_internal.h" -static void rufl_dump_character_set(struct rufl_character_set *charset); +static void rufl_dump_character_set_list( + const struct rufl_character_set *charset); static void rufl_dump_unicode_map(struct rufl_unicode_map *umap); -static void rufl_dump_substitution_table(void); /** * Dump the internal library state to stdout. */ -void rufl_dump_state(void) +void rufl_dump_state(bool verbose) { unsigned int i, j; @@ -27,12 +27,12 @@ void rufl_dump_state(void) printf(" %u \"%s\"\n", i, rufl_font_list[i].identifier); if (rufl_font_list[i].charset) { printf(" "); - rufl_dump_character_set(rufl_font_list[i].charset); + rufl_dump_character_set_list(rufl_font_list[i].charset); printf("\n"); } else { printf(" (no charset table)\n"); } - if (rufl_font_list[i].umap) { + if (verbose && rufl_font_list[i].umap) { for (j = 0; j < rufl_font_list[i].num_umaps; j++) { struct rufl_unicode_map *map = rufl_font_list[i].umap + j; @@ -65,7 +65,7 @@ void rufl_dump_state(void) } printf("rufl_substitution_table:\n"); - rufl_dump_substitution_table(); + rufl_substitution_table_dump(); } @@ -75,28 +75,45 @@ void rufl_dump_state(void) * \param charset character set to print */ -void rufl_dump_character_set(struct rufl_character_set *charset) +static void rufl_dump_character_set(const struct rufl_character_set *charset) { - unsigned int u, t; + unsigned int u, t, plane = PLANE_ID(charset->metadata) << 16; u = 0; while (u != 0x10000) { - while (u != 0x10000 && !rufl_character_set_test(charset, u)) + while (u != 0x10000 && + !rufl_character_set_test(charset, plane + u)) u++; if (u != 0x10000) { - if (!rufl_character_set_test(charset, u + 1)) { - printf("%x ", u); + if (!rufl_character_set_test(charset, plane + u + 1)) { + printf("%x ", plane + u); u++; } else { t = u; - while (rufl_character_set_test(charset, u)) + while (u != 0x10000 && rufl_character_set_test( + charset, plane + u)) u++; - printf("%x-%x ", t, u - 1); + printf("%x-%x ", plane + t, plane + u - 1); } } } } +/** + * Dump a representation of a character set list to stdout. + * + * \param charset character set to print + */ + +void rufl_dump_character_set_list(const struct rufl_character_set *charset) +{ + while (EXTENSION_FOLLOWS(charset->metadata)) { + rufl_dump_character_set(charset); + charset = (void *)(((uint8_t *)charset) + + PLANE_SIZE(charset->metadata)); + } + rufl_dump_character_set(charset); +} /** * Dump a representation of a unicode map to stdout. @@ -114,25 +131,3 @@ void rufl_dump_unicode_map(struct rufl_unicode_map *umap) for (i = 0; i != umap->entries; i++) printf("%x:%x ", umap->map[i].u, umap->map[i].c); } - - -/** - * Dump a representation of the substitution table to stdout. - */ - -void rufl_dump_substitution_table(void) -{ - unsigned int font; - unsigned int u, t; - - u = 0; - while (u != 0x10000) { - t = u; - font = rufl_substitution_table[t]; - while (u != 0x10000 && font == rufl_substitution_table[u]) - u++; - if (font != NOT_AVAILABLE) - printf(" %x-%x => %u \"%s\"\n", t, u - 1, - font, rufl_font_list[font].identifier); - } -} diff --git a/src/rufl_init.c b/src/rufl_init.c index 533955a..a4fe618 100644 --- a/src/rufl_init.c +++ b/src/rufl_init.c @@ -14,6 +14,8 @@ #include <string.h> #include <strings.h> #include <search.h> +#include <sys/stat.h> +#include <sys/types.h> #include <oslib/font.h> #include <oslib/hourglass.h> #include <oslib/os.h> @@ -23,20 +25,30 @@ #include <oslib/wimpreadsysinfo.h> #include "rufl_internal.h" +/* Both of the below options are currently disabled as we only parse + * Encoding files when using a non-UCS FontManager. */ +/* Enable support for /uniXXXX and /uXXXX[XXXX] form glyph "names" */ +#define SUPPORT_UCS_GLYPH_NAMES 0 +/* Enable support for parsing UCS FM sparse encoding files */ +#define SUPPORT_UCS_SPARSE_ENCODING 0 -struct rufl_font_list_entry *rufl_font_list = 0; +struct rufl_font_list_entry *rufl_font_list = NULL; size_t rufl_font_list_entries = 0; -const char **rufl_family_list = 0; -unsigned int rufl_family_list_entries = 0; -struct rufl_family_map_entry *rufl_family_map = 0; -os_error *rufl_fm_error = 0; -void *rufl_family_menu = 0; -unsigned short *rufl_substitution_table = 0; +const char **rufl_family_list = NULL; +size_t rufl_family_list_entries = 0; +struct rufl_family_map_entry *rufl_family_map = NULL; +os_error *rufl_fm_error = NULL; +void *rufl_family_menu = NULL; struct rufl_cache_entry rufl_cache[rufl_CACHE_SIZE]; -int rufl_cache_time = 0; +uint32_t rufl_cache_time = 0; bool rufl_old_font_manager = false; +static bool rufl_broken_font_enumerate_characters = false; wimp_w rufl_status_w = 0; char rufl_status_buffer[80]; +#if 1 /* ndef NDEBUG */ +bool rufl_log_got_start_time; +time_t rufl_log_start_time; +#endif /** An entry in rufl_weight_table. */ struct rufl_weight_table_entry { @@ -58,11 +70,13 @@ const struct rufl_weight_table_entry rufl_weight_table[] = { { "ExtraLight", 1 }, { "Heavy", 8 }, { "Light", 2 }, + { "MedBold", 6 }, { "Medium", 5 }, { "Regular", 4 }, { "Semi", 6 }, { "SemiBold", 6 }, { "SemiLight", 3 }, + { "Thin", 1 }, { "UltraBlack", 9 }, { "UltraBold", 9 }, }; @@ -73,17 +87,19 @@ static rufl_code rufl_init_add_font(const char *identifier, const char *local_name); static int rufl_weight_table_cmp(const void *keyval, const void *datum); static rufl_code rufl_init_scan_font(unsigned int font); -static rufl_code rufl_init_scan_font_no_enumerate(unsigned int font); static bool rufl_is_space(unsigned int u); static rufl_code rufl_init_scan_font_old(unsigned int font_index); static rufl_code rufl_init_scan_font_in_encoding(const char *font_name, const char *encoding, struct rufl_character_set *charset, struct rufl_unicode_map *umap, unsigned int *last); -static rufl_code rufl_init_read_encoding(font_f font, +static rufl_code rufl_init_populate_unicode_map(font_f f, struct rufl_unicode_map *umap); +static rufl_code rufl_init_read_encoding(font_f font, + rufl_code (*callback)(void *pw, + uint32_t glyph_idx, uint32_t ucs4), + void *pw); static int rufl_glyph_map_cmp(const void *keyval, const void *datum); static int rufl_unicode_map_cmp(const void *z1, const void *z2); -static rufl_code rufl_init_substitution_table(void); static rufl_code rufl_save_cache(void); static rufl_code rufl_load_cache(void); static int rufl_font_list_cmp(const void *keyval, const void *datum); @@ -101,7 +117,6 @@ static void rufl_init_status_close(void); rufl_code rufl_init(void) { - bool rufl_broken_font_enumerate_characters = false; unsigned int changes = 0; unsigned int i; int fm_version; @@ -151,22 +166,31 @@ rufl_code rufl_init(void) xfont_lose_font(font); } - LOG("%s font manager", rufl_old_font_manager ? "old" : "new"); /* test if the font manager supports background blending */ rufl_fm_error = xfont_cache_addr(&fm_version, 0, 0); - if (rufl_fm_error) + if (rufl_fm_error) { + LOG("xfont_cache_addr: 0x%x: %s", + rufl_fm_error->errnum, + rufl_fm_error->errmess); return rufl_FONT_MANAGER_ERROR; + } if (fm_version >= 335) rufl_can_background_blend = true; + LOG("%s font manager (v %d.%d)%s", + rufl_old_font_manager ? "old" : "new", + fm_version / 100, fm_version % 100, + rufl_broken_font_enumerate_characters ? " (broken fec)" : ""); + code = rufl_init_font_list(); if (code != rufl_OK) { + LOG("rufl_init_font_list: 0x%x", code); rufl_quit(); xhourglass_off(); return code; } - LOG("%zu faces, %u families", rufl_font_list_entries, + LOG("%zu faces, %zu families", rufl_font_list_entries, rufl_family_list_entries); code = rufl_load_cache(); @@ -189,8 +213,6 @@ rufl_code rufl_init(void) (float) i / rufl_font_list_entries); if (rufl_old_font_manager) code = rufl_init_scan_font_old(i); - else if (rufl_broken_font_enumerate_characters) - code = rufl_init_scan_font_no_enumerate(i); else code = rufl_init_scan_font(i); if (code != rufl_OK) { @@ -204,9 +226,9 @@ rufl_code rufl_init(void) xhourglass_leds(2, 0, 0); xhourglass_colours(0x0000ff, 0x00ffff, &old_sand, &old_glass); - code = rufl_init_substitution_table(); + code = rufl_substitution_table_init(); if (code != rufl_OK) { - LOG("rufl_init_substitution_table: 0x%x", code); + LOG("rufl_substitution_table_init: 0x%x", code); rufl_quit(); xhourglass_off(); return code; @@ -230,7 +252,7 @@ rufl_code rufl_init(void) code = rufl_init_family_menu(); if (code != rufl_OK) { - LOG("rufl_init_substitution_table: 0x%x", code); + LOG("rufl_init_family_menu: 0x%x", code); rufl_quit(); xhourglass_off(); return code; @@ -255,7 +277,9 @@ rufl_code rufl_init_font_list(void) font_list_context context = 0; char identifier[80], local_name[80]; - while (context != -1) { + /* Permit up to 65535 font faces (we rely on 16bits of storage + * being sufficient in the substitution tables. */ + while (context != -1 && rufl_font_list_entries < UINT16_MAX) { /* read identifier */ rufl_fm_error = xfont_list_fonts((byte *)identifier, font_RETURN_FONT_NAME | @@ -274,8 +298,10 @@ rufl_code rufl_init_font_list(void) break; code = rufl_init_add_font(identifier, local_name); - if (code != rufl_OK) + if (code != rufl_OK) { + LOG("rufl_init_add_font: 0x%x", code); return code; + } } return rufl_OK; @@ -340,8 +366,9 @@ rufl_code rufl_init_add_font(const char *identifier, const char *local_name) rufl_font_list[rufl_font_list_entries].identifier = strdup(identifier); if (!rufl_font_list[rufl_font_list_entries].identifier) return rufl_OUT_OF_MEMORY; - rufl_font_list[rufl_font_list_entries].charset = 0; - rufl_font_list[rufl_font_list_entries].umap = 0; + rufl_font_list[rufl_font_list_entries].charset = NULL; + rufl_font_list[rufl_font_list_entries].umap = NULL; + rufl_font_list[rufl_font_list_entries].num_umaps = 0; rufl_font_list_entries++; /* determine family, weight, and slant */ @@ -425,179 +452,299 @@ int rufl_weight_table_cmp(const void *keyval, const void *datum) return strcasecmp(key, entry->name); } -/** - * Scan a font for available characters. - */ +static struct rufl_character_set *rufl_init_alloc_plane(uint8_t index) +{ + struct rufl_character_set *charset; + unsigned int u; -rufl_code rufl_init_scan_font(unsigned int font_index) + charset = calloc(1, sizeof *charset); + if (charset != NULL) { + /* Set plane ID. Extension/size must be filled in by caller */ + charset->metadata |= ((index & 0x1f) << 26); + for (u = 0; u != 256; u++) + charset->index[u] = BLOCK_EMPTY; + } + + return charset; +} + +static void rufl_init_shrinkwrap_plane(struct rufl_character_set *charset) +{ + unsigned int last_used = PLANE_SIZE(charset->metadata); + unsigned int u, bit, byte; + + /* Determine which blocks are full, and mark them as such */ + for (u = 0; u != 256; u++) { + const unsigned int block = charset->index[u]; + + if (block == BLOCK_EMPTY) + continue; + + bit = 0xff; + + for (byte = 0; byte != 32; byte++) + bit &= charset->block[block][byte]; + + + if (bit != 0xff) + continue; + + /* Block is full */ + + /* Move subsequent blocks up and rewrite their indices */ + memmove(charset->block[block], + charset->block[block+1], + (254-(block+1)) * 32); + for (byte = 0; byte != 256; byte++) { + if (charset->index[byte] < BLOCK_EMPTY && + charset->index[byte] > block) { + charset->index[byte]--; + } + } + + /* Now mark this block as full */ + charset->index[u] = BLOCK_FULL; + last_used--; + } + + /* Fill in this plane's size now we know it */ + charset->metadata = (charset->metadata & 0xffff0000) | + (offsetof(struct rufl_character_set, block) + + 32 * last_used); +} + +static struct rufl_character_set *rufl_init_shrinkwrap_planes( + struct rufl_character_set *planes[17]) { - char font_name[80]; - int x_out, y_out; - unsigned int byte, bit; - unsigned int last_used = 0; - unsigned int string[2] = { 0, 0 }; - unsigned int u, next; struct rufl_character_set *charset; - struct rufl_character_set *charset2; - font_f font; - font_scan_block block = { { 0, 0 }, { 0, 0 }, -1, { 0, 0, 0, 0 } }; + unsigned int size = 0, pos, u; - /*LOG("font %u \"%s\"", font_index, - rufl_font_list[font_index].identifier);*/ + /* Shrink-wrap each plane, accumulating total required size as we go */ + for (u = 0; u < 17; u++) { + if (planes[u]) { + rufl_init_shrinkwrap_plane(planes[u]); + size += PLANE_SIZE(planes[u]->metadata); + } + } - charset = calloc(1, sizeof *charset); + charset = malloc(size); if (!charset) - return rufl_OUT_OF_MEMORY; - for (u = 0; u != 256; u++) - charset->index[u] = BLOCK_EMPTY; + return NULL; - snprintf(font_name, sizeof font_name, "%s\\EUTF8", - rufl_font_list[font_index].identifier); + /* Copy planes into output, backwards, setting the extension bit for + * all but the last plane present */ + pos = size; + for (u = 17; u > 0; u--) { + if (!planes[u-1]) + continue; - rufl_fm_error = xfont_find_font(font_name, 160, 160, 0, 0, &font, 0, 0); - if (rufl_fm_error) { - LOG("xfont_find_font(\"%s\"): 0x%x: %s", font_name, - rufl_fm_error->errnum, rufl_fm_error->errmess); - free(charset); - return rufl_OK; + /* Set E bit if not the last plane */ + if (pos != size) + planes[u-1]->metadata |= (1u<<31); + + pos -= PLANE_SIZE(planes[u-1]->metadata); + memcpy(((uint8_t *)charset) + pos, planes[u-1], + PLANE_SIZE(planes[u-1]->metadata)); } - /* Scan through mapped characters */ - for (u = 0; u != (unsigned int) -1; u = next) { - unsigned int internal; + return charset; +} - rufl_fm_error = xfont_enumerate_characters(font, u, - (int *) &next, (int *) &internal); +static rufl_code rufl_init_enumerate_characters(const char *font_name, + font_f font, + rufl_code (*callback)(void *pw, + uint32_t glyph_idx, uint32_t ucs4), + void *pw) +{ + unsigned int u = 0, next, internal; + rufl_code result = rufl_OK; + + if (rufl_broken_font_enumerate_characters) { + /* We know that any codepoints in the first chunk will + * be missed because Font_EnumerateCharacters is broken + * on this version of the Font Manager. Find the first + * codepoint it will report. */ + unsigned int first; + rufl_fm_error = xfont_enumerate_characters(font, 0, + (int *) &first, (int *) &internal); if (rufl_fm_error) { - LOG("xfont_enumerate_characters: 0x%x: %s", - rufl_fm_error->errnum, + LOG("xfont_enumerate_characters(\"%s\", " + "U+%x, ...): 0x%x: %s", + font_name, 0, + rufl_fm_error->errnum, rufl_fm_error->errmess); - xfont_lose_font(font); - free(charset); + return rufl_FONT_MANAGER_ERROR; + } + if (first == (unsigned int) -1) { + /* Font has no defined characters */ return rufl_OK; } - /* Skip DELETE and C0/C1 controls */ - if (u < 0x0020 || (0x007f <= u && u <= 0x009f)) - continue; + /* Search the entire space up to the first codepoint it + * reported. */ + for (u = 1; u != first; u++) { + rufl_fm_error = xfont_enumerate_characters(font, u, + (int *) &next, (int *) &internal); + if (rufl_fm_error) { + LOG("xfont_enumerate_characters(\"%s\", " + "U+%x, ...): 0x%x: %s", + font_name, u, + rufl_fm_error->errnum, + rufl_fm_error->errmess); + return rufl_FONT_MANAGER_ERROR; + } - /* Skip astral characters */ - if (u > 0xffff) - continue; + /* Skip unmapped characters */ + if (internal == (unsigned int) -1) + continue; + + /* Character is mapped, emit it */ + result = callback(pw, internal, u); + if (result != rufl_OK) + return result; + } + + /* Now fall through to the normal path */ + } + + /* Scan through mapped characters */ + for (; u != (unsigned int) -1; u = next) { + rufl_fm_error = xfont_enumerate_characters(font, u, + (int *) &next, (int *) &internal); + if (rufl_fm_error) { + LOG("xfont_enumerate_characters(\"%s\", " + "U+%x, ...): 0x%x: %s", + font_name, u, + rufl_fm_error->errnum, + rufl_fm_error->errmess); + result = rufl_FONT_MANAGER_ERROR; + break; + } /* Skip unmapped characters */ if (internal == (unsigned int) -1) continue; - if (u % 0x200 == 0) - rufl_init_status(0, 0); - - /* Character is mapped, let's see if it's really there */ - string[0] = u; - rufl_fm_error = xfont_scan_string(font, (char *) string, - font_RETURN_BBOX | font_GIVEN32_BIT | - font_GIVEN_FONT | font_GIVEN_LENGTH | - font_GIVEN_BLOCK, - 0x7fffffff, 0x7fffffff, - &block, 0, 4, - 0, &x_out, &y_out, 0); - if (rufl_fm_error) + /* Character is mapped, emit it */ + result = callback(pw, internal, u); + if (result != rufl_OK) break; + } - if (block.bbox.x0 == 0x20000000) { - /* absent (no definition) */ - } else if (x_out == 0 && y_out == 0 && - block.bbox.x0 == 0 && block.bbox.y0 == 0 && - block.bbox.x1 == 0 && block.bbox.y1 == 0) { - /* absent (empty) */ - } else if (block.bbox.x0 == 0 && block.bbox.y0 == 0 && - block.bbox.x1 == 0 && block.bbox.y1 == 0 && - !rufl_is_space(u)) { - /* absent (space but not a space character - some - * fonts do this) */ - } else { - /* present */ - if (charset->index[u >> 8] == BLOCK_EMPTY) { - charset->index[u >> 8] = last_used; - last_used++; - if (last_used == 254) - /* too many characters */ - break; - } + return result; +} - byte = (u >> 3) & 31; - bit = u & 7; - charset->block[charset->index[u >> 8]][byte] |= - 1 << bit; - } - } +static rufl_code find_plane_cb(void *pw, uint32_t glyph_idx, uint32_t ucs4) +{ + struct rufl_character_set **planes = pw; - xfont_lose_font(font); + (void) glyph_idx; - if (rufl_fm_error) { - free(charset); - LOG("xfont_scan_string: 0x%x: %s", - rufl_fm_error->errnum, rufl_fm_error->errmess); - return rufl_FONT_MANAGER_ERROR; - } + /* Skip DELETE and C0/C1 controls */ + if (ucs4 > 0x0020 && (ucs4 < 0x007f || 0x009f < ucs4)) + planes[(ucs4 >> 16) & 0x1f] = (struct rufl_character_set *) 1; - /* Determine which blocks are full, and mark them as such */ - for (u = 0; u != 256; u++) { - if (charset->index[u] == BLOCK_EMPTY) - continue; + return rufl_OK; +} - bit = 0xff; +struct find_glyph_ctx { + const char *font_name; + font_f font; + struct rufl_character_set **planes; +}; - for (byte = 0; byte != 32; byte++) - bit &= charset->block[u][byte]; +static rufl_code find_glyph_cb(void *pw, uint32_t glyph_idx, uint32_t ucs4) +{ + struct find_glyph_ctx *ctx = pw; + int x_out, y_out; + unsigned int string[2] = { 0, 0 }; + font_scan_block block = { { 0, 0 }, { 0, 0 }, -1, { 0, 0, 0, 0 } }; - if (bit == 0xff) { - /* Block is full */ - charset->index[u] = BLOCK_FULL; + (void) glyph_idx; - for (byte = 0; byte != 32; byte++) - charset->block[u][byte] = 0; - } - } + /* Skip DELETE and C0/C1 controls */ + if (ucs4 < 0x0020 || (0x007f <= ucs4 && ucs4 <= 0x009f)) + return rufl_OK; - /* shrink-wrap */ - charset->size = offsetof(struct rufl_character_set, block) + - 32 * last_used; - charset2 = realloc(charset, charset->size); - if (!charset2) { - free(charset); - return rufl_OUT_OF_MEMORY; + if (ucs4 % 0x200 == 0) + rufl_init_status(0, 0); + + string[0] = ucs4; + rufl_fm_error = xfont_scan_string(ctx->font, (char *) string, + font_RETURN_BBOX | font_GIVEN32_BIT | + font_GIVEN_FONT | font_GIVEN_LENGTH | + font_GIVEN_BLOCK, + 0x7fffffff, 0x7fffffff, + &block, 0, 4, + 0, &x_out, &y_out, 0); + if (rufl_fm_error) { + LOG("xfont_scan_string(\"%s\", U+%x, ...): 0x%x: %s", + ctx->font_name, ucs4, + rufl_fm_error->errnum, rufl_fm_error->errmess); + return rufl_FONT_MANAGER_ERROR; } - rufl_font_list[font_index].charset = charset; + if (block.bbox.x0 == 0x20000000) { + /* absent (no definition) */ + } else if (x_out == 0 && y_out == 0 && + block.bbox.x0 == 0 && block.bbox.y0 == 0 && + block.bbox.x1 == 0 && block.bbox.y1 == 0) { + /* absent (empty) */ + } else if (block.bbox.x0 == 0 && block.bbox.y0 == 0 && + block.bbox.x1 == 0 && block.bbox.y1 == 0 && + !rufl_is_space(ucs4)) { + /* absent (space but not a space character - some + * fonts do this) */ + } else { + /* present */ + const unsigned int plane = (ucs4 >> 16) & 0x1f; + const unsigned int blk = (ucs4 >> 8) & 0xff; + const unsigned int byte = (ucs4 >> 3) & 31; + const unsigned int bit = ucs4 & 7; + + /* Allocate block, if it's currently empty */ + if (ctx->planes[plane]->index[blk] == BLOCK_EMPTY) { + unsigned int last_used = + PLANE_SIZE(ctx->planes[plane]->metadata); + if (last_used < BLOCK_EMPTY) { + ctx->planes[plane]->index[blk] = last_used; + ctx->planes[plane]->metadata = + (ctx->planes[plane]->metadata & + 0xffff0000) | + (last_used + 1); + } + } + + /* Set bit for codepoint in bitmap, if bitmap exists */ + if (ctx->planes[plane]->index[blk] < BLOCK_EMPTY) { + ctx->planes[plane]->block[ + ctx->planes[plane]->index[blk] + ][byte] |= 1 << bit; + } + } return rufl_OK; } /** - * Scan a font for available characters (version without character enumeration) + * Scan a font for available characters */ -rufl_code rufl_init_scan_font_no_enumerate(unsigned int font_index) +rufl_code rufl_init_scan_font(unsigned int font_index) { char font_name[80]; - int x_out, y_out; - unsigned int byte, bit; - unsigned int block_count = 0; - unsigned int last_used = 0; - unsigned int string[2] = { 0, 0 }; - unsigned int u; + struct rufl_character_set *planes[17]; struct rufl_character_set *charset; - struct rufl_character_set *charset2; font_f font; - font_scan_block block = { { 0, 0 }, { 0, 0 }, -1, { 0, 0, 0, 0 } }; + unsigned int plane; + struct find_glyph_ctx ctx; + rufl_code rc; /*LOG("font %u \"%s\"", font_index, rufl_font_list[font_index].identifier);*/ - charset = calloc(1, sizeof *charset); - if (!charset) - return rufl_OUT_OF_MEMORY; + for (plane = 0; plane < 17; plane++) + planes[plane] = NULL; snprintf(font_name, sizeof font_name, "%s\\EUTF8", rufl_font_list[font_index].identifier); @@ -606,89 +753,55 @@ rufl_code rufl_init_scan_font_no_enumerate(unsigned int font_index) if (rufl_fm_error) { LOG("xfont_find_font(\"%s\"): 0x%x: %s", font_name, rufl_fm_error->errnum, rufl_fm_error->errmess); - free(charset); return rufl_OK; } - /* scan through all characters */ - for (u = 0x0020; u != 0x10000; u++) { - if (u == 0x007f) { - /* skip DELETE and C1 controls */ - u = 0x009f; - continue; - } - - if (u % 0x200 == 0) - rufl_init_status(0, 0); + /* First pass: find the planes we need */ + rc = rufl_init_enumerate_characters(font_name, font, + find_plane_cb, planes); + if (rc != rufl_OK) + return rc; - string[0] = u; - rufl_fm_error = xfont_scan_string(font, (char *) string, - font_RETURN_BBOX | font_GIVEN32_BIT | - font_GIVEN_FONT | font_GIVEN_LENGTH | - font_GIVEN_BLOCK, - 0x7fffffff, 0x7fffffff, - &block, 0, 4, - 0, &x_out, &y_out, 0); - if (rufl_fm_error) - break; - - if (block.bbox.x0 == 0x20000000) { - /* absent (no definition) */ - } else if (x_out == 0 && y_out == 0 && - block.bbox.x0 == 0 && block.bbox.y0 == 0 && - block.bbox.x1 == 0 && block.bbox.y1 == 0) { - /* absent (empty) */ - } else if (block.bbox.x0 == 0 && block.bbox.y0 == 0 && - block.bbox.x1 == 0 && block.bbox.y1 == 0 && - !rufl_is_space(u)) { - /* absent (space but not a space character - some - * fonts do this) */ - } else { - /* present */ - byte = (u >> 3) & 31; - bit = u & 7; - charset->block[last_used][byte] |= 1 << bit; - - block_count++; - } + /* Allocate the planes */ + for (plane = 0; plane < 17; plane++) { + if (!planes[plane]) + continue; - if ((u + 1) % 256 == 0) { - /* end of block */ - if (block_count == 0) - charset->index[u >> 8] = BLOCK_EMPTY; - else if (block_count == 256) { - charset->index[u >> 8] = BLOCK_FULL; - for (byte = 0; byte != 32; byte++) - charset->block[last_used][byte] = 0; - } else { - charset->index[u >> 8] = last_used; - last_used++; - if (last_used == 254) - /* too many characters */ - break; - } - block_count = 0; + planes[plane] = rufl_init_alloc_plane(plane); + if (!planes[plane]) { + while (plane > 0) + free(planes[plane-1]); + xfont_lose_font(font); + return rufl_OUT_OF_MEMORY; } } - xfont_lose_font(font); + /* Second pass: populate the planes */ + ctx.font_name = font_name; + ctx.font = font; + ctx.planes = planes; - if (rufl_fm_error) { - free(charset); - LOG("xfont_scan_string: 0x%x: %s", - rufl_fm_error->errnum, rufl_fm_error->errmess); - return rufl_FONT_MANAGER_ERROR; + rc = rufl_init_enumerate_characters(font_name, font, + find_glyph_cb, &ctx); + if (rc != rufl_OK) { + for (plane = 0; plane < 17; plane++) + free(planes[plane]); + xfont_lose_font(font); + return rc; } - /* shrink-wrap */ - charset->size = offsetof(struct rufl_character_set, block) + - 32 * last_used; - charset2 = realloc(charset, charset->size); - if (!charset2) { - free(charset); + xfont_lose_font(font); + + charset = rufl_init_shrinkwrap_planes(planes); + if (!charset) { + for (plane = 0; plane < 17; plane++) + free(planes[plane]); return rufl_OUT_OF_MEMORY; } + for (plane = 0; plane < 17; plane++) + free(planes[plane]); + rufl_font_list[font_index].charset = charset; return rufl_OK; @@ -708,6 +821,9 @@ bool rufl_is_space(unsigned int u) /** * Scan a font for available characters (old font manager version). + * By definition, no astral characters are supported when using a non-UCS + * Font Manager (font encodings are defined using PostScript glyph names + * which, per the Glyph list, can only fall in the Basic Multilingual Plane) */ rufl_code rufl_init_scan_font_old(unsigned int font_index) @@ -770,22 +886,34 @@ rufl_code rufl_init_scan_font_old(unsigned int font_index) code = rufl_init_scan_font_in_encoding(font_name, encoding, charset, umap + (num_umaps - 1), &last_used); - if (code != rufl_OK) { - /* Not finding the font isn't fatal */ - if (code != rufl_FONT_MANAGER_ERROR || - (rufl_fm_error->errnum != - error_FONT_NOT_FOUND && - rufl_fm_error->errnum != - error_FILE_NOT_FOUND)) { - free(charset); - for (i = 0; i < num_umaps; i++) - free((umap + i)->encoding); - free(umap); - return code; - } - + /* Not finding the font isn't fatal */ + if (code == rufl_FONT_MANAGER_ERROR && + (rufl_fm_error->errnum == + error_FONT_NOT_FOUND || + rufl_fm_error->errnum == + error_FILE_NOT_FOUND || + rufl_fm_error->errnum == + error_FONT_ENCODING_NOT_FOUND || + /* Neither is a too modern font */ + rufl_fm_error->errnum == + error_FONT_TOO_MANY_CHUNKS)) { /* Ensure we reuse the currently allocated umap */ num_umaps--; + rufl_fm_error = NULL; + } else if (code != rufl_OK) { + LOG("rufl_init_scan_font_in_encoding(\"%s\", \"%s\", " + "...): 0x%x (0x%x: %s)", + font_name, encoding, code, + code == rufl_FONT_MANAGER_ERROR ? + rufl_fm_error->errnum : 0, + code == rufl_FONT_MANAGER_ERROR ? + rufl_fm_error->errmess : ""); + + free(charset); + for (i = 0; i < num_umaps; i++) + free((umap + i)->encoding); + free(umap); + return code; } else { /* If this mapping is identical to an existing one, * then we can discard it */ @@ -798,6 +926,7 @@ rufl_code rufl_init_scan_font_old(unsigned int font_index) memcmp(a->map, b->map, sizeof a->map) == 0) { /* Found identical map; discard */ + free(b->encoding); num_umaps--; break; } @@ -824,28 +953,50 @@ rufl_code rufl_init_scan_font_old(unsigned int font_index) code = rufl_init_scan_font_in_encoding(font_name, NULL, charset, umap, &last_used); - if (code != rufl_OK) { - /* Not finding the font isn't fatal */ - if (code != rufl_FONT_MANAGER_ERROR || - (rufl_fm_error->errnum != - error_FONT_NOT_FOUND && - rufl_fm_error->errnum != - error_FILE_NOT_FOUND)) { - free(charset); - for (i = 0; i < num_umaps; i++) - free((umap + i)->encoding); - free(umap); - return code; - } - + /* Not finding the font isn't fatal */ + if (code == rufl_FONT_MANAGER_ERROR && + (rufl_fm_error->errnum == + error_FONT_NOT_FOUND || + rufl_fm_error->errnum == + error_FILE_NOT_FOUND || + rufl_fm_error->errnum == + error_FONT_ENCODING_NOT_FOUND || + /* Neither is a too modern font */ + rufl_fm_error->errnum == + error_FONT_TOO_MANY_CHUNKS)) { + /* Ensure we reuse the currently allocated umap */ num_umaps--; + rufl_fm_error = NULL; + } else if (code != rufl_OK) { + LOG("rufl_init_scan_font_in_encoding(\"%s\", NULL, " + "...): 0x%x (0x%x: %s)", + font_name, code, + code == rufl_FONT_MANAGER_ERROR ? + rufl_fm_error->errnum : 0, + code == rufl_FONT_MANAGER_ERROR ? + rufl_fm_error->errmess : ""); + + free(charset); + for (i = 0; i < num_umaps; i++) + free((umap + i)->encoding); + free(umap); + return code; } } - /* shrink-wrap */ - charset->size = offsetof(struct rufl_character_set, block) + - 32 * last_used; - charset2 = realloc(charset, charset->size); + if (num_umaps == 0) { + /* No mappings found: font is empty or couldn't be found */ + free(umap); + free(charset); + return rufl_OK; + } + + /* Shrink-wrap. We only have the one plane so fill in last_used + * as expected by the shrinkwrapping helper and then resize the + * resulting charset manually. */ + charset->metadata = (charset->metadata & 0xffff0000) | last_used; + rufl_init_shrinkwrap_plane(charset); + charset2 = realloc(charset, PLANE_SIZE(charset->metadata)); if (!charset2) { for (i = 0; i < num_umaps; i++) free((umap + i)->encoding); @@ -854,7 +1005,7 @@ rufl_code rufl_init_scan_font_old(unsigned int font_index) return rufl_OUT_OF_MEMORY; } - rufl_font_list[font_index].charset = charset; + rufl_font_list[font_index].charset = charset2; rufl_font_list[font_index].umap = umap; rufl_font_list[font_index].num_umaps = num_umaps; @@ -888,17 +1039,41 @@ rufl_code rufl_init_scan_font_in_encoding(const char *font_name, rufl_fm_error = xfont_find_font(buf, 160, 160, 0, 0, &font, 0, 0); if (rufl_fm_error) { - LOG("xfont_find_font(\"%s\"): 0x%x: %s", buf, - rufl_fm_error->errnum, rufl_fm_error->errmess); + /* Leave it to our caller to log, if they wish */ return rufl_FONT_MANAGER_ERROR; } - code = rufl_init_read_encoding(font, umap); + code = rufl_init_populate_unicode_map(font, umap); if (code != rufl_OK) { + LOG("rufl_init_read_encoding(\"%s\", ...): 0x%x", + buf, code); + umap->encoding = NULL; xfont_lose_font(font); return code; } + /* Detect attempts to use UCS fonts with a non-UCS Font Manager. + * There is a bug in all known non-UCS Font Managers which is + * often triggered by scanning many fonts. The Font Manager will + * attempt to dereference a bogus pointer (at the start of + * getbbox_unscaled) and thus cause an abort in SVC mode. + * Fallout can be as (relatively) benign as the application + * crashing or escalate to an entire system freeze requiring + * a reset. As there are no "good" outcomes here, and we do + * not have a time machine to go back and fix long-ago released + * Font Managers, ensure we ignore UCS fonts here. */ + if ((uintptr_t) umap->encoding > 256) { + static os_error err = { + error_FONT_TOO_MANY_CHUNKS, "Rejecting UCS font"}; + LOG("%s", "Rejecting UCS font"); + umap->encoding = NULL; + xfont_lose_font(font); + rufl_fm_error = &err; + return rufl_FONT_MANAGER_ERROR; + } + /* Eliminate all trace of our (ab)use of the encoding field */ + umap->encoding = NULL; + for (i = 0; i != umap->entries; i++) { u = umap->map[i].u; string[0] = umap->map[i].c; @@ -942,7 +1117,8 @@ rufl_code rufl_init_scan_font_in_encoding(const char *font_name, xfont_lose_font(font); if (rufl_fm_error) { - LOG("xfont_scan_string: 0x%x: %s", + LOG("xfont_scan_string(\"%s\", U+%x, ...) (c=%x): 0x%x: %s", + buf, umap->map[i].u, umap->map[i].c, rufl_fm_error->errnum, rufl_fm_error->errmess); return rufl_FONT_MANAGER_ERROR; } @@ -958,21 +1134,184 @@ rufl_code rufl_init_scan_font_in_encoding(const char *font_name, return rufl_OK; } +static rufl_code rufl_init_umap_cb(void *pw, uint32_t glyph_idx, uint32_t ucs4) +{ + struct rufl_unicode_map *umap = pw; + rufl_code result = rufl_OK; + + /* Ignore first 32 character codes (these are control chars) */ + if (glyph_idx > 31 && glyph_idx < 256 && umap->entries < 256 && + ucs4 != (uint32_t) -1) { + umap->map[umap->entries].u = ucs4; + umap->map[umap->entries].c = glyph_idx; + umap->entries++; + } + /* Stash the total number of encoding file entries so that + * rufl_init_scan_font_in_encoding can detect the presence of a + * UCS font on a non-UCS capable system. It will clean up for us. */ + umap->encoding = (void *) ((uintptr_t) (glyph_idx + 1)); + + return result; +} /** - * Parse an encoding file and fill in a rufl_unicode_map. + * Populate a unicode map by parsing the font's encoding file */ -rufl_code rufl_init_read_encoding(font_f font, +rufl_code rufl_init_populate_unicode_map(font_f f, struct rufl_unicode_map *umap) { - unsigned int u = 0; + rufl_code result; + + umap->entries = 0; + + result = rufl_init_read_encoding(f, rufl_init_umap_cb, umap); + if (result == rufl_OK) { + /* sort by unicode */ + qsort(umap->map, umap->entries, sizeof umap->map[0], + rufl_unicode_map_cmp); + } + return result; +} + +#if SUPPORT_UCS_GLYPH_NAMES || SUPPORT_UCS_SPARSE_ENCODING +static int fromhex(char val, bool permit_lc) +{ + if ('0' <= val && val <= '9') + return val - '0'; + else if ('A' <= val && val <= 'F') + return val - 'A' + 10; + else if (permit_lc && 'a' <= val && val <= 'f') + return val - 'a' + 10; + return -1; +} +#endif + +static rufl_code emit_codepoint(char s[200], unsigned int i, + rufl_code (*callback)(void *pw, + uint32_t glyph_idx, uint32_t ucs4), + void *pw) +{ + struct rufl_glyph_map_entry *entry; + rufl_code result = rufl_OK; + +#if SUPPORT_UCS_SPARSE_ENCODING + if (s[0] != '/') { + /* Sparse encoding entry: [XX;]XXXX;NNN..;.... */ + uint32_t val = 0; + int digits; + + if (s[2] == ';' && fromhex(s[0], true) != -1 && + fromhex(s[1], true) != -1) { + /* Skip leading "XX;" */ + s += 3; + } + + for (digits = 0; digits < 4; digits++) { + int nibble = fromhex(s[digits], true); + if (nibble == -1) + break; + val = (val << 4) | nibble; + } + + /* Bail out if the data is not what we expect */ + if (digits != 4 || s[digits] != ';') + return result; + + /* Set the glyph index to the value we found */ + i = val; + /* Advance s to the start of the glyph name */ + s += digits + 1; + /* Terminate the glyph name */ + for (digits = 0; s[digits] != '\0'; digits++) { + if (s[digits] == ';') { + s[digits] = '\0'; + break; + } + } + /* Fall through to the glyph name search */ + } else +#endif + { + /* Skip the leading / */ + s += 1; + +#if SUPPORT_UCS_GLYPH_NAMES + if (!rufl_old_font_manager && s[0] == 'u') { + /* Handle /uniXXXX and /uXXXX - /uXXXXXXXX. + * In the case of /uXXXXX - /uXXXXXXXX, no + * leading zeroes are permitted. */ + int max_digits = 8, off = 1, digits = 0; + bool leading_zero = false; + uint32_t val = 0; + + if (s[1] == 'n' && s[2] == 'i') { + max_digits = 4; + off = 3; + } + + while (digits < max_digits) { + int nibble = fromhex(s[off], false); + if (nibble == -1) + break; + leading_zero = (digits == 0 && nibble == 0); + val = (val << 4) | nibble; + off++; + digits++; + } + if ((digits == 4 && s[off] == '\0') || + (digits > 4 && s[off] == '\0' && + !leading_zero)) { + return callback(pw, i, val); + } + + /* Otherwise, let the glyph name search decide */ + } +#endif + } + + entry = bsearch(s, rufl_glyph_map, + rufl_glyph_map_size, + sizeof rufl_glyph_map[0], + rufl_glyph_map_cmp); + if (entry) { + /* may be more than one unicode for the glyph + * sentinels stop overshooting array */ + while (strcmp(s, (entry - 1)->glyph_name) == 0) + entry--; + for (; strcmp(s, entry->glyph_name) == 0; entry++) { + result = callback(pw, i, entry->u); + if (result != rufl_OK) + break; + } + } else { + /* No mapping: inform callback in case it cares */ + result = callback(pw, i, (uint32_t) -1); + } + + return result; +} + +/** + * Parse an encoding file + */ + +rufl_code rufl_init_read_encoding(font_f font, + rufl_code (*callback)(void *pw, + uint32_t glyph_idx, uint32_t ucs4), + void *pw) +{ + enum { + STATE_START, + STATE_COMMENT, + STATE_COLLECT, + } state = STATE_START; + bool emit = false, done = false; unsigned int i = 0; + unsigned int n = 0; int c; - int n; char filename[200]; char s[200]; - struct rufl_glyph_map_entry *entry; FILE *fp; rufl_fm_error = xfont_read_encoding_filename(font, filename, @@ -984,52 +1323,79 @@ rufl_code rufl_init_read_encoding(font_f font, } fp = fopen(filename, "r"); - if (!fp) - /* many "symbol" fonts have no encoding file: assume Latin 1 */ + if (!fp && rufl_old_font_manager) { + /* many "symbol" fonts have no encoding file */ fp = fopen("Resources:$.Fonts.Encodings.Latin1", "r"); + } if (!fp) return rufl_IO_ERROR; - while (!feof(fp) && u != 256) { + while (!feof(fp) && !done) { c = fgetc(fp); - if (c == '%') { - /* comment line */ - fgets(s, sizeof s, fp); - } else if (c == '/') { - /* character definition */ - if (i++ < 32) - continue; - n = fscanf(fp, "%100s", s); - if (n != 1) - break; - entry = bsearch(s, rufl_glyph_map, - rufl_glyph_map_size, - sizeof rufl_glyph_map[0], - rufl_glyph_map_cmp); - if (entry) { - /* may be more than one unicode for the glyph - * sentinels stop overshooting array */ - while (strcmp(s, (entry - 1)->glyph_name) == 0) - entry--; - for (; strcmp(s, entry->glyph_name) == 0; - entry++) { - umap->map[u].u = entry->u; - umap->map[u].c = i - 1; - u++; - if (u == 256) - break; + + if (state == STATE_START) { + if (c == '/') { + s[0] = c; + n = 1; + state = STATE_COLLECT; +#if SUPPORT_UCS_SPARSE_ENCODING + } else if (!rufl_old_font_manager && + (('0' <= c && c <= '9') || + ('A' <= c && c <= 'F') || + ('a' <= c && c <= 'f'))) { + /* New-style sparse encoding file */ + s[0] = c; + n = 1; + state = STATE_COLLECT; +#endif + } else if (c <= 0x20) { + /* Consume C0 and space */ + } else { + /* Comment, or garbage */ + state = STATE_COMMENT; + } + } else if (state == STATE_COMMENT) { + /* Consume until the next C0 */ + if (c < 0x20) { + state = STATE_START; + } + } else { + if ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '.') || (c == '_') || + (c == ';') || + (c == ' ' && s[0] != '/')) { + /* Printable (or space in new-style): append */ + s[n++] = c; + if (n >= sizeof(s)) { + /* Too long: garbage */ + state = STATE_COMMENT; } + } else if (c > 0x20) { + /* Garbage */ + state = STATE_COMMENT; + } else { + /* C0 or space: done */ + s[n] = '\0'; + if (n != 0) { + emit = true; + } + state = STATE_START; } } + + if (emit) { + emit = false; + if (emit_codepoint(s, i, callback, pw) != rufl_OK) + done = true; + i++; + } } if (fclose(fp) == EOF) return rufl_IO_ERROR; - /* sort by unicode */ - qsort(umap->map, u, sizeof umap->map[0], rufl_unicode_map_cmp); - umap->entries = u; - return rufl_OK; } @@ -1054,65 +1420,51 @@ int rufl_unicode_map_cmp(const void *z1, const void *z2) } -/** - * Construct the font substitution table. - */ - -rufl_code rufl_init_substitution_table(void) +static FILE *rufl_open_cache(const char *mode) { - unsigned char z; - unsigned int i; - unsigned int block, byte, bit; - unsigned int u; - unsigned int index; - const struct rufl_character_set *charset; - - rufl_substitution_table = malloc(65536 * - sizeof rufl_substitution_table[0]); - if (!rufl_substitution_table) { - LOG("malloc(%zu) failed", 65536 * - sizeof rufl_substitution_table[0]); - return rufl_OUT_OF_MEMORY; - } - - for (u = 0; u != 0x10000; u++) - rufl_substitution_table[u] = NOT_AVAILABLE; + const unsigned int version = rufl_CACHE_VERSION; + size_t len; + FILE *fp; + char fn[PATH_MAX]; + + if (!mode) + return NULL; + + strcpy(fn, rufl_CACHE_TEMPLATE); + len = strlen(fn); + + /* Fill in version suffix */ + fn[len-4] = "0123456789abcdef"[(version>>12) & 0xf]; + fn[len-3] = "0123456789abcdef"[(version>> 8) & 0xf]; + fn[len-2] = "0123456789abcdef"[(version>> 4) & 0xf]; + fn[len-1] = "0123456789abcdef"[version & 0xf]; + + if (mode[0] == 'a' || mode[0] == 'w') { + /* Wind back to directory separator */ + while (len > 0 && fn[len] != '.') + len--; + if (len == 0) { + LOG("%s", "Malformed cache location"); + return NULL; + } - for (i = 0; i != rufl_font_list_entries; i++) { - charset = rufl_font_list[i].charset; - if (!charset) - continue; - for (block = 0; block != 256; block++) { - if (charset->index[block] == BLOCK_EMPTY) - continue; - if (charset->index[block] == BLOCK_FULL) { - for (u = block << 8; u != (block << 8) + 256; - u++) { - if (rufl_substitution_table[u] == - NOT_AVAILABLE) - rufl_substitution_table[u] = i; - } - continue; - } - index = charset->index[block]; - for (byte = 0; byte != 32; byte++) { - z = charset->block[index][byte]; - if (z == 0) - continue; - u = (block << 8) | (byte << 3); - for (bit = 0; bit != 8; bit++, u++) { - if (rufl_substitution_table[u] == - NOT_AVAILABLE && - z & (1 << bit)) - rufl_substitution_table[u] = i; - } - } + /* Ensure directory exists */ + fn[len] = '\0'; + if (mkdir(fn, 0755) == -1 && errno != EEXIST) { + LOG("mkdir: 0x%x: %s", errno, strerror(errno)); + return NULL; } + fn[len] = '.'; } - return rufl_OK; -} + fp = fopen(fn, mode); + if (!fp) { + LOG("fopen: 0x%x: %s", errno, strerror(errno)); + return NULL;; + } + return fp; +} /** * Save character sets to cache. @@ -1125,11 +1477,9 @@ rufl_code rufl_save_cache(void) size_t len; FILE *fp; - fp = fopen(rufl_CACHE, "wb"); - if (!fp) { - LOG("fopen: 0x%x: %s", errno, strerror(errno)); + fp = rufl_open_cache("wb"); + if (!fp) return rufl_OK; - } /* cache format version */ if (fwrite(&version, sizeof version, 1, fp) != 1) { @@ -1147,7 +1497,10 @@ rufl_code rufl_save_cache(void) } for (i = 0; i != rufl_font_list_entries; i++) { - if (!rufl_font_list[i].charset) + const struct rufl_character_set *charset = + rufl_font_list[i].charset; + + if (!charset) continue; /* length of font identifier */ @@ -1165,9 +1518,19 @@ rufl_code rufl_save_cache(void) return rufl_OK; } - /* character set */ - if (fwrite(rufl_font_list[i].charset, - rufl_font_list[i].charset->size, 1, fp) != 1) { + /* character set (all planes) */ + while (EXTENSION_FOLLOWS(charset->metadata)) { + if (fwrite(charset, PLANE_SIZE(charset->metadata), + 1, fp) != 1) { + LOG("fwrite: 0x%x: %s", errno, strerror(errno)); + fclose(fp); + return rufl_OK; + } + charset = (void *)(((uint8_t *)charset) + + PLANE_SIZE(charset->metadata)); + } + if (fwrite(charset, PLANE_SIZE(charset->metadata), + 1, fp) != 1) { LOG("fwrite: 0x%x: %s", errno, strerror(errno)); fclose(fp); return rufl_OK; @@ -1250,18 +1613,17 @@ rufl_code rufl_load_cache(void) unsigned int i = 0; bool old_font_manager; char *identifier; - size_t len, size; + size_t len, size = 0; + uint32_t metadata; FILE *fp; struct rufl_font_list_entry *entry; - struct rufl_character_set *charset; + struct rufl_character_set *charset = NULL, *cur_charset; struct rufl_unicode_map *umap = NULL; - unsigned int num_umaps = 0; + size_t num_umaps = 0; - fp = fopen(rufl_CACHE, "rb"); - if (!fp) { - LOG("fopen: 0x%x: %s", errno, strerror(errno)); + fp = rufl_open_cache("rb"); + if (!fp) return rufl_OK; - } /* cache format version */ if (fread(&version, sizeof version, 1, fp) != 1) { @@ -1325,33 +1687,52 @@ rufl_code rufl_load_cache(void) identifier[len] = 0; /* character set */ - if (fread(&size, sizeof size, 1, fp) != 1) { - if (feof(fp)) - LOG("fread: %s", "unexpected eof"); - else - LOG("fread: 0x%x: %s", errno, strerror(errno)); - free(identifier); - break; - } + do { + if (fread(&metadata, sizeof metadata, 1, fp) != 1) { + if (feof(fp)) + LOG("fread: %s", "unexpected eof"); + else + LOG("fread: 0x%x: %s", + errno, strerror(errno)); + free(identifier); + break; + } - charset = malloc(size); - if (!charset) { - LOG("malloc(%zu) failed", size); - free(identifier); - fclose(fp); - return rufl_OUT_OF_MEMORY; - } + if (!charset) { + charset = cur_charset = malloc( + PLANE_SIZE(metadata)); + } else { + struct rufl_character_set *c2 = realloc(charset, + size + PLANE_SIZE(metadata)); + if (!c2) { + free(charset); + } + charset = c2; + cur_charset = (void *)(((uint8_t *) charset) + + size); + } + if (!charset) { + LOG("malloc(%zu) failed", size); + free(identifier); + fclose(fp); + return rufl_OUT_OF_MEMORY; + } - charset->size = size; - if (fread(charset->index, size - sizeof size, 1, fp) != 1) { - if (feof(fp)) - LOG("fread: %s", "unexpected eof"); - else - LOG("fread: 0x%x: %s", errno, strerror(errno)); - free(charset); - free(identifier); - break; - } + size += PLANE_SIZE(metadata); + cur_charset->metadata = metadata; + if (fread(cur_charset->index, + PLANE_SIZE(metadata) - sizeof metadata, + 1, fp) != 1) { + if (feof(fp)) + LOG("fread: %s", "unexpected eof"); + else + LOG("fread: 0x%x: %s", + errno, strerror(errno)); + free(charset); + free(identifier); + break; + } + } while(EXTENSION_FOLLOWS(cur_charset->metadata)); /* unicode map */ if (rufl_old_font_manager) { @@ -1483,7 +1864,10 @@ rufl_code rufl_load_cache(void) free(charset); } - free(identifier); + charset = NULL; + size = 0; + + free(identifier); } fclose(fp); @@ -1510,10 +1894,10 @@ rufl_code rufl_init_family_menu(void) wimp_menu *menu; unsigned int i; - menu = malloc(wimp_SIZEOF_MENU(rufl_family_list_entries)); + menu = malloc(wimp_SIZEOF_MENU(rufl_family_list_entries + 1)); if (!menu) return rufl_OUT_OF_MEMORY; - menu->title_data.indirected_text.text = (char *) "Fonts"; + strcpy(menu->title_data.text, "Fonts"); menu->title_fg = wimp_COLOUR_BLACK; menu->title_bg = wimp_COLOUR_LIGHT_GREY; menu->work_fg = wimp_COLOUR_BLACK; @@ -1534,8 +1918,18 @@ rufl_code rufl_init_family_menu(void) menu->entries[i].data.indirected_text.size = strlen(rufl_family_list[i]); } - menu->entries[0].menu_flags = wimp_MENU_TITLE_INDIRECTED; - menu->entries[i - 1].menu_flags |= wimp_MENU_LAST; + if (i == 0) { + menu->entries[i].menu_flags = wimp_MENU_LAST; + menu->entries[i].sub_menu = wimp_NO_SUB_MENU; + menu->entries[i].icon_flags = wimp_ICON_TEXT | + wimp_ICON_SHADED | + (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) | + (wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT); + menu->entries[i].data.text[0] = '\0'; + + } else { + menu->entries[i - 1].menu_flags |= wimp_MENU_LAST; + } rufl_family_menu = menu; diff --git a/src/rufl_internal.h b/src/rufl_internal.h index 7801178..42ed5c4 100644 --- a/src/rufl_internal.h +++ b/src/rufl_internal.h @@ -5,29 +5,88 @@ * Copyright 2006 James Bursa <james@semichrome.net> */ +#include <inttypes.h> #include <limits.h> -#include "oslib/font.h" +#include <oslib/font.h> #include "rufl.h" #ifdef __CC_NORCROFT #include "strfuncs.h" #endif -/** The available characters in a font. The range which can be represented is - * 0x0000 to 0xffff. The size of the structure is 4 + 256 + 32 * blocks. A - * typical * 200 glyph font might have characters in 10 blocks, giving 580 - * bytes. The maximum possible size of the structure is 8388 bytes. Note that - * since two index values are reserved, fonts with 65280-65024 glyphs may be - * unrepresentable, if there are no full blocks. This is unlikely. The primary - * aim of this structure is to make lookup fast. */ +/** + * The available Unicode codepoints represented by a font. The entire Unicode + * range (U+0000 - U+10FFFF) may be covered by the font, but only codepoints + * in the Basic Multilingual Plane (i.e. U+0000 - U+FFFF) can be represented + * without the need for extension structures. + * + * Fonts which provide glyphs for astral characters will set the extension + * bit in the structure size field. If set, this indicates that an additional + * character set structure follows immediately after this one. The plane id + * field in the structure metadata indicates which plane the structure relates + * to. Planes are specified in ascending order (as the most commonly used + * codepoints occur in earlier planes). Planes for which the font has no + * glyphs are omitted entirely. + * + * Each plane is subdivided into 256 codepoint blocks (each block representing + * 256 contiguous codepoints). Note, however, that two index values are + * reserved (to indicate full or empty blocks) so only 254 partial blocks may + * be represented. As of Unicode 13, all planes have at least two blocks + * unused (or, in the case of the surrogate ranges in the Basic Multilingual + * Plane, defined as containing no characters), so all valid codepoints should + * be representable using this scheme. + * + * The size of the structure is 4 + 256 + 32 * blocks. A typical 200 glyph + * font might represent codepoints in 10 blocks, using 580 bytes of storage. + * A plane with glyphs in every block (but no block fully populated) requires + * the maximum possible structure size of (4 + 256 + 32 * 254 =) 8388 bytes. + * The maximum storage required for (the unlikely scenario of) a font + * providing glyphs in every block in each of the 17 Unicode planes is + * 17 * 8388 = 142596 bytes. + * + * The primary aim of this structure is to make lookup fast. + */ struct rufl_character_set { - /** Size of structure / bytes. */ - size_t size; + /** Structure metadata. + * + * This field contains metadata about the structure in the form: + * + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |E| PID | Reserved | Size | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * where: + * + * extension (E): 1 bit + * If set, another character set covering a different plane + * follows. + * + * plane id (PID): 5 bits + * The 0-based index of the Unicode plane this structure relates + * to. Valid values are in the range [0, 16], where 0 represents + * the Basic Multilingual Plane, and 16 represents the + * Supplementary Private Use Area - B. + * + * reserved: 10 bits + * These bits are currently unused and must be set to 0. + * + * size: 16 bits + * The total size of this structure, in bytes. + */ + uint32_t metadata; +# define EXTENSION_FOLLOWS(x) ((x) & (1u<<31)) +# define PLANE_ID(x) (((x) >> 26) & 0x1f) +# define PLANE_SIZE(x) ((x) & 0xffff) - /** Index table. Each entry represents a block of 256 characters, so - * i[k] refers to characters [256*k, 256*(k+1)). The value is either - * BLOCK_EMPTY, BLOCK_FULL, or an offset into the block table. */ - unsigned char index[256]; + /** Index table. + * + * Each entry represents a block of 256 codepoints, so i[k] refers + * to codepoints [256*k, 256*(k+1)). The value is either BLOCK_EMPTY, + * BLOCK_FULL, or an offset into the block table. + * */ + uint8_t index[256]; /** The block has no characters present. */ # define BLOCK_EMPTY 254 /** All characters in the block are present. */ @@ -35,16 +94,16 @@ struct rufl_character_set { /** Block table. Each entry is a 256-bit bitmap indicating which * characters in the block are present and absent. */ - unsigned char block[254][32]; + uint8_t block[254][32]; }; /** Part of struct rufl_unicode_map. */ struct rufl_unicode_map_entry { - /** Unicode value. */ - unsigned short u; + /** Unicode value (must be in Basic Multilingual Plane). */ + uint16_t u; /** Corresponding character. */ - unsigned char c; + uint8_t c; }; @@ -57,7 +116,7 @@ struct rufl_unicode_map { /** Corresponding encoding name */ char *encoding; /** Number of valid entries in map. */ - unsigned int entries; + size_t entries; /** Map from Unicode to character code. */ struct rufl_unicode_map_entry map[256]; }; @@ -70,16 +129,16 @@ struct rufl_font_list_entry { /** Character set of font. */ struct rufl_character_set *charset; /** Number of Unicode mapping tables */ - unsigned int num_umaps; + size_t num_umaps; /** Mappings from Unicode to character code. */ struct rufl_unicode_map *umap; /** Family that this font belongs to (index in rufl_family_list and * rufl_family_map). */ - unsigned int family; + uint32_t family; /** Font weight (0 to 8). */ - unsigned int weight; + uint32_t weight; /** Font slant (0 or 1). */ - unsigned int slant; + uint32_t slant; }; /** List of all available fonts. */ extern struct rufl_font_list_entry *rufl_font_list; @@ -92,17 +151,14 @@ struct rufl_family_map_entry { /** This style does not exist in this family. */ # define NO_FONT UINT_MAX /** Map from weight and slant to index in rufl_font_list, or NO_FONT. */ - unsigned int font[9][2]; + uint32_t font[9][2]; }; /** Map from font family to fonts, rufl_family_list_entries entries. */ extern struct rufl_family_map_entry *rufl_family_map; /** No font contains this character. */ -#define NOT_AVAILABLE 65535 -/** Font substitution table. */ -extern unsigned short *rufl_substitution_table; - +#define NOT_AVAILABLE 0xffff /** Number of slots in recent-use cache. This is the maximum number of RISC OS * font handles that will be used at any time by the library. */ @@ -111,24 +167,24 @@ extern unsigned short *rufl_substitution_table; /** An entry in rufl_cache. */ struct rufl_cache_entry { /** Font number (index in rufl_font_list), or rufl_CACHE_*. */ - unsigned int font; + uint32_t font; /** No font cached in this slot. */ #define rufl_CACHE_NONE UINT_MAX /** Font for rendering hex substitutions in this slot. */ #define rufl_CACHE_CORPUS (UINT_MAX - 1) /** Font size. */ - unsigned int size; + uint32_t size; /** Font encoding */ const char *encoding; /** Value of rufl_cache_time when last used. */ - unsigned int last_used; + uint32_t last_used; /** RISC OS font handle. */ font_f f; }; /** Cache of rufl_CACHE_SIZE most recently used font handles. */ extern struct rufl_cache_entry rufl_cache[rufl_CACHE_SIZE]; /** Counter for measuring age of cache entries. */ -extern int rufl_cache_time; +extern uint32_t rufl_cache_time; /** Font manager does not support Unicode. */ extern bool rufl_old_font_manager; @@ -141,9 +197,13 @@ rufl_code rufl_find_font_family(const char *family, rufl_style font_style, struct rufl_character_set **charset); rufl_code rufl_find_font(unsigned int font, unsigned int font_size, const char *encoding, font_f *fhandle); -bool rufl_character_set_test(struct rufl_character_set *charset, - unsigned int c); +bool rufl_character_set_test(const struct rufl_character_set *charset, + uint32_t u); +rufl_code rufl_substitution_table_init(void); +void rufl_substitution_table_fini(void); +unsigned int rufl_substitution_table_lookup(uint32_t u); +void rufl_substitution_table_dump(void); #define rufl_utf8_read(s, l, u) \ if (4 <= l && ((s[0] & 0xf8) == 0xf0) && ((s[1] & 0xc0) == 0x80) && \ @@ -151,54 +211,59 @@ bool rufl_character_set_test(struct rufl_character_set *charset, u = ((s[0] & 0x7) << 18) | ((s[1] & 0x3f) << 12) | \ ((s[2] & 0x3f) << 6) | (s[3] & 0x3f); \ s += 4; l -= 4; \ + if (u < 0x10000) u = 0xfffd; \ } else if (3 <= l && ((s[0] & 0xf0) == 0xe0) && \ ((s[1] & 0xc0) == 0x80) && \ ((s[2] & 0xc0) == 0x80)) { \ u = ((s[0] & 0xf) << 12) | ((s[1] & 0x3f) << 6) | \ (s[2] & 0x3f); \ s += 3; l -= 3; \ + if (u < 0x800) u = 0xfffd; \ } else if (2 <= l && ((s[0] & 0xe0) == 0xc0) && \ ((s[1] & 0xc0) == 0x80)) { \ u = ((s[0] & 0x3f) << 6) | (s[1] & 0x3f); \ s += 2; l -= 2; \ + if (u < 0x80) u = 0xfffd; \ } else if ((s[0] & 0x80) == 0) { \ u = s[0]; \ s++; l--; \ } else { \ u = 0xfffd; \ s++; l--; \ + } \ + if ((u >= 0xd800 && u <= 0xdfff) || u == 0xfffe || u == 0xffff) { \ + u = 0xfffd; \ } -#define rufl_CACHE "<Wimp$ScrapDir>.RUfl_cache" -#define rufl_CACHE_VERSION 3 +#define rufl_CACHE_TEMPLATE "<Wimp$ScrapDir>.RUfl.CacheNNNN" +#define rufl_CACHE_VERSION 4 struct rufl_glyph_map_entry { const char *glyph_name; - unsigned short u; + /* The glyph map contains codepoints in the BMP only */ + uint16_t u; }; extern const struct rufl_glyph_map_entry rufl_glyph_map[]; extern const size_t rufl_glyph_map_size; -#ifndef NDEBUG -#ifdef __CC_NORCROFT -#define __PRETTY_FUNCTION__ __func__ -#endif +#if 1 /*ndef NDEBUG*/ #include <time.h> -bool log_got_start_time; -time_t log_start_time; +extern bool rufl_log_got_start_time; +extern time_t rufl_log_start_time; #define LOG(format, ...) \ do { \ - if (log_got_start_time == false) { \ - log_start_time = time(NULL); \ - log_got_start_time = true; \ + if (rufl_log_got_start_time == false) { \ + rufl_log_start_time = time(NULL); \ + rufl_log_got_start_time = true; \ } \ \ fprintf(stderr,"(%.6fs) " __FILE__ " %s %i: ", \ - difftime(time(NULL), log_start_time), \ - __PRETTY_FUNCTION__, __LINE__); \ + difftime(time(NULL), \ + rufl_log_start_time), \ + __func__, __LINE__); \ fprintf(stderr, format, __VA_ARGS__); \ fprintf(stderr, "\n"); \ } while (0) diff --git a/src/rufl_invalidate_cache.c b/src/rufl_invalidate_cache.c index 65a3897..3ec73d9 100644 --- a/src/rufl_invalidate_cache.c +++ b/src/rufl_invalidate_cache.c @@ -5,7 +5,7 @@ * Copyright 2005 James Bursa <james@semichrome.net> */ -#include "oslib/font.h" +#include <oslib/font.h> #include "rufl_internal.h" diff --git a/src/rufl_metrics.c b/src/rufl_metrics.c index af4727f..bbfd6bb 100644 --- a/src/rufl_metrics.c +++ b/src/rufl_metrics.c @@ -9,7 +9,7 @@ #include <stdio.h> #include <stdlib.h> -#include "oslib/font.h" +#include <oslib/font.h> #include "rufl_internal.h" @@ -19,10 +19,10 @@ static int rufl_unicode_map_search_cmp(const void *keyval, const void *datum); * Read a font's metrics (sized for a 1pt font) */ rufl_code rufl_font_metrics(const char *font_family, rufl_style font_style, - os_box *bbox, int *xkern, int *ykern, int *italic, - int *ascent, int *descent, - int *xheight, int *cap_height, - signed char *uline_position, unsigned char *uline_thickness) + os_box *bbox, int32_t *xkern, int32_t *ykern, int32_t *italic, + int32_t *ascent, int32_t *descent, + int32_t *xheight, int32_t *cap_height, + int8_t *uline_position, uint8_t *uline_thickness) { unsigned int font; font_f f; @@ -115,13 +115,14 @@ rufl_code rufl_font_metrics(const char *font_family, rufl_style font_style, rufl_code rufl_glyph_metrics(const char *font_family, rufl_style font_style, unsigned int font_size, const char *string, size_t length, - int *x_bearing, int *y_bearing, - int *width, int *height, - int *x_advance, int *y_advance) + int32_t *x_bearing, int32_t *y_bearing, + int32_t *width, int32_t *height, + int32_t *x_advance, int32_t *y_advance) { + const uint8_t *ustring = (const uint8_t *) string; const char *font_encoding = NULL; unsigned int font, font1, u; - unsigned short u1[2]; + uint32_t u1[2]; struct rufl_character_set *charset; struct rufl_unicode_map_entry *umap_entry = NULL; font_f f; @@ -136,13 +137,14 @@ rufl_code rufl_glyph_metrics(const char *font_family, if (code != rufl_OK) return code; - rufl_utf8_read(string, length, u); + rufl_utf8_read(ustring, length, u); if (charset && rufl_character_set_test(charset, u)) font1 = font; - else if (u < 0x10000) - font1 = rufl_substitution_table[u]; - else - font1 = rufl_CACHE_CORPUS; + else { + font1 = rufl_substitution_table_lookup(u); + if (font1 == NOT_AVAILABLE) + font1 = rufl_CACHE_CORPUS; + } /* Old font managers need the font encoding, too */ if (rufl_old_font_manager && font1 != rufl_CACHE_CORPUS) { @@ -235,7 +237,7 @@ rufl_code rufl_glyph_metrics(const char *font_family, flags = font_GIVEN_BLOCK | font_GIVEN_LENGTH | font_GIVEN_FONT | font_RETURN_BBOX; - u1[0] = (unsigned short)u; + u1[0] = u; u1[1] = 0; if (font1 == rufl_CACHE_CORPUS) { @@ -266,8 +268,8 @@ rufl_code rufl_glyph_metrics(const char *font_family, } else { /* UCS Font Manager */ rufl_fm_error = xfont_scan_string(f, (const char *)u1, - flags | font_GIVEN16_BIT, - 0x7fffffff, 0x7fffffff, &block, 0, 2, + flags | font_GIVEN32_BIT, + 0x7fffffff, 0x7fffffff, &block, 0, 4, 0, &xa, &ya, 0); if (rufl_fm_error) { LOG("xfont_scan_string: 0x%x: %s", diff --git a/src/rufl_paint.c b/src/rufl_paint.c index 8b9edf1..9dc0e3f 100644 --- a/src/rufl_paint.c +++ b/src/rufl_paint.c @@ -10,7 +10,7 @@ #include <stdlib.h> #include <string.h> #include <strings.h> -#include "oslib/font.h" +#include <oslib/font.h> #include "rufl_internal.h" @@ -27,25 +27,25 @@ static const os_trfm trfm_oblique = static rufl_code rufl_process(rufl_action action, const char *font_family, rufl_style font_style, unsigned int font_size, - const char *string0, size_t length, + const uint8_t *string0, size_t length, int x, int y, unsigned int flags, int *width, int click_x, size_t *char_offset, int *actual_x, rufl_callback_t callback, void *context); static rufl_code rufl_process_span(rufl_action action, - unsigned short *s, unsigned int n, + uint32_t *s, unsigned int n, unsigned int font, unsigned int font_size, unsigned int slant, int *x, int y, unsigned int flags, int click_x, size_t *offset, rufl_callback_t callback, void *context); static rufl_code rufl_process_span_old(rufl_action action, - unsigned short *s, unsigned int n, + uint32_t *s, unsigned int n, unsigned int font, unsigned int font_size, unsigned int slant, int *x, int y, unsigned int flags, int click_x, size_t *offset, rufl_callback_t callback, void *context); static int rufl_unicode_map_search_cmp(const void *keyval, const void *datum); static rufl_code rufl_process_not_available(rufl_action action, - unsigned short *s, unsigned int n, + uint32_t *s, unsigned int n, unsigned int font_size, int *x, int y, unsigned int flags, int click_x, size_t *offset, @@ -62,8 +62,9 @@ rufl_code rufl_paint(const char *font_family, rufl_style font_style, int x, int y, unsigned int flags) { return rufl_process(rufl_PAINT, - font_family, font_style, font_size, string, - length, x, y, flags, 0, 0, 0, 0, 0, 0); + font_family, font_style, font_size, + (const uint8_t *) string, length, + x, y, flags, 0, 0, 0, 0, 0, 0); } @@ -77,8 +78,9 @@ rufl_code rufl_width(const char *font_family, rufl_style font_style, int *width) { return rufl_process(rufl_WIDTH, - font_family, font_style, font_size, string, - length, 0, 0, 0, width, 0, 0, 0, 0, 0); + font_family, font_style, font_size, + (const uint8_t *) string, length, + 0, 0, 0, width, 0, 0, 0, 0, 0); } @@ -94,9 +96,9 @@ rufl_code rufl_x_to_offset(const char *font_family, rufl_style font_style, size_t *char_offset, int *actual_x) { return rufl_process(rufl_X_TO_OFFSET, - font_family, font_style, font_size, string, - length, 0, 0, 0, 0, - click_x, char_offset, actual_x, 0, 0); + font_family, font_style, font_size, + (const uint8_t *) string, length, + 0, 0, 0, 0, click_x, char_offset, actual_x, 0, 0); } @@ -111,9 +113,9 @@ rufl_code rufl_split(const char *font_family, rufl_style font_style, size_t *char_offset, int *actual_x) { return rufl_process(rufl_SPLIT, - font_family, font_style, font_size, string, - length, 0, 0, 0, 0, - width, char_offset, actual_x, 0, 0); + font_family, font_style, font_size, + (const uint8_t *) string, length, + 0, 0, 0, 0, width, char_offset, actual_x, 0, 0); } @@ -128,8 +130,9 @@ rufl_code rufl_paint_callback(const char *font_family, rufl_style font_style, rufl_callback_t callback, void *context) { return rufl_process(rufl_PAINT_CALLBACK, - font_family, font_style, font_size, string, - length, x, y, 0, 0, 0, 0, 0, callback, context); + font_family, font_style, font_size, + (const uint8_t *) string, length, + x, y, 0, 0, 0, 0, 0, callback, context); } @@ -139,11 +142,11 @@ rufl_code rufl_paint_callback(const char *font_family, rufl_style font_style, rufl_code rufl_font_bbox(const char *font_family, rufl_style font_style, unsigned int font_size, - int *bbox) + os_box *bbox) { return rufl_process(rufl_FONT_BBOX, font_family, font_style, font_size, 0, - 0, 0, 0, 0, bbox, 0, 0, 0, 0, 0); + 0, 0, 0, 0, (int *) bbox, 0, 0, 0, 0, 0); } @@ -154,12 +157,12 @@ rufl_code rufl_font_bbox(const char *font_family, rufl_style font_style, rufl_code rufl_process(rufl_action action, const char *font_family, rufl_style font_style, unsigned int font_size, - const char *string0, size_t length, + const uint8_t *string0, size_t length, int x, int y, unsigned int flags, int *width, int click_x, size_t *char_offset, int *actual_x, rufl_callback_t callback, void *context) { - unsigned short s[rufl_PROCESS_CHUNK]; + uint32_t s[rufl_PROCESS_CHUNK]; unsigned int font; unsigned int font0, font1; unsigned int n; @@ -168,7 +171,7 @@ rufl_code rufl_process(rufl_action action, size_t offset_u; size_t offset_map[rufl_PROCESS_CHUNK]; unsigned int slant; - const char *string = string0; + const uint8_t *string = string0; struct rufl_character_set *charset; rufl_code code; @@ -225,10 +228,8 @@ rufl_code rufl_process(rufl_action action, font1 = NOT_AVAILABLE; else if (charset && rufl_character_set_test(charset, u)) font1 = font; - else if (u < 0x10000) - font1 = rufl_substitution_table[u]; else - font1 = NOT_AVAILABLE; + font1 = rufl_substitution_table_lookup(u); do { s[0] = u; offset_map[0] = offset_u; @@ -244,10 +245,8 @@ rufl_code rufl_process(rufl_action action, font1 = NOT_AVAILABLE; else if (charset && rufl_character_set_test(charset, u)) font1 = font; - else if (u < 0x10000) - font1 = rufl_substitution_table[u]; else - font1 = NOT_AVAILABLE; + font1 = rufl_substitution_table_lookup(u); if (font1 == font0) n++; } @@ -295,13 +294,13 @@ rufl_code rufl_process(rufl_action action, */ rufl_code rufl_process_span(rufl_action action, - unsigned short *s, unsigned int n, + uint32_t *s, unsigned int n, unsigned int font, unsigned int font_size, unsigned int slant, int *x, int y, unsigned int flags, int click_x, size_t *offset, rufl_callback_t callback, void *context) { - unsigned short *split_point; + uint32_t *split_point; int x_out, y_out; unsigned int i; char font_name[80]; @@ -314,7 +313,9 @@ rufl_code rufl_process_span(rufl_action action, return code; if (action == rufl_FONT_BBOX) { - rufl_fm_error = xfont_read_info(f, &x[0], &x[1], &x[2], &x[3]); + os_box *bbox = (os_box *) x; + rufl_fm_error = xfont_read_info(f, &bbox->x0, &bbox->y0, + &bbox->x1, &bbox->y1); if (rufl_fm_error) return rufl_FONT_MANAGER_ERROR; return rufl_OK; @@ -327,10 +328,10 @@ rufl_code rufl_process_span(rufl_action action, (oblique ? font_GIVEN_TRFM : 0) | font_GIVEN_LENGTH | font_GIVEN_FONT | font_KERN | - font_GIVEN16_BIT | + font_GIVEN32_BIT | ((flags & rufl_BLEND_FONT) ? font_BLEND_FONT : 0), - *x, y, 0, &trfm_oblique, n * 2); + *x, y, 0, &trfm_oblique, n * 4); if (rufl_fm_error) { LOG("xfont_paint: 0x%x: %s", rufl_fm_error->errnum, @@ -350,19 +351,19 @@ rufl_code rufl_process_span(rufl_action action, if (action == rufl_X_TO_OFFSET || action == rufl_SPLIT) { rufl_fm_error = xfont_scan_string(f, (const char *) s, font_GIVEN_LENGTH | font_GIVEN_FONT | - font_KERN | font_GIVEN16_BIT | + font_KERN | font_GIVEN32_BIT | ((action == rufl_X_TO_OFFSET) ? font_RETURN_CARET_POS : 0), (click_x - *x) * 400, 0x7fffffff, 0, 0, - n * 2, + n * 4, (char **)(void *)&split_point, &x_out, &y_out, 0); *offset = split_point - s; } else { rufl_fm_error = xfont_scan_string(f, (const char *) s, font_GIVEN_LENGTH | font_GIVEN_FONT | - font_KERN | font_GIVEN16_BIT, - 0x7fffffff, 0x7fffffff, 0, 0, n * 2, + font_KERN | font_GIVEN32_BIT, + 0x7fffffff, 0x7fffffff, 0, 0, n * 4, 0, &x_out, &y_out, 0); } if (rufl_fm_error) { @@ -383,13 +384,13 @@ rufl_code rufl_process_span(rufl_action action, */ rufl_code rufl_process_span_old(rufl_action action, - unsigned short *s, unsigned int n, + uint32_t *s, unsigned int n, unsigned int font, unsigned int font_size, unsigned int slant, int *x, int y, unsigned int flags, int click_x, size_t *offset, rufl_callback_t callback, void *context) { - char s2[rufl_PROCESS_CHUNK]; + uint8_t s2[rufl_PROCESS_CHUNK]; char *split_point; int x_out, y_out; unsigned int i; @@ -398,12 +399,15 @@ rufl_code rufl_process_span_old(rufl_action action, rufl_code code; if (action == rufl_FONT_BBOX) { + os_box *bbox = (os_box *) x; + /* Don't need encoding for bounding box */ code = rufl_find_font(font, font_size, NULL, &f); if (code != rufl_OK) return code; - rufl_fm_error = xfont_read_info(f, &x[0], &x[1], &x[2], &x[3]); + rufl_fm_error = xfont_read_info(f, &bbox->x0, &bbox->y0, + &bbox->x1, &bbox->y1); if (rufl_fm_error) { LOG("xfont_read_info: 0x%x: %s", rufl_fm_error->errnum, @@ -466,7 +470,8 @@ rufl_code rufl_process_span_old(rufl_action action, return rufl_FONT_MANAGER_ERROR; } - rufl_fm_error = xfont_paint(f, s2, font_OS_UNITS | + rufl_fm_error = xfont_paint(f, (char *) s2, + font_OS_UNITS | (oblique ? font_GIVEN_TRFM : 0) | font_GIVEN_LENGTH | font_GIVEN_FONT | font_KERN | @@ -496,7 +501,7 @@ rufl_code rufl_process_span_old(rufl_action action, /* increment x by width of span */ if (action == rufl_X_TO_OFFSET || action == rufl_SPLIT) { - rufl_fm_error = xfont_scan_string(f, s2, + rufl_fm_error = xfont_scan_string(f, (char *) s2, font_GIVEN_LENGTH | font_GIVEN_FONT | font_KERN | ((action == rufl_X_TO_OFFSET) ? @@ -504,9 +509,9 @@ rufl_code rufl_process_span_old(rufl_action action, (click_x - *x) * 400, 0x7fffffff, 0, 0, i, &split_point, &x_out, &y_out, 0); - *offset += split_point - s2; + *offset += split_point - (char *) s2; } else { - rufl_fm_error = xfont_scan_string(f, s2, + rufl_fm_error = xfont_scan_string(f, (char *) s2, font_GIVEN_LENGTH | font_GIVEN_FONT | font_KERN, 0x7fffffff, 0x7fffffff, 0, 0, i, @@ -546,28 +551,33 @@ int rufl_unicode_map_search_cmp(const void *keyval, const void *datum) */ rufl_code rufl_process_not_available(rufl_action action, - unsigned short *s, unsigned int n, + uint32_t *s, unsigned int n, unsigned int font_size, int *x, int y, unsigned int flags, int click_x, size_t *offset, rufl_callback_t callback, void *context) { - char missing[] = "0000"; - int dx = 7 * font_size / 64; + uint8_t missing[] = "000000"; + const int dx = 7 * font_size / 64; + const int dx3 = 10.5 * font_size / 64; int top_y = y + 5 * font_size / 64; unsigned int i; font_f f; rufl_code code; if (action == rufl_WIDTH) { - *x += n * dx; + for (i = 0; i != n; i++) + *x += (s[i] < 0x10000) ? dx : dx3; return rufl_OK; } else if (action == rufl_X_TO_OFFSET || action == rufl_SPLIT) { - if (click_x - *x < (int) (n * dx)) - *offset = (click_x - *x) / dx; - else - *offset = n; - *x += *offset * dx; + int width = 0; + for (i = 0; i != n; i++) { + if (click_x - *x <= width) + break; + width += (s[i] < 0x10000) ? dx : dx3; + } + *offset = i; + *x += width; return rufl_OK; } @@ -576,45 +586,52 @@ rufl_code rufl_process_not_available(rufl_action action, return code; for (i = 0; i != n; i++) { - missing[0] = "0123456789abcdef"[(s[i] >> 12) & 0xf]; - missing[1] = "0123456789abcdef"[(s[i] >> 8) & 0xf]; - missing[2] = "0123456789abcdef"[(s[i] >> 4) & 0xf]; - missing[3] = "0123456789abcdef"[(s[i] >> 0) & 0xf]; + int offset = (s[i] < 0x10000) ? 2 : 0; + int step = (s[i] < 0x10000) ? 2 : 3; + + missing[0] = "0123456789abcdef"[(s[i] >> 20) & 0xf]; + missing[1] = "0123456789abcdef"[(s[i] >> 16) & 0xf]; + missing[2] = "0123456789abcdef"[(s[i] >> 12) & 0xf]; + missing[3] = "0123456789abcdef"[(s[i] >> 8) & 0xf]; + missing[4] = "0123456789abcdef"[(s[i] >> 4) & 0xf]; + missing[5] = "0123456789abcdef"[(s[i] >> 0) & 0xf]; /* first two characters in top row */ if (action == rufl_PAINT) { - rufl_fm_error = xfont_paint(f, missing, font_OS_UNITS | - font_GIVEN_LENGTH | font_GIVEN_FONT | - font_KERN | + rufl_fm_error = xfont_paint(f, + (char *) (missing + offset), + font_OS_UNITS | font_GIVEN_LENGTH | + font_GIVEN_FONT | font_KERN | ((flags & rufl_BLEND_FONT) ? font_BLEND_FONT : 0), - *x, top_y, 0, 0, 2); + *x, top_y, 0, 0, step); if (rufl_fm_error) return rufl_FONT_MANAGER_ERROR; } else { callback(context, "Corpus.Medium\\ELatin1", - font_size / 2, missing, 0, 2, - *x, top_y); + font_size / 2, missing + offset, 0, + step, *x, top_y); } /* last two characters underneath */ if (action == rufl_PAINT) { - rufl_fm_error = xfont_paint(f, missing + 2, + rufl_fm_error = xfont_paint(f, + (char *) (missing + offset + step), font_OS_UNITS | font_GIVEN_LENGTH | font_GIVEN_FONT | font_KERN | ((flags & rufl_BLEND_FONT) ? font_BLEND_FONT : 0), - *x, y, 0, 0, 2); + *x, y, 0, 0, step); if (rufl_fm_error) return rufl_FONT_MANAGER_ERROR; } else { callback(context, "Corpus.Medium\\ELatin1", - font_size / 2, missing + 2, 0, 2, - *x, y); + font_size / 2, missing + offset + step, + 0, step, *x, y); } - *x += dx; + *x += (s[i] < 0x10000) ? dx : dx3; } return rufl_OK; diff --git a/src/rufl_quit.c b/src/rufl_quit.c index fc429c0..039229c 100644 --- a/src/rufl_quit.c +++ b/src/rufl_quit.c @@ -6,7 +6,7 @@ */ #include <stdlib.h> -#include "oslib/font.h" +#include <oslib/font.h> #include "rufl_internal.h" @@ -24,26 +24,36 @@ void rufl_quit(void) for (i = 0; i != rufl_font_list_entries; i++) { free(rufl_font_list[i].identifier); free(rufl_font_list[i].charset); + if (rufl_font_list[i].umap != NULL) { + size_t j; + for (j = 0; j != rufl_font_list[i].num_umaps; j++) { + free((rufl_font_list[i].umap + j)->encoding); + } + free(rufl_font_list[i].umap); + } } free(rufl_font_list); - rufl_font_list = 0; + rufl_font_list = NULL; + rufl_font_list_entries = 0; for (i = 0; i != rufl_family_list_entries; i++) free((void *) rufl_family_list[i]); free(rufl_family_list); free(rufl_family_map); - rufl_family_list = 0; + rufl_family_map = NULL; + rufl_family_list = NULL; + rufl_family_list_entries = 0; for (i = 0; i != rufl_CACHE_SIZE; i++) { if (rufl_cache[i].font != rufl_CACHE_NONE) { xfont_lose_font(rufl_cache[i].f); rufl_cache[i].font = rufl_CACHE_NONE; } - } + } + rufl_cache_time = 0; free(rufl_family_menu); - rufl_family_menu = 0; + rufl_family_menu = NULL; - free(rufl_substitution_table); - rufl_substitution_table = 0; + rufl_substitution_table_fini(); } diff --git a/src/rufl_substitution_table.c b/src/rufl_substitution_table.c new file mode 100644 index 0000000..0bdf78d --- /dev/null +++ b/src/rufl_substitution_table.c @@ -0,0 +1,1143 @@ +/* + * This file is part of RUfl + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license + * Copyright 2006 James Bursa <james@semichrome.net> + */ + +#include <assert.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include "rufl_internal.h" + +#undef RUFL_SUBSTITUTION_TABLE_DEBUG + +/** + * Base type for a substitution table. + */ +struct rufl_substitution_table { + /** Description of table implementation */ + const char *desc; + /** Look up a Unicode codepoint. */ + unsigned int (*lookup)(const struct rufl_substitution_table *t, + uint32_t u); + /** Free the resources used by this table */ + void (*free)(struct rufl_substitution_table *t); + /** Dump the contents of this table to stdout */ + void (*dump)(const struct rufl_substitution_table *t, + unsigned int plane); + /** Compute the storage size of this table */ + size_t (*size)(const struct rufl_substitution_table *t, + unsigned int *glyph_count); +}; + +/** + * Implementation of a substitution table using direct lookup. + */ +struct rufl_substitution_table_direct { + struct rufl_substitution_table base; + + /** Table index. + * + * Each entry represents a block of 256 codepoints, so i[k] refers + * to codepoints [256*k, 256*(k+1)). The value is an offset into + * the block table. + */ + uint8_t index[256]; + + /** Bits per block table entry. Will be 8 or 16. */ + uint8_t bits_per_entry; + + /** Substitution table. + * + * Entries are the index into rufl_font_list of a font providing a + * substitution glyph for this codepoint or NOT_AVAILABLE. + * + * Note that, although this is defined as a 16bit type, + * the actual field width is indicated by bits_per_entry (and, + * if bits_per_entry is 8, then (NOT_AVAILABLE & 0xff) represents + * a missing glyph). + */ + uint16_t *table; +}; + +/** + * Implementation of a substitution table using a perfect hash. + * + * A perfect hash constructed at library initialisation time using the + * CHD algorithm. Hash entries are found via a two-step process: + * + * 1. apply a first-stage hash to the key to find the bucket + * in which the corresponding entry should be found. + * 2. apply a second-stage hash to the key and the stored + * displacement value for the bucket to find the index + * into the substitution table. + */ +struct rufl_substitution_table_chd { + struct rufl_substitution_table base; + + uint32_t num_buckets; /**< Number of buckets in the hash */ + uint32_t num_slots; /**< Number of slots in the table */ + /** Substitution table. + * + * Fields in the substitution table have the following format: + * + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Unicode codepoint | Font identifier | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * where: + * + * unicode codepoint: 16 bits + * The low 16 bits of the Unicode codepoint value. + * + * font identifier: 16 bits + * The index into rufl_font_list of a font providing a + * substitution glyph for this codepoint or NOT_AVAILABLE. + * + * Note that, as the substitution table is sparse and may not be + * fully populated, it is necessary to verify that the Unicode + * codepoint matches the key being hashed and that the font + * identifier is not NOT_AVAILABLE. If either of these tests + * fail, no font provides a suitable glyph and the not available + * path should be taken. + */ + uint32_t *table; + uint8_t bits_per_entry; /**< Bits per displacement bitmap entry */ + /** Displacement bitmap. + * + * The displacement values are stored in a bitmap of num_buckets + * fields each being bits_per_entry wide. Both values are computed + * at runtime. + */ + uint8_t displacement_map[]; +}; +/** Font substitution tables -- one per plane */ +static struct rufl_substitution_table *rufl_substitution_table[17]; + +/** + * Round an unsigned 32bit value up to the next power of 2 + */ +static uint32_t ceil2(uint32_t val) +{ + val--; + val |= (val >> 1); + val |= (val >> 2); + val |= (val >> 4); + val |= (val >> 8); + val |= (val >> 16); + val++; + val += (val == 0); + return val; +} + +/** + * Compute the number of bits needed to store a value + */ +static uint32_t bits_needed(uint32_t val) +{ + int32_t result = 0; + + if (val == 0) + return 1; + + if ((val & (val - 1))) { + /* Not a power of 2: round up */ + val = ceil2(val); + /* Will need one fewer bit than we're about to count */ + result = -1; + } + + while (val > 0) { + result += 1; + val >>= 1; + } + + return (uint32_t) result; +} + +/** + * Perform one round of MurmurHash2 + */ +static uint32_t mround(uint32_t val, uint32_t s) +{ + val *= 0x5db1e995; + val ^= (val >> 24); + val *= 0x5db1e995; + val ^= (s * 0x5db1e995); + + return val; +} + +/** + * Perform the MurmurHash2 mixing step + */ +static uint32_t mmix(uint32_t val) +{ + val ^= (val >> 13); + val *= 0x5db1e995; + val ^= (val >> 15); + + return val; +} + +/** + * First-stage hash (i.e. g(x)) for substitution table. + * + * As we know that the input values are Unicode codepoints, + * do some trivial bit manipulation, which has reasonable + * distribution properties. + */ +static uint32_t hash1(uint32_t val) +{ + val ^= (val >> 7); + val ^= (val << 3); + val ^= (val >> 4); + return val; +} + +/** + * Second-stage hash (i.e. f(d, x)) for substitution table. + * + * Apply MurmurHash2 to the value and displacement + */ +static uint32_t hash2(uint32_t val, uint32_t d) +{ + return mmix(mround(val, mround(d, 4))); +} + +/** + * Comparison function for table entries. + * + * We use this when sorting the intermediate table for CHD. + */ +static int table_chd_cmp(const void *a, const void *b) +{ + /* We're only interested in the CHD metadata here. + * (i.e. the computed value of g(x) and the bucket size) */ + const uint64_t aa = (*(const uint64_t *) a) & 0x000fffff00000000llu; + const uint64_t bb = (*(const uint64_t *) b) & 0x000fffff00000000llu; + + if (aa > bb) + return -1; + else if (aa < bb) + return 1; + return 0; +} + +/** + * Test that all specified bits in a bit map are clear and set them if so. + * + * \param bitmap Bit map to inspect + * \param idx Table of indices to inspect + * \param len Number of entries in index table + * \return True if all bits were clear. False otherwise. + */ +static bool test_and_set_bits(uint8_t *bitmap, const uint32_t *idx, size_t len) +{ + unsigned int i; + bool result = true; + + /* Test if all specified bits are clear */ + for (i = 0; i != len; i++) { + const uint32_t byte = (idx[i] >> 3); + const uint32_t bit = (idx[i] & 0x7); + + result &= ((bitmap[byte] & (1 << bit)) == 0); + } + + if (result) { + /* They are, so set them */ + for (i = 0; i != len; i++) { + const uint32_t byte = (idx[i] >> 3); + const uint32_t bit = (idx[i] & 0x7); + + bitmap[byte] |= (1 << bit); + } + } + + return result; +} + +static void rufl_substitution_table_free_chd( + struct rufl_substitution_table *t) +{ + free(((struct rufl_substitution_table_chd *)t)->table); + free(t); +} + + +static unsigned int rufl_substitution_table_lookup_chd( + const struct rufl_substitution_table *ts, uint32_t u) +{ + const struct rufl_substitution_table_chd *t = (const void *) ts; + uint32_t displacement = 0; + uint32_t f, g = hash1(u & 0xffff) & (t->num_buckets - 1); + uint32_t bits_to_read = t->bits_per_entry; + uint32_t offset_bits = g * bits_to_read; + const uint8_t *pread = &t->displacement_map[offset_bits >> 3]; + + offset_bits &= 7; + + while (bits_to_read > 0) { + uint32_t space_available = (8 - offset_bits); + if (space_available > bits_to_read) + space_available = bits_to_read; + + displacement <<= space_available; + displacement |= (*pread & (0xff >> offset_bits)) >> + (8 - space_available - offset_bits); + + offset_bits += space_available; + if (offset_bits >= 8) { + pread++; + offset_bits = 0; + } + bits_to_read -= space_available; + } + + f = hash2((u & 0xffff), displacement) & (t->num_slots - 1); + + if ((t->table[f] & 0xffff) != NOT_AVAILABLE && + ((t->table[f] >> 16) & 0xffff) == (u & 0xffff)) + return t->table[f] & 0xffff; + + return NOT_AVAILABLE; +} + +static int table_dump_cmp(const void *a, const void *b) +{ + const uint32_t aa = (*(const uint32_t *) a); + const uint32_t bb = (*(const uint32_t *) b); + + if (aa > bb) + return 1; + else if (aa < bb) + return -1; + return 0; +} + +static void rufl_substitution_table_dump_chd( + const struct rufl_substitution_table *ts, unsigned int plane) +{ + const struct rufl_substitution_table_chd *t = (const void *) ts; + unsigned int font; + unsigned int u, prev; + uint32_t *table; + + table = malloc(t->num_slots * sizeof(*table)); + if (table == NULL) + return; + + memcpy(table, t->table, t->num_slots * sizeof(*table)); + + qsort(table, t->num_slots, sizeof(*table), table_dump_cmp); + + u = 0; + while (u < t->num_slots) { + prev = u; + font = table[prev] & 0xffff; + while (u < t->num_slots && font == (table[u] & 0xffff) && + ((u == prev) || + ((table[u - 1] >> 16) == + ((table[u] >> 16) - 1)))) + u++; + if (font != NOT_AVAILABLE) + printf(" %x-%x => %u \"%s\"\n", + (plane << 16) | (table[prev] >> 16), + (plane << 16) | (table[u - 1] >> 16), + font, rufl_font_list[font].identifier); + } + + free(table); +} + +static size_t rufl_substitution_table_size_chd( + const struct rufl_substitution_table *ts, + unsigned int *glyph_count) +{ + const struct rufl_substitution_table_chd *t = (const void *) ts; + size_t size = sizeof(*t); + unsigned int count = 0; + uint32_t i; + + /* Add on displacement map size */ + size += (t->num_buckets * t->bits_per_entry + 7) >> 3; + + /* Add on table size */ + size += t->num_slots * sizeof(*t->table); + + /* Count glyphs */ + for (i = 0; i < t->num_slots; i++) { + if ((t->table[i] & 0xffff) != NOT_AVAILABLE) + count++; + } + if (glyph_count != NULL) + *glyph_count = count; + + return size; +} + +/** + * Create the final substitution table from the intermediate parts + * + * \param table Substitution table + * \param table_entries Number of entries in table + * \param buckets Number of CHD buckets + * \param range Number of slots in final table + * \param max_displacement max(displacements) + * \param displacements Table of displacement values. One per bucket. + * \param substitution_table Location to receive result. + */ +static rufl_code create_substitution_table_chd(uint64_t *table, + size_t table_entries, uint32_t buckets, uint32_t range, + uint32_t max_displacement, uint32_t *displacements, + struct rufl_substitution_table **substitution_table) +{ + struct rufl_substitution_table_chd *subst_table; + uint64_t *t64; + size_t subst_table_size; + unsigned int i; + +#ifdef RUFL_SUBSTITUTION_TABLE_DEBUG + LOG("max displacement of %u requires %u bits", + max_displacement, bits_needed(max_displacement)); +#endif + + subst_table_size = offsetof(struct rufl_substitution_table_chd, + displacement_map) + + ((buckets * bits_needed(max_displacement) + 7) >> 3); + + subst_table = calloc(subst_table_size, 1); + if (!subst_table) + return rufl_OUT_OF_MEMORY; + + /* We know there are at least table_entries in the table, but + * we should now resize it to the size of the target hashtable. + * We still want each entry to be 64bits wide at this point. */ + t64 = realloc(table, range * sizeof(*t64)); + if (!t64) { + free(subst_table); + return rufl_OUT_OF_MEMORY; + } + /* Initialise unused slots */ + for (i = table_entries; i < range; i++) { + t64[i] = NOT_AVAILABLE; + } + + subst_table->base.desc = "CHD"; + subst_table->base.lookup = rufl_substitution_table_lookup_chd; + subst_table->base.free = rufl_substitution_table_free_chd; + subst_table->base.dump = rufl_substitution_table_dump_chd; + subst_table->base.size = rufl_substitution_table_size_chd; + subst_table->num_buckets = buckets; + subst_table->num_slots = range; + subst_table->bits_per_entry = bits_needed(max_displacement); + subst_table->table = (uint32_t *) t64; + + /* Fill in displacement map */ + //XXX: compress map using Fredriksson-Nikitin encoding? + for (i = 0; i < buckets; i++) { + uint32_t offset_bits = i * subst_table->bits_per_entry; + uint32_t bits_to_write = subst_table->bits_per_entry; + uint8_t *pwrite = + &subst_table->displacement_map[offset_bits >> 3]; + + offset_bits &= 7; + + while (bits_to_write > 0) { + uint32_t space_available = (8 - offset_bits); + uint32_t mask = 0, mask_idx; + + if (space_available > bits_to_write) + space_available = bits_to_write; + + for (mask_idx = 0; mask_idx != space_available; + mask_idx++) { + mask <<= 1; + mask |= 1; + } + + *pwrite |= ((displacements[i] >> + (bits_to_write - space_available)) & mask) << + (8 - offset_bits - space_available); + pwrite++; + offset_bits = 0; + bits_to_write -= space_available; + } + } + + /* Shuffle table data so the indices match the hash values */ + for (i = 0; i < table_entries; ) { + uint32_t f, g; + uint64_t tmp; + + if (t64[i] == NOT_AVAILABLE) { + i++; + continue; + } + + g = ((t64[i] >> 32) & 0xffff); + f = hash2((t64[i] >> 16) & 0xffff, + displacements[g]) & (range - 1); + + /* Exchange this entry with the one in the slot at f.*/ + if (f != i) { + tmp = t64[f]; + t64[f] = t64[i]; + t64[i] = tmp; + } else { + /* Reconsider this slot unless it already + * had the correct entry */ + i++; + } + } + /* Strip all the CHD metadata out of the final table. + * We can simply drop the top 32bits of each entry by + * compacting the entries. */ + for (i = 0; i < range; i++) { + subst_table->table[i] = t64[i] & 0xffffffffu; + } + + /* Shrink the table to its final size. If this fails, leave + * the existing data intact as it's correct -- we just have + * twice the storage usage we need. */ + subst_table->table = realloc(t64, + range * sizeof(*subst_table->table)); + if (!subst_table->table) + subst_table->table = (uint32_t *) t64; + + *substitution_table = &subst_table->base; + +#ifdef RUFL_SUBSTITUTION_TABLE_DEBUG + LOG("table size(%zu) entries %zu buckets(%u@%ubpe => %u)", + subst_table->num_slots * sizeof(*subst_table->table), + table_entries, + subst_table->num_buckets, + subst_table->bits_per_entry, + (subst_table->num_buckets * + subst_table->bits_per_entry + 7) >> 3); +#endif + + return rufl_OK; +} + +/** + * Compute a perfect hash to address the substitution table. + * + * We use the CHD algorithm to do this. + * (https://doi.org/10.1007/978-3-642-04128-0_61 ; + * http://cmph.sourceforge.net/papers/esa09.pdf) + * + * A more recent alternative might be RecSplit + * (https://arxiv.org/abs/1910.06416v2). + * + * \param table Pre-filled table of raw substitution data + * \param table_entries Number of entries in the table + * \param substitution_table Location to receive result + */ +static rufl_code chd(uint64_t *table, size_t table_entries, + struct rufl_substitution_table **substitution_table) +{ + /** Number of buckets assuming an average bucket size of 4 */ + const uint32_t buckets = ceil2((table_entries + 3) & ~3); + /** Number of output hash slots assuming a load factor of 1 */ + const uint32_t range = ceil2(table_entries); + uint32_t bucket_size, max_displacement = 0; + unsigned int i; + uint8_t *entries_per_bucket, *bitmap; + uint32_t *displacements; + rufl_code result = rufl_OK; + +#ifdef RUFL_SUBSTITUTION_TABLE_DEBUG + LOG("hashing %zu entries into %u buckets with range %u", + table_entries, buckets, range); +#endif + + entries_per_bucket = calloc(buckets, sizeof(*entries_per_bucket)); + if (!entries_per_bucket) + return rufl_OUT_OF_MEMORY; + + /* Round up bitmap size to the next byte boundary */ + bitmap = calloc(((range + 7) & ~7) >> 3, 1); + if (!bitmap) { + free(entries_per_bucket); + return rufl_OUT_OF_MEMORY; + } + + displacements = calloc(buckets, sizeof(*displacements)); + if (!displacements) { + free(bitmap); + free(entries_per_bucket); + return rufl_OUT_OF_MEMORY; + } + + /* Compute g(x) for each entry, placing them into buckets */ + for (i = 0; i < table_entries; i++) { + uint64_t g = hash1((table[i] >> 16) & 0xffff) & (buckets - 1); + + /* Insert hash into entry (it's 16 bits at most, + * so use bits 32-47) */ + table[i] |= ((g & 0xffff) << 32); + + entries_per_bucket[g]++; + } + + /* Inject bucket size into entries */ + for (i = 0; i < table_entries; i++) { + uint32_t g = ((table[i] >> 32) & 0xffff); + + /* With a target bucket size of 4, do not expect + * >= twice that number of entries in the largest + * bucket. If there are, the hash function needs + * work (we allocate 4 bits for the bucket size, + * so should have sufficient headroom). */ + if (entries_per_bucket[g] >= 8) + LOG("unexpectedly large bucket %u", + entries_per_bucket[g]); + + /* Stash bucket size into bits 48-51 of the entry */ + table[i] |= ((uint64_t)entries_per_bucket[g] << 48); + } + + /* Bits 52-63 of table entries are currently unused */ + + free(entries_per_bucket); + + /* Sort entries in descending bucket size order */ + qsort(table, table_entries, sizeof(*table), table_chd_cmp); + + /* Compute f(x) for each bucket, finding a unique mapping */ + for (i = 0; i < table_entries; i += bucket_size) { + const uint32_t g = ((table[i] >> 32) & 0xffff); + uint32_t hashes[8], num_hashes; + uint32_t d = 0; + + bucket_size = ((table[i] >> 48) & 0xf); + + do { + uint32_t j, k; + + d++; + num_hashes = 0; + + for (j = 0; j != bucket_size; j++) { + uint32_t f = hash2( + (table[i+j] >> 16) & 0xffff, d) & + (range - 1); + for (k = 0; k < num_hashes; k++) { + if (f == hashes[k]) + break; + } + if (k == num_hashes) { + hashes[num_hashes] = f; + num_hashes++; + } + } + } while (num_hashes != bucket_size || !test_and_set_bits( + bitmap, hashes, num_hashes)); + + displacements[g] = d; + if (d > max_displacement) + max_displacement = d; + } + + free(bitmap); + + result = create_substitution_table_chd(table, table_entries, + buckets, range, max_displacement, displacements, + substitution_table); + free(displacements); + + return result; +} + +static size_t rufl_substitution_table_estimate_size_chd(size_t table_entries, + size_t blocks_used) +{ + size_t size = sizeof(struct rufl_substitution_table_chd); + + (void) blocks_used; + + /** Number of buckets assuming an average bucket size of 4 */ + const uint32_t buckets = ceil2((table_entries + 3) & ~3); + /** Number of output hash slots assuming a load factor of 1 */ + const uint32_t range = ceil2(table_entries); + + /* Conservatively assume 6 bits per displacement map entry */ + size += (buckets * 6 + 7) >> 3; + + /* Add on table size */ + size += range * 4; + + return size; +} + +/****************************************************************************/ + +static void rufl_substitution_table_free_direct( + struct rufl_substitution_table *t) +{ + free(((struct rufl_substitution_table_direct *)t)->table); + free(t); +} + + +static unsigned int rufl_substitution_table_lookup_direct( + const struct rufl_substitution_table *ts, uint32_t u) +{ + const struct rufl_substitution_table_direct *t = (const void *) ts; + uint32_t block = (u >> 8) & 0xff; + uint32_t slot = (u & 0xff); + unsigned int font; + + if (t->bits_per_entry == 8) { + font = ((uint8_t *) t->table)[t->index[block] * 256 + slot]; + if (font == (NOT_AVAILABLE & 0xff)) + font = NOT_AVAILABLE; + } else + font = t->table[t->index[block] * 256 + slot]; + + return font; +} + +static void rufl_substitution_table_dump_direct( + const struct rufl_substitution_table *ts, unsigned int plane) +{ + const struct rufl_substitution_table_direct *t = (const void *) ts; + unsigned int font, na; + unsigned int u, prev; + uint8_t *t8 = (uint8_t *) t->table; + + na = NOT_AVAILABLE & ((t->bits_per_entry == 8) ? 0xff : 0xffff); + +#define LOOKUP(u) (t->bits_per_entry == 8 \ + ? t8[t->index[(u >> 8) & 0xff] * 256 + (u & 0xff)] \ + : t->table[t->index[(u >> 8) & 0xff] * 256 + (u & 0xff)]) + + u = 0; + while (u < 0x10000) { + prev = u; + font = LOOKUP(u); + while (u < 0x10000 && font == LOOKUP(u)) + u++; + if (font != na) + printf(" %x-%x => %u \"%s\"\n", + (plane << 16) | prev, + (plane << 16) | (u - 1), + font, rufl_font_list[font].identifier); + } + +#undef LOOKUP +} + +static size_t rufl_substitution_table_size_direct( + const struct rufl_substitution_table *ts, + unsigned int *glyph_count) +{ + const struct rufl_substitution_table_direct *t = (const void *) ts; + size_t size = sizeof(*t); + unsigned int i, block_idx = 0; + unsigned int count = 0, na; + + /* Find the largest block index (blocks are contiguous) */ + for (i = 0; i < 256; i++) + if (t->index[i] > block_idx) + block_idx = t->index[i]; + + /* Add on table size */ + size += (t->bits_per_entry * (block_idx + 1) * 256) >> 3; + + /* Count glyphs */ + na = NOT_AVAILABLE & ((t->bits_per_entry == 8) ? 0xff : 0xffff); + for (i = 0; i < 0x10000; i++) { + const uint8_t *t8 = (const uint8_t *) t->table; + +#define LOOKUP(u) (t->bits_per_entry == 8 \ + ? t8[t->index[(u >> 8) & 0xff] * 256 + (u & 0xff)] \ + : t->table[t->index[(u >> 8) & 0xff] * 256 + (u & 0xff)]) + + if (LOOKUP(i) != na) + count++; +#undef LOOKUP + } + if (glyph_count != NULL) + *glyph_count = count; + + return size; +} + +/** + * Construct a direct-mapped substitution table + */ +static rufl_code direct(uint64_t *table, size_t table_entries, + size_t blocks_used, const uint8_t block_histogram[256], + struct rufl_substitution_table **substitution_table) +{ + struct rufl_substitution_table_direct *subst_table; + size_t blocks_needed, table_size; + unsigned int i, block; + + subst_table = calloc(sizeof(*subst_table), 1); + if (!subst_table) + return rufl_OUT_OF_MEMORY; + + subst_table->base.desc = "Direct"; + subst_table->base.lookup = rufl_substitution_table_lookup_direct; + subst_table->base.free = rufl_substitution_table_free_direct; + subst_table->base.dump = rufl_substitution_table_dump_direct; + subst_table->base.size = rufl_substitution_table_size_direct; + /* We can use 8bits per entry if there are fewer than 255 fonts */ + subst_table->bits_per_entry = rufl_font_list_entries < 255 ? 8 : 16; + + /* Need one extra block if there's at least one free */ + blocks_needed = (blocks_used < 256) ? + (blocks_used + 1) : blocks_used; + + table_size = (256 * blocks_needed * subst_table->bits_per_entry) >> 3; + + /* Populate block index */ + for (i = 0, block = 0; i != 256; i++) { + if (block_histogram[i] == 0) { + subst_table->index[i] = blocks_used; + } else { + subst_table->index[i] = block; + block++; + } + } + +#ifdef RUFL_SUBSTITUTION_TABLE_DEBUG + LOG("blocks-used = %zu blocks-needed = %zu" + " bits-per-entry = %u size = %zu", + blocks_used, blocks_needed, subst_table->bits_per_entry, + table_size); +#endif + + /* Allocate table */ + //XXX: can we just rearrange the existing one in-place? + subst_table->table = malloc(table_size * (subst_table->bits_per_entry >> 3)); + if (!subst_table->table) { + free(subst_table); + return rufl_OUT_OF_MEMORY; + } + /* Fill it with NOT_AVAILABLE */ + if (subst_table->bits_per_entry == 8) { + memset(subst_table->table, (NOT_AVAILABLE & 0xff), table_size); + } else { + for (i = 0; i < table_size; i++) + subst_table->table[i] = NOT_AVAILABLE; + } + + /* Populate the table */ + for (i = 0; i < table_entries; i++) { + uint64_t val = table[i]; + uint32_t slot = (val >> 16) & 0xff; + block = subst_table->index[(val >> 24) & 0xff]; + + if (subst_table->bits_per_entry == 8) { + uint8_t *t8 = (uint8_t *) subst_table->table; + t8[256 * block + slot] = (val & 0xff); + } else { + subst_table->table[256 * block + slot] = (val & 0xffff); + } + } + + free(table); + + *substitution_table = &subst_table->base; + + return rufl_OK; +} + +static size_t rufl_substitution_table_estimate_size_direct(size_t table_entries, + size_t blocks_used) +{ + size_t size = sizeof(struct rufl_substitution_table_direct); + + (void) table_entries; + + /* Add one for empty block */ + if (blocks_used < 256) + blocks_used += 1; + + /* We can use 8bits per entry if there are fewer than 255 fonts */ + size += blocks_used * 256 * (rufl_font_list_entries < 255 ? 1 : 2); + + return size; +} + +/****************************************************************************/ + +/** + * Populate the substitution map for a given block + */ +static void fill_map_for_block(const struct rufl_character_set **charsets, + uint32_t block, uint16_t map_for_block[256]) +{ + unsigned int i, u; + + for (i = 0; i != rufl_font_list_entries; i++) { + if (!charsets[i]) + continue; + + if (charsets[i]->index[block] == BLOCK_FULL) { + for (u = 0; u != 256; u++) + if (map_for_block[u] == NOT_AVAILABLE) + map_for_block[u] = i; + } else if (charsets[i]->index[block] != BLOCK_EMPTY) { + const uint8_t *blk = charsets[i]->block[ + charsets[i]->index[block]]; + for (u = 0; u != 256; u++) { + if (map_for_block[u] == NOT_AVAILABLE && + (blk[(u>>3)] & (1 << (u&7)))) { + map_for_block[u] = i; + } + } + } + } +} + +/** + * Create a substitution table for the plane specified + */ +static rufl_code create_substitution_table_for_plane(unsigned int plane) +{ + unsigned int i; + unsigned int block; + unsigned int u; + const struct rufl_character_set **charsets; + const struct rufl_character_set *charset; + unsigned int num_charsets; + uint64_t *table; + size_t table_size; + size_t table_entries; + uint8_t block_histogram[256]; + size_t blocks_used; + size_t direct_size, chd_size; + rufl_code result; + + charsets = malloc(rufl_font_list_entries * sizeof(*charsets)); + if (!charsets) { + LOG("malloc(%zu) failed", + rufl_font_list_entries * sizeof(*charsets)); + return rufl_OUT_OF_MEMORY; + } + + /* Find fonts that have charsets for this plane */ + num_charsets = 0; + for (i = 0; i != rufl_font_list_entries; i++) { + charset = rufl_font_list[i].charset; + if (!charset) { + charsets[i] = NULL; + continue; + } + + while (PLANE_ID(charset->metadata) != plane && + EXTENSION_FOLLOWS(charset->metadata)) { + charset = (void *)(((uint8_t *)charset) + + PLANE_SIZE(charset->metadata)); + } + if (PLANE_ID(charset->metadata) != plane) + charset = NULL; + charsets[i] = charset; + num_charsets++; + } + if (num_charsets == 0) { +#ifdef RUFL_SUBSTITUTION_TABLE_DEBUG + LOG("no charsets for plane %u", plane); +#endif + rufl_substitution_table[plane] = NULL; + free(charsets); + return rufl_OK; + } + + table = malloc(1024 * sizeof(*table)); + if (!table) { + LOG("malloc(%zu) failed", 1024 * sizeof(*table)); + free(charsets); + return rufl_OUT_OF_MEMORY; + } + table_size = 1024; + table_entries = 0; + + /* Process each block, finding fonts that have glyphs */ + blocks_used = 0; + memset(block_histogram, 0, 256); + for (block = 0; block != 256; block++) { + size_t prev_table_entries = table_entries; + uint16_t map_for_block[256]; + for (i = 0; i != 256; i++) + map_for_block[i] = NOT_AVAILABLE; + + fill_map_for_block(charsets, block, map_for_block); + + /* Merge block map into table */ + for (i = 0; i != 256; i++) { + if (map_for_block[i] == NOT_AVAILABLE) + continue; + + u = (block << 8) | i; + table[table_entries] = (u << 16) | map_for_block[i]; + if (++table_entries == table_size) { + uint64_t *tmp = realloc(table, + 2 * table_size * + sizeof(*table)); + if (!tmp) { + LOG("realloc(%zu) failed", + 2 * table_size * + sizeof(*table)); + free(table); + return rufl_OUT_OF_MEMORY; + } + + table = tmp; + table_size *= 2; + } + } + + block_histogram[block] = (table_entries != prev_table_entries); + if (block_histogram[block] != 0) + blocks_used++; + } + + if (table_entries == 0) { +#ifdef RUFL_SUBSTITUTION_TABLE_DEBUG + LOG("no glyphs for plane %u", plane); +#endif + rufl_substitution_table[plane] = NULL; + free(table); + free(charsets); + return rufl_OK; + } + + /* Build final substitution table using whichever implementation + * estimates the smallest storage requirements. */ + direct_size = rufl_substitution_table_estimate_size_direct( + table_entries, blocks_used); + chd_size = rufl_substitution_table_estimate_size_chd( + table_entries, blocks_used); + if (direct_size <= chd_size) { + result = direct(table, table_entries, blocks_used, + block_histogram, + &rufl_substitution_table[plane]); + } else { + result = chd(table, table_entries, + &rufl_substitution_table[plane]); + } + +#ifdef RUFL_SUBSTITUTION_TABLE_DEBUG + LOG("plane %u: table-entries = %zu blocks-used = %zu" + " estimated-direct-size = %zu estimated-chd-size = %zu" + " actual-size = %zu", + plane, table_entries, blocks_used, + direct_size, chd_size, + rufl_substitution_table[plane] ? + rufl_substitution_table[plane]->size( + rufl_substitution_table[plane], + NULL) : 0); +#endif + + free(charsets); + + return result; +} + +/** + * Construct the font substitution table. + */ + +rufl_code rufl_substitution_table_init(void) +{ + unsigned int plane; + rufl_code rc; + + for (plane = 0; plane < 17; plane++) { + rc = create_substitution_table_for_plane(plane); + if (rc != rufl_OK) { + while (plane > 0) { + plane--; + if (!rufl_substitution_table[plane]) + continue; + rufl_substitution_table[plane]->free( + rufl_substitution_table[plane]); + } + return rc; + } + } + + return rufl_OK; +} + +/** + * Destroy the substitution table and clean up its resources + */ + +void rufl_substitution_table_fini(void) +{ + unsigned int plane; + + for (plane = 0; plane < 17; plane++) { + if (rufl_substitution_table[plane] != NULL) + rufl_substitution_table[plane]->free( + rufl_substitution_table[plane]); + rufl_substitution_table[plane] = NULL; + } +} + +/** + * Look up a Unicode codepoint in the substitution table + */ + +unsigned int rufl_substitution_table_lookup(uint32_t u) +{ + unsigned int plane = (u >> 16) & 0x1f; + + if (17 <= plane || !rufl_substitution_table[plane]) + return NOT_AVAILABLE; + + return rufl_substitution_table[plane]->lookup( + rufl_substitution_table[plane], u); +} + +/** + * Dump a representation of the substitution table to stdout. + */ + +void rufl_substitution_table_dump(void) +{ + unsigned int plane, glyphs = 0; + size_t size = 0; + + for (plane = 0; plane < 17; plane++) { + if (!rufl_substitution_table[plane]) + continue; + rufl_substitution_table[plane]->dump( + rufl_substitution_table[plane], plane); + } + + for (plane = 0; plane < 17; plane++) { + size_t plane_size; + unsigned int plane_glyphs; + const char *plane_desc; + + if (!rufl_substitution_table[plane]) { + plane_size = 0; + plane_glyphs = 0; + plane_desc = "None"; + } else { + plane_size = rufl_substitution_table[plane]->size( + rufl_substitution_table[plane], &plane_glyphs); + plane_desc = rufl_substitution_table[plane]->desc; + } + size += plane_size; + glyphs += plane_glyphs; + printf(" Storage for plane %2d: %8zu bytes %7u glyphs (%s)\n", + plane + 1, plane_size, plane_glyphs, plane_desc); + } + + printf(" Total substitution table storage: %8zu bytes %7u glyphs\n", + size + sizeof(rufl_substitution_table), glyphs); +} diff --git a/test/INDEX b/test/INDEX new file mode 100644 index 0000000..417b032 --- /dev/null +++ b/test/INDEX @@ -0,0 +1,8 @@ +# Index for testcases +# +# Test Description DataDir +nofonts Ensure a lack of fonts "works" +ucsinit Ensure that UCS FM initialisation works +olducsinit Ensure that UCS FM (pre 3.64) initialisation works +oldfminit Ensure that non-UCS FM initialisation works oldfminit +manyfonts Ensure that more than 256 fonts works diff --git a/test/Makefile b/test/Makefile index 2bfa58b..65c3170 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,4 +1,16 @@ # Tests -DIR_TEST_ITEMS := rufl_test:rufl_test.c rufl_chars:rufl_chars.c +ifeq ($(findstring -riscos,$(HOST)),-riscos) + DIR_TEST_ITEMS := $(DIR_TEST_ITEMS) \ + rufl_test:rufl_test.c \ + rufl_chars:rufl_chars.c + # We do not want to run tests if building for RISC OS + TESTRUNNER := echo +else + DIR_TEST_ITEMS := nofonts:nofonts.c;harness.c;mocks.c \ + oldfminit:oldfminit.c;harness.c;mocks.c \ + olducsinit:olducsinit.c;harness.c;mocks.c \ + ucsinit:ucsinit.c;harness.c;mocks.c \ + manyfonts:manyfonts.c;harness.c;mocks.c +endif include $(NSBUILD)/Makefile.subdir diff --git a/test/data/oldfminit/Allerta b/test/data/oldfminit/Allerta new file mode 100644 index 0000000..e5b633e --- /dev/null +++ b/test/data/oldfminit/Allerta @@ -0,0 +1,7 @@ +% Encoding file for font 'Allerta' +/ +/x +/w + % xyz abc/y + /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /space /exclam /quotedbl /.notdef /dollar /percent /ampersand /quotesingle /parenleft /parenright /asterisk /plus /comma /hyphen /period /slash /zero /one /two /three /four /five /six /seven /eight /nine /colon /semicolon /less /equal /greater /question /at /A /B /C /D /E /F /G /H /I /J /K /L /M /N /O /P /Q /R /S /T /U /V /W /X /Y /Z /bracketleft /backslash /bracketright /.notdef /.notdef /grave /a /b /c /d /e /f /g /h /i /j /k /l /m /n /o /p /q /r /s /t /u /v /w /x /y /z /.notdef /bar /.notdef /.notdef /.notdef /Euro /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /quoteleft /quoteright /guilsinglleft /guilsinglright /quotedblleft /quotedblright /.notdef /endash /emdash /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /space /exclamdown /cent /sterling /currency /yen /.notdef /.notdef /dieresis /.notdef /.notdef /guillemotleft /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /acute /.notdef /.notdef /.notdef /cedilla /.notdef /.notdef /guillemotright /.notdef /.notdef /.notdef /questiondown /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /.notdef /Ccedilla /Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis /Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply /.notdef /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls /agrave /aacute /acircumflex /atilde /adieresis /aring /.notdef /ccedilla /egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis /eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide /.notdef /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis /dotlessi /Lslash /lslash /Scaron /scaron /Ydieresis /Zcaron /zcaron /circumflex /caron /breve /dotaccent /ring /ogonek /tilde /hungarumlaut + diff --git a/test/data/oldfminit/INDEX b/test/data/oldfminit/INDEX new file mode 100644 index 0000000..c2d250f --- /dev/null +++ b/test/data/oldfminit/INDEX @@ -0,0 +1,10 @@ +# Index file for non-UCS FM initialisation tests +# +# Test Description + +latin1.cfg Simple Latin1 Encoding +mergeumap.cfg Merge identical umaps +nomapping.cfg Fonts with no mapping +symbol.cfg Simple symbol fonts + +brokenencoding.cfg Garbage encoding file diff --git a/test/data/oldfminit/Latin1 b/test/data/oldfminit/Latin1 new file mode 100644 index 0000000..6821aa8 --- /dev/null +++ b/test/data/oldfminit/Latin1 @@ -0,0 +1,270 @@ +% Acorn_Latin1Encoding 1.00 0 + +%%RISCOS_BasedOn 0 +%%RISCOS_Alphabet 101 + +% These first characters are for use by PostScript printer driver ONLY, +% they are not accessible using the RISC OS font manager. +/ring +/circumflex +/tilde +/dotlessi +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef +/.notdef + +/space +/exclam +/quotedbl +/numbersign +/dollar +/percent +/ampersand +/quotesingle +/parenleft +/parenright +/asterisk +/plus +/comma +/hyphen +/period +/slash +/zero +/one +/two +/three +/four +/five +/six +/seven +/eight +/nine +/colon +/semicolon +/less +/equal +/greater +/question + +/at +/A +/B +/C +/D +/E +/F +/G +/H +/I +/J +/K +/L +/M +/N +/O +/P +/Q +/R +/S +/T +/U +/V +/W +/X +/Y +/Z +/bracketleft +/backslash +/bracketright +/asciicircum +/underscore + +/grave +/a +/b +/c +/d +/e +/f +/g +/h +/i +/j +/k +/l +/m +/n +/o +/p +/q +/r +/s +/t +/u +/v +/w +/x +/y +/z +/braceleft +/bar +/braceright +/asciitilde +/.notdef + +/Euro +/Wcircumflex +/wcircumflex +/.notdef +/.notdef +/Ycircumflex +/ycircumflex +/special1 +/special2 +/special3 +/special4 +/special5 +/ellipsis +/trademark +/perthousand +/bullet +/quoteleft +/quoteright +/guilsinglleft +/guilsinglright +/quotedblleft +/quotedblright +/quotedblbase +/endash +/emdash +/minus +/OE +/oe +/dagger +/daggerdbl +/fi +/fl + +/space +/exclamdown +/cent +/sterling +/currency +/yen +/brokenbar +/section +/dieresis +/copyright +/ordfeminine +/guillemotleft +/logicalnot +/hyphen +/registered +/macron +/degree +/plusminus +/twosuperior +/threesuperior +/acute +/mu +/paragraph +/periodcentered +/cedilla +/onesuperior +/ordmasculine +/guillemotright +/onequarter +/onehalf +/threequarters +/questiondown + +/Agrave +/Aacute +/Acircumflex +/Atilde +/Adieresis +/Aring +/AE +/Ccedilla +/Egrave +/Eacute +/Ecircumflex +/Edieresis +/Igrave +/Iacute +/Icircumflex +/Idieresis +/Eth +/Ntilde +/Ograve +/Oacute +/Ocircumflex +/Otilde +/Odieresis +/multiply +/Oslash +/Ugrave +/Uacute +/Ucircumflex +/Udieresis +/Yacute +/Thorn +/germandbls + +/agrave +/aacute +/acircumflex +/atilde +/adieresis +/aring +/ae +/ccedilla +/egrave +/eacute +/ecircumflex +/edieresis +/igrave +/iacute +/icircumflex +/idieresis +/eth +/ntilde +/ograve +/oacute +/ocircumflex +/otilde +/odieresis +/divide +/oslash +/ugrave +/uacute +/ucircumflex +/udieresis +/yacute +/thorn +/ydieresis diff --git a/test/data/oldfminit/brokenencoding.cfg b/test/data/oldfminit/brokenencoding.cfg new file mode 100644 index 0000000..b5e12c1 --- /dev/null +++ b/test/data/oldfminit/brokenencoding.cfg @@ -0,0 +1,28 @@ +# Configuration for broken encoding file + +%expumaps Corpus.Bold 0 +%expumaps Corpus.Bold.Oblique 0 +%expumaps Corpus.Medium 1 +%expumaps Corpus.Medium.Oblique 0 +%expumaps Homerton.Bold 0 +%expumaps Homerton.Bold.Oblique 0 +%expumaps Homerton.Medium 1 +%expumaps Homerton.Medium.Oblique 0 +%expumaps Trinity.Bold 0 +%expumaps Trinity.Bold.Italic 0 +%expumaps Trinity.Medium 1 +%expumaps Trinity.Medium.Italic 0 + +# Font name Encoding name Filename +Corpus.Bold Latin1 Allerta +Corpus.Bold.Oblique Latin1 Allerta +Corpus.Medium Latin1 Latin1 +Corpus.Medium.Oblique Latin1 Allerta +Homerton.Bold Latin1 Allerta +Homerton.Bold.Oblique Latin1 Allerta +Homerton.Medium Latin1 Latin1 +Homerton.Medium.Oblique Latin1 Allerta +Trinity.Bold Latin1 Allerta +Trinity.Bold.Italic Latin1 Allerta +Trinity.Medium Latin1 Latin1 +Trinity.Medium.Italic Latin1 Allerta diff --git a/test/data/oldfminit/latin1.cfg b/test/data/oldfminit/latin1.cfg new file mode 100644 index 0000000..646582a --- /dev/null +++ b/test/data/oldfminit/latin1.cfg @@ -0,0 +1,28 @@ +# Configuration for Latin1 language fonts + +%expumaps Corpus.Bold 1 +%expumaps Corpus.Bold.Oblique 1 +%expumaps Corpus.Medium 1 +%expumaps Corpus.Medium.Oblique 1 +%expumaps Homerton.Bold 1 +%expumaps Homerton.Bold.Oblique 1 +%expumaps Homerton.Medium 1 +%expumaps Homerton.Medium.Oblique 1 +%expumaps Trinity.Bold 1 +%expumaps Trinity.Bold.Italic 1 +%expumaps Trinity.Medium 1 +%expumaps Trinity.Medium.Italic 1 + +# Font name Encoding name Filename +Corpus.Bold Latin1 Latin1 +Corpus.Bold.Oblique Latin1 Latin1 +Corpus.Medium Latin1 Latin1 +Corpus.Medium.Oblique Latin1 Latin1 +Homerton.Bold Latin1 Latin1 +Homerton.Bold.Oblique Latin1 Latin1 +Homerton.Medium Latin1 Latin1 +Homerton.Medium.Oblique Latin1 Latin1 +Trinity.Bold Latin1 Latin1 +Trinity.Bold.Italic Latin1 Latin1 +Trinity.Medium Latin1 Latin1 +Trinity.Medium.Italic Latin1 Latin1 diff --git a/test/data/oldfminit/mergeumap.cfg b/test/data/oldfminit/mergeumap.cfg new file mode 100644 index 0000000..654a0bf --- /dev/null +++ b/test/data/oldfminit/mergeumap.cfg @@ -0,0 +1,40 @@ +# Configuration for merging duplicate umaps + +%expumaps Corpus.Bold 1 +%expumaps Corpus.Bold.Oblique 1 +%expumaps Corpus.Medium 1 +%expumaps Corpus.Medium.Oblique 1 +%expumaps Homerton.Bold 1 +%expumaps Homerton.Bold.Oblique 1 +%expumaps Homerton.Medium 1 +%expumaps Homerton.Medium.Oblique 1 +%expumaps Trinity.Bold 1 +%expumaps Trinity.Bold.Italic 1 +%expumaps Trinity.Medium 1 +%expumaps Trinity.Medium.Italic 1 + +# Font name Encoding name Filename +Corpus.Bold Latin1 Latin1 +Corpus.Bold.Oblique Latin1 Latin1 +Corpus.Medium Latin1 Latin1 +Corpus.Medium.Oblique Latin1 Latin1 +Corpus.Bold Latin2 Latin1 +Corpus.Bold.Oblique Latin2 Latin1 +Corpus.Medium Latin2 Latin1 +Corpus.Medium.Oblique Latin2 Latin1 +Homerton.Bold Latin1 Latin1 +Homerton.Bold.Oblique Latin1 Latin1 +Homerton.Medium Latin1 Latin1 +Homerton.Medium.Oblique Latin1 Latin1 +Homerton.Bold Latin2 Latin1 +Homerton.Bold.Oblique Latin2 Latin1 +Homerton.Medium Latin2 Latin1 +Homerton.Medium.Oblique Latin2 Latin1 +Trinity.Bold Latin1 Latin1 +Trinity.Bold.Italic Latin1 Latin1 +Trinity.Medium Latin1 Latin1 +Trinity.Medium.Italic Latin1 Latin1 +Trinity.Bold Latin2 Latin1 +Trinity.Bold.Italic Latin2 Latin1 +Trinity.Medium Latin2 Latin1 +Trinity.Medium.Italic Latin2 Latin1 diff --git a/test/data/oldfminit/nomapping.cfg b/test/data/oldfminit/nomapping.cfg new file mode 100644 index 0000000..18de2fb --- /dev/null +++ b/test/data/oldfminit/nomapping.cfg @@ -0,0 +1,19 @@ +# Configuration for fonts with no mapping + +%expumaps Corpus.Bold 0 +%expumaps Corpus.Bold.Oblique 0 +%expumaps Corpus.Medium 1 +%expumaps Corpus.Medium.Oblique 0 +%expumaps Homerton.Bold 0 +%expumaps Homerton.Bold.Oblique 0 +%expumaps Homerton.Medium 1 +%expumaps Homerton.Medium.Oblique 0 +%expumaps Trinity.Bold 0 +%expumaps Trinity.Bold.Italic 0 +%expumaps Trinity.Medium 1 +%expumaps Trinity.Medium.Italic 0 + +# Font name Encoding name Filename +Corpus.Medium Latin1 Latin1 +Homerton.Medium Latin1 Latin1 +Trinity.Medium Latin1 Latin1 diff --git a/test/data/oldfminit/symbol.cfg b/test/data/oldfminit/symbol.cfg new file mode 100644 index 0000000..ca7c760 --- /dev/null +++ b/test/data/oldfminit/symbol.cfg @@ -0,0 +1,28 @@ +# Configuration for symbol fonts + +%expumaps Corpus.Bold 1 +%expumaps Corpus.Bold.Oblique 1 +%expumaps Corpus.Medium 1 +%expumaps Corpus.Medium.Oblique 1 +%expumaps Homerton.Bold 1 +%expumaps Homerton.Bold.Oblique 1 +%expumaps Homerton.Medium 1 +%expumaps Homerton.Medium.Oblique 1 +%expumaps Trinity.Bold 1 +%expumaps Trinity.Bold.Italic 1 +%expumaps Trinity.Medium 1 +%expumaps Trinity.Medium.Italic 1 + +# Font name Encoding name Filename +Corpus.Bold Symbol Latin1 +Corpus.Bold.Oblique Symbol Latin1 +Corpus.Medium Symbol Latin1 +Corpus.Medium.Oblique Symbol Latin1 +Homerton.Bold Symbol Latin1 +Homerton.Bold.Oblique Symbol Latin1 +Homerton.Medium Symbol Latin1 +Homerton.Medium.Oblique Symbol Latin1 +Trinity.Bold Symbol Latin1 +Trinity.Bold.Italic Symbol Latin1 +Trinity.Medium Symbol Latin1 +Trinity.Medium.Italic Symbol Latin1 diff --git a/test/harness-priv.h b/test/harness-priv.h new file mode 100644 index 0000000..60d5135 --- /dev/null +++ b/test/harness-priv.h @@ -0,0 +1,45 @@ +#ifndef rufl_test_harness_priv_h_ +#define rufl_test_harness_priv_h_ + +#include <stdbool.h> +#include <stddef.h> +#include <inttypes.h> + +#include "harness.h" + +typedef struct { + unsigned int refcnt; + size_t name; /* Index of name in names array */ +#define FONT_ENCODING_SYMBOL ((size_t) -1) /* Symbol, not language, font */ + size_t encoding; /* Index of encoding in encodings array */ + int xsize; /* XSize of this font */ + int ysize; /* YSize if this font */ + int xres; /* XResolution of this font */ + int yres; /* YResolution of this font */ +} rufl_test_harness_sized_font; + +typedef struct { + int fm_version; + bool fm_ucs; + bool fm_broken_fec; + + const char **font_names; + size_t n_font_names; + + const char **encodings; + size_t n_encodings; + + /* n_font_names * (n_encodings + 1) entries */ + char **encoding_filenames; + + /* At most 256 active font handles */ + rufl_test_harness_sized_font fonts[256]; + int current_font; + + char *buffer; + int buffer_flags; +} rufl_test_harness_t; + +extern rufl_test_harness_t *h; + +#endif diff --git a/test/harness.c b/test/harness.c new file mode 100644 index 0000000..8f8ea9a --- /dev/null +++ b/test/harness.c @@ -0,0 +1,139 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "harness-priv.h" + +rufl_test_harness_t *h = NULL; + +static void rufl_test_harness_free(void) +{ + size_t ni, ei; + + free(h->font_names); + free(h->encodings); + if (h->encoding_filenames != NULL) { + for (ni = 0; ni != h->n_font_names; ni++) { + for (ei = 0; ei != h->n_encodings + 1; ei++) { + free(h->encoding_filenames[ + (ni * (h->n_encodings + 1)) + ei]); + } + } + } + free(h->encoding_filenames); + free(h); +} + +void rufl_test_harness_init(int fm_version, bool fm_ucs, bool preload) +{ + h = calloc(1, sizeof(*h)); + assert(h != NULL); + + h->fm_version = fm_version; + h->fm_ucs = fm_ucs; + h->fm_broken_fec = fm_version < 364; + + if (preload) { + /* Register ROM fonts as a convenience */ + rufl_test_harness_register_font("Corpus.Bold"); + rufl_test_harness_register_font("Corpus.Bold.Oblique"); + rufl_test_harness_register_font("Corpus.Medium"); + rufl_test_harness_register_font("Corpus.Medium.Oblique"); + rufl_test_harness_register_font("Homerton.Bold"); + rufl_test_harness_register_font("Homerton.Bold.Oblique"); + rufl_test_harness_register_font("Homerton.Medium"); + rufl_test_harness_register_font("Homerton.Medium.Oblique"); + rufl_test_harness_register_font("Trinity.Bold"); + rufl_test_harness_register_font("Trinity.Bold.Italic"); + rufl_test_harness_register_font("Trinity.Medium"); + rufl_test_harness_register_font("Trinity.Medium.Italic"); + + /* Register encodings as a convenience */ + rufl_test_harness_register_encoding("Cyrillic"); + rufl_test_harness_register_encoding("Greek"); + rufl_test_harness_register_encoding("Hebrew"); + rufl_test_harness_register_encoding("Latin1"); + rufl_test_harness_register_encoding("Latin2"); + rufl_test_harness_register_encoding("Latin3"); + rufl_test_harness_register_encoding("Latin4"); + rufl_test_harness_register_encoding("Latin5"); + rufl_test_harness_register_encoding("Latin6"); + rufl_test_harness_register_encoding("Latin7"); + rufl_test_harness_register_encoding("Latin8"); + rufl_test_harness_register_encoding("Latin9"); + rufl_test_harness_register_encoding("Latin10"); + if (fm_ucs) + rufl_test_harness_register_encoding("UTF8"); + rufl_test_harness_register_encoding("Welsh"); + } + + atexit(rufl_test_harness_free); +} + +void rufl_test_harness_register_font(const char *name) +{ + const char **names; + + /* Encoding paths must be registered last */ + assert(h->encoding_filenames == NULL); + + names = realloc(h->font_names, + (h->n_font_names + 1) * sizeof(*names)); + assert(names != NULL); + + h->font_names = names; + + h->font_names[h->n_font_names++] = name; +} + +void rufl_test_harness_register_encoding(const char *encoding) +{ + const char **encodings; + + /* Encoding paths must be registered last */ + assert(h->encoding_filenames == NULL); + + encodings = realloc(h->encodings, + (h->n_encodings + 1) * sizeof(*encodings)); + assert(encodings != NULL); + + h->encodings = encodings; + + h->encodings[h->n_encodings++] = encoding; +} + +void rufl_test_harness_set_font_encoding(const char *fontname, + const char *encoding, const char *path) +{ + size_t ni, ei; + + if (h->encoding_filenames == NULL) { + h->encoding_filenames = calloc( + h->n_font_names * (h->n_encodings + 1), + sizeof(*h->encoding_filenames)); + assert(h->encoding_filenames != NULL); + } + + /* Find font index */ + for (ni = 0; ni < h->n_font_names; ni++) { + if (strcmp(h->font_names[ni], fontname) == 0) + break; + } + assert(ni != h->n_font_names); + + /* Find encoding index */ + if (strcmp("Symbol", encoding) == 0) { + ei = h->n_encodings; + } else { + for (ei = 0; ei < h->n_encodings; ei++) { + if (strcmp(h->encodings[ei], encoding) == 0) + break; + } + assert(ei != h->n_encodings); + } + + if (h->encoding_filenames[(ni * (h->n_encodings + 1)) + ei] != NULL) + free(h->encoding_filenames[(ni * (h->n_encodings + 1)) + ei]); + h->encoding_filenames[(ni * (h->n_encodings + 1)) + ei] = strdup(path); + assert(h->encoding_filenames[(ni * (h->n_encodings + 1)) + ei] != NULL); +} diff --git a/test/harness.h b/test/harness.h new file mode 100644 index 0000000..b62617f --- /dev/null +++ b/test/harness.h @@ -0,0 +1,10 @@ +#ifndef rufl_test_harness_h_ +#define rufl_test_harness_h_ + +void rufl_test_harness_init(int fm_version, bool fm_ucs, bool preload); +void rufl_test_harness_register_font(const char *name); +void rufl_test_harness_register_encoding(const char *encoding); +void rufl_test_harness_set_font_encoding(const char *fontname, + const char *encoding, const char *path); + +#endif diff --git a/test/manyfonts.c b/test/manyfonts.c new file mode 100644 index 0000000..bc4b01e --- /dev/null +++ b/test/manyfonts.c @@ -0,0 +1,100 @@ +/* + * XXX: This test currently needs the following patch to be valid. + * We need a way of ensuring the library chooses the direct substitution + * table format (which basically means we need to flood a plane with glyphs) + * + * diff --git a/src/rufl_substitution_table.c b/src/rufl_substitution_table.c + * index f5de7d8..2b58b72 100644 + * --- a/src/rufl_substitution_table.c + * +++ b/src/rufl_substitution_table.c + * @@ -1019,7 +1019,7 @@ static rufl_code create_substitution_table_for_plane(unsigned int plane) + * table_entries, blocks_used); + * chd_size = rufl_substitution_table_estimate_size_chd( + * table_entries, blocks_used); + * - if (direct_size <= chd_size) { + * + if (1 || direct_size <= chd_size) { + * result = direct(table, table_entries, blocks_used, + * block_histogram, + * &rufl_substitution_table[plane]); + */ + +#include <ftw.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "rufl.h" + +#include "harness.h" +#include "testutils.h" + +static char template[] = "/tmp/manyfontsXXXXXX"; +static const char *ptmp = NULL; + +static int ftw_cb(const char *path, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + (void) sb; + (void) typeflag; + (void) ftwbuf; + + remove(path); + + return 0; +} + +static void cleanup(void) +{ + if (ptmp == NULL) + return; + + nftw(ptmp, ftw_cb, FOPEN_MAX, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); +} + +int main(int argc, const char **argv) +{ + char *names[300]; + int x; + + UNUSED(argc); + UNUSED(argv); + + ptmp = mkdtemp(template); + assert(NULL != ptmp); + atexit(cleanup); + chdir(ptmp); + + rufl_test_harness_init(380, true, true); + + for (x = 0; x < 300; x++) { + char buf[64]; + sprintf(buf, "Font%03d", x); + names[x] = strdup(buf); + rufl_test_harness_register_font(names[x]); + } + + assert(rufl_OK == rufl_init()); + assert(NULL == rufl_fm_error); + assert(303 == rufl_family_list_entries); + assert(NULL != rufl_family_menu); + + rufl_dump_state(true); + + rufl_quit(); + + /* Reinit -- should load cache */ + assert(rufl_OK == rufl_init()); + assert(NULL == rufl_fm_error); + assert(303 == rufl_family_list_entries); + assert(NULL != rufl_family_menu); + /* Done for real this time */ + rufl_quit(); + + for (x = 0; x < 300; x++) { + free(names[x]); + } + + printf("PASS\n"); + + return 0; +} diff --git a/test/mocks.c b/test/mocks.c new file mode 100644 index 0000000..734211b --- /dev/null +++ b/test/mocks.c @@ -0,0 +1,671 @@ +#include <assert.h> +#include <string.h> + +#include <oslib/font.h> +#include <oslib/hourglass.h> +#include <oslib/os.h> +#include <oslib/osfscontrol.h> +#include <oslib/taskwindow.h> +#include <oslib/wimp.h> +#include <oslib/wimpreadsysinfo.h> + +#include "harness-priv.h" + +static os_error font_no_font = { error_FONT_NO_FONT, "Undefined font handle" }; +static os_error font_bad_font_number = { + error_FONT_BAD_FONT_NUMBER, + "Font handle out of range" +}; +static os_error font_not_found = { error_FONT_NOT_FOUND, "Font not found" }; +static os_error font_encoding_not_found = { + error_FONT_ENCODING_NOT_FOUND, + "Encoding not found" +}; +static os_error font_no_handles = { + error_FONT_NO_HANDLES, + "No more font handles" +}; +static os_error font_reserved = { + error_FONT_RESERVED, + "Reserved fields must be zero" +}; +static os_error buff_overflow = { error_BUFF_OVERFLOW, "Buffer overflow" }; +static os_error bad_parameters = { error_BAD_PARAMETERS, "Bad parameters" }; +static os_error no_such_swi = { error_NO_SUCH_SWI, "SWI not known" }; +static os_error unimplemented = { error_UNIMPLEMENTED, "Not implemented" }; + +/****************************************************************************/ + +os_error *xfont_cache_addr (int *version, int *cache_size, int *cache_used) +{ + if (version != NULL) + *version = h->fm_version; + if (cache_size != NULL) + *cache_size = 512 * 1024; + if (cache_used != NULL) + *cache_used = 0; + + return NULL; +} + +os_error *xfont_find_font (char const *font_name, int xsize, int ysize, + int xres, int yres, font_f *font, int *xres_out, int *yres_out) +{ + char name[80], encoding[80]; + const char *slash; + size_t ni, ei; + int fh; + + /* Default xres and yres */ + if (xres <= 0) + xres = 90; + if (yres <= 0) + yres = 90; + + /* Parse font name */ + slash = strchr(font_name, '\\'); + if (slash == NULL) { + /* Bare name: symbol font */ + strncpy(name, font_name, sizeof(name)); + name[sizeof(name)-1] = '\0'; + strcpy(encoding, "Symbol"); + } else { + /* Identifier: extract encoding */ + strncpy(name, font_name, slash - font_name); + name[slash-font_name] = '\0'; + assert(slash[1] == 'E'); + strncpy(encoding, slash + 2, sizeof(encoding)); + encoding[sizeof(encoding)-1] = '\0'; + } + + /* Determine if we know about this font name */ + for (ni = 0; ni < h->n_font_names; ni++) { + if (strcmp(h->font_names[ni], name) == 0) + break; + } + if (ni == h->n_font_names) { + return &font_not_found; + } + + /* Determine if we know about this encoding */ + if (strcmp("Symbol", encoding) == 0) { + ei = FONT_ENCODING_SYMBOL; + } else { + for (ei = 0; ei < h->n_encodings; ei++) { + if (strcmp(h->encodings[ei], encoding) == 0) + break; + } + if (ei == h->n_encodings) { + return &font_encoding_not_found; + } + } + + /* Find existing font handle (0 is forbidden) */ + for (fh = 1; fh < 256; fh++) { + if (h->fonts[fh].refcnt > 0 && h->fonts[fh].name == ni && + h->fonts[fh].encoding == ei && + h->fonts[fh].xsize == xsize && + h->fonts[fh].ysize == ysize && + h->fonts[fh].xres == xres && + h->fonts[fh].yres == yres) + break; + } + if (fh == 256) { + /* No existing font found: allocate new one */ + for (fh = 1; fh < 256; fh++) { + if (h->fonts[fh].refcnt == 0) + break; + } + if (fh == 256) { + return &font_no_handles; + } + + h->fonts[fh].name = ni; + h->fonts[fh].encoding = ei; + h->fonts[fh].xsize = xsize; + h->fonts[fh].ysize = ysize; + h->fonts[fh].xres = xres; + h->fonts[fh].yres = yres; + } + + /* Bump refcnt */ + h->fonts[fh].refcnt++; + + /* Set current font */ + h->current_font = fh; + + if (font != NULL) + *font = (font_f) fh; + if (xres_out != NULL) + *xres_out = xres; + if (yres_out != NULL) + *yres_out = yres; + + return NULL; +} + +os_error *xfont_lose_font (font_f font) +{ + if (font != 0 && h->fonts[font].refcnt > 0) + h->fonts[font].refcnt--; + + return NULL; +} + +os_error *xfont_read_info (font_f font, int *x0, int *y0, int *x1, int *y1) +{ + if (font == 0) + return &font_bad_font_number; + if (h->fonts[font].refcnt == 0) + return &font_no_font; + + /* Cheat: just scale point size to OS units */ + if (x0 != NULL) + *x0 = 0; + if (y0 != NULL) + *y0 = 0; + if (x1 != NULL) + *x1 = (h->fonts[font].xsize * 180) / (72 * 16); + if (y1 != NULL) + *y1 = (h->fonts[font].ysize * 180) / (72 * 16); + + return NULL; +} + +os_error *xfont_read_font_metrics (font_f font, font_bbox_info *bbox_info, + font_width_info *xwidth_info, font_width_info *ywidth_info, + font_metrics_misc_info *misc_info, font_kern_info *kern_info, + font_metric_flags *flags, int *bbox_info_size, + int *xwidth_info_size, int *ywidth_info_size, + int *misc_info_size, int *kern_info_size) +{ + if (font == 0) + return &font_bad_font_number; + if (h->fonts[font].refcnt == 0) + return &font_no_font; + if (bbox_info != NULL || xwidth_info != NULL || ywidth_info != NULL || + kern_info != NULL || flags != NULL) + return &unimplemented; + + if (misc_info != NULL) { + os_error *err = xfont_read_info(font, + &misc_info->x0, &misc_info->y0, + &misc_info->x1, &misc_info->y1); + if (err != NULL) + return err; + misc_info->xkern = misc_info->ykern = 0; + misc_info->italic_correction = 0; + misc_info->underline_position = 0; + misc_info->underline_thickness = 0; + misc_info->cap_height = misc_info->y1 - misc_info->y0; + misc_info->xheight = misc_info->cap_height >> 1; + misc_info->descender = misc_info->ascender = 0; + } + + if (bbox_info_size != NULL) + *bbox_info_size = 0; + if (xwidth_info_size != NULL) + *xwidth_info_size = 0; + if (ywidth_info_size != NULL) + *ywidth_info_size = 0; + if (misc_info_size != NULL) + *misc_info_size = sizeof(font_metrics_misc_info); + if (kern_info_size != NULL) + *kern_info_size = 0; + + return NULL; +} + +os_error *xfont_read_encoding_filename (font_f font, char *buffer, int size, + char **end) +{ + const char *filename = NULL; + size_t ei; + + if (font == 0) + return &font_bad_font_number; + if (h->fonts[font].refcnt == 0) + return &font_no_font; + if (h->encoding_filenames == NULL) + return &font_encoding_not_found; + if (h->fonts[font].encoding != FONT_ENCODING_SYMBOL) { + ei = h->fonts[font].encoding; + } else { + ei = h->n_encodings; + } + filename = h->encoding_filenames[ + (h->fonts[font].name * (h->n_encodings + 1)) + ei]; + if (filename == NULL) + return &font_encoding_not_found; + if (buffer == NULL || (size_t) size < strlen(filename) + 1) + return &bad_parameters; + + strcpy(buffer, filename); + + if (end != NULL) + *end = buffer + strlen(filename) + 1; + + return NULL; +} + +os_error *xfont_list_fonts (byte *buffer1, font_list_context context, + int size1, byte *buffer2, int size2, char const *tick_font, + font_list_context *context_out, int *used1, int *used2) +{ + const char **values; + size_t n_values; + size_t index = (context & 0xffff); + + if ((context & font_RETURN_FONT_MENU) && + (context & ~(font_USE_LINEFEED | + font_RETURN_FONT_MENU | + font_ALLOW_SYSTEM_FONT | + font_GIVEN_TICK | 0x400000)) >> 16) + return &bad_parameters; + if (!(context & font_RETURN_FONT_MENU) && + (context & ~(font_RETURN_FONT_NAME | + font_RETURN_LOCAL_FONT_NAME | + font_USE_LINEFEED | 0x400000)) >> 16) + return &bad_parameters; + if (context & font_RETURN_FONT_MENU) + return &unimplemented; + + if (context & 0x400000) { + values = h->encodings; + n_values = h->n_encodings; + } else { + values = h->font_names; + n_values = h->n_font_names; + } + + if (index < n_values) { + int len = (int) strlen(values[index]) + 1; + if (context & font_RETURN_FONT_NAME) { + if (buffer1 != NULL && size1 < len) + return &buff_overflow; + if (buffer1 != NULL) + strcpy((char *) buffer1, values[index]); + if (used1 != NULL) + *used1 = len; + } + if (context & font_RETURN_LOCAL_FONT_NAME) { + if (buffer2 != NULL && size2 < len) + return &buff_overflow; + if (buffer2 != NULL) + strcpy((char *) buffer2, values[index]); + if (used2 != NULL) + *used2 = len; + } + index++; + } else { + index = -1; + } + + if (context_out != NULL) + *context_out = (font_list_context) index; + + (void) tick_font; + + return NULL; +} + +os_error *xfont_set_font (font_f font) +{ + if (font == 0) + return &font_bad_font_number; + if (h->fonts[font].refcnt == 0) + return &font_no_font; + + h->current_font = font; + + return NULL; +} + +os_error *xfont_paint (font_f font, char const *string, + font_string_flags flags, int xpos, int ypos, + font_paint_block const *block, os_trfm const *trfm, int length) +{ + if (!(flags & font_GIVEN_FONT) || font == 0) + font = h->current_font; + if (font == 0 || h->fonts[font].refcnt == 0) + return &font_no_font; + + if (flags & font_GIVEN_FONT) + h->current_font = font; + + //XXX: + //XXX: also, pay attention to redirection to buffer + (void) string; + (void) xpos; + (void) ypos; + (void) block; + (void) trfm; + (void) length; + + return NULL; +} + +os_error *xfont_scan_string (font_f font, char const *s, + font_string_flags flags, int x, int y, font_scan_block *block, + os_trfm const *trfm, int length, char **split_point, + int *x_out, int *y_out, int *num_split_chars) +{ + size_t advance = 1; + int width = 0; + + if (!(flags & font_GIVEN_FONT) || font == 0) + font = h->current_font; + if (font == 0 || h->fonts[font].refcnt == 0) + return &font_no_font; + + if ((flags & font_GIVEN_BLOCK) && block == NULL) + return &bad_parameters; + if ((flags & font_RETURN_BBOX) && !(flags & font_GIVEN_BLOCK)) + return &bad_parameters; + if ((flags & font_GIVEN_BLOCK) && (block->space.x != 0 || + block->space.y != 0 || + block->letter.x != 0 || + block->letter.y != 0 || + block->split_char != -1)) + return &unimplemented; + + if ((flags & font_GIVEN32_BIT) && (flags & font_GIVEN16_BIT)) + return &bad_parameters; + + if (!(flags & font_GIVEN_LENGTH)) + length = 0x7ffffffc; + + if (flags & font_GIVEN32_BIT) + advance = 4; + else if (flags & font_GIVEN16_BIT) + advance = 2; + + /* Consume up to length bytes of input */ + while (length > 0) { + uint32_t c = 0, i; + int cwidth; + for (i = 0; i < advance; i++) { + c |= s[i] << (advance - i - 1); + } + s += advance; + length -= advance; + + /* Regardless of length, stop on terminator */ + if (c == 0 || c == 10 || c == 13) + break; + + /* Just scale font size to millipoints and add on the width */ + cwidth = ((h->fonts[font].xsize * 1000) >> 4); + if ((flags & font_RETURN_CARET_POS) && x > 0 && + (width + cwidth/2) > x) { + /* Split point is less than half way through + * this character: exclude it */ + s -= advance; + length += advance; + break; + } + width += cwidth; + //XXX: how is negative x meant to work? + if (x > 0 && width > x) + break; + } + + if (flags & font_RETURN_BBOX) { + block->bbox.x0 = 0; + block->bbox.y0 = 0; + block->bbox.x1 = width; + block->bbox.y1 = (h->fonts[font].ysize * 1000) >> 4; + } + + if (x_out != NULL) + *x_out = width; + if (y_out != NULL) + *y_out = (h->fonts[font].ysize * 1000) >> 4; + if (split_point != NULL) + *split_point = (char *) s; + + (void) y; + (void) trfm; + (void) num_split_chars; + + return NULL; +} + +os_error *xfont_switch_output_to_buffer (font_output_flags flags, + byte *buffer, char **end) +{ + if ((intptr_t) buffer <= 0 && flags != 0) + return &font_reserved; + if (flags & ~(font_NO_OUTPUT | font_ADD_HINTS | font_ERROR_IF_BITMAP)) + return &font_reserved; + + if (end) + *end = h->buffer; + + if ((intptr_t) buffer != -1) { + h->buffer = (char *) buffer; + h->buffer_flags = flags; + } + + return NULL; +} + +os_error *xfont_enumerate_characters (font_f font, int character, + int *next_character, int *internal_character_code) +{ + static int extchars[] = { 0x20, 0x21, 0x30, 0x31, 0x32, 0xa0, 0x10ac0, 0x20021, 0x30000, -1 }; + static int intchars[] = { 1, 2, 3, 4, -1, 5, 6, 7, 8 }; + size_t index = 0; + int next = -1, internal = -1; + + if (!h->fm_ucs) + return &no_such_swi; + + if (font == 0) + font = h->current_font; + if (font == 0) + return &font_bad_font_number; + if (h->fonts[font].refcnt == 0) + return &font_no_font; + + /* Broken FEC: skip first chunk unless code is valid. + * (only 0x20 and 0x21 are valid in the first chunk here, + * so we simply need to skip over these if the code is + * less than 0x20 -- any other codes in the first chunk + * will just fall out of the usual "next code" logic) + */ + if (h->fm_broken_fec && character < extchars[0]) + index = 2; + + for (; index < (sizeof(intchars)/sizeof(intchars[0])); index++) { + if (extchars[index] == character) { + /* Found: return it and compute next */ + next = extchars[index+1]; + internal = intchars[index]; + break; + } else if (extchars[index] > character) { + /* Not found and won't be: compute next */ + next = extchars[index]; + internal = -1; + break; + } + } + + if (next_character != NULL) + *next_character = next; + if (internal_character_code != NULL) + *internal_character_code = internal; + + return NULL; +} + +/****************************************************************************/ + +os_error *xhourglass_on (void) +{ + return &unimplemented; +} + +os_error *xhourglass_off (void) +{ + return &unimplemented; +} + +os_error *xhourglass_percentage (int percent) +{ + (void) percent; + + return &unimplemented; +} + +os_error *xhourglass_leds (bits eor_mask, bits and_mask, bits *old_leds) +{ + (void) eor_mask; + (void) and_mask; + (void) old_leds; + + return &unimplemented; +} + +os_error *xhourglass_colours (os_colour sand, os_colour glass, + os_colour *old_sand, os_colour *old_glass) +{ + (void) sand; + (void) glass; + (void) old_sand; + (void) old_glass; + + return &unimplemented; +} + +/****************************************************************************/ + +os_error *xos_read_monotonic_time (os_t *t) +{ + (void) t; + + return &unimplemented; +} + +os_error *xos_read_mode_variable (os_mode mode, os_mode_var var, int *var_val, + bits *psr) +{ + (void) mode; + (void) var; + (void) var_val; + (void) psr; + + return &unimplemented; +} + +/****************************************************************************/ + +os_error *xosfscontrol_canonicalise_path (char const *path_name, char *buffer, + char const *var, char const *path, int size, int *spare) +{ + const char *prefix = "Resources:$.Fonts."; + size_t len = strlen(path_name) + strlen(prefix) + 1; + + if (strcmp(var, "Font$Path") != 0 || path != NULL) + return &unimplemented; + + if (buffer == NULL && size != 0) + return &bad_parameters; + + if (buffer != NULL && size < (int) len) + return &buff_overflow; + + if (buffer != NULL) { + strcpy(buffer, prefix); + strcpy(buffer + strlen(prefix), path_name); + } + + if (spare != NULL) + *spare = size - len; + + return NULL; +} + +/****************************************************************************/ + +os_error *xtaskwindowtaskinfo_window_task (osbool *window_task) +{ + (void) window_task; + + return &unimplemented; +} + +/****************************************************************************/ + +os_error *xwimp_create_window (wimp_window const *window, wimp_w *w) +{ + (void) window; + (void) w; + + return &unimplemented; +} + +os_error *xwimp_delete_window (wimp_w w) +{ + (void) w; + + return &unimplemented; +} + +os_error *xwimp_get_window_state (wimp_window_state *state) +{ + (void) state; + + return &unimplemented; +} + +os_error *xwimp_open_window (wimp_open *open) +{ + (void) open; + + return &unimplemented; +} + +os_error *xwimp_set_icon_state (wimp_w w, wimp_i i, wimp_icon_flags eor_bits, + wimp_icon_flags clear_bits) +{ + (void) w; + (void) i; + (void) eor_bits; + (void) clear_bits; + + return &unimplemented; +} + +os_error *xwimp_resize_icon (wimp_w w, wimp_i i, int x0, int y0, int x1, int y1) +{ + (void) w; + (void) i; + (void) x0; + (void) y0; + (void) x1; + (void) y1; + + return &unimplemented; +} + +os_error *xwimp_poll (wimp_poll_flags mask, wimp_block *block, int *pollword, + wimp_event_no *event) +{ + (void) mask; + (void) block; + (void) pollword; + (void) event; + + return &unimplemented; +} + +/****************************************************************************/ + +os_error *xwimpreadsysinfo_task (wimp_t *task, wimp_version_no *version) +{ + (void) task; + (void) version; + + return &unimplemented; +} diff --git a/test/nofonts.c b/test/nofonts.c new file mode 100644 index 0000000..557d764 --- /dev/null +++ b/test/nofonts.c @@ -0,0 +1,20 @@ +#include <stdio.h> + +#include "rufl.h" + +#include "harness.h" +#include "testutils.h" + +int main(int argc, const char **argv) +{ + UNUSED(argc); + UNUSED(argv); + + rufl_test_harness_init(380, true, false); + + assert(rufl_FONT_MANAGER_ERROR == rufl_init()); + + printf("PASS\n"); + + return 0; +} diff --git a/test/oldfminit.c b/test/oldfminit.c new file mode 100644 index 0000000..c554499 --- /dev/null +++ b/test/oldfminit.c @@ -0,0 +1,353 @@ +#include <ftw.h> +#include <libgen.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "rufl.h" + +/* dirty! */ +#include "../src/rufl_internal.h" + +#include "harness.h" +#include "testutils.h" + +struct expumap { + char *fontname; + size_t num_umaps; +}; + +struct cfg { + const char *datadir; + + struct expumap *expumaps; + size_t n_expumaps; +}; + +static char template[] = "/tmp/oldfminitXXXXXX"; +static const char *ptmp = NULL; +static struct cfg cfg; + +static int ftw_cb(const char *path, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + (void) sb; + (void) typeflag; + (void) ftwbuf; + + remove(path); + + return 0; +} + +static void cleanup(void) +{ + if (cfg.expumaps != NULL) { + size_t i; + + for (i = 0; i < cfg.n_expumaps; i++) { + free(cfg.expumaps[i].fontname); + } + free(cfg.expumaps); + } + + if (ptmp == NULL) + return; + + nftw(ptmp, ftw_cb, FOPEN_MAX, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); +} + +static void parse_cfg(const char *path, struct cfg *cfg, + void (*cb)(struct cfg *cfg, const char *line, size_t len)) +{ + FILE *fp; + char wbuf[4096]; + size_t nleft = 0; + + fp = fopen(path, "r"); + assert(fp != NULL); + + while (!feof(fp)) { + char buf[2048]; + size_t nread; + const char *p, *s; + + nread = fread(buf, 1, sizeof(buf), fp); + if (nread != sizeof(buf)) { + assert(ferror(fp) == 0); + } + + memcpy(wbuf + nleft, buf, nread); + nleft += nread; + + for (p = s = wbuf; p < wbuf + nleft; p++) { + if (*p == '\n') { + cb(cfg, s, p - s); + s = p+1; + } + } + if (s != wbuf + nleft) { + memmove(wbuf, s, p - s); + nleft = p - s; + } else { + nleft = 0; + } + assert(nleft < sizeof(buf)); + } + assert(nleft == 0); + + fclose(fp); +} + +static void parse_expumaps(struct cfg *cfg, char *data, size_t len) +{ + char *p, *s; + const char *font = NULL, *count = NULL; + struct expumap *e; + size_t num_umaps; + + for (p = s = data; p < data+len; p++) { + if (*p == ' ' || *p == '\t') { + *p = '\0'; + if (s != p) { + if (font == NULL) + font = s; + else if (count == NULL) + count = s; + } + s = p+1; + } + } + if (count == NULL) + count = s; + + num_umaps = strtoul(count, &p, 10); + assert((size_t)(p-count) == strlen(count)); + + e = realloc(cfg->expumaps, (cfg->n_expumaps + 1) * sizeof(*e)); + assert(e != NULL); + + cfg->expumaps = e; + cfg->expumaps[cfg->n_expumaps].fontname = strdup(font); + assert(cfg->expumaps[cfg->n_expumaps].fontname != NULL); + cfg->expumaps[cfg->n_expumaps].num_umaps = num_umaps; + cfg->n_expumaps++; +} + +static void parse_directive(struct cfg *cfg, char *linecpy, size_t len) +{ + char *p, *s; + const char *directive = NULL; + + for (p = s = linecpy; p < linecpy+len; p++) { + if (*p == ' ' || *p == '\t') { + *p = '\0'; + if (s != p && directive == NULL) { + directive = s; + s = p+1; + break; + } + s = p+1; + } + } + if (directive == NULL) + directive = s; + + if (strcmp("\%expumaps", directive) == 0) { + parse_expumaps(cfg, s, len - (s - linecpy)); + } +} + +static void parse_encoding(struct cfg *cfg, char *linecpy, size_t len) +{ + char *p, *s; + const char *font = NULL, *encoding = NULL, *file = NULL; + char *path; + + for (p = s = linecpy; p < linecpy+len; p++) { + if (*p == ' ' || *p == '\t') { + *p = '\0'; + if (s != p) { + if (font == NULL) + font = s; + else if (encoding == NULL) + encoding = s; + else if (file == NULL) + file = s; + } + s = p+1; + } + } + if (file == NULL) + file = s; + + assert(font != NULL); + assert(encoding != NULL); + assert(file != NULL); + + path = malloc(strlen(cfg->datadir) + strlen(file) + 2); + assert(path != NULL); + strcpy(path, cfg->datadir); + path[strlen(cfg->datadir)] = '/'; //XXX: platform-agnostic dirsep? + strcpy(path+strlen(cfg->datadir)+1, file); + + rufl_test_harness_set_font_encoding(font, encoding, path); + + free(path); +} + +static void line_cb(struct cfg *cfg, const char *line, size_t len) +{ + char *linecpy; + + if (len == 0 || line[0] == '#') + return; + + linecpy = malloc(len + 1); + assert(linecpy != NULL); + memcpy(linecpy, line, len); + linecpy[len] = '\0'; + + if (line[0] == '%') + parse_directive(cfg, linecpy, len); + else + parse_encoding(cfg, linecpy, len); + + free(linecpy); +} + +static void read_config(const char *path, struct cfg *cfg) +{ + char *pathcpy; + + pathcpy = strdup(path); + assert(pathcpy != NULL); + + cfg->datadir = dirname(pathcpy); + + parse_cfg(path, cfg, line_cb); + + free(pathcpy); + cfg->datadir = NULL; +} + +int main(int argc, const char **argv) +{ + int width, x; + size_t offset; + int32_t xkern, ykern, italic, ascent, descent, xheight, cap_height; + int32_t x_bearing, y_bearing, mwidth, mheight, x_advance, y_advance; + int8_t uline_position; + uint8_t uline_thickness; + os_box bbox; + + assert(2 == argc); + + ptmp = mkdtemp(template); + assert(NULL != ptmp); + atexit(cleanup); + chdir(ptmp); + + rufl_test_harness_init(339, false, true); + + read_config(argv[1], &cfg); + + assert(rufl_OK == rufl_init()); + assert(NULL == rufl_fm_error); + assert(3 == rufl_family_list_entries); + assert(NULL != rufl_family_menu); + + if (cfg.expumaps != NULL) { + size_t i, j; + for (i = 0; i != cfg.n_expumaps; i++) { + for (j = 0; j != rufl_font_list_entries; j++) { + if (strcmp(cfg.expumaps[i].fontname, rufl_font_list[j].identifier) == 0) { + assert(cfg.expumaps[i].num_umaps == rufl_font_list[j].num_umaps); + } + } + } + } + + assert(rufl_OK == rufl_font_metrics("Corpus", rufl_WEIGHT_500, + &bbox, &xkern, &ykern, &italic, + &ascent, &descent, &xheight, &cap_height, + &uline_position, &uline_thickness)); + assert(0 == bbox.x0); + assert(2 == bbox.x1); + assert(0 == bbox.y0); + assert(2 == bbox.y1); + assert(0 == xkern); + assert(0 == ykern); + assert(0 == italic); + assert(0 == ascent); + assert(0 == descent); + assert((bbox.y1 - bbox.y0) == cap_height); + assert((cap_height / 2) == xheight); + assert(0 == uline_position); + assert(0 == uline_thickness); + + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, &width)); + assert(50 == width); + + /* Place caret after first character */ + assert(rufl_OK == rufl_x_to_offset("Homerton", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 25, &offset, &x)); + assert(1 == offset); + assert(25 == x); + + /* Attempt to split after first character. As the split point is + * coincident with the start of the second character, however, + * the split point is placed after it. */ + assert(rufl_OK == rufl_split("Trinity", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 25, &offset, &x)); + assert(3 == offset); + assert(50 == x); + + /* Compute width of replacement character */ + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "\xef\xbf\xbd", 3, &width)); + assert(17 == width); + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "\xf0\xa0\x80\xa5", 4, &width)); + assert(26 == width); + + /* Measure font bounding box */ + assert(rufl_OK == rufl_font_bbox("Corpus", rufl_WEIGHT_500, 160, + &bbox)); + assert(0 == bbox.x0); + assert(25 == bbox.x1); + assert(0 == bbox.y0); + assert(25 == bbox.y1); + + /* Trivial render */ + assert(rufl_OK == rufl_paint("Trinity", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 0, 0, 0)); + + rufl_dump_state(true); + + /* Obtain metrics for a glyph */ + assert(rufl_OK == rufl_glyph_metrics("Homerton", rufl_WEIGHT_500, 160, + "!", 1, &x_bearing, &y_bearing, + &mwidth, &mheight, &x_advance, &y_advance)); + assert(0 == x_bearing); + assert(10000 == y_bearing); + assert(10000 == mwidth); + assert(10000 == mheight); + assert(10000 == x_advance); + assert(10000 == y_advance); + + rufl_quit(); + + /* Reinit -- should load cache */ + assert(rufl_OK == rufl_init()); + assert(NULL == rufl_fm_error); + assert(3 == rufl_family_list_entries); + assert(NULL != rufl_family_menu); + /* Done for real this time */ + rufl_quit(); + + printf("PASS\n"); + + return 0; +} diff --git a/test/olducsinit.c b/test/olducsinit.c new file mode 100644 index 0000000..ed3d846 --- /dev/null +++ b/test/olducsinit.c @@ -0,0 +1,140 @@ +#include <ftw.h> +#include <stdio.h> +#include <unistd.h> + +#include "rufl.h" + +#include "harness.h" +#include "testutils.h" + +static char template[] = "/tmp/olducsinitXXXXXX"; +static const char *ptmp = NULL; + +static int ftw_cb(const char *path, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + (void) sb; + (void) typeflag; + (void) ftwbuf; + + remove(path); + + return 0; +} + +static void cleanup(void) +{ + if (ptmp == NULL) + return; + + nftw(ptmp, ftw_cb, FOPEN_MAX, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); +} + +int main(int argc, const char **argv) +{ + int width, x; + size_t offset; + int32_t xkern, ykern, italic, ascent, descent, xheight, cap_height; + int32_t x_bearing, y_bearing, mwidth, mheight, x_advance, y_advance; + int8_t uline_position; + uint8_t uline_thickness; + os_box bbox; + + UNUSED(argc); + UNUSED(argv); + + ptmp = mkdtemp(template); + assert(NULL != ptmp); + atexit(cleanup); + chdir(ptmp); + + rufl_test_harness_init(361, true, true); + + assert(rufl_OK == rufl_init()); + assert(NULL == rufl_fm_error); + assert(3 == rufl_family_list_entries); + assert(NULL != rufl_family_menu); + + assert(rufl_OK == rufl_font_metrics("Corpus", rufl_WEIGHT_500, + &bbox, &xkern, &ykern, &italic, + &ascent, &descent, &xheight, &cap_height, + &uline_position, &uline_thickness)); + assert(0 == bbox.x0); + assert(2 == bbox.x1); + assert(0 == bbox.y0); + assert(2 == bbox.y1); + assert(0 == xkern); + assert(0 == ykern); + assert(0 == italic); + assert(0 == ascent); + assert(0 == descent); + assert((bbox.y1 - bbox.y0) == cap_height); + assert((cap_height / 2) == xheight); + assert(0 == uline_position); + assert(0 == uline_thickness); + + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, &width)); + assert(50 == width); + + /* Place caret after first character */ + assert(rufl_OK == rufl_x_to_offset("Homerton", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 25, &offset, &x)); + assert(1 == offset); + assert(25 == x); + + /* Attempt to split after first character. As the split point is + * coincident with the start of the second character, however, + * the split point is placed after it. */ + assert(rufl_OK == rufl_split("Trinity", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 25, &offset, &x)); + assert(3 == offset); + assert(50 == x); + + /* Compute width of replacement character */ + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "\xef\xbf\xbd", 3, &width)); + assert(17 == width); + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "\xf0\xa0\x80\xa5", 4, &width)); + assert(26 == width); + + /* Measure font bounding box */ + assert(rufl_OK == rufl_font_bbox("Corpus", rufl_WEIGHT_500, 160, + &bbox)); + assert(0 == bbox.x0); + assert(25 == bbox.x1); + assert(0 == bbox.y0); + assert(25 == bbox.y1); + + /* Trivial render */ + assert(rufl_OK == rufl_paint("Trinity", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 0, 0, 0)); + + /* Obtain metrics for a glyph */ + assert(rufl_OK == rufl_glyph_metrics("Homerton", rufl_WEIGHT_500, 160, + "!", 1, &x_bearing, &y_bearing, + &mwidth, &mheight, &x_advance, &y_advance)); + assert(0 == x_bearing); + assert(10000 == y_bearing); + assert(10000 == mwidth); + assert(10000 == mheight); + assert(10000 == x_advance); + assert(10000 == y_advance); + + rufl_dump_state(true); + + rufl_quit(); + + /* Reinit -- should load cache */ + assert(rufl_OK == rufl_init()); + assert(NULL == rufl_fm_error); + assert(3 == rufl_family_list_entries); + assert(NULL != rufl_family_menu); + /* Done for real this time */ + rufl_quit(); + + printf("PASS\n"); + + return 0; +} diff --git a/test/rufl_chars.c b/test/rufl_chars.c index 14a0fb6..64831ad 100644 --- a/test/rufl_chars.c +++ b/test/rufl_chars.c @@ -18,13 +18,13 @@ unsigned int font = 0; unsigned int weight = rufl_WEIGHT_400; bool italic = false; +unsigned int plane = 0; static rufl_code redraw(int x, int y, int y0, int y1); static void try(rufl_code code, const char *context); static void die(const char *error); - int main(void) { unsigned int i; @@ -73,7 +73,7 @@ int main(void) try(rufl_init(), "rufl_init"); - menu = malloc(wimp_SIZEOF_MENU(10 + rufl_family_list_entries)); + menu = malloc(wimp_SIZEOF_MENU(27 + rufl_family_list_entries)); if (!menu) die("Out of memory"); strcpy(menu->title_data.text, "Fonts"); @@ -99,23 +99,37 @@ int main(void) (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) | (wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT); strcpy(menu->entries[9].data.text, "Italic"); - for (i = 0; i != rufl_family_list_entries; i++) { - menu->entries[10 + i].menu_flags = 0; + for (i = 0; i != 17; i++) { + menu->entries[10 + i].menu_flags = + (i == 16 ? wimp_MENU_SEPARATE :0); menu->entries[10 + i].sub_menu = wimp_NO_SUB_MENU; menu->entries[10 + i].icon_flags = wimp_ICON_TEXT | + (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) | + (wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT); + strcpy(menu->entries[10 + i].data.text, "Plane 1"); + menu->entries[10 + i].data.text[6] = '0' + (i+1)/10; + if (menu->entries[10 + i].data.text[6] == '0') + menu->entries[10 + i].data.text[6] = ' '; + menu->entries[10 + i].data.text[7] = '0' + (i+1)%10; + } + for (i = 0; i != rufl_family_list_entries; i++) { + menu->entries[27 + i].menu_flags = 0; + menu->entries[27 + i].sub_menu = wimp_NO_SUB_MENU; + menu->entries[27 + i].icon_flags = wimp_ICON_TEXT | wimp_ICON_INDIRECTED | (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) | (wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT); - menu->entries[10 + i].data.indirected_text.text = + menu->entries[27 + i].data.indirected_text.text = (char *) rufl_family_list[i]; - menu->entries[10 + i].data.indirected_text.validation = + menu->entries[27 + i].data.indirected_text.validation = (char *) -1; - menu->entries[10 + i].data.indirected_text.size = + menu->entries[27 + i].data.indirected_text.size = strlen(rufl_family_list[i]); } menu->entries[3].menu_flags |= wimp_MENU_TICKED; menu->entries[10].menu_flags |= wimp_MENU_TICKED; - menu->entries[i + 9].menu_flags |= wimp_MENU_LAST; + menu->entries[27].menu_flags |= wimp_MENU_TICKED; + menu->entries[i + 26].menu_flags |= wimp_MENU_LAST; error = xwimp_create_window((wimp_window *) &window, &w); if (error) @@ -194,11 +208,17 @@ int main(void) } else if (block.selection.items[0] == 9) { italic = !italic; menu->entries[9].menu_flags ^= wimp_MENU_TICKED; + } else if (block.selection.items[0] <= 26) { + menu->entries[10 + plane].menu_flags ^= + wimp_MENU_TICKED; + plane = block.selection.items[0] - 10; + menu->entries[10 + plane].menu_flags ^= + wimp_MENU_TICKED; } else { - menu->entries[10 + font].menu_flags ^= + menu->entries[27 + font].menu_flags ^= wimp_MENU_TICKED; - font = block.selection.items[0] - 10; - menu->entries[10 + font].menu_flags ^= + font = block.selection.items[0] - 27; + menu->entries[27 + font].menu_flags ^= wimp_MENU_TICKED; } error = xwimp_force_redraw(w, @@ -249,15 +269,21 @@ rufl_code redraw(int x, int y, int y0, int y1) rufl_style style = weight | (italic ? rufl_SLANTED : 0); for (u = y0 / 40 * 32; (int) u != (y1 / 40 + 1) * 32; u++) { - if (u <= 0x7f) - s[0] = u, l = 1; - else if (u <= 0x7ff) - s[0] = 0xc0 | (u >> 6), - s[1] = 0x80 | (u & 0x3f), l = 2; - else if (u <= 0xffff) - s[0] = 0xe0 | (u >> 12), - s[1] = 0x80 | ((u >> 6) & 0x3f), - s[2] = 0x80 | (u & 0x3f), l = 3; + unsigned int c = (plane << 16) | u; + if (c <= 0x7f) + s[0] = c, l = 1; + else if (c <= 0x7ff) + s[0] = 0xc0 | (c >> 6), + s[1] = 0x80 | (c & 0x3f), l = 2; + else if (c <= 0xffff) + s[0] = 0xe0 | (c >> 12), + s[1] = 0x80 | ((c >> 6) & 0x3f), + s[2] = 0x80 | (c & 0x3f), l = 3; + else if (c <= 0x10ffff) + s[0] = 0xf0 | (c >> 18), + s[1] = 0x80 | ((c >> 12) & 0x3f), + s[2] = 0x80 | ((c >> 6) & 0x3f), + s[3] = 0x80 | (c & 0x3f), l = 4; else break; s[l] = 0; @@ -276,7 +302,7 @@ rufl_code redraw(int x, int y, int y0, int y1) void try(rufl_code code, const char *context) { - char s[200]; + char s[400]; if (code == rufl_OK) return; else if (code == rufl_OUT_OF_MEMORY) diff --git a/test/rufl_test.c b/test/rufl_test.c index 51a29d6..bc644f5 100644 --- a/test/rufl_test.c +++ b/test/rufl_test.c @@ -18,23 +18,24 @@ static int cubic_to(os_coord *control1, os_coord *control2, os_coord *to, void *user); static void callback(void *context, const char *font_name, unsigned int font_size, - const char *s8, unsigned short *s16, unsigned int n, + const uint8_t *s8, const uint32_t *s32, unsigned int n, int x, int y); int main(void) { - char utf8_test[] = "Hello, world! ὕαλον " - "Uherské Hradiště. 𐀀"; + const char utf8_test[] = "Hello, world! ὕαλον " + "Uherské Hradiště. 𐀀" + "\xf0\xa0\x80\xa1"; int width; size_t char_offset; int x; int actual_x; struct rufl_decomp_funcs funcs = { move_to, line_to, cubic_to }; - int bbox[4]; + os_box bbox; try(rufl_init(), "rufl_init"); - rufl_dump_state(); + rufl_dump_state(false); try(rufl_paint("NewHall", rufl_WEIGHT_400, 240, utf8_test, sizeof utf8_test - 1, 1200, 1000, 0), "rufl_paint"); @@ -62,9 +63,9 @@ int main(void) try(rufl_paint_callback("NewHall", rufl_WEIGHT_400, 240, utf8_test, sizeof utf8_test - 1, 1200, 1000, callback, 0), "rufl_paint_callback"); - try(rufl_font_bbox("NewHall", rufl_WEIGHT_400, 240, bbox), + try(rufl_font_bbox("NewHall", rufl_WEIGHT_400, 240, &bbox), "rufl_font_bbox"); - printf("bbox: %i %i %i %i\n", bbox[0], bbox[1], bbox[2], bbox[3]); + printf("bbox: %i %i %i %i\n", bbox.x0, bbox.y0, bbox.x1, bbox.y1); rufl_quit(); return 0; @@ -131,7 +132,7 @@ int cubic_to(os_coord *control1, os_coord *control2, os_coord *to, void callback(void *context, const char *font_name, unsigned int font_size, - const char *s8, unsigned short *s16, unsigned int n, + const uint8_t *s8, const uint32_t *s32, unsigned int n, int x, int y) { (void) context; @@ -140,9 +141,9 @@ void callback(void *context, if (s8) printf("s8 \"%.*s\" ", n, s8); else { - printf("s16 \""); + printf("s32 \""); for (unsigned int i = 0; i != n; i++) - printf("%x ", (unsigned int) s16[i]); + printf("%x ", (unsigned int) s32[i]); printf("\" "); } printf("%i %i\n", x, y); diff --git a/test/testutils.h b/test/testutils.h new file mode 100644 index 0000000..7fe6333 --- /dev/null +++ b/test/testutils.h @@ -0,0 +1,123 @@ +#ifndef test_testutils_h_ +#define test_testutils_h_ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +#ifndef UNUSED +#define UNUSED(x) ((void) (x)) +#endif + +/* Redefine assert, so we can simply use the standard assert mechanism + * within testcases and exit with the right output for the testrunner + * to do the right thing. */ +void __assert2(const char *expr, const char *function, + const char *file, int line); + +void __assert2(const char *expr, const char *function, + const char *file, int line) +{ + UNUSED(function); + UNUSED(file); + + printf("FAIL - %s at line %d\n", expr, line); + + exit(EXIT_FAILURE); +} + +#define assert(expr) \ + ((void) ((expr) || (__assert2 (#expr, __func__, __FILE__, __LINE__), 0))) + + +typedef bool (*line_func)(const char *data, size_t datalen, void *pw); + +static size_t parse_strlen(const char *str, size_t limit); +bool parse_testfile(const char *filename, line_func callback, void *pw); +size_t parse_filesize(const char *filename); + +/** + * Testcase datafile parser driver + * + * \param filename Name of file to parse + * \param callback Pointer to function to handle each line of input data + * \param pw Pointer to client-specific private data + * \return true on success, false otherwise. + */ +bool parse_testfile(const char *filename, line_func callback, void *pw) +{ + FILE *fp; + char buf[300]; + + fp = fopen(filename, "rb"); + if (fp == NULL) { + printf("Failed opening %s\n", filename); + return false; + } + + while (fgets(buf, sizeof buf, fp)) { + if (buf[0] == '\n') + continue; + + if (!callback(buf, parse_strlen(buf, sizeof buf - 1), pw)) { + fclose(fp); + return false; + } + } + + fclose(fp); + + return true; +} + +/** + * Utility string length measurer; assumes strings are '\n' terminated + * + * \param str String to measure length of + * \param limit Upper bound on string length + * \return String length + */ +size_t parse_strlen(const char *str, size_t limit) +{ + size_t len = 0; + + if (str == NULL) + return 0; + + while (len < limit - 1 && *str != '\n') { + len++; + str++; + } + + len++; + + return len; +} + +/** + * Read the size of a file + * + * \param filename Name of file to read size of + * \return File size (in bytes), or 0 on error + */ +size_t parse_filesize(const char *filename) +{ + FILE *fp; + size_t len = 0; + + fp = fopen(filename, "rb"); + if (fp == NULL) { + printf("Failed opening %s\n", filename); + return 0; + } + + fseek(fp, 0, SEEK_END); + len = ftell(fp); + + fclose(fp); + + return len; +} + + +#endif diff --git a/test/ucsinit.c b/test/ucsinit.c new file mode 100644 index 0000000..25aea60 --- /dev/null +++ b/test/ucsinit.c @@ -0,0 +1,140 @@ +#include <ftw.h> +#include <stdio.h> +#include <unistd.h> + +#include "rufl.h" + +#include "harness.h" +#include "testutils.h" + +static char template[] = "/tmp/ucsinitXXXXXX"; +static const char *ptmp = NULL; + +static int ftw_cb(const char *path, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + (void) sb; + (void) typeflag; + (void) ftwbuf; + + remove(path); + + return 0; +} + +static void cleanup(void) +{ + if (ptmp == NULL) + return; + + nftw(ptmp, ftw_cb, FOPEN_MAX, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); +} + +int main(int argc, const char **argv) +{ + int width, x; + size_t offset; + int32_t xkern, ykern, italic, ascent, descent, xheight, cap_height; + int32_t x_bearing, y_bearing, mwidth, mheight, x_advance, y_advance; + int8_t uline_position; + uint8_t uline_thickness; + os_box bbox; + + UNUSED(argc); + UNUSED(argv); + + ptmp = mkdtemp(template); + assert(NULL != ptmp); + atexit(cleanup); + chdir(ptmp); + + rufl_test_harness_init(380, true, true); + + assert(rufl_OK == rufl_init()); + assert(NULL == rufl_fm_error); + assert(3 == rufl_family_list_entries); + assert(NULL != rufl_family_menu); + + assert(rufl_OK == rufl_font_metrics("Corpus", rufl_WEIGHT_500, + &bbox, &xkern, &ykern, &italic, + &ascent, &descent, &xheight, &cap_height, + &uline_position, &uline_thickness)); + assert(0 == bbox.x0); + assert(2 == bbox.x1); + assert(0 == bbox.y0); + assert(2 == bbox.y1); + assert(0 == xkern); + assert(0 == ykern); + assert(0 == italic); + assert(0 == ascent); + assert(0 == descent); + assert((bbox.y1 - bbox.y0) == cap_height); + assert((cap_height / 2) == xheight); + assert(0 == uline_position); + assert(0 == uline_thickness); + + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, &width)); + assert(50 == width); + + /* Place caret after first character */ + assert(rufl_OK == rufl_x_to_offset("Homerton", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 25, &offset, &x)); + assert(1 == offset); + assert(25 == x); + + /* Attempt to split after first character. As the split point is + * coincident with the start of the second character, however, + * the split point is placed after it. */ + assert(rufl_OK == rufl_split("Trinity", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 25, &offset, &x)); + assert(3 == offset); + assert(50 == x); + + /* Compute width of replacement character */ + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "\xef\xbf\xbd", 3, &width)); + assert(17 == width); + assert(rufl_OK == rufl_width("Corpus", rufl_WEIGHT_500, 160, + "\xf0\xa0\x80\xa5", 4, &width)); + assert(26 == width); + + /* Measure font bounding box */ + assert(rufl_OK == rufl_font_bbox("Corpus", rufl_WEIGHT_500, 160, + &bbox)); + assert(0 == bbox.x0); + assert(25 == bbox.x1); + assert(0 == bbox.y0); + assert(25 == bbox.y1); + + /* Trivial render */ + assert(rufl_OK == rufl_paint("Trinity", rufl_WEIGHT_500, 160, + "!\xc2\xa0", 3, 0, 0, 0)); + + /* Obtain metrics for a glyph */ + assert(rufl_OK == rufl_glyph_metrics("Homerton", rufl_WEIGHT_500, 160, + "!", 1, &x_bearing, &y_bearing, + &mwidth, &mheight, &x_advance, &y_advance)); + assert(0 == x_bearing); + assert(10000 == y_bearing); + assert(10000 == mwidth); + assert(10000 == mheight); + assert(10000 == x_advance); + assert(10000 == y_advance); + + rufl_dump_state(true); + + rufl_quit(); + + /* Reinit -- should load cache */ + assert(rufl_OK == rufl_init()); + assert(NULL == rufl_fm_error); + assert(3 == rufl_family_list_entries); + assert(NULL != rufl_family_menu); + /* Done for real this time */ + rufl_quit(); + + printf("PASS\n"); + + return 0; +} |