summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-10-06 12:16:43 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-10-06 12:16:43 +0300
commit61a3dc017067be43472dadb7909094aa04d1fe9d (patch)
tree18b87895489844b4e516f79cd1588038f1d49494
parentf6a54d5375aab9c41af3f7c8a5e8fcbd1e0c9287 (diff)
Revised runtime font management
The built-in fonts are loaded via FontPack, and the font table is now constructed dynamically based on available fonts. A full set of variants (style, size) are prepared for each font, but some of the data gets allocated lazily when needed. GmRun needed a larger allocation for fonts, so now all the fields are combined into a single bit field. TODO: Glyph scaling, vertical offsets, and symbol lookup are still not fully working.
-rw-r--r--res/fonts/fontpack.ini3
-rw-r--r--src/app.c8
-rw-r--r--src/fontpack.c143
-rw-r--r--src/fontpack.h45
-rw-r--r--src/gmdocument.c103
-rw-r--r--src/gmdocument.h21
-rw-r--r--src/main.c3
-rw-r--r--src/ui/color.h2
-rw-r--r--src/ui/documentwidget.c49
-rw-r--r--src/ui/mediaui.c4
-rw-r--r--src/ui/mobile.c8
-rw-r--r--src/ui/root.c13
-rw-r--r--src/ui/sidebarwidget.c18
-rw-r--r--src/ui/text.c337
-rw-r--r--src/ui/text.h131
-rw-r--r--src/ui/uploadwidget.c2
-rw-r--r--src/ui/util.c10
-rw-r--r--src/ui/window.c4
18 files changed, 568 insertions, 336 deletions
diff --git a/res/fonts/fontpack.ini b/res/fonts/fontpack.ini
index 53bd7402..153b7f86 100644
--- a/res/fonts/fontpack.ini
+++ b/res/fonts/fontpack.ini
@@ -16,6 +16,7 @@ regular = "IosevkaTerm-Extended.ttf"
16 16
17[smolemoji] 17[smolemoji]
18name = "Smol Emoji" 18name = "Smol Emoji"
19override = true # These Emoji are always preferred.
19auxiliary = true 20auxiliary = true
20priority = 100 21priority = 100
21regular = "SmolEmoji-Regular.ttf" 22regular = "SmolEmoji-Regular.ttf"
@@ -32,6 +33,7 @@ name = "Noto Sans Symbols 2"
32auxiliary = true 33auxiliary = true
33priority = 20 34priority = 20
34scaling = 1.45 35scaling = 1.45
36voffset = 1.2
35regular = "NotoSansSymbols2-Regular.ttf" 37regular = "NotoSansSymbols2-Regular.ttf"
36 38
37[notosymbols] 39[notosymbols]
@@ -39,4 +41,5 @@ name = "Noto Sans Symbols"
39auxiliary = true 41auxiliary = true
40priority = 10 42priority = 10
41scaling = 2.0 43scaling = 2.0
44voffset = 0.5
42regular = "NotoSansSymbols-Regular.ttf" 45regular = "NotoSansSymbols-Regular.ttf"
diff --git a/src/app.c b/src/app.c
index 5fc01a5b..3a96bd40 100644
--- a/src/app.c
+++ b/src/app.c
@@ -116,7 +116,7 @@ struct Impl_App {
116 iMimeHooks * mimehooks; 116 iMimeHooks * mimehooks;
117 iGmCerts * certs; 117 iGmCerts * certs;
118 iVisited * visited; 118 iVisited * visited;
119 iBookmarks * bookmarks; 119 iBookmarks * bookmarks;
120 iMainWindow *window; 120 iMainWindow *window;
121 iPtrArray popupWindows; 121 iPtrArray popupWindows;
122 iSortedArray tickers; /* per-frame callbacks, used for animations */ 122 iSortedArray tickers; /* per-frame callbacks, used for animations */
@@ -758,7 +758,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
758 listen_Ipc(); /* We'll respond to commands from other instances. */ 758 listen_Ipc(); /* We'll respond to commands from other instances. */
759 } 759 }
760#endif 760#endif
761 printf("Lagrange: A Beautiful Gemini Client\n"); 761 puts("Lagrange: A Beautiful Gemini Client");
762 const iBool isFirstRun = 762 const iBool isFirstRun =
763 !fileExistsCStr_FileInfo(cleanedPath_CStr(concatPath_CStr(dataDir_App_(), "prefs.cfg"))); 763 !fileExistsCStr_FileInfo(cleanedPath_CStr(concatPath_CStr(dataDir_App_(), "prefs.cfg")));
764 d->isFinishedLaunching = iFalse; 764 d->isFinishedLaunching = iFalse;
@@ -803,6 +803,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
803 setupApplication_iOS(); 803 setupApplication_iOS();
804#endif 804#endif
805 init_Keys(); 805 init_Keys();
806 init_Fonts(dataDir_App_());
806 loadPalette_Color(dataDir_App_()); 807 loadPalette_Color(dataDir_App_());
807 setThemePalette_Color(d->prefs.theme); /* default UI colors */ 808 setThemePalette_Color(d->prefs.theme); /* default UI colors */
808 loadPrefs_App_(d); 809 loadPrefs_App_(d);
@@ -883,6 +884,7 @@ static void deinit_App(iApp *d) {
883 deinit_Feeds(); 884 deinit_Feeds();
884 save_Keys(dataDir_App_()); 885 save_Keys(dataDir_App_());
885 deinit_Keys(); 886 deinit_Keys();
887 deinit_Fonts();
886 deinit_SiteSpec(); 888 deinit_SiteSpec();
887 savePrefs_App_(d); 889 savePrefs_App_(d);
888 deinit_Prefs(&d->prefs); 890 deinit_Prefs(&d->prefs);
@@ -2124,6 +2126,7 @@ iBool handleCommand_App(const char *cmd) {
2124 resetFonts_App_(d); 2126 resetFonts_App_(d);
2125 return iTrue; 2127 return iTrue;
2126 } 2128 }
2129#if 0
2127 else if (equal_Command(cmd, "font.user")) { 2130 else if (equal_Command(cmd, "font.user")) {
2128 const char *path = suffixPtr_Command(cmd, "path"); 2131 const char *path = suffixPtr_Command(cmd, "path");
2129 if (cmp_String(&d->prefs.symbolFontPath, path)) { 2132 if (cmp_String(&d->prefs.symbolFontPath, path)) {
@@ -2140,6 +2143,7 @@ iBool handleCommand_App(const char *cmd) {
2140 } 2143 }
2141 return iTrue; 2144 return iTrue;
2142 } 2145 }
2146#endif
2143 else if (equal_Command(cmd, "font.set")) { 2147 else if (equal_Command(cmd, "font.set")) {
2144 if (!isFrozen) { 2148 if (!isFrozen) {
2145 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); 2149 setFreezeDraw_MainWindow(get_MainWindow(), iTrue);
diff --git a/src/fontpack.c b/src/fontpack.c
index af341363..0ca0d486 100644
--- a/src/fontpack.c
+++ b/src/fontpack.c
@@ -30,11 +30,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include <the_Foundation/string.h> 30#include <the_Foundation/string.h>
31#include <the_Foundation/toml.h> 31#include <the_Foundation/toml.h>
32 32
33float scale_FontSize(enum iFontSize size) {
34 static const float sizes[max_FontSize] = {
35 1.000, /* UI sizes */
36 1.125,
37 1.333,
38 1.666,
39 0.800,
40 0.900,
41 1.000, /* document sizes */
42 1.200,
43 1.333,
44 1.666,
45 2.000,
46 0.568,
47 0.710,
48 0.800,
49 };
50 if (size < 0 || size >= max_FontSize) {
51 return 1.0f;
52 }
53 return sizes[size];
54}
55
33iDeclareType(Fonts) 56iDeclareType(Fonts)
34 57
35struct Impl_Fonts { 58struct Impl_Fonts {
36 iString userDir; 59 iString userDir;
60 iPtrArray packs;
37 iPtrArray files; 61 iPtrArray files;
62 iPtrArray specOrder; /* specs sorted by priority */
38}; 63};
39 64
40static iFonts fonts_; 65static iFonts fonts_;
@@ -55,6 +80,9 @@ void init_FontFile(iFontFile *d) {
55static void load_FontFile_(iFontFile *d, const iBlock *data) { 80static void load_FontFile_(iFontFile *d, const iBlock *data) {
56 set_Block(&d->sourceData, data); 81 set_Block(&d->sourceData, data);
57 stbtt_InitFont(&d->stbInfo, constData_Block(&d->sourceData), 0); 82 stbtt_InitFont(&d->stbInfo, constData_Block(&d->sourceData), 0);
83 /* Basic metrics. */
84 stbtt_GetFontVMetrics(&d->stbInfo, &d->ascent, &d->descent, NULL);
85 stbtt_GetCodepointHMetrics(&d->stbInfo, 'M', &d->emAdvance, NULL);
58#if defined(LAGRANGE_ENABLE_HARFBUZZ) 86#if defined(LAGRANGE_ENABLE_HARFBUZZ)
59 /* HarfBuzz will read the font data. */ 87 /* HarfBuzz will read the font data. */
60 d->hbBlob = hb_blob_create(constData_Block(&d->sourceData), size_Block(&d->sourceData), 88 d->hbBlob = hb_blob_create(constData_Block(&d->sourceData), size_Block(&d->sourceData),
@@ -83,6 +111,23 @@ void deinit_FontFile(iFontFile *d) {
83 deinit_Block(&d->sourceData); 111 deinit_Block(&d->sourceData);
84} 112}
85 113
114float scaleForPixelHeight_FontFile(const iFontFile *d, int pixelHeight) {
115 return stbtt_ScaleForPixelHeight(&d->stbInfo, pixelHeight);
116}
117
118uint8_t *rasterizeGlyph_FontFile(const iFontFile *d, float xScale, float yScale, float xShift,
119 uint32_t glyphIndex, int *w, int *h) {
120 return stbtt_GetGlyphBitmapSubpixel(
121 &d->stbInfo, xScale, yScale, xShift, 0.0f, glyphIndex, w, h, 0, 0);
122}
123
124void measureGlyph_FontFile(const iFontFile *d, uint32_t glyphIndex,
125 float xScale, float yScale, float xShift,
126 int *x0, int *y0, int *x1, int *y1) {
127 stbtt_GetGlyphBitmapBoxSubpixel(
128 &d->stbInfo, glyphIndex, xScale, yScale, xShift, 0.0f, x0, y0, x1, y1);
129}
130
86/*----------------------------------------------------------------------------------------------*/ 131/*----------------------------------------------------------------------------------------------*/
87 132
88 133
@@ -90,7 +135,12 @@ iDefineTypeConstruction(FontSpec)
90 135
91void init_FontSpec(iFontSpec *d) { 136void init_FontSpec(iFontSpec *d) {
92 init_String(&d->id); 137 init_String(&d->id);
93 init_String(&d->name); 138 init_String(&d->name);
139 d->flags = 0;
140 d->priority = 0;
141 d->scaling = 1.0f;
142 d->vertOffset = 1.0f;
143 iZap(d->styles);
94} 144}
95 145
96void deinit_FontSpec(iFontSpec *d) { 146void deinit_FontSpec(iFontSpec *d) {
@@ -124,6 +174,8 @@ void deinit_FontPack(iFontPack *d) {
124 deinit_Array(&d->fonts); 174 deinit_Array(&d->fonts);
125} 175}
126 176
177iDefineTypeConstruction(FontPack)
178
127void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart) { 179void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart) {
128 iFontPack *d = context; 180 iFontPack *d = context;
129 if (isStart) { 181 if (isStart) {
@@ -132,6 +184,24 @@ void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart
132 set_String(&d->loadSpec->id, table); 184 set_String(&d->loadSpec->id, table);
133 } 185 }
134 else { 186 else {
187 /* Set fallback font files. */ {
188 const iFontFile **styles = d->loadSpec->styles;
189 if (!styles[regular_FontStyle]) {
190 fprintf(stderr, "[FontPack] \"%s\" missing a regular style font file\n",
191 cstr_String(table));
192 delete_FontSpec(d->loadSpec);
193 d->loadSpec = NULL;
194 return;
195 }
196 if (!styles[semiBold_FontStyle]) {
197 styles[semiBold_FontStyle] = styles[bold_FontStyle];
198 }
199 for (size_t s = 0; s < max_FontStyle; s++) {
200 if (!styles[s]) {
201 styles[s] = styles[regular_FontStyle];
202 }
203 }
204 }
135 pushBack_Array(&d->fonts, d->loadSpec); 205 pushBack_Array(&d->fonts, d->loadSpec);
136 d->loadSpec = NULL; 206 d->loadSpec = NULL;
137 } 207 }
@@ -151,6 +221,12 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr
151 else if (!cmp_String(key, "scaling")) { 221 else if (!cmp_String(key, "scaling")) {
152 d->loadSpec->scaling = (float) number_TomlValue(value); 222 d->loadSpec->scaling = (float) number_TomlValue(value);
153 } 223 }
224 else if (!cmp_String(key, "voffset")) {
225 d->loadSpec->vertOffset = (float) number_TomlValue(value);
226 }
227 else if (!cmp_String(key, "override") && value->type == boolean_TomlType) {
228 iChangeFlags(d->loadSpec->flags, override_FontSpecFlag, value->value.boolean);
229 }
154 else if (!cmp_String(key, "monospace") && value->type == boolean_TomlType) { 230 else if (!cmp_String(key, "monospace") && value->type == boolean_TomlType) {
155 iChangeFlags(d->loadSpec->flags, monospace_FontSpecFlag, value->value.boolean); 231 iChangeFlags(d->loadSpec->flags, monospace_FontSpecFlag, value->value.boolean);
156 } 232 }
@@ -160,6 +236,10 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr
160 else if (!cmp_String(key, "arabic") && value->type == boolean_TomlType) { 236 else if (!cmp_String(key, "arabic") && value->type == boolean_TomlType) {
161 iChangeFlags(d->loadSpec->flags, arabic_FontSpecFlag, value->value.boolean); 237 iChangeFlags(d->loadSpec->flags, arabic_FontSpecFlag, value->value.boolean);
162 } 238 }
239 else if (!cmp_String(key, "tweaks")) {
240 iChangeFlags(d->loadSpec->flags, fixNunitoKerning_FontSpecFlag,
241 ((int) number_TomlValue(value)) & 1);
242 }
163 else if (value->type == string_TomlType) { 243 else if (value->type == string_TomlType) {
164 const char *styles[max_FontStyle] = { "regular", "italic", "light", "semibold", "bold" }; 244 const char *styles[max_FontStyle] = { "regular", "italic", "light", "semibold", "bold" };
165 iForIndices(i, styles) { 245 iForIndices(i, styles) {
@@ -191,8 +271,10 @@ iBool loadIniFile_FontPack(iFontPack *d, const iString *iniPath) {
191 d->loadPath = collect_String(newRange_String(dirName_Path(iniPath))); 271 d->loadPath = collect_String(newRange_String(dirName_Path(iniPath)));
192 iString *src = collect_String(readString_File(f)); 272 iString *src = collect_String(readString_File(f));
193 iTomlParser *ini = collect_TomlParser(new_TomlParser()); 273 iTomlParser *ini = collect_TomlParser(new_TomlParser());
194 setHandlers_TomlParser(ini, 0, 0, d); 274 setHandlers_TomlParser(ini, handleIniTable_FontPack_, handleIniKeyValue_FontPack_, d);
195 parse_TomlParser(ini, src); 275 if (!parse_TomlParser(ini, src)) {
276 fprintf(stderr, "[FontPack] error parsing %s\n", cstr_String(iniPath));
277 }
196 iAssert(d->loadSpec == NULL); 278 iAssert(d->loadSpec == NULL);
197 d->loadPath = NULL; 279 d->loadPath = NULL;
198 ok = iTrue; 280 ok = iTrue;
@@ -204,23 +286,74 @@ iBool loadIniFile_FontPack(iFontPack *d, const iString *iniPath) {
204/*----------------------------------------------------------------------------------------------*/ 286/*----------------------------------------------------------------------------------------------*/
205 287
206static void unloadFiles_Fonts_(iFonts *d) { 288static void unloadFiles_Fonts_(iFonts *d) {
289 /* TODO: Mark all files in font packs as not resident. */
207 iForEach(PtrArray, i, &d->files) { 290 iForEach(PtrArray, i, &d->files) {
208 delete_FontFile(i.ptr); 291 delete_FontFile(i.ptr);
209 } 292 }
210 clear_PtrArray(&d->files); 293 clear_PtrArray(&d->files);
211} 294}
212 295
296static void unloadFonts_Fonts_(iFonts *d) {
297 iForEach(PtrArray, i, &d->packs) {
298 iFontPack *pack = i.ptr;
299 delete_FontPack(pack);
300 }
301 clear_PtrArray(&d->packs);
302}
303
304static int cmpPriority_FontSpecPtr_(const void *a, const void *b) {
305 const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b;
306 return -iCmp((*p1)->priority, (*p2)->priority); /* highest priority first */
307}
308
309static void sortSpecs_Fonts_(iFonts *d) {
310 clear_PtrArray(&d->specOrder);
311 iConstForEach(PtrArray, p, &d->packs) {
312 const iFontPack *pack = p.ptr;
313 iConstForEach(Array, i, &pack->fonts) {
314 pushBack_PtrArray(&d->specOrder, i.value);
315 }
316 }
317 sort_Array(&d->specOrder, cmpPriority_FontSpecPtr_);
318}
319
213void init_Fonts(const char *userDir) { 320void init_Fonts(const char *userDir) {
214 iFonts *d = &fonts_; 321 iFonts *d = &fonts_;
215 initCStr_String(&d->userDir, userDir); 322 initCStr_String(&d->userDir, userDir);
323 init_PtrArray(&d->packs);
216 init_PtrArray(&d->files); 324 init_PtrArray(&d->files);
325 init_PtrArray(&d->specOrder);
217 /* Load the required fonts. */ 326 /* Load the required fonts. */
218 327 iFontPack *pack = new_FontPack();
328 /* TODO: put default.fontpack in resources.lgr as a binary blob (uncompressed) */
329 /* TODO: find and load .fontpack files in known locations */
330 loadIniFile_FontPack(pack, collectNewCStr_String("/Users/jaakko/src/lagrange/"
331 "res/fonts/fontpack.ini"));
332 pushBack_PtrArray(&d->packs, pack);
333 sortSpecs_Fonts_(d);
219} 334}
220 335
221void deinit_Fonts(void) { 336void deinit_Fonts(void) {
222 iFonts *d = &fonts_; 337 iFonts *d = &fonts_;
338 unloadFonts_Fonts_(d);
223 unloadFiles_Fonts_(d); 339 unloadFiles_Fonts_(d);
340 deinit_PtrArray(&d->specOrder);
341 deinit_PtrArray(&d->packs);
224 deinit_PtrArray(&d->files); 342 deinit_PtrArray(&d->files);
225 deinit_String(&d->userDir); 343 deinit_String(&d->userDir);
226} 344}
345
346const iFontSpec *findSpec_Fonts(const char *fontId) {
347 iFonts *d = &fonts_;
348 iConstForEach(PtrArray, i, &d->specOrder) {
349 const iFontSpec *spec = i.ptr;
350 if (!cmp_String(&spec->id, fontId)) {
351 return spec;
352 }
353 }
354 return NULL;
355}
356
357const iPtrArray *listSpecsByPriority_Fonts(void) {
358 return &fonts_.specOrder;
359}
diff --git a/src/fontpack.h b/src/fontpack.h
index 7e78071f..c5aa993b 100644
--- a/src/fontpack.h
+++ b/src/fontpack.h
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
23#pragma once 23#pragma once
24 24
25#include <the_Foundation/archive.h> 25#include <the_Foundation/archive.h>
26#include <the_Foundation/ptrarray.h>
26#include "stb_truetype.h" 27#include "stb_truetype.h"
27 28
28#if defined (LAGRANGE_ENABLE_HARFBUZZ) 29#if defined (LAGRANGE_ENABLE_HARFBUZZ)
@@ -38,12 +39,12 @@ The user may install new fontpacks via the GUI. The user's fontpacks are stored
38the config directory. There may also be fontpacks available from system-wide locations. */ 39the config directory. There may also be fontpacks available from system-wide locations. */
39 40
40enum iFontSize { 41enum iFontSize {
41 uiTiny_FontSize, /* 0.800 */ 42 uiNormal_FontSize, /* 1.000 -- keep at index 0 for convenience */
42 uiSmall_FontSize, /* 0.900 */
43 uiNormal_FontSize, /* 1.000 */
44 uiMedium_FontSize, /* 1.125 */ 43 uiMedium_FontSize, /* 1.125 */
45 uiBig_FontSize, /* 1.333 */ 44 uiBig_FontSize, /* 1.333 */
46 uiLarge_FontSize, /* 1.666 */ 45 uiLarge_FontSize, /* 1.666 */
46 uiTiny_FontSize, /* 0.800 */
47 uiSmall_FontSize, /* 0.900 */
47 contentRegular_FontSize, 48 contentRegular_FontSize,
48 contentMedium_FontSize, 49 contentMedium_FontSize,
49 contentBig_FontSize, 50 contentBig_FontSize,
@@ -51,6 +52,7 @@ enum iFontSize {
51 contentHuge_FontSize, 52 contentHuge_FontSize,
52 contentMonoSmall_FontSize, 53 contentMonoSmall_FontSize,
53 contentMono_FontSize, 54 contentMono_FontSize,
55 contentSmall_FontSize,
54 max_FontSize 56 max_FontSize
55}; 57};
56 58
@@ -60,23 +62,22 @@ enum iFontStyle {
60 light_FontStyle, 62 light_FontStyle,
61 semiBold_FontStyle, 63 semiBold_FontStyle,
62 bold_FontStyle, 64 bold_FontStyle,
63 max_FontStyle 65 max_FontStyle,
66 /* all permutations: */
67 maxVariants_Fonts = max_FontStyle * max_FontSize
64}; 68};
65 69
66iLocalDef enum iFontSize larger_FontSize(enum iFontSize size) { 70float scale_FontSize (enum iFontSize size);
67 if (size == uiLarge_FontSize || size == contentHuge_FontSize || size == contentMono_FontSize) {
68 return size; /* largest available */
69 }
70 return size + 1;
71}
72 71
73iDeclareType(FontSpec) 72iDeclareType(FontSpec)
74iDeclareTypeConstruction(FontSpec) 73iDeclareTypeConstruction(FontSpec)
75 74
76enum iFontSpecFlags { 75enum iFontSpecFlags {
77 monospace_FontSpecFlag = iBit(1), /* can be used in preformatted content */ 76 override_FontSpecFlag = iBit(1),
78 auxiliary_FontSpecFlag = iBit(2), /* only used for looking up glyphs missing from other fonts */ 77 monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */
79 arabic_FontSpecFlag = iBit(3), 78 auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */
79 arabic_FontSpecFlag = iBit(4),
80 fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */
80}; 81};
81 82
82iDeclareType(FontFile) 83iDeclareType(FontFile)
@@ -91,17 +92,33 @@ struct Impl_FontFile {
91 hb_face_t *hbFace; 92 hb_face_t *hbFace;
92 hb_font_t *hbFont; 93 hb_font_t *hbFont;
93#endif 94#endif
95 /* Metrics: */
96 int ascent, descent, emAdvance;
94}; 97};
95 98
99float scaleForPixelHeight_FontFile (const iFontFile *, int pixelHeight);
100
101iLocalDef uint32_t findGlyphIndex_FontFile(const iFontFile *d, iChar ch) {
102 return stbtt_FindGlyphIndex(&d->stbInfo, ch);
103}
104
105uint8_t * rasterizeGlyph_FontFile(const iFontFile *, float xScale, float yScale, float xShift,
106 uint32_t glyphIndex, int *w, int *h); /* caller must free() the returned bitmap */
107void measureGlyph_FontFile (const iFontFile *, uint32_t glyphIndex,
108 float xScale, float yScale, float xShift,
109 int *x0, int *y0, int *x1, int *y1);
96struct Impl_FontSpec { 110struct Impl_FontSpec {
97 iString id; /* unique ID */ 111 iString id; /* unique ID */
98 iString name; /* human-readable label */ 112 iString name; /* human-readable label */
99 int flags; 113 int flags;
100 int priority; 114 int priority;
101 float scaling; 115 float scaling;
102 const iFontFile *styles[max_FontSize]; 116 float vertOffset;
117 const iFontFile *styles[max_FontStyle];
103}; 118};
104 119
105void init_Fonts (const char *userDir); 120void init_Fonts (const char *userDir);
106void deinit_Fonts (void); 121void deinit_Fonts (void);
107 122
123const iFontSpec * findSpec_Fonts (const char *fontId);
124const iPtrArray * listSpecsByPriority_Fonts (void);
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 2f4c7972..ce9fdec8 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -355,7 +355,7 @@ static enum iGmDocumentTheme currentTheme_(void) {
355} 355}
356 356
357static void alignDecoration_GmRun_(iGmRun *run, iBool isCentered) { 357static void alignDecoration_GmRun_(iGmRun *run, iBool isCentered) {
358 const iRect visBounds = visualBounds_Text(run->textParams.font, run->text); 358 const iRect visBounds = visualBounds_Text(run->font, run->text);
359 const int visWidth = width_Rect(visBounds); 359 const int visWidth = width_Rect(visBounds);
360 int xAdjust = 0; 360 int xAdjust = 0;
361 if (!isCentered) { 361 if (!isCentered) {
@@ -438,7 +438,7 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange,
438 trimEnd_Rangecc(&wrapRange); 438 trimEnd_Rangecc(&wrapRange);
439// printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); 439// printf("typeset: {%s}\n", cstr_Rangecc(wrapRange));
440 iRunTypesetter *d = wrap->context; 440 iRunTypesetter *d = wrap->context;
441 const int fontId = d->run.textParams.font; 441 const int fontId = d->run.font;
442 d->run.text = wrapRange; 442 d->run.text = wrapRange;
443 if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) { 443 if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) {
444 d->pos.y -= d->lineHeightReduction * lineHeight_Text(fontId); 444 d->pos.y -= d->lineHeightReduction * lineHeight_Text(fontId);
@@ -450,7 +450,7 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange,
450 d->run.bounds.size.y = dims.y; 450 d->run.bounds.size.y = dims.y;
451 d->run.visBounds = d->run.bounds; 451 d->run.visBounds = d->run.bounds;
452 d->run.visBounds.size.x = dims.x; 452 d->run.visBounds.size.x = dims.x;
453 d->run.textParams.isRTL = isBaseRTL; 453 d->run.isRTL = isBaseRTL;
454 pushBack_Array(&d->layout, &d->run); 454 pushBack_Array(&d->layout, &d->run);
455 d->run.flags &= ~startOfLine_GmRunFlag; 455 d->run.flags &= ~startOfLine_GmRunFlag;
456 d->pos.y += lineHeight_Text(fontId) * prefs_App()->lineSpacing; 456 d->pos.y += lineHeight_Text(fontId) * prefs_App()->lineSpacing;
@@ -543,7 +543,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
543 if (*line.end == '\r') { 543 if (*line.end == '\r') {
544 line.end--; /* trim CR always */ 544 line.end--; /* trim CR always */
545 } 545 }
546 iGmRun run = { .textParams = { .color = white_ColorId } }; 546 iGmRun run = { .color = white_ColorId };
547 enum iGmLineType type; 547 enum iGmLineType type;
548 float indent = 0.0f; 548 float indent = 0.0f;
549 /* Detect the type of the line. */ 549 /* Detect the type of the line. */
@@ -581,14 +581,16 @@ static void doLayout_GmDocument_(iGmDocument *d) {
581 continue; 581 continue;
582 } 582 }
583 else if (type == link_GmLineType) { 583 else if (type == link_GmLineType) {
584 line = addLink_GmDocument_(d, line, &run.linkId); 584 iGmLinkId linkId;
585 line = addLink_GmDocument_(d, line, &linkId);
586 run.linkId = linkId;
585 if (!run.linkId) { 587 if (!run.linkId) {
586 /* Invalid formatting. */ 588 /* Invalid formatting. */
587 type = text_GmLineType; 589 type = text_GmLineType;
588 } 590 }
589 } 591 }
590 trimLine_Rangecc(&line, type, isNormalized); 592 trimLine_Rangecc(&line, type, isNormalized);
591 run.textParams.font = fonts[type]; 593 run.font = fonts[type];
592 /* Remember headings for the document outline. */ 594 /* Remember headings for the document outline. */
593 if (type == heading1_GmLineType || type == heading2_GmLineType || type == heading3_GmLineType) { 595 if (type == heading1_GmLineType || type == heading2_GmLineType || type == heading3_GmLineType) {
594 pushBack_Array( 596 pushBack_Array(
@@ -609,7 +611,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
609 continue; 611 continue;
610 } 612 }
611 run.preId = preId; 613 run.preId = preId;
612 run.textParams.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont); 614 run.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont);
613 indent = indents[type]; 615 indent = indents[type];
614 } 616 }
615 if (addSiteBanner) { 617 if (addSiteBanner) {
@@ -624,9 +626,9 @@ static void doLayout_GmDocument_(iGmDocument *d) {
624 banner.visBounds.size.y += iMaxi(6000 * lineHeight_Text(uiLabel_FontId) / 626 banner.visBounds.size.y += iMaxi(6000 * lineHeight_Text(uiLabel_FontId) /
625 d->size.x, lineHeight_Text(uiLabel_FontId) * 5); 627 d->size.x, lineHeight_Text(uiLabel_FontId) * 5);
626 } 628 }
627 banner.text = bannerText; 629 banner.text = bannerText;
628 banner.textParams.font = banner_FontId; 630 banner.font = banner_FontId;
629 banner.textParams.color = tmBannerTitle_ColorId; 631 banner.color = tmBannerTitle_ColorId;
630 pushBack_Array(&d->layout, &banner); 632 pushBack_Array(&d->layout, &banner);
631 pos.y += height_Rect(banner.visBounds) + 633 pos.y += height_Rect(banner.visBounds) +
632 1.5f * lineHeight_Text(paragraph_FontId) * prefs->lineSpacing; 634 1.5f * lineHeight_Text(paragraph_FontId) * prefs->lineSpacing;
@@ -637,13 +639,13 @@ static void doLayout_GmDocument_(iGmDocument *d) {
637 if (type == quote_GmLineType && !prefs->quoteIcon) { 639 if (type == quote_GmLineType && !prefs->quoteIcon) {
638 /* For quote indicators we still need to produce a run. */ 640 /* For quote indicators we still need to produce a run. */
639 run.visBounds.pos = addX_I2(pos, indents[type] * gap_Text); 641 run.visBounds.pos = addX_I2(pos, indents[type] * gap_Text);
640 run.visBounds.size = init_I2(gap_Text, lineHeight_Text(run.textParams.font)); 642 run.visBounds.size = init_I2(gap_Text, lineHeight_Text(run.font));
641 run.bounds = zero_Rect(); /* just visual */ 643 run.bounds = zero_Rect(); /* just visual */
642 run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag;
643 run.text = iNullRange; 644 run.text = iNullRange;
645 run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag;
644 pushBack_Array(&d->layout, &run); 646 pushBack_Array(&d->layout, &run);
645 } 647 }
646 pos.y += lineHeight_Text(run.textParams.font) * prefs->lineSpacing; 648 pos.y += lineHeight_Text(run.font) * prefs->lineSpacing;
647 prevType = type; 649 prevType = type;
648 if (type != quote_GmLineType) { 650 if (type != quote_GmLineType) {
649 addQuoteIcon = prefs->quoteIcon; 651 addQuoteIcon = prefs->quoteIcon;
@@ -687,14 +689,14 @@ static void doLayout_GmDocument_(iGmDocument *d) {
687 const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); 689 const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1);
688 if (meta->flags & folded_GmPreMetaFlag) { 690 if (meta->flags & folded_GmPreMetaFlag) {
689 const iBool isBlank = isEmpty_Range(&meta->altText); 691 const iBool isBlank = isEmpty_Range(&meta->altText);
690 iGmRun altText = { 692 iGmRun altText = { .font = paragraph_FontId,
691 .textParams = { .font = paragraph_FontId, .color = tmQuote_ColorId }, 693 .color = tmQuote_ColorId,
692 .flags = (isBlank ? decoration_GmRunFlag : 0) | altText_GmRunFlag 694 .flags = (isBlank ? decoration_GmRunFlag : 0) | altText_GmRunFlag
693 }; 695 };
694 const iInt2 margin = preRunMargin_GmDocument(d, 0); 696 const iInt2 margin = preRunMargin_GmDocument(d, 0);
695 altText.text = isBlank ? range_Lang(range_CStr("doc.pre.nocaption")) 697 altText.text = isBlank ? range_Lang(range_CStr("doc.pre.nocaption"))
696 : meta->altText; 698 : meta->altText;
697 iInt2 size = measureWrapRange_Text(altText.textParams.font, d->size.x - 2 * margin.x, 699 iInt2 size = measureWrapRange_Text(altText.font, d->size.x - 2 * margin.x,
698 altText.text).bounds.size; 700 altText.text).bounds.size;
699 altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, 701 altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x,
700 size.y + 2 * margin.y); 702 size.y + 2 * margin.y);
@@ -713,19 +715,20 @@ static void doLayout_GmDocument_(iGmDocument *d) {
713 setRange_String(&d->title, line); 715 setRange_String(&d->title, line);
714 } 716 }
715 /* List bullet. */ 717 /* List bullet. */
716 run.textParams.color = colors[type]; 718 run.color = colors[type];
717 if (type == bullet_GmLineType) { 719 if (type == bullet_GmLineType) {
718 /* TODO: Literata bullet is broken? */ 720 /* TODO: Literata bullet is broken? */
719 iGmRun bulRun = run; 721 iGmRun bulRun = run;
720 if (prefs->font == literata_TextFont) { 722 if (prefs->font == literata_TextFont) {
721 /* Something wrong this the glyph in Literata, looks cropped. */ 723 /* Something wrong this the glyph in Literata, looks cropped. */
722 bulRun.textParams.font = defaultContentRegular_FontId; 724 bulRun.font = FONT_ID(default_FontId, regular_FontStyle,
725 contentRegular_FontSize);
723 } 726 }
724 bulRun.textParams.color = tmQuote_ColorId; 727 bulRun.color = tmQuote_ColorId;
725 bulRun.visBounds.pos = addX_I2(pos, (indents[text_GmLineType] - 0.55f) * gap_Text); 728 bulRun.visBounds.pos = addX_I2(pos, (indents[text_GmLineType] - 0.55f) * gap_Text);
726 bulRun.visBounds.size = 729 bulRun.visBounds.size =
727 init_I2((indents[bullet_GmLineType] - indents[text_GmLineType]) * gap_Text, 730 init_I2((indents[bullet_GmLineType] - indents[text_GmLineType]) * gap_Text,
728 lineHeight_Text(bulRun.textParams.font)); 731 lineHeight_Text(bulRun.font));
729 // bulRun.visBounds.pos.x -= 4 * gap_Text - width_Rect(bulRun.visBounds) / 2; 732 // bulRun.visBounds.pos.x -= 4 * gap_Text - width_Rect(bulRun.visBounds) / 2;
730 bulRun.bounds = zero_Rect(); /* just visual */ 733 bulRun.bounds = zero_Rect(); /* just visual */
731 bulRun.text = range_CStr(bullet); 734 bulRun.text = range_CStr(bullet);
@@ -737,11 +740,11 @@ static void doLayout_GmDocument_(iGmDocument *d) {
737 if (type == quote_GmLineType && addQuoteIcon) { 740 if (type == quote_GmLineType && addQuoteIcon) {
738 addQuoteIcon = iFalse; 741 addQuoteIcon = iFalse;
739 iGmRun quoteRun = run; 742 iGmRun quoteRun = run;
740 quoteRun.textParams.font = heading1_FontId; 743 quoteRun.font = heading1_FontId;
741 quoteRun.text = range_CStr(quote); 744 quoteRun.text = range_CStr(quote);
742 quoteRun.textParams.color = tmQuoteIcon_ColorId; 745 quoteRun.color = tmQuoteIcon_ColorId;
743 iRect vis = visualBounds_Text(quoteRun.textParams.font, quoteRun.text); 746 iRect vis = visualBounds_Text(quoteRun.font, quoteRun.text);
744 quoteRun.visBounds.size = measure_Text(quoteRun.textParams.font, quote).bounds.size; 747 quoteRun.visBounds.size = measure_Text(quoteRun.font, quote).bounds.size;
745 quoteRun.visBounds.pos = 748 quoteRun.visBounds.pos =
746 add_I2(pos, 749 add_I2(pos,
747 init_I2((indents[quote_GmLineType] - 5) * gap_Text, 750 init_I2((indents[quote_GmLineType] - 5) * gap_Text,
@@ -757,7 +760,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
757 if (type == link_GmLineType) { 760 if (type == link_GmLineType) {
758 iGmRun icon = run; 761 iGmRun icon = run;
759 icon.visBounds.pos = pos; 762 icon.visBounds.pos = pos;
760 icon.visBounds.size = init_I2(indent * gap_Text, lineHeight_Text(run.textParams.font)); 763 icon.visBounds.size = init_I2(indent * gap_Text, lineHeight_Text(run.font));
761 icon.bounds = zero_Rect(); /* just visual */ 764 icon.bounds = zero_Rect(); /* just visual */
762 const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); 765 const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1);
763 const enum iGmLinkScheme scheme = scheme_GmLinkFlag(link->flags); 766 const enum iGmLinkScheme scheme = scheme_GmLinkFlag(link->flags);
@@ -775,23 +778,23 @@ static void doLayout_GmDocument_(iGmDocument *d) {
775 } 778 }
776 /* TODO: List bullets needs the same centering logic. */ 779 /* TODO: List bullets needs the same centering logic. */
777 /* Special exception for the tiny bullet operator. */ 780 /* Special exception for the tiny bullet operator. */
778 icon.textParams.font = equal_Rangecc(link->labelIcon, "\u2219") ? regularMonospace_FontId 781 icon.font = equal_Rangecc(link->labelIcon, "\u2219") ? regularMonospace_FontId
779 : regular_FontId; 782 : paragraph_FontId;
780 alignDecoration_GmRun_(&icon, iFalse); 783 alignDecoration_GmRun_(&icon, iFalse);
781 icon.textParams.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart); 784 icon.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart);
782 icon.flags |= decoration_GmRunFlag; 785 icon.flags |= decoration_GmRunFlag;
783 pushBack_Array(&d->layout, &icon); 786 pushBack_Array(&d->layout, &icon);
784 } 787 }
785 run.textParams.color = colors[type]; 788 run.color = colors[type];
786 if (d->format == plainText_SourceFormat) { 789 if (d->format == plainText_SourceFormat) {
787 run.textParams.color = colors[text_GmLineType]; 790 run.color = colors[text_GmLineType];
788 } 791 }
789 /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ 792 /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */
790// int bigCount = 0; 793// int bigCount = 0;
791 iBool isLedeParagraph = iFalse; 794 iBool isLedeParagraph = iFalse;
792 if (type == text_GmLineType && isFirstText) { 795 if (type == text_GmLineType && isFirstText) {
793 if (!isMono) run.textParams.font = firstParagraph_FontId; 796 if (!isMono) run.font = firstParagraph_FontId;
794 run.textParams.color = tmFirstParagraph_ColorId; 797 run.color = tmFirstParagraph_ColorId;
795// bigCount = 15; /* max lines -- what if the whole document is one paragraph? */ 798// bigCount = 15; /* max lines -- what if the whole document is one paragraph? */
796 isLedeParagraph = iTrue; 799 isLedeParagraph = iTrue;
797 isFirstText = iFalse; 800 isFirstText = iFalse;
@@ -838,7 +841,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
838 } 841 }
839 /* Visited links are never bold. */ 842 /* Visited links are never bold. */
840 if (run.linkId && linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { 843 if (run.linkId && linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) {
841 rts.run.textParams.font = paragraph_FontId; 844 rts.run.font = paragraph_FontId;
842 } 845 }
843 } 846 }
844 if (!prefs->quoteIcon && type == quote_GmLineType) { 847 if (!prefs->quoteIcon && type == quote_GmLineType) {
@@ -854,7 +857,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
854 .mode = word_WrapTextMode, 857 .mode = word_WrapTextMode,
855 .wrapFunc = typesetOneLine_RunTypesetter_, 858 .wrapFunc = typesetOneLine_RunTypesetter_,
856 .context = &rts }; 859 .context = &rts };
857 measure_WrapText(&wrapText, rts.run.textParams.font); 860 measure_WrapText(&wrapText, rts.run.font);
858 if (!isLedeParagraph || size_Array(&rts.layout) <= maxLedeLines_) { 861 if (!isLedeParagraph || size_Array(&rts.layout) <= maxLedeLines_) {
859 if (wrapText.baseDir < 0) { 862 if (wrapText.baseDir < 0) {
860 /* Right-aligned paragraphs need margins and decorations to be flipped. */ 863 /* Right-aligned paragraphs need margins and decorations to be flipped. */
@@ -880,8 +883,8 @@ static void doLayout_GmDocument_(iGmDocument *d) {
880 } 883 }
881 clear_RunTypesetter_(&rts); 884 clear_RunTypesetter_(&rts);
882 rts.pos = pos; 885 rts.pos = pos;
883 rts.run.textParams.font = rts.fonts[text_GmLineType]; 886 rts.run.font = rts.fonts[text_GmLineType];
884 rts.run.textParams.color = colors[text_GmLineType]; 887 rts.run.color = colors[text_GmLineType];
885 isLedeParagraph = iFalse; 888 isLedeParagraph = iFalse;
886 } 889 }
887 pos = rts.pos; 890 pos = rts.pos;
@@ -922,11 +925,11 @@ static void doLayout_GmDocument_(iGmDocument *d) {
922 run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2; 925 run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2;
923 run.bounds.size.y = run.visBounds.size.y; 926 run.bounds.size.y = run.visBounds.size.y;
924 } 927 }
925 run.text = iNullRange; 928 run.text = iNullRange;
926 run.textParams.font = 0; 929 run.font = 0;
927 run.textParams.color = 0; 930 run.color = 0;
928 run.mediaType = image_GmRunMediaType; 931 run.mediaType = image_GmRunMediaType;
929 run.mediaId = imageId; 932 run.mediaId = imageId;
930 pushBack_Array(&d->layout, &run); 933 pushBack_Array(&d->layout, &run);
931 pos.y += run.bounds.size.y + margin; 934 pos.y += run.bounds.size.y + margin;
932 } 935 }
@@ -941,9 +944,9 @@ static void doLayout_GmDocument_(iGmDocument *d) {
941 run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI; 944 run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI;
942 run.visBounds = run.bounds; 945 run.visBounds = run.bounds;
943 run.text = iNullRange; 946 run.text = iNullRange;
944 run.textParams.color = 0; 947 run.color = 0;
945 run.mediaType = audio_GmRunMediaType; 948 run.mediaType = audio_GmRunMediaType;
946 run.mediaId = audioId; 949 run.mediaId = audioId;
947 pushBack_Array(&d->layout, &run); 950 pushBack_Array(&d->layout, &run);
948 pos.y += run.bounds.size.y + margin; 951 pos.y += run.bounds.size.y + margin;
949 } 952 }
@@ -958,7 +961,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
958 run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI; 961 run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI;
959 run.visBounds = run.bounds; 962 run.visBounds = run.bounds;
960 run.text = iNullRange; 963 run.text = iNullRange;
961 run.textParams.color = 0; 964 run.color = 0;
962 run.mediaType = download_GmRunMediaType; 965 run.mediaType = download_GmRunMediaType;
963 run.mediaId = downloadId; 966 run.mediaId = downloadId;
964 pushBack_Array(&d->layout, &run); 967 pushBack_Array(&d->layout, &run);
@@ -1572,11 +1575,11 @@ static void markLinkRunsVisited_GmDocument_(iGmDocument *d, const iIntSet *linkI
1572 iForEach(Array, r, &d->layout) { 1575 iForEach(Array, r, &d->layout) {
1573 iGmRun *run = r.value; 1576 iGmRun *run = r.value;
1574 if (run->linkId && !run->mediaId && contains_IntSet(linkIds, run->linkId)) { 1577 if (run->linkId && !run->mediaId && contains_IntSet(linkIds, run->linkId)) {
1575 if (run->textParams.font == bold_FontId) { 1578 if (run->font == bold_FontId) {
1576 run->textParams.font = paragraph_FontId; 1579 run->font = paragraph_FontId;
1577 } 1580 }
1578 else if (run->flags & decoration_GmRunFlag) { 1581 else if (run->flags & decoration_GmRunFlag) {
1579 run->textParams.color = linkColor_GmDocument(d, run->linkId, icon_GmLinkPart); 1582 run->color = linkColor_GmDocument(d, run->linkId, icon_GmLinkPart);
1580 } 1583 }
1581 } 1584 }
1582 } 1585 }
@@ -2072,7 +2075,7 @@ iRangecc findLoc_GmRun(const iGmRun *d, iInt2 pos) {
2072 return (iRangecc){ d->text.start, d->text.start }; 2075 return (iRangecc){ d->text.start, d->text.start };
2073 } 2076 }
2074 iRangecc loc; 2077 iRangecc loc;
2075 tryAdvanceNoWrap_Text(d->textParams.font, d->text, x, &loc.start); 2078 tryAdvanceNoWrap_Text(d->font, d->text, x, &loc.start);
2076 loc.end = loc.start; 2079 loc.end = loc.start;
2077 if (!contains_Range(&d->text, loc.start)) { 2080 if (!contains_Range(&d->text, loc.start)) {
2078 return iNullRange; /* it's some other text */ 2081 return iNullRange; /* it's some other text */
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 332c3e00..b2c6d9b7 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -133,20 +133,23 @@ enum iGmRunMediaType {
133 download_GmRunMediaType, 133 download_GmRunMediaType,
134}; 134};
135 135
136/* This structure is tightly packed because GmDocuments are mostly composed of
137 a large number of GmRuns. */
136struct Impl_GmRun { 138struct Impl_GmRun {
137 iRangecc text; 139 iRangecc text;
138 iRect bounds; /* used for hit testing, may extend to edges */ 140 iRect bounds; /* used for hit testing, may extend to edges */
139 iRect visBounds; /* actual visual bounds */ 141 iRect visBounds; /* actual visual bounds */
140 struct { 142 struct {
141 uint16_t color : 8; 143 uint32_t linkId : 16; /* GmLinkId; zero for non-links */
142 uint16_t font : 7; 144 uint32_t flags : 8; /* GmRunFlags */
143 uint16_t isRTL : 1; 145 uint32_t isRTL : 1;
144 } textParams; 146 uint32_t color : 7; /* see max_ColorId */
145 uint8_t flags; 147
146 uint8_t mediaType; 148 uint32_t font : 10;
147 uint16_t preId; /* preformatted block ID (sequential) */ 149 uint32_t mediaType : 2;
148 iGmLinkId linkId; /* zero for non-links */ 150 uint32_t mediaId : 10; /* zero if not an image */
149 uint16_t mediaId; /* zero if not an image */ 151 uint32_t preId : 10; /* preformatted block ID (sequential); merge with mediaId? */
152 };
150}; 153};
151 154
152iDeclareType(GmRunRange) 155iDeclareType(GmRunRange)
diff --git a/src/main.c b/src/main.c
index 61320e79..146f13f2 100644
--- a/src/main.c
+++ b/src/main.c
@@ -68,6 +68,9 @@ int main(int argc, char **argv) {
68 SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); 68 SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
69 SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1"); 69 SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
70 SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); 70 SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
71#if 0
72 SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1"); /* debugging! */
73#endif
71 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) { 74 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) {
72 fprintf(stderr, "[SDL] init failed: %s\n", SDL_GetError()); 75 fprintf(stderr, "[SDL] init failed: %s\n", SDL_GetError());
73 return -1; 76 return -1;
diff --git a/src/ui/color.h b/src/ui/color.h
index 179db3e9..c9dff598 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -160,7 +160,7 @@ enum iColorId {
160 tmGopherLinkDomain_ColorId, 160 tmGopherLinkDomain_ColorId,
161 tmGopherLinkLastVisitDate_ColorId, 161 tmGopherLinkLastVisitDate_ColorId,
162 162
163 max_ColorId, 163 max_ColorId, /* note: GmRun packs color into limited number of bits */
164 tmMax_ColorId = max_ColorId - tmFirst_ColorId 164 tmMax_ColorId = max_ColorId - tmFirst_ColorId
165}; 165};
166 166
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 589b9e56..b83490f9 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -928,8 +928,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
928 pushBackCStr_StringArray(title, "Lagrange"); 928 pushBackCStr_StringArray(title, "Lagrange");
929 } 929 }
930 /* Take away parts if it doesn't fit. */ 930 /* Take away parts if it doesn't fit. */
931 const int avail = bounds_Widget(as_Widget(tabButton)).size.x - 3 * gap_UI; 931 const int avail = bounds_Widget(as_Widget(tabButton)).size.x - 3 * gap_UI;
932 iBool setWindow = (document_App() == d && isUnderKeyRoot_Widget(d)); 932 iBool setWindow = (document_App() == d && isUnderKeyRoot_Widget(d));
933 const int font = uiLabel_FontId;
933 for (;;) { 934 for (;;) {
934 iString *text = collect_String(joinCStr_StringArray(title, " \u2014 ")); 935 iString *text = collect_String(joinCStr_StringArray(title, " \u2014 "));
935 if (setWindow) { 936 if (setWindow) {
@@ -945,7 +946,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
945 prependChar_String(text, siteIcon); 946 prependChar_String(text, siteIcon);
946 prependCStr_String(text, escape_Color(uiIcon_ColorId)); 947 prependCStr_String(text, escape_Color(uiIcon_ColorId));
947 } 948 }
948 const int width = measureRange_Text(default_FontId, range_String(text)).advance.x; 949 const int width = measureRange_Text(font, range_String(text)).advance.x;
949 if (width <= avail || 950 if (width <= avail ||
950 isEmpty_StringArray(title)) { 951 isEmpty_StringArray(title)) {
951 updateText_LabelWidget(tabButton, text); 952 updateText_LabelWidget(tabButton, text);
@@ -954,9 +955,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
954 if (size_StringArray(title) == 1) { 955 if (size_StringArray(title) == 1) {
955 /* Just truncate to fit. */ 956 /* Just truncate to fit. */
956 const char *endPos; 957 const char *endPos;
957 tryAdvanceNoWrap_Text(default_FontId, 958 tryAdvanceNoWrap_Text(font,
958 range_String(text), 959 range_String(text),
959 avail - measure_Text(default_FontId, "...").advance.x, 960 avail - measure_Text(font, "...").advance.x,
960 &endPos); 961 &endPos);
961 updateText_LabelWidget( 962 updateText_LabelWidget(
962 tabButton, 963 tabButton,
@@ -1619,7 +1620,7 @@ static void parseUser_DocumentWidget_(iDocumentWidget *d) {
1619static void cacheRunGlyphs_(void *data, const iGmRun *run) { 1620static void cacheRunGlyphs_(void *data, const iGmRun *run) {
1620 iUnused(data); 1621 iUnused(data);
1621 if (!isEmpty_Range(&run->text)) { 1622 if (!isEmpty_Range(&run->text)) {
1622 cache_Text(run->textParams.font, run->text); 1623 cache_Text(run->font, run->text);
1623 } 1624 }
1624} 1625}
1625 1626
@@ -4020,14 +4021,14 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4020 contains_Range(&mark, run->text.start))) { 4021 contains_Range(&mark, run->text.start))) {
4021 int x = 0; 4022 int x = 0;
4022 if (!*isInside) { 4023 if (!*isInside) {
4023 x = measureRange_Text(run->textParams.font, 4024 x = measureRange_Text(run->font,
4024 (iRangecc){ run->text.start, iMax(run->text.start, mark.start) }) 4025 (iRangecc){ run->text.start, iMax(run->text.start, mark.start) })
4025 .advance.x; 4026 .advance.x;
4026 } 4027 }
4027 int w = width_Rect(run->visBounds) - x; 4028 int w = width_Rect(run->visBounds) - x;
4028 if (contains_Range(&run->text, mark.end) || mark.end < run->text.start) { 4029 if (contains_Range(&run->text, mark.end) || mark.end < run->text.start) {
4029 w = measureRange_Text( 4030 w = measureRange_Text(
4030 run->textParams.font, 4031 run->font,
4031 !*isInside ? mark 4032 !*isInside ? mark
4032 : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) }) 4033 : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) })
4033 .advance.x; 4034 .advance.x;
@@ -4083,15 +4084,15 @@ static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2
4083 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2)); 4084 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2));
4084 if (icon) { 4085 if (icon) {
4085 appendChar_String(&str, icon); 4086 appendChar_String(&str, icon);
4086 const iRect iconRect = visualBounds_Text(run->textParams.font, range_String(&str)); 4087 const iRect iconRect = visualBounds_Text(run->font, range_String(&str));
4087 drawRange_Text( 4088 drawRange_Text(
4088 run->textParams.font, 4089 run->font,
4089 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->textParams.font) / 2), 4090 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2),
4090 tmBannerIcon_ColorId, 4091 tmBannerIcon_ColorId,
4091 range_String(&str)); 4092 range_String(&str));
4092 bpos.x += right_Rect(iconRect) + 3 * gap_Text; 4093 bpos.x += right_Rect(iconRect) + 3 * gap_Text;
4093 } 4094 }
4094 drawRange_Text(run->textParams.font, 4095 drawRange_Text(run->font,
4095 bpos, 4096 bpos,
4096 tmBannerTitle_ColorId, 4097 tmBannerTitle_ColorId,
4097 bannerText_DocumentWidget_(d->widget)); 4098 bannerText_DocumentWidget_(d->widget));
@@ -4198,7 +4199,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4198 /* Media UIs are drawn afterwards as a dynamic overlay. */ 4199 /* Media UIs are drawn afterwards as a dynamic overlay. */
4199 return; 4200 return;
4200 } 4201 }
4201 enum iColorId fg = run->textParams.color; 4202 enum iColorId fg = run->color;
4202 const iGmDocument *doc = d->widget->doc; 4203 const iGmDocument *doc = d->widget->doc;
4203 const int linkFlags = linkFlags_GmDocument(doc, run->linkId); 4204 const int linkFlags = linkFlags_GmDocument(doc, run->linkId);
4204 /* Hover state of a link. */ 4205 /* Hover state of a link. */
@@ -4265,10 +4266,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4265 const iInt2 margin = preRunMargin_GmDocument(doc, run->preId); 4266 const iInt2 margin = preRunMargin_GmDocument(doc, run->preId);
4266 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId); 4267 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId);
4267 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId); 4268 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId);
4268 drawWrapRange_Text(run->textParams.font, 4269 drawWrapRange_Text(run->font,
4269 add_I2(visPos, margin), 4270 add_I2(visPos, margin),
4270 run->visBounds.size.x - 2 * margin.x, 4271 run->visBounds.size.x - 2 * margin.x,
4271 run->textParams.color, 4272 run->color,
4272 run->text); 4273 run->text);
4273 } 4274 }
4274 else if (run->flags & siteBanner_GmRunFlag) { 4275 else if (run->flags & siteBanner_GmRunFlag) {
@@ -4287,17 +4288,17 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4287 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); 4288 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase);
4288 if (ordChar) { 4289 if (ordChar) {
4289 const char *circle = "\u25ef"; /* Large Circle */ 4290 const char *circle = "\u25ef"; /* Large Circle */
4290 const int circleFont = defaultContentRegular_FontId; 4291 const int circleFont = FONT_ID(default_FontId, regular_FontStyle, contentRegular_FontSize);
4291 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), 4292 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y),
4292 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) }; 4293 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) };
4293 drawRange_Text( 4294 drawRange_Text(
4294 circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); 4295 circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle));
4295 iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle)); 4296 iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle));
4296 addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); 4297 addv_I2(&circleArea.pos, topLeft_Rect(nbArea));
4297 drawCentered_Text(defaultContentSmall_FontId, 4298 drawCentered_Text(FONT_ID(default_FontId, regular_FontStyle, contentSmall_FontSize),
4298 circleArea, 4299 circleArea,
4299 iTrue, 4300 iTrue,
4300 tmQuote_ColorId, 4301 tmQuote_ColorId,
4301 "%lc", 4302 "%lc",
4302 (int) ordChar); 4303 (int) ordChar);
4303 goto runDrawn; 4304 goto runDrawn;
@@ -4307,15 +4308,15 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4307 if (run->flags & quoteBorder_GmRunFlag) { 4308 if (run->flags & quoteBorder_GmRunFlag) {
4308 drawVLine_Paint(&d->paint, 4309 drawVLine_Paint(&d->paint,
4309 addX_I2(visPos, 4310 addX_I2(visPos,
4310 !run->textParams.isRTL 4311 !run->isRTL
4311 ? -gap_Text * 5 / 2 4312 ? -gap_Text * 5 / 2
4312 : (width_Rect(run->visBounds) + gap_Text * 5 / 2)), 4313 : (width_Rect(run->visBounds) + gap_Text * 5 / 2)),
4313 height_Rect(run->visBounds), 4314 height_Rect(run->visBounds),
4314 tmQuoteIcon_ColorId); 4315 tmQuoteIcon_ColorId);
4315 } 4316 }
4316 drawBoundRange_Text(run->textParams.font, 4317 drawBoundRange_Text(run->font,
4317 visPos, 4318 visPos,
4318 (run->textParams.isRTL ? -1 : 1) * width_Rect(run->visBounds), 4319 (run->isRTL ? -1 : 1) * width_Rect(run->visBounds),
4319 fg, 4320 fg,
4320 run->text); 4321 run->text);
4321 runDrawn:; 4322 runDrawn:;
@@ -4431,7 +4432,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4431 append_String(&str, collect_String(format_Date(&date, "%b %d"))); 4432 append_String(&str, collect_String(format_Date(&date, "%b %d")));
4432 } 4433 }
4433 if (!isEmpty_String(&str)) { 4434 if (!isEmpty_String(&str)) {
4434 if (run->textParams.isRTL) { 4435 if (run->isRTL) {
4435 appendCStr_String(&str, " \u2014 "); 4436 appendCStr_String(&str, " \u2014 ");
4436 } 4437 }
4437 else { 4438 else {
@@ -4440,7 +4441,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4440 const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size; 4441 const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size;
4441 int tx = topRight_Rect(linkRect).x; 4442 int tx = topRight_Rect(linkRect).x;
4442 const char *msg = cstr_String(&str); 4443 const char *msg = cstr_String(&str);
4443 if (run->textParams.isRTL) { 4444 if (run->isRTL) {
4444 tx = topLeft_Rect(linkRect).x - textSize.x; 4445 tx = topLeft_Rect(linkRect).x - textSize.x;
4445 } 4446 }
4446 if (tx + textSize.x > right_Rect(d->widgetBounds)) { 4447 if (tx + textSize.x > right_Rect(d->widgetBounds)) {
@@ -4909,7 +4910,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4909 } 4910 }
4910 /* Pinch zoom indicator. */ 4911 /* Pinch zoom indicator. */
4911 if (d->flags & pinchZoom_DocumentWidgetFlag) { 4912 if (d->flags & pinchZoom_DocumentWidgetFlag) {
4912 const int font = defaultLargeBold_FontId; 4913 const int font = uiLabelLargeBold_FontId;
4913 const int height = lineHeight_Text(font) * 2; 4914 const int height = lineHeight_Text(font) * 2;
4914 const iInt2 size = init_I2(height * 2, height); 4915 const iInt2 size = init_I2(height * 2, height);
4915 const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size }; 4916 const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size };
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c
index b622a554..22552027 100644
--- a/src/ui/mediaui.c
+++ b/src/ui/mediaui.c
@@ -86,7 +86,7 @@ static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) {
86 const int hours = seconds / 3600; 86 const int hours = seconds / 3600;
87 const int mins = (seconds / 60) % 60; 87 const int mins = (seconds / 60) % 60;
88 const int secs = seconds % 60; 88 const int secs = seconds % 60;
89 const int font = defaultBig_FontId; 89 const int font = uiLabelBig_FontId;
90 iString num; 90 iString num;
91 init_String(&num); 91 init_String(&num);
92 if (hours) { 92 if (hours) {
@@ -123,7 +123,7 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
123 drawPlayerButton_( 123 drawPlayerButton_(
124 p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); 124 p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId);
125 } 125 }
126 const int hgt = lineHeight_Text(defaultBig_FontId); 126 const int hgt = lineHeight_Text(uiLabelBig_FontId);
127 const int yMid = mid_Rect(d->scrubberRect).y; 127 const int yMid = mid_Rect(d->scrubberRect).y;
128 const float playTime = time_Player(d->player); 128 const float playTime = time_Player(d->player);
129 const float totalTime = duration_Player(d->player); 129 const float totalTime = duration_Player(d->player);
diff --git a/src/ui/mobile.c b/src/ui/mobile.c
index 3cb6e631..f11769f5 100644
--- a/src/ui/mobile.c
+++ b/src/ui/mobile.c
@@ -48,11 +48,11 @@ static iBool isSideBySideLayout_(void) {
48} 48}
49 49
50static enum iFontId labelFont_(void) { 50static enum iFontId labelFont_(void) {
51 return deviceType_App() == phone_AppDeviceType ? defaultBig_FontId : defaultMedium_FontId; 51 return deviceType_App() == phone_AppDeviceType ? uiLabelBig_FontId : uiLabelMedium_FontId;
52} 52}
53 53
54static enum iFontId labelBoldFont_(void) { 54static enum iFontId labelBoldFont_(void) {
55 return deviceType_App() == phone_AppDeviceType ? defaultBigBold_FontId : defaultMediumBold_FontId; 55 return deviceType_App() == phone_AppDeviceType ? uiLabelBigBold_FontId : uiLabelMediumBold_FontId;
56} 56}
57 57
58static void updatePanelSheetMetrics_(iWidget *sheet) { 58static void updatePanelSheetMetrics_(iWidget *sheet) {
@@ -546,7 +546,7 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
546 updateSize_LabelWidget(button); 546 updateSize_LabelWidget(button);
547 } 547 }
548 setId_Widget(as_Widget(button), radId); 548 setId_Widget(as_Widget(button), radId);
549 setFont_LabelWidget(button, defaultMedium_FontId); 549 setFont_LabelWidget(button, uiLabelMedium_FontId);
550 addChildFlags_Widget(widget, iClob(button), flags); 550 addChildFlags_Widget(widget, iClob(button), flags);
551 } 551 }
552 } 552 }
@@ -1056,7 +1056,7 @@ void initPanels_Mobile(iWidget *panels, iWidget *parentWidget,
1056 iForEach(ObjectList, sub, children_Widget(value)) { 1056 iForEach(ObjectList, sub, children_Widget(value)) {
1057 if (isInstance_Object(sub.object, &Class_LabelWidget)) { 1057 if (isInstance_Object(sub.object, &Class_LabelWidget)) {
1058 iLabelWidget *opt = sub.object; 1058 iLabelWidget *opt = sub.object;
1059 setFont_LabelWidget(opt, defaultMedium_FontId); 1059 setFont_LabelWidget(opt, uiLabelMedium_FontId);
1060 setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); 1060 setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue);
1061 } 1061 }
1062 } 1062 }
diff --git a/src/ui/root.c b/src/ui/root.c
index 63cce62d..a2e6062f 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -447,7 +447,7 @@ static void updateNavBarIdentity_(iWidget *navBar) {
447 if (toolName) { 447 if (toolName) {
448 setOutline_LabelWidget(toolButton, ident == NULL); 448 setOutline_LabelWidget(toolButton, ident == NULL);
449 updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : ""); 449 updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : "");
450 setFont_LabelWidget(toolButton, subjectName ? defaultMedium_FontId : uiLabelLarge_FontId); 450 setFont_LabelWidget(toolButton, subjectName ? uiLabelMedium_FontId : uiLabelLarge_FontId);
451 arrange_Widget(parent_Widget(toolButton)); 451 arrange_Widget(parent_Widget(toolButton));
452 } 452 }
453} 453}
@@ -996,7 +996,7 @@ void updateMetrics_Root(iRoot *d) {
996 const iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); 996 const iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
997 const iWidget *viewButton = findChild_Widget(d->widget, "toolbar.view"); 997 const iWidget *viewButton = findChild_Widget(d->widget, "toolbar.view");
998 const iWidget *idButton = findChild_Widget(toolBar, "toolbar.ident"); 998 const iWidget *idButton = findChild_Widget(toolBar, "toolbar.ident");
999 const int font = defaultTiny_FontId; 999 const int font = uiLabelTiny_FontId;
1000 setFont_LabelWidget(idName, font); 1000 setFont_LabelWidget(idName, font);
1001 setPos_Widget(as_Widget(idName), 1001 setPos_Widget(as_Widget(idName),
1002 windowToLocal_Widget(as_Widget(idName), 1002 windowToLocal_Widget(as_Widget(idName),
@@ -1125,7 +1125,7 @@ void createUserInterface_Root(iRoot *d) {
1125 iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")), 1125 iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")),
1126 embedFlags | moveToParentLeftEdge_WidgetFlag); 1126 embedFlags | moveToParentLeftEdge_WidgetFlag);
1127 setId_Widget(as_Widget(lock), "navbar.lock"); 1127 setId_Widget(as_Widget(lock), "navbar.lock");
1128 setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize); 1128// setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize);
1129 updateTextCStr_LabelWidget(lock, "\U0001f512"); 1129 updateTextCStr_LabelWidget(lock, "\U0001f512");
1130 } 1130 }
1131 /* Button for clearing the URL bar contents. */ { 1131 /* Button for clearing the URL bar contents. */ {
@@ -1134,7 +1134,8 @@ void createUserInterface_Root(iRoot *d) {
1134 iClob(newIcon_LabelWidget(delete_Icon, 0, 0, "navbar.clear")), 1134 iClob(newIcon_LabelWidget(delete_Icon, 0, 0, "navbar.clear")),
1135 hidden_WidgetFlag | embedFlags | moveToParentLeftEdge_WidgetFlag | tight_WidgetFlag); 1135 hidden_WidgetFlag | embedFlags | moveToParentLeftEdge_WidgetFlag | tight_WidgetFlag);
1136 setId_Widget(as_Widget(clear), "navbar.clear"); 1136 setId_Widget(as_Widget(clear), "navbar.clear");
1137 setFont_LabelWidget(clear, symbols2_FontId + uiNormal_FontSize); 1137// setFont_LabelWidget(clear, symbols2_FontId + uiNormal_FontSize);
1138 setFont_LabelWidget(clear, uiLabelSymbols_FontId);
1138// setFlags_Widget(as_Widget(clear), noBackground_WidgetFlag, iFalse); 1139// setFlags_Widget(as_Widget(clear), noBackground_WidgetFlag, iFalse);
1139// setBackgroundColor_Widget(as_Widget(clear), uiBackground_ColorId); 1140// setBackgroundColor_Widget(as_Widget(clear), uiBackground_ColorId);
1140 } 1141 }
@@ -1149,7 +1150,7 @@ void createUserInterface_Root(iRoot *d) {
1149 iLabelWidget *queryInd = new_LabelWidget("${status.query} " return_Icon, NULL); 1150 iLabelWidget *queryInd = new_LabelWidget("${status.query} " return_Icon, NULL);
1150 setId_Widget(as_Widget(queryInd), "input.indicator.search"); 1151 setId_Widget(as_Widget(queryInd), "input.indicator.search");
1151 setTextColor_LabelWidget(queryInd, uiTextAction_ColorId); 1152 setTextColor_LabelWidget(queryInd, uiTextAction_ColorId);
1152 setFont_LabelWidget(queryInd, defaultSmall_FontId); 1153 setFont_LabelWidget(queryInd, uiLabelSmall_FontId);
1153 setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); 1154 setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId);
1154 setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); 1155 setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId);
1155// setAlignVisually_LabelWidget(queryInd, iTrue); 1156// setAlignVisually_LabelWidget(queryInd, iTrue);
@@ -1162,7 +1163,7 @@ void createUserInterface_Root(iRoot *d) {
1162 iLabelWidget *fprog = new_LabelWidget("", NULL); 1163 iLabelWidget *fprog = new_LabelWidget("", NULL);
1163 setId_Widget(as_Widget(fprog), "feeds.progress"); 1164 setId_Widget(as_Widget(fprog), "feeds.progress");
1164 setTextColor_LabelWidget(fprog, uiTextCaution_ColorId); 1165 setTextColor_LabelWidget(fprog, uiTextCaution_ColorId);
1165 setFont_LabelWidget(fprog, defaultSmall_FontId); 1166 setFont_LabelWidget(fprog, uiLabelSmall_FontId);
1166 setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId); 1167 setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId);
1167// setAlignVisually_LabelWidget(fprog, iTrue); 1168// setAlignVisually_LabelWidget(fprog, iTrue);
1168 setNoAutoMinHeight_LabelWidget(fprog, iTrue); 1169 setNoAutoMinHeight_LabelWidget(fprog, iTrue);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 057ff614..42661f6b 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -188,7 +188,7 @@ static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const cha
188 // extraPadding_WidgetFlag : 0) | 188 // extraPadding_WidgetFlag : 0) |
189 flags); 189 flags);
190 setFont_LabelWidget(btn, deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide 190 setFont_LabelWidget(btn, deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide
191 ? defaultBig_FontId 191 ? uiLabelBig_FontId
192 : d->buttonFont); 192 : d->buttonFont);
193 checkIcon_LabelWidget(btn); 193 checkIcon_LabelWidget(btn);
194 return btn; 194 return btn;
@@ -742,7 +742,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
742 d->itemFonts[1] = uiContentBold_FontId; 742 d->itemFonts[1] = uiContentBold_FontId;
743#if defined (iPlatformMobile) 743#if defined (iPlatformMobile)
744 if (deviceType_App() == phone_AppDeviceType) { 744 if (deviceType_App() == phone_AppDeviceType) {
745 d->itemFonts[0] = defaultBig_FontId; 745 d->itemFonts[0] = uiLabelBig_FontId;
746 d->itemFonts[1] = defaultBigBold_FontId; 746 d->itemFonts[1] = defaultBigBold_FontId;
747 } 747 }
748 d->widthAsGaps = 73.0f; 748 d->widthAsGaps = 73.0f;
@@ -774,7 +774,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
774 format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))), 774 format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))),
775 frameless_WidgetFlag | noBackground_WidgetFlag); 775 frameless_WidgetFlag | noBackground_WidgetFlag);
776 } 776 }
777 setButtonFont_SidebarWidget(d, isPhone ? defaultBig_FontId : uiLabel_FontId); 777 setButtonFont_SidebarWidget(d, isPhone ? uiLabelBig_FontId : uiLabel_FontId);
778 addChildFlags_Widget(vdiv, 778 addChildFlags_Widget(vdiv,
779 iClob(buttons), 779 iClob(buttons),
780 arrangeHorizontal_WidgetFlag | 780 arrangeHorizontal_WidgetFlag |
@@ -936,8 +936,8 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
936 if (deviceType_App() == phone_AppDeviceType) { 936 if (deviceType_App() == phone_AppDeviceType) {
937 /* Change font size depending on orientation. */ 937 /* Change font size depending on orientation. */
938 const int fonts[2] = { 938 const int fonts[2] = {
939 isPortrait_App() ? defaultBig_FontId : uiContent_FontId, 939 isPortrait_App() ? uiLabelBig_FontId : uiContent_FontId,
940 isPortrait_App() ? defaultBigBold_FontId : uiContentBold_FontId 940 isPortrait_App() ? uiLabelBigBold_FontId : uiContentBold_FontId
941 }; 941 };
942 if (d->itemFonts[0] != fonts[0]) { 942 if (d->itemFonts[0] != fonts[0]) {
943 d->itemFonts[0] = fonts[0]; 943 d->itemFonts[0] = fonts[0];
@@ -945,7 +945,7 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
945// updateMetrics_SidebarWidget_(d); 945// updateMetrics_SidebarWidget_(d);
946 updateItemHeight_SidebarWidget_(d); 946 updateItemHeight_SidebarWidget_(d);
947 } 947 }
948 setButtonFont_SidebarWidget(d, isPortrait_App() ? defaultBig_FontId : uiLabel_FontId); 948 setButtonFont_SidebarWidget(d, isPortrait_App() ? uiLabelBig_FontId : uiLabel_FontId);
949 } 949 }
950 const iBool isTight = 950 const iBool isTight =
951 (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth); 951 (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth);
@@ -1962,7 +1962,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1962 deinit_String(&str); 1962 deinit_String(&str);
1963 const iInt2 textPos = addY_I2(topRight_Rect(iconArea), (itemHeight - lineHeight_Text(font)) / 2); 1963 const iInt2 textPos = addY_I2(topRight_Rect(iconArea), (itemHeight - lineHeight_Text(font)) / 2);
1964 drawRange_Text(font, textPos, fg, range_String(&d->label)); 1964 drawRange_Text(font, textPos, fg, range_String(&d->label));
1965 const int metaFont = default_FontId; 1965 const int metaFont = uiLabel_FontId;
1966 const int metaIconWidth = 4.5f * gap_UI; 1966 const int metaIconWidth = 4.5f * gap_UI;
1967 const iInt2 metaPos = 1967 const iInt2 metaPos =
1968 init_I2(right_Rect(itemRect) - 1968 init_I2(right_Rect(itemRect) -
@@ -2045,7 +2045,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
2045 const int indent = 1.4f * lineHeight_Text(font); 2045 const int indent = 1.4f * lineHeight_Text(font);
2046 addv_I2(&cPos, 2046 addv_I2(&cPos,
2047 init_I2(3 * gap_UI, 2047 init_I2(3 * gap_UI,
2048 (itemHeight - lineHeight_Text(default_FontId) * 2 - lineHeight_Text(font)) / 2048 (itemHeight - lineHeight_Text(uiLabel_FontId) * 2 - lineHeight_Text(font)) /
2049 2)); 2049 2));
2050 const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId 2050 const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId
2051 : uiTextFramelessHover_ColorId) 2051 : uiTextFramelessHover_ColorId)
@@ -2064,7 +2064,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
2064 add_I2(cPos, init_I2(indent, 0)), 2064 add_I2(cPos, init_I2(indent, 0)),
2065 fg, 2065 fg,
2066 range_String(&d->label)); 2066 range_String(&d->label));
2067 drawRange_Text(default_FontId, 2067 drawRange_Text(uiLabel_FontId,
2068 add_I2(cPos, init_I2(indent, lineHeight_Text(font))), 2068 add_I2(cPos, init_I2(indent, lineHeight_Text(font))),
2069 metaFg, 2069 metaFg,
2070 range_String(&d->meta)); 2070 range_String(&d->meta));
diff --git a/src/ui/text.c b/src/ui/text.c
index e3f7f7c4..86fdc084 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -123,40 +123,57 @@ iDefineTypeConstructionArgs(Glyph, (iChar ch), ch)
123 123
124static iGlyph *glyph_Font_(iFont *d, iChar ch); 124static iGlyph *glyph_Font_(iFont *d, iChar ch);
125 125
126struct Impl_Font { 126iDeclareType(GlyphTable)
127 iBlock * data; 127
128 enum iTextFont family; 128struct Impl_GlyphTable {
129 stbtt_fontinfo font; 129 iHash glyphs; /* key is glyph index in the font */
130 float xScale, yScale; 130 /* TODO: `glyphs` does not need to be a Hash.
131 int vertOffset; /* offset due to scaling */ 131 We could lazily allocate an array with glyphCount elements instead. */
132 int height;
133 int baseline;
134 iHash glyphs; /* key is glyph index in the font */ /* TODO: does not need to be a Hash */
135 iBool isMonospaced;
136 float emAdvance;
137 enum iFontSize sizeId; /* used to look up different fonts of matching size */
138 uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ 132 uint32_t indexTable[128 - 32]; /* quick ASCII lookup */
139#if defined (LAGRANGE_ENABLE_HARFBUZZ)
140 hb_blob_t * hbBlob; /* raw TrueType data */
141 hb_face_t * hbFace;
142 hb_font_t * hbMainFont;
143 hb_font_t * hbFont; /* may be a sub-font with customized font metrics */
144#endif
145}; 133};
146 134
147static iFont *font_Text_(enum iFontId id); 135static void clearGlyphs_Font_(iGlyphTable *d) {
148 136 iForEach(Hash, i, &d->glyphs) {
149#if 0 137 delete_Glyph((iGlyph *) i.value);
150static hb_position_t hbGlyphHKernForNunito_(hb_font_t *font, void *fontData, 138 }
151 hb_codepoint_t firstGlyph, hb_codepoint_t secondGlyph, 139 clear_Hash(&d->glyphs);
152 void *userData) {
153 return 100;
154} 140}
155#endif
156 141
157static void init_Font(iFont *d, const iBlock *data, int height, float scale, 142static void init_GlyphTable(iGlyphTable *d) {
158 enum iFontSize sizeId, iBool isMonospaced) {
159 init_Hash(&d->glyphs); 143 init_Hash(&d->glyphs);
144 memset(d->indexTable, 0xff, sizeof(d->indexTable));
145}
146
147static void deinit_GlyphTable(iGlyphTable *d) {
148 clearGlyphs_Font_(d);
149 deinit_Hash(&d->glyphs);
150}
151
152iDefineTypeConstruction(GlyphTable)
153
154struct Impl_Font {
155 const iFontSpec *fontSpec;
156 const iFontFile *fontFile;
157 int height;
158 int baseline;
159 int vertOffset; /* offset due to glyph scaling */
160 float xScale, yScale;
161 float emAdvance;
162 iGlyphTable * table;
163};
164
165iLocalDef iBool isMonospaced_Font(const iFont *d) {
166 return (d->fontSpec->flags & monospace_FontSpecFlag) != 0;
167}
168
169static iFont *font_Text_(enum iFontId id);
170
171static void init_Font(iFont *d, const iFontSpec *fontSpec, const iFontFile *fontFile,
172 enum iFontSize sizeId, int height) {
173 d->fontSpec = fontSpec;
174 d->fontFile = fontFile;
175 /* TODO: Nunito kerning fixes need to be a font parameter of its own. */
176#if 0
160 d->data = NULL; 177 d->data = NULL;
161 d->family = undefined_TextFont; 178 d->family = undefined_TextFont;
162 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */ 179 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */
@@ -177,95 +194,49 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale,
177 data == &fontSmolEmojiRegular_Embedded) { 194 data == &fontSmolEmojiRegular_Embedded) {
178 d->family = emojiAndSymbols_TextFont; 195 d->family = emojiAndSymbols_TextFont;
179 } 196 }
180 d->isMonospaced = isMonospaced; 197#endif
198// d->isMonospaced = (fontSpec->flags & monospace_FontSpecFlag) != 0;
181 d->height = height; 199 d->height = height;
182 iZap(d->font); 200 //iZap(d->font);
183 stbtt_InitFont(&d->font, constData_Block(data), 0); 201// stbtt_InitFont(&d->font, constData_Block(data), 0);
184 int ascent, descent, emAdv; 202// int ascent, descent, emAdv;
185 stbtt_GetFontVMetrics(&d->font, &ascent, &descent, NULL); 203// stbtt_GetFontVMetrics(&d->font, &ascent, &descent, NULL);
186 stbtt_GetCodepointHMetrics(&d->font, 'M', &emAdv, NULL); 204// stbtt_GetCodepointHMetrics(&d->font, 'M', &emAdv, NULL);
187 d->xScale = d->yScale = stbtt_ScaleForPixelHeight(&d->font, height) * scale; 205 const float scale = fontSpec->scaling;
188 if (d->isMonospaced) { 206 d->xScale = d->yScale = scaleForPixelHeight_FontFile(fontFile, height) * scale;
207 if (isMonospaced_Font(d)) {
189 /* It is important that monospaced fonts align 1:1 with the pixel grid so that 208 /* It is important that monospaced fonts align 1:1 with the pixel grid so that
190 box-drawing characters don't have partially occupied edge pixels, leading to seams 209 box-drawing characters don't have partially occupied edge pixels, leading to seams
191 between adjacent glyphs. */ 210 between adjacent glyphs. */
192 const float advance = (float) emAdv * d->xScale; 211 const float advance = (float) fontFile->emAdvance * d->xScale;
193 if (advance > 4) { /* not too tiny */ 212 if (advance > 4) { /* not too tiny */
194 d->xScale *= floorf(advance) / advance; 213 d->xScale *= floorf(advance) / advance;
195 } 214 }
196 } 215 }
197 d->emAdvance = emAdv * d->xScale; 216 d->emAdvance = fontFile->emAdvance * d->xScale;
198 d->baseline = ascent * d->yScale; 217 d->baseline = fontFile->ascent * d->yScale;
199 d->vertOffset = height * (1.0f - scale) / 2; 218 d->vertOffset = height * (1.0f - scale) / 2 * fontSpec->vertOffset;
200 /* Custom tweaks. */ 219 d->table = NULL;
201 if (data == &fontNotoSansSymbolsRegular_Embedded) {
202 d->vertOffset *= 1.2f;
203 }
204 else if (data == &fontNotoSansSymbols2Regular_Embedded) {
205 d->vertOffset /= 2;
206 }
207 else if (data == &fontNotoEmojiRegular_Embedded) {
208 //d->vertOffset -= height / 30;
209 }
210 d->sizeId = sizeId;
211 memset(d->indexTable, 0xff, sizeof(d->indexTable));
212#if defined(LAGRANGE_ENABLE_HARFBUZZ)
213 /* HarfBuzz will read the font data. */ {
214 d->hbBlob = hb_blob_create(constData_Block(data), size_Block(data),
215 HB_MEMORY_MODE_READONLY, NULL, NULL);
216 d->hbFace = hb_face_create(d->hbBlob, 0);
217 d->hbMainFont = hb_font_create(d->hbFace);
218#if 0
219 /* TODO: The custom kerning function doesn't get called?
220 Maybe HarfBuzz needs FreeType to do kerning? */
221 if (d->family == nunito_TextFont) {
222 /* Customize the kerning of Nunito. */
223 d->hbFont = hb_font_create_sub_font(d->hbMainFont);
224 hb_font_funcs_t *ffs = hb_font_funcs_create();
225 hb_font_funcs_set_glyph_h_kerning_func(ffs, hbGlyphHKernForNunito_, d, NULL);
226 hb_font_set_funcs(d->hbFont, ffs, NULL, NULL);
227 hb_font_funcs_destroy(ffs);
228 }
229 else
230#endif
231 {
232 d->hbFont = hb_font_reference(d->hbMainFont);
233 }
234 }
235#endif
236}
237
238static void clearGlyphs_Font_(iFont *d) {
239 iForEach(Hash, i, &d->glyphs) {
240 delete_Glyph((iGlyph *) i.value);
241 }
242 clear_Hash(&d->glyphs);
243} 220}
244 221
245static void deinit_Font(iFont *d) { 222static void deinit_Font(iFont *d) {
246#if defined(LAGRANGE_ENABLE_HARFBUZZ) 223 delete_GlyphTable(d->table);
247 /* HarfBuzz objects. */ {
248 hb_font_destroy(d->hbFont);
249 hb_font_destroy(d->hbMainFont);
250 hb_face_destroy(d->hbFace);
251 hb_blob_destroy(d->hbBlob);
252 }
253#endif
254 clearGlyphs_Font_(d);
255 deinit_Hash(&d->glyphs);
256 delete_Block(d->data);
257} 224}
258 225
259static uint32_t glyphIndex_Font_(iFont *d, iChar ch) { 226static uint32_t glyphIndex_Font_(iFont *d, iChar ch) {
260 /* TODO: Add a small cache of ~5 most recently found indices. */ 227 /* TODO: Add a small cache of ~5 most recently found indices. */
261 const size_t entry = ch - 32; 228 const size_t entry = ch - 32;
262 if (entry < iElemCount(d->indexTable)) { 229 if (!d->table) {
263 if (d->indexTable[entry] == ~0u) { 230 d->table = new_GlyphTable();
264 d->indexTable[entry] = stbtt_FindGlyphIndex(&d->font, ch); 231 }
232 iGlyphTable *table = d->table;
233 if (entry < iElemCount(table->indexTable)) {
234 if (table->indexTable[entry] == ~0u) {
235 table->indexTable[entry] = findGlyphIndex_FontFile(d->fontFile, ch);
265 } 236 }
266 return d->indexTable[entry]; 237 return table->indexTable[entry];
267 } 238 }
268 return stbtt_FindGlyphIndex(&d->font, ch); 239 return findGlyphIndex_FontFile(d->fontFile, ch);
269} 240}
270 241
271/*----------------------------------------------------------------------------------------------*/ 242/*----------------------------------------------------------------------------------------------*/
@@ -282,7 +253,8 @@ struct Impl_Text {
282 enum iTextFont contentFont; 253 enum iTextFont contentFont;
283 enum iTextFont headingFont; 254 enum iTextFont headingFont;
284 float contentFontSize; 255 float contentFontSize;
285 iFont fonts[max_FontId]; 256 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */
257 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */
286 SDL_Renderer * render; 258 SDL_Renderer * render;
287 SDL_Texture * cache; 259 SDL_Texture * cache;
288 iInt2 cacheSize; 260 iInt2 cacheSize;
@@ -296,12 +268,49 @@ struct Impl_Text {
296iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) 268iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
297 269
298static iText *activeText_; 270static iText *activeText_;
299static iBlock *userFont_; 271
272static void setupFontVariants_Text_(iText *d, const iFontSpec *spec, int baseId) {
273#if defined (iPlatformMobile)
274 const float uiSize = fontSize_UI * 1.1f;
275#else
276 const float uiSize = fontSize_UI;
277#endif
278 const float textSize = fontSize_UI * d->contentFontSize;
279// const float monoSize = textSize * 0.71f;
280// const float smallMonoSize = monoSize * 0.8f;
281 if (spec->flags & override_FontSpecFlag && d->overrideFontId < 0) {
282 /* This is the highest priority override font. */
283 d->overrideFontId = baseId;
284 }
285 for (enum iFontStyle style = 0; style < max_FontStyle; style++) {
286 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) {
287 init_Font(font_Text_(FONT_ID(baseId, style, sizeId)),
288 spec,
289 spec->styles[style],
290 sizeId,
291 (sizeId < contentRegular_FontSize ? uiSize : textSize) * scale_FontSize(sizeId));
292 }
293 }
294}
295
296iLocalDef iFont *font_Text_(enum iFontId id) {
297 return at_Array(&activeText_->fonts, id & mask_FontId);
298}
299
300static enum iFontId fontId_Text_(const iFont *font) {
301 return (enum iFontId) (font - (const iFont *) constData_Array(&activeText_->fonts));
302}
303
304iLocalDef enum iFontSize sizeId_Text_(const iFont *d) {
305 return fontId_Text_(d) % max_FontSize;
306}
307
308iLocalDef enum iFontStyle styleId_Text_(const iFont *d) {
309 return (fontId_Text_(d) / max_FontSize) % max_FontStyle;
310}
300 311
301static void initFonts_Text_(iText *d) { 312static void initFonts_Text_(iText *d) {
302 const float textSize = fontSize_UI * d->contentFontSize; 313#if 0
303 const float monoSize = textSize * 0.71f;
304 const float smallMonoSize = monoSize * 0.8f;
305 const iBlock *regularFont = &fontNunitoRegular_Embedded; 314 const iBlock *regularFont = &fontNunitoRegular_Embedded;
306 const iBlock *boldFont = &fontNunitoBold_Embedded; 315 const iBlock *boldFont = &fontNunitoBold_Embedded;
307 const iBlock *italicFont = &fontNunitoLightItalic_Embedded; 316 const iBlock *italicFont = &fontNunitoLightItalic_Embedded;
@@ -367,15 +376,10 @@ static void initFonts_Text_(iText *d) {
367 h12Font = &fontIosevkaTermExtended_Embedded; 376 h12Font = &fontIosevkaTermExtended_Embedded;
368 h3Font = &fontIosevkaTermExtended_Embedded; 377 h3Font = &fontIosevkaTermExtended_Embedded;
369 } 378 }
370#if defined (iPlatformMobile)
371 const float uiSize = fontSize_UI * 1.1f;
372#else
373 const float uiSize = fontSize_UI;
374#endif
375 const struct { 379 const struct {
376 const iBlock *ttf; 380 const iFontFile *fontFile;
377 int size; 381 int size; /* pixels */
378 float scaling; 382// float scaling;
379 enum iFontSize sizeId; 383 enum iFontSize sizeId;
380 /* UI sizes: 1.0, 1.125, 1.333, 1.666 */ 384 /* UI sizes: 1.0, 1.125, 1.333, 1.666 */
381 /* Content sizes: smallmono, mono, 1.0, 1.2, 1.333, 1.666, 2.0 */ 385 /* Content sizes: smallmono, mono, 1.0, 1.2, 1.333, 1.666, 2.0 */
@@ -433,8 +437,34 @@ static void initFonts_Text_(iText *d) {
433 DEFINE_FONT_SET(&fontNotoSansArabicUIRegular_Embedded, 1.0f), 437 DEFINE_FONT_SET(&fontNotoSansArabicUIRegular_Embedded, 1.0f),
434// DEFINE_FONT_SET(&fontScheherazadeNewRegular_Embedded, 1.0f), 438// DEFINE_FONT_SET(&fontScheherazadeNewRegular_Embedded, 1.0f),
435 }; 439 };
440#endif
441 /* The `fonts` array has precomputed scaling factors and other parameters in all sizes
442 and styles for each available font. Indices to `fonts` act as font runtime IDs. */
443 /* First the mandatory fonts. */
444 d->overrideFontId = -1;
445 resize_Array(&d->fonts, auxiliary_FontId); /* room for the built-ins */
446 iAssert(auxiliary_FontId == documentHeading_FontId + maxVariants_Fonts);
447 setupFontVariants_Text_(d, findSpec_Fonts("default"), default_FontId);
448 setupFontVariants_Text_(d, findSpec_Fonts("iosevka"), monospace_FontId);
449 setupFontVariants_Text_(d, findSpec_Fonts("default"), documentBody_FontId);
450 setupFontVariants_Text_(d, findSpec_Fonts("default"), documentHeading_FontId);
451 /* Check if there are auxiliary fonts available and set those up, too. */
452 iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) {
453 const iFontSpec *spec = s.ptr;
454 if (spec->flags & auxiliary_FontSpecFlag) {
455 const int fontId = size_Array(&d->fonts);
456 resize_Array(&d->fonts, fontId + maxVariants_Fonts);
457 setupFontVariants_Text_(d, spec, fontId);
458 }
459 }
460 /* test */ {
461 const iFont *h = font_Text_(preformatted_FontId); // FONT_ID(documentBody_FontId, regular_FontStyle, contentRegular_FontSize));
462 printf("{%s} %d sz:%d st:%d\n", cstr_String(&h->fontSpec->name), h->height, sizeId_Text_(h),
463 styleId_Text_(h));
464 }
465#if 0
436 iForIndices(i, fontData) { 466 iForIndices(i, fontData) {
437 iFont *font = &d->fonts[i]; 467 iFont *font = font_Text_(i);
438 init_Font(font, 468 init_Font(font,
439 fontData[i].ttf, 469 fontData[i].ttf,
440 fontData[i].size, 470 fontData[i].size,
@@ -442,13 +472,15 @@ static void initFonts_Text_(iText *d) {
442 fontData[i].sizeId, 472 fontData[i].sizeId,
443 fontData[i].ttf == &fontIosevkaTermExtended_Embedded); 473 fontData[i].ttf == &fontIosevkaTermExtended_Embedded);
444 } 474 }
475#endif
445 gap_Text = iRound(gap_UI * d->contentFontSize); 476 gap_Text = iRound(gap_UI * d->contentFontSize);
446} 477}
447 478
448static void deinitFonts_Text_(iText *d) { 479static void deinitFonts_Text_(iText *d) {
449 iForIndices(i, d->fonts) { 480 iForEach(Array, i, &d->fonts) {
450 deinit_Font(&d->fonts[i]); 481 deinit_Font(i.value);
451 } 482 }
483 clear_Array(&d->fonts);
452} 484}
453 485
454static int maxGlyphHeight_Text_(const iText *d) { 486static int maxGlyphHeight_Text_(const iText *d) {
@@ -490,6 +522,7 @@ static void deinitCache_Text_(iText *d) {
490 SDL_DestroyTexture(d->cache); 522 SDL_DestroyTexture(d->cache);
491} 523}
492 524
525#if 0
493void loadUserFonts_Text(void) { 526void loadUserFonts_Text(void) {
494 if (userFont_) { 527 if (userFont_) {
495 delete_Block(userFont_); 528 delete_Block(userFont_);
@@ -508,9 +541,12 @@ void loadUserFonts_Text(void) {
508 iRelease(f); 541 iRelease(f);
509 } 542 }
510} 543}
544#endif
511 545
512void init_Text(iText *d, SDL_Renderer *render) { 546void init_Text(iText *d, SDL_Renderer *render) {
513 loadUserFonts_Text(); 547 iText *oldActive = activeText_;
548 activeText_ = d;
549 init_Array(&d->fonts, sizeof(iFont));
514 d->contentFont = nunito_TextFont; 550 d->contentFont = nunito_TextFont;
515 d->headingFont = nunito_TextFont; 551 d->headingFont = nunito_TextFont;
516 d->contentFontSize = contentScale_Text_; 552 d->contentFontSize = contentScale_Text_;
@@ -526,6 +562,7 @@ void init_Text(iText *d, SDL_Renderer *render) {
526 } 562 }
527 initCache_Text_(d); 563 initCache_Text_(d);
528 initFonts_Text_(d); 564 initFonts_Text_(d);
565 activeText_ = oldActive;
529} 566}
530 567
531void deinit_Text(iText *d) { 568void deinit_Text(iText *d) {
@@ -534,6 +571,7 @@ void deinit_Text(iText *d) {
534 deinitCache_Text_(d); 571 deinitCache_Text_(d);
535 d->render = NULL; 572 d->render = NULL;
536 iRelease(d->ansiEscape); 573 iRelease(d->ansiEscape);
574 deinit_Array(&d->fonts);
537} 575}
538 576
539void setCurrent_Text(iText *d) { 577void setCurrent_Text(iText *d) {
@@ -569,8 +607,8 @@ void setContentFontSize_Text(iText *d, float fontSizeFactor) {
569 607
570static void resetCache_Text_(iText *d) { 608static void resetCache_Text_(iText *d) {
571 deinitCache_Text_(d); 609 deinitCache_Text_(d);
572 for (int i = 0; i < max_FontId; i++) { 610 iForEach(Array, i, &d->fonts) {
573 clearGlyphs_Font_(&d->fonts[i]); 611 clearGlyphs_Font_(i.value);
574 } 612 }
575 initCache_Text_(d); 613 initCache_Text_(d);
576} 614}
@@ -582,14 +620,10 @@ void resetFonts_Text(iText *d) {
582 initFonts_Text_(d); 620 initFonts_Text_(d);
583} 621}
584 622
585iLocalDef iFont *font_Text_(enum iFontId id) {
586 return &activeText_->fonts[id & mask_FontId];
587}
588
589static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { 623static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) {
590 int w, h; 624 int w, h;
591 uint8_t *bmp = stbtt_GetGlyphBitmapSubpixel( 625 uint8_t *bmp = rasterizeGlyph_FontFile(d->fontFile, d->xScale, d->yScale, xShift, glyphIndex,
592 &d->font, d->xScale, d->yScale, xShift, 0.0f, glyphIndex, &w, &h, 0, 0); 626 &w, &h);
593 SDL_Surface *surface8 = 627 SDL_Surface *surface8 =
594 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); 628 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);
595 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE); 629 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE);
@@ -636,8 +670,8 @@ static iInt2 assignCachePos_Text_(iText *d, iInt2 size) {
636static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { 670static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
637 iRect *glRect = &glyph->rect[hoff]; 671 iRect *glRect = &glyph->rect[hoff];
638 int x0, y0, x1, y1; 672 int x0, y0, x1, y1;
639 stbtt_GetGlyphBitmapBoxSubpixel( 673 measureGlyph_FontFile(d->fontFile, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f,
640 &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); 674 &x0, &y0, &x1, &y1);
641 glRect->size = init_I2(x1 - x0, y1 - y0); 675 glRect->size = init_I2(x1 - x0, y1 - y0);
642 /* Determine placement in the glyph cache texture, advancing in rows. */ 676 /* Determine placement in the glyph cache texture, advancing in rows. */
643 glRect->pos = assignCachePos_Text_(activeText_, glRect->size); 677 glRect->pos = assignCachePos_Text_(activeText_, glRect->size);
@@ -645,7 +679,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
645 glyph->d[hoff].y += d->vertOffset; 679 glyph->d[hoff].y += d->vertOffset;
646 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ 680 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */
647 int adv; 681 int adv;
648 stbtt_GetGlyphHMetrics(&d->font, index_Glyph_(glyph), &adv, NULL); 682 stbtt_GetGlyphHMetrics(&d->fontFile->stbInfo, index_Glyph_(glyph), &adv, NULL);
649 glyph->advance = d->xScale * adv; 683 glyph->advance = d->xScale * adv;
650 } 684 }
651} 685}
@@ -654,13 +688,18 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
654 if (isVariationSelector_Char(ch)) { 688 if (isVariationSelector_Char(ch)) {
655 return d; 689 return d;
656 } 690 }
657 /* Smol Emoji overrides all other fonts. */ 691 const enum iFontStyle styleId = styleId_Text_(d);
658 if (ch != 0x20) { 692 const enum iFontSize sizeId = sizeId_Text_(d);
659 iFont *smol = font_Text_(smolEmoji_FontId + d->sizeId); 693 iFont *overrideFont = NULL;
660 if (smol != d && (*glyphIndex = glyphIndex_Font_(smol, ch)) != 0) { 694 if (ch != 0x20 && activeText_->overrideFontId >= 0) {
661 return smol; 695 /* Override font is checked first. */
696 overrideFont = font_Text_(FONT_ID(activeText_->overrideFontId, styleId, sizeId));
697 if (overrideFont != d && (*glyphIndex = glyphIndex_Font_(overrideFont, ch)) != 0) {
698 return overrideFont;
662 } 699 }
663 } 700 }
701#if 0
702 /* TODO: Put arrows in Smol Emoji. */
664 /* Manual exceptions. */ { 703 /* Manual exceptions. */ {
665 if (ch >= 0x2190 && ch <= 0x2193 /* arrows */) { 704 if (ch >= 0x2190 && ch <= 0x2193 /* arrows */) {
666 d = font_Text_(iosevka_FontId + d->sizeId); 705 d = font_Text_(iosevka_FontId + d->sizeId);
@@ -668,9 +707,23 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
668 return d; 707 return d;
669 } 708 }
670 } 709 }
710#endif
711 /* The font's own version of the glyph. */
671 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { 712 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) {
672 return d; 713 return d;
673 } 714 }
715 /* As a fallback, check all other available fonts of this size. */
716 for (iFont *font = font_Text_(FONT_ID(0, styleId, sizeId));
717 font < (iFont *) end_Array(&activeText_->fonts);
718 font += maxVariants_Fonts) {
719 if (font == d || font == overrideFont) {
720 continue; /* already checked this one */
721 }
722 if ((*glyphIndex = glyphIndex_Font_(font, ch)) != 0) {
723 return font;
724 }
725 }
726#if 0
674 const int fallbacks[] = { 727 const int fallbacks[] = {
675 notoEmoji_FontId, 728 notoEmoji_FontId,
676 symbols2_FontId, 729 symbols2_FontId,
@@ -732,6 +785,7 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
732 if (d != font) { 785 if (d != font) {
733 *glyphIndex = glyphIndex_Font_(font, ch); 786 *glyphIndex = glyphIndex_Font_(font, ch);
734 } 787 }
788#endif // 0
735 if (!*glyphIndex) { 789 if (!*glyphIndex) {
736 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); 790 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr);
737 } 791 }
@@ -739,8 +793,9 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
739} 793}
740 794
741static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) { 795static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
796 iAssert(d->table);
742 iGlyph* glyph = NULL; 797 iGlyph* glyph = NULL;
743 void * node = value_Hash(&d->glyphs, glyphIndex); 798 void * node = value_Hash(&d->table->glyphs, glyphIndex);
744 if (node) { 799 if (node) {
745 glyph = node; 800 glyph = node;
746 } 801 }
@@ -758,7 +813,7 @@ static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
758 and updates the glyph metrics. */ 813 and updates the glyph metrics. */
759 allocate_Font_(d, glyph, 0); 814 allocate_Font_(d, glyph, 0);
760 allocate_Font_(d, glyph, 1); 815 allocate_Font_(d, glyph, 1);
761 insert_Hash(&d->glyphs, &glyph->node); 816 insert_Hash(&d->table->glyphs, &glyph->node);
762 } 817 }
763 return glyph; 818 return glyph;
764} 819}
@@ -866,10 +921,6 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i
866 run->logical.start = endAt; 921 run->logical.start = endAt;
867} 922}
868 923
869static enum iFontId fontId_Text_(const iFont *font) {
870 return (enum iFontId) (font - activeText_->fonts);
871}
872
873static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { 924static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {
874 iAssert(isEmpty_Array(&d->runs)); 925 iAssert(isEmpty_Array(&d->runs));
875 size_t length = 0; 926 size_t length = 0;
@@ -997,14 +1048,14 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
997 continue; 1048 continue;
998 } 1049 }
999 if (ch == 0x20) { 1050 if (ch == 0x20) {
1000 if (run.font->family == emojiAndSymbols_TextFont) { 1051 if (run.font->fontSpec->flags & auxiliary_FontSpecFlag) {
1001 finishRun_AttributedText_(d, &run, pos); 1052 finishRun_AttributedText_(d, &run, pos);
1002 run.font = d->font; /* never use space from the symbols font, it's too wide */ 1053 run.font = d->font; /* never use space from the symbols font, it's too wide */
1003 } 1054 }
1004 continue; 1055 continue;
1005 } 1056 }
1006 iFont *currentFont = d->font; 1057 iFont *currentFont = d->font;
1007 if (run.font->family == arabic_TextFont && isPunct_Char(ch)) { 1058 if (run.font->fontSpec->flags & arabic_FontSpecFlag && isPunct_Char(ch)) {
1008 currentFont = run.font; /* remain as Arabic for whitespace */ 1059 currentFont = run.font; /* remain as Arabic for whitespace */
1009 } 1060 }
1010 const iGlyph *glyph = glyph_Font_(currentFont, ch); 1061 const iGlyph *glyph = glyph_Font_(currentFont, ch);
@@ -1283,7 +1334,7 @@ static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int
1283 1334
1284float horizKern_Font_(iFont *d, uint32_t glyph1, uint32_t glyph2) { 1335float horizKern_Font_(iFont *d, uint32_t glyph1, uint32_t glyph2) {
1285#if defined (LAGRANGE_ENABLE_KERNING) 1336#if defined (LAGRANGE_ENABLE_KERNING)
1286 if (!enableKerning_Text || d->family != nunito_TextFont) { 1337 if (!enableKerning_Text || ~d->fontSpec->flags & fixNunitoKerning_FontSpecFlag) {
1287 return 0.0f; 1338 return 0.0f;
1288 } 1339 }
1289 if (glyph1 && glyph2) { 1340 if (glyph1 && glyph2) {
@@ -1335,7 +1386,7 @@ static void deinit_GlyphBuffer_(iGlyphBuffer *d) {
1335 1386
1336static void shape_GlyphBuffer_(iGlyphBuffer *d) { 1387static void shape_GlyphBuffer_(iGlyphBuffer *d) {
1337 if (!d->glyphInfo) { 1388 if (!d->glyphInfo) {
1338 hb_shape(d->font->hbFont, d->hb, NULL, 0); 1389 hb_shape(d->font->fontFile->hbFont, d->hb, NULL, 0);
1339 d->glyphInfo = hb_buffer_get_glyph_infos(d->hb, &d->glyphCount); 1390 d->glyphInfo = hb_buffer_get_glyph_infos(d->hb, &d->glyphCount);
1340 d->glyphPos = hb_buffer_get_glyph_positions(d->hb, &d->glyphCount); 1391 d->glyphPos = hb_buffer_get_glyph_positions(d->hb, &d->glyphCount);
1341 } 1392 }
@@ -1389,7 +1440,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1389 float xCursor = 0.0f; 1440 float xCursor = 0.0f;
1390 float yCursor = 0.0f; 1441 float yCursor = 0.0f;
1391 float xCursorMax = 0.0f; 1442 float xCursorMax = 0.0f;
1392 const iBool isMonospaced = d->isMonospaced; 1443 const iBool isMonospaced = isMonospaced_Font(d);
1393 iWrapText *wrap = args->wrap; 1444 iWrapText *wrap = args->wrap;
1394 iAssert(args->text.end >= args->text.start); 1445 iAssert(args->text.end >= args->text.start);
1395 /* Split the text into a number of attributed runs that specify exactly which 1446 /* Split the text into a number of attributed runs that specify exactly which
diff --git a/src/ui/text.h b/src/ui/text.h
index 1b3200d4..f76c8125 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -31,79 +31,92 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
31 31
32/* Content sizes: regular (1x) -> medium (1.2x) -> big (1.33x) -> large (1.67x) -> huge (2x) */ 32/* Content sizes: regular (1x) -> medium (1.2x) -> big (1.33x) -> large (1.67x) -> huge (2x) */
33 33
34#define FONT_ID(name, style, size) ((name) + ((style) * max_FontSize) + (size))
35
34enum iFontId { 36enum iFontId {
35 /* UI fonts: normal weight (1x, 1.125x, 1.33x, 1.67x) */ 37 default_FontId = 0, /* default is always the first font */
36 default_FontId = 0, 38 monospace_FontId = maxVariants_Fonts, /* 2nd font is always the monospace font */
37 defaultMedium_FontId, 39 documentBody_FontId = maxVariants_Fonts * 2, /* 3rd font is the body font */
38 defaultBig_FontId, 40 documentHeading_FontId = maxVariants_Fonts * 3, /* heading font */
39 defaultLarge_FontId, 41 auxiliary_FontId = maxVariants_Fonts * 4, /* the first auxiliary font (e.g., symbols) */
40 defaultTiny_FontId, 42
41 defaultSmall_FontId, 43 // defaultMedium_FontId,
44// defaultBig_FontId,
45// defaultLarge_FontId,
46// defaultTiny_FontId,
47// defaultSmall_FontId,
42 /* UI fonts: bold weight */ 48 /* UI fonts: bold weight */
43 defaultBold_FontId, 49// defaultBold_FontId,
44 defaultMediumBold_FontId, 50// defaultMediumBold_FontId,
45 defaultBigBold_FontId, 51// defaultBigBold_FontId,
46 defaultLargeBold_FontId, 52// defaultLargeBold_FontId,
47 /* content fonts */ 53 /* content fonts */
48 regular_FontId, 54// bold_FontId,
49 bold_FontId, 55// italic_FontId,
50 italic_FontId, 56// medium_FontId,
51 medium_FontId, 57// big_FontId,
52 big_FontId, 58// largeBold_FontId,
53 largeBold_FontId, 59// largeLight_FontId,
54 largeLight_FontId, 60// hugeBold_FontId,
55 hugeBold_FontId, 61// monospaceSmall_FontId,
56 monospaceSmall_FontId,
57 monospace_FontId,
58 /* extra content fonts */ 62 /* extra content fonts */
59 defaultContentRegular_FontId, /* UI font but sized to regular_FontId */ 63// defaultContentRegular_FontId, /* UI font but sized to regular_FontId */
60 defaultContentSmall_FontId, /* UI font but sized smaller */ 64// defaultContentSmall_FontId, /* UI font but sized smaller */
61 /* symbols and scripts */ 65 /* symbols and scripts */
62 userSymbols_FontId, 66// userSymbols_FontId,
63 iosevka_FontId = userSymbols_FontId + max_FontSize, 67// iosevka_FontId = userSymbols_FontId + max_FontSize,
64 symbols_FontId = iosevka_FontId + max_FontSize, 68// symbols_FontId = iosevka_FontId + max_FontSize,
65 symbols2_FontId = symbols_FontId + max_FontSize, 69// symbols2_FontId = symbols_FontId + max_FontSize,
66 smolEmoji_FontId = symbols2_FontId + max_FontSize, 70// smolEmoji_FontId = symbols2_FontId + max_FontSize,
67 notoEmoji_FontId = smolEmoji_FontId + max_FontSize, 71// notoEmoji_FontId = smolEmoji_FontId + max_FontSize,
68 japanese_FontId = notoEmoji_FontId + max_FontSize, 72// japanese_FontId = notoEmoji_FontId + max_FontSize,
69 chineseSimplified_FontId = japanese_FontId + max_FontSize, 73// chineseSimplified_FontId = japanese_FontId + max_FontSize,
70 korean_FontId = chineseSimplified_FontId + max_FontSize, 74// korean_FontId = chineseSimplified_FontId + max_FontSize,
71 arabic_FontId = korean_FontId + max_FontSize, 75// arabic_FontId = korean_FontId + max_FontSize,
72 max_FontId = arabic_FontId + max_FontSize, 76// max_FontId = arabic_FontId + max_FontSize,
73 77
74 /* Meta: */ 78 /* Meta: */
75 mask_FontId = 0xffff, 79 mask_FontId = 0x0000ffff, /* font IDs are 16-bit; see GmRun's packing */
76 alwaysVariableFlag_FontId = 0x10000, 80 alwaysVariableFlag_FontId = 0x00010000,
77 81
78 /* UI fonts: */ 82 /* UI fonts: */
79 uiLabel_FontId = default_FontId, 83 uiLabelTiny_FontId = FONT_ID(default_FontId, semiBold_FontStyle, uiTiny_FontSize),
80 uiLabelBold_FontId = defaultBold_FontId, 84 uiLabelSmall_FontId = FONT_ID(default_FontId, regular_FontStyle, uiSmall_FontSize),
81 uiLabelLarge_FontId = defaultLarge_FontId, 85 uiLabel_FontId = FONT_ID(default_FontId, regular_FontStyle, uiNormal_FontSize),
82 uiLabelLargeBold_FontId = defaultLargeBold_FontId, 86 uiLabelMedium_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize),
83 uiShortcuts_FontId = default_FontId, 87 uiLabelMediumBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiMedium_FontSize),
84 uiInput_FontId = defaultMedium_FontId, 88 uiLabelBig_FontId = FONT_ID(default_FontId, regular_FontStyle, uiBig_FontSize),
85 uiContent_FontId = defaultMedium_FontId, 89 uiLabelBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiNormal_FontSize),
86 uiContentBold_FontId = defaultMediumBold_FontId, 90 uiLabelBigBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiBig_FontSize),
87 uiContentSymbols_FontId = symbols_FontId + uiMedium_FontSize, 91 uiLabelLarge_FontId = FONT_ID(default_FontId, regular_FontStyle, uiLarge_FontSize),
92 uiLabelLargeBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiLarge_FontSize),
93 uiLabelSymbols_FontId = FONT_ID(auxiliary_FontId, regular_FontStyle, uiNormal_FontSize),
94 uiShortcuts_FontId = FONT_ID(default_FontId, regular_FontStyle, uiNormal_FontSize),
95 uiInput_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize),
96 uiContent_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize),
97 uiContentBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiMedium_FontSize),
98 uiContentSymbols_FontId = FONT_ID(auxiliary_FontId, regular_FontStyle, uiMedium_FontSize),
88 /* Document fonts: */ 99 /* Document fonts: */
89 paragraph_FontId = regular_FontId, 100 paragraph_FontId = FONT_ID(documentBody_FontId, regular_FontStyle, contentRegular_FontSize),
90 firstParagraph_FontId = medium_FontId, 101 bold_FontId = FONT_ID(documentBody_FontId, semiBold_FontStyle, contentRegular_FontSize),
91 preformatted_FontId = monospace_FontId, 102 firstParagraph_FontId = FONT_ID(documentBody_FontId, regular_FontStyle, contentMedium_FontSize),
92 preformattedSmall_FontId = monospaceSmall_FontId, 103 preformatted_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentMono_FontSize),
93 quote_FontId = italic_FontId, 104 preformattedSmall_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentMonoSmall_FontSize),
94 heading1_FontId = hugeBold_FontId, 105 quote_FontId = FONT_ID(documentBody_FontId, italic_FontStyle, contentRegular_FontSize),
95 heading2_FontId = largeBold_FontId, 106 heading1_FontId = FONT_ID(documentHeading_FontId, bold_FontStyle, contentHuge_FontSize),
96 heading3_FontId = big_FontId, 107 heading2_FontId = FONT_ID(documentHeading_FontId, bold_FontStyle, contentLarge_FontSize),
97 banner_FontId = largeLight_FontId, 108 heading3_FontId = FONT_ID(documentHeading_FontId, regular_FontStyle, contentBig_FontSize),
98 regularMonospace_FontId = iosevka_FontId + contentRegular_FontSize 109 banner_FontId = FONT_ID(documentHeading_FontId, light_FontStyle, contentLarge_FontSize),
110 regularMonospace_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentRegular_FontSize),
99}; 111};
100 112
101iLocalDef iBool isJapanese_FontId(enum iFontId id) { 113//iLocalDef iBool isJapanese_FontId(enum iFontId id) {
102 return id >= japanese_FontId && id < japanese_FontId + max_FontSize; 114// return id >= japanese_FontId && id < japanese_FontId + max_FontSize;
103} 115//}
104 116
105#define emojiVariationSelector_Char ((iChar) 0xfe0f) 117#define emojiVariationSelector_Char ((iChar) 0xfe0f)
106 118
119/* TODO: get rid of this; configure using font ID strings, check RTL from FontFile flags */
107enum iTextFont { 120enum iTextFont {
108 undefined_TextFont = -1, 121 undefined_TextFont = -1,
109 nunito_TextFont = 0, 122 nunito_TextFont = 0,
@@ -127,7 +140,7 @@ void deinit_Text (iText *);
127 140
128void setCurrent_Text (iText *); 141void setCurrent_Text (iText *);
129 142
130void loadUserFonts_Text (void); /* based on Prefs */ 143//void loadUserFonts_Text (void); /* based on Prefs */
131 144
132void setContentFont_Text (iText *, enum iTextFont font); 145void setContentFont_Text (iText *, enum iTextFont font);
133void setHeadingFont_Text (iText *, enum iTextFont font); 146void setHeadingFont_Text (iText *, enum iTextFont font);
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c
index ba7545fd..ded8d7f8 100644
--- a/src/ui/uploadwidget.c
+++ b/src/ui/uploadwidget.c
@@ -255,7 +255,7 @@ void init_UploadWidget(iUploadWidget *d) {
255 setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); 255 setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue);
256 setFocus_Widget(as_Widget(d->input)); 256 setFocus_Widget(as_Widget(d->input));
257 } 257 }
258 setFont_InputWidget(d->input, iosevka_FontId); 258 setFont_InputWidget(d->input, FONT_ID(monospace_FontId, regular_FontStyle, uiNormal_FontSize));
259 setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */ 259 setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */
260 setLineLimits_InputWidget(d->input, 7, 20); 260 setLineLimits_InputWidget(d->input, 7, 20);
261 setHint_InputWidget(d->input, "${hint.upload.text}"); 261 setHint_InputWidget(d->input, "${hint.upload.text}");
diff --git a/src/ui/util.c b/src/ui/util.c
index adca6269..a4411ea5 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -873,7 +873,7 @@ static void updateMenuItemFonts_Widget_(iWidget *d) {
873 } 873 }
874 else if (isPortraitPhone) { 874 else if (isPortraitPhone) {
875 if (!isSlidePanel) { 875 if (!isSlidePanel) {
876 setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); 876 setFont_LabelWidget(label, isCaution ? uiLabelBigBold_FontId : uiLabelBig_FontId);
877 } 877 }
878 } 878 }
879 else { 879 else {
@@ -1626,8 +1626,8 @@ iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) {
1626 } 1626 }
1627 int fonts[2] = { uiLabel_FontId, uiLabelBold_FontId }; 1627 int fonts[2] = { uiLabel_FontId, uiLabelBold_FontId };
1628 if (deviceType_App() == phone_AppDeviceType) { 1628 if (deviceType_App() == phone_AppDeviceType) {
1629 fonts[0] = defaultMedium_FontId; 1629 fonts[0] = uiLabelMedium_FontId;
1630 fonts[1] = defaultMediumBold_FontId; 1630 fonts[1] = uiLabelMediumBold_FontId;
1631 } 1631 }
1632 for (size_t i = 0; i < numActions; i++) { 1632 for (size_t i = 0; i < numActions; i++) {
1633 const char *label = actions[i].label; 1633 const char *label = actions[i].label;
@@ -1694,7 +1694,7 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con
1694 resizeToParentWidth_WidgetFlag); 1694 resizeToParentWidth_WidgetFlag);
1695 setContentPadding_InputWidget(input, 0.5f * gap_UI, 0.5f * gap_UI); 1695 setContentPadding_InputWidget(input, 0.5f * gap_UI, 0.5f * gap_UI);
1696 if (deviceType_App() == phone_AppDeviceType) { 1696 if (deviceType_App() == phone_AppDeviceType) {
1697 setFont_InputWidget(input, defaultBig_FontId); 1697 setFont_InputWidget(input, uiLabelBig_FontId);
1698 setBackgroundColor_Widget(dlg, uiBackgroundSidebar_ColorId); 1698 setBackgroundColor_Widget(dlg, uiBackgroundSidebar_ColorId);
1699 setContentPadding_InputWidget(input, gap_UI, gap_UI); 1699 setContentPadding_InputWidget(input, gap_UI, gap_UI);
1700 } 1700 }
@@ -1811,7 +1811,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg,
1811 resizeToParentWidth_WidgetFlag | 1811 resizeToParentWidth_WidgetFlag |
1812 (first == '&' ? selected_WidgetFlag : 0)); 1812 (first == '&' ? selected_WidgetFlag : 0));
1813 if (deviceType_App() != desktop_AppDeviceType) { 1813 if (deviceType_App() != desktop_AppDeviceType) {
1814 setFont_LabelWidget(option, defaultBig_FontId); 1814 setFont_LabelWidget(option, uiLabelBig_FontId);
1815 } 1815 }
1816 } 1816 }
1817 } 1817 }
diff --git a/src/ui/window.c b/src/ui/window.c
index 686a6dd6..ea783331 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -1188,7 +1188,7 @@ void draw_Window(iWindow *d) {
1188 extern int drawCount_; 1188 extern int drawCount_;
1189 drawRoot_Widget(root->widget); 1189 drawRoot_Widget(root->widget);
1190#if !defined (NDEBUG) 1190#if !defined (NDEBUG)
1191 draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_); 1191 draw_Text(uiLabelBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_);
1192 drawCount_ = 0; 1192 drawCount_ = 0;
1193#endif 1193#endif
1194 } 1194 }
@@ -1287,7 +1287,7 @@ void draw_MainWindow(iMainWindow *d) {
1287 } 1287 }
1288 setCurrent_Root(NULL); 1288 setCurrent_Root(NULL);
1289#if !defined (NDEBUG) 1289#if !defined (NDEBUG)
1290 draw_Text(defaultBold_FontId, safeRect_Root(w->roots[0]).pos, red_ColorId, "%d", drawCount_); 1290 draw_Text(uiLabelBold_FontId, safeRect_Root(w->roots[0]).pos, red_ColorId, "%d", drawCount_);
1291 drawCount_ = 0; 1291 drawCount_ = 0;
1292#endif 1292#endif
1293 } 1293 }