Coverage Report

Created: 2026-06-30 07:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/splash/SplashFTFont.cc
Line
Count
Source
1
//========================================================================
2
//
3
// SplashFTFont.cc
4
//
5
//========================================================================
6
7
//========================================================================
8
//
9
// Modified under the Poppler project - http://poppler.freedesktop.org
10
//
11
// All changes made under the Poppler project to this file are licensed
12
// under GPL version 2 or later
13
//
14
// Copyright (C) 2005, 2007-2011, 2014, 2018, 2020, 2025, 2026 Albert Astals Cid <aacid@kde.org>
15
// Copyright (C) 2006 Kristian Høgsberg <krh@bitplanet.net>
16
// Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com>
17
// Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
18
// Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
19
// Copyright (C) 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
20
// Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
21
// Copyright (C) 2018 Oliver Sander <oliver.sander@tu-dresden.de>
22
// Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
23
//
24
// To see a description of the changes please see the Changelog file that
25
// came with your tarball or type make ChangeLog if you are building from git
26
//
27
//========================================================================
28
29
#include <config.h>
30
31
#include <ft2build.h>
32
#include FT_OUTLINE_H
33
#include FT_SIZES_H
34
#include FT_GLYPH_H
35
#include "goo/gmem.h"
36
#include "SplashMath.h"
37
#include "SplashGlyphBitmap.h"
38
#include "SplashPath.h"
39
#include "SplashFTFontEngine.h"
40
#include "SplashFTFontFile.h"
41
#include "SplashFTFont.h"
42
43
#include "goo/GooLikely.h"
44
45
//------------------------------------------------------------------------
46
47
static int glyphPathMoveTo(const FT_Vector *pt, void *path);
48
static int glyphPathLineTo(const FT_Vector *pt, void *path);
49
static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt, void *path);
50
static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2, const FT_Vector *pt, void *path);
51
52
//------------------------------------------------------------------------
53
// SplashFTFont
54
//------------------------------------------------------------------------
55
56
SplashFTFont::SplashFTFont(const std::shared_ptr<SplashFTFontFile> &fontFileA, const std::array<double, 4> &matA, const std::array<double, 4> &textMatA)
57
203k
    : SplashFont(fontFileA, matA, textMatA, fontFileA->engine->aa), enableFreeTypeHinting(fontFileA->engine->enableFreeTypeHinting), enableSlightHinting(fontFileA->engine->enableSlightHinting)
