diff mupdf-source/platform/gl/gl-font.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/platform/gl/gl-font.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,452 @@
+// 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.
+
+/*
+ * A very simple font cache and rasterizer that uses FreeType
+ * to draw fonts from a single OpenGL texture. The code uses
+ * a linear-probe hashtable, and writes new glyphs into
+ * the texture using glTexSubImage2D. When the texture fills
+ * up, or the hash table gets too crowded, the cache is emptied.
+ *
+ * This is designed to be used for horizontal text only,
+ * and draws unhinted text with subpixel accurate metrics
+ * and kerning. As such, you should always call the drawing
+ * function with an orthogonal transform that maps units
+ * to pixels accurately.
+ */
+
+#include "gl-app.h"
+
+#include <string.h>
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define PADDING 1		/* set to 0 to save some space but disallow arbitrary transforms */
+
+#define MAXGLYPHS 4093	/* prime number for hash table goodness */
+#define CACHESIZE 1024
+#define XPRECISION 4
+#define YPRECISION 1
+
+struct key
+{
+	fz_font *font;
+	float size;
+	short gid;
+	unsigned char subx;
+	unsigned char suby;
+};
+
+struct glyph
+{
+	char lsb, top, w, h;
+	short s, t;
+};
+
+struct table
+{
+	struct key key;
+	struct glyph glyph;
+};
+
+static struct table g_table[MAXGLYPHS];
+static int g_table_load = 0;
+static unsigned int g_cache_tex = 0;
+static int g_cache_w = CACHESIZE;
+static int g_cache_h = CACHESIZE;
+static int g_cache_row_y = 0;
+static int g_cache_row_x = 0;
+static int g_cache_row_h = 0;
+
+static fz_font *g_font = NULL;
+
+static void clear_font_cache(void)
+{
+#if PADDING > 0
+	unsigned char *zero = malloc((size_t)g_cache_w * g_cache_h);
+	memset(zero, 0, (size_t)g_cache_w * g_cache_h);
+	glBindTexture(GL_TEXTURE_2D, g_cache_tex);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_cache_w, g_cache_h, GL_ALPHA, GL_UNSIGNED_BYTE, zero);
+	free(zero);
+#endif
+
+	memset(g_table, 0, sizeof(g_table));
+	g_table_load = 0;
+
+	g_cache_row_y = PADDING;
+	g_cache_row_x = PADDING;
+	g_cache_row_h = 0;
+}
+
+void ui_init_fonts(void)
+{
+	const unsigned char *data;
+	int size;
+
+	glGenTextures(1, &g_cache_tex);
+	glBindTexture(GL_TEXTURE_2D, g_cache_tex);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, g_cache_w, g_cache_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);
+
+	clear_font_cache();
+
+	data = fz_lookup_builtin_font(ctx, "Charis SIL", 0, 0, &size);
+	if (!data)
+		data = fz_lookup_builtin_font(ctx, "Times", 0, 0, &size);
+	g_font = fz_new_font_from_memory(ctx, NULL, data, size, 0, 0);
+}
+
+void ui_finish_fonts(void)
+{
+	clear_font_cache();
+	fz_drop_font(ctx, g_font);
+}
+
+static unsigned int hashfunc(struct key *key)
+{
+	unsigned char *buf = (unsigned char *)key;
+	unsigned int len = sizeof(struct key);
+	unsigned int h = 0;
+	while (len--)
+		h = *buf++ + (h << 6) + (h << 16) - h;
+	return h;
+}
+
+static unsigned int lookup_table(struct key *key)
+{
+	unsigned int pos = hashfunc(key) % MAXGLYPHS;
+	while (1)
+	{
+		if (!g_table[pos].key.font) /* empty slot */
+			return pos;
+		if (!memcmp(key, &g_table[pos].key, sizeof(struct key))) /* matching slot */
+			return pos;
+		pos = (pos + 1) % MAXGLYPHS;
+	}
+}
+
+static struct glyph *lookup_glyph(fz_font *font, float size, int gid, float *xp, float *yp)
+{
+	fz_matrix trm, subpix_trm;
+	unsigned char subx, suby;
+	fz_pixmap *pixmap;
+	struct key key;
+	unsigned int pos;
+	int w, h;
+
+	/* match fitz's glyph cache quantization */
+	trm = fz_scale(size, -size);
+	trm.e = *xp;
+	trm.f = *yp;
+	fz_subpixel_adjust(ctx, &trm, &subpix_trm, &subx, &suby);
+	*xp = trm.e;
+	*yp = trm.f;
+
+	/*
+	 * Look it up in the table
+	 */
+
+	memset(&key, 0, sizeof key);
+	key.font = font;
+	key.size = size;
+	key.gid = gid;
+	key.subx = subx;
+	key.suby = suby;
+
+	pos = lookup_table(&key);
+	if (g_table[pos].key.font)
+		return &g_table[pos].glyph;
+
+	/*
+	 * Render the bitmap
+	 */
+
+	glEnd();
+
+	pixmap = fz_render_glyph_pixmap(ctx, font, gid, &subpix_trm, NULL, 8);
+	w = pixmap->w;
+	h = pixmap->h;
+
+	/*
+	 * Find an empty slot in the texture
+	 */
+
+	if (g_table_load == (MAXGLYPHS * 3) / 4)
+	{
+		puts("font cache table full, clearing cache");
+		clear_font_cache();
+		pos = lookup_table(&key);
+	}
+
+	if (h + PADDING > g_cache_h || w + PADDING > g_cache_w)
+		return NULL;
+
+	if (g_cache_row_x + w + PADDING > g_cache_w)
+	{
+		g_cache_row_y += g_cache_row_h + PADDING;
+		g_cache_row_x = PADDING;
+		g_cache_row_h = 0;
+	}
+	if (g_cache_row_y + h + PADDING > g_cache_h)
+	{
+		puts("font cache texture full, clearing cache");
+		clear_font_cache();
+		pos = lookup_table(&key);
+	}
+
+	/*
+	 * Copy bitmap into texture
+	 */
+
+	memcpy(&g_table[pos].key, &key, sizeof(struct key));
+	g_table[pos].glyph.w = pixmap->w;
+	g_table[pos].glyph.h = pixmap->h;
+	g_table[pos].glyph.lsb = pixmap->x;
+	g_table[pos].glyph.top = -pixmap->y;
+	g_table[pos].glyph.s = g_cache_row_x;
+	g_table[pos].glyph.t = g_cache_row_y;
+	g_table_load ++;
+
+	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+	glPixelStorei(GL_UNPACK_ROW_LENGTH, pixmap->w);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, g_cache_row_x, g_cache_row_y, w, h,
+			GL_ALPHA, GL_UNSIGNED_BYTE, pixmap->samples);
+	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+	fz_drop_pixmap(ctx, pixmap);
+
+	glBegin(GL_QUADS);
+
+	g_cache_row_x += w + PADDING;
+	if (g_cache_row_h < h + PADDING)
+		g_cache_row_h = h + PADDING;
+
+	return &g_table[pos].glyph;
+}
+
+static float ui_draw_glyph(fz_font *font, float size, int gid, float x, float y)
+{
+	struct glyph *glyph;
+	float s0, t0, s1, t1, xc, yc;
+
+	glyph = lookup_glyph(font, size, gid, &x, &y);
+	if (!glyph)
+		return 0;
+
+	s0 = (float) glyph->s / g_cache_w;
+	t0 = (float) glyph->t / g_cache_h;
+	s1 = (float) (glyph->s + glyph->w) / g_cache_w;
+	t1 = (float) (glyph->t + glyph->h) / g_cache_h;
+	xc = floorf(x) + glyph->lsb;
+	yc = floorf(y) - glyph->top + glyph->h;
+
+	glTexCoord2f(s0, t0); glVertex2f(xc, yc - glyph->h);
+	glTexCoord2f(s1, t0); glVertex2f(xc + glyph->w, yc - glyph->h);
+	glTexCoord2f(s1, t1); glVertex2f(xc + glyph->w, yc);
+	glTexCoord2f(s0, t1); glVertex2f(xc, yc);
+
+	return fz_advance_glyph(ctx, font, gid, 0) * size;
+}
+
+float ui_measure_character(int c)
+{
+	fz_font *font;
+	int gid = fz_encode_character_with_fallback(ctx, g_font, c, 0, 0, &font);
+	return fz_advance_glyph(ctx, font, gid, 0) * ui.fontsize;
+}
+
+static float ui_draw_character_imp(float x, float y, int c)
+{
+	fz_font *font;
+	int gid = fz_encode_character_with_fallback(ctx, g_font, c, 0, 0, &font);
+	return ui_draw_glyph(font, ui.fontsize, gid, x, y);
+}
+
+static void ui_begin_text(void)
+{
+	glBindTexture(GL_TEXTURE_2D, g_cache_tex);
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	glEnable(GL_BLEND);
+	glEnable(GL_TEXTURE_2D);
+	glBegin(GL_QUADS);
+}
+
+static void ui_end_text(void)
+{
+	glEnd();
+	glDisable(GL_TEXTURE_2D);
+	glDisable(GL_BLEND);
+}
+
+void ui_draw_string(float x, float y, const char *str)
+{
+	int c;
+	ui_begin_text();
+	while (*str)
+	{
+		str += fz_chartorune(&c, str);
+		x += ui_draw_character_imp(x, y + ui.baseline, c);
+	}
+	ui_end_text();
+}
+
+void ui_draw_string_part(float x, float y, const char *s, const char *e)
+{
+	int c;
+	ui_begin_text();
+	while (s < e)
+	{
+		s += fz_chartorune(&c, s);
+		x += ui_draw_character_imp(x, y + ui.baseline, c);
+	}
+	ui_end_text();
+}
+
+void ui_draw_character(float x, float y, int c)
+{
+	ui_begin_text();
+	ui_draw_character_imp(x, y + ui.baseline, c);
+	ui_end_text();
+}
+
+float ui_measure_string(const char *str)
+{
+	int c;
+	float x = 0;
+	while (*str)
+	{
+		str += fz_chartorune(&c, str);
+		x += ui_measure_character(c);
+	}
+	return x;
+}
+
+float ui_measure_string_part(const char *s, const char *e)
+{
+	int c;
+	float w = 0;
+	while (s < e)
+	{
+		s += fz_chartorune(&c, s);
+		w += ui_measure_character(c);
+	}
+	return w;
+}
+
+int ui_break_lines(char *a, struct line *lines, int maxlines, int width, int *maxwidth)
+{
+	char *next, *space = NULL, *b = a;
+	int c, n = 0;
+	float space_x, x = 0, w = 0;
+
+	if (maxwidth)
+		*maxwidth = 0;
+
+	while (*b)
+	{
+		next = b + fz_chartorune(&c, b);
+		if (c == '\r' || c == '\n')
+		{
+			if (lines && n < maxlines)
+			{
+				lines[n].a = a;
+				lines[n].b = b;
+			}
+			++n;
+			if (maxwidth && *maxwidth < x)
+				*maxwidth = x;
+			a = next;
+			x = 0;
+			space = NULL;
+		}
+		else
+		{
+			if (c == ' ' && maxlines > 1)
+			{
+				space = b;
+				space_x = x;
+			}
+
+			w = ui_measure_character(c);
+			if (x + w > width)
+			{
+				if (space)
+				{
+					if (lines && n < maxlines)
+					{
+						lines[n].a = a;
+						lines[n].b = space;
+					}
+					++n;
+					if (maxwidth && *maxwidth < space_x)
+						*maxwidth = space_x;
+					a = next = space + 1;
+					x = 0;
+					space = NULL;
+				}
+				else
+				{
+					if (lines && n < maxlines)
+					{
+						lines[n].a = a;
+						lines[n].b = b;
+					}
+					++n;
+					if (maxwidth && *maxwidth < x)
+						*maxwidth = x;
+					a = b;
+					x = w;
+					space = NULL;
+				}
+			}
+			else
+			{
+				x += w;
+			}
+		}
+		b = next;
+	}
+
+	if (lines && n < maxlines)
+	{
+		lines[n].a = a;
+		lines[n].b = b;
+	}
+	++n;
+	if (maxwidth && *maxwidth < x)
+		*maxwidth = x;
+	return n < maxlines ? n : maxlines;
+}
+
+void ui_draw_lines(float x, float y, struct line *lines, int n)
+{
+	int i;
+	for (i = 0; i < n; ++i)
+	{
+		ui_draw_string_part(x, y, lines[i].a, lines[i].b);
+		y += ui.lineheight;
+	}
+}