/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 | 0 | : SplashFont(fontFileA, matA, textMatA, fontFileA->engine->aa), enableFreeTypeHinting(fontFileA->engine->enableFreeTypeHinting), enableSlightHinting(fontFileA->engine->enableSlightHinting) |
58 | 0 | { |
59 | 0 | FT_Face face; |
60 | 0 | int div; |
61 | 0 | int x, y; |
62 | |
|
63 | 0 | face = fontFileA->face; |
64 | 0 | if (FT_New_Size(face, &sizeObj)) { |
65 | 0 | return; |
66 | 0 | } |
67 | 0 | face->size = sizeObj; |
68 | 0 | size = splashRound(splashDist(0, 0, mat[2], mat[3])); |
69 | 0 | if (size < 1) { |
70 | 0 | size = 1; |
71 | 0 | } |
72 | 0 | if (FT_Set_Pixel_Sizes(face, 0, size)) { |
73 | 0 | return; |
74 | 0 | } |
75 | | // if the textMat values are too small, FreeType's fixed point |
76 | | // arithmetic doesn't work so well |
77 | 0 | textScale = splashDist(0, 0, textMat[2], textMat[3]) / size; |
78 | |
|
79 | 0 | if (unlikely(textScale == 0 || face->units_per_EM == 0)) { |
80 | 0 | return; |
81 | 0 | } |
82 | | |
83 | 0 | 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 | 0 | x = static_cast<int>((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMin) / (div * face->units_per_EM)); |
88 | 0 | xMin = xMax = x; |
89 | 0 | y = static_cast<int>((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMin) / (div * face->units_per_EM)); |
90 | 0 | yMin = yMax = y; |
91 | 0 | x = static_cast<int>((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMax) / (div * face->units_per_EM)); |
92 | 0 | if (x < xMin) { |
93 | 0 | xMin = x; |
94 | 0 | } else if (x > xMax) { |
95 | 0 | xMax = x; |
96 | 0 | } |
97 | 0 | y = static_cast<int>((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMax) / (div * face->units_per_EM)); |
98 | 0 | if (y < yMin) { |
99 | 0 | yMin = y; |
100 | 0 | } else if (y > yMax) { |
101 | 0 | yMax = y; |
102 | 0 | } |
103 | 0 | x = static_cast<int>((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMin) / (div * face->units_per_EM)); |
104 | 0 | if (x < xMin) { |
105 | 0 | xMin = x; |
106 | 0 | } else if (x > xMax) { |
107 | 0 | xMax = x; |
108 | 0 | } |
109 | 0 | y = static_cast<int>((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMin) / (div * face->units_per_EM)); |
110 | 0 | if (y < yMin) { |
111 | 0 | yMin = y; |
112 | 0 | } else if (y > yMax) { |
113 | 0 | yMax = y; |
114 | 0 | } |
115 | 0 | x = static_cast<int>((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMax) / (div * face->units_per_EM)); |
116 | 0 | if (x < xMin) { |
117 | 0 | xMin = x; |
118 | 0 | } else if (x > xMax) { |
119 | 0 | xMax = x; |
120 | 0 | } |
121 | 0 | y = static_cast<int>((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMax) / (div * face->units_per_EM)); |
122 | 0 | if (y < yMin) { |
123 | 0 | yMin = y; |
124 | 0 | } else if (y > yMax) { |
125 | 0 | yMax = y; |
126 | 0 | } |
127 | | // This is a kludge: some buggy PDF generators embed fonts with |
128 | | // zero bounding boxes. |
129 | 0 | if (xMax == xMin) { |
130 | 0 | xMin = 0; |
131 | 0 | xMax = size; |
132 | 0 | } |
133 | 0 | if (yMax == yMin) { |
134 | 0 | yMin = 0; |
135 | 0 | yMax = static_cast<int>(1.2 * size); |
136 | 0 | } |
137 | | |
138 | | // compute the transform matrix |
139 | 0 | matrix.xx = static_cast<FT_Fixed>((mat[0] / size) * 65536); |
140 | 0 | matrix.yx = static_cast<FT_Fixed>((mat[1] / size) * 65536); |
141 | 0 | matrix.xy = static_cast<FT_Fixed>((mat[2] / size) * 65536); |
142 | 0 | matrix.yy = static_cast<FT_Fixed>((mat[3] / size) * 65536); |
143 | 0 | textMatrix.xx = static_cast<FT_Fixed>((textMat[0] / (textScale * size)) * 65536); |
144 | 0 | textMatrix.yx = static_cast<FT_Fixed>((textMat[1] / (textScale * size)) * 65536); |
145 | 0 | textMatrix.xy = static_cast<FT_Fixed>((textMat[2] / (textScale * size)) * 65536); |
146 | 0 | textMatrix.yy = static_cast<FT_Fixed>((textMat[3] / (textScale * size)) * 65536); |
147 | |
|
148 | 0 | isOk = true; |
149 | 0 | } |
150 | | |
151 | 0 | 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 | 0 | { |
155 | 0 | return SplashFont::getGlyph(c, xFrac, 0, bitmap, x0, y0, clip, clipRes); |
156 | 0 | } |
157 | | |
158 | | static FT_Int32 getFTLoadFlags(bool type1, bool trueType, bool aa, bool enableFreeTypeHinting, bool enableSlightHinting) |
159 | 0 | { |
160 | 0 | int ret = FT_LOAD_DEFAULT; |
161 | 0 | if (aa) { |
162 | 0 | ret |= FT_LOAD_NO_BITMAP; |
163 | 0 | } |
164 | |
|
165 | 0 | 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 | 0 | } else { |
183 | 0 | ret |= FT_LOAD_NO_HINTING; |
184 | 0 | } |
185 | 0 | return ret; |
186 | 0 | } |
187 | | |
188 | | bool SplashFTFont::makeGlyph(int c, int xFrac, int /*yFrac*/, SplashGlyphBitmap *bitmap, int x0, int y0, const SplashClip &clip, SplashClipResult *clipRes) |
189 | 0 | { |
190 | 0 | SplashFTFontFile *ff; |
191 | 0 | FT_Vector offset; |
192 | 0 | FT_GlyphSlot slot; |
193 | 0 | FT_UInt gid; |
194 | 0 | int rowSize; |
195 | 0 | unsigned char *p, *q; |
196 | 0 | int i; |
197 | |
|
198 | 0 | if (unlikely(!isOk)) { |
199 | 0 | return false; |
200 | 0 | } |
201 | | |
202 | 0 | ff = static_cast<SplashFTFontFile *>(fontFile.get()); |
203 | |
|
204 | 0 | ff->face->size = sizeObj; |
205 | 0 | offset.x = static_cast<FT_Pos>(static_cast<int>(static_cast<double>(xFrac) * splashFontFractionMul * 64)); |
206 | 0 | offset.y = 0; |
207 | 0 | FT_Set_Transform(ff->face, &matrix, &offset); |
208 | 0 | slot = ff->face->glyph; |
209 | |
|
210 | 0 | if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) { |
211 | 0 | gid = static_cast<FT_UInt>(ff->codeToGID[c]); |
212 | 0 | } else { |
213 | 0 | gid = static_cast<FT_UInt>(c); |
214 | 0 | } |
215 | |
|
216 | 0 | if (FT_Load_Glyph(ff->face, gid, getFTLoadFlags(ff->type1, ff->trueType, aa, enableFreeTypeHinting, enableSlightHinting))) { |
217 | 0 | return false; |
218 | 0 | } |
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 | 0 | FT_BBox cbox; |
223 | 0 | FT_Outline_Get_CBox(&ff->face->glyph->outline, &cbox); |
224 | 0 | bitmap->x = -(cbox.xMin / 64) + 2; |
225 | 0 | bitmap->y = (cbox.yMax / 64) + 2; |
226 | 0 | bitmap->w = ((cbox.xMax - cbox.xMin) / 64) + 4; |
227 | 0 | bitmap->h = ((cbox.yMax - cbox.yMin) / 64) + 4; |
228 | |
|
229 | 0 | int rectXMin, rectYMin; |
230 | 0 | if (checkedSubtraction(x0, bitmap->x, &rectXMin)) { |
231 | 0 | return false; |
232 | 0 | } |
233 | 0 | if (checkedSubtraction(y0, bitmap->y, &rectYMin)) { |
234 | 0 | return false; |
235 | 0 | } |
236 | 0 | *clipRes = clip.testRect(rectXMin, rectYMin, rectXMin + bitmap->w, rectYMin + bitmap->h); |
237 | 0 | if (*clipRes == splashClipAllOutside) { |
238 | 0 | bitmap->freeData = false; |
239 | 0 | return true; |
240 | 0 | } |
241 | | |
242 | 0 | if (FT_Render_Glyph(slot, aa ? ft_render_mode_normal : ft_render_mode_mono)) { |
243 | 0 | return false; |
244 | 0 | } |
245 | | |
246 | 0 | 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 | 0 | return false; |
250 | 0 | } |
251 | | |
252 | 0 | bitmap->x = -slot->bitmap_left; |
253 | 0 | bitmap->y = slot->bitmap_top; |
254 | 0 | bitmap->w = slot->bitmap.width; |
255 | 0 | bitmap->h = slot->bitmap.rows; |
256 | 0 | bitmap->aa = aa; |
257 | 0 | if (aa) { |
258 | 0 | rowSize = bitmap->w; |
259 | 0 | } else { |
260 | 0 | rowSize = (bitmap->w + 7) >> 3; |
261 | 0 | } |
262 | 0 | bitmap->data = static_cast<unsigned char *>(gmallocn_checkoverflow(rowSize, bitmap->h)); |
263 | 0 | if (!bitmap->data) { |
264 | 0 | return false; |
265 | 0 | } |
266 | 0 | bitmap->freeData = true; |
267 | 0 | for (i = 0, p = bitmap->data, q = slot->bitmap.buffer; i < bitmap->h; ++i, p += rowSize, q += slot->bitmap.pitch) { |
268 | 0 | memcpy(p, q, rowSize); |
269 | 0 | } |
270 | |
|
271 | 0 | return true; |
272 | 0 | } |
273 | | |
274 | | double SplashFTFont::getGlyphAdvance(int c) |
275 | 0 | { |
276 | 0 | SplashFTFontFile *ff; |
277 | 0 | FT_Vector offset; |
278 | 0 | FT_UInt gid; |
279 | 0 | FT_Matrix identityMatrix; |
280 | |
|
281 | 0 | ff = static_cast<SplashFTFontFile *>(fontFile.get()); |
282 | | |
283 | | // init the matrix |
284 | 0 | identityMatrix.xx = 65536; // 1 in 16.16 format |
285 | 0 | identityMatrix.xy = 0; |
286 | 0 | identityMatrix.yx = 0; |
287 | 0 | identityMatrix.yy = 65536; // 1 in 16.16 format |
288 | | |
289 | | // init the offset |
290 | 0 | offset.x = 0; |
291 | 0 | offset.y = 0; |
292 | |
|
293 | 0 | ff->face->size = sizeObj; |
294 | 0 | FT_Set_Transform(ff->face, &identityMatrix, &offset); |
295 | |
|
296 | 0 | if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) { |
297 | 0 | gid = static_cast<FT_UInt>(ff->codeToGID[c]); |
298 | 0 | } else { |
299 | 0 | gid = static_cast<FT_UInt>(c); |
300 | 0 | } |
301 | |
|
302 | 0 | 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 | 0 | return ff->face->glyph->metrics.horiAdvance / 64.0 / size; |
308 | 0 | } |
309 | | |
310 | | struct SplashFTFontPath |
311 | | { |
312 | | SplashPath *path; |
313 | | double textScale; |
314 | | bool needClose; |
315 | | }; |
316 | | |
317 | | SplashPath *SplashFTFont::getGlyphPath(int c) |
318 | 0 | { |
319 | 0 | 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 | 0 | .move_to = &glyphPathMoveTo, |
327 | 0 | .line_to = &glyphPathLineTo, |
328 | 0 | .conic_to = &glyphPathConicTo, |
329 | 0 | .cubic_to = &glyphPathCubicTo, |
330 | 0 | #endif |
331 | 0 | .shift = 0, |
332 | 0 | .delta = 0 |
333 | 0 | }; |
334 | 0 | SplashFTFontFile *ff; |
335 | 0 | SplashFTFontPath path; |
336 | 0 | FT_GlyphSlot slot; |
337 | 0 | FT_UInt gid; |
338 | 0 | FT_Glyph glyph; |
339 | |
|
340 | 0 | if (unlikely(textScale == 0)) { |
341 | 0 | return nullptr; |
342 | 0 | } |
343 | | |
344 | 0 | ff = static_cast<SplashFTFontFile *>(fontFile.get()); |
345 | 0 | ff->face->size = sizeObj; |
346 | 0 | FT_Set_Transform(ff->face, &textMatrix, nullptr); |
347 | 0 | slot = ff->face->glyph; |
348 | 0 | if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) { |
349 | 0 | gid = ff->codeToGID[c]; |
350 | 0 | } else { |
351 | 0 | gid = static_cast<FT_UInt>(c); |
352 | 0 | } |
353 | 0 | if (FT_Load_Glyph(ff->face, gid, getFTLoadFlags(ff->type1, ff->trueType, aa, enableFreeTypeHinting, enableSlightHinting))) { |
354 | 0 | return nullptr; |
355 | 0 | } |
356 | 0 | if (FT_Get_Glyph(slot, &glyph)) { |
357 | 0 | return nullptr; |
358 | 0 | } |
359 | 0 | if (FT_Outline_Check(&(reinterpret_cast<FT_OutlineGlyph>(glyph))->outline)) { |
360 | 0 | return nullptr; |
361 | 0 | } |
362 | 0 | path.path = new SplashPath(); |
363 | 0 | path.textScale = textScale; |
364 | 0 | path.needClose = false; |
365 | 0 | FT_Outline_Decompose(&(reinterpret_cast<FT_OutlineGlyph>(glyph))->outline, &outlineFuncs, &path); |
366 | 0 | if (path.needClose) { |
367 | 0 | path.path->close(); |
368 | 0 | } |
369 | 0 | FT_Done_Glyph(glyph); |
370 | 0 | return path.path; |
371 | 0 | } |
372 | | |
373 | | static int glyphPathMoveTo(const FT_Vector *pt, void *path) |
374 | 0 | { |
375 | 0 | auto *p = static_cast<SplashFTFontPath *>(path); |
376 | |
|
377 | 0 | if (p->needClose) { |
378 | 0 | p->path->close(); |
379 | 0 | p->needClose = false; |
380 | 0 | } |
381 | 0 | p->path->moveTo(static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0); |
382 | 0 | return 0; |
383 | 0 | } |
384 | | |
385 | | static int glyphPathLineTo(const FT_Vector *pt, void *path) |
386 | 0 | { |
387 | 0 | auto *p = static_cast<SplashFTFontPath *>(path); |
388 | |
|
389 | 0 | p->path->lineTo(static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0); |
390 | 0 | p->needClose = true; |
391 | 0 | return 0; |
392 | 0 | } |
393 | | |
394 | | static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt, void *path) |
395 | 0 | { |
396 | 0 | auto *p = static_cast<SplashFTFontPath *>(path); |
397 | 0 | double x0, y0, x1, y1, x2, y2, x3, y3, xc, yc; |
398 | |
|
399 | 0 | if (!p->path->getCurPt(&x0, &y0)) { |
400 | 0 | return 0; |
401 | 0 | } |
402 | 0 | xc = static_cast<double>(ctrl->x) * p->textScale / 64.0; |
403 | 0 | yc = static_cast<double>(ctrl->y) * p->textScale / 64.0; |
404 | 0 | x3 = static_cast<double>(pt->x) * p->textScale / 64.0; |
405 | 0 | 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 | 0 | x1 = (1.0 / 3.0) * (x0 + 2.0 * xc); |
424 | 0 | y1 = (1.0 / 3.0) * (y0 + 2.0 * yc); |
425 | 0 | x2 = (1.0 / 3.0) * (2.0 * xc + x3); |
426 | 0 | y2 = (1.0 / 3.0) * (2.0 * yc + y3); |
427 | |
|
428 | 0 | p->path->curveTo(x1, y1, x2, y2, x3, y3); |
429 | 0 | p->needClose = true; |
430 | 0 | return 0; |
431 | 0 | } |
432 | | |
433 | | static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2, const FT_Vector *pt, void *path) |
434 | 0 | { |
435 | 0 | auto *p = static_cast<SplashFTFontPath *>(path); |
436 | |
|
437 | 0 | 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 | 0 | static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0); |
439 | 0 | p->needClose = true; |
440 | 0 | return 0; |
441 | 0 | } |