58
203k
{
59
203k
    FT_Face face;
60
203k
    int div;
61
203k
    int x, y;
62
63
203k
    face = fontFileA->face;
64
203k
    if (FT_New_Size(face, &sizeObj)) {
65
0
        return;
66
0
    }
67
203k
    face->size = sizeObj;
68
203k
    size = splashRound(splashDist(0, 0, mat[2], mat[3]));
69
203k
    if (size < 1) {
70
3.84k
        size = 1;
71
3.84k
    }
72
203k
    if (FT_Set_Pixel_Sizes(face, 0, size)) {
73
19
        return;
74
19
    }
75
    // if the textMat values are too small, FreeType's fixed point
76
    // arithmetic doesn't work so well
77
203k
    textScale = splashDist(0, 0, textMat[2], textMat[3]) / size;
78
79
203k
    if (unlikely(textScale == 0 || face->units_per_EM == 0)) {
80
436
        return;
81
436
    }
82
83
202k
    div = face->bbox.xMax > 20000 ? 65536 : 1;
84
85
    // transform the four corners of the font bounding box -- the min
86
    // and max values form the bounding box of the transformed font
87
202k
    x = static_cast<int>((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMin) / (div * face->units_per_EM));
88
202k
    xMin = xMax = x;
89
202k
    y = static_cast<int>((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMin) / (div * face->units_per_EM));
90
202k
    yMin = yMax = y;
91
202k
    x = static_cast<int>((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMax) / (div * face->units_per_EM));
92
202k
    if (x < xMin) {
93
837
        xMin = x;
94
201k
    } else if (x > xMax) {
95
2.99k
        xMax = x;
96
2.99k
    }
97
202k
    y = static_cast<int>((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMax) / (div * face->units_per_EM));
98
202k
    if (y < yMin) {
99
5.80k
        yMin = y;
100
197k
    } else if (y > yMax) {
101
188k
        yMax = y;
102
188k
    }
103
202k
    x = static_cast<int>((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMin) / (div * face->units_per_EM));
104
202k
    if (x < xMin) {
105
4.61k
        xMin = x;
106
198k
    } else if (x > xMax) {
107
189k
        xMax = x;
108
189k
    }
109
202k
    y = static_cast<int>((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMin) / (div * face->units_per_EM));
110
202k
    if (y < yMin) {
111
1.88k
        yMin = y;
112
200k
    } else if (y > yMax) {
113
862
        yMax = y;
114
862
    }
115
202k
    x = static_cast<int>((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMax) / (div * face->units_per_EM));
116
202k
    if (x < xMin) {
117
375
        xMin = x;
118
202k
    } else if (x > xMax) {
119
2.13k
        xMax = x;
120
2.13k
    }
121
202k
    y = static_cast<int>((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMax) / (div * face->units_per_EM));
122
202k
    if (y < yMin) {
123
56
        yMin = y;
124
202k
    } else if (y > yMax) {
125
961
        yMax = y;
126
961
    }
127
    // This is a kludge: some buggy PDF generators embed fonts with
128
    // zero bounding boxes.
129
202k
    if (xMax == xMin) {
130
6.71k
        xMin = 0;
131
6.71k
        xMax = size;
132
6.71k
    }
133
202k
    if (yMax == yMin) {
134
7.13k
        yMin = 0;
135
7.13k
        yMax = static_cast<int>(1.2 * size);
136
7.13k
    }
137
138
    // compute the transform matrix
139
202k
    matrix.xx = static_cast<FT_Fixed>((mat[0] / size) * 65536);
140
202k
    matrix.yx = static_cast<FT_Fixed>((mat[1] / size) * 65536);
141
202k
    matrix.xy = static_cast<FT_Fixed>((mat[2] / size) * 65536);
142
202k
    matrix.yy = static_cast<FT_Fixed>((mat[3] / size) * 65536);
143
202k
    textMatrix.xx = static_cast<FT_Fixed>((textMat[0] / (textScale * size)) * 65536);
144
202k
    textMatrix.yx = static_cast<FT_Fixed>((textMat[1] / (textScale * size)) * 65536);
145
202k
    textMatrix.xy = static_cast<FT_Fixed>((textMat[2] / (textScale * size)) * 65536);
146
202k
    textMatrix.yy = static_cast<FT_Fixed>((textMat[3] / (textScale * size)) * 65536);
147
148
202k
    isOk = true;
149
202k
}
150
151
203k
SplashFTFont::~SplashFTFont() = default;
152
153
bool SplashFTFont::getGlyph(int c, int xFrac, int /*yFrac*/, SplashGlyphBitmap *bitmap, int x0, int y0, const SplashClip &clip, SplashClipResult *clipRes)
154
28.8M
{
155
28.8M
    return SplashFont::getGlyph(c, xFrac, 0, bitmap, x0, y0, clip, clipRes);
156
28.8M
}
157
158
static FT_Int32 getFTLoadFlags(bool type1, bool trueType, bool aa, bool enableFreeTypeHinting, bool enableSlightHinting)
159
10.6M
{
160
10.6M
    int ret = FT_LOAD_DEFAULT;
161
10.6M
    if (aa) {
162
0
        ret |= FT_LOAD_NO_BITMAP;
163
0
    }
164
165
10.6M
    if (enableFreeTypeHinting) {
166
0
        if (enableSlightHinting) {
167
0
            ret |= FT_LOAD_TARGET_LIGHT;
168
0
        } else {
169
0
            if (trueType) {
170
                // FT2's autohinting doesn't always work very well (especially with
171
                // font subsets), so turn it off if anti-aliasing is enabled; if
172
                // anti-aliasing is disabled, this seems to be a tossup - some fonts
173
                // look better with hinting, some without, so leave hinting on
174
0
                if (aa) {
175
0
                    ret |= FT_LOAD_NO_AUTOHINT;
176
0
                }
177
0
            } else if (type1) {
178
                // Type 1 fonts seem to look better with 'light' hinting mode
179
0
                ret |= FT_LOAD_TARGET_LIGHT;
180
0
            }
181
0
        }
182
10.6M
    } else {
183
10.6M
        ret |= FT_LOAD_NO_HINTING;
184
10.6M
    }
185
10.6M
    return ret;
186
10.6M
}
187
188
bool SplashFTFont::makeGlyph(int c, int xFrac, int /*yFrac*/, SplashGlyphBitmap *bitmap, int x0, int y0, const SplashClip &clip, SplashClipResult *clipRes)
189
8.24M
{
190
8.24M
    SplashFTFontFile *ff;
191
8.24M
    FT_Vector offset;
192
8.24M
    FT_GlyphSlot slot;
193
8.24M
    FT_UInt gid;
194
8.24M
    int rowSize;
195
8.24M
    unsigned char *p, *q;
196
8.24M
    int i;
197
198
8.24M
    if (unlikely(!isOk)) {
199
202k
        return false;
200
202k
    }
201
202
8.04M
    ff = static_cast<SplashFTFontFile *>(fontFile.get());
203
204
8.04M
    ff->face->size = sizeObj;
205
8.04M
    offset.x = static_cast<FT_Pos>(static_cast<int>(static_cast<double>(xFrac) * splashFontFractionMul * 64));
206
8.04M
    offset.y = 0;
207
8.04M
    FT_Set_Transform(ff->face, &matrix, &offset);
208
8.04M
    slot = ff->face->glyph;
209
210
8.04M
    if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) {
211
7.47M
        gid = static_cast<FT_UInt>(ff->codeToGID[c]);
212
7.47M
    } else {
213
564k
        gid = static_cast<FT_UInt>(c);
214
564k
    }
