Solvespace

Форк
0
/
ttf.cpp 
448 строк · 15.9 Кб
1
//-----------------------------------------------------------------------------
2
// Routines to read a TrueType font as vector outlines, and generate them
3
// as entities, since they're always representable as either lines or
4
// quadratic Bezier curves.
5
//
6
// Copyright 2016 whitequark, Peter Barfuss.
7
//-----------------------------------------------------------------------------
8
#include <ft2build.h>
9
#include FT_FREETYPE_H
10
#include FT_OUTLINE_H
11
#include FT_ADVANCES_H
12

13
/* Yecch. Irritatingly, you need to do this nonsense to get the error string table,
14
   since nobody thought to put this exact function into FreeType itself. */
15
#undef __FTERRORS_H__
16
#define FT_ERRORDEF(e, v, s) { (e), (s) },
17
#define FT_ERROR_START_LIST
18
#define FT_ERROR_END_LIST { 0, NULL }
19

20
struct ft_error {
21
    int err;
22
    const char *str;
23
};
24

25
static const struct ft_error ft_errors[] = {
26
#include FT_ERRORS_H
27
};
28

29
extern "C" const char *ft_error_string(int err) {
30
    const struct ft_error *e;
31
    for(e = ft_errors; e->str; e++)
32
        if(e->err == err)
33
            return e->str;
34
    return "Unknown error";
35
}
36

37
/* Okay, we're done with that. */
38
#undef FT_ERRORDEF
39
#undef FT_ERROR_START_LIST
40
#undef FT_ERROR_END_LIST
41

42
#include "solvespace.h"
43

44
//-----------------------------------------------------------------------------
45
// Get the list of available font filenames, and load the name for each of
46
// them. Only that, though, not the glyphs too.
47
//-----------------------------------------------------------------------------
48
TtfFontList::TtfFontList() {
49
    FT_Init_FreeType(&fontLibrary);
50
}
51

52
TtfFontList::~TtfFontList() {
53
    FT_Done_FreeType(fontLibrary);
54
}
55

56
void TtfFontList::LoadAll() {
57
    if(loaded) return;
58

59
    for(const Platform::Path &font : Platform::GetFontFiles()) {
60
        TtfFont tf = {};
61
        tf.fontFile = font;
62
        if(tf.LoadFromFile(fontLibrary))
63
            l.Add(&tf);
64
    }
65

66
    // Add builtin font to end of font list so it is displayed first in the UI
67
    {
68
        TtfFont tf = {};
69
        tf.SetResourceID("fonts/BitstreamVeraSans-Roman-builtin.ttf");
70
        if(tf.LoadFromResource(fontLibrary))
71
            l.Add(&tf);
72
    }
73

74
    // Sort fonts according to their actual name, not filename.
75
    std::sort(l.begin(), l.end(),
76
        [](const TtfFont &a, const TtfFont &b) { return a.name < b.name; });
77

78
    // Filter out fonts with the same family and style name. This is not
79
    // strictly necessarily the exact same font, but it will almost always be.
80
    TtfFont *it = std::unique(l.begin(), l.end(),
81
                              [](const TtfFont &a, const TtfFont &b) { return a.name == b.name; });
82
    l.RemoveLast(&l[l.n] - it);
83

84
    //! @todo identify fonts by their name and not filename, which may change
85
    //! between OSes.
86

87
    loaded = true;
88
}
89

90
TtfFont *TtfFontList::LoadFont(const std::string &font)
91
{
92
    LoadAll();
93

94
    TtfFont *tf = std::find_if(l.begin(), l.end(),
95
        [&font](const TtfFont &tf) { return tf.FontFileBaseName() == font; });
96

97
    if(tf != l.end()) {
98
        if(tf->fontFace == NULL) {
99
            if(tf->IsResource())
100
                tf->LoadFromResource(fontLibrary, /*keepOpen=*/true);
101
            else
102
                tf->LoadFromFile(fontLibrary, /*keepOpen=*/true);
103
        }
104
        return tf;
105
    } else {
106
        return NULL;
107
    }
108
}
109

110
void TtfFontList::PlotString(const std::string &font, const std::string &str,
111
                             SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v)
112
{
113
    TtfFont *tf = LoadFont(font);
114
    if(!str.empty() && tf != NULL) {
115
        tf->PlotString(str, sbl, kerning, origin, u, v);
116
    } else {
117
        // No text or no font; so draw a big X for an error marker.
118
        SBezier sb;
119
        sb = SBezier::From(origin, origin.Plus(u).Plus(v));
120
        sbl->l.Add(&sb);
121
        sb = SBezier::From(origin.Plus(v), origin.Plus(u));
122
        sbl->l.Add(&sb);
123
    }
124
}
125

