diff options
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 }; |