215
216
8.04M
    if (FT_Load_Glyph(ff->face, gid, getFTLoadFlags(ff->type1, ff->trueType, aa, enableFreeTypeHinting, enableSlightHinting))) {
217
4.06M
        return false;
218
4.06M
    }
219
220
    // prelimirary values based on FT_Outline_Get_CBox
221
    // we add two pixels to each side to be in the safe side
222
3.97M
    FT_BBox cbox;
223
3.97M
    FT_Outline_Get_CBox(&ff->face->glyph->outline, &cbox);
224
3.97M
    bitmap->x = -(cbox.xMin / 64) + 2;
225
3.97M
    bitmap->y = (cbox.yMax / 64) + 2;
226
3.97M
    bitmap->w = ((cbox.xMax - cbox.xMin) / 64) + 4;
227
3.97M
    bitmap->h = ((cbox.yMax - cbox.yMin) / 64) + 4;
228
229
3.97M
    int rectXMin, rectYMin;
230
3.97M
    if (checkedSubtraction(x0, bitmap->x, &rectXMin)) {
231
8.56k
        return false;
232
8.56k
    }
233
3.96M
    if (checkedSubtraction(y0, bitmap->y, &rectYMin)) {
234
4.18k
        return false;
235
4.18k
    }
236
3.96M
    *clipRes = clip.testRect(rectXMin, rectYMin, rectXMin + bitmap->w, rectYMin + bitmap->h);
237
3.96M
    if (*clipRes == splashClipAllOutside) {
238
1.57M
        bitmap->freeData = false;
239
1.57M
        return true;
240
1.57M
    }
241
242
2.38M
    if (FT_Render_Glyph(slot, aa ? ft_render_mode_normal : ft_render_mode_mono)) {
243
9.39k
        return false;
244
9.39k
    }
245
246
2.37M
    if (slot->bitmap.width == 0 || slot->bitmap.rows == 0) {
247
        // this can happen if (a) the glyph is really tiny or (b) the
248
        // metrics in the TrueType file are broken
249
15
        return false;
250
15
    }
251
252
2.37M
    bitmap->x = -slot->bitmap_left;
253
2.37M
    bitmap->y = slot->bitmap_top;
254
2.37M
    bitmap->w = slot->bitmap.width;
255
2.37M
    bitmap->h = slot->bitmap.rows;
256
2.37M
    bitmap->aa = aa;
257
2.37M
    if (aa) {
258
0
        rowSize = bitmap->w;
259
2.37M
    } else {
260
2.37M
        rowSize = (bitmap->w + 7) >> 3;
261
2.37M
    }
262
2.37M
    bitmap->data = static_cast<unsigned char *>(gmallocn_checkoverflow(rowSize, bitmap->h));
263
2.37M
    if (!bitmap->data) {
264
0
        return false;
265
0
    }