126
double TtfFontList::AspectRatio(const std::string &font, const std::string &str, bool kerning)
127
{
128
    TtfFont *tf = LoadFont(font);
129
    if(tf != NULL) {
130
        return tf->AspectRatio(str, kerning);
131
    }
132

133
    return 0.0;
134
}
135

136
//-----------------------------------------------------------------------------
137
// Return the basename of our font filename; that's how the requests and
138
// entities that reference us will store it.
139
//-----------------------------------------------------------------------------
140
std::string TtfFont::FontFileBaseName() const {
141
    return fontFile.FileName();
142
}
143

144
//-----------------------------------------------------------------------------
145
// Convenience method to set fontFile for resource-loaded fonts as res://<path>
146
//-----------------------------------------------------------------------------
147
void TtfFont::SetResourceID(const std::string &resource) {
148
    fontFile = { "res://" + resource };
149
}
150

151
bool TtfFont::IsResource() const {
152
    return fontFile.raw.compare(0, 6, "res://") == 0;
153
}
154

155
//-----------------------------------------------------------------------------
156
// Load a TrueType font into memory.
157
//-----------------------------------------------------------------------------
158
bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool keepOpen) {
159
    ssassert(!IsResource(), "Cannot load a font provided by a resource as a file.");
160

161
    FT_Open_Args args = {};
162
    args.flags    = FT_OPEN_PATHNAME;
163
    args.pathname = &fontFile.raw[0]; // FT_String is char* for historical reasons
164

165
    // We don't use OpenFile() here to let freetype do its own memory management.
166
    // This is OK because on Linux/OS X we just delegate to fopen and on Windows
167
    // we only look into C:\Windows\Fonts, which has a known short path.
168
    if(int fterr = FT_Open_Face(fontLibrary, &args, 0, &fontFace)) {
169
        dbp("freetype: loading font from file '%s' failed: %s",
170
            fontFile.raw.c_str(), ft_error_string(fterr));
171
        return false;
172
    }
173

174
    return ExtractTTFData(keepOpen);
175
}
176

177
//-----------------------------------------------------------------------------
178
// Load a TrueType from resource in memory. Implemented to load bundled fonts
179
// through theresource system.
180
//-----------------------------------------------------------------------------
181
bool TtfFont::LoadFromResource(FT_Library fontLibrary, bool keepOpen) {
182
    ssassert(IsResource(), "Font to be loaded as resource is not provided by a resource "
183
             "or does not have the 'res://' prefix.");
184

185
    size_t _size;
186
    // substr to cut off 'res://' (length: 6)
187
    const void *_buffer = Platform::LoadResource(fontFile.raw.substr(6, fontFile.raw.size()),
188
                                                 &_size);
189

190
    FT_Long size = static_cast<FT_Long>(_size);
191
    const FT_Byte *buffer = reinterpret_cast<const FT_Byte*>(_buffer);
192

193
    if(int fterr = FT_New_Memory_Face(fontLibrary, buffer, size, 0, &fontFace)) {
194
            dbp("freetype: loading font '%s' from memory failed: %s",
195
                fontFile.raw.c_str(), ft_error_string(fterr));
196
            return false;
197
    }
198

199
    return ExtractTTFData(keepOpen);
200
}
201

202
//-----------------------------------------------------------------------------
203
// Extract font information. We care about the font name and unit size.
204
//-----------------------------------------------------------------------------
205
bool TtfFont::ExtractTTFData(bool keepOpen) {
206
    if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) {
207
        dbp("freetype: loading unicode CMap for file '%s' failed: %s",
208
            fontFile.raw.c_str(), ft_error_string(fterr));
209
        FT_Done_Face(fontFace);
210
        fontFace = NULL;
211
        return false;
212
    }
213

214
    name = std::string(fontFace->family_name) +
215
           " (" + std::string(fontFace->style_name) + ")";
216

217
    // We always ask Freetype to give us a unit size character.
218
    // It uses fixed point; put the unit size somewhere in the middle of the dynamic
219
    // range of its 26.6 fixed point type, and adjust the factors so that the unit
220
    // matches cap height.
221
    FT_Size_RequestRec sizeRequest;
222
    sizeRequest.type           = FT_SIZE_REQUEST_TYPE_REAL_DIM;
223
    sizeRequest.width          = 1 << 16;
