summaryrefslogtreecommitdiff
path: root/render/glyphcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'render/glyphcache.c')
-rw-r--r--render/glyphcache.c370
1 files changed, 370 insertions, 0 deletions
diff --git a/render/glyphcache.c b/render/glyphcache.c
new file mode 100644
index 00000000..81b1f5d5
--- /dev/null
+++ b/render/glyphcache.c
@@ -0,0 +1,370 @@
+#include <fitz.h>
+
+#define HSUBPIX 5.0
+#define VSUBPIX 5.0
+
+typedef struct fz_hash_s fz_hash;
+typedef struct fz_key_s fz_key;
+typedef struct fz_val_s fz_val;
+
+struct fz_glyphcache_s
+{
+ int slots;
+ int size;
+ fz_hash *hash;
+ fz_val *lru;
+ unsigned char *buffer;
+ int load;
+ int used;
+};
+
+struct fz_key_s
+{
+ void *fid;
+ int a, b;
+ int c, d;
+ unsigned short gid;
+ unsigned char e, f;
+};
+
+struct fz_hash_s
+{
+ fz_key key;
+ fz_val *val;
+};
+
+struct fz_val_s
+{
+ fz_hash *ent;
+ unsigned char *data;
+ short w, h, lsb, top;
+ int uses;
+};
+
+static unsigned int hashkey(fz_key *key)
+{
+ unsigned char *s = (char*)key;
+ unsigned int hash = 0;
+ unsigned int i;
+ for (i = 0; i < sizeof(fz_key); i++)
+ {
+ hash += s[i];
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return hash;
+}
+
+fz_error *
+fz_newglyphcache(fz_glyphcache **arenap, int slots, int size)
+{
+ fz_glyphcache *arena;
+
+ arena = *arenap = fz_malloc(sizeof(fz_glyphcache));
+ if (!arena)
+ return fz_outofmem;
+
+ arena->slots = slots;
+ arena->size = size;
+
+ arena->hash = nil;
+ arena->lru = nil;
+ arena->buffer = nil;
+
+ arena->hash = fz_malloc(sizeof(fz_hash) * slots);
+ if (!arena->hash)
+ goto cleanup;
+
+ arena->lru = fz_malloc(sizeof(fz_val) * slots);
+ if (!arena->lru)
+ goto cleanup;
+
+ arena->buffer = fz_malloc(size);
+ if (!arena->buffer)
+ goto cleanup;
+
+ memset(arena->hash, 0, sizeof(fz_hash) * slots);
+ memset(arena->lru, 0, sizeof(fz_val) * slots);
+ memset(arena->buffer, 0, size);
+ arena->load = 0;
+ arena->used = 0;
+
+ return nil;
+
+cleanup:
+ fz_free(arena->hash);
+ fz_free(arena->lru);
+ fz_free(arena->buffer);
+ fz_free(arena);
+ return fz_outofmem;
+}
+
+void
+fz_freeglyphcache(fz_glyphcache *arena)
+{
+ fz_free(arena->hash);
+ fz_free(arena->lru);
+ fz_free(arena->buffer);
+ fz_free(arena);
+}
+
+static int hokay = 0;
+static int hcoll = 0;
+static int hdist = 0;
+static int coos = 0;
+static int covf = 0;
+
+static fz_val *
+hashfind(fz_glyphcache *arena, fz_key *key)
+{
+ fz_hash *tab = arena->hash;
+ int pos = hashkey(key) % arena->slots;
+
+ while (1)
+ {
+ if (!tab[pos].val)
+ return nil;
+
+ if (memcmp(key, &tab[pos].key, sizeof (fz_key)) == 0)
+ return tab[pos].val;
+
+ pos = pos + 1;
+ if (pos == arena->slots)
+ pos = 0;
+ }
+}
+
+static void
+hashinsert(fz_glyphcache *arena, fz_key *key, fz_val *val)
+{
+ fz_hash *tab = arena->hash;
+ int pos = hashkey(key) % arena->slots;
+int didcoll = 0;
+
+ while (1)
+ {
+ if (!tab[pos].val)
+ {
+ tab[pos].key = *key;
+ tab[pos].val = val;
+ tab[pos].val->ent = &tab[pos];
+if (didcoll) hcoll ++; else hokay ++; hdist += didcoll;
+ return;
+ }
+
+ pos = pos + 1;
+ if (pos == arena->slots)
+ pos = 0;
+didcoll ++;
+ }
+}
+
+static void
+hashremove(fz_glyphcache *arena, fz_key *key)
+{
+ fz_hash *tab = arena->hash;
+ unsigned int pos = hashkey(key) % arena->slots;
+ unsigned int hole;
+ unsigned int look;
+ unsigned int code;
+
+ while (1)
+ {
+ if (!tab[pos].val)
+ return; /* boo hoo! tried to remove non-existant key */
+
+ if (memcmp(key, &tab[pos].key, sizeof (fz_key)) == 0)
+ {
+ tab[pos].val = nil;
+
+ hole = pos;
+ look = hole + 1;
+ if (look == arena->slots)
+ look = 0;
+
+ while (tab[look].val)
+ {
+ code = (hashkey(&tab[look].key) % arena->slots);
+ if ((code <= hole && hole < look) ||
+ (look < code && code <= hole) ||
+ (hole < look && look < code))
+ {
+ tab[hole] = tab[look];
+ tab[hole].val->ent = &tab[hole];
+ tab[look].val = nil;
+ hole = look;
+ }
+
+ look = look + 1;
+ if (look == arena->slots)
+ look = 0;
+ }
+
+ return;
+ }
+
+ pos = pos + 1;
+ if (pos == arena->slots)
+ pos = 0;
+ }
+}
+
+void
+fz_debugglyphcache(fz_glyphcache *arena)
+{
+ int i;
+
+ printf("cache load %d / %d (%d / %d bytes)\n",
+ arena->load, arena->slots, arena->used, arena->size);
+ printf("no-colliders: %d colliders: %d\n", hokay, hcoll);
+ printf("avg dist: %d / %d: %g\n", hdist, hcoll, (double)hdist / hcoll);
+ printf("out-of-space evicts: %d\n", coos);
+ printf("out-of-hash evicts: %d\n", covf);
+
+ for (i = 0; i < arena->slots; i++)
+ {
+ if (!arena->hash[i].val)
+ printf("glyph % 4d: empty\n", i);
+ else {
+ fz_key *k = &arena->hash[i].key;
+ fz_val *b = arena->hash[i].val;
+ printf("glyph % 4d: %p %d [%g %g %g %g + %d %d] "
+ "-> [%dx%d %d,%d]\n", i,
+ k->fid, k->gid,
+ k->a / 65536.0,
+ k->b / 65536.0,
+ k->c / 65536.0,
+ k->d / 65536.0,
+ k->e, k->f,
+ b->w, b->h, b->lsb, b->top);
+ }
+ }
+
+ for (i = 0; i < arena->load; i++)
+ printf("lru %04d: glyph %d (%d)\n", i,
+ arena->lru[i].ent - arena->hash, arena->lru[i].uses);
+}
+
+static void
+bubble(fz_glyphcache *arena, int i)
+{
+ fz_val tmp;
+
+ if (i == 0 || arena->load < 2)
+ return;
+
+ tmp = arena->lru[i - 1];
+ arena->lru[i - 1] = arena->lru[i];
+ arena->lru[i] = tmp;
+
+ arena->lru[i - 1].ent->val = &arena->lru[i - 1];
+ arena->lru[i].ent->val = &arena->lru[i];
+}
+
+static void
+evictlast(fz_glyphcache *arena)
+{
+ fz_val *lru = arena->lru;
+ unsigned char *s, *e;
+ int i, k;
+ fz_key key;
+
+ if (arena->load == 0)
+ return;
+
+ k = arena->load - 1;
+ s = lru[k].data;
+ e = s + lru[k].w * lru[k].h;
+
+ /* pack buffer to fill hole */
+ memmove(s, e, arena->buffer + arena->used - e);
+ memset(arena->buffer + arena->used - (e - s), 0, e - s);
+ arena->used -= e - s;
+
+ /* update lru pointers */
+ for (i = 0; i < k; i++)
+ if (lru[i].data >= e)
+ lru[i].data -= e - s;
+
+ /* remove hash entry */
+ key = lru[k].ent->key;
+ hashremove(arena, &key);
+
+ arena->load --;
+}
+
+fz_error *
+fz_renderglyph(fz_glyphcache *arena, fz_glyph *glyph, fz_font *font, int gid, fz_matrix ctm)
+{
+ fz_error *error;
+ fz_key key;
+ fz_val *val;
+ int size;
+
+ key.fid = font;
+ key.gid = gid;
+ key.a = ctm.a * 65536;
+ key.b = ctm.b * 65536;
+ key.c = ctm.c * 65536;
+ key.d = ctm.d * 65536;
+ key.e = (ctm.e - floor(ctm.e)) * HSUBPIX;
+ key.f = (ctm.f - floor(ctm.f)) * VSUBPIX;
+
+ val = hashfind(arena, &key);
+ if (val)
+ {
+ val->uses ++;
+ glyph->w = val->w;
+ glyph->h = val->h;
+ glyph->lsb = val->lsb;
+ glyph->top = val->top;
+ glyph->bitmap = val->data;
+
+ bubble(arena, val - arena->lru);
+
+ return nil;
+ }
+
+ ctm.e = floor(ctm.e) + key.e / HSUBPIX;
+ ctm.f = floor(ctm.f) + key.f / HSUBPIX;
+
+ error = font->render(glyph, font, gid, ctm);
+ if (error)
+ return error;
+
+ size = glyph->w * glyph->h;
+
+ if (size > arena->size / 6)
+ return nil;
+
+ while (arena->load > arena->slots * 75 / 100) {
+ covf ++;
+ evictlast(arena);
+ }
+
+ while (arena->used + size >= arena->size) {
+ coos ++;
+ evictlast(arena);
+ }
+
+ val = &arena->lru[arena->load++];
+ val->uses = 0;
+ val->w = glyph->w;
+ val->h = glyph->h;
+ val->lsb = glyph->lsb;
+ val->top = glyph->top;
+ val->data = arena->buffer + arena->used;
+
+ arena->used += size;
+
+ memcpy(val->data, glyph->bitmap, glyph->w * glyph->h);
+ glyph->bitmap = val->data;
+
+ hashinsert(arena, &key, val);
+
+ return nil;
+}
+