Solvespace
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,
14since 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
20struct ft_error {
21int err;
22const char *str;
23};
24
25static const struct ft_error ft_errors[] = {
26#include FT_ERRORS_H
27};
28
29extern "C" const char *ft_error_string(int err) {
30const struct ft_error *e;
31for(e = ft_errors; e->str; e++)
32if(e->err == err)
33return e->str;
34return "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//-----------------------------------------------------------------------------
48TtfFontList::TtfFontList() {
49FT_Init_FreeType(&fontLibrary);
50}
51
52TtfFontList::~TtfFontList() {
53FT_Done_FreeType(fontLibrary);
54}
55
56void TtfFontList::LoadAll() {
57if(loaded) return;
58
59for(const Platform::Path &font : Platform::GetFontFiles()) {
60TtfFont tf = {};
61tf.fontFile = font;
62if(tf.LoadFromFile(fontLibrary))
63l.Add(&tf);
64}
65
66// Add builtin font to end of font list so it is displayed first in the UI
67{
68TtfFont tf = {};
69tf.SetResourceID("fonts/BitstreamVeraSans-Roman-builtin.ttf");
70if(tf.LoadFromResource(fontLibrary))
71l.Add(&tf);
72}
73
74// Sort fonts according to their actual name, not filename.
75std::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.
80TtfFont *it = std::unique(l.begin(), l.end(),
81[](const TtfFont &a, const TtfFont &b) { return a.name == b.name; });
82l.RemoveLast(&l[l.n] - it);
83
84//! @todo identify fonts by their name and not filename, which may change
85//! between OSes.
86
87loaded = true;
88}
89
90TtfFont *TtfFontList::LoadFont(const std::string &font)
91{
92LoadAll();
93
94TtfFont *tf = std::find_if(l.begin(), l.end(),
95[&font](const TtfFont &tf) { return tf.FontFileBaseName() == font; });
96
97if(tf != l.end()) {
98if(tf->fontFace == NULL) {
99if(tf->IsResource())
100tf->LoadFromResource(fontLibrary, /*keepOpen=*/true);
101else
102tf->LoadFromFile(fontLibrary, /*keepOpen=*/true);
103}
104return tf;
105} else {
106return NULL;
107}
108}
109
110void TtfFontList::PlotString(const std::string &font, const std::string &str,
111SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v)
112{
113TtfFont *tf = LoadFont(font);
114if(!str.empty() && tf != NULL) {
115tf->PlotString(str, sbl, kerning, origin, u, v);
116} else {
117// No text or no font; so draw a big X for an error marker.
118SBezier sb;
119sb = SBezier::From(origin, origin.Plus(u).Plus(v));
120sbl->l.Add(&sb);
121sb = SBezier::From(origin.Plus(v), origin.Plus(u));
122sbl->l.Add(&sb);
123}
124}
125
126double TtfFontList::AspectRatio(const std::string &font, const std::string &str, bool kerning)
127{
128TtfFont *tf = LoadFont(font);
129if(tf != NULL) {
130return tf->AspectRatio(str, kerning);
131}
132
133return 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//-----------------------------------------------------------------------------
140std::string TtfFont::FontFileBaseName() const {
141return fontFile.FileName();
142}
143
144//-----------------------------------------------------------------------------
145// Convenience method to set fontFile for resource-loaded fonts as res://<path>
146//-----------------------------------------------------------------------------
147void TtfFont::SetResourceID(const std::string &resource) {
148fontFile = { "res://" + resource };
149}
150
151bool TtfFont::IsResource() const {
152return fontFile.raw.compare(0, 6, "res://") == 0;
153}
154
155//-----------------------------------------------------------------------------
156// Load a TrueType font into memory.
157//-----------------------------------------------------------------------------
158bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool keepOpen) {
159ssassert(!IsResource(), "Cannot load a font provided by a resource as a file.");
160
161FT_Open_Args args = {};
162args.flags = FT_OPEN_PATHNAME;
163args.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.
168if(int fterr = FT_Open_Face(fontLibrary, &args, 0, &fontFace)) {
169dbp("freetype: loading font from file '%s' failed: %s",
170fontFile.raw.c_str(), ft_error_string(fterr));
171return false;
172}
173
174return ExtractTTFData(keepOpen);
175}
176
177//-----------------------------------------------------------------------------
178// Load a TrueType from resource in memory. Implemented to load bundled fonts
179// through theresource system.
180//-----------------------------------------------------------------------------
181bool TtfFont::LoadFromResource(FT_Library fontLibrary, bool keepOpen) {
182ssassert(IsResource(), "Font to be loaded as resource is not provided by a resource "
183"or does not have the 'res://' prefix.");
184
185size_t _size;
186// substr to cut off 'res://' (length: 6)
187const void *_buffer = Platform::LoadResource(fontFile.raw.substr(6, fontFile.raw.size()),
188&_size);
189
190FT_Long size = static_cast<FT_Long>(_size);
191const FT_Byte *buffer = reinterpret_cast<const FT_Byte*>(_buffer);
192
193if(int fterr = FT_New_Memory_Face(fontLibrary, buffer, size, 0, &fontFace)) {
194dbp("freetype: loading font '%s' from memory failed: %s",
195fontFile.raw.c_str(), ft_error_string(fterr));
196return false;
197}
198
199return ExtractTTFData(keepOpen);
200}
201
202//-----------------------------------------------------------------------------
203// Extract font information. We care about the font name and unit size.
204//-----------------------------------------------------------------------------
205bool TtfFont::ExtractTTFData(bool keepOpen) {
206if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) {
207dbp("freetype: loading unicode CMap for file '%s' failed: %s",
208fontFile.raw.c_str(), ft_error_string(fterr));
209FT_Done_Face(fontFace);
210fontFace = NULL;
211return false;
212}
213
214name = 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.
221FT_Size_RequestRec sizeRequest;
222sizeRequest.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
223sizeRequest.width = 1 << 16;
224sizeRequest.height = 1 << 16;
225sizeRequest.horiResolution = 128;
226sizeRequest.vertResolution = 128;
227if(int fterr = FT_Request_Size(fontFace, &sizeRequest)) {
228dbp("freetype: size request for file '%s' failed: %s",
229fontFile.raw.c_str(), ft_error_string(fterr));
230FT_Done_Face(fontFace);
231fontFace = NULL;
232return false;
233}
234
235char chr = 'A';
236uint32_t gid = FT_Get_Char_Index(fontFace, 'A');
237if (gid == 0) {
238dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; "
239"using CID as GID",
240chr, fontFile.raw.c_str(), ft_error_string(gid));
241dbp("Assuming cap height is the same as requested height (this is likely wrong).");
242capHeight = (double)sizeRequest.height;
243}
244
245if(gid) {
246if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) {
247dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s",
248gid, fontFile.raw.c_str(), ft_error_string(fterr));
249FT_Done_Face(fontFace);
250fontFace = NULL;
251return false;
252}
253
254FT_BBox bbox;
255FT_Outline_Get_CBox(&fontFace->glyph->outline, &bbox);
256capHeight = (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.
262if(!keepOpen) {
263FT_Done_Face(fontFace);
264fontFace = NULL;
265return true;
266}
267
268return true;
269}
270
271typedef struct OutlineData {
272Vector origin, u, v; // input parameters
273SBezierList *beziers; // output bezier list
274float factor; // ratio between freetype and solvespace coordinates
275FT_Pos bx; // x offset of the current glyph
276FT_Pos px, py; // current point
277} OutlineData;
278
279static Vector Transform(OutlineData *data, FT_Pos x, FT_Pos y) {
280Vector r = data->origin;
281r = r.Plus(data->u.ScaledBy((float)(data->bx + x) * data->factor));
282r = r.Plus(data->v.ScaledBy((float)y * data->factor));
283return r;
284}
285
286static int MoveTo(const FT_Vector *p, void *cc)
287{
288OutlineData *data = (OutlineData *) cc;
289data->px = p->x;
290data->py = p->y;
291return 0;
292}
293
294static int LineTo(const FT_Vector *p, void *cc)
295{
296OutlineData *data = (OutlineData *) cc;
297SBezier sb = SBezier::From(
298Transform(data, data->px, data->py),
299Transform(data, p->x, p->y));
300data->beziers->l.Add(&sb);
301data->px = p->x;
302data->py = p->y;
303return 0;
304}
305
306static int ConicTo(const FT_Vector *c, const FT_Vector *p, void *cc)
307{
308OutlineData *data = (OutlineData *) cc;
309SBezier sb = SBezier::From(
310Transform(data, data->px, data->py),
311Transform(data, c->x, c->y),
312Transform(data, p->x, p->y));
313data->beziers->l.Add(&sb);
314data->px = p->x;
315data->py = p->y;
316return 0;
317}
318
319static int CubicTo(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *p, void *cc)
320{
321OutlineData *data = (OutlineData *) cc;
322SBezier sb = SBezier::From(
323Transform(data, data->px, data->py),
324Transform(data, c1->x, c1->y),
325Transform(data, c2->x, c2->y),
326Transform(data, p->x, p->y));
327data->beziers->l.Add(&sb);
328data->px = p->x;
329data->py = p->y;
330return 0;
331}
332
333void TtfFont::PlotString(const std::string &str,
334SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v)
335{
336ssassert(fontFace != NULL, "Expected font face to be loaded");
337
338FT_Outline_Funcs outlineFuncs;
339outlineFuncs.move_to = MoveTo;
340outlineFuncs.line_to = LineTo;
341outlineFuncs.conic_to = ConicTo;
342outlineFuncs.cubic_to = CubicTo;
343outlineFuncs.shift = 0;
344outlineFuncs.delta = 0;
345
346FT_Pos dx = 0;
347uint32_t prevGid = 0;
348for(char32_t cid : ReadUTF8(str)) {
349uint32_t gid = FT_Get_Char_Index(fontFace, cid);
350if (gid == 0) {
351dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; "
352"using CID as GID",
353cid, fontFile.raw.c_str(), ft_error_string(gid));
354gid = 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*/
365if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) {
366dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s",
367gid, fontFile.raw.c_str(), ft_error_string(fterr));
368return;
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*/
384FT_BBox cbox;
385FT_Outline_Get_CBox(&fontFace->glyph->outline, &cbox);
386
387// Apply Kerning, if any:
388FT_Vector kernVector;
389if(kerning && FT_Get_Kerning(fontFace, prevGid, gid, FT_KERNING_DEFAULT, &kernVector) == 0) {
390dx += kernVector.x;
391}
392
393FT_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.
396bx += fontFace->glyph->metrics.horiBearingX;
397
398OutlineData data = {};
399data.origin = origin;
400data.u = u;
401data.v = v;
402data.beziers = sbl;
403data.factor = (float)(1.0 / capHeight);
404data.bx = bx;
405if(int fterr = FT_Outline_Decompose(&fontFace->glyph->outline, &outlineFuncs, &data)) {
406dbp("freetype: bezier decomposition failed for GID 0x%4x in file '%s': %s",
407gid, 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.
412dx += fontFace->glyph->advance.x;
413prevGid = gid;
414}
415}
416
417double TtfFont::AspectRatio(const std::string &str, bool kerning) {
418ssassert(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.
421double dx = 0;
422uint32_t prevGid = 0;
423for(char32_t chr : ReadUTF8(str)) {
424uint32_t gid = FT_Get_Char_Index(fontFace, chr);
425if (gid == 0) {
426dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; "
427"using CID as GID",
428chr, fontFile.raw.c_str(), ft_error_string(gid));
429}
430
431if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) {
432dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s",
433gid, fontFile.raw.c_str(), ft_error_string(fterr));
434break;
435}
436
437// Apply Kerning, if any:
438FT_Vector kernVector;
439if(kerning && FT_Get_Kerning(fontFace, prevGid, gid, FT_KERNING_DEFAULT, &kernVector) == 0) {
440dx += (double)kernVector.x / capHeight;
441}
442
443dx += (double)fontFace->glyph->advance.x / capHeight;
444prevGid = gid;
445}
446
447return dx;
448}
449