266
2.37M
    bitmap->freeData = true;
267
27.7M
    for (i = 0, p = bitmap->data, q = slot->bitmap.buffer; i < bitmap->h; ++i, p += rowSize, q += slot->bitmap.pitch) {
268
25.3M
        memcpy(p, q, rowSize);
269
25.3M
    }
270
271
2.37M
    return true;
272
2.37M
}
273
274
double SplashFTFont::getGlyphAdvance(int c)
275
811k
{
276
811k
    SplashFTFontFile *ff;
277
811k
    FT_Vector offset;
278
811k
    FT_UInt gid;
279
811k
    FT_Matrix identityMatrix;
280
281
811k
    ff = static_cast<SplashFTFontFile *>(fontFile.get());
282
283
    // init the matrix
284
811k
    identityMatrix.xx = 65536; // 1 in 16.16 format
285
811k
    identityMatrix.xy = 0;
286
811k
    identityMatrix.yx = 0;
287
811k
    identityMatrix.yy = 65536; // 1 in 16.16 format
288
289
    // init the offset
290
811k
    offset.x = 0;
291
811k
    offset.y = 0;
292
293
811k
    ff->face->size = sizeObj;
294
811k
    FT_Set_Transform(ff->face, &identityMatrix, &offset);
295
296
811k
    if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) {
297
811k
        gid = static_cast<FT_UInt>(ff->codeToGID[c]);
298
811k
    } else {
299
0
        gid = static_cast<FT_UInt>(c);
300
0
    }
301
302
811k
    if (FT_Load_Glyph(ff->face, gid, getFTLoadFlags(ff->type1, ff->trueType, aa, enableFreeTypeHinting, enableSlightHinting))) {
303
0
        return -1;
304
0
    }
305
306
    // 64.0 is 1 in 26.6 format
307
811k
    return ff->face->glyph->metrics.horiAdvance / 64.0 / size;
308
811k
}
309
310
struct SplashFTFontPath
311
{
312
    SplashPath *path;
313
    double textScale;
314
    bool needClose;
315
};
316
317
SplashPath *SplashFTFont::getGlyphPath(int c)
318
1.97M
{
319
1.97M
    static const FT_Outline_Funcs outlineFuncs = {
320
#if FREETYPE_MINOR <= 1
321
        (int (*)(FT_Vector *, void *))&glyphPathMoveTo,
322
        (int (*)(FT_Vector *, void *))&glyphPathLineTo,
323
        (int (*)(FT_Vector *, FT_Vector *, void *))&glyphPathConicTo,
324
        (int (*)(FT_Vector *, FT_Vector *, FT_Vector *, void *))&glyphPathCubicTo,
325
#else
326
1.97M
        .move_to = &glyphPathMoveTo,
327
1.97M
        .line_to = &glyphPathLineTo,
328
1.97M
        .conic_to = &glyphPathConicTo,
329
1.97M
        .cubic_to = &glyphPathCubicTo,
330
1.97M
#endif
331
1.97M
        .shift = 0,
332
1.97M
        .delta = 0
333
1.97M
    };
334
1.97M
    SplashFTFontFile *ff;
335
1.97M
    SplashFTFontPath path;
336
1.97M
    FT_GlyphSlot slot;
337
1.97M
    FT_UInt gid;
338
1.97M
    FT_Glyph glyph;
339
340
1.97M
    if (unlikely(textScale == 0)) {
341
151k
        return nullptr;
342
151k
    }
343
344
1.82M
    ff = static_cast<SplashFTFontFile *>(fontFile.get());
345
1.82M
    ff->face->size = sizeObj;
346
1.82M
    FT_Set_Transform(ff->face, &textMatrix, nullptr);
347
1.82M
    slot = ff->face->glyph;
348
1.82M
    if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) {
349
1.81M
        gid = ff->codeToGID[c];
350
1.81M
    } else {
351
7.62k
        gid = static_cast<FT_UInt>(c);
352
7.62k
    }
353
1.82M
    if (FT_Load_Glyph(ff->face, gid, getFTLoadFlags(ff->type1, ff->trueType, aa, enableFreeTypeHinting, enableSlightHinting))) {
354
472k
        return nullptr;
355
472k
    }