224
    sizeRequest.height         = 1 << 16;
225
    sizeRequest.horiResolution = 128;
226
    sizeRequest.vertResolution = 128;
227
    if(int fterr = FT_Request_Size(fontFace, &sizeRequest)) {
228
        dbp("freetype: size request for file '%s' failed: %s",
229
            fontFile.raw.c_str(), ft_error_string(fterr));
230
        FT_Done_Face(fontFace);
231
        fontFace = NULL;
232
        return false;
233
    }
234

235
    char chr = 'A';
236
    uint32_t gid = FT_Get_Char_Index(fontFace, 'A');
237
    if (gid == 0) {
238
        dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; "
239
            "using CID as GID",
240
            chr, fontFile.raw.c_str(), ft_error_string(gid));
241
        dbp("Assuming cap height is the same as requested height (this is likely wrong).");
242
        capHeight = (double)sizeRequest.height;
243
    }
244

245
    if(gid) {
246
        if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) {
247
            dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s",
248
                gid, fontFile.raw.c_str(), ft_error_string(fterr));
249
            FT_Done_Face(fontFace);
250
            fontFace = NULL;
251
            return false;
252
        }
253

254
        FT_BBox bbox;
255
        FT_Outline_Get_CBox(&fontFace->glyph->outline, &bbox);
256
        capHeight = (double)bbox.yMax;
257
    }
258

259
    // If we just wanted to get the font's name and figure out if it's actually usable, close
260
    // it now. If we don't do this, and there are a lot of fonts, we can bump into the file
261
    // descriptor limit (especially on Windows), breaking all file operations.
262
    if(!keepOpen) {
263
        FT_Done_Face(fontFace);
264
        fontFace = NULL;
265
        return true;
266
    }
267

268
    return true;
269
}
270

271
typedef struct OutlineData {
272
    Vector       origin, u, v; // input parameters
273
    SBezierList *beziers;      // output bezier list
274
    float        factor;       // ratio between freetype and solvespace coordinates
275
    FT_Pos       bx;           // x offset of the current glyph
276
    FT_Pos       px, py;       // current point
277
} OutlineData;
278

279
static Vector Transform(OutlineData *data, FT_Pos x, FT_Pos y) {
280
    Vector r = data->origin;
281
    r = r.Plus(data->u.ScaledBy((float)(data->bx + x) * data->factor));
282
    r = r.Plus(data->v.ScaledBy((float)y * data->factor));
283
    return r;
284
}
285

286
static int MoveTo(const FT_Vector *p, void *cc)
287
{
288
    OutlineData *data = (OutlineData *) cc;
289
    data->px = p->x;
290
    data->py = p->y;
291
    return 0;
292
}
293

294
static int LineTo(const FT_Vector *p, void *cc)
295
{
296
    OutlineData *data = (OutlineData *) cc;
297
    SBezier sb = SBezier::From(
298
        Transform(data, data->px, data->py),
299
        Transform(data, p->x,     p->y));
300
    data->beziers->l.Add(&sb);
301
    data->px = p->x;
302
    data->py = p->y;
303
    return 0;
304
}
305

306
static int ConicTo(const FT_Vector *c, const FT_Vector *p, void *cc)
307
{
308
    OutlineData *data = (OutlineData *) cc;
309
    SBezier sb = SBezier::From(
310
        Transform(data, data->px, data->py),
311
        Transform(data, c->x,     c->y),
312
        Transform(data, p->x,     p->y));
313
    data->beziers->l.Add(&sb);
314
    data->px = p->x;
315
    data->py = p->y;
316
    return 0;
317
}
318

319
static int CubicTo(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *p, void *cc)
320
{
321
    OutlineData *data = (OutlineData *) cc;
322
    SBezier sb = SBezier::From(
323
        Transform(data, data->px, data->py),
324
        Transform(data, c1->x,    c1->y),
325
        Transform(data, c2->x,    c2->y),
326
        Transform(data, p->x,     p->y));
327
    data->beziers->l.Add(&sb);
328
    data->px = p->x;
329
    data->py = p->y;
330
    return 0;
331
}
332

333
void TtfFont::PlotString(const std::string &str,
334
                         SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v)
