comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2021 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 /*
24 * A very simple font cache and rasterizer that uses FreeType
25 * to draw fonts from a single OpenGL texture. The code uses
26 * a linear-probe hashtable, and writes new glyphs into
27 * the texture using glTexSubImage2D. When the texture fills
28 * up, or the hash table gets too crowded, the cache is emptied.
29 *
30 * This is designed to be used for horizontal text only,
31 * and draws unhinted text with subpixel accurate metrics
32 * and kerning. As such, you should always call the drawing
33 * function with an orthogonal transform that maps units
34 * to pixels accurately.
35 */
36
37 #include "gl-app.h"
38
39 #include <string.h>
40 #include <math.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43
44 #define PADDING 1 /* set to 0 to save some space but disallow arbitrary transforms */
45
46 #define MAXGLYPHS 4093 /* prime number for hash table goodness */
47 #define CACHESIZE 1024
48 #define XPRECISION 4
49 #define YPRECISION 1
50
51 struct key
52 {
53 fz_font *font;
54 float size;
55 short gid;
56 unsigned char subx;
57 unsigned char suby;
58 };
59
60 struct glyph
61 {
62 char lsb, top, w, h;
63 short s, t;
64 };
65
66 struct table
67 {
68 struct key key;
69 struct glyph glyph;
70 };
71
72 static struct table g_table[MAXGLYPHS];
73 static int g_table_load = 0;
74 static unsigned int g_cache_tex = 0;
75 static int g_cache_w = CACHESIZE;
76 static int g_cache_h = CACHESIZE;
77 static int g_cache_row_y = 0;
78 static int g_cache_row_x = 0;
79 static int g_cache_row_h = 0;
80
81 static fz_font *g_font = NULL;
82
83 static void clear_font_cache(void)
84 {
85 #if PADDING > 0
86 unsigned char *zero = malloc((size_t)g_cache_w * g_cache_h);
87 memset(zero, 0, (size_t)g_cache_w * g_cache_h);
88 glBindTexture(GL_TEXTURE_2D, g_cache_tex);
89 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_cache_w, g_cache_h, GL_ALPHA, GL_UNSIGNED_BYTE, zero);
90 free(zero);
91 #endif
92
93 memset(g_table, 0, sizeof(g_table));
94 g_table_load = 0;
95
96 g_cache_row_y = PADDING;
97 g_cache_row_x = PADDING;
98 g_cache_row_h = 0;
99 }
100
101 void ui_init_fonts(void)
102 {
103 const unsigned char *data;
104 int size;
105
106 glGenTextures(1, &g_cache_tex);
107 glBindTexture(GL_TEXTURE_2D, g_cache_tex);
108 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
109 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
110 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
111 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
112 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, g_cache_w, g_cache_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);
113
114 clear_font_cache();
115
116 data = fz_lookup_builtin_font(ctx, "Charis SIL", 0, 0, &size);
117 if (!data)
118 data = fz_lookup_builtin_font(ctx, "Times", 0, 0, &size);
119 g_font = fz_new_font_from_memory(ctx, NULL, data, size, 0, 0);
120 }
121
122 void ui_finish_fonts(void)
123 {
124 clear_font_cache();
125 fz_drop_font(ctx, g_font);
126 }
127
128 static unsigned int hashfunc(struct key *key)
129 {
130 unsigned char *buf = (unsigned char *)key;
131 unsigned int len = sizeof(struct key);
132 unsigned int h = 0;
133 while (len--)
134 h = *buf++ + (h << 6) + (h << 16) - h;
135 return h;
136 }
137
138 static unsigned int lookup_table(struct key *key)
139 {
140 unsigned int pos = hashfunc(key) % MAXGLYPHS;
141 while (1)
142 {
143 if (!g_table[pos].key.font) /* empty slot */
144 return pos;
145 if (!memcmp(key, &g_table[pos].key, sizeof(struct key))) /* matching slot */
146 return pos;
147 pos = (pos + 1) % MAXGLYPHS;
148 }
149 }
150
151 static struct glyph *lookup_glyph(fz_font *font, float size, int gid, float *xp, float *yp)
152 {
153 fz_matrix trm, subpix_trm;
154 unsigned char subx, suby;
155 fz_pixmap *pixmap;
156 struct key key;
157 unsigned int pos;
158 int w, h;
159
160 /* match fitz's glyph cache quantization */
161 trm = fz_scale(size, -size);
162 trm.e = *xp;
163 trm.f = *yp;
164 fz_subpixel_adjust(ctx, &trm, &subpix_trm, &subx, &suby);
165 *xp = trm.e;
166 *yp = trm.f;
167
168 /*
169 * Look it up in the table
170 */
171
172 memset(&key, 0, sizeof key);
173 key.font = font;
174 key.size = size;
175 key.gid = gid;
176 key.subx = subx;
177 key.suby = suby;
178
179 pos = lookup_table(&key);
180 if (g_table[pos].key.font)
181 return &g_table[pos].glyph;
182
183 /*
184 * Render the bitmap
185 */
186
187 glEnd();
188
189 pixmap = fz_render_glyph_pixmap(ctx, font, gid, &subpix_trm, NULL, 8);
190 w = pixmap->w;
191 h = pixmap->h;
192
193 /*
194 * Find an empty slot in the texture
195 */
196
197 if (g_table_load == (MAXGLYPHS * 3) / 4)
198 {
199 puts("font cache table full, clearing cache");
200 clear_font_cache();
201 pos = lookup_table(&key);
202 }
203
204 if (h + PADDING > g_cache_h || w + PADDING > g_cache_w)
205 return NULL;
206
207 if (g_cache_row_x + w + PADDING > g_cache_w)
208 {
209 g_cache_row_y += g_cache_row_h + PADDING;
210 g_cache_row_x = PADDING;
211 g_cache_row_h = 0;
212 }
213 if (g_cache_row_y + h + PADDING > g_cache_h)
214 {
215 puts("font cache texture full, clearing cache");
216 clear_font_cache();
217 pos = lookup_table(&key);
218 }
219
220 /*
221 * Copy bitmap into texture
222 */
223
224 memcpy(&g_table[pos].key, &key, sizeof(struct key));
225 g_table[pos].glyph.w = pixmap->w;
226 g_table[pos].glyph.h = pixmap->h;
227 g_table[pos].glyph.lsb = pixmap->x;
228 g_table[pos].glyph.top = -pixmap->y;
229 g_table[pos].glyph.s = g_cache_row_x;
230 g_table[pos].glyph.t = g_cache_row_y;
231 g_table_load ++;
232
233 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
234 glPixelStorei(GL_UNPACK_ROW_LENGTH, pixmap->w);
235 glTexSubImage2D(GL_TEXTURE_2D, 0, g_cache_row_x, g_cache_row_y, w, h,
236 GL_ALPHA, GL_UNSIGNED_BYTE, pixmap->samples);
237 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
238
239 fz_drop_pixmap(ctx, pixmap);
240
241 glBegin(GL_QUADS);
242
243 g_cache_row_x += w + PADDING;
244 if (g_cache_row_h < h + PADDING)
245 g_cache_row_h = h + PADDING;
246
247 return &g_table[pos].glyph;
248 }
249
250 static float ui_draw_glyph(fz_font *font, float size, int gid, float x, float y)
251 {
252 struct glyph *glyph;
253 float s0, t0, s1, t1, xc, yc;
254
255 glyph = lookup_glyph(font, size, gid, &x, &y);
256 if (!glyph)
257 return 0;
258
259 s0 = (float) glyph->s / g_cache_w;
260 t0 = (float) glyph->t / g_cache_h;
261 s1 = (float) (glyph->s + glyph->w) / g_cache_w;
262 t1 = (float) (glyph->t + glyph->h) / g_cache_h;
263 xc = floorf(x) + glyph->lsb;
264 yc = floorf(y) - glyph->top + glyph->h;
265
266 glTexCoord2f(s0, t0); glVertex2f(xc, yc - glyph->h);
267 glTexCoord2f(s1, t0); glVertex2f(xc + glyph->w, yc - glyph->h);
268 glTexCoord2f(s1, t1); glVertex2f(xc + glyph->w, yc);
269 glTexCoord2f(s0, t1); glVertex2f(xc, yc);
270
271 return fz_advance_glyph(ctx, font, gid, 0) * size;
272 }
273
274 float ui_measure_character(int c)
275 {
276 fz_font *font;
277 int gid = fz_encode_character_with_fallback(ctx, g_font, c, 0, 0, &font);
278 return fz_advance_glyph(ctx, font, gid, 0) * ui.fontsize;
279 }
280
281 static float ui_draw_character_imp(float x, float y, int c)
282 {
283 fz_font *font;
284 int gid = fz_encode_character_with_fallback(ctx, g_font, c, 0, 0, &font);
285 return ui_draw_glyph(font, ui.fontsize, gid, x, y);
286 }
287
288 static void ui_begin_text(void)
289 {
290 glBindTexture(GL_TEXTURE_2D, g_cache_tex);
291 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
292 glEnable(GL_BLEND);
293 glEnable(GL_TEXTURE_2D);
294 glBegin(GL_QUADS);
295 }
296
297 static void ui_end_text(void)
298 {
299 glEnd();
300 glDisable(GL_TEXTURE_2D);
301 glDisable(GL_BLEND);
302 }
303
304 void ui_draw_string(float x, float y, const char *str)
305 {
306 int c;
307 ui_begin_text();
308 while (*str)
309 {
310 str += fz_chartorune(&c, str);
311 x += ui_draw_character_imp(x, y + ui.baseline, c);
312 }
313 ui_end_text();
314 }
315
316 void ui_draw_string_part(float x, float y, const char *s, const char *e)
317 {
318 int c;
319 ui_begin_text();
320 while (s < e)
321 {
322 s += fz_chartorune(&c, s);
323 x += ui_draw_character_imp(x, y + ui.baseline, c);
324 }
325 ui_end_text();
326 }
327
328 void ui_draw_character(float x, float y, int c)
329 {
330 ui_begin_text();
331 ui_draw_character_imp(x, y + ui.baseline, c);
332 ui_end_text();
333 }
334
335 float ui_measure_string(const char *str)
336 {
337 int c;
338 float x = 0;
339 while (*str)
340 {
341 str += fz_chartorune(&c, str);
342 x += ui_measure_character(c);
343 }
344 return x;
345 }
346
347 float ui_measure_string_part(const char *s, const char *e)
348 {
349 int c;
350 float w = 0;
351 while (s < e)
352 {
353 s += fz_chartorune(&c, s);
354 w += ui_measure_character(c);
355 }
356 return w;
357 }
358
359 int ui_break_lines(char *a, struct line *lines, int maxlines, int width, int *maxwidth)
360 {
361 char *next, *space = NULL, *b = a;
362 int c, n = 0;
363 float space_x, x = 0, w = 0;
364
365 if (maxwidth)
366 *maxwidth = 0;
367
368 while (*b)
369 {
370 next = b + fz_chartorune(&c, b);
371 if (c == '\r' || c == '\n')
372 {
373 if (lines && n < maxlines)
374 {
375 lines[n].a = a;
376 lines[n].b = b;
377 }
378 ++n;
379 if (maxwidth && *maxwidth < x)
380 *maxwidth = x;
381 a = next;
382 x = 0;
383 space = NULL;
384 }
385 else
386 {
387 if (c == ' ' && maxlines > 1)
388 {
389 space = b;
390 space_x = x;
391 }
392
393 w = ui_measure_character(c);
394 if (x + w > width)
395 {
396 if (space)
397 {
398 if (lines && n < maxlines)
399 {
400 lines[n].a = a;
401 lines[n].b = space;
402 }
403 ++n;
404 if (maxwidth && *maxwidth < space_x)
405 *maxwidth = space_x;
406 a = next = space + 1;
407 x = 0;
408 space = NULL;
409 }
410 else
411 {
412 if (lines && n < maxlines)
413 {
414 lines[n].a = a;
415 lines[n].b = b;
416 }
417 ++n;
418 if (maxwidth && *maxwidth < x)
419 *maxwidth = x;
420 a = b;
421 x = w;
422 space = NULL;
423 }
424 }
425 else
426 {
427 x += w;
428 }
429 }
430 b = next;
431 }
432
433 if (lines && n < maxlines)
434 {
435 lines[n].a = a;
436 lines[n].b = b;
437 }
438 ++n;
439 if (maxwidth && *maxwidth < x)
440 *maxwidth = x;
441 return n < maxlines ? n : maxlines;
442 }
443
444 void ui_draw_lines(float x, float y, struct line *lines, int n)
445 {
446 int i;
447 for (i = 0; i < n; ++i)
448 {
449 ui_draw_string_part(x, y, lines[i].a, lines[i].b);
450 y += ui.lineheight;
451 }
452 }