Coverage Report

Created: 2026-03-31 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/source/fitz/draw-glyph.c
Line
Count
Source
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
#include "mupdf/fitz.h"
24
#include "draw-imp.h"
25
#include "glyph-imp.h"
26
#include "pixmap-imp.h"
27
28
#include <string.h>
29
#include <math.h>
30
31
16
#define MAX_GLYPH_SIZE 256
32
4
#define MAX_CACHE_SIZE (1024*1024)
33
34
11.2k
#define GLYPH_HASH_LEN 509
35
36
typedef struct
37
{
38
  fz_font *font;
39
  int a, b;
40
  int c, d;
41
  unsigned short gid;
42
  unsigned char e, f;
43
  int aa;
44
} fz_glyph_key;
45
46
typedef struct fz_glyph_cache_entry
47
{
48
  fz_glyph_key key;
49
  unsigned hash;
50
  struct fz_glyph_cache_entry *lru_prev;
51
  struct fz_glyph_cache_entry *lru_next;
52
  struct fz_glyph_cache_entry *bucket_next;
53
  struct fz_glyph_cache_entry *bucket_prev;
54
  fz_glyph *val;
55
} fz_glyph_cache_entry;
56
57
struct fz_glyph_cache
58
{
59
  int refs;
60
  size_t total;
61
#ifndef NDEBUG
62
  int num_evictions;
63
  ptrdiff_t evicted;
64
#endif
65
  fz_glyph_cache_entry *entry[GLYPH_HASH_LEN];
66
  fz_glyph_cache_entry *lru_head;
67
  fz_glyph_cache_entry *lru_tail;
68
};
69
70
static size_t
71
fz_glyph_size(fz_context *ctx, fz_glyph *glyph)
72
8
{
73
8
  if (glyph == NULL)
74
0
    return 0;
75
8
  return sizeof(fz_glyph) + glyph->size + fz_pixmap_size(ctx, glyph->pixmap);
76
8
}
77
78
void
79
fz_new_glyph_cache_context(fz_context *ctx)
80
12
{
81
12
  fz_glyph_cache *cache;
82
83
12
  cache = fz_malloc_struct(ctx, fz_glyph_cache);
84
12
  cache->total = 0;
85
12
  cache->refs = 1;
86
87
12
  ctx->glyph_cache = cache;
88
12
}
89
90
static void
91
drop_glyph_cache_entry(fz_context *ctx, fz_glyph_cache_entry *entry)
92
4
{
93
4
  fz_glyph_cache *cache = ctx->glyph_cache;
94
95
4
  if (entry->lru_next)
96
2
    entry->lru_next->lru_prev = entry->lru_prev;
97
2
  else
98
2
    cache->lru_tail = entry->lru_prev;
99
4
  if (entry->lru_prev)
100
2
    entry->lru_prev->lru_next = entry->lru_next;
101
2
  else
102
2
    cache->lru_head = entry->lru_next;
103
4
  cache->total -= fz_glyph_size(ctx, entry->val);
104
4
  if (entry->bucket_next)
105
0
    entry->bucket_next->bucket_prev = entry->bucket_prev;
106
4
  if (entry->bucket_prev)
107
0
    entry->bucket_prev->bucket_next = entry->bucket_next;
108
4
  else
109
4
    cache->entry[entry->hash] = entry->bucket_next;
110
4
  fz_drop_font(ctx, entry->key.font);
111
4
  fz_drop_glyph(ctx, entry->val);
112
4
  fz_free(ctx, entry);
113
4
}
114
115
/* The glyph cache lock is always held when this function is called. */
116
static void
117
do_purge(fz_context *ctx)
118
22
{
119
22
  fz_glyph_cache *cache = ctx->glyph_cache;
120
22
  int i;
121
122
11.2k
  for (i = 0; i < GLYPH_HASH_LEN; i++)
123
11.1k
  {
124
11.2k
    while (cache->entry[i])
125
4
      drop_glyph_cache_entry(ctx, cache->entry[i]);
126
11.1k
  }
127
128
22
  cache->total = 0;
129
22
}
130
131
void
132
fz_purge_glyph_cache(fz_context *ctx)
133
10
{
134
10
  fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
135
10
  do_purge(ctx);
136
10
  fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
137
10
}
138
139
void
140
fz_drop_glyph_cache_context(fz_context *ctx)
141
12
{
142
12
  if (!ctx || !ctx->glyph_cache)
143
0
    return;
144
145
12
  fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
146
12
  ctx->glyph_cache->refs--;
147
12
  if (ctx->glyph_cache->refs == 0)
148
12
  {
149
12
    do_purge(ctx);
150
12
    fz_free(ctx, ctx->glyph_cache);
151
12
    ctx->glyph_cache = NULL;
152
12
  }
153
12
  fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
154
12
}
155
156
fz_glyph_cache *
157
fz_keep_glyph_cache(fz_context *ctx)
158
0
{
159
0
  fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
160
0
  ctx->glyph_cache->refs++;
161
0
  fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
162
0
  return ctx->glyph_cache;
163
0
}
164
165
float
166
fz_subpixel_adjust(fz_context *ctx, fz_matrix *ctm, fz_matrix *subpix_ctm, unsigned char *qe, unsigned char *qf)
167
4
{
168
4
  float size = fz_matrix_expansion(*ctm);
169
4
  int q, hq, vq, qmin;
170
4
  float pix_e, pix_f, r, hr, vr, rmin;
171
172
  /* Quantise the subpixel positions. First, in the direction of
173
   * movement (i.e. normally X). We never need more than 4 subpixel
174
   * positions for glyphs - arguably even that is too much.
175
   * Suppress this as we get larger, because it makes less impact. */
176
4
  if (size >= 48)
177
0
    q = 0, r = 0.5f;
178
4
  else if (size >= 24)
179
0
    q = 128, r = 0.25f;
180
4
  else
181
4
    q = 192, r = 0.125f;
182
183
  /* Then in the 'downward' direction (normally Y). */
184
4
  if (size >= 8)
185
4
    qmin = 0, rmin = 0.5f;
186
0
  else if (size >= 4)
187
0
    qmin = 128, rmin =  0.25f;
188
0
  else
189
0
    qmin = 192, rmin = 0.125f;
190
191
  /* Suppress subpixel antialiasing in y axis if we have a horizontal
192
   * matrix, and in x axis if we have a vertical matrix, unless we're
193
   * really small. */
194
4
  hq = vq = q;
195
4
  hr = vr = r;
196
4
  if (ctm->a == 0 && ctm->d == 0)
197
0
    hq = qmin, hr = rmin;
198
4
  if (ctm->b == 0 && ctm->c == 0)
199
4
    vq = qmin, vr = rmin;
200
201
  /* Split translation into pixel and subpixel parts */
202
4
  subpix_ctm->a = ctm->a;
203
4
  subpix_ctm->b = ctm->b;
204
4
  subpix_ctm->c = ctm->c;
205
4
  subpix_ctm->d = ctm->d;
206
4
  subpix_ctm->e = ctm->e + hr;
207
4
  pix_e = floorf(subpix_ctm->e);
208
4
  subpix_ctm->e -= pix_e;
209
4
  subpix_ctm->f = ctm->f + vr;
210
4
  pix_f = floorf(subpix_ctm->f);
211
4
  subpix_ctm->f -= pix_f;
212
213
  /* Quantise the subpixel part */
214
4
  *qe = (int)(subpix_ctm->e * 256) & hq;
215
4
  subpix_ctm->e = *qe / 256.0f;
216
4
  *qf = (int)(subpix_ctm->f * 256) & vq;
217
4
  subpix_ctm->f = *qf / 256.0f;
218
219
  /* Reassemble the complete translation */
220
4
  ctm->e = subpix_ctm->e + pix_e;
221
4
  ctm->f = subpix_ctm->f + pix_f;
222
223
4
  return size;
224
4
}
225
226
fz_glyph *
227
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)
228
0
{
229
0
  if (fz_font_ft_face(ctx, font))
230
0
  {
231
0
    fz_matrix subpix_trm;
232
0
    unsigned char qe, qf;
233
234
0
    if (stroke->dash_len > 0)
235
0
      return NULL;
236
0
    (void)fz_subpixel_adjust(ctx, trm, &subpix_trm, &qe, &qf);
237
0
    return fz_render_ft_stroked_glyph(ctx, font, gid, subpix_trm, ctm, stroke, aa);
238
0
  }
239
0
  return fz_render_glyph(ctx, font, gid, trm, model, scissor, 1, aa);
240
0
}
241
242
static unsigned do_hash(unsigned char *s, int len)
243
4
{
244
4
  unsigned val = 0;
245
4
  int i;
246
132
  for (i = 0; i < len; i++)
247
128
  {
248
128
    val += s[i];
249
128
    val += (val << 10);
250
128
    val ^= (val >> 6);
251
128
  }
252
4
  val += (val << 3);
253
4
  val ^= (val >> 11);
254
4
  val += (val << 15);
255
4
  return val;
256
4
}
257
258
static inline void
259
move_to_front(fz_glyph_cache *cache, fz_glyph_cache_entry *entry)
260
0
{
261
0
  if (entry->lru_prev == NULL)
262
0
    return; /* At front already */
263
264
  /* Unlink */
265
0
  entry->lru_prev->lru_next = entry->lru_next;
266
0
  if (entry->lru_next)
267
0
    entry->lru_next->lru_prev = entry->lru_prev;
268
0
  else
269
0
    cache->lru_tail = entry->lru_prev;
270
  /* Relink */
271
0
  entry->lru_next = cache->lru_head;
272
0
  if (entry->lru_next)
273
0
    entry->lru_next->lru_prev = entry;
274
0
  cache->lru_head = entry;
275
0
  entry->lru_prev = NULL;
276
0
}
277
278
fz_glyph *
279
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)
280
4
{
281
4
  fz_glyph_cache *cache;
282
4
  fz_glyph_key key;
283
4
  fz_matrix subpix_ctm;
284
4
  fz_irect subpix_scissor;
285
4
  float size;
286
4
  fz_glyph *val;
287
4
  int do_cache, locked, caching;
288
4
  fz_glyph_cache_entry *entry;
289
4
  unsigned hash;
290
4
  int is_ft_font = !!fz_font_ft_face(ctx, font);
291
292
4
  fz_var(locked);
293
4
  fz_var(caching);
294
4
  fz_var(val);
295
296
4
  memset(&key, 0, sizeof key);
297
4
  size = fz_subpixel_adjust(ctx, ctm, &subpix_ctm, &key.e, &key.f);
298
4
  if (size <= MAX_GLYPH_SIZE)
299
4
  {
300
4
    scissor = &fz_infinite_irect;
301
4
    do_cache = 1;
302
4
  }
303
0
  else
304
0
  {
305
0
    if (is_ft_font)
306
0
      return NULL;
307
0
    subpix_scissor.x0 = scissor->x0 - floorf(ctm->e);
308
0
    subpix_scissor.y0 = scissor->y0 - floorf(ctm->f);
309
0
    subpix_scissor.x1 = scissor->x1 - floorf(ctm->e);
310
0
    subpix_scissor.y1 = scissor->y1 - floorf(ctm->f);
311
0
    scissor = &subpix_scissor;
312
0
    do_cache = 0;
313
0
  }
314
315
4
  cache = ctx->glyph_cache;
316
317
4
  key.font = font;
318
4
  key.gid = gid;
319
4
  key.a = subpix_ctm.a * 65536;
320
4
  key.b = subpix_ctm.b * 65536;
321
4
  key.c = subpix_ctm.c * 65536;
322
4
  key.d = subpix_ctm.d * 65536;
323
4
  key.aa = aa;
324
325
4
  hash = do_hash((unsigned char *)&key, sizeof(key)) % GLYPH_HASH_LEN;
326
4
  fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
327
4
  entry = cache->entry[hash];
328
4
  while (entry)
329
0
  {
330
0
    if (memcmp(&entry->key, &key, sizeof(key)) == 0)
331
0
    {
332
0
      move_to_front(cache, entry);
333
0
      val = fz_keep_glyph(ctx, entry->val);
334
0
      fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
335
0
      return val;
336
0
    }
337
0
    entry = entry->bucket_next;
338
0
  }
339
340
4
  locked = 1;
341
4
  caching = 0;
342
4
  val = NULL;
343
344
8
  fz_try(ctx)
345
8
  {
346
4
    if (is_ft_font)
347
4
    {
348
4
      val = fz_render_ft_glyph(ctx, font, gid, subpix_ctm, aa);
349
4
    }
350
0
    else if (fz_font_t3_procs(ctx, font))
351
0
    {
352
      /* We drop the glyphcache here, and execute the t3
353
       * glyph code. The danger here is that some other
354
       * thread will come along, and want the same glyph
355
       * too. If it does, we may both end up rendering
356
       * pixmaps. We cope with this later on, by ensuring
357
       * that only one gets inserted into the cache. If
358
       * we insert ours to find one already there, we
359
       * abandon ours, and use the one there already.
360
       */
361
0
      fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
362
0
      locked = 0;
363
0
      val = fz_render_t3_glyph(ctx, font, gid, subpix_ctm, model, scissor, aa);
364
0
      fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
365
0
      locked = 1;
366
0
    }
367
0
    else
368
0
    {
369
0
      fz_warn(ctx, "assert: uninitialized font structure");
370
0
    }
371
4
    if (val && do_cache)
372
4
    {
373
4
      if (val->w < MAX_GLYPH_SIZE && val->h < MAX_GLYPH_SIZE)
374
4
      {
375
        /* If we throw an exception whilst caching,
376
         * just ignore the exception and carry on. */
377
4
        caching = 1;
378
4
        if (!is_ft_font)
379
0
        {
380
          /* We had to unlock. Someone else might
381
           * have rendered in the meantime */
382
0
          entry = cache->entry[hash];
383
0
          while (entry)
384
0
          {
385
0
            if (memcmp(&entry->key, &key, sizeof(key)) == 0)
386
0
            {
387
0
              fz_drop_glyph(ctx, val);
388
0
              move_to_front(cache, entry);
389
0
              val = fz_keep_glyph(ctx, entry->val);
390
0
              goto unlock_and_return_val;
391
0
            }
392
0
            entry = entry->bucket_next;
393
0
          }
394
0
        }
395
396
4
        entry = fz_malloc_struct(ctx, fz_glyph_cache_entry);
397
4
        entry->key = key;
398
4
        entry->hash = hash;
399
4
        entry->bucket_next = cache->entry[hash];
400
4
        if (entry->bucket_next)
401
0
          entry->bucket_next->bucket_prev = entry;
402
4
        cache->entry[hash] = entry;
403
4
        entry->val = fz_keep_glyph(ctx, val);
404
4
        fz_keep_font(ctx, key.font);
405
406
4
        entry->lru_next = cache->lru_head;
407
4
        if (entry->lru_next)
408
3
          entry->lru_next->lru_prev = entry;
409
1
        else
410
1
          cache->lru_tail = entry;
411
4
        cache->lru_head = entry;
412
413
4
        cache->total += fz_glyph_size(ctx, val);
414
4
        while (cache->total > MAX_CACHE_SIZE)
415
0
        {
416
0
#ifndef NDEBUG
417
0
          cache->num_evictions++;
418
0
          cache->evicted += fz_glyph_size(ctx, cache->lru_tail->val);
419
0
#endif
420
0
          drop_glyph_cache_entry(ctx, cache->lru_tail);
421
0
        }
422
4
      }
423
4
    }
424
4
unlock_and_return_val:
425
4
    {
426
4
    }
427
4
  }
428
8
  fz_always(ctx)
429
4
  {
430
4
    if (locked)
431
4
      fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
432
4
  }
433
4
  fz_catch(ctx)
434
0
  {
435
0
    if (caching)
436
0
    {
437
0
      fz_report_error(ctx);
438
0
      fz_warn(ctx, "cannot encache glyph; continuing");
439
0
    }
440
0
    else
441
0
    {
442
0
      fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
443
0
      fz_report_error(ctx);
444
0
    }
445
0
  }
446
447
4
  return val;
448
4
}
449
450
fz_pixmap *
451
fz_render_glyph_pixmap(fz_context *ctx, fz_font *font, int gid, fz_matrix *ctm, const fz_irect *scissor, int aa)
452
0
{
453
0
  fz_pixmap *val = NULL;
454
0
  unsigned char qe, qf;
455
0
  fz_matrix subpix_ctm;
456
0
  float size = fz_subpixel_adjust(ctx, ctm, &subpix_ctm, &qe, &qf);
457
0
  int is_ft_font = !!fz_font_ft_face(ctx, font);
458
459
0
  if (size <= MAX_GLYPH_SIZE)
460
0
  {
461
0
    scissor = &fz_infinite_irect;
462
0
  }
463
0
  else
464
0
  {
465
0
    if (is_ft_font)
466
0
      return NULL;
467
0
  }
468
469
0
  if (is_ft_font)
470
0
  {
471
0
    val = fz_render_ft_glyph_pixmap(ctx, font, gid, subpix_ctm, aa);
472
0
  }
473
0
  else if (fz_font_t3_procs(ctx, font))
474
0
  {
475
0
    val = fz_render_t3_glyph_pixmap(ctx, font, gid, subpix_ctm, NULL, scissor, aa);
476
0
  }
477
0
  else
478
0
  {
479
0
    fz_warn(ctx, "assert: uninitialized font structure");
480
0
    val = NULL;
481
0
  }
482
483
0
  return val;
484
0
}
485
486
void
487
fz_dump_glyph_cache_stats(fz_context *ctx, fz_output *out)
488
0
{
489
0
  fz_glyph_cache *cache = ctx->glyph_cache;
490
0
  fz_write_printf(ctx, out, "Glyph Cache Size: %zu\n", cache->total);
491
0
#ifndef NDEBUG
492
0
  fz_write_printf(ctx, out, "Glyph Cache Evictions: %d (%zu bytes)\n", cache->num_evictions, cache->evicted);
493
0
#endif
494
0
}