335
{
336
    ssassert(fontFace != NULL, "Expected font face to be loaded");
337

338
    FT_Outline_Funcs outlineFuncs;
339
    outlineFuncs.move_to  = MoveTo;
340
    outlineFuncs.line_to  = LineTo;
341
    outlineFuncs.conic_to = ConicTo;
342
    outlineFuncs.cubic_to = CubicTo;
343
    outlineFuncs.shift    = 0;
344
    outlineFuncs.delta    = 0;
345

346
    FT_Pos dx = 0;
347
    uint32_t prevGid = 0;
348
    for(char32_t cid : ReadUTF8(str)) {
349
        uint32_t gid = FT_Get_Char_Index(fontFace, cid);
350
        if (gid == 0) {
351
            dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; "
352
                "using CID as GID",
353
                cid, fontFile.raw.c_str(), ft_error_string(gid));
354
            gid = cid;
355
        }
356

357
        /*
358
         * Stupid hacks:
359
         *  - if we want fake-bold, use FT_Outline_Embolden(). This actually looks
360
         *    quite good.
361
         *  - if we want fake-italic, apply a shear transform [1 s s 1 0 0] here using
362
         *    FT_Set_Transform. This looks decent at small font sizes and bad at larger
363
         *    ones, antialiasing mitigates this considerably though.
364
         */
365
        if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) {
366
            dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s",
367
                gid, fontFile.raw.c_str(), ft_error_string(fterr));
368
            return;
369
        }
370

371
        /* A point that has x = xMin should be plotted at (dx0 + lsb); fix up
372
         * our x-position so that the curve-generating code will put stuff
373
         * at the right place.
374
         *
375
         * There's no point in getting the glyph BBox here - not only can it be
376
         * needlessly slow sometimes, but because we're about to render a single glyph,
377
         * what we want actually *is* the CBox.
378
         *
379
         * This is notwithstanding that this makes extremely little sense, this
380
         * looks like a workaround for either mishandling the start glyph on a line,
381
         * or as a really hacky pseudo-track-kerning (in which case it works better than
382
         * one would expect! especially since most fonts don't set track kerning).
383
         */
384
        FT_BBox cbox;
385
        FT_Outline_Get_CBox(&fontFace->glyph->outline, &cbox);
386

387
        // Apply Kerning, if any:
388
        FT_Vector kernVector;
389
        if(kerning && FT_Get_Kerning(fontFace, prevGid, gid, FT_KERNING_DEFAULT, &kernVector) == 0) {
390
            dx += kernVector.x;
391
        }
392

393
        FT_Pos bx = dx - cbox.xMin;
394
        // Yes, this is what FreeType calls left-side bearing.
395
        // Then interchangeably uses that with "left-side bearing". Sigh.
396
        bx += fontFace->glyph->metrics.horiBearingX;
397

398
        OutlineData data = {};
399
        data.origin  = origin;
400
        data.u       = u;
401
        data.v       = v;
402
        data.beziers = sbl;
403
        data.factor  = (float)(1.0 / capHeight);
404
        data.bx      = bx;
405
        if(int fterr = FT_Outline_Decompose(&fontFace->glyph->outline, &outlineFuncs, &data)) {
406
            dbp("freetype: bezier decomposition failed for GID 0x%4x in file '%s': %s",
407
                gid, fontFile.raw.c_str(), ft_error_string(fterr));
408
        }
409

410
        // And we're done, so advance our position by the requested advance
411
        // width, plus the user-requested extra advance.
412
        dx += fontFace->glyph->advance.x;
413
        prevGid = gid;
414
    }
415
}
416

417
double TtfFont::AspectRatio(const std::string &str, bool kerning) {
418
    ssassert(fontFace != NULL, "Expected font face to be loaded");
419

420
    // We always request a unit size character, so the aspect ratio is the same as advance length.
421
    double dx = 0;
422
    uint32_t prevGid = 0;
423
    for(char32_t chr : ReadUTF8(str)) {
424
        uint32_t gid = FT_Get_Char_Index(fontFace, chr);
425
        if (gid == 0) {
426
            dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; "
427
                "using CID as GID",
428
                chr, fontFile.raw.c_str(), ft_error_string(gid));
429
        }
430

431
        if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) {
432
            dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s",
433
                gid, fontFile.raw.c_str(), ft_error_string(fterr));
434
            break;
435
        }
436

437
        // Apply Kerning, if any:
438
        FT_Vector kernVector;
439
        if(kerning && FT_Get_Kerning(fontFace, prevGid, gid, FT_KERNING_DEFAULT, &kernVector) == 0) {
440
            dx += (double)kernVector.x / capHeight;
441
        }
442

443
        dx += (double)fontFace->glyph->advance.x / capHeight;
444
        prevGid = gid;
445
    }
446

447
    return dx;
448
}
449

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.