summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-10-13 09:49:44 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-10-13 09:49:44 +0300
commit6b931c95725eef2ebb7e831c4017d3d67b33294f (patch)
tree254595663d93c0dcc33baad475fb2f6c3ddeec40 /src/ui
parentdd0a8798f32bf192ed703c6c512d4a76c4d407bc (diff)
Text attributes that change inside a run
These changes concern the situation when the attributes of text (i.e., font, color) are changed via escape sequences. The concept of "base attributes" was added so that the low-level text renderer knows which font/color to set when a "reset" escape sequence is encountered. This depends on what kind of text is being renderer, e.g., preformatted or regular paragraphs. The base attributes were added as variables in Text because it was getting unwieldy to pass all the information via the draw/measure/WrapText functions. GmDocument now has a GmTheme struct that collects the font and color information into a single place.
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c40
-rw-r--r--src/ui/inputwidget.c18
-rw-r--r--src/ui/text.c196
-rw-r--r--src/ui/text.h26
4 files changed, 180 insertions, 100 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 8c87ba1a..8b2d6a5a 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -569,7 +569,7 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
569 } 569 }
570 d->visibleRuns.end = run; 570 d->visibleRuns.end = run;
571 } 571 }
572 if (run->preId) { 572 if (preId_GmRun(run)) {
573 pushBack_PtrArray(&d->visiblePre, run); 573 pushBack_PtrArray(&d->visiblePre, run);
574 if (run->flags & wide_GmRunFlag) { 574 if (run->flags & wide_GmRunFlag) {
575 pushBack_PtrArray(&d->visibleWideRuns, run); 575 pushBack_PtrArray(&d->visibleWideRuns, run);
@@ -635,14 +635,14 @@ static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) {
635} 635}
636 636
637static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { 637static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) {
638 if (run->preId && run->flags & wide_GmRunFlag) { 638 if (preId_GmRun(run) && run->flags & wide_GmRunFlag) {
639 if (d->animWideRunId == run->preId) { 639 if (d->animWideRunId == preId_GmRun(run)) {
640 return -value_Anim(&d->animWideRunOffset); 640 return -value_Anim(&d->animWideRunOffset);
641 } 641 }
642 const size_t numOffsets = size_Array(&d->wideRunOffsets); 642 const size_t numOffsets = size_Array(&d->wideRunOffsets);
643 const int *offsets = constData_Array(&d->wideRunOffsets); 643 const int *offsets = constData_Array(&d->wideRunOffsets);
644 if (run->preId <= numOffsets) { 644 if (preId_GmRun(run) <= numOffsets) {
645 return -offsets[run->preId - 1]; 645 return -offsets[preId_GmRun(run) - 1];
646 } 646 }
647 } 647 }
648 return 0; 648 return 0;
@@ -731,7 +731,7 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
731 } 731 }
732 } 732 }
733 else if (d->hoverPre && 733 else if (d->hoverPre &&
734 preHasAltText_GmDocument(d->doc, d->hoverPre->preId) && 734 preHasAltText_GmDocument(d->doc, preId_GmRun(d->hoverPre)) &&
735 ~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { 735 ~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) {
736 setValueSpeed_Anim(&d->altTextOpacity, 1.0f, 1.5f); 736 setValueSpeed_Anim(&d->altTextOpacity, 1.0f, 1.5f);
737 if (!isFinished_Anim(&d->altTextOpacity)) { 737 if (!isFinished_Anim(&d->altTextOpacity)) {
@@ -1812,10 +1812,10 @@ static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos,
1812 maxWidth = iMax(maxWidth, width_Rect(r->visBounds)); 1812 maxWidth = iMax(maxWidth, width_Rect(r->visBounds));
1813 } 1813 }
1814 const int maxOffset = maxWidth - documentWidth_DocumentWidget_(d) + d->pageMargin * gap_UI; 1814 const int maxOffset = maxWidth - documentWidth_DocumentWidget_(d) + d->pageMargin * gap_UI;
1815 if (size_Array(&d->wideRunOffsets) <= run->preId) { 1815 if (size_Array(&d->wideRunOffsets) <= preId_GmRun(run)) {
1816 resize_Array(&d->wideRunOffsets, run->preId + 1); 1816 resize_Array(&d->wideRunOffsets, preId_GmRun(run) + 1);
1817 } 1817 }
1818 int *offset = at_Array(&d->wideRunOffsets, run->preId - 1); 1818 int *offset = at_Array(&d->wideRunOffsets, preId_GmRun(run) - 1);
1819 const int oldOffset = *offset; 1819 const int oldOffset = *offset;
1820 *offset = iClamp(*offset + delta, 0, maxOffset); 1820 *offset = iClamp(*offset + delta, 0, maxOffset);
1821 /* Make sure the whole block gets redraw. */ 1821 /* Make sure the whole block gets redraw. */
@@ -1828,8 +1828,8 @@ static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos,
1828 d->foundMark = iNullRange; 1828 d->foundMark = iNullRange;
1829 } 1829 }
1830 if (duration) { 1830 if (duration) {
1831 if (d->animWideRunId != run->preId || isFinished_Anim(&d->animWideRunOffset)) { 1831 if (d->animWideRunId != preId_GmRun(run) || isFinished_Anim(&d->animWideRunOffset)) {
1832 d->animWideRunId = run->preId; 1832 d->animWideRunId = preId_GmRun(run);
1833 init_Anim(&d->animWideRunOffset, oldOffset); 1833 init_Anim(&d->animWideRunOffset, oldOffset);
1834 } 1834 }
1835 setValueEased_Anim(&d->animWideRunOffset, *offset, duration); 1835 setValueEased_Anim(&d->animWideRunOffset, *offset, duration);
@@ -3814,7 +3814,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3814 } 3814 }
3815 /* Fold/unfold a preformatted block. */ 3815 /* Fold/unfold a preformatted block. */
3816 if (~d->flags & selecting_DocumentWidgetFlag && d->hoverPre && 3816 if (~d->flags & selecting_DocumentWidgetFlag && d->hoverPre &&
3817 preIsFolded_GmDocument(d->doc, d->hoverPre->preId)) { 3817 preIsFolded_GmDocument(d->doc, preId_GmRun(d->hoverPre))) {
3818 return iTrue; 3818 return iTrue;
3819 } 3819 }
3820 /* Begin selecting a range of text. */ 3820 /* Begin selecting a range of text. */
@@ -3925,7 +3925,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3925 } 3925 }
3926 } 3926 }
3927 if (d->hoverPre) { 3927 if (d->hoverPre) {
3928 togglePreFold_DocumentWidget_(d, d->hoverPre->preId); 3928 togglePreFold_DocumentWidget_(d, preId_GmRun(d->hoverPre));
3929 return iTrue; 3929 return iTrue;
3930 } 3930 }
3931 if (d->hoverLink) { 3931 if (d->hoverLink) {
@@ -4124,7 +4124,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4124 4124
4125static void drawMark_DrawContext_(void *context, const iGmRun *run) { 4125static void drawMark_DrawContext_(void *context, const iGmRun *run) {
4126 iDrawContext *d = context; 4126 iDrawContext *d = context;
4127 if (run->mediaType == none_MediaType) { 4127 if (!isMedia_GmRun(run)) {
4128 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); 4128 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);
4129 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); 4129 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);
4130 } 4130 }
@@ -4249,7 +4249,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4249 } 4249 }
4250 return; 4250 return;
4251 } 4251 }
4252 else if (run->mediaType) { 4252 else if (isMedia_GmRun(run)) {
4253 /* Media UIs are drawn afterwards as a dynamic overlay. */ 4253 /* Media UIs are drawn afterwards as a dynamic overlay. */
4254 return; 4254 return;
4255 } 4255 }
@@ -4317,7 +4317,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4317 } 4317 }
4318 } 4318 }
4319 if (run->flags & altText_GmRunFlag) { 4319 if (run->flags & altText_GmRunFlag) {
4320 const iInt2 margin = preRunMargin_GmDocument(doc, run->preId); 4320 const iInt2 margin = preRunMargin_GmDocument(doc, preId_GmRun(run));
4321 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId); 4321 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId);
4322 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId); 4322 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId);
4323 drawWrapRange_Text(run->font, 4323 drawWrapRange_Text(run->font,
@@ -4368,11 +4368,17 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4368 height_Rect(run->visBounds), 4368 height_Rect(run->visBounds),
4369 tmQuoteIcon_ColorId); 4369 tmQuoteIcon_ColorId);
4370 } 4370 }
4371 /* Base attributes. */ {
4372 int f, c;
4373 runBaseAttributes_GmDocument(doc, run, &f, &c);
4374 setBaseAttributes_Text(f, c);
4375 }
4371 drawBoundRange_Text(run->font, 4376 drawBoundRange_Text(run->font,
4372 visPos, 4377 visPos,
4373 (run->isRTL ? -1 : 1) * width_Rect(run->visBounds), 4378 (run->isRTL ? -1 : 1) * width_Rect(run->visBounds),
4374 fg, 4379 fg,
4375 run->text); 4380 run->text);
4381 setBaseAttributes_Text(-1, -1);
4376 runDrawn:; 4382 runDrawn:;
4377 } 4383 }
4378 /* Presentation of links. */ 4384 /* Presentation of links. */
@@ -4944,7 +4950,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4944 /* Alt text. */ 4950 /* Alt text. */
4945 const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5; 4951 const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5;
4946 if (d->hoverAltPre && altTextOpacity > 0) { 4952 if (d->hoverAltPre && altTextOpacity > 0) {
4947 const iGmPreMeta *meta = preMeta_GmDocument(d->doc, d->hoverAltPre->preId); 4953 const iGmPreMeta *meta = preMeta_GmDocument(d->doc, preId_GmRun(d->hoverAltPre));
4948 if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag && 4954 if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag &&
4949 !isEmpty_Range(&meta->altText)) { 4955 !isEmpty_Range(&meta->altText)) {
4950 const int margin = 3 * gap_UI / 2; 4956 const int margin = 3 * gap_UI / 2;
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 874cf2b5..e20d6f17 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -2245,31 +2245,33 @@ struct Impl_MarkPainter {
2245 iRect lastMarkRect; 2245 iRect lastMarkRect;
2246}; 2246};
2247 2247
2248static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, 2248static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, iTextAttrib attrib,
2249 iBool isBaseRTL) { 2249 int origin, int advance) {
2250 iUnused(isBaseRTL);
2251 iMarkPainter *mp = wrapText->context; 2250 iMarkPainter *mp = wrapText->context;
2252 const iRanges mark = mp->mark; 2251 const iRanges mark = mp->mark;
2253 if (isEmpty_Range(&mark)) { 2252 if (isEmpty_Range(&mark)) {
2254 return iTrue; /* nothing marked */ 2253 return iTrue; /* nothing marked */
2255 } 2254 }
2255 int fontId = mp->d->font;
2256 /* TODO: Apply attrib on the font */
2256 const char *cstr = cstr_String(&mp->line->text); 2257 const char *cstr = cstr_String(&mp->line->text);
2257 const iRanges lineRange = { 2258 const iRanges lineRange = {
2258 wrappedText.start - cstr + mp->line->range.start, 2259 wrappedText.start - cstr + mp->line->range.start,
2259 wrappedText.end - cstr + mp->line->range.start 2260 wrappedText.end - cstr + mp->line->range.start
2260 }; 2261 };
2262 const int lineHeight = lineHeight_Text(mp->d->font);
2261 if (mark.end <= lineRange.start || mark.start >= lineRange.end) { 2263 if (mark.end <= lineRange.start || mark.start >= lineRange.end) {
2262 mp->pos.y += lineHeight_Text(mp->d->font); 2264 mp->pos.y += lineHeight;
2263 return iTrue; /* outside of mark */ 2265 return iTrue; /* outside of mark */
2264 } 2266 }
2265 iRect rect = { addX_I2(mp->pos, origin), init_I2(advance, lineHeight_Text(mp->d->font)) }; 2267 iRect rect = { addX_I2(mp->pos, origin), init_I2(advance, lineHeight) };
2266 if (mark.end < lineRange.end) { 2268 if (mark.end < lineRange.end) {
2267 /* Calculate where the mark ends. */ 2269 /* Calculate where the mark ends. */
2268 const iRangecc markedPrefix = { 2270 const iRangecc markedPrefix = {
2269 wrappedText.start, 2271 wrappedText.start,
2270 wrappedText.start + mark.end - lineRange.start 2272 wrappedText.start + mark.end - lineRange.start
2271 }; 2273 };
2272 rect.size.x = measureRange_Text(mp->d->font, markedPrefix).advance.x; 2274 rect.size.x = measureRange_Text(fontId, markedPrefix).advance.x;
2273 } 2275 }
2274 if (mark.start > lineRange.start) { 2276 if (mark.start > lineRange.start) {
2275 /* Calculate where the mark starts. */ 2277 /* Calculate where the mark starts. */
@@ -2277,10 +2279,10 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int or
2277 wrappedText.start, 2279 wrappedText.start,
2278 wrappedText.start + mark.start - lineRange.start 2280 wrappedText.start + mark.start - lineRange.start
2279 }; 2281 };
2280 adjustEdges_Rect(&rect, 0, 0, 0, measureRange_Text(mp->d->font, unmarkedPrefix).advance.x); 2282 adjustEdges_Rect(&rect, 0, 0, 0, measureRange_Text(fontId, unmarkedPrefix).advance.x);
2281 } 2283 }
2282 rect.size.x = iMax(gap_UI / 3, rect.size.x); 2284 rect.size.x = iMax(gap_UI / 3, rect.size.x);
2283 mp->pos.y += lineHeight_Text(mp->d->font); 2285 mp->pos.y += lineHeight;
2284 fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId); 2286 fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId);
2285 if (deviceType_App() != desktop_AppDeviceType) { 2287 if (deviceType_App() != desktop_AppDeviceType) {
2286 if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect; 2288 if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect;
diff --git a/src/ui/text.c b/src/ui/text.c
index fd865fbd..52c6c4e0 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -262,6 +262,8 @@ struct Impl_Text {
262 SDL_Palette * grayscale; 262 SDL_Palette * grayscale;
263 SDL_Palette * blackAndWhite; /* unsmoothed glyph palette */ 263 SDL_Palette * blackAndWhite; /* unsmoothed glyph palette */
264 iRegExp * ansiEscape; 264 iRegExp * ansiEscape;
265 int baseFontId; /* base attributes (for restoring via escapes) */
266 int baseColorId;
265}; 267};
266 268
267iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) 269iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
@@ -537,10 +539,10 @@ void init_Text(iText *d, SDL_Renderer *render) {
537 iText *oldActive = activeText_; 539 iText *oldActive = activeText_;
538 activeText_ = d; 540 activeText_ = d;
539 init_Array(&d->fonts, sizeof(iFont)); 541 init_Array(&d->fonts, sizeof(iFont));
540// d->contentFont = nunito_TextFont;
541// d->headingFont = nunito_TextFont;
542 d->contentFontSize = contentScale_Text_; 542 d->contentFontSize = contentScale_Text_;
543 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;
544 d->render = render; 546 d->render = render;
545 /* A grayscale palette for rasterized glyphs. */ { 547 /* A grayscale palette for rasterized glyphs. */ {
546 SDL_Color colors[256]; 548 SDL_Color colors[256];
@@ -581,13 +583,10 @@ void setOpacity_Text(float opacity) {
581 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);
582} 584}
583 585
584//void setFont_Text(iText *d, int fontId, const char *fontSpecId) { 586void setBaseAttributes_Text(int fontId, int colorId) {
585// setupFontVariants_Text_(d, findSpec_Fonts(fontSpecId), fontId); 587 activeText_->baseFontId = fontId;
586// if (d->contentFont != font) { 588 activeText_->baseColorId = colorId;
587// d->contentFont = font; 589}
588// resetFonts_Text(d);
589// }
590//}
591 590
592void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { 591void setDocumentFontSize_Text(iText *d, float fontSizeFactor) {
593 fontSizeFactor *= contentScale_Text_; 592 fontSizeFactor *= contentScale_Text_;
@@ -855,25 +854,45 @@ static iBool isControl_Char_(iChar c) {
855iDeclareType(AttributedRun) 854iDeclareType(AttributedRun)
856 855
857struct Impl_AttributedRun { 856struct Impl_AttributedRun {
858 iRangei logical; /* UTF-32 codepoint indices in the logical-order text */ 857 iRangei logical; /* UTF-32 codepoint indices in the logical-order text */
859 iFont * font; 858 iTextAttrib attrib;
860 iColor fgColor; 859 iFont *font;
860 iColor fgColor_; /* any RGB color; A > 0 */
861 struct { 861 struct {
862 uint8_t isLineBreak : 1; 862 uint8_t isLineBreak : 1;
863 uint8_t isRTL : 1; 863// uint8_t isRTL : 1;
864 uint8_t isArabic : 1; /* Arabic script detected */ 864 uint8_t isArabic : 1; /* Arabic script detected */
865 } flags; 865 } flags;
866}; 866};
867 867
868static iColor fgColor_AttributedRun_(const iAttributedRun *d) {
869 if (d->fgColor_.a) {
870 return d->fgColor_;
871 }
872 if (d->attrib.colorId == none_ColorId) {
873 return (iColor){ 255, 255, 255, 255 };
874 }
875 return get_Color(d->attrib.colorId);
876}
877
878static void setFgColor_AttributedRun_(iAttributedRun *d, int colorId) {
879 d->attrib.colorId = colorId;
880 d->fgColor_.a = 0;
881}
882
868iDeclareType(AttributedText) 883iDeclareType(AttributedText)
869iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, 884iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font,
870 iColor fgColor, int baseDir, iChar overrideChar) 885 int colorId, int baseDir, iFont *baseFont, int baseColorId,
886 iChar overrideChar)
871 887
872struct Impl_AttributedText { 888struct Impl_AttributedText {
873 iRangecc source; /* original source text */ 889 iRangecc source; /* original source text */
874 size_t maxLen; 890 size_t maxLen;
875 iFont * font; 891 iFont * font;
876 iColor fgColor; 892 int colorId;
893 iFont * baseFont;
894 int baseColorId;
895 iBool isBaseRTL;
877 iArray runs; 896 iArray runs;
878 iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */ 897 iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */
879 iArray visual; /* UTF-32 text in visual order (LTR) */ 898 iArray visual; /* UTF-32 text in visual order (LTR) */
@@ -881,13 +900,14 @@ struct Impl_AttributedText {
881 iArray visualToLogical; 900 iArray visualToLogical;
882 iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */ 901 iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */
883 char * bidiLevels; 902 char * bidiLevels;
884 iBool isBaseRTL;
885}; 903};
886 904
887iDefineTypeConstructionArgs(AttributedText, 905iDefineTypeConstructionArgs(AttributedText,
888 (iRangecc text, size_t maxLen, iFont *font, iColor fgColor, 906 (iRangecc text, size_t maxLen, iFont *font, int colorId,
889 int baseDir, iChar overrideChar), 907 int baseDir, iFont *baseFont, int baseColorId,
890 text, maxLen, font, fgColor, baseDir, overrideChar) 908 iChar overrideChar),
909 text, maxLen, font, colorId, baseDir, baseFont, baseColorId,
910 overrideChar)
891 911
892static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) { 912static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) {
893 const int *logToSource = constData_Array(&d->logicalToSourceOffset); 913 const int *logToSource = constData_Array(&d->logicalToSourceOffset);
@@ -916,16 +936,22 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i
916 run->logical.start = endAt; 936 run->logical.start = endAt;
917} 937}
918 938
919static iFont *withStyle_Font_(const iFont *d, enum iFontStyle styleId) { 939int fontWithSize_Text(int font, enum iFontSize sizeId) {
920 const int fontId = (fontId_Text_(d) / maxVariants_Fonts) * maxVariants_Fonts; 940 const int familyId = (font / maxVariants_Fonts) * maxVariants_Fonts;
921 const int sizeId = sizeId_Text_(d); 941 const int styleId = (font / max_FontSize) % max_FontStyle;
922 return font_Text_(FONT_ID(fontId, styleId, sizeId)); 942 return FONT_ID(familyId, styleId, sizeId);
943}
944
945int fontWithStyle_Text(int font, enum iFontStyle styleId) {
946 const int familyId = (font / maxVariants_Fonts) * maxVariants_Fonts;
947 const int sizeId = font % max_FontSize;
948 return FONT_ID(familyId, styleId, sizeId);
923} 949}
924 950
925static iFont *withFontId_Font_(const iFont *d, enum iFontId fontId) { 951int fontWithFamily_Text(int font, enum iFontId familyId) {
926 const int styleId = styleId_Text_(d); 952 const int styleId = (font / max_FontSize) % max_FontStyle;
927 const int sizeId = sizeId_Text_(d); 953 const int sizeId = font % max_FontSize;
928 return font_Text_(FONT_ID(fontId, styleId, sizeId)); 954 return FONT_ID(familyId, styleId, sizeId);
929} 955}
930 956
931static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { 957static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {
@@ -982,15 +1008,17 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
982 pushBack_Array(&d->logicalToVisual, &(int){ length }); 1008 pushBack_Array(&d->logicalToVisual, &(int){ length });
983 pushBack_Array(&d->visualToLogical, &(int){ length }); 1009 pushBack_Array(&d->visualToLogical, &(int){ length });
984 } 1010 }
985 iAttributedRun run = { .logical = { 0, length }, 1011 iAttributedRun run = {
986 .font = d->font, 1012 .logical = { 0, length },
987 .fgColor = d->fgColor }; 1013 .attrib = { .colorId = d->colorId, .isBaseRTL = d->isBaseRTL },
988 const int * logToSource = constData_Array(&d->logicalToSourceOffset); 1014 .font = d->font,
1015 };
1016 const int *logToSource = constData_Array(&d->logicalToSourceOffset);
989 const int * logToVis = constData_Array(&d->logicalToVisual); 1017 const int * logToVis = constData_Array(&d->logicalToVisual);
990 const iChar * logicalText = constData_Array(&d->logical); 1018 const iChar * logicalText = constData_Array(&d->logical);
991 iBool isRTL = d->isBaseRTL; 1019 iBool isRTL = d->isBaseRTL;
992 int numNonSpace = 0; 1020 int numNonSpace = 0;
993 iFont * activeFont = d->font; 1021 iFont * attribFont = d->font;
994 for (int pos = 0; pos < length; pos++) { 1022 for (int pos = 0; pos < length; pos++) {
995 const iChar ch = logicalText[pos]; 1023 const iChar ch = logicalText[pos];
996#if defined (LAGRANGE_ENABLE_FRIBIDI) 1024#if defined (LAGRANGE_ENABLE_FRIBIDI)
@@ -1010,7 +1038,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
1010#else 1038#else
1011 const iBool isNeutral = iTrue; 1039 const iBool isNeutral = iTrue;
1012#endif 1040#endif
1013 run.flags.isRTL = isRTL; 1041 run.attrib.isRTL = isRTL;
1014 if (ch == 0x1b) { /* ANSI escape. */ 1042 if (ch == 0x1b) { /* ANSI escape. */
1015 pos++; 1043 pos++;
1016 const char *srcPos = d->source.start + logToSource[pos]; 1044 const char *srcPos = d->source.start + logToSource[pos];
@@ -1020,21 +1048,36 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
1020 if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { 1048 if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) {
1021 finishRun_AttributedText_(d, &run, pos - 1); 1049 finishRun_AttributedText_(d, &run, pos - 1);
1022 const iRangecc sequence = capturedRange_RegExpMatch(&m, 1); 1050 const iRangecc sequence = capturedRange_RegExpMatch(&m, 1);
1051 /* TODO: Bold/italic attributes are assumed to be inside body text.
1052 We don't know what the current text style is supposed to be.
1053 That should be an additional attribute passed to WrapText, or a feature of
1054 WrapText that can be called both from here and in the run typesetter.
1055 The styling here is hardcoded to match `typesetOneLine_RunTypesetter_()`. */
1023 if (equal_Rangecc(sequence, "1")) { 1056 if (equal_Rangecc(sequence, "1")) {
1024 activeFont = withStyle_Font_(activeFont, bold_FontStyle); 1057 run.attrib.bold = iTrue;
1058 if (d->baseColorId == tmParagraph_ColorId) {
1059 setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId);
1060 }
1061 attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), bold_FontStyle));
1025 } 1062 }
1026 else if (equal_Rangecc(sequence, "3")) { 1063 else if (equal_Rangecc(sequence, "3")) {
1027 activeFont = withStyle_Font_(activeFont, italic_FontStyle); 1064 run.attrib.italic = iTrue;
1065 attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), italic_FontStyle));
1028 } 1066 }
1029 else if (equal_Rangecc(sequence, "4")) { 1067 else if (equal_Rangecc(sequence, "4")) {
1030 activeFont = withFontId_Font_(activeFont, monospace_FontId); 1068 run.attrib.monospace = iTrue;
1069 setFgColor_AttributedRun_(&run, tmPreformatted_ColorId);
1070 attribFont = font_Text_(fontWithFamily_Text(fontId_Text_(d->baseFont), monospace_FontId));
1031 } 1071 }
1032 else if (equal_Rangecc(sequence, "0")) { 1072 else if (equal_Rangecc(sequence, "0")) {
1033 activeFont = d->font; /* restore original */ 1073 run.attrib.bold = iFalse;
1034 run.fgColor = d->fgColor; 1074 run.attrib.italic = iFalse;
1075 run.attrib.monospace = iFalse;
1076 attribFont = d->baseFont;
1077 setFgColor_AttributedRun_(&run, d->baseColorId);
1035 } 1078 }
1036 else { 1079 else {
1037 run.fgColor = ansiForeground_Color(sequence, tmParagraph_ColorId); 1080 run.fgColor_ = ansiForeground_Color(sequence, tmParagraph_ColorId);
1038 } 1081 }
1039 pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); 1082 pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0));
1040 iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start); 1083 iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start);
@@ -1056,7 +1099,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
1056 colorNum = esc - asciiBase_ColorEscape; 1099 colorNum = esc - asciiBase_ColorEscape;
1057 } 1100 }
1058 run.logical.start = pos + 1; 1101 run.logical.start = pos + 1;
1059 run.fgColor = (colorNum >= 0 ? get_Color(colorNum) : d->fgColor); 1102 setFgColor_AttributedRun_(&run, colorNum >= 0 ? colorNum : d->colorId);
1060 continue; 1103 continue;
1061 } 1104 }
1062 if (ch == '\n') { 1105 if (ch == '\n') {
@@ -1077,7 +1120,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
1077 } 1120 }
1078 continue; 1121 continue;
1079 } 1122 }
1080 iFont *currentFont = activeFont; 1123 iFont *currentFont = attribFont;
1081 if (run.font->fontSpec->flags & arabic_FontSpecFlag && isPunct_Char(ch)) { 1124 if (run.font->fontSpec->flags & arabic_FontSpecFlag && isPunct_Char(ch)) {
1082 currentFont = run.font; /* remain as Arabic for whitespace */ 1125 currentFont = run.font; /* remain as Arabic for whitespace */
1083 } 1126 }
@@ -1113,12 +1156,15 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
1113#endif 1156#endif
1114} 1157}
1115 1158
1116void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor, 1159void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, int colorId,
1117 int baseDir, iChar overrideChar) { 1160 int baseDir, iFont *baseFont, int baseColorId, iChar overrideChar) {
1118 d->source = text; 1161 d->source = text;
1119 d->maxLen = maxLen ? maxLen : iInvalidSize; 1162 d->maxLen = maxLen ? maxLen : iInvalidSize;
1120 d->font = font; 1163 d->font = font;
1121 d->fgColor = fgColor; 1164 d->colorId = colorId;
1165 d->baseFont = baseFont;
1166 d->baseColorId = baseColorId;
1167 d->isBaseRTL = iFalse;
1122 init_Array(&d->runs, sizeof(iAttributedRun)); 1168 init_Array(&d->runs, sizeof(iAttributedRun));
1123 init_Array(&d->logical, sizeof(iChar)); 1169 init_Array(&d->logical, sizeof(iChar));
1124 init_Array(&d->visual, sizeof(iChar)); 1170 init_Array(&d->visual, sizeof(iChar));
@@ -1126,7 +1172,6 @@ void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont
1126 init_Array(&d->visualToLogical, sizeof(int)); 1172 init_Array(&d->visualToLogical, sizeof(int));
1127 init_Array(&d->logicalToSourceOffset, sizeof(int)); 1173 init_Array(&d->logicalToSourceOffset, sizeof(int));
1128 d->bidiLevels = NULL; 1174 d->bidiLevels = NULL;
1129 d->isBaseRTL = iFalse;
1130 prepare_AttributedText_(d, baseDir, overrideChar); 1175 prepare_AttributedText_(d, baseDir, overrideChar);
1131} 1176}
1132 1177
@@ -1278,7 +1323,7 @@ static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
1278 iArray glyphIndices; 1323 iArray glyphIndices;
1279 init_Array(&glyphIndices, sizeof(uint32_t)); 1324 init_Array(&glyphIndices, sizeof(uint32_t));
1280 iAttributedText attrText; 1325 iAttributedText attrText;
1281 init_AttributedText(&attrText, text, 0, d, (iColor){}, 0, 0); 1326 init_AttributedText(&attrText, text, 0, d, none_ColorId, 0, d, none_ColorId, 0);
1282 /* We use AttributedText here so the font lookup matches the behavior during text drawing -- 1327 /* We use AttributedText here so the font lookup matches the behavior during text drawing --
1283 glyphs may be selected from a font that's different than `d`. */ 1328 glyphs may be selected from a font that's different than `d`. */
1284 const iChar *logicalText = constData_Array(&attrText.logical); 1329 const iChar *logicalText = constData_Array(&attrText.logical);
@@ -1333,17 +1378,17 @@ struct Impl_RunArgs {
1333 /* TODO: Cleanup using TextMetrics 1378 /* TODO: Cleanup using TextMetrics
1334 Use TextMetrics output pointer instead of return value & cursorAdvance_out. */ 1379 Use TextMetrics output pointer instead of return value & cursorAdvance_out. */
1335 iInt2 * cursorAdvance_out; 1380 iInt2 * cursorAdvance_out;
1336// const char ** continueFrom_out;
1337 int * runAdvance_out; 1381 int * runAdvance_out;
1338}; 1382};
1339 1383
1340static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int advance, iBool isBaseRTL) { 1384static iBool notify_WrapText_(iWrapText *d, const char *ending, iTextAttrib attrib,
1385 int origin, int advance) {
1341 if (d && d->wrapFunc && d->wrapRange_.start) { 1386 if (d && d->wrapFunc && d->wrapRange_.start) {
1342 /* `wrapRange_` uses logical indices. */ 1387 /* `wrapRange_` uses logical indices. */
1343 const char *end = ending ? ending : d->wrapRange_.end; 1388 const char *end = ending ? ending : d->wrapRange_.end;
1344 iRangecc range = { d->wrapRange_.start, end }; 1389 iRangecc range = { d->wrapRange_.start, end };
1345 iAssert(range.start <= range.end); 1390 iAssert(range.start <= range.end);
1346 const iBool result = d->wrapFunc(d, range, origin, advance, isBaseRTL); 1391 const iBool result = d->wrapFunc(d, range, attrib, origin, advance);
1347 if (result) { 1392 if (result) {
1348 d->wrapRange_.start = end; 1393 d->wrapRange_.start = end;
1349 } 1394 }
@@ -1470,8 +1515,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1470 font is used and other attributes such as color. (HarfBuzz shaping is done 1515 font is used and other attributes such as color. (HarfBuzz shaping is done
1471 with one specific font.) */ 1516 with one specific font.) */
1472 iAttributedText attrText; 1517 iAttributedText attrText;
1473 init_AttributedText(&attrText, args->text, args->maxLen, d, get_Color(args->color), 1518 init_AttributedText(&attrText, args->text, args->maxLen, d, args->color,
1474 args->baseDir, wrap ? wrap->overrideChar : 0); 1519 args->baseDir,
1520 activeText_->baseFontId >= 0 ? font_Text_(activeText_->baseFontId) : d,
1521 activeText_->baseColorId,
1522 wrap ? wrap->overrideChar : 0);
1475 if (wrap) { 1523 if (wrap) {
1476 wrap->baseDir = attrText.isBaseRTL ? -1 : +1; 1524 wrap->baseDir = attrText.isBaseRTL ? -1 : +1;
1477 /* TODO: Duplicated args? */ 1525 /* TODO: Duplicated args? */
@@ -1522,6 +1570,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1522 iRangei wrapPosRange = { 0, textLen }; 1570 iRangei wrapPosRange = { 0, textLen };
1523 int wrapResumePos = textLen; /* logical position where next line resumes */ 1571 int wrapResumePos = textLen; /* logical position where next line resumes */
1524 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */ 1572 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */
1573 iTextAttrib attrib = { .colorId = args->color, .isBaseRTL = attrText.isBaseRTL };
1574 iTextAttrib wrapAttrib = attrib;
1575 iTextAttrib lastAttrib = attrib;
1525 const int layoutBound = (wrap ? wrap->maxWidth : 0); 1576 const int layoutBound = (wrap ? wrap->maxWidth : 0);
1526 iBool isFirst = iTrue; 1577 iBool isFirst = iTrue;
1527 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2()); 1578 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2());
@@ -1545,6 +1596,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1545 /* Determine ends of wrapRuns and wrapVisRange. */ 1596 /* Determine ends of wrapRuns and wrapVisRange. */
1546 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { 1597 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1547 const iAttributedRun *run = at_Array(&attrText.runs, runIndex); 1598 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1599 /* Update the attributes. */
1548 if (run->flags.isLineBreak) { 1600 if (run->flags.isLineBreak) {
1549 if (checkHitChar && 1601 if (checkHitChar &&
1550 wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) { 1602 wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) {
@@ -1554,7 +1606,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1554 wrapResumePos = run->logical.end; 1606 wrapResumePos = run->logical.end;
1555 wrapRuns.end = runIndex; 1607 wrapRuns.end = runIndex;
1556 wrapResumeRunIndex = runIndex + 1; 1608 wrapResumeRunIndex = runIndex + 1;
1557 //yCursor += d->height;
1558 break; 1609 break;
1559 } 1610 }
1560 wrapResumeRunIndex = runCount; 1611 wrapResumeRunIndex = runCount;
@@ -1564,8 +1615,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1564 shape_GlyphBuffer_(buf); 1615 shape_GlyphBuffer_(buf);
1565 int safeBreakPos = -1; 1616 int safeBreakPos = -1;
1566 iChar prevCh = 0; 1617 iChar prevCh = 0;
1618 lastAttrib = run->attrib;
1567 for (unsigned int ir = 0; ir < buf->glyphCount; ir++) { 1619 for (unsigned int ir = 0; ir < buf->glyphCount; ir++) {
1568 const int i = (run->flags.isRTL ? buf->glyphCount - ir - 1 : ir); 1620 const int i = (run->attrib.isRTL ? buf->glyphCount - ir - 1 : ir);
1569 const hb_glyph_info_t *info = &buf->glyphInfo[i]; 1621 const hb_glyph_info_t *info = &buf->glyphInfo[i];
1570 const hb_codepoint_t glyphId = info->codepoint; 1622 const hb_codepoint_t glyphId = info->codepoint;
1571 const int logPos = info->cluster; 1623 const int logPos = info->cluster;
@@ -1603,10 +1655,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1603 prevCh = ch; 1655 prevCh = ch;
1604 } 1656 }
1605 else { 1657 else {
1606 //if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { 1658 safeBreakPos = logPos;
1607 safeBreakPos = logPos; 1659 breakAdvance = wrapAdvance;
1608 breakAdvance = wrapAdvance; 1660 wrapAttrib = run->attrib;
1609 //}
1610 } 1661 }
1611 if (isHitPointOnThisLine) { 1662 if (isHitPointOnThisLine) {
1612 if (wrap->hitPoint.x >= orig.x + wrapAdvance && 1663 if (wrap->hitPoint.x >= orig.x + wrapAdvance &&
@@ -1638,7 +1689,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1638 wrapPosRange.end = logPos; 1689 wrapPosRange.end = logPos;
1639 breakAdvance = wrapAdvance; 1690 breakAdvance = wrapAdvance;
1640 } 1691 }
1641 wrapResumePos = wrapPosRange.end; 1692 wrapResumePos = wrapPosRange.end;
1642 if (args->wrap->mode != anyCharacter_WrapTextMode) { 1693 if (args->wrap->mode != anyCharacter_WrapTextMode) {
1643 while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) { 1694 while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) {
1644 wrapResumePos++; /* skip space */ 1695 wrapResumePos++; /* skip space */
@@ -1688,7 +1739,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1688 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { 1739 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1689 const iAttributedRun *run = at_Array(&attrText.runs, runIndex); 1740 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1690 if (!attrText.isBaseRTL) { /* left-to-right */ 1741 if (!attrText.isBaseRTL) { /* left-to-right */
1691 if (run->flags.isRTL) { 1742 if (run->attrib.isRTL) {
1692 if (oppositeInsertIndex == iInvalidPos) { 1743 if (oppositeInsertIndex == iInvalidPos) {
1693 oppositeInsertIndex = size_Array(&runOrder); 1744 oppositeInsertIndex = size_Array(&runOrder);
1694 } 1745 }
@@ -1700,7 +1751,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1700 } 1751 }
1701 } 1752 }
1702 else { /* right-to-left */ 1753 else { /* right-to-left */
1703 if (!run->flags.isRTL) { 1754 if (!run->attrib.isRTL) {
1704 if (oppositeInsertIndex == iInvalidPos) { 1755 if (oppositeInsertIndex == iInvalidPos) {
1705 oppositeInsertIndex = 0; 1756 oppositeInsertIndex = 0;
1706 } 1757 }
@@ -1739,11 +1790,12 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1739 if (wrap && wrap->wrapFunc && 1790 if (wrap && wrap->wrapFunc &&
1740 !notify_WrapText_(args->wrap, 1791 !notify_WrapText_(args->wrap,
1741 sourcePtr_AttributedText_(&attrText, wrapResumePos), 1792 sourcePtr_AttributedText_(&attrText, wrapResumePos),
1793 wrapAttrib,
1742 origin, 1794 origin,
1743 iRound(wrapAdvance), 1795 iRound(wrapAdvance))) {
1744 attrText.isBaseRTL)) {
1745 willAbortDueToWrap = iTrue; 1796 willAbortDueToWrap = iTrue;
1746 } 1797 }
1798 wrapAttrib = lastAttrib;
1747 xCursor = origin; 1799 xCursor = origin;
1748 /* We have determined a possible wrap position and alignment for the work runs, 1800 /* We have determined a possible wrap position and alignment for the work runs,
1749 so now we can process the glyphs. */ 1801 so now we can process the glyphs. */
@@ -1796,8 +1848,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1796 orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, 1848 orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y,
1797 glyph->rect[hoff].size.x, 1849 glyph->rect[hoff].size.x,
1798 glyph->rect[hoff].size.y }; 1850 glyph->rect[hoff].size.y };
1799 if (run->font->height < d->height) { 1851 if (run->font->height < attrText.baseFont->height) {
1800 dst.y += d->baseline - run->font->baseline; 1852 dst.y += attrText.baseFont->baseline - run->font->baseline;
1801 } 1853 }
1802 if (mode & visualFlag_RunMode) { 1854 if (mode & visualFlag_RunMode) {
1803 if (isEmpty_Rect(bounds)) { 1855 if (isEmpty_Rect(bounds)) {
@@ -1819,7 +1871,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1819 iAssert(isRasterized_Glyph_(glyph, hoff)); 1871 iAssert(isRasterized_Glyph_(glyph, hoff));
1820 } 1872 }
1821 if (~mode & permanentColorFlag_RunMode) { 1873 if (~mode & permanentColorFlag_RunMode) {
1822 const iColor clr = run->fgColor; 1874 const iColor clr = fgColor_AttributedRun_(run);
1823 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); 1875 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
1824 if (args->mode & fillBackground_RunMode) { 1876 if (args->mode & fillBackground_RunMode) {
1825 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); 1877 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
@@ -1939,9 +1991,9 @@ static int runFlagsFromId_(enum iFontId fontId) {
1939 return runFlags; 1991 return runFlags;
1940} 1992}
1941 1993
1942static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int origin, int advance, 1994static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, iTextAttrib attrib, int origin,
1943 iBool isBaseRTL) { 1995 int advance) {
1944 iUnused(origin, advance, isBaseRTL); 1996 iUnused(attrib, origin, advance);
1945 *((const char **) d->context) = range.end; 1997 *((const char **) d->context) = range.end;
1946 return iFalse; /* just one line */ 1998 return iFalse; /* just one line */
1947} 1999}
diff --git a/src/ui/text.h b/src/ui/text.h
index ac59e7c8..51f7754d 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -120,6 +120,9 @@ void resetFonts_Text (iText *);
120int lineHeight_Text (int fontId); 120int lineHeight_Text (int fontId);
121float emRatio_Text (int fontId); /* em advance to line height ratio */ 121float emRatio_Text (int fontId); /* em advance to line height ratio */
122iRect visualBounds_Text (int fontId, iRangecc text); 122iRect visualBounds_Text (int fontId, iRangecc text);
123int fontWithSize_Text (int fontId, enum iFontSize sizeId);
124int fontWithStyle_Text (int fontId, enum iFontStyle styleId);
125int fontWithFamily_Text (int fontId, enum iFontId familyId);
123 126
124iDeclareType(TextMetrics) 127iDeclareType(TextMetrics)
125 128
@@ -149,9 +152,10 @@ enum iAlignment {
149 right_Alignment, 152 right_Alignment,
150}; 153};
151 154
152void setOpacity_Text (float opacity); 155void setOpacity_Text (float opacity);
156void setBaseAttributes_Text (int fontId, int colorId); /* current "normal" text attributes */
153 157
154void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ 158void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */
155 159
156void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); 160void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...);
157void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); 161void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...);
@@ -173,12 +177,28 @@ enum iWrapTextMode {
173 word_WrapTextMode, 177 word_WrapTextMode,
174}; 178};
175 179
180iDeclareType(TextAttrib)
181
182/* Initial attributes at the start of a text string. These may be modified by control
183 sequences inside a text run. */
184struct Impl_TextAttrib {
185 int16_t colorId;
186 struct {
187 uint16_t bold : 1;
188 uint16_t italic : 1;
189 uint16_t monospace : 1;
190 uint16_t isBaseRTL : 1;
191 uint16_t isRTL : 1;
192 };
193};
194
176struct Impl_WrapText { 195struct Impl_WrapText {
177 /* arguments */ 196 /* arguments */
178 iRangecc text; 197 iRangecc text;
179 int maxWidth; 198 int maxWidth;
180 enum iWrapTextMode mode; 199 enum iWrapTextMode mode;
181 iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int origin, int advance, iBool isBaseRTL); 200 iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, iTextAttrib attrib, int origin,
201 int advance);
182 void * context; 202 void * context;
183 iChar overrideChar; /* use this for all characters instead of the real ones */ 203 iChar overrideChar; /* use this for all characters instead of the real ones */
184 int baseDir; /* set to +1 for LTR, -1 for RTL */ 204 int baseDir; /* set to +1 for LTR, -1 for RTL */