diff options
Diffstat (limited to 'src/ui/text.c')
-rw-r--r-- | src/ui/text.c | 668 |
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 | ||
124 | static iGlyph *glyph_Font_(iFont *d, iChar ch); | 124 | static iGlyph *glyph_Font_(iFont *d, iChar ch); |
125 | 125 | ||
126 | struct Impl_Font { | 126 | iDeclareType(GlyphTable) |
127 | iBlock * data; | 127 | |
128 | enum iTextFont family; | 128 | struct 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 | ||
147 | static iFont *font_Text_(enum iFontId id); | 135 | static void clearGlyphs_GlyphTable_(iGlyphTable *d) { |
148 | 136 | if (d) { | |
149 | #if 0 | 137 | iForEach(Hash, i, &d->glyphs) { |
150 | static 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 | ||
157 | static void init_Font(iFont *d, const iBlock *data, int height, float scale, | 144 | static 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 | |||
149 | static void deinit_GlyphTable(iGlyphTable *d) { | ||
150 | clearGlyphs_GlyphTable_(d); | ||
151 | deinit_Hash(&d->glyphs); | ||
152 | } | ||
153 | |||
154 | iDefineTypeConstruction(GlyphTable) | ||
155 | |||
156 | struct 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 | |||
167 | iLocalDef iBool isMonospaced_Font(const iFont *d) { | ||
168 | return (d->fontSpec->flags & monospace_FontSpecFlag) != 0; | ||
169 | } | ||
170 | |||
171 | static iFont *font_Text_(enum iFontId id); | ||
172 | |||
173 | static 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 | |||
238 | static 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 | ||
245 | static void deinit_Font(iFont *d) { | 219 | static 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 | ||
259 | static uint32_t glyphIndex_Font_(iFont *d, iChar ch) { | 223 | static 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 | ||
281 | struct Impl_Text { | 249 | struct 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 | ||
296 | iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) | 269 | iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) |
297 | 270 | ||
298 | static iText *activeText_; | 271 | static iText *activeText_; |
299 | static iBlock *userFont_; | 272 | |
273 | static 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 | |||
298 | iLocalDef iFont *font_Text_(enum iFontId id) { | ||
299 | return at_Array(&activeText_->fonts, id & mask_FontId); | ||
300 | } | ||
301 | |||
302 | static enum iFontId fontId_Text_(const iFont *font) { | ||
303 | return (enum iFontId) (font - (const iFont *) constData_Array(&activeText_->fonts)); | ||
304 | } | ||
305 | |||
306 | iLocalDef enum iFontSize sizeId_Text_(const iFont *d) { | ||
307 | return fontId_Text_(d) % max_FontSize; | ||
308 | } | ||
309 | |||
310 | iLocalDef enum iFontStyle styleId_Text_(const iFont *d) { | ||
311 | return (fontId_Text_(d) / max_FontSize) % max_FontStyle; | ||
312 | } | ||
313 | |||
314 | static 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 | ||
301 | static void initFonts_Text_(iText *d) { | 319 | static 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 | ||
448 | static void deinitFonts_Text_(iText *d) { | 470 | static 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 | ||
454 | static int maxGlyphHeight_Text_(const iText *d) { | 477 | static 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 | ||
458 | static void initCache_Text_(iText *d) { | 482 | static 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 | ||
493 | void loadUserFonts_Text(void) { | 518 | void 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 | ||
512 | void init_Text(iText *d, SDL_Renderer *render) { | 538 | void 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 | ||
531 | void deinit_Text(iText *d) { | 568 | void 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 | ||
539 | void setCurrent_Text(iText *d) { | 578 | void 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 | ||
547 | void setContentFont_Text(iText *d, enum iTextFont font) { | 586 | void 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 | ||
554 | void setHeadingFont_Text(iText *d, enum iTextFont font) { | 591 | void 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 | ||
561 | void setContentFontSize_Text(iText *d, float fontSizeFactor) { | 595 | void 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 | ||
570 | static void resetCache_Text_(iText *d) { | 604 | static 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 | ||
585 | iLocalDef iFont *font_Text_(enum iFontId id) { | 619 | static SDL_Palette *glyphPalette_(void) { |
586 | return &activeText_->fonts[id & mask_FontId]; | 620 | return prefs_App()->fontSmoothing ? activeText_->grayscale : activeText_->blackAndWhite; |
587 | } | 621 | } |
588 | 622 | ||
589 | static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { | 623 | static 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) { | |||
636 | static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { | 670 | static 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 | ||
741 | static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) { | 805 | static 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) { | |||
793 | iDeclareType(AttributedRun) | 858 | iDeclareType(AttributedRun) |
794 | 859 | ||
795 | struct Impl_AttributedRun { | 860 | struct 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 | ||
872 | static 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 | |||
882 | static void setFgColor_AttributedRun_(iAttributedRun *d, int colorId) { | ||
883 | d->attrib.colorId = colorId; | ||
884 | d->fgColor_.a = 0; | ||
885 | } | ||
886 | |||
806 | iDeclareType(AttributedText) | 887 | iDeclareType(AttributedText) |
807 | iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, | 888 | iDeclareTypeConstructionArgs(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 | ||
810 | struct Impl_AttributedText { | 892 | struct 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 | ||
825 | iDefineTypeConstructionArgs(AttributedText, | 909 | iDefineTypeConstructionArgs(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 | ||
830 | static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) { | 916 | static 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 | ||
846 | static 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 | |||
857 | static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) { | 931 | static 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 | ||
869 | static enum iFontId fontId_Text_(const iFont *font) { | 949 | int 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 | |||
955 | int 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 | |||
961 | int 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 | ||
873 | static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { | 967 | static 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 | ||
1042 | void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor, | 1176 | void 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 | ||
1266 | static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int advance, iBool isBaseRTL) { | 1401 | static 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 | ||
1284 | float horizKern_Font_(iFont *d, uint32_t glyph1, uint32_t glyph2) { | 1420 | float 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 | ||
1336 | static void shape_GlyphBuffer_(iGlyphBuffer *d) { | 1472 | static 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 | ||
1989 | float emRatio_Text(int fontId) { | ||
1990 | const iFont *font = font_Text_(fontId); | ||
1991 | return font->emAdvance / font->height; | ||
1992 | } | ||
1993 | |||
1828 | iTextMetrics measureRange_Text(int fontId, iRangecc text) { | 1994 | iTextMetrics 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 | ||
1861 | static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int origin, int advance, | 2027 | static 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 | } |