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/gmdocument.c | |
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/gmdocument.c')
-rw-r--r-- | src/gmdocument.c | 200 |
1 files changed, 128 insertions, 72 deletions
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 }; |