/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 | } |