#include "mupdf/fitz.h" #include "draw-imp.h" #define MAX_GLYPH_SIZE 256 #define MAX_CACHE_SIZE (1024*1024) #define GLYPH_HASH_LEN 509 typedef struct fz_glyph_cache_entry_s fz_glyph_cache_entry; typedef struct fz_glyph_key_s fz_glyph_key; struct fz_glyph_key_s { fz_font *font; int a, b; int c, d; unsigned short gid; unsigned char e, f; int aa; }; struct fz_glyph_cache_entry_s { fz_glyph_key key; unsigned hash; fz_glyph_cache_entry *lru_prev; fz_glyph_cache_entry *lru_next; fz_glyph_cache_entry *bucket_next; fz_glyph_cache_entry *bucket_prev; fz_glyph *val; }; struct fz_glyph_cache_s { int refs; size_t total; #ifndef NDEBUG int num_evictions; ptrdiff_t evicted; #endif fz_glyph_cache_entry *entry[GLYPH_HASH_LEN]; fz_glyph_cache_entry *lru_head; fz_glyph_cache_entry *lru_tail; }; void fz_new_glyph_cache_context(fz_context *ctx) { fz_glyph_cache *cache; cache = fz_malloc_struct(ctx, fz_glyph_cache); cache->total = 0; cache->refs = 1; ctx->glyph_cache = cache; } static void drop_glyph_cache_entry(fz_context *ctx, fz_glyph_cache_entry *entry) { fz_glyph_cache *cache = ctx->glyph_cache; if (entry->lru_next) entry->lru_next->lru_prev = entry->lru_prev; else cache->lru_tail = entry->lru_prev; if (entry->lru_prev) entry->lru_prev->lru_next = entry->lru_next; else cache->lru_head = entry->lru_next; cache->total -= fz_glyph_size(ctx, entry->val); if (entry->bucket_next) entry->bucket_next->bucket_prev = entry->bucket_prev; if (entry->bucket_prev) entry->bucket_prev->bucket_next = entry->bucket_next; else cache->entry[entry->hash] = entry->bucket_next; fz_drop_font(ctx, entry->key.font); fz_drop_glyph(ctx, entry->val); fz_free(ctx, entry); } /* The glyph cache lock is always held when this function is called. */ static void do_purge(fz_context *ctx) { fz_glyph_cache *cache = ctx->glyph_cache; int i; for (i = 0; i < GLYPH_HASH_LEN; i++) { while (cache->entry[i]) drop_glyph_cache_entry(ctx, cache->entry[i]); } cache->total = 0; } void fz_purge_glyph_cache(fz_context *ctx) { fz_lock(ctx, FZ_LOCK_GLYPHCACHE); do_purge(ctx); fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); } void fz_drop_glyph_cache_context(fz_context *ctx) { if (!ctx->glyph_cache) return; fz_lock(ctx, FZ_LOCK_GLYPHCACHE); ctx->glyph_cache->refs--; if (ctx->glyph_cache->refs == 0) { do_purge(ctx); fz_free(ctx, ctx->glyph_cache); ctx->glyph_cache = NULL; } fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); } fz_glyph_cache * fz_keep_glyph_cache(fz_context *ctx) { fz_lock(ctx, FZ_LOCK_GLYPHCACHE); ctx->glyph_cache->refs++; fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); return ctx->glyph_cache; } float fz_subpixel_adjust(fz_context *ctx, fz_matrix *ctm, fz_matrix *subpix_ctm, unsigned char *qe, unsigned char *qf) { float size = fz_matrix_expansion(ctm); int q; float pix_e, pix_f, r; /* Quantise the subpixel positions. */ /* We never need more than 4 subpixel positions for glyphs - arguably * even that is too much. */ if (size >= 48) q = 0, r = 0.5f; else if (size >= 24) q = 128, r = 0.25f; else q = 192, r = 0.125f; /* Split translation into pixel and subpixel parts */ subpix_ctm->a = ctm->a; subpix_ctm->b = ctm->b; subpix_ctm->c = ctm->c; subpix_ctm->d = ctm->d; subpix_ctm->e = ctm->e + r; pix_e = floorf(subpix_ctm->e); subpix_ctm->e -= pix_e; subpix_ctm->f = ctm->f + r; pix_f = floorf(subpix_ctm->f); subpix_ctm->f -= pix_f; /* Quantise the subpixel part */ *qe = (int)(subpix_ctm->e * 256) & q; subpix_ctm->e = *qe / 256.0f; *qf = (int)(subpix_ctm->f * 256) & q; subpix_ctm->f = *qf / 256.0f; /* Reassemble the complete translation */ ctm->e = subpix_ctm->e + pix_e; ctm->f = subpix_ctm->f + pix_f; return size; } fz_glyph * fz_render_stroked_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix *trm, const fz_matrix *ctm, const fz_stroke_state *stroke, const fz_irect *scissor) { if (fz_font_ft_face(font)) { fz_matrix subpix_trm; unsigned char qe, qf; if (stroke->dash_len > 0) return NULL; (void)fz_subpixel_adjust(ctx, trm, &subpix_trm, &qe, &qf); return fz_render_ft_stroked_glyph(ctx, font, gid, &subpix_trm, ctm, stroke); } return fz_render_glyph(ctx, font, gid, trm, NULL, scissor, 1); } fz_pixmap * fz_render_stroked_glyph_pixmap(fz_context *ctx, fz_font *font, int gid, fz_matrix *trm, const fz_matrix *ctm, const fz_stroke_state *stroke, const fz_irect *scissor) { if (fz_font_ft_face(font)) { fz_matrix subpix_trm; unsigned char qe, qf; if (stroke->dash_len > 0) return NULL; (void)fz_subpixel_adjust(ctx, trm, &subpix_trm, &qe, &qf); return fz_render_ft_stroked_glyph_pixmap(ctx, font, gid, &subpix_trm, ctm, stroke); } return fz_render_glyph_pixmap(ctx, font, gid, trm, scissor); } static unsigned do_hash(unsigned char *s, int len) { unsigned val = 0; int i; for (i = 0; i < len; i++) { val += s[i]; val += (val << 10); val ^= (val >> 6); } val += (val << 3); val ^= (val >> 11); val += (val << 15); return val; } static inline void move_to_front(fz_glyph_cache *cache, fz_glyph_cache_entry *entry) { if (entry->lru_prev == NULL) return; /* At front already */ /* Unlink */ entry->lru_prev->lru_next = entry->lru_next; if (entry->lru_next) entry->lru_next->lru_prev = entry->lru_prev; else cache->lru_tail = entry->lru_prev; /* Relink */ entry->lru_next = cache->lru_head; if (entry->lru_next) entry->lru_next->lru_prev = entry; cache->lru_head = entry; entry->lru_prev = NULL; } fz_glyph * fz_render_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix *ctm, fz_colorspace *model, const fz_irect *scissor, int alpha) { fz_glyph_cache *cache; fz_glyph_key key; fz_matrix subpix_ctm; fz_irect subpix_scissor; float size; fz_glyph *val; int do_cache, locked, caching; fz_glyph_cache_entry *entry; unsigned hash; int is_ft_font = !!fz_font_ft_face(font); fz_var(locked); fz_var(caching); fz_var(val); memset(&key, 0, sizeof key); size = fz_subpixel_adjust(ctx, ctm, &subpix_ctm, &key.e, &key.f); if (size <= MAX_GLYPH_SIZE) { scissor = &fz_infinite_irect; do_cache = 1; } else { if (is_ft_font) return NULL; subpix_scissor.x0 = scissor->x0 - floorf(ctm->e); subpix_scissor.y0 = scissor->y0 - floorf(ctm->f); subpix_scissor.x1 = scissor->x1 - floorf(ctm->e); subpix_scissor.y1 = scissor->y1 - floorf(ctm->f); scissor = &subpix_scissor; do_cache = 0; } cache = ctx->glyph_cache; key.font = font; key.gid = gid; key.a = subpix_ctm.a * 65536; key.b = subpix_ctm.b * 65536; key.c = subpix_ctm.c * 65536; key.d = subpix_ctm.d * 65536; key.aa = fz_text_aa_level(ctx); hash = do_hash((unsigned char *)&key, sizeof(key)) % GLYPH_HASH_LEN; fz_lock(ctx, FZ_LOCK_GLYPHCACHE); entry = cache->entry[hash]; while (entry) { if (memcmp(&entry->key, &key, sizeof(key)) == 0) { move_to_front(cache, entry); val = fz_keep_glyph(ctx, entry->val); fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); return val; } entry = entry->bucket_next; } locked = 1; caching = 0; val = NULL; fz_try(ctx) { if (is_ft_font) { val = fz_render_ft_glyph(ctx, font, gid, &subpix_ctm, key.aa); } else if (fz_font_t3_procs(font)) { /* We drop the glyphcache here, and execute the t3 * glyph code. The danger here is that some other * thread will come along, and want the same glyph * too. If it does, we may both end up rendering * pixmaps. We cope with this later on, by ensuring * that only one gets inserted into the cache. If * we insert ours to find one already there, we * abandon ours, and use the one there already. */ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); locked = 0; val = fz_render_t3_glyph(ctx, font, gid, &subpix_ctm, model, scissor); fz_lock(ctx, FZ_LOCK_GLYPHCACHE); locked = 1; } else { fz_warn(ctx, "assert: uninitialized font structure"); } if (val && do_cache) { if (val->w < MAX_GLYPH_SIZE && val->h < MAX_GLYPH_SIZE) { /* If we throw an exception whilst caching, * just ignore the exception and carry on. */ caching = 1; if (!is_ft_font) { /* We had to unlock. Someone else might * have rendered in the meantime */ entry = cache->entry[hash]; while (entry) { if (memcmp(&entry->key, &key, sizeof(key)) == 0) { fz_drop_glyph(ctx, val); move_to_front(cache, entry); val = fz_keep_glyph(ctx, entry->val); goto unlock_and_return_val; } entry = entry->bucket_next; } } entry = fz_malloc_struct(ctx, fz_glyph_cache_entry); entry->key = key; entry->hash = hash; entry->bucket_next = cache->entry[hash]; if (entry->bucket_next) entry->bucket_next->bucket_prev = entry; cache->entry[hash] = entry; entry->val = fz_keep_glyph(ctx, val); fz_keep_font(ctx, key.font); entry->lru_next = cache->lru_head; if (entry->lru_next) entry->lru_next->lru_prev = entry; else cache->lru_tail = entry; cache->lru_head = entry; cache->total += fz_glyph_size(ctx, val); while (cache->total > MAX_CACHE_SIZE) { #ifndef NDEBUG cache->num_evictions++; cache->evicted += fz_glyph_size(ctx, cache->lru_tail->val); #endif drop_glyph_cache_entry(ctx, cache->lru_tail); } } } unlock_and_return_val: { } } fz_always(ctx) { if (locked) fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); } fz_catch(ctx) { if (caching) fz_warn(ctx, "cannot encache glyph; continuing"); else fz_rethrow(ctx); } return val; } fz_pixmap * fz_render_glyph_pixmap(fz_context *ctx, fz_font *font, int gid, fz_matrix *ctm, const fz_irect *scissor) { fz_pixmap *val; unsigned char qe, qf; fz_matrix subpix_ctm; float size = fz_subpixel_adjust(ctx, ctm, &subpix_ctm, &qe, &qf); int is_ft_font = !!fz_font_ft_face(font); if (size <= MAX_GLYPH_SIZE) { scissor = &fz_infinite_irect; } else { if (is_ft_font) return NULL; } fz_try(ctx) { if (is_ft_font) { val = fz_render_ft_glyph_pixmap(ctx, font, gid, &subpix_ctm, fz_text_aa_level(ctx)); } else if (fz_font_t3_procs(font)) { val = fz_render_t3_glyph_pixmap(ctx, font, gid, &subpix_ctm, NULL, scissor); } else { fz_warn(ctx, "assert: uninitialized font structure"); val = NULL; } } fz_catch(ctx) { fz_rethrow(ctx); } return val; } void fz_dump_glyph_cache_stats(fz_context *ctx) { fz_glyph_cache *cache = ctx->glyph_cache; fprintf(stderr, "Glyph Cache Size: " FMT_zu "\n", cache->total); #ifndef NDEBUG fprintf(stderr, "Glyph Cache Evictions: %d (" FMT_zu " bytes)\n", cache->num_evictions, cache->evicted); #endif }