summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c668
1 files changed, 417 insertions, 251 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
index 44897a70..106c55e9 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -123,40 +123,60 @@ 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_GlyphTable_(iGlyphTable *d) {
148 136 if (d) {
149#if 0 137 iForEach(Hash, i, &d->glyphs) {
150static hb_position_t hbGlyphHKernForNunito_(hb_font_t *font, void *fontData, 138 delete_Glyph((iGlyph *) i.value);
151 hb_codepoint_t firstGlyph, hb_codepoint_t secondGlyph, 139 }
152 void *userData) { 140 clear_Hash(&d->glyphs);
153 return 100; 141 }
154} 142}
155#endif
156 143
157static void init_Font(iFont *d, const iBlock *data, int height, float scale, 144static void init_GlyphTable(iGlyphTable *d) {
158 enum iFontSize sizeId, iBool isMonospaced) {
159 init_Hash(&d->glyphs); 145 init_Hash(&d->glyphs);
146 memset(d->indexTable, 0xff, sizeof(d->indexTable));
147}
148
149static void deinit_GlyphTable(iGlyphTable *d) {
150 clearGlyphs_GlyphTable_(d);
151 deinit_Hash(&d->glyphs);
152}
153
154iDefineTypeConstruction(GlyphTable)
155
156struct Impl_Font {
157 const iFontSpec *fontSpec;
158 const iFontFile *fontFile;
159 int height;
160 int baseline;
161 int vertOffset; /* offset due to glyph scaling */
162 float xScale, yScale;
163 float emAdvance;
164 iGlyphTable * table;
165};
166
167iLocalDef iBool isMonospaced_Font(const iFont *d) {
168 return (d->fontSpec->flags & monospace_FontSpecFlag) != 0;
169}
170
171static iFont *font_Text_(enum iFontId id);
172
173static void init_Font(iFont *d, const iFontSpec *fontSpec, const iFontFile *fontFile,
174 enum iFontSize sizeId, float height) {
175 const int scaleType = scaleType_FontSpec(sizeId);
176 d->fontSpec = fontSpec;
177 d->fontFile = fontFile;
178 /* TODO: Nunito kerning fixes need to be a font parameter of its own. */
179#if 0
160 d->data = NULL; 180 d->data = NULL;
161 d->family = undefined_TextFont; 181 d->family = undefined_TextFont;
162 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */ 182 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */
@@ -177,95 +197,43 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale,
177 data == &fontSmolEmojiRegular_Embedded) { 197 data == &fontSmolEmojiRegular_Embedded) {
178 d->family = emojiAndSymbols_TextFont; 198 d->family = emojiAndSymbols_TextFont;
179 } 199 }
180 d->isMonospaced = isMonospaced; 200#endif
181 d->height = height; 201 d->height = (int) (height * fontSpec->heightScale[scaleType]);
182 iZap(d->font); 202 const float glyphScale = fontSpec->glyphScale[scaleType];
183 stbtt_InitFont(&d->font, constData_Block(data), 0); 203 d->xScale = d->yScale = scaleForPixelHeight_FontFile(fontFile, d->height) * glyphScale;
184 int ascent, descent, emAdv; 204 if (isMonospaced_Font(d)) {
185 stbtt_GetFontVMetrics(&d->font, &ascent, &descent, NULL);
186 stbtt_GetCodepointHMetrics(&d->font, 'M', &emAdv, NULL);
187 d->xScale = d->yScale = stbtt_ScaleForPixelHeight(&d->font, height) * scale;
188 if (d->isMonospaced) {
189 /* It is important that monospaced fonts align 1:1 with the pixel grid so that 205 /* 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 206 box-drawing characters don't have partially occupied edge pixels, leading to seams
191 between adjacent glyphs. */ 207 between adjacent glyphs. */
192 const float advance = (float) emAdv * d->xScale; 208 const float advance = (float) fontFile->emAdvance * d->xScale;
193 if (advance > 4) { /* not too tiny */ 209 if (advance > 4) { /* not too tiny */
194 d->xScale *= floorf(advance) / advance; 210 d->xScale *= floorf(advance) / advance;
195 } 211 }
196 } 212 }
197 d->emAdvance = emAdv * d->xScale; 213 d->emAdvance = fontFile->emAdvance * d->xScale;
198 d->baseline = ascent * d->yScale; 214 d->baseline = fontFile->ascent * d->yScale;
199 d->vertOffset = height * (1.0f - scale) / 2; 215 d->vertOffset = d->height * (1.0f - glyphScale) / 2 * fontSpec->vertOffsetScale[scaleType];
200 /* Custom tweaks. */ 216 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} 217}
244 218
245static void deinit_Font(iFont *d) { 219static void deinit_Font(iFont *d) {
246#if defined(LAGRANGE_ENABLE_HARFBUZZ) 220 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} 221}
258 222
259static uint32_t glyphIndex_Font_(iFont *d, iChar ch) { 223static uint32_t glyphIndex_Font_(iFont *d, iChar ch) {
260 /* TODO: Add a small cache of ~5 most recently found indices. */ 224 /* TODO: Add a small cache of ~5 most recently found indices. */
261 const size_t entry = ch - 32; 225 const size_t entry = ch - 32;
262 if (entry < iElemCount(d->indexTable)) { 226 if (!d->table) {
263 if (d->indexTable[entry] == ~0u) { 227 d->table = new_GlyphTable();
264 d->indexTable[entry] = stbtt_FindGlyphIndex(&d->font, ch); 228 }
229 iGlyphTable *table = d->table;
230 if (entry < iElemCount(table->indexTable)) {
231 if (table->indexTable[entry] == ~0u) {
232 table->indexTable[entry] = findGlyphIndex_FontFile(d->fontFile, ch);
265 } 233 }
266 return d->indexTable[entry]; 234 return table->indexTable[entry];
267 } 235 }
268 return stbtt_FindGlyphIndex(&d->font, ch); 236 return findGlyphIndex_FontFile(d->fontFile, ch);
269} 237}
270 238
271/*----------------------------------------------------------------------------------------------*/ 239/*----------------------------------------------------------------------------------------------*/
@@ -279,10 +247,11 @@ struct Impl_CacheRow {
279}; 247};
280 248
281struct Impl_Text { 249struct Impl_Text {
282 enum iTextFont contentFont; 250// enum iTextFont contentFont;
283 enum iTextFont headingFont; 251// enum iTextFont headingFont;
284 float contentFontSize; 252 float contentFontSize;
285 iFont fonts[max_FontId]; 253 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */
254 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */
286 SDL_Renderer * render; 255 SDL_Renderer * render;
287 SDL_Texture * cache; 256 SDL_Texture * cache;
288 iInt2 cacheSize; 257 iInt2 cacheSize;
@@ -290,18 +259,65 @@ struct Impl_Text {
290 int cacheBottom; 259 int cacheBottom;
291 iArray cacheRows; 260 iArray cacheRows;
292 SDL_Palette * grayscale; 261 SDL_Palette * grayscale;
262 SDL_Palette * blackAndWhite; /* unsmoothed glyph palette */
293 iRegExp * ansiEscape; 263 iRegExp * ansiEscape;
264 int ansiFlags;
265 int baseFontId; /* base attributes (for restoring via escapes) */
266 int baseColorId;
294}; 267};
295 268
296iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) 269iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
297 270
298static iText *activeText_; 271static iText *activeText_;
299static iBlock *userFont_; 272
273static void setupFontVariants_Text_(iText *d, const iFontSpec *spec, int baseId) {
274#if defined (iPlatformMobile)
275 const float uiSize = fontSize_UI * 1.1f;
276#else
277 const float uiSize = fontSize_UI;
278#endif
279 const float textSize = fontSize_UI * d->contentFontSize;
280// const float monoSize = textSize * 0.71f;
281// const float smallMonoSize = monoSize * 0.8f;
282 if (spec->flags & override_FontSpecFlag && d->overrideFontId < 0) {
283 /* This is the highest priority override font. */
284 d->overrideFontId = baseId;
285 }
286 for (enum iFontStyle style = 0; style < max_FontStyle; style++) {
287 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) {
288 init_Font(font_Text_(FONT_ID(baseId, style, sizeId)),
289 spec,
290 spec->styles[style],
291 sizeId,
292 (sizeId < contentRegular_FontSize ? uiSize : textSize) *
293 scale_FontSize(sizeId));
294 }
295 }
296}
297
298iLocalDef iFont *font_Text_(enum iFontId id) {
299 return at_Array(&activeText_->fonts, id & mask_FontId);
300}
301
302static enum iFontId fontId_Text_(const iFont *font) {
303 return (enum iFontId) (font - (const iFont *) constData_Array(&activeText_->fonts));
304}
305
306iLocalDef enum iFontSize sizeId_Text_(const iFont *d) {
307 return fontId_Text_(d) % max_FontSize;
308}
309
310iLocalDef enum iFontStyle styleId_Text_(const iFont *d) {
311 return (fontId_Text_(d) / max_FontSize) % max_FontStyle;
312}
313
314static const iFontSpec *tryFindSpec_(enum iPrefsString ps, const char *fallback) {
315 const iFontSpec *spec = findSpec_Fonts(cstr_String(&prefs_App()->strings[ps]));
316 return spec ? spec : findSpec_Fonts(fallback);
317}
300 318
301static void initFonts_Text_(iText *d) { 319static void initFonts_Text_(iText *d) {
302 const float textSize = fontSize_UI * d->contentFontSize; 320#if 0
303 const float monoSize = textSize * 0.71f;
304 const float smallMonoSize = monoSize * 0.8f;
305 const iBlock *regularFont = &fontNunitoRegular_Embedded; 321 const iBlock *regularFont = &fontNunitoRegular_Embedded;
306 const iBlock *boldFont = &fontNunitoBold_Embedded; 322 const iBlock *boldFont = &fontNunitoBold_Embedded;
307 const iBlock *italicFont = &fontNunitoLightItalic_Embedded; 323 const iBlock *italicFont = &fontNunitoLightItalic_Embedded;
@@ -367,15 +383,10 @@ static void initFonts_Text_(iText *d) {
367 h12Font = &fontIosevkaTermExtended_Embedded; 383 h12Font = &fontIosevkaTermExtended_Embedded;
368 h3Font = &fontIosevkaTermExtended_Embedded; 384 h3Font = &fontIosevkaTermExtended_Embedded;
369 } 385 }
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 { 386 const struct {
376 const iBlock *ttf; 387 const iFontFile *fontFile;
377 int size; 388 int size; /* pixels */
378 float scaling; 389// float scaling;
379 enum iFontSize sizeId; 390 enum iFontSize sizeId;
380 /* UI sizes: 1.0, 1.125, 1.333, 1.666 */ 391 /* 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 */ 392 /* Content sizes: smallmono, mono, 1.0, 1.2, 1.333, 1.666, 2.0 */
@@ -385,8 +396,8 @@ static void initFonts_Text_(iText *d) {
385 { &fontSourceSans3Regular_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, 396 { &fontSourceSans3Regular_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize },
386 { &fontSourceSans3Regular_Embedded, uiSize * 1.333f, 1.0f, uiBig_FontSize }, 397 { &fontSourceSans3Regular_Embedded, uiSize * 1.333f, 1.0f, uiBig_FontSize },
387 { &fontSourceSans3Regular_Embedded, uiSize * 1.666f, 1.0f, uiLarge_FontSize }, 398 { &fontSourceSans3Regular_Embedded, uiSize * 1.666f, 1.0f, uiLarge_FontSize },
388 { &fontSourceSans3Regular_Embedded, uiSize * 0.900f, 1.0f, uiSmall_FontSize },
389 { &fontSourceSans3Semibold_Embedded, uiSize * 0.800f, 1.0f, uiTiny_FontSize }, 399 { &fontSourceSans3Semibold_Embedded, uiSize * 0.800f, 1.0f, uiTiny_FontSize },
400 { &fontSourceSans3Regular_Embedded, uiSize * 0.900f, 1.0f, uiSmall_FontSize },
390 /* UI fonts: bold weight */ 401 /* UI fonts: bold weight */
391 { &fontSourceSans3Bold_Embedded, uiSize, 1.0f, uiNormal_FontSize }, 402 { &fontSourceSans3Bold_Embedded, uiSize, 1.0f, uiNormal_FontSize },
392 { &fontSourceSans3Bold_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, 403 { &fontSourceSans3Bold_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize },
@@ -408,12 +419,12 @@ static void initFonts_Text_(iText *d) {
408 { &fontSourceSans3Regular_Embedded, textSize * 0.80f, scaling, contentRegular_FontSize }, 419 { &fontSourceSans3Regular_Embedded, textSize * 0.80f, scaling, contentRegular_FontSize },
409 /* symbols and scripts */ 420 /* symbols and scripts */
410#define DEFINE_FONT_SET(data, glyphScale) \ 421#define DEFINE_FONT_SET(data, glyphScale) \
422 { (data), uiSize * 0.800f, glyphScale, uiTiny_FontSize }, \
423 { (data), uiSize * 0.900f, glyphScale, uiSmall_FontSize }, \
411 { (data), uiSize, glyphScale, uiNormal_FontSize }, \ 424 { (data), uiSize, glyphScale, uiNormal_FontSize }, \
412 { (data), uiSize * 1.125f, glyphScale, uiMedium_FontSize }, \ 425 { (data), uiSize * 1.125f, glyphScale, uiMedium_FontSize }, \
413 { (data), uiSize * 1.333f, glyphScale, uiBig_FontSize }, \ 426 { (data), uiSize * 1.333f, glyphScale, uiBig_FontSize }, \
414 { (data), uiSize * 1.666f, glyphScale, uiLarge_FontSize }, \ 427 { (data), uiSize * 1.666f, glyphScale, uiLarge_FontSize }, \
415 { (data), uiSize * 0.900f, glyphScale, uiSmall_FontSize }, \
416 { (data), uiSize * 0.800f, glyphScale, uiTiny_FontSize }, \
417 { (data), textSize, glyphScale, contentRegular_FontSize }, \ 428 { (data), textSize, glyphScale, contentRegular_FontSize }, \
418 { (data), textSize * 1.200f, glyphScale, contentMedium_FontSize }, \ 429 { (data), textSize * 1.200f, glyphScale, contentMedium_FontSize }, \
419 { (data), textSize * 1.333f, glyphScale, contentBig_FontSize }, \ 430 { (data), textSize * 1.333f, glyphScale, contentBig_FontSize }, \
@@ -433,26 +444,39 @@ static void initFonts_Text_(iText *d) {
433 DEFINE_FONT_SET(&fontNotoSansArabicUIRegular_Embedded, 1.0f), 444 DEFINE_FONT_SET(&fontNotoSansArabicUIRegular_Embedded, 1.0f),
434// DEFINE_FONT_SET(&fontScheherazadeNewRegular_Embedded, 1.0f), 445// DEFINE_FONT_SET(&fontScheherazadeNewRegular_Embedded, 1.0f),
435 }; 446 };
436 iForIndices(i, fontData) { 447#endif
437 iFont *font = &d->fonts[i]; 448 /* The `fonts` array has precomputed scaling factors and other parameters in all sizes
438 init_Font(font, 449 and styles for each available font. Indices to `fonts` act as font runtime IDs. */
439 fontData[i].ttf, 450 /* First the mandatory fonts. */
440 fontData[i].size, 451 d->overrideFontId = -1;
441 fontData[i].scaling, 452 resize_Array(&d->fonts, auxiliary_FontId); /* room for the built-ins */
442 fontData[i].sizeId, 453 setupFontVariants_Text_(d, tryFindSpec_(uiFont_PrefsString, "default"), default_FontId);
443 fontData[i].ttf == &fontIosevkaTermExtended_Embedded); 454 setupFontVariants_Text_(d, tryFindSpec_(monospaceFont_PrefsString, "iosevka"), monospace_FontId);
455 setupFontVariants_Text_(d, tryFindSpec_(headingFont_PrefsString, "default"), documentHeading_FontId);
456 setupFontVariants_Text_(d, tryFindSpec_(bodyFont_PrefsString, "default"), documentBody_FontId);
457 setupFontVariants_Text_(d, tryFindSpec_(monospaceDocumentFont_PrefsString, "iosevka-body"), documentMonospace_FontId);
458 /* Check if there are auxiliary fonts available and set those up, too. */
459 iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) {
460 const iFontSpec *spec = s.ptr;
461 if (spec->flags & (auxiliary_FontSpecFlag | user_FontSpecFlag)) {
462 const int fontId = size_Array(&d->fonts);
463 resize_Array(&d->fonts, fontId + maxVariants_Fonts);
464 setupFontVariants_Text_(d, spec, fontId);
465 }
444 } 466 }
445 gap_Text = iRound(gap_UI * d->contentFontSize); 467 gap_Text = iRound(gap_UI * d->contentFontSize);
446} 468}
447 469
448static void deinitFonts_Text_(iText *d) { 470static void deinitFonts_Text_(iText *d) {
449 iForIndices(i, d->fonts) { 471 iForEach(Array, i, &d->fonts) {
450 deinit_Font(&d->fonts[i]); 472 deinit_Font(i.value);
451 } 473 }
474 clear_Array(&d->fonts);
452} 475}
453 476
454static int maxGlyphHeight_Text_(const iText *d) { 477static int maxGlyphHeight_Text_(const iText *d) {
455 return 2 * d->contentFontSize * fontSize_UI; 478 /* Huge size is 2 * contentFontSize. */
479 return 4 * d->contentFontSize * fontSize_UI;
456} 480}
457 481
458static void initCache_Text_(iText *d) { 482static void initCache_Text_(iText *d) {
@@ -471,7 +495,7 @@ static void initCache_Text_(iText *d) {
471 /* Allocate initial (empty) rows. These will be assigned actual locations in the cache 495 /* Allocate initial (empty) rows. These will be assigned actual locations in the cache
472 once at least one glyph is stored. */ 496 once at least one glyph is stored. */
473 for (int h = d->cacheRowAllocStep; 497 for (int h = d->cacheRowAllocStep;
474 h <= 2.5 * textSize + d->cacheRowAllocStep; 498 h <= 5 * textSize + d->cacheRowAllocStep;
475 h += d->cacheRowAllocStep) { 499 h += d->cacheRowAllocStep) {
476 pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 }); 500 pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 });
477 } 501 }
@@ -490,6 +514,7 @@ static void deinitCache_Text_(iText *d) {
490 SDL_DestroyTexture(d->cache); 514 SDL_DestroyTexture(d->cache);
491} 515}
492 516
517#if 0
493void loadUserFonts_Text(void) { 518void loadUserFonts_Text(void) {
494 if (userFont_) { 519 if (userFont_) {
495 delete_Block(userFont_); 520 delete_Block(userFont_);
@@ -508,13 +533,16 @@ void loadUserFonts_Text(void) {
508 iRelease(f); 533 iRelease(f);
509 } 534 }
510} 535}
536#endif
511 537
512void init_Text(iText *d, SDL_Renderer *render) { 538void init_Text(iText *d, SDL_Renderer *render) {
513 loadUserFonts_Text(); 539 iText *oldActive = activeText_;
514 d->contentFont = nunito_TextFont; 540 activeText_ = d;
515 d->headingFont = nunito_TextFont; 541 init_Array(&d->fonts, sizeof(iFont));
516 d->contentFontSize = contentScale_Text_; 542 d->contentFontSize = contentScale_Text_;
517 d->ansiEscape = new_RegExp("[[()]([0-9;AB]*)m", 0); 543 d->ansiEscape = new_RegExp("[[()]([0-9;AB]*?)m", 0);
544 d->baseFontId = -1;
545 d->baseColorId = -1;
518 d->render = render; 546 d->render = render;
519 /* A grayscale palette for rasterized glyphs. */ { 547 /* A grayscale palette for rasterized glyphs. */ {
520 SDL_Color colors[256]; 548 SDL_Color colors[256];
@@ -524,16 +552,27 @@ void init_Text(iText *d, SDL_Renderer *render) {
524 d->grayscale = SDL_AllocPalette(256); 552 d->grayscale = SDL_AllocPalette(256);
525 SDL_SetPaletteColors(d->grayscale, colors, 0, 256); 553 SDL_SetPaletteColors(d->grayscale, colors, 0, 256);
526 } 554 }
555 /* Black-and-white palette for unsmoothed glyphs. */ {
556 SDL_Color colors[256];
557 for (int i = 0; i < 256; ++i) {
558 colors[i] = (SDL_Color){ 255, 255, 255, i < 100 ? 0 : 255 };
559 }
560 d->blackAndWhite = SDL_AllocPalette(256);
561 SDL_SetPaletteColors(d->blackAndWhite, colors, 0, 256);
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) {
569 SDL_FreePalette(d->blackAndWhite);
532 SDL_FreePalette(d->grayscale); 570 SDL_FreePalette(d->grayscale);
533 deinitFonts_Text_(d); 571 deinitFonts_Text_(d);
534 deinitCache_Text_(d); 572 deinitCache_Text_(d);
535 d->render = NULL; 573 d->render = NULL;
536 iRelease(d->ansiEscape); 574 iRelease(d->ansiEscape);
575 deinit_Array(&d->fonts);
537} 576}
538 577
539void setCurrent_Text(iText *d) { 578void setCurrent_Text(iText *d) {
@@ -544,21 +583,16 @@ void setOpacity_Text(float opacity) {
544 SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); 583 SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);
545} 584}
546 585
547void setContentFont_Text(iText *d, enum iTextFont font) { 586void setBaseAttributes_Text(int fontId, int colorId) {
548 if (d->contentFont != font) { 587 activeText_->baseFontId = fontId;
549 d->contentFont = font; 588 activeText_->baseColorId = colorId;
550 resetFonts_Text(d);
551 }
552} 589}
553 590
554void setHeadingFont_Text(iText *d, enum iTextFont font) { 591void setAnsiFlags_Text(int ansiFlags) {
555 if (d->headingFont != font) { 592 activeText_->ansiFlags = ansiFlags;
556 d->headingFont = font;
557 resetFonts_Text(d);
558 }
559} 593}
560 594
561void setContentFontSize_Text(iText *d, float fontSizeFactor) { 595void setDocumentFontSize_Text(iText *d, float fontSizeFactor) {
562 fontSizeFactor *= contentScale_Text_; 596 fontSizeFactor *= contentScale_Text_;
563 iAssert(fontSizeFactor > 0); 597 iAssert(fontSizeFactor > 0);
564 if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) { 598 if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) {
@@ -569,8 +603,8 @@ void setContentFontSize_Text(iText *d, float fontSizeFactor) {
569 603
570static void resetCache_Text_(iText *d) { 604static void resetCache_Text_(iText *d) {
571 deinitCache_Text_(d); 605 deinitCache_Text_(d);
572 for (int i = 0; i < max_FontId; i++) { 606 iForEach(Array, i, &d->fonts) {
573 clearGlyphs_Font_(&d->fonts[i]); 607 clearGlyphs_GlyphTable_(((iFont *) i.value)->table);
574 } 608 }
575 initCache_Text_(d); 609 initCache_Text_(d);
576} 610}
@@ -582,18 +616,18 @@ void resetFonts_Text(iText *d) {
582 initFonts_Text_(d); 616 initFonts_Text_(d);
583} 617}
584 618
585iLocalDef iFont *font_Text_(enum iFontId id) { 619static SDL_Palette *glyphPalette_(void) {
586 return &activeText_->fonts[id & mask_FontId]; 620 return prefs_App()->fontSmoothing ? activeText_->grayscale : activeText_->blackAndWhite;
587} 621}
588 622
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);
596 SDL_SetSurfacePalette(surface8, activeText_->grayscale); 630 SDL_SetSurfacePalette(surface8, glyphPalette_());
597#if LAGRANGE_RASTER_DEPTH != 8 631#if LAGRANGE_RASTER_DEPTH != 8
598 /* Convert to the cache format. */ 632 /* Convert to the cache format. */
599 SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0); 633 SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0);
@@ -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,33 @@ 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 (int aux = 0; aux < 2; aux++) {
717 for (iFont *font = font_Text_(FONT_ID(0, styleId, sizeId));
718 font < (iFont *) end_Array(&activeText_->fonts);
719 font += maxVariants_Fonts) {
720 const iBool isAuxiliary = (font->fontSpec->flags & auxiliary_FontSpecFlag) ? 1 : 0;
721 if (aux == isAuxiliary) {
722 /* First try auxiliary fonts, then other remaining fonts. */
723 continue;
724 }
725 if (font == d || font == overrideFont) {
726 continue; /* already checked this one */
727 }
728 if ((*glyphIndex = glyphIndex_Font_(font, ch)) != 0) {
729// printf("using %s[%f] for %lc (%x) => %d\n",
730// cstr_String(&font->fontSpec->name), font->fontSpec->scaling,
731// (int) ch, ch, glyphIndex_Font_(font, ch));
732 return font;
733 }
734 }
735 }
736#if 0
674 const int fallbacks[] = { 737 const int fallbacks[] = {
675 notoEmoji_FontId, 738 notoEmoji_FontId,
676 symbols2_FontId, 739 symbols2_FontId,
@@ -732,6 +795,7 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
732 if (d != font) { 795 if (d != font) {
733 *glyphIndex = glyphIndex_Font_(font, ch); 796 *glyphIndex = glyphIndex_Font_(font, ch);
734 } 797 }
798#endif // 0
735 if (!*glyphIndex) { 799 if (!*glyphIndex) {
736 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); 800 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr);
737 } 801 }
@@ -739,8 +803,9 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
739} 803}
740 804
741static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) { 805static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
806 iAssert(d->table);
742 iGlyph* glyph = NULL; 807 iGlyph* glyph = NULL;
743 void * node = value_Hash(&d->glyphs, glyphIndex); 808 void * node = value_Hash(&d->table->glyphs, glyphIndex);
744 if (node) { 809 if (node) {
745 glyph = node; 810 glyph = node;
746 } 811 }
@@ -758,7 +823,7 @@ static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
758 and updates the glyph metrics. */ 823 and updates the glyph metrics. */
759 allocate_Font_(d, glyph, 0); 824 allocate_Font_(d, glyph, 0);
760 allocate_Font_(d, glyph, 1); 825 allocate_Font_(d, glyph, 1);
761 insert_Hash(&d->glyphs, &glyph->node); 826 insert_Hash(&d->table->glyphs, &glyph->node);
762 } 827 }
763 return glyph; 828 return glyph;
764} 829}
@@ -793,25 +858,45 @@ static iBool isControl_Char_(iChar c) {
793iDeclareType(AttributedRun) 858iDeclareType(AttributedRun)
794 859
795struct Impl_AttributedRun { 860struct Impl_AttributedRun {
796 iRangei logical; /* UTF-32 codepoint indices in the logical-order text */ 861 iRangei logical; /* UTF-32 codepoint indices in the logical-order text */
797 iFont * font; 862 iTextAttrib attrib;
798 iColor fgColor; 863 iFont *font;
864 iColor fgColor_; /* any RGB color; A > 0 */
799 struct { 865 struct {
800 uint8_t isLineBreak : 1; 866 uint8_t isLineBreak : 1;
801 uint8_t isRTL : 1; 867// uint8_t isRTL : 1;
802 uint8_t isArabic : 1; /* Arabic script detected */ 868 uint8_t isArabic : 1; /* Arabic script detected */
803 } flags; 869 } flags;
804}; 870};
805 871
872static iColor fgColor_AttributedRun_(const iAttributedRun *d) {
873 if (d->fgColor_.a) {
874 return d->fgColor_;
875 }
876 if (d->attrib.colorId == none_ColorId) {
877 return (iColor){ 255, 255, 255, 255 };
878 }
879 return get_Color(d->attrib.colorId);
880}
881
882static void setFgColor_AttributedRun_(iAttributedRun *d, int colorId) {
883 d->attrib.colorId = colorId;
884 d->fgColor_.a = 0;
885}
886
806iDeclareType(AttributedText) 887iDeclareType(AttributedText)
807iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, 888iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font,
808 iColor fgColor, int baseDir, iChar overrideChar) 889 int colorId, int baseDir, iFont *baseFont, int baseColorId,
890 iChar overrideChar)
809 891
810struct Impl_AttributedText { 892struct Impl_AttributedText {
811 iRangecc source; /* original source text */ 893 iRangecc source; /* original source text */
812 size_t maxLen; 894 size_t maxLen;
813 iFont * font; 895 iFont * font;
814 iColor fgColor; 896 int colorId;
897 iFont * baseFont;
898 int baseColorId;
899 iBool isBaseRTL;
815 iArray runs; 900 iArray runs;
816 iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */ 901 iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */
817 iArray visual; /* UTF-32 text in visual order (LTR) */ 902 iArray visual; /* UTF-32 text in visual order (LTR) */
@@ -819,13 +904,14 @@ struct Impl_AttributedText {
819 iArray visualToLogical; 904 iArray visualToLogical;
820 iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */ 905 iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */
821 char * bidiLevels; 906 char * bidiLevels;
822 iBool isBaseRTL;
823}; 907};
824 908
825iDefineTypeConstructionArgs(AttributedText, 909iDefineTypeConstructionArgs(AttributedText,
826 (iRangecc text, size_t maxLen, iFont *font, iColor fgColor, 910 (iRangecc text, size_t maxLen, iFont *font, int colorId,
827 int baseDir, iChar overrideChar), 911 int baseDir, iFont *baseFont, int baseColorId,
828 text, maxLen, font, fgColor, baseDir, overrideChar) 912 iChar overrideChar),
913 text, maxLen, font, colorId, baseDir, baseFont, baseColorId,
914 overrideChar)
829 915
830static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) { 916static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) {
831 const int *logToSource = constData_Array(&d->logicalToSourceOffset); 917 const int *logToSource = constData_Array(&d->logicalToSourceOffset);
@@ -842,23 +928,17 @@ static iRangecc sourceRange_AttributedText_(const iAttributedText *d, iRangei lo
842 return range; 928 return range;
843} 929}
844 930
845#if 0
846static iBool isAllSpace_AttributedText_(const iAttributedText *d, iRangei range) {
847 const iChar *logicalText = constData_Array(&d->logical);
848 for (size_t i = range.start; i < range.end; i++) {
849 if (logicalText[i] != 0x20) {
850 return iFalse;
851 }
852 }
853 return iTrue;
854}
855#endif
856
857static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) { 931static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) {
858 iAttributedRun finishedRun = *run; 932 iAttributedRun finishedRun = *run;
859 iAssert(endAt >= 0 && endAt <= size_Array(&d->logical)); 933 iAssert(endAt >= 0 && endAt <= size_Array(&d->logical));
860 finishedRun.logical.end = endAt; 934 finishedRun.logical.end = endAt;
861 if (!isEmpty_Range(&finishedRun.logical)) { 935 if (!isEmpty_Range(&finishedRun.logical)) {
936#if 0
937 /* Colorize individual runs to see boundaries. */
938 static int dbg;
939 static const int dbgClr[3] = { red_ColorId, green_ColorId, blue_ColorId };
940 finishedRun.attrib.colorId = dbgClr[dbg++ % 3];
941#endif
862 pushBack_Array(&d->runs, &finishedRun); 942 pushBack_Array(&d->runs, &finishedRun);
863 run->flags.isLineBreak = iFalse; 943 run->flags.isLineBreak = iFalse;
864 run->flags.isArabic = iFalse; 944 run->flags.isArabic = iFalse;
@@ -866,8 +946,22 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i
866 run->logical.start = endAt; 946 run->logical.start = endAt;
867} 947}
868 948
869static enum iFontId fontId_Text_(const iFont *font) { 949int fontWithSize_Text(int font, enum iFontSize sizeId) {
870 return (enum iFontId) (font - activeText_->fonts); 950 const int familyId = (font / maxVariants_Fonts) * maxVariants_Fonts;
951 const int styleId = (font / max_FontSize) % max_FontStyle;
952 return FONT_ID(familyId, styleId, sizeId);
953}
954
955int fontWithStyle_Text(int font, enum iFontStyle styleId) {
956 const int familyId = (font / maxVariants_Fonts) * maxVariants_Fonts;
957 const int sizeId = font % max_FontSize;
958 return FONT_ID(familyId, styleId, sizeId);
959}
960
961int fontWithFamily_Text(int font, enum iFontId familyId) {
962 const int styleId = (font / max_FontSize) % max_FontStyle;
963 const int sizeId = font % max_FontSize;
964 return FONT_ID(familyId, styleId, sizeId);
871} 965}
872 966
873static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { 967static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {
@@ -924,14 +1018,17 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
924 pushBack_Array(&d->logicalToVisual, &(int){ length }); 1018 pushBack_Array(&d->logicalToVisual, &(int){ length });
925 pushBack_Array(&d->visualToLogical, &(int){ length }); 1019 pushBack_Array(&d->visualToLogical, &(int){ length });
926 } 1020 }
927 iAttributedRun run = { .logical = { 0, length }, 1021 iAttributedRun run = {
928 .font = d->font, 1022 .logical = { 0, length },
929 .fgColor = d->fgColor }; 1023 .attrib = { .colorId = d->colorId, .isBaseRTL = d->isBaseRTL },
930 const int * logToSource = constData_Array(&d->logicalToSourceOffset); 1024 .font = d->font,
1025 };
1026 const int *logToSource = constData_Array(&d->logicalToSourceOffset);
931 const int * logToVis = constData_Array(&d->logicalToVisual); 1027 const int * logToVis = constData_Array(&d->logicalToVisual);
932 const iChar * logicalText = constData_Array(&d->logical); 1028 const iChar * logicalText = constData_Array(&d->logical);
933 iBool isRTL = d->isBaseRTL; 1029 iBool isRTL = d->isBaseRTL;
934 int numNonSpace = 0; 1030 int numNonSpace = 0;
1031 iFont * attribFont = d->font;
935 for (int pos = 0; pos < length; pos++) { 1032 for (int pos = 0; pos < length; pos++) {
936 const iChar ch = logicalText[pos]; 1033 const iChar ch = logicalText[pos];
937#if defined (LAGRANGE_ENABLE_FRIBIDI) 1034#if defined (LAGRANGE_ENABLE_FRIBIDI)
@@ -951,7 +1048,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
951#else 1048#else
952 const iBool isNeutral = iTrue; 1049 const iBool isNeutral = iTrue;
953#endif 1050#endif
954 run.flags.isRTL = isRTL; 1051 run.attrib.isRTL = isRTL;
955 if (ch == 0x1b) { /* ANSI escape. */ 1052 if (ch == 0x1b) { /* ANSI escape. */
956 pos++; 1053 pos++;
957 const char *srcPos = d->source.start + logToSource[pos]; 1054 const char *srcPos = d->source.start + logToSource[pos];
@@ -960,10 +1057,42 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
960 init_RegExpMatch(&m); 1057 init_RegExpMatch(&m);
961 if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { 1058 if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) {
962 finishRun_AttributedText_(d, &run, pos - 1); 1059 finishRun_AttributedText_(d, &run, pos - 1);
963 run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), 1060 const int ansi = activeText_->ansiFlags;
964 tmParagraph_ColorId); 1061 if (ansi) {
1062 const iRangecc sequence = capturedRange_RegExpMatch(&m, 1);
1063 /* Note: This styling is hardcoded to match `typesetOneLine_RunTypesetter_()`. */
1064 if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "1")) {
1065 run.attrib.bold = iTrue;
1066 if (d->baseColorId == tmParagraph_ColorId) {
1067 setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId);
1068 }
1069 attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont),
1070 bold_FontStyle));
1071 }
1072 else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "3")) {
1073 run.attrib.italic = iTrue;
1074 attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont),
1075 italic_FontStyle));
1076 }
1077 else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "4")) {
1078 run.attrib.monospace = iTrue;
1079 setFgColor_AttributedRun_(&run, tmPreformatted_ColorId);
1080 attribFont = font_Text_(fontWithFamily_Text(fontId_Text_(d->baseFont),
1081 monospace_FontId));
1082 }
1083 else if (equal_Rangecc(sequence, "0")) {
1084 run.attrib.bold = iFalse;
1085 run.attrib.italic = iFalse;
1086 run.attrib.monospace = iFalse;
1087 attribFont = run.font = d->baseFont;
1088 setFgColor_AttributedRun_(&run, d->baseColorId);
1089 }
1090 else if (ansi & allowFg_AnsiFlag) {
1091 run.fgColor_ = ansiForeground_Color(sequence, tmParagraph_ColorId);
1092 }
1093 }
965 pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); 1094 pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0));
966 iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start); 1095// iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start);
967 /* The run continues after the escape sequence. */ 1096 /* The run continues after the escape sequence. */
968 run.logical.start = pos--; /* loop increments `pos` */ 1097 run.logical.start = pos--; /* loop increments `pos` */
969 continue; 1098 continue;
@@ -982,7 +1111,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
982 colorNum = esc - asciiBase_ColorEscape; 1111 colorNum = esc - asciiBase_ColorEscape;
983 } 1112 }
984 run.logical.start = pos + 1; 1113 run.logical.start = pos + 1;
985 run.fgColor = (colorNum >= 0 ? get_Color(colorNum) : d->fgColor); 1114 setFgColor_AttributedRun_(&run, colorNum >= 0 ? colorNum : d->colorId);
986 continue; 1115 continue;
987 } 1116 }
988 if (ch == '\n') { 1117 if (ch == '\n') {
@@ -997,15 +1126,18 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
997 continue; 1126 continue;
998 } 1127 }
999 if (ch == 0x20) { 1128 if (ch == 0x20) {
1000 if (run.font->family == emojiAndSymbols_TextFont) { 1129 if (run.font->fontSpec->flags & auxiliary_FontSpecFlag &&
1130 ~run.font->fontSpec->flags & allowSpacePunct_FontSpecFlag) {
1001 finishRun_AttributedText_(d, &run, pos); 1131 finishRun_AttributedText_(d, &run, pos);
1002 run.font = d->font; /* never use space from the symbols font, it's too wide */ 1132 run.font = d->font; /* auxilitary font space not allowed, could be wrong width */
1003 } 1133 }
1004 continue; 1134 continue;
1005 } 1135 }
1006 iFont *currentFont = d->font; 1136 iFont *currentFont = attribFont;
1007 if (run.font->family == arabic_TextFont && isPunct_Char(ch)) { 1137 if (run.font->fontSpec->flags & auxiliary_FontSpecFlag &&
1008 currentFont = run.font; /* remain as Arabic for whitespace */ 1138 run.font->fontSpec->flags & allowSpacePunct_FontSpecFlag &&
1139 isPunct_Char(ch)) {
1140 currentFont = run.font; /* keep the current font */
1009 } 1141 }
1010 const iGlyph *glyph = glyph_Font_(currentFont, ch); 1142 const iGlyph *glyph = glyph_Font_(currentFont, ch);
1011 if (index_Glyph_(glyph) && glyph->font != run.font) { 1143 if (index_Glyph_(glyph) && glyph->font != run.font) {
@@ -1030,8 +1162,10 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
1030 printf("[AttributedText] %zu runs:\n", size_Array(&d->runs)); 1162 printf("[AttributedText] %zu runs:\n", size_Array(&d->runs));
1031 iConstForEach(Array, i, &d->runs) { 1163 iConstForEach(Array, i, &d->runs) {
1032 const iAttributedRun *run = i.value; 1164 const iAttributedRun *run = i.value;
1033 printf(" %zu %s log:%d...%d vis:%d...%d {%s}\n", index_ArrayConstIterator(&i), 1165 printf(" %zu %s fnt:%d log:%d...%d vis:%d...%d {%s}\n",
1034 run->flags.isRTL ? "<-" : "->", 1166 index_ArrayConstIterator(&i),
1167 run->attrib.isRTL ? "<-" : "->",
1168 fontId_Text_(run->font),
1035 run->logical.start, run->logical.end - 1, 1169 run->logical.start, run->logical.end - 1,
1036 logToVis[run->logical.start], logToVis[run->logical.end - 1], 1170 logToVis[run->logical.start], logToVis[run->logical.end - 1],
1037 cstr_Rangecc(sourceRange_AttributedText_(d, run->logical))); 1171 cstr_Rangecc(sourceRange_AttributedText_(d, run->logical)));
@@ -1039,12 +1173,15 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
1039#endif 1173#endif
1040} 1174}
1041 1175
1042void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor, 1176void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, int colorId,
1043 int baseDir, iChar overrideChar) { 1177 int baseDir, iFont *baseFont, int baseColorId, iChar overrideChar) {
1044 d->source = text; 1178 d->source = text;
1045 d->maxLen = maxLen ? maxLen : iInvalidSize; 1179 d->maxLen = maxLen ? maxLen : iInvalidSize;
1046 d->font = font; 1180 d->font = font;
1047 d->fgColor = fgColor; 1181 d->colorId = colorId;
1182 d->baseFont = baseFont;
1183 d->baseColorId = baseColorId;
1184 d->isBaseRTL = iFalse;
1048 init_Array(&d->runs, sizeof(iAttributedRun)); 1185 init_Array(&d->runs, sizeof(iAttributedRun));
1049 init_Array(&d->logical, sizeof(iChar)); 1186 init_Array(&d->logical, sizeof(iChar));
1050 init_Array(&d->visual, sizeof(iChar)); 1187 init_Array(&d->visual, sizeof(iChar));
@@ -1052,7 +1189,6 @@ void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont
1052 init_Array(&d->visualToLogical, sizeof(int)); 1189 init_Array(&d->visualToLogical, sizeof(int));
1053 init_Array(&d->logicalToSourceOffset, sizeof(int)); 1190 init_Array(&d->logicalToSourceOffset, sizeof(int));
1054 d->bidiLevels = NULL; 1191 d->bidiLevels = NULL;
1055 d->isBaseRTL = iFalse;
1056 prepare_AttributedText_(d, baseDir, overrideChar); 1192 prepare_AttributedText_(d, baseDir, overrideChar);
1057} 1193}
1058 1194
@@ -1112,7 +1248,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1112 LAGRANGE_RASTER_DEPTH, 1248 LAGRANGE_RASTER_DEPTH,
1113 LAGRANGE_RASTER_FORMAT); 1249 LAGRANGE_RASTER_FORMAT);
1114 SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE); 1250 SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE);
1115 SDL_SetSurfacePalette(buf, activeText_->grayscale); 1251 SDL_SetSurfacePalette(buf, glyphPalette_());
1116 } 1252 }
1117 SDL_Surface *surfaces[2] = { 1253 SDL_Surface *surfaces[2] = {
1118 !isRasterized_Glyph_(glyph, 0) ? 1254 !isRasterized_Glyph_(glyph, 0) ?
@@ -1204,7 +1340,7 @@ static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
1204 iArray glyphIndices; 1340 iArray glyphIndices;
1205 init_Array(&glyphIndices, sizeof(uint32_t)); 1341 init_Array(&glyphIndices, sizeof(uint32_t));
1206 iAttributedText attrText; 1342 iAttributedText attrText;
1207 init_AttributedText(&attrText, text, 0, d, (iColor){}, 0, 0); 1343 init_AttributedText(&attrText, text, 0, d, none_ColorId, 0, d, none_ColorId, 0);
1208 /* We use AttributedText here so the font lookup matches the behavior during text drawing -- 1344 /* We use AttributedText here so the font lookup matches the behavior during text drawing --
1209 glyphs may be selected from a font that's different than `d`. */ 1345 glyphs may be selected from a font that's different than `d`. */
1210 const iChar *logicalText = constData_Array(&attrText.logical); 1346 const iChar *logicalText = constData_Array(&attrText.logical);
@@ -1259,17 +1395,17 @@ struct Impl_RunArgs {
1259 /* TODO: Cleanup using TextMetrics 1395 /* TODO: Cleanup using TextMetrics
1260 Use TextMetrics output pointer instead of return value & cursorAdvance_out. */ 1396 Use TextMetrics output pointer instead of return value & cursorAdvance_out. */
1261 iInt2 * cursorAdvance_out; 1397 iInt2 * cursorAdvance_out;
1262// const char ** continueFrom_out;
1263 int * runAdvance_out; 1398 int * runAdvance_out;
1264}; 1399};
1265 1400
1266static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int advance, iBool isBaseRTL) { 1401static iBool notify_WrapText_(iWrapText *d, const char *ending, iTextAttrib attrib,
1402 int origin, int advance) {
1267 if (d && d->wrapFunc && d->wrapRange_.start) { 1403 if (d && d->wrapFunc && d->wrapRange_.start) {
1268 /* `wrapRange_` uses logical indices. */ 1404 /* `wrapRange_` uses logical indices. */
1269 const char *end = ending ? ending : d->wrapRange_.end; 1405 const char *end = ending ? ending : d->wrapRange_.end;
1270 iRangecc range = { d->wrapRange_.start, end }; 1406 iRangecc range = { d->wrapRange_.start, end };
1271 iAssert(range.start <= range.end); 1407 iAssert(range.start <= range.end);
1272 const iBool result = d->wrapFunc(d, range, origin, advance, isBaseRTL); 1408 const iBool result = d->wrapFunc(d, range, attrib, origin, advance);
1273 if (result) { 1409 if (result) {
1274 d->wrapRange_.start = end; 1410 d->wrapRange_.start = end;
1275 } 1411 }
@@ -1283,7 +1419,7 @@ static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int
1283 1419
1284float horizKern_Font_(iFont *d, uint32_t glyph1, uint32_t glyph2) { 1420float horizKern_Font_(iFont *d, uint32_t glyph1, uint32_t glyph2) {
1285#if defined (LAGRANGE_ENABLE_KERNING) 1421#if defined (LAGRANGE_ENABLE_KERNING)
1286 if (!enableKerning_Text || d->family != nunito_TextFont) { 1422 if (!enableKerning_Text || ~d->fontSpec->flags & fixNunitoKerning_FontSpecFlag) {
1287 return 0.0f; 1423 return 0.0f;
1288 } 1424 }
1289 if (glyph1 && glyph2) { 1425 if (glyph1 && glyph2) {
@@ -1335,7 +1471,7 @@ static void deinit_GlyphBuffer_(iGlyphBuffer *d) {
1335 1471
1336static void shape_GlyphBuffer_(iGlyphBuffer *d) { 1472static void shape_GlyphBuffer_(iGlyphBuffer *d) {
1337 if (!d->glyphInfo) { 1473 if (!d->glyphInfo) {
1338 hb_shape(d->font->hbFont, d->hb, NULL, 0); 1474 hb_shape(d->font->fontFile->hbFont, d->hb, NULL, 0);
1339 d->glyphInfo = hb_buffer_get_glyph_infos(d->hb, &d->glyphCount); 1475 d->glyphInfo = hb_buffer_get_glyph_infos(d->hb, &d->glyphCount);
1340 d->glyphPos = hb_buffer_get_glyph_positions(d->hb, &d->glyphCount); 1476 d->glyphPos = hb_buffer_get_glyph_positions(d->hb, &d->glyphCount);
1341 } 1477 }
@@ -1389,15 +1525,18 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1389 float xCursor = 0.0f; 1525 float xCursor = 0.0f;
1390 float yCursor = 0.0f; 1526 float yCursor = 0.0f;
1391 float xCursorMax = 0.0f; 1527 float xCursorMax = 0.0f;
1392 const iBool isMonospaced = d->isMonospaced; 1528 const iBool isMonospaced = isMonospaced_Font(d);
1393 iWrapText *wrap = args->wrap; 1529 iWrapText *wrap = args->wrap;
1394 iAssert(args->text.end >= args->text.start); 1530 iAssert(args->text.end >= args->text.start);
1395 /* Split the text into a number of attributed runs that specify exactly which 1531 /* Split the text into a number of attributed runs that specify exactly which
1396 font is used and other attributes such as color. (HarfBuzz shaping is done 1532 font is used and other attributes such as color. (HarfBuzz shaping is done
1397 with one specific font.) */ 1533 with one specific font.) */
1398 iAttributedText attrText; 1534 iAttributedText attrText;
1399 init_AttributedText(&attrText, args->text, args->maxLen, d, get_Color(args->color), 1535 init_AttributedText(&attrText, args->text, args->maxLen, d, args->color,
1400 args->baseDir, wrap ? wrap->overrideChar : 0); 1536 args->baseDir,
1537 activeText_->baseFontId >= 0 ? font_Text_(activeText_->baseFontId) : d,
1538 activeText_->baseColorId,
1539 wrap ? wrap->overrideChar : 0);
1401 if (wrap) { 1540 if (wrap) {
1402 wrap->baseDir = attrText.isBaseRTL ? -1 : +1; 1541 wrap->baseDir = attrText.isBaseRTL ? -1 : +1;
1403 /* TODO: Duplicated args? */ 1542 /* TODO: Duplicated args? */
@@ -1448,6 +1587,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1448 iRangei wrapPosRange = { 0, textLen }; 1587 iRangei wrapPosRange = { 0, textLen };
1449 int wrapResumePos = textLen; /* logical position where next line resumes */ 1588 int wrapResumePos = textLen; /* logical position where next line resumes */
1450 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */ 1589 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */
1590 iTextAttrib attrib = { .colorId = args->color, .isBaseRTL = attrText.isBaseRTL };
1591 iTextAttrib wrapAttrib = attrib;
1592 iTextAttrib lastAttrib = attrib;
1451 const int layoutBound = (wrap ? wrap->maxWidth : 0); 1593 const int layoutBound = (wrap ? wrap->maxWidth : 0);
1452 iBool isFirst = iTrue; 1594 iBool isFirst = iTrue;
1453 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2()); 1595 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2());
@@ -1467,10 +1609,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1467 wrap->hitPoint.y < orig.y + yCursor + d->height); 1609 wrap->hitPoint.y < orig.y + yCursor + d->height);
1468 iBool wasCharHit = iFalse; /* on this line */ 1610 iBool wasCharHit = iFalse; /* on this line */
1469 float breakAdvance = -1.0f; 1611 float breakAdvance = -1.0f;
1612 size_t breakRunIndex = iInvalidPos;
1470 iAssert(wrapPosRange.end == textLen); 1613 iAssert(wrapPosRange.end == textLen);
1471 /* Determine ends of wrapRuns and wrapVisRange. */ 1614 /* Determine ends of wrapRuns and wrapVisRange. */
1615 int safeBreakPos = -1;
1472 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { 1616 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1473 const iAttributedRun *run = at_Array(&attrText.runs, runIndex); 1617 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1618 /* Update the attributes. */
1474 if (run->flags.isLineBreak) { 1619 if (run->flags.isLineBreak) {
1475 if (checkHitChar && 1620 if (checkHitChar &&
1476 wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) { 1621 wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) {
@@ -1480,7 +1625,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1480 wrapResumePos = run->logical.end; 1625 wrapResumePos = run->logical.end;
1481 wrapRuns.end = runIndex; 1626 wrapRuns.end = runIndex;
1482 wrapResumeRunIndex = runIndex + 1; 1627 wrapResumeRunIndex = runIndex + 1;
1483 //yCursor += d->height;
1484 break; 1628 break;
1485 } 1629 }
1486 wrapResumeRunIndex = runCount; 1630 wrapResumeRunIndex = runCount;
@@ -1488,10 +1632,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1488 iGlyphBuffer *buf = at_Array(&buffers, runIndex); 1632 iGlyphBuffer *buf = at_Array(&buffers, runIndex);
1489 iAssert(run->font == buf->font); 1633 iAssert(run->font == buf->font);
1490 shape_GlyphBuffer_(buf); 1634 shape_GlyphBuffer_(buf);
1491 int safeBreakPos = -1;
1492 iChar prevCh = 0; 1635 iChar prevCh = 0;
1636 lastAttrib = run->attrib;
1637// printf("checking run %zu...\n", runIndex);
1493 for (unsigned int ir = 0; ir < buf->glyphCount; ir++) { 1638 for (unsigned int ir = 0; ir < buf->glyphCount; ir++) {
1494 const int i = (run->flags.isRTL ? buf->glyphCount - ir - 1 : ir); 1639 const int i = (run->attrib.isRTL ? buf->glyphCount - ir - 1 : ir);
1495 const hb_glyph_info_t *info = &buf->glyphInfo[i]; 1640 const hb_glyph_info_t *info = &buf->glyphInfo[i];
1496 const hb_codepoint_t glyphId = info->codepoint; 1641 const hb_codepoint_t glyphId = info->codepoint;
1497 const int logPos = info->cluster; 1642 const int logPos = info->cluster;
@@ -1516,23 +1661,27 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1516 iAssert(xAdvance >= 0); 1661 iAssert(xAdvance >= 0);
1517 if (args->wrap->mode == word_WrapTextMode) { 1662 if (args->wrap->mode == word_WrapTextMode) {
1518 /* When word wrapping, only consider certain places breakable. */ 1663 /* When word wrapping, only consider certain places breakable. */
1519 if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { 1664 if ((prevCh == '-' || prevCh == '/') && !isPunct_Char(ch)) {
1520 safeBreakPos = logPos; 1665 safeBreakPos = logPos;
1521 breakAdvance = wrapAdvance; 1666 breakAdvance = wrapAdvance;
1667 breakRunIndex = runIndex;
1668// printf("sbp:%d breakAdv_A:%f\n", safeBreakPos, breakAdvance);
1522 // isSoftHyphenBreak = iFalse; 1669 // isSoftHyphenBreak = iFalse;
1523 } 1670 }
1524 else if (isSpace_Char(ch)) { 1671 else if (isSpace_Char(ch)) {
1525 safeBreakPos = logPos; 1672 safeBreakPos = logPos;
1526 breakAdvance = wrapAdvance; 1673 breakAdvance = wrapAdvance;
1674 breakRunIndex = runIndex;
1675// printf("sbp:%d breakAdv_B:%f\n", safeBreakPos, breakAdvance);
1527 // isSoftHyphenBreak = iFalse; 1676 // isSoftHyphenBreak = iFalse;
1528 } 1677 }
1529 prevCh = ch; 1678 prevCh = ch;
1530 } 1679 }
1531 else { 1680 else {
1532 //if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { 1681 safeBreakPos = logPos;
1533 safeBreakPos = logPos; 1682 breakAdvance = wrapAdvance;
1534 breakAdvance = wrapAdvance; 1683 breakRunIndex = runIndex;
1535 //} 1684 wrapAttrib = run->attrib;
1536 } 1685 }
1537 if (isHitPointOnThisLine) { 1686 if (isHitPointOnThisLine) {
1538 if (wrap->hitPoint.x >= orig.x + wrapAdvance && 1687 if (wrap->hitPoint.x >= orig.x + wrapAdvance &&
@@ -1548,6 +1697,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1548 if (wrap->maxWidth > 0 && 1697 if (wrap->maxWidth > 0 &&
1549 wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > 1698 wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x >
1550 args->wrap->maxWidth) { 1699 args->wrap->maxWidth) {
1700// printf("out of room at lp:%d! safeBreakPos:%d (idx:%zu) breakAdv:%f\n",
1701// logPos, safeBreakPos,
1702// breakRunIndex, breakAdvance);
1551 if (safeBreakPos >= 0) { 1703 if (safeBreakPos >= 0) {
1552 wrapPosRange.end = safeBreakPos; 1704 wrapPosRange.end = safeBreakPos;
1553 } 1705 }
@@ -1563,16 +1715,19 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1563 } 1715 }
1564 wrapPosRange.end = logPos; 1716 wrapPosRange.end = logPos;
1565 breakAdvance = wrapAdvance; 1717 breakAdvance = wrapAdvance;
1718 breakRunIndex = runIndex;
1566 } 1719 }
1567 wrapResumePos = wrapPosRange.end; 1720 wrapResumePos = wrapPosRange.end;
1568 if (args->wrap->mode != anyCharacter_WrapTextMode) { 1721 if (args->wrap->mode != anyCharacter_WrapTextMode) {
1569 while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) { 1722 while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) {
1570 wrapResumePos++; /* skip space */ 1723 wrapResumePos++; /* skip space */
1571 } 1724 }
1572 } 1725 }
1573 wrapRuns.end = runIndex + 1; /* still includes this run */ 1726 wrapRuns.end = breakRunIndex + 1; /* still includes this run */
1574 wrapResumeRunIndex = runIndex; /* ...but continue from the same one */ 1727 wrapResumeRunIndex = breakRunIndex; /* ...but continue from the same one */
1728// printf("-> wrapAdv:%f (breakAdv:%f)\n", wrapAdvance, breakAdvance);
1575 wrapAdvance = breakAdvance; 1729 wrapAdvance = breakAdvance;
1730// printf("wrapResumePos:%d\n", wrapResumePos);
1576 break; 1731 break;
1577 } 1732 }
1578 wrapAdvance += xAdvance; 1733 wrapAdvance += xAdvance;
@@ -1584,6 +1739,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1584 buf->glyphInfo[i + 1].codepoint); 1739 buf->glyphInfo[i + 1].codepoint);
1585 } 1740 }
1586 } 1741 }
1742// printf("...finished checking run %zu\n", runIndex);
1587 } 1743 }
1588 if (isHitPointOnThisLine && wrap->hitPoint.x >= orig.x + wrapAdvance) { 1744 if (isHitPointOnThisLine && wrap->hitPoint.x >= orig.x + wrapAdvance) {
1589 /* On the right side. */ 1745 /* On the right side. */
@@ -1614,7 +1770,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1614 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { 1770 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1615 const iAttributedRun *run = at_Array(&attrText.runs, runIndex); 1771 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1616 if (!attrText.isBaseRTL) { /* left-to-right */ 1772 if (!attrText.isBaseRTL) { /* left-to-right */
1617 if (run->flags.isRTL) { 1773 if (run->attrib.isRTL) {
1618 if (oppositeInsertIndex == iInvalidPos) { 1774 if (oppositeInsertIndex == iInvalidPos) {
1619 oppositeInsertIndex = size_Array(&runOrder); 1775 oppositeInsertIndex = size_Array(&runOrder);
1620 } 1776 }
@@ -1626,7 +1782,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1626 } 1782 }
1627 } 1783 }
1628 else { /* right-to-left */ 1784 else { /* right-to-left */
1629 if (!run->flags.isRTL) { 1785 if (!run->attrib.isRTL) {
1630 if (oppositeInsertIndex == iInvalidPos) { 1786 if (oppositeInsertIndex == iInvalidPos) {
1631 oppositeInsertIndex = 0; 1787 oppositeInsertIndex = 0;
1632 } 1788 }
@@ -1638,16 +1794,17 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1638 } 1794 }
1639 } 1795 }
1640 } 1796 }
1641 }
1642#if 0 1797#if 0
1643 printf("Run order: "); 1798 printf("Run order: ");
1644 iConstForEach(Array, ro, &runOrder) { 1799 iConstForEach(Array, ro, &runOrder) {
1645 const size_t *idx = ro.value; 1800 const size_t *idx = ro.value;
1646 printf("%zu {%s}\n", *idx, 1801 printf("%zu {%s}\n", *idx,
1647 cstr_Rangecc(sourceRange_AttributedText_(&attrText, ((const iAttributedRun *) at_Array(&attrText.runs, *idx))->logical))); 1802 cstr_Rangecc(sourceRange_AttributedText_(&attrText, ((const iAttributedRun *) at_Array(&attrText.runs, *idx))->logical)));
1648 } 1803 }
1649 printf("\n"); 1804 printf("\n");
1650#endif 1805#endif
1806
1807 }
1651 iAssert(size_Array(&runOrder) == size_Range(&wrapRuns)); 1808 iAssert(size_Array(&runOrder) == size_Range(&wrapRuns));
1652 /* Alignment. */ 1809 /* Alignment. */
1653 int origin = 0; 1810 int origin = 0;
@@ -1656,20 +1813,17 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1656 if (layoutBound > 0) { 1813 if (layoutBound > 0) {
1657 origin = layoutBound - wrapAdvance; 1814 origin = layoutBound - wrapAdvance;
1658 } 1815 }
1659// else if (args->xposLayoutBound > 0) {
1660// iAssert(mode & draw_RunMode);
1661//// origin = args->xposLayoutBound - orig.x - wrapAdvance * 2;
1662// }
1663 } 1816 }
1664 /* Make a callback for each wrapped line. */ 1817 /* Make a callback for each wrapped line. */
1665 if (wrap && wrap->wrapFunc && 1818 if (wrap && wrap->wrapFunc &&
1666 !notify_WrapText_(args->wrap, 1819 !notify_WrapText_(args->wrap,
1667 sourcePtr_AttributedText_(&attrText, wrapResumePos), 1820 sourcePtr_AttributedText_(&attrText, wrapResumePos),
1821 wrapAttrib,
1668 origin, 1822 origin,
1669 iRound(wrapAdvance), 1823 iRound(wrapAdvance))) {
1670 attrText.isBaseRTL)) {
1671 willAbortDueToWrap = iTrue; 1824 willAbortDueToWrap = iTrue;
1672 } 1825 }
1826 wrapAttrib = lastAttrib;
1673 xCursor = origin; 1827 xCursor = origin;
1674 /* We have determined a possible wrap position and alignment for the work runs, 1828 /* We have determined a possible wrap position and alignment for the work runs,
1675 so now we can process the glyphs. */ 1829 so now we can process the glyphs. */
@@ -1722,6 +1876,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1722 orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, 1876 orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y,
1723 glyph->rect[hoff].size.x, 1877 glyph->rect[hoff].size.x,
1724 glyph->rect[hoff].size.y }; 1878 glyph->rect[hoff].size.y };
1879 /* Align baselines of different fonts. */
1880 if (run->font != attrText.baseFont &&
1881 ~run->font->fontSpec->flags & auxiliary_FontSpecFlag) {
1882 const int bl1 = attrText.baseFont->baseline + attrText.baseFont->vertOffset;
1883 const int bl2 = run->font->baseline + run->font->vertOffset;
1884 dst.y += bl1 - bl2;
1885 }
1725 if (mode & visualFlag_RunMode) { 1886 if (mode & visualFlag_RunMode) {
1726 if (isEmpty_Rect(bounds)) { 1887 if (isEmpty_Rect(bounds)) {
1727 bounds = init_Rect(dst.x, dst.y, dst.w, dst.h); 1888 bounds = init_Rect(dst.x, dst.y, dst.w, dst.h);
@@ -1742,7 +1903,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1742 iAssert(isRasterized_Glyph_(glyph, hoff)); 1903 iAssert(isRasterized_Glyph_(glyph, hoff));
1743 } 1904 }
1744 if (~mode & permanentColorFlag_RunMode) { 1905 if (~mode & permanentColorFlag_RunMode) {
1745 const iColor clr = run->fgColor; 1906 const iColor clr = fgColor_AttributedRun_(run);
1746 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); 1907 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
1747 if (args->mode & fillBackground_RunMode) { 1908 if (args->mode & fillBackground_RunMode) {
1748 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); 1909 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
@@ -1825,6 +1986,11 @@ int lineHeight_Text(int fontId) {
1825 return font_Text_(fontId)->height; 1986 return font_Text_(fontId)->height;
1826} 1987}
1827 1988
1989float emRatio_Text(int fontId) {
1990 const iFont *font = font_Text_(fontId);
1991 return font->emAdvance / font->height;
1992}
1993
1828iTextMetrics measureRange_Text(int fontId, iRangecc text) { 1994iTextMetrics measureRange_Text(int fontId, iRangecc text) {
1829 if (isEmpty_Range(&text)) { 1995 if (isEmpty_Range(&text)) {
1830 return (iTextMetrics){ init_Rect(0, 0, 0, lineHeight_Text(fontId)), zero_I2() }; 1996 return (iTextMetrics){ init_Rect(0, 0, 0, lineHeight_Text(fontId)), zero_I2() };
@@ -1858,9 +2024,9 @@ static int runFlagsFromId_(enum iFontId fontId) {
1858 return runFlags; 2024 return runFlags;
1859} 2025}
1860 2026
1861static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int origin, int advance, 2027static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, iTextAttrib attrib, int origin,
1862 iBool isBaseRTL) { 2028 int advance) {
1863 iUnused(origin, advance, isBaseRTL); 2029 iUnused(attrib, origin, advance);
1864 *((const char **) d->context) = range.end; 2030 *((const char **) d->context) = range.end;
1865 return iFalse; /* just one line */ 2031 return iFalse; /* just one line */
1866} 2032}