diff mupdf-source/source/fitz/draw-glyph.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/source/fitz/draw-glyph.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,488 @@
+// Copyright (C) 2004-2021 Artifex Software, Inc.
+//
+// This file is part of MuPDF.
+//
+// MuPDF is free software: you can redistribute it and/or modify it under the
+// terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
+//
+// Alternative licensing terms are available from the licensor.
+// For commercial licensing, see <https://www.artifex.com/> or contact
+// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
+// CA 94129, USA, for further information.
+
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+#include "glyph-imp.h"
+#include "pixmap-imp.h"
+
+#include <string.h>
+#include <math.h>
+
+#define MAX_GLYPH_SIZE 256
+#define MAX_CACHE_SIZE (1024*1024)
+
+#define GLYPH_HASH_LEN 509
+
+typedef struct
+{
+	fz_font *font;
+	int a, b;
+	int c, d;
+	unsigned short gid;
+	unsigned char e, f;
+	int aa;
+} fz_glyph_key;
+
+typedef struct fz_glyph_cache_entry
+{
+	fz_glyph_key key;
+	unsigned hash;
+	struct fz_glyph_cache_entry *lru_prev;
+	struct fz_glyph_cache_entry *lru_next;
+	struct fz_glyph_cache_entry *bucket_next;
+	struct fz_glyph_cache_entry *bucket_prev;
+	fz_glyph *val;
+} fz_glyph_cache_entry;
+
+struct fz_glyph_cache
+{
+	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;
+};
+
+static size_t
+fz_glyph_size(fz_context *ctx, fz_glyph *glyph)
+{
+	if (glyph == NULL)
+		return 0;
+	return sizeof(fz_glyph) + glyph->size + fz_pixmap_size(ctx, glyph->pixmap);
+}
+
+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 || !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, hq, vq, qmin;
+	float pix_e, pix_f, r, hr, vr, rmin;
+
+	/* Quantise the subpixel positions. First, in the direction of
+	 * movement (i.e. normally X). We never need more than 4 subpixel
+	 * positions for glyphs - arguably even that is too much.
+	 * Suppress this as we get larger, because it makes less impact. */
+	if (size >= 48)
+		q = 0, r = 0.5f;
+	else if (size >= 24)
+		q = 128, r = 0.25f;
+	else
+		q = 192, r = 0.125f;
+
+	/* Then in the 'downward' direction (normally Y). */
+	if (size >= 8)
+		qmin = 0, rmin = 0.5f;
+	else if (size >= 4)
+		qmin = 128, rmin =  0.25f;
+	else
+		qmin = 192, rmin = 0.125f;
+
+	/* Suppress subpixel antialiasing in y axis if we have a horizontal
+	 * matrix, and in x axis if we have a vertical matrix, unless we're
+	 * really small. */
+	hq = vq = q;
+	hr = vr = r;
+	if (ctm->a == 0 && ctm->d == 0)
+		hq = qmin, hr = rmin;
+	if (ctm->b == 0 && ctm->c == 0)
+		vq = qmin, vr = rmin;
+
+	/* 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 + hr;
+	pix_e = floorf(subpix_ctm->e);
+	subpix_ctm->e -= pix_e;
+	subpix_ctm->f = ctm->f + vr;
+	pix_f = floorf(subpix_ctm->f);
+	subpix_ctm->f -= pix_f;
+
+	/* Quantise the subpixel part */
+	*qe = (int)(subpix_ctm->e * 256) & hq;
+	subpix_ctm->e = *qe / 256.0f;
+	*qf = (int)(subpix_ctm->f * 256) & vq;
+	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, fz_matrix ctm, fz_colorspace *model, const fz_stroke_state *stroke, const fz_irect *scissor, int aa)
+{
+	if (fz_font_ft_face(ctx, 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, aa);
+	}
+	return fz_render_glyph(ctx, font, gid, trm, model, scissor, 1, aa);
+}
+
+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, int aa)
+{
+	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(ctx, 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 = aa;
+
+	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, aa);
+		}
+		else if (fz_font_t3_procs(ctx, 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, aa);
+			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, int aa)
+{
+	fz_pixmap *val = NULL;
+	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(ctx, font);
+
+	if (size <= MAX_GLYPH_SIZE)
+	{
+		scissor = &fz_infinite_irect;
+	}
+	else
+	{
+		if (is_ft_font)
+			return NULL;
+	}
+
+	if (is_ft_font)
+	{
+		val = fz_render_ft_glyph_pixmap(ctx, font, gid, subpix_ctm, aa);
+	}
+	else if (fz_font_t3_procs(ctx, font))
+	{
+		val = fz_render_t3_glyph_pixmap(ctx, font, gid, subpix_ctm, NULL, scissor, aa);
+	}
+	else
+	{
+		fz_warn(ctx, "assert: uninitialized font structure");
+		val = NULL;
+	}
+
+	return val;
+}
+
+void
+fz_dump_glyph_cache_stats(fz_context *ctx, fz_output *out)
+{
+	fz_glyph_cache *cache = ctx->glyph_cache;
+	fz_write_printf(ctx, out, "Glyph Cache Size: %zu\n", cache->total);
+#ifndef NDEBUG
+	fz_write_printf(ctx, out, "Glyph Cache Evictions: %d (%zu bytes)\n", cache->num_evictions, cache->evicted);
+#endif
+}