356
1.35M
    if (FT_Get_Glyph(slot, &glyph)) {
357
245
        return nullptr;
358
245
    }
359
1.35M
    if (FT_Outline_Check(&(reinterpret_cast<FT_OutlineGlyph>(glyph))->outline)) {
360
0
        return nullptr;
361
0
    }
362
1.35M
    path.path = new SplashPath();
363
1.35M
    path.textScale = textScale;
364
1.35M
    path.needClose = false;
365
1.35M
    FT_Outline_Decompose(&(reinterpret_cast<FT_OutlineGlyph>(glyph))->outline, &outlineFuncs, &path);
366
1.35M
    if (path.needClose) {
367
1.28M
        path.path->close();
368
1.28M
    }
369
1.35M
    FT_Done_Glyph(glyph);
370
1.35M
    return path.path;
371
1.35M
}
372
373
static int glyphPathMoveTo(const FT_Vector *pt, void *path)
374
1.77M
{
375
1.77M
    auto *p = static_cast<SplashFTFontPath *>(path);
376
377
1.77M
    if (p->needClose) {
378
487k
        p->path->close();
379
487k
        p->needClose = false;
380
487k
    }
381
1.77M
    p->path->moveTo(static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0);
382
1.77M
    return 0;
383
1.77M
}
384
385
static int glyphPathLineTo(const FT_Vector *pt, void *path)
386
12.3M
{
387
12.3M
    auto *p = static_cast<SplashFTFontPath *>(path);
388
389
12.3M
    p->path->lineTo(static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0);
390
12.3M
    p->needClose = true;
391
12.3M
    return 0;
392
12.3M
}
393
394
static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt, void *path)
395
5.89M
{
396
5.89M
    auto *p = static_cast<SplashFTFontPath *>(path);
397
5.89M
    double x0, y0, x1, y1, x2, y2, x3, y3, xc, yc;
398
399
5.89M
    if (!p->path->getCurPt(&x0, &y0)) {
400
0
        return 0;
401
0
    }
402
5.89M
    xc = static_cast<double>(ctrl->x) * p->textScale / 64.0;
403
5.89M
    yc = static_cast<double>(ctrl->y) * p->textScale / 64.0;
404
5.89M
    x3 = static_cast<double>(pt->x) * p->textScale / 64.0;
405
5.89M
    y3 = static_cast<double>(pt->y) * p->textScale / 64.0;
406
407
    // A second-order Bezier curve is defined by two endpoints, p0 and
408
    // p3, and one control point, pc:
409
    //
410
    //     p(t) = (1-t)^2*p0 + t*(1-t)*pc + t^2*p3
411
    //
412
    // A third-order Bezier curve is defined by the same two endpoints,
413
    // p0 and p3, and two control points, p1 and p2:
414
    //
415
    //     p(t) = (1-t)^3*p0 + 3t*(1-t)^2*p1 + 3t^2*(1-t)*p2 + t^3*p3
416
    //
417
    // Applying some algebra, we can convert a second-order curve to a
418
    // third-order curve:
419
    //
420
    //     p1 = (1/3) * (p0 + 2pc)
421
    //     p2 = (1/3) * (2pc + p3)
422
423
5.89M
    x1 = (1.0 / 3.0) * (x0 + 2.0 * xc);
424
5.89M
    y1 = (1.0 / 3.0) * (y0 + 2.0 * yc);
425
5.89M
    x2 = (1.0 / 3.0) * (2.0 * xc + x3);
426
5.89M
    y2 = (1.0 / 3.0) * (2.0 * yc + y3);
427
428
5.89M
    p->path->curveTo(x1, y1, x2, y2, x3, y3);
429
5.89M
    p->needClose = true;
430
5.89M
    return 0;
431
5.89M
}
432
433
static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2, const FT_Vector *pt, void *path)
434
574k
{
435
574k
    auto *p = static_cast<SplashFTFontPath *>(path);
436
437
574k
    p->path->curveTo(static_cast<double>(ctrl1->x) * p->textScale / 64.0, static_cast<double>(ctrl1->y) * p->textScale / 64.0, static_cast<double>(ctrl2->x) * p->textScale / 64.0, static_cast<double>(ctrl2->y) * p->textScale / 64.0,
438
574k
                     static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0);
439
574k
    p->needClose = true;
440
574k
    return 0;
441
574k
}