diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-10-13 09:49:44 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-10-13 09:49:44 +0300 |
commit | 6b931c95725eef2ebb7e831c4017d3d67b33294f (patch) | |
tree | 254595663d93c0dcc33baad475fb2f6c3ddeec40 /src | |
parent | dd0a8798f32bf192ed703c6c512d4a76c4d407bc (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')
-rw-r--r-- | src/fontpack.c | 2 | ||||
-rw-r--r-- | src/gmdocument.c | 200 | ||||
-rw-r--r-- | src/gmdocument.h | 30 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 40 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 18 | ||||
-rw-r--r-- | src/ui/text.c | 196 | ||||
-rw-r--r-- | src/ui/text.h | 26 |
7 files changed, 331 insertions, 181 deletions
diff --git a/src/fontpack.c b/src/fontpack.c index fb1c98ee..9baedc0e 100644 --- a/src/fontpack.c +++ b/src/fontpack.c | |||
@@ -48,7 +48,7 @@ float scale_FontSize(enum iFontSize size) { | |||
48 | 1.333, | 48 | 1.333, |
49 | 1.666, | 49 | 1.666, |
50 | 2.000, | 50 | 2.000, |
51 | 0.568, | 51 | 0.650, //0.568, |
52 | 0.710, /* calibration: fits the Lagrange title screen with Normal line width */ | 52 | 0.710, /* calibration: fits the Lagrange title screen with Normal line width */ |
53 | }; | 53 | }; |
54 | if (size < 0 || size >= max_FontSize) { | 54 | if (size < 0 || size >= max_FontSize) { |
diff --git a/src/gmdocument.c b/src/gmdocument.c index f0d9bf08..5c2a849e 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -75,6 +75,15 @@ iDefineTypeConstruction(GmLink) | |||
75 | 75 | ||
76 | /*----------------------------------------------------------------------------------------------*/ | 76 | /*----------------------------------------------------------------------------------------------*/ |
77 | 77 | ||
78 | iDeclareType(GmTheme) | ||
79 | |||
80 | struct Impl_GmTheme { | ||
81 | int colors[max_GmLineType]; | ||
82 | int fonts[max_GmLineType]; | ||
83 | }; | ||
84 | |||
85 | /*----------------------------------------------------------------------------------------------*/ | ||
86 | |||
78 | struct Impl_GmDocument { | 87 | struct Impl_GmDocument { |
79 | iObject object; | 88 | iObject object; |
80 | enum iSourceFormat format; | 89 | enum iSourceFormat format; |
@@ -91,6 +100,7 @@ struct Impl_GmDocument { | |||
91 | iString title; /* the first top-level title */ | 100 | iString title; /* the first top-level title */ |
92 | iArray headings; | 101 | iArray headings; |
93 | iArray preMeta; /* metadata about preformatted blocks */ | 102 | iArray preMeta; /* metadata about preformatted blocks */ |
103 | iGmTheme theme; | ||
94 | uint32_t themeSeed; | 104 | uint32_t themeSeed; |
95 | iChar siteIcon; | 105 | iChar siteIcon; |
96 | iMedia * media; | 106 | iMedia * media; |
@@ -101,6 +111,51 @@ struct Impl_GmDocument { | |||
101 | 111 | ||
102 | iDefineObjectConstruction(GmDocument) | 112 | iDefineObjectConstruction(GmDocument) |
103 | 113 | ||
114 | static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) { | ||
115 | const iRangecc scheme = urlScheme_String(&d->url); | ||
116 | if (equalCase_Rangecc(scheme, "gemini")) { | ||
117 | return prefs_App()->monospaceGemini; | ||
118 | } | ||
119 | if (equalCase_Rangecc(scheme, "gopher") || | ||
120 | equalCase_Rangecc(scheme, "finger")) { | ||
121 | return prefs_App()->monospaceGopher; | ||
122 | } | ||
123 | return iFalse; | ||
124 | } | ||
125 | |||
126 | static void initTheme_GmDocument_(iGmDocument *d) { | ||
127 | static const int defaultColors[max_GmLineType] = { | ||
128 | tmParagraph_ColorId, | ||
129 | tmParagraph_ColorId, /* bullet */ | ||
130 | tmPreformatted_ColorId, | ||
131 | tmQuote_ColorId, | ||
132 | tmHeading1_ColorId, | ||
133 | tmHeading2_ColorId, | ||
134 | tmHeading3_ColorId, | ||
135 | tmLinkText_ColorId, | ||
136 | }; | ||
137 | iGmTheme *theme = &d->theme; | ||
138 | memcpy(theme->colors, defaultColors, sizeof(theme->colors)); | ||
139 | const iPrefs *prefs = prefs_App(); | ||
140 | const iBool isMono = isForcedMonospace_GmDocument_(d); | ||
141 | const iBool isDarkBg = isDark_GmDocumentTheme( | ||
142 | isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); | ||
143 | const enum iFontId headingFont = isMono ? documentMonospace_FontId : documentHeading_FontId; | ||
144 | const enum iFontId bodyFont = isMono ? documentMonospace_FontId : documentBody_FontId; | ||
145 | theme->fonts[text_GmLineType] = FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize); | ||
146 | theme->fonts[bullet_GmLineType] = FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize); | ||
147 | theme->fonts[preformatted_GmLineType] = preformatted_FontId; | ||
148 | theme->fonts[quote_GmLineType] = isMono ? monospaceParagraph_FontId : quote_FontId; | ||
149 | theme->fonts[heading1_GmLineType] = FONT_ID(headingFont, bold_FontStyle, contentHuge_FontSize); | ||
150 | theme->fonts[heading2_GmLineType] = FONT_ID(headingFont, bold_FontStyle, contentLarge_FontSize); | ||
151 | theme->fonts[heading3_GmLineType] = FONT_ID(headingFont, regular_FontStyle, contentBig_FontSize); | ||
152 | theme->fonts[link_GmLineType] = FONT_ID( | ||
153 | bodyFont, | ||
154 | ((isDarkBg && prefs->boldLinkDark) || (!isDarkBg && prefs->boldLinkLight)) ? semiBold_FontStyle | ||
155 | : regular_FontStyle, | ||
156 | contentRegular_FontSize); | ||
157 | } | ||
158 | |||
104 | static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { | 159 | static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { |
105 | if (d->format == plainText_SourceFormat) { | 160 | if (d->format == plainText_SourceFormat) { |
106 | return text_GmLineType; | 161 | return text_GmLineType; |
@@ -318,18 +373,6 @@ static iBool isGopher_GmDocument_(const iGmDocument *d) { | |||
318 | equalCase_Rangecc(scheme, "finger")); | 373 | equalCase_Rangecc(scheme, "finger")); |
319 | } | 374 | } |
320 | 375 | ||
321 | static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) { | ||
322 | const iRangecc scheme = urlScheme_String(&d->url); | ||
323 | if (equalCase_Rangecc(scheme, "gemini")) { | ||
324 | return prefs_App()->monospaceGemini; | ||
325 | } | ||
326 | if (equalCase_Rangecc(scheme, "gopher") || | ||
327 | equalCase_Rangecc(scheme, "finger")) { | ||
328 | return prefs_App()->monospaceGopher; | ||
329 | } | ||
330 | return iFalse; | ||
331 | } | ||
332 | |||
333 | static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo, | 376 | static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo, |
334 | uint16_t linkId) { | 377 | uint16_t linkId) { |
335 | iGmLink *link = at_PtrArray(&d->links, linkId - 1); | 378 | iGmLink *link = at_PtrArray(&d->links, linkId - 1); |
@@ -402,7 +445,8 @@ struct Impl_RunTypesetter { | |||
402 | int rightMargin; | 445 | int rightMargin; |
403 | iBool isWordWrapped; | 446 | iBool isWordWrapped; |
404 | iBool isPreformat; | 447 | iBool isPreformat; |
405 | const int *fonts; | 448 | int baseFont; |
449 | int baseColor; | ||
406 | }; | 450 | }; |
407 | 451 | ||
408 | static void init_RunTypesetter_(iRunTypesetter *d) { | 452 | static void init_RunTypesetter_(iRunTypesetter *d) { |
@@ -425,39 +469,47 @@ static void commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) { | |||
425 | 469 | ||
426 | static const int maxLedeLines_ = 10; | 470 | static const int maxLedeLines_ = 10; |
427 | 471 | ||
428 | static const int colors[max_GmLineType] = { | 472 | static int applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) { |
429 | tmParagraph_ColorId, | 473 | /* WARNING: This is duplicated in run_Font_(). Make sure they behave identically. */ |
430 | tmParagraph_ColorId, | 474 | if (attrib.bold) { |
431 | tmPreformatted_ColorId, | 475 | d->run.font = fontWithStyle_Text(d->baseFont, bold_FontStyle); |
432 | tmQuote_ColorId, | 476 | d->run.color = tmFirstParagraph_ColorId; |
433 | tmHeading1_ColorId, | 477 | } |
434 | tmHeading2_ColorId, | 478 | else if (attrib.italic) { |
435 | tmHeading3_ColorId, | 479 | d->run.font = fontWithStyle_Text(d->baseFont, italic_FontStyle); |
436 | tmLinkText_ColorId, | 480 | } |
437 | }; | 481 | else if (attrib.monospace) { |
482 | d->run.font = fontWithFamily_Text(d->baseFont, monospace_FontId); | ||
483 | d->run.color = tmPreformatted_ColorId; | ||
484 | } | ||
485 | else { | ||
486 | d->run.font = d->baseFont; | ||
487 | d->run.color = d->baseColor; | ||
488 | } | ||
489 | } | ||
438 | 490 | ||
439 | static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, int origin, | 491 | static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, iTextAttrib attrib, |
440 | int advance, iBool isBaseRTL) { | 492 | int origin, int advance) { |
441 | iAssert(wrapRange.start <= wrapRange.end); | 493 | iAssert(wrapRange.start <= wrapRange.end); |
442 | trimEnd_Rangecc(&wrapRange); | 494 | trimEnd_Rangecc(&wrapRange); |
443 | // printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); | 495 | // printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); |
444 | iRunTypesetter *d = wrap->context; | 496 | iRunTypesetter *d = wrap->context; |
445 | const int fontId = d->run.font; | ||
446 | d->run.text = wrapRange; | 497 | d->run.text = wrapRange; |
498 | applyAttributes_RunTypesetter_(d, attrib); | ||
447 | if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) { | 499 | if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) { |
448 | d->pos.y -= d->lineHeightReduction * lineHeight_Text(fontId); | 500 | d->pos.y -= d->lineHeightReduction * lineHeight_Text(d->baseFont); |
449 | } | 501 | } |
450 | d->run.bounds.pos = addX_I2(d->pos, origin + d->indent); | 502 | d->run.bounds.pos = addX_I2(d->pos, origin + d->indent); |
451 | const iInt2 dims = init_I2(advance, lineHeight_Text(fontId)); | 503 | const iInt2 dims = init_I2(advance, lineHeight_Text(d->baseFont)); |
452 | iChangeFlags(d->run.flags, wide_GmRunFlag, (d->isPreformat && dims.x > d->layoutWidth)); | 504 | iChangeFlags(d->run.flags, wide_GmRunFlag, (d->isPreformat && dims.x > d->layoutWidth)); |
453 | d->run.bounds.size.x = iMax(wrap->maxWidth, dims.x) - origin; /* Extends to the right edge for selection. */ | 505 | d->run.bounds.size.x = iMax(wrap->maxWidth, dims.x) - origin; /* Extends to the right edge for selection. */ |
454 | d->run.bounds.size.y = dims.y; | 506 | d->run.bounds.size.y = dims.y; |
455 | d->run.visBounds = d->run.bounds; | 507 | d->run.visBounds = d->run.bounds; |
456 | d->run.visBounds.size.x = dims.x; | 508 | d->run.visBounds.size.x = dims.x; |
457 | d->run.isRTL = isBaseRTL; | 509 | d->run.isRTL = attrib.isBaseRTL; |
458 | pushBack_Array(&d->layout, &d->run); | 510 | pushBack_Array(&d->layout, &d->run); |
459 | d->run.flags &= ~startOfLine_GmRunFlag; | 511 | d->run.flags &= ~startOfLine_GmRunFlag; |
460 | d->pos.y += lineHeight_Text(fontId) * prefs_App()->lineSpacing; | 512 | d->pos.y += lineHeight_Text(d->baseFont) * prefs_App()->lineSpacing; |
461 | return iTrue; /* continue to next wrapped line */ | 513 | return iTrue; /* continue to next wrapped line */ |
462 | } | 514 | } |
463 | 515 | ||
@@ -469,25 +521,10 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
469 | const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; | 521 | const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; |
470 | const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; | 522 | const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; |
471 | const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); | 523 | const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); |
472 | const iBool isDarkBg = isDark_GmDocumentTheme( | 524 | // const iBool isDarkBg = isDark_GmDocumentTheme( |
473 | isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); | 525 | // isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); |
526 | initTheme_GmDocument_(d); | ||
474 | /* TODO: Collect these parameters into a GmTheme. */ | 527 | /* TODO: Collect these parameters into a GmTheme. */ |
475 | const enum iFontId headingFont = isMono ? documentMonospace_FontId : documentHeading_FontId; | ||
476 | const enum iFontId bodyFont = isMono ? documentMonospace_FontId : documentBody_FontId; | ||
477 | const int fonts[max_GmLineType] = { | ||
478 | FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize), /* text */ | ||
479 | FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize), /* bullet */ | ||
480 | preformatted_FontId, /* pre */ | ||
481 | isMono ? monospaceParagraph_FontId : quote_FontId, /* quote */ | ||
482 | FONT_ID(headingFont, bold_FontStyle, contentHuge_FontSize), /* h1 */ | ||
483 | FONT_ID(headingFont, bold_FontStyle, contentLarge_FontSize), /* h2 */ | ||
484 | FONT_ID(headingFont, regular_FontStyle, contentBig_FontSize), /* h3 */ | ||
485 | FONT_ID(bodyFont, | ||
486 | ((isDarkBg && prefs->boldLinkDark) || (!isDarkBg && prefs->boldLinkLight)) | ||
487 | ? semiBold_FontStyle | ||
488 | : regular_FontStyle, | ||
489 | contentRegular_FontSize) /* link */ | ||
490 | }; | ||
491 | float indents[max_GmLineType] = { 5, 10, 5, isNarrow ? 5 : 10, 0, 0, 0, 5 }; | 528 | float indents[max_GmLineType] = { 5, 10, 5, isNarrow ? 5 : 10, 0, 0, 0, 5 }; |
492 | if (isExtremelyNarrow) { | 529 | if (isExtremelyNarrow) { |
493 | /* Further reduce the margins. */ | 530 | /* Further reduce the margins. */ |
@@ -598,7 +635,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
598 | } | 635 | } |
599 | } | 636 | } |
600 | trimLine_Rangecc(&line, type, isNormalized); | 637 | trimLine_Rangecc(&line, type, isNormalized); |
601 | run.font = fonts[type]; | 638 | run.font = d->theme.fonts[type]; |
602 | /* Remember headings for the document outline. */ | 639 | /* Remember headings for the document outline. */ |
603 | if (type == heading1_GmLineType || type == heading2_GmLineType || type == heading3_GmLineType) { | 640 | if (type == heading1_GmLineType || type == heading2_GmLineType || type == heading3_GmLineType) { |
604 | pushBack_Array( | 641 | pushBack_Array( |
@@ -618,7 +655,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
618 | addSiteBanner = iFalse; /* overrides the banner */ | 655 | addSiteBanner = iFalse; /* overrides the banner */ |
619 | continue; | 656 | continue; |
620 | } | 657 | } |
621 | run.preId = preId; | 658 | run.mediaType = max_MediaType; /* preformatted block */ |
659 | run.mediaId = preId; | ||
622 | run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); | 660 | run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); |
623 | indent = indents[type]; | 661 | indent = indents[type]; |
624 | } | 662 | } |
@@ -650,7 +688,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
650 | run.visBounds.size = init_I2(gap_Text, lineHeight_Text(run.font)); | 688 | run.visBounds.size = init_I2(gap_Text, lineHeight_Text(run.font)); |
651 | run.bounds = zero_Rect(); /* just visual */ | 689 | run.bounds = zero_Rect(); /* just visual */ |
652 | run.text = iNullRange; | 690 | run.text = iNullRange; |
653 | run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag; | 691 | run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag; |
654 | pushBack_Array(&d->layout, &run); | 692 | pushBack_Array(&d->layout, &run); |
655 | } | 693 | } |
656 | pos.y += lineHeight_Text(run.font) * prefs->lineSpacing; | 694 | pos.y += lineHeight_Text(run.font) * prefs->lineSpacing; |
@@ -708,7 +746,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
708 | altText.text).bounds.size; | 746 | altText.text).bounds.size; |
709 | altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, | 747 | altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, |
710 | size.y + 2 * margin.y); | 748 | size.y + 2 * margin.y); |
711 | altText.preId = preId; | 749 | altText.mediaType = max_MediaType; /* preformatted */ |
750 | altText.mediaId = preId; | ||
712 | pushBack_Array(&d->layout, &altText); | 751 | pushBack_Array(&d->layout, &altText); |
713 | pos.y += height_Rect(altText.bounds); | 752 | pos.y += height_Rect(altText.bounds); |
714 | contentLine = meta->bounds; /* Skip the whole thing. */ | 753 | contentLine = meta->bounds; /* Skip the whole thing. */ |
@@ -723,7 +762,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
723 | setRange_String(&d->title, line); | 762 | setRange_String(&d->title, line); |
724 | } | 763 | } |
725 | /* List bullet. */ | 764 | /* List bullet. */ |
726 | run.color = colors[type]; | ||
727 | if (type == bullet_GmLineType) { | 765 | if (type == bullet_GmLineType) { |
728 | /* TODO: Literata bullet is broken? */ | 766 | /* TODO: Literata bullet is broken? */ |
729 | iGmRun bulRun = run; | 767 | iGmRun bulRun = run; |
@@ -795,18 +833,17 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
795 | icon.flags |= decoration_GmRunFlag; | 833 | icon.flags |= decoration_GmRunFlag; |
796 | pushBack_Array(&d->layout, &icon); | 834 | pushBack_Array(&d->layout, &icon); |
797 | } | 835 | } |
798 | run.color = colors[type]; | 836 | run.lineType = type; |
837 | run.color = d->theme.colors[type]; | ||
799 | if (d->format == plainText_SourceFormat) { | 838 | if (d->format == plainText_SourceFormat) { |
800 | run.color = colors[text_GmLineType]; | 839 | run.color = d->theme.colors[text_GmLineType]; |
801 | } | 840 | } |
802 | /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ | 841 | /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ |
803 | // int bigCount = 0; | 842 | // int bigCount = 0; |
804 | iBool isLedeParagraph = iFalse; | ||
805 | if (type == text_GmLineType && isFirstText) { | 843 | if (type == text_GmLineType && isFirstText) { |
806 | if (!isMono) run.font = firstParagraph_FontId; | 844 | if (!isMono) run.font = firstParagraph_FontId; |
807 | run.color = tmFirstParagraph_ColorId; | 845 | run.color = tmFirstParagraph_ColorId; |
808 | // bigCount = 15; /* max lines -- what if the whole document is one paragraph? */ | 846 | run.isLede = iTrue; |
809 | isLedeParagraph = iTrue; | ||
810 | isFirstText = iFalse; | 847 | isFirstText = iFalse; |
811 | } | 848 | } |
812 | else if (type != heading1_GmLineType) { | 849 | else if (type != heading1_GmLineType) { |
@@ -826,7 +863,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
826 | init_RunTypesetter_(&rts); | 863 | init_RunTypesetter_(&rts); |
827 | rts.run = run; | 864 | rts.run = run; |
828 | rts.pos = pos; | 865 | rts.pos = pos; |
829 | rts.fonts = fonts; | 866 | //rts.fonts = fonts; |
830 | rts.isWordWrapped = (d->format == plainText_SourceFormat ? prefs->plainTextWrap | 867 | rts.isWordWrapped = (d->format == plainText_SourceFormat ? prefs->plainTextWrap |
831 | : !isPreformat); | 868 | : !isPreformat); |
832 | rts.isPreformat = isPreformat; | 869 | rts.isPreformat = isPreformat; |
@@ -859,6 +896,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
859 | } | 896 | } |
860 | for (;;) { /* need to retry if the font needs changing */ | 897 | for (;;) { /* need to retry if the font needs changing */ |
861 | rts.run.flags |= startOfLine_GmRunFlag; | 898 | rts.run.flags |= startOfLine_GmRunFlag; |
899 | rts.baseFont = rts.run.font; | ||
900 | rts.baseColor = rts.run.color; | ||
862 | iWrapText wrapText = { .text = line, | 901 | iWrapText wrapText = { .text = line, |
863 | .maxWidth = rts.isWordWrapped | 902 | .maxWidth = rts.isWordWrapped |
864 | ? d->size.x - run.bounds.pos.x - | 903 | ? d->size.x - run.bounds.pos.x - |
@@ -868,7 +907,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
868 | .wrapFunc = typesetOneLine_RunTypesetter_, | 907 | .wrapFunc = typesetOneLine_RunTypesetter_, |
869 | .context = &rts }; | 908 | .context = &rts }; |
870 | measure_WrapText(&wrapText, rts.run.font); | 909 | measure_WrapText(&wrapText, rts.run.font); |
871 | if (!isLedeParagraph || size_Array(&rts.layout) <= maxLedeLines_) { | 910 | if (!rts.run.isLede || size_Array(&rts.layout) <= maxLedeLines_) { |
872 | if (wrapText.baseDir < 0) { | 911 | if (wrapText.baseDir < 0) { |
873 | /* Right-aligned paragraphs need margins and decorations to be flipped. */ | 912 | /* Right-aligned paragraphs need margins and decorations to be flipped. */ |
874 | iForEach(Array, pr, &rts.layout) { | 913 | iForEach(Array, pr, &rts.layout) { |
@@ -891,11 +930,12 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
891 | commit_RunTypesetter_(&rts, d); | 930 | commit_RunTypesetter_(&rts, d); |
892 | break; | 931 | break; |
893 | } | 932 | } |
933 | /* Try again... */ | ||
894 | clear_RunTypesetter_(&rts); | 934 | clear_RunTypesetter_(&rts); |
895 | rts.pos = pos; | 935 | rts.pos = pos; |
896 | rts.run.font = rts.fonts[text_GmLineType]; | 936 | rts.run.font = rts.baseFont = d->theme.fonts[text_GmLineType]; |
897 | rts.run.color = colors[text_GmLineType]; | 937 | rts.run.color = rts.baseColor = d->theme.colors[text_GmLineType]; |
898 | isLedeParagraph = iFalse; | 938 | rts.run.isLede = iFalse; |
899 | } | 939 | } |
900 | pos = rts.pos; | 940 | pos = rts.pos; |
901 | deinit_RunTypesetter_(&rts); | 941 | deinit_RunTypesetter_(&rts); |
@@ -996,7 +1036,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
996 | /* TODO: Store the dimensions and ranges for later access. */ | 1036 | /* TODO: Store the dimensions and ranges for later access. */ |
997 | iForEach(Array, i, &d->layout) { | 1037 | iForEach(Array, i, &d->layout) { |
998 | iGmRun *run = i.value; | 1038 | iGmRun *run = i.value; |
999 | if (run->preId && run->flags & wide_GmRunFlag) { | 1039 | if (preId_GmRun(run) && run->flags & wide_GmRunFlag) { |
1000 | iGmRunRange block = findPreformattedRange_GmDocument(d, run); | 1040 | iGmRunRange block = findPreformattedRange_GmDocument(d, run); |
1001 | for (const iGmRun *j = block.start; j != block.end; j++) { | 1041 | for (const iGmRun *j = block.start; j != block.end; j++) { |
1002 | iConstCast(iGmRun *, j)->flags |= wide_GmRunFlag; | 1042 | iConstCast(iGmRun *, j)->flags |= wide_GmRunFlag; |
@@ -1800,11 +1840,11 @@ static void flushPendingLinks_(iArray *links, const iString *source, iString *ou | |||
1800 | static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { | 1840 | static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { |
1801 | iAssert(d->format == markdown_SourceFormat); | 1841 | iAssert(d->format == markdown_SourceFormat); |
1802 | /* Get rid of indented preformats. */ { | 1842 | /* Get rid of indented preformats. */ { |
1803 | iArray *pendingLinks = collectNew_Array(sizeof(iPendingLink)); | 1843 | iArray *pendingLinks = collectNew_Array(sizeof(iPendingLink)); |
1804 | const iRegExp *imageLinkPattern = iClob(new_RegExp("\n?!\\[(.+)\\]\\(([^)]+)\\)\n?", 0)); | 1844 | const iRegExp *imageLinkPattern = iClob(new_RegExp("\n?!\\[(.+)\\]\\(([^)]+)\\)\n?", 0)); |
1805 | const iRegExp *linkPattern = iClob(new_RegExp("\\[(.+?)\\]\\(([^)]+)\\)", 0)); | 1845 | const iRegExp *linkPattern = iClob(new_RegExp("\\[(.+?)\\]\\(([^)]+)\\)", 0)); |
1806 | const iRegExp *namedLinkPattern = iClob(new_RegExp("\\[(.+?)\\]\\[(.+?)\\]", 0)); | 1846 | const iRegExp *namedLinkPattern = iClob(new_RegExp("\\[(.+?)\\]\\[(.+?)\\]", 0)); |
1807 | const iRegExp *namePattern = iClob(new_RegExp("\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0)); | 1847 | const iRegExp *namePattern = iClob(new_RegExp("\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0)); |
1808 | iString result; | 1848 | iString result; |
1809 | init_String(&result); | 1849 | init_String(&result); |
1810 | iRangecc line = iNullRange; | 1850 | iRangecc line = iNullRange; |
@@ -1865,7 +1905,7 @@ static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { | |||
1865 | replaceRegExp_String(&ln, imageLinkPattern, "\n=> \\2 \\1\n", NULL, NULL); | 1905 | replaceRegExp_String(&ln, imageLinkPattern, "\n=> \\2 \\1\n", NULL, NULL); |
1866 | replaceRegExp_String(&ln, namedLinkPattern, "\\1", addPendingNamedLink_, pendingLinks); | 1906 | replaceRegExp_String(&ln, namedLinkPattern, "\\1", addPendingNamedLink_, pendingLinks); |
1867 | replaceRegExp_String(&ln, linkPattern, "\\1", addPendingLink_, pendingLinks); | 1907 | replaceRegExp_String(&ln, linkPattern, "\\1", addPendingLink_, pendingLinks); |
1868 | replaceRegExp_String(&ln, iClob(new_RegExp("(?<!`)`([^`]*?)`(?!`)", 0)), "\x1b[4m\\1\x1b[0m", NULL, NULL); | 1908 | replaceRegExp_String(&ln, iClob(new_RegExp("(?<!`)`([^`]+?)`(?!`)", 0)), "\x1b[4m\\1\x1b[0m", NULL, NULL); |
1869 | append_String(&result, &ln); | 1909 | append_String(&result, &ln); |
1870 | deinit_String(&ln); | 1910 | deinit_String(&ln); |
1871 | } | 1911 | } |
@@ -2054,17 +2094,17 @@ iRangecc findTextBefore_GmDocument(const iGmDocument *d, const iString *text, co | |||
2054 | } | 2094 | } |
2055 | 2095 | ||
2056 | iGmRunRange findPreformattedRange_GmDocument(const iGmDocument *d, const iGmRun *run) { | 2096 | iGmRunRange findPreformattedRange_GmDocument(const iGmDocument *d, const iGmRun *run) { |
2057 | iAssert(run->preId); | 2097 | iAssert(preId_GmRun(run)); |
2058 | iGmRunRange range = { run, run }; | 2098 | iGmRunRange range = { run, run }; |
2059 | /* Find the beginning. */ | 2099 | /* Find the beginning. */ |
2060 | while (range.start > (const iGmRun *) constData_Array(&d->layout)) { | 2100 | while (range.start > (const iGmRun *) constData_Array(&d->layout)) { |
2061 | const iGmRun *prev = range.start - 1; | 2101 | const iGmRun *prev = range.start - 1; |
2062 | if (prev->preId != run->preId) break; | 2102 | if (preId_GmRun(prev) != preId_GmRun(run)) break; |
2063 | range.start = prev; | 2103 | range.start = prev; |
2064 | } | 2104 | } |
2065 | /* Find the ending. */ | 2105 | /* Find the ending. */ |
2066 | while (range.end < (const iGmRun *) constEnd_Array(&d->layout)) { | 2106 | while (range.end < (const iGmRun *) constEnd_Array(&d->layout)) { |
2067 | if (range.end->preId != run->preId) break; | 2107 | if (preId_GmRun(range.end) != preId_GmRun(run)) break; |
2068 | range.end++; | 2108 | range.end++; |
2069 | } | 2109 | } |
2070 | return range; | 2110 | return range; |
@@ -2240,6 +2280,22 @@ iChar siteIcon_GmDocument(const iGmDocument *d) { | |||
2240 | return d->siteIcon; | 2280 | return d->siteIcon; |
2241 | } | 2281 | } |
2242 | 2282 | ||
2283 | void runBaseAttributes_GmDocument(const iGmDocument *d, const iGmRun *run, int *fontId_out, | ||
2284 | int *colorId_out) { | ||
2285 | /* Font and color according to the line type. These are needed because each GmRun is | ||
2286 | a segment of a paragraph, and if the font or color changes inside the run, each wrapped | ||
2287 | segment needs to know both the current font/color and ALSO the base font/color, so | ||
2288 | the default attributes can be restored. */ | ||
2289 | if (run->isLede) { | ||
2290 | *fontId_out = firstParagraph_FontId; | ||
2291 | *colorId_out = tmFirstParagraph_ColorId; | ||
2292 | } | ||
2293 | else { | ||
2294 | *fontId_out = fontWithSize_Text(d->theme.fonts[run->lineType], run->font % max_FontSize); /* retain size */ | ||
2295 | *colorId_out = d->theme.colors[run->lineType]; | ||
2296 | } | ||
2297 | } | ||
2298 | |||
2243 | iRangecc findLoc_GmRun(const iGmRun *d, iInt2 pos) { | 2299 | iRangecc findLoc_GmRun(const iGmRun *d, iInt2 pos) { |
2244 | if (pos.y < top_Rect(d->bounds)) { | 2300 | if (pos.y < top_Rect(d->bounds)) { |
2245 | return (iRangecc){ d->text.start, d->text.start }; | 2301 | return (iRangecc){ d->text.start, d->text.start }; |
diff --git a/src/gmdocument.h b/src/gmdocument.h index 20bc9890..6ad7efdc 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -140,16 +140,13 @@ struct Impl_GmRun { | |||
140 | uint32_t color : 7; /* see max_ColorId */ | 140 | uint32_t color : 7; /* see max_ColorId */ |
141 | 141 | ||
142 | uint32_t font : 10; | 142 | uint32_t font : 10; |
143 | uint32_t mediaType : 3; | 143 | uint32_t mediaType : 3; /* note: max_MediaType means preformatted block */ |
144 | uint32_t mediaId : 9; /* zero if not an image */ | 144 | uint32_t lineType : 3; |
145 | uint32_t preId : 10; /* preformatted block ID (sequential); merge with mediaId? */ | 145 | uint32_t mediaId : 15; /* zero if not an image */ |
146 | uint32_t isLede : 1; | ||
146 | }; | 147 | }; |
147 | }; | 148 | }; |
148 | 149 | ||
149 | iLocalDef iMediaId mediaId_GmRun(const iGmRun *d) { | ||
150 | return (iMediaId){ .type = d->mediaType, .id = d->mediaId }; | ||
151 | } | ||
152 | |||
153 | iDeclareType(GmRunRange) | 150 | iDeclareType(GmRunRange) |
154 | 151 | ||
155 | struct Impl_GmRunRange { | 152 | struct Impl_GmRunRange { |
@@ -157,7 +154,20 @@ struct Impl_GmRunRange { | |||
157 | const iGmRun *end; | 154 | const iGmRun *end; |
158 | }; | 155 | }; |
159 | 156 | ||
160 | iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos); | 157 | iLocalDef iBool isMedia_GmRun(const iGmRun *d) { |
158 | return d->mediaType > 0 && d->mediaType < max_MediaType; | ||
159 | } | ||
160 | iLocalDef iMediaId mediaId_GmRun(const iGmRun *d) { | ||
161 | if (d->mediaType < max_MediaType) { | ||
162 | return (iMediaId){ .type = d->mediaType, .id = d->mediaId }; | ||
163 | } | ||
164 | return iInvalidMediaId; | ||
165 | } | ||
166 | iLocalDef uint32_t preId_GmRun(const iGmRun *d) { | ||
167 | return d->mediaType == max_MediaType ? d->mediaId : 0; | ||
168 | } | ||
169 | |||
170 | iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos); | ||
161 | 171 | ||
162 | iDeclareClass(GmDocument) | 172 | iDeclareClass(GmDocument) |
163 | iDeclareObjectConstruction(GmDocument) | 173 | iDeclareObjectConstruction(GmDocument) |
@@ -215,6 +225,9 @@ iRangecc findText_GmDocument (const iGmDocument *, const | |||
215 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); | 225 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); |
216 | iGmRunRange findPreformattedRange_GmDocument (const iGmDocument *, const iGmRun *run); | 226 | iGmRunRange findPreformattedRange_GmDocument (const iGmDocument *, const iGmRun *run); |
217 | 227 | ||
228 | void runBaseAttributes_GmDocument (const iGmDocument *, const iGmRun *run, | ||
229 | int *fontId_out, int *colorId_out); | ||
230 | |||
218 | enum iGmLinkPart { | 231 | enum iGmLinkPart { |
219 | icon_GmLinkPart, | 232 | icon_GmLinkPart, |
220 | text_GmLinkPart, | 233 | text_GmLinkPart, |
@@ -241,3 +254,4 @@ const iGmPreMeta *preMeta_GmDocument (const iGmDocument *, uint16_t preId); | |||
241 | iInt2 preRunMargin_GmDocument (const iGmDocument *, uint16_t preId); | 254 | iInt2 preRunMargin_GmDocument (const iGmDocument *, uint16_t preId); |
242 | iBool preIsFolded_GmDocument (const iGmDocument *, uint16_t preId); | 255 | iBool preIsFolded_GmDocument (const iGmDocument *, uint16_t preId); |
243 | iBool preHasAltText_GmDocument(const iGmDocument *, uint16_t preId); | 256 | iBool preHasAltText_GmDocument(const iGmDocument *, uint16_t preId); |
257 | |||
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 | ||
637 | static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { | 637 | static 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 | ||
4125 | static void drawMark_DrawContext_(void *context, const iGmRun *run) { | 4125 | static 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 | ||
2248 | static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, | 2248 | static 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 | ||
267 | iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) | 269 | iDefineTypeConstructionArgs(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) { | 586 | void 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 | ||
592 | void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { | 591 | void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { |
593 | fontSizeFactor *= contentScale_Text_; | 592 | fontSizeFactor *= contentScale_Text_; |
@@ -855,25 +854,45 @@ static iBool isControl_Char_(iChar c) { | |||
855 | iDeclareType(AttributedRun) | 854 | iDeclareType(AttributedRun) |
856 | 855 | ||
857 | struct Impl_AttributedRun { | 856 | struct 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 | ||
868 | static 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 | |||
878 | static void setFgColor_AttributedRun_(iAttributedRun *d, int colorId) { | ||
879 | d->attrib.colorId = colorId; | ||
880 | d->fgColor_.a = 0; | ||
881 | } | ||
882 | |||
868 | iDeclareType(AttributedText) | 883 | iDeclareType(AttributedText) |
869 | iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, | 884 | iDeclareTypeConstructionArgs(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 | ||
872 | struct Impl_AttributedText { | 888 | struct 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 | ||
887 | iDefineTypeConstructionArgs(AttributedText, | 905 | iDefineTypeConstructionArgs(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 | ||
892 | static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) { | 912 | static 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 | ||
919 | static iFont *withStyle_Font_(const iFont *d, enum iFontStyle styleId) { | 939 | int 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 | |||
945 | int 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 | ||
925 | static iFont *withFontId_Font_(const iFont *d, enum iFontId fontId) { | 951 | int 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 | ||
931 | static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { | 957 | static 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 | ||
1116 | void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor, | 1159 | void 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 | ||
1340 | static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int advance, iBool isBaseRTL) { | 1384 | static 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 | ||
1942 | static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int origin, int advance, | 1994 | static 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 *); | |||
120 | int lineHeight_Text (int fontId); | 120 | int lineHeight_Text (int fontId); |
121 | float emRatio_Text (int fontId); /* em advance to line height ratio */ | 121 | float emRatio_Text (int fontId); /* em advance to line height ratio */ |
122 | iRect visualBounds_Text (int fontId, iRangecc text); | 122 | iRect visualBounds_Text (int fontId, iRangecc text); |
123 | int fontWithSize_Text (int fontId, enum iFontSize sizeId); | ||
124 | int fontWithStyle_Text (int fontId, enum iFontStyle styleId); | ||
125 | int fontWithFamily_Text (int fontId, enum iFontId familyId); | ||
123 | 126 | ||
124 | iDeclareType(TextMetrics) | 127 | iDeclareType(TextMetrics) |
125 | 128 | ||
@@ -149,9 +152,10 @@ enum iAlignment { | |||
149 | right_Alignment, | 152 | right_Alignment, |
150 | }; | 153 | }; |
151 | 154 | ||
152 | void setOpacity_Text (float opacity); | 155 | void setOpacity_Text (float opacity); |
156 | void setBaseAttributes_Text (int fontId, int colorId); /* current "normal" text attributes */ | ||
153 | 157 | ||
154 | void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ | 158 | void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ |
155 | 159 | ||
156 | void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); | 160 | void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); |
157 | void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); | 161 | void 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 | ||
180 | iDeclareType(TextAttrib) | ||
181 | |||
182 | /* Initial attributes at the start of a text string. These may be modified by control | ||
183 | sequences inside a text run. */ | ||
184 | struct 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 | |||
176 | struct Impl_WrapText { | 195 | struct 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 */ |