=> 6b931c95725eef2ebb7e831c4017d3d67b33294f
[1mdiff --git a/src/fontpack.c b/src/fontpack.c[m [1mindex fb1c98ee..9baedc0e 100644[m [1m--- a/src/fontpack.c[m [1m+++ b/src/fontpack.c[m [36m@@ -48,7 +48,7 @@[m [mfloat scale_FontSize(enum iFontSize size) {[m 1.333,[m 1.666,[m 2.000,[m [31m- 0.568,[m [32m+[m[32m 0.650, //0.568,[m 0.710, /* calibration: fits the Lagrange title screen with Normal line width */[m };[m if (size < 0 || size >= max_FontSize) {[m [1mdiff --git a/src/gmdocument.c b/src/gmdocument.c[m [1mindex f0d9bf08..5c2a849e 100644[m [1m--- a/src/gmdocument.c[m [1m+++ b/src/gmdocument.c[m [36m@@ -75,6 +75,15 @@[m [miDefineTypeConstruction(GmLink)[m [m /*----------------------------------------------------------------------------------------------*/[m [m [32m+[m[32miDeclareType(GmTheme)[m [32m+[m [32m+[m[32mstruct Impl_GmTheme {[m [32m+[m[32m int colors[max_GmLineType];[m [32m+[m[32m int fonts[max_GmLineType];[m [32m+[m[32m};[m [32m+[m [32m+[m[32m/*----------------------------------------------------------------------------------------------*/[m [32m+[m struct Impl_GmDocument {[m iObject object;[m enum iSourceFormat format;[m [36m@@ -91,6 +100,7 @@[m [mstruct Impl_GmDocument {[m iString title; /* the first top-level title */[m iArray headings;[m iArray preMeta; /* metadata about preformatted blocks */[m [32m+[m[32m iGmTheme theme;[m uint32_t themeSeed;[m iChar siteIcon;[m iMedia * media;[m [36m@@ -101,6 +111,51 @@[m [mstruct Impl_GmDocument {[m [m iDefineObjectConstruction(GmDocument)[m [m [32m+[m[32mstatic iBool isForcedMonospace_GmDocument_(const iGmDocument *d) {[m [32m+[m[32m const iRangecc scheme = urlScheme_String(&d->url);[m [32m+[m[32m if (equalCase_Rangecc(scheme, "gemini")) {[m [32m+[m[32m return prefs_App()->monospaceGemini;[m [32m+[m[32m }[m [32m+[m[32m if (equalCase_Rangecc(scheme, "gopher") ||[m [32m+[m[32m equalCase_Rangecc(scheme, "finger")) {[m [32m+[m[32m return prefs_App()->monospaceGopher;[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void initTheme_GmDocument_(iGmDocument *d) {[m [32m+[m[32m static const int defaultColors[max_GmLineType] = {[m [32m+[m[32m tmParagraph_ColorId,[m [32m+[m[32m tmParagraph_ColorId, /* bullet */[m [32m+[m[32m tmPreformatted_ColorId,[m [32m+[m[32m tmQuote_ColorId,[m [32m+[m[32m tmHeading1_ColorId,[m [32m+[m[32m tmHeading2_ColorId,[m [32m+[m[32m tmHeading3_ColorId,[m [32m+[m[32m tmLinkText_ColorId,[m [32m+[m[32m };[m [32m+[m[32m iGmTheme *theme = &d->theme;[m [32m+[m[32m memcpy(theme->colors, defaultColors, sizeof(theme->colors));[m [32m+[m[32m const iPrefs *prefs = prefs_App();[m [32m+[m[32m const iBool isMono = isForcedMonospace_GmDocument_(d);[m [32m+[m[32m const iBool isDarkBg = isDark_GmDocumentTheme([m [32m+[m[32m isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight);[m [32m+[m[32m const enum iFontId headingFont = isMono ? documentMonospace_FontId : documentHeading_FontId;[m [32m+[m[32m const enum iFontId bodyFont = isMono ? documentMonospace_FontId : documentBody_FontId;[m [32m+[m[32m theme->fonts[text_GmLineType] = FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize);[m [32m+[m[32m theme->fonts[bullet_GmLineType] = FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize);[m [32m+[m[32m theme->fonts[preformatted_GmLineType] = preformatted_FontId;[m [32m+[m[32m theme->fonts[quote_GmLineType] = isMono ? monospaceParagraph_FontId : quote_FontId;[m [32m+[m[32m theme->fonts[heading1_GmLineType] = FONT_ID(headingFont, bold_FontStyle, contentHuge_FontSize);[m [32m+[m[32m theme->fonts[heading2_GmLineType] = FONT_ID(headingFont, bold_FontStyle, contentLarge_FontSize);[m [32m+[m[32m theme->fonts[heading3_GmLineType] = FONT_ID(headingFont, regular_FontStyle, contentBig_FontSize);[m [32m+[m[32m theme->fonts[link_GmLineType] = FONT_ID([m [32m+[m[32m bodyFont,[m [32m+[m[32m ((isDarkBg && prefs->boldLinkDark) || (!isDarkBg && prefs->boldLinkLight)) ? semiBold_FontStyle[m [32m+[m[32m : regular_FontStyle,[m [32m+[m[32m contentRegular_FontSize);[m [32m+[m[32m}[m [32m+[m static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) {[m if (d->format == plainText_SourceFormat) {[m return text_GmLineType;[m [36m@@ -318,18 +373,6 @@[m [mstatic iBool isGopher_GmDocument_(const iGmDocument *d) {[m equalCase_Rangecc(scheme, "finger"));[m }[m [m [31m-static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) {[m [31m- const iRangecc scheme = urlScheme_String(&d->url);[m [31m- if (equalCase_Rangecc(scheme, "gemini")) {[m [31m- return prefs_App()->monospaceGemini;[m [31m- }[m [31m- if (equalCase_Rangecc(scheme, "gopher") ||[m [31m- equalCase_Rangecc(scheme, "finger")) {[m [31m- return prefs_App()->monospaceGopher;[m [31m- }[m [31m- return iFalse;[m [31m-}[m [31m-[m static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo,[m uint16_t linkId) {[m iGmLink *link = at_PtrArray(&d->links, linkId - 1);[m [36m@@ -402,7 +445,8 @@[m [mstruct Impl_RunTypesetter {[m int rightMargin;[m iBool isWordWrapped;[m iBool isPreformat;[m [31m- const int *fonts;[m [32m+[m[32m int baseFont;[m [32m+[m[32m int baseColor;[m };[m [m static void init_RunTypesetter_(iRunTypesetter *d) {[m [36m@@ -425,39 +469,47 @@[m [mstatic void commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) {[m [m static const int maxLedeLines_ = 10;[m [m [31m-static const int colors[max_GmLineType] = {[m [31m- tmParagraph_ColorId,[m [31m- tmParagraph_ColorId,[m [31m- tmPreformatted_ColorId,[m [31m- tmQuote_ColorId,[m [31m- tmHeading1_ColorId,[m [31m- tmHeading2_ColorId,[m [31m- tmHeading3_ColorId,[m [31m- tmLinkText_ColorId,[m [31m-};[m [32m+[m[32mstatic int applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) {[m [32m+[m[32m /* WARNING: This is duplicated in run_Font_(). Make sure they behave identically. */[m [32m+[m[32m if (attrib.bold) {[m [32m+[m[32m d->run.font = fontWithStyle_Text(d->baseFont, bold_FontStyle);[m [32m+[m[32m d->run.color = tmFirstParagraph_ColorId;[m [32m+[m[32m }[m [32m+[m[32m else if (attrib.italic) {[m [32m+[m[32m d->run.font = fontWithStyle_Text(d->baseFont, italic_FontStyle);[m [32m+[m[32m }[m [32m+[m[32m else if (attrib.monospace) {[m [32m+[m[32m d->run.font = fontWithFamily_Text(d->baseFont, monospace_FontId);[m [32m+[m[32m d->run.color = tmPreformatted_ColorId;[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m d->run.font = d->baseFont;[m [32m+[m[32m d->run.color = d->baseColor;[m [32m+[m[32m }[m [32m+[m[32m}[m [m [31m-static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, int origin,[m [31m- int advance, iBool isBaseRTL) {[m [32m+[m[32mstatic iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, iTextAttrib attrib,[m [32m+[m[32m int origin, int advance) {[m iAssert(wrapRange.start <= wrapRange.end);[m trimEnd_Rangecc(&wrapRange);[m // printf("typeset: {%s}\n", cstr_Rangecc(wrapRange));[m iRunTypesetter *d = wrap->context;[m [31m- const int fontId = d->run.font;[m d->run.text = wrapRange;[m [32m+[m[32m applyAttributes_RunTypesetter_(d, attrib);[m if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) {[m [31m- d->pos.y -= d->lineHeightReduction * lineHeight_Text(fontId);[m [32m+[m[32m d->pos.y -= d->lineHeightReduction * lineHeight_Text(d->baseFont);[m }[m d->run.bounds.pos = addX_I2(d->pos, origin + d->indent);[m [31m- const iInt2 dims = init_I2(advance, lineHeight_Text(fontId));[m [32m+[m[32m const iInt2 dims = init_I2(advance, lineHeight_Text(d->baseFont));[m iChangeFlags(d->run.flags, wide_GmRunFlag, (d->isPreformat && dims.x > d->layoutWidth));[m d->run.bounds.size.x = iMax(wrap->maxWidth, dims.x) - origin; /* Extends to the right edge for selection. */[m d->run.bounds.size.y = dims.y;[m d->run.visBounds = d->run.bounds;[m d->run.visBounds.size.x = dims.x;[m [31m- d->run.isRTL = isBaseRTL;[m [32m+[m[32m d->run.isRTL = attrib.isBaseRTL;[m pushBack_Array(&d->layout, &d->run);[m d->run.flags &= ~startOfLine_GmRunFlag;[m [31m- d->pos.y += lineHeight_Text(fontId) * prefs_App()->lineSpacing;[m [32m+[m[32m d->pos.y += lineHeight_Text(d->baseFont) * prefs_App()->lineSpacing;[m return iTrue; /* continue to next wrapped line */[m }[m [m [36m@@ -469,25 +521,10 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m const iBool isVeryNarrow = d->size.x <= 70 * gap_Text;[m const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text;[m const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI);[m [31m- const iBool isDarkBg = isDark_GmDocumentTheme([m [31m- isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight);[m [32m+[m[32m// const iBool isDarkBg = isDark_GmDocumentTheme([m [32m+[m[32m// isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight);[m [32m+[m[32m initTheme_GmDocument_(d);[m /* TODO: Collect these parameters into a GmTheme. */[m [31m- const enum iFontId headingFont = isMono ? documentMonospace_FontId : documentHeading_FontId;[m [31m- const enum iFontId bodyFont = isMono ? documentMonospace_FontId : documentBody_FontId;[m [31m- const int fonts[max_GmLineType] = {[m [31m- FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize), /* text */[m [31m- FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize), /* bullet */[m [31m- preformatted_FontId, /* pre */[m [31m- isMono ? monospaceParagraph_FontId : quote_FontId, /* quote */[m [31m- FONT_ID(headingFont, bold_FontStyle, contentHuge_FontSize), /* h1 */[m [31m- FONT_ID(headingFont, bold_FontStyle, contentLarge_FontSize), /* h2 */[m [31m- FONT_ID(headingFont, regular_FontStyle, contentBig_FontSize), /* h3 */[m [31m- FONT_ID(bodyFont,[m [31m- ((isDarkBg && prefs->boldLinkDark) || (!isDarkBg && prefs->boldLinkLight))[m [31m- ? semiBold_FontStyle[m [31m- : regular_FontStyle,[m [31m- contentRegular_FontSize) /* link */[m [31m- };[m float indents[max_GmLineType] = { 5, 10, 5, isNarrow ? 5 : 10, 0, 0, 0, 5 };[m if (isExtremelyNarrow) {[m /* Further reduce the margins. */[m [36m@@ -598,7 +635,7 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m }[m }[m trimLine_Rangecc(&line, type, isNormalized);[m [31m- run.font = fonts[type];[m [32m+[m[32m run.font = d->theme.fonts[type];[m /* Remember headings for the document outline. */[m if (type == heading1_GmLineType || type == heading2_GmLineType || type == heading3_GmLineType) {[m pushBack_Array([m [36m@@ -618,7 +655,8 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m addSiteBanner = iFalse; /* overrides the banner */[m continue;[m }[m [31m- run.preId = preId;[m [32m+[m[32m run.mediaType = max_MediaType; /* preformatted block */[m [32m+[m[32m run.mediaId = preId;[m run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont);[m indent = indents[type];[m }[m [36m@@ -650,7 +688,7 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m run.visBounds.size = init_I2(gap_Text, lineHeight_Text(run.font));[m run.bounds = zero_Rect(); /* just visual */[m run.text = iNullRange;[m [31m- run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag;[m [32m+[m[32m run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag;[m pushBack_Array(&d->layout, &run);[m }[m pos.y += lineHeight_Text(run.font) * prefs->lineSpacing;[m [36m@@ -708,7 +746,8 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m altText.text).bounds.size;[m altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x,[m size.y + 2 * margin.y);[m [31m- altText.preId = preId;[m [32m+[m[32m altText.mediaType = max_MediaType; /* preformatted */[m [32m+[m[32m altText.mediaId = preId;[m pushBack_Array(&d->layout, &altText);[m pos.y += height_Rect(altText.bounds);[m contentLine = meta->bounds; /* Skip the whole thing. */[m [36m@@ -723,7 +762,6 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m setRange_String(&d->title, line);[m }[m /* List bullet. */[m [31m- run.color = colors[type];[m if (type == bullet_GmLineType) {[m /* TODO: Literata bullet is broken? */[m iGmRun bulRun = run;[m [36m@@ -795,18 +833,17 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m icon.flags |= decoration_GmRunFlag;[m pushBack_Array(&d->layout, &icon);[m }[m [31m- run.color = colors[type];[m [32m+[m[32m run.lineType = type;[m [32m+[m[32m run.color = d->theme.colors[type];[m if (d->format == plainText_SourceFormat) {[m [31m- run.color = colors[text_GmLineType];[m [32m+[m[32m run.color = d->theme.colors[text_GmLineType];[m }[m /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */[m // int bigCount = 0;[m [31m- iBool isLedeParagraph = iFalse;[m if (type == text_GmLineType && isFirstText) {[m if (!isMono) run.font = firstParagraph_FontId;[m [31m- run.color = tmFirstParagraph_ColorId;[m [31m-// bigCount = 15; /* max lines -- what if the whole document is one paragraph? */[m [31m- isLedeParagraph = iTrue;[m [32m+[m[32m run.color = tmFirstParagraph_ColorId;[m [32m+[m[32m run.isLede = iTrue;[m isFirstText = iFalse;[m }[m else if (type != heading1_GmLineType) {[m [36m@@ -826,7 +863,7 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m init_RunTypesetter_(&rts);[m rts.run = run;[m rts.pos = pos;[m [31m- rts.fonts = fonts;[m [32m+[m[32m //rts.fonts = fonts;[m rts.isWordWrapped = (d->format == plainText_SourceFormat ? prefs->plainTextWrap[m : !isPreformat);[m rts.isPreformat = isPreformat;[m [36m@@ -859,6 +896,8 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m }[m for (;;) { /* need to retry if the font needs changing */[m rts.run.flags |= startOfLine_GmRunFlag;[m [32m+[m[32m rts.baseFont = rts.run.font;[m [32m+[m[32m rts.baseColor = rts.run.color;[m iWrapText wrapText = { .text = line,[m .maxWidth = rts.isWordWrapped[m ? d->size.x - run.bounds.pos.x -[m [36m@@ -868,7 +907,7 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m .wrapFunc = typesetOneLine_RunTypesetter_,[m .context = &rts };[m measure_WrapText(&wrapText, rts.run.font);[m [31m- if (!isLedeParagraph || size_Array(&rts.layout) <= maxLedeLines_) {[m [32m+[m[32m if (!rts.run.isLede || size_Array(&rts.layout) <= maxLedeLines_) {[m if (wrapText.baseDir < 0) {[m /* Right-aligned paragraphs need margins and decorations to be flipped. */[m iForEach(Array, pr, &rts.layout) {[m [36m@@ -891,11 +930,12 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m commit_RunTypesetter_(&rts, d);[m break;[m }[m [32m+[m[32m /* Try again... */[m clear_RunTypesetter_(&rts);[m rts.pos = pos;[m [31m- rts.run.font = rts.fonts[text_GmLineType];[m [31m- rts.run.color = colors[text_GmLineType];[m [31m- isLedeParagraph = iFalse;[m [32m+[m[32m rts.run.font = rts.baseFont = d->theme.fonts[text_GmLineType];[m [32m+[m[32m rts.run.color = rts.baseColor = d->theme.colors[text_GmLineType];[m [32m+[m[32m rts.run.isLede = iFalse;[m }[m pos = rts.pos;[m deinit_RunTypesetter_(&rts);[m [36m@@ -996,7 +1036,7 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m /* TODO: Store the dimensions and ranges for later access. */[m iForEach(Array, i, &d->layout) {[m iGmRun *run = i.value;[m [31m- if (run->preId && run->flags & wide_GmRunFlag) {[m [32m+[m[32m if (preId_GmRun(run) && run->flags & wide_GmRunFlag) {[m iGmRunRange block = findPreformattedRange_GmDocument(d, run);[m for (const iGmRun *j = block.start; j != block.end; j++) {[m iConstCast(iGmRun *, j)->flags |= wide_GmRunFlag;[m [36m@@ -1800,11 +1840,11 @@[m [mstatic void flushPendingLinks_(iArray *links, const iString *source, iString *ou[m static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) {[m iAssert(d->format == markdown_SourceFormat);[m /* Get rid of indented preformats. */ {[m [31m- iArray *pendingLinks = collectNew_Array(sizeof(iPendingLink));[m [32m+[m[32m iArray *pendingLinks = collectNew_Array(sizeof(iPendingLink));[m const iRegExp *imageLinkPattern = iClob(new_RegExp("\n?!\\[(.+)\\]\\(([^)]+)\\)\n?", 0));[m [31m- const iRegExp *linkPattern = iClob(new_RegExp("\\[(.+?)\\]\\(([^)]+)\\)", 0));[m [32m+[m[32m const iRegExp *linkPattern = iClob(new_RegExp("\\[(.+?)\\]\\(([^)]+)\\)", 0));[m const iRegExp *namedLinkPattern = iClob(new_RegExp("\\[(.+?)\\]\\[(.+?)\\]", 0));[m [31m- const iRegExp *namePattern = iClob(new_RegExp("\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0));[m [32m+[m[32m const iRegExp *namePattern = iClob(new_RegExp("\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0));[m iString result;[m init_String(&result);[m iRangecc line = iNullRange;[m [36m@@ -1865,7 +1905,7 @@[m [mstatic void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) {[m replaceRegExp_String(&ln, imageLinkPattern, "\n=> \\2 \\1\n", NULL, NULL);[m replaceRegExp_String(&ln, namedLinkPattern, "\\1", addPendingNamedLink_, pendingLinks);[m replaceRegExp_String(&ln, linkPattern, "\\1", addPendingLink_, pendingLinks);[m [31m- replaceRegExp_String(&ln, iClob(new_RegExp("(?preId);[m [32m+[m[32m iAssert(preId_GmRun(run));[m iGmRunRange range = { run, run };[m /* Find the beginning. */[m while (range.start > (const iGmRun *) constData_Array(&d->layout)) {[m const iGmRun *prev = range.start - 1;[m [31m- if (prev->preId != run->preId) break;[m [32m+[m[32m if (preId_GmRun(prev) != preId_GmRun(run)) break;[m range.start = prev;[m }[m /* Find the ending. */[m while (range.end < (const iGmRun *) constEnd_Array(&d->layout)) {[m [31m- if (range.end->preId != run->preId) break;[m [32m+[m[32m if (preId_GmRun(range.end) != preId_GmRun(run)) break;[m range.end++;[m }[m return range;[m [36m@@ -2240,6 +2280,22 @@[m [miChar siteIcon_GmDocument(const iGmDocument *d) {[m return d->siteIcon;[m }[m [m [32m+[m[32mvoid runBaseAttributes_GmDocument(const iGmDocument *d, const iGmRun *run, int *fontId_out,[m [32m+[m[32m int *colorId_out) {[m [32m+[m[32m /* Font and color according to the line type. These are needed because each GmRun is[m [32m+[m[32m a segment of a paragraph, and if the font or color changes inside the run, each wrapped[m [32m+[m[32m segment needs to know both the current font/color and ALSO the base font/color, so[m [32m+[m[32m the default attributes can be restored. */[m [32m+[m[32m if (run->isLede) {[m [32m+[m[32m *fontId_out = firstParagraph_FontId;[m [32m+[m[32m *colorId_out = tmFirstParagraph_ColorId;[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m *fontId_out = fontWithSize_Text(d->theme.fonts[run->lineType], run->font % max_FontSize); /* retain size */[m [32m+[m[32m *colorId_out = d->theme.colors[run->lineType];[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m iRangecc findLoc_GmRun(const iGmRun *d, iInt2 pos) {[m if (pos.y < top_Rect(d->bounds)) {[m return (iRangecc){ d->text.start, d->text.start };[m [1mdiff --git a/src/gmdocument.h b/src/gmdocument.h[m [1mindex 20bc9890..6ad7efdc 100644[m [1m--- a/src/gmdocument.h[m [1m+++ b/src/gmdocument.h[m [36m@@ -140,16 +140,13 @@[m [mstruct Impl_GmRun {[m uint32_t color : 7; /* see max_ColorId */[m [m uint32_t font : 10;[m [31m- uint32_t mediaType : 3;[m [31m- uint32_t mediaId : 9; /* zero if not an image */[m [31m- uint32_t preId : 10; /* preformatted block ID (sequential); merge with mediaId? */[m [32m+[m[32m uint32_t mediaType : 3; /* note: max_MediaType means preformatted block */[m [32m+[m[32m uint32_t lineType : 3;[m [32m+[m[32m uint32_t mediaId : 15; /* zero if not an image */[m [32m+[m[32m uint32_t isLede : 1;[m };[m };[m [m [31m-iLocalDef iMediaId mediaId_GmRun(const iGmRun *d) {[m [31m- return (iMediaId){ .type = d->mediaType, .id = d->mediaId };[m [31m-}[m [31m-[m iDeclareType(GmRunRange)[m [m struct Impl_GmRunRange {[m [36m@@ -157,7 +154,20 @@[m [mstruct Impl_GmRunRange {[m const iGmRun *end;[m };[m [m [31m-iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos);[m [32m+[m[32miLocalDef iBool isMedia_GmRun(const iGmRun *d) {[m [32m+[m[32m return d->mediaType > 0 && d->mediaType < max_MediaType;[m [32m+[m[32m}[m [32m+[m[32miLocalDef iMediaId mediaId_GmRun(const iGmRun *d) {[m [32m+[m[32m if (d->mediaType < max_MediaType) {[m [32m+[m[32m return (iMediaId){ .type = d->mediaType, .id = d->mediaId };[m [32m+[m[32m }[m [32m+[m[32m return iInvalidMediaId;[m [32m+[m[32m}[m [32m+[m[32miLocalDef uint32_t preId_GmRun(const iGmRun *d) {[m [32m+[m[32m return d->mediaType == max_MediaType ? d->mediaId : 0;[m [32m+[m[32m}[m [32m+[m [32m+[m[32miRangecc findLoc_GmRun (const iGmRun *, iInt2 pos);[m [m iDeclareClass(GmDocument)[m iDeclareObjectConstruction(GmDocument)[m [36m@@ -215,6 +225,9 @@[m [miRangecc findText_GmDocument (const iGmDocument *, const[m iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before);[m iGmRunRange findPreformattedRange_GmDocument (const iGmDocument *, const iGmRun *run);[m [m [32m+[m[32mvoid runBaseAttributes_GmDocument (const iGmDocument *, const iGmRun *run,[m [32m+[m[32m int *fontId_out, int *colorId_out);[m [32m+[m enum iGmLinkPart {[m icon_GmLinkPart,[m text_GmLinkPart,[m [36m@@ -241,3 +254,4 @@[m [mconst iGmPreMeta *preMeta_GmDocument (const iGmDocument *, uint16_t preId);[m iInt2 preRunMargin_GmDocument (const iGmDocument *, uint16_t preId);[m iBool preIsFolded_GmDocument (const iGmDocument *, uint16_t preId);[m iBool preHasAltText_GmDocument(const iGmDocument *, uint16_t preId);[m [41m+[m [1mdiff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c[m [1mindex 8c87ba1a..8b2d6a5a 100644[m [1m--- a/src/ui/documentwidget.c[m [1m+++ b/src/ui/documentwidget.c[m [36m@@ -569,7 +569,7 @@[m [mstatic void addVisible_DocumentWidget_(void *context, const iGmRun *run) {[m }[m d->visibleRuns.end = run;[m }[m [31m- if (run->preId) {[m [32m+[m[32m if (preId_GmRun(run)) {[m pushBack_PtrArray(&d->visiblePre, run);[m if (run->flags & wide_GmRunFlag) {[m pushBack_PtrArray(&d->visibleWideRuns, run);[m [36m@@ -635,14 +635,14 @@[m [mstatic void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) {[m }[m [m static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) {[m [31m- if (run->preId && run->flags & wide_GmRunFlag) {[m [31m- if (d->animWideRunId == run->preId) {[m [32m+[m[32m if (preId_GmRun(run) && run->flags & wide_GmRunFlag) {[m [32m+[m[32m if (d->animWideRunId == preId_GmRun(run)) {[m return -value_Anim(&d->animWideRunOffset);[m }[m const size_t numOffsets = size_Array(&d->wideRunOffsets);[m const int *offsets = constData_Array(&d->wideRunOffsets);[m [31m- if (run->preId <= numOffsets) {[m [31m- return -offsets[run->preId - 1];[m [32m+[m[32m if (preId_GmRun(run) <= numOffsets) {[m [32m+[m[32m return -offsets[preId_GmRun(run) - 1];[m }[m }[m return 0;[m [36m@@ -731,7 +731,7 @@[m [mstatic void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {[m }[m }[m else if (d->hoverPre &&[m [31m- preHasAltText_GmDocument(d->doc, d->hoverPre->preId) &&[m [32m+[m[32m preHasAltText_GmDocument(d->doc, preId_GmRun(d->hoverPre)) &&[m ~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) {[m setValueSpeed_Anim(&d->altTextOpacity, 1.0f, 1.5f);[m if (!isFinished_Anim(&d->altTextOpacity)) {[m [36m@@ -1812,10 +1812,10 @@[m [mstatic void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos,[m maxWidth = iMax(maxWidth, width_Rect(r->visBounds));[m }[m const int maxOffset = maxWidth - documentWidth_DocumentWidget_(d) + d->pageMargin * gap_UI;[m [31m- if (size_Array(&d->wideRunOffsets) <= run->preId) {[m [31m- resize_Array(&d->wideRunOffsets, run->preId + 1);[m [32m+[m[32m if (size_Array(&d->wideRunOffsets) <= preId_GmRun(run)) {[m [32m+[m[32m resize_Array(&d->wideRunOffsets, preId_GmRun(run) + 1);[m }[m [31m- int *offset = at_Array(&d->wideRunOffsets, run->preId - 1);[m [32m+[m[32m int *offset = at_Array(&d->wideRunOffsets, preId_GmRun(run) - 1);[m const int oldOffset = *offset;[m *offset = iClamp(*offset + delta, 0, maxOffset);[m /* Make sure the whole block gets redraw. */[m [36m@@ -1828,8 +1828,8 @@[m [mstatic void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos,[m d->foundMark = iNullRange;[m }[m if (duration) {[m [31m- if (d->animWideRunId != run->preId || isFinished_Anim(&d->animWideRunOffset)) {[m [31m- d->animWideRunId = run->preId;[m [32m+[m[32m if (d->animWideRunId != preId_GmRun(run) || isFinished_Anim(&d->animWideRunOffset)) {[m [32m+[m[32m d->animWideRunId = preId_GmRun(run);[m init_Anim(&d->animWideRunOffset, oldOffset);[m }[m setValueEased_Anim(&d->animWideRunOffset, *offset, duration);[m [36m@@ -3814,7 +3814,7 @@[m [mstatic iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e[m }[m /* Fold/unfold a preformatted block. */[m if (~d->flags & selecting_DocumentWidgetFlag && d->hoverPre &&[m [31m- preIsFolded_GmDocument(d->doc, d->hoverPre->preId)) {[m [32m+[m[32m preIsFolded_GmDocument(d->doc, preId_GmRun(d->hoverPre))) {[m return iTrue;[m }[m /* Begin selecting a range of text. */[m [36m@@ -3925,7 +3925,7 @@[m [mstatic iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e[m }[m }[m if (d->hoverPre) {[m [31m- togglePreFold_DocumentWidget_(d, d->hoverPre->preId);[m [32m+[m[32m togglePreFold_DocumentWidget_(d, preId_GmRun(d->hoverPre));[m return iTrue;[m }[m if (d->hoverLink) {[m [36m@@ -4124,7 +4124,7 @@[m [mstatic void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol[m [m static void drawMark_DrawContext_(void *context, const iGmRun *run) {[m iDrawContext *d = context;[m [31m- if (run->mediaType == none_MediaType) {[m [32m+[m[32m if (!isMedia_GmRun(run)) {[m fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);[m fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);[m }[m [36m@@ -4249,7 +4249,7 @@[m [mstatic void drawRun_DrawContext_(void *context, const iGmRun *run) {[m }[m return;[m }[m [31m- else if (run->mediaType) {[m [32m+[m[32m else if (isMedia_GmRun(run)) {[m /* Media UIs are drawn afterwards as a dynamic overlay. */[m return;[m }[m [36m@@ -4317,7 +4317,7 @@[m [mstatic void drawRun_DrawContext_(void *context, const iGmRun *run) {[m }[m }[m if (run->flags & altText_GmRunFlag) {[m [31m- const iInt2 margin = preRunMargin_GmDocument(doc, run->preId);[m [32m+[m[32m const iInt2 margin = preRunMargin_GmDocument(doc, preId_GmRun(run));[m fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId);[m drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId);[m drawWrapRange_Text(run->font,[m [36m@@ -4368,11 +4368,17 @@[m [mstatic void drawRun_DrawContext_(void *context, const iGmRun *run) {[m height_Rect(run->visBounds),[m tmQuoteIcon_ColorId);[m }[m [32m+[m[32m /* Base attributes. */ {[m [32m+[m[32m int f, c;[m [32m+[m[32m runBaseAttributes_GmDocument(doc, run, &f, &c);[m [32m+[m[32m setBaseAttributes_Text(f, c);[m [32m+[m[32m }[m drawBoundRange_Text(run->font,[m visPos,[m (run->isRTL ? -1 : 1) * width_Rect(run->visBounds),[m fg,[m run->text);[m [32m+[m[32m setBaseAttributes_Text(-1, -1);[m runDrawn:;[m }[m /* Presentation of links. */[m [36m@@ -4944,7 +4950,7 @@[m [mstatic void draw_DocumentWidget_(const iDocumentWidget *d) {[m /* Alt text. */[m const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5;[m if (d->hoverAltPre && altTextOpacity > 0) {[m [31m- const iGmPreMeta *meta = preMeta_GmDocument(d->doc, d->hoverAltPre->preId);[m [32m+[m[32m const iGmPreMeta *meta = preMeta_GmDocument(d->doc, preId_GmRun(d->hoverAltPre));[m if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag &&[m !isEmpty_Range(&meta->altText)) {[m const int margin = 3 * gap_UI / 2;[m [1mdiff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c[m [1mindex 874cf2b5..e20d6f17 100644[m [1m--- a/src/ui/inputwidget.c[m [1m+++ b/src/ui/inputwidget.c[m [36m@@ -2245,31 +2245,33 @@[m [mstruct Impl_MarkPainter {[m iRect lastMarkRect;[m };[m [m [31m-static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance,[m [31m- iBool isBaseRTL) {[m [31m- iUnused(isBaseRTL);[m [32m+[m[32mstatic iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, iTextAttrib attrib,[m [32m+[m[32m int origin, int advance) {[m iMarkPainter *mp = wrapText->context;[m const iRanges mark = mp->mark;[m if (isEmpty_Range(&mark)) {[m return iTrue; /* nothing marked */[m }[m [32m+[m[32m int fontId = mp->d->font;[m [32m+[m[32m /* TODO: Apply attrib on the font */[m const char *cstr = cstr_String(&mp->line->text);[m const iRanges lineRange = {[m wrappedText.start - cstr + mp->line->range.start,[m wrappedText.end - cstr + mp->line->range.start[m };[m [32m+[m[32m const int lineHeight = lineHeight_Text(mp->d->font);[m if (mark.end <= lineRange.start || mark.start >= lineRange.end) {[m [31m- mp->pos.y += lineHeight_Text(mp->d->font);[m [32m+[m[32m mp->pos.y += lineHeight;[m return iTrue; /* outside of mark */[m }[m [31m- iRect rect = { addX_I2(mp->pos, origin), init_I2(advance, lineHeight_Text(mp->d->font)) };[m [32m+[m[32m iRect rect = { addX_I2(mp->pos, origin), init_I2(advance, lineHeight) };[m if (mark.end < lineRange.end) {[m /* Calculate where the mark ends. */[m const iRangecc markedPrefix = {[m wrappedText.start,[m wrappedText.start + mark.end - lineRange.start[m };[m [31m- rect.size.x = measureRange_Text(mp->d->font, markedPrefix).advance.x;[m [32m+[m[32m rect.size.x = measureRange_Text(fontId, markedPrefix).advance.x;[m }[m if (mark.start > lineRange.start) {[m /* Calculate where the mark starts. */[m [36m@@ -2277,10 +2279,10 @@[m [mstatic iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int or[m wrappedText.start,[m wrappedText.start + mark.start - lineRange.start[m };[m [31m- adjustEdges_Rect(&rect, 0, 0, 0, measureRange_Text(mp->d->font, unmarkedPrefix).advance.x);[m [32m+[m[32m adjustEdges_Rect(&rect, 0, 0, 0, measureRange_Text(fontId, unmarkedPrefix).advance.x);[m }[m rect.size.x = iMax(gap_UI / 3, rect.size.x);[m [31m- mp->pos.y += lineHeight_Text(mp->d->font);[m [32m+[m[32m mp->pos.y += lineHeight;[m fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId);[m if (deviceType_App() != desktop_AppDeviceType) {[m if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect;[m [1mdiff --git a/src/ui/text.c b/src/ui/text.c[m [1mindex fd865fbd..52c6c4e0 100644[m [1m--- a/src/ui/text.c[m [1m+++ b/src/ui/text.c[m [36m@@ -262,6 +262,8 @@[m [mstruct Impl_Text {[m SDL_Palette * grayscale;[m SDL_Palette * blackAndWhite; /* unsmoothed glyph palette */[m iRegExp * ansiEscape;[m [32m+[m[32m int baseFontId; /* base attributes (for restoring via escapes) */[m [32m+[m[32m int baseColorId;[m };[m [m iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)[m [36m@@ -537,10 +539,10 @@[m [mvoid init_Text(iText *d, SDL_Renderer *render) {[m iText *oldActive = activeText_;[m activeText_ = d;[m init_Array(&d->fonts, sizeof(iFont));[m [31m-// d->contentFont = nunito_TextFont;[m [31m-// d->headingFont = nunito_TextFont;[m d->contentFontSize = contentScale_Text_;[m d->ansiEscape = new_RegExp("[[()]([0-9;AB]*)m", 0);[m [32m+[m[32m d->baseFontId = -1;[m [32m+[m[32m d->baseColorId = -1;[m d->render = render;[m /* A grayscale palette for rasterized glyphs. */ {[m SDL_Color colors[256];[m [36m@@ -581,13 +583,10 @@[m [mvoid setOpacity_Text(float opacity) {[m SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);[m }[m [m [31m-//void setFont_Text(iText *d, int fontId, const char *fontSpecId) {[m [31m-// setupFontVariants_Text_(d, findSpec_Fonts(fontSpecId), fontId);[m [31m-// if (d->contentFont != font) {[m [31m-// d->contentFont = font;[m [31m-// resetFonts_Text(d);[m [31m-// }[m [31m-//}[m [32m+[m[32mvoid setBaseAttributes_Text(int fontId, int colorId) {[m [32m+[m[32m activeText_->baseFontId = fontId;[m [32m+[m[32m activeText_->baseColorId = colorId;[m [32m+[m[32m}[m [m void setDocumentFontSize_Text(iText *d, float fontSizeFactor) {[m fontSizeFactor *= contentScale_Text_;[m [36m@@ -855,25 +854,45 @@[m [mstatic iBool isControl_Char_(iChar c) {[m iDeclareType(AttributedRun)[m [m struct Impl_AttributedRun {[m [31m- iRangei logical; /* UTF-32 codepoint indices in the logical-order text */[m [31m- iFont * font;[m [31m- iColor fgColor;[m [32m+[m[32m iRangei logical; /* UTF-32 codepoint indices in the logical-order text */[m [32m+[m[32m iTextAttrib attrib;[m [32m+[m[32m iFont *font;[m [32m+[m[32m iColor fgColor_; /* any RGB color; A > 0 */[m struct {[m uint8_t isLineBreak : 1;[m [31m- uint8_t isRTL : 1;[m [31m- uint8_t isArabic : 1; /* Arabic script detected */[m [32m+[m[32m// uint8_t isRTL : 1;[m [32m+[m[32m uint8_t isArabic : 1; /* Arabic script detected */[m } flags;[m };[m [m [32m+[m[32mstatic iColor fgColor_AttributedRun_(const iAttributedRun *d) {[m [32m+[m[32m if (d->fgColor_.a) {[m [32m+[m[32m return d->fgColor_;[m [32m+[m[32m }[m [32m+[m[32m if (d->attrib.colorId == none_ColorId) {[m [32m+[m[32m return (iColor){ 255, 255, 255, 255 };[m [32m+[m[32m }[m [32m+[m[32m return get_Color(d->attrib.colorId);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void setFgColor_AttributedRun_(iAttributedRun *d, int colorId) {[m [32m+[m[32m d->attrib.colorId = colorId;[m [32m+[m[32m d->fgColor_.a = 0;[m [32m+[m[32m}[m [32m+[m iDeclareType(AttributedText)[m iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font,[m [31m- iColor fgColor, int baseDir, iChar overrideChar)[m [32m+[m[32m int colorId, int baseDir, iFont *baseFont, int baseColorId,[m [32m+[m[32m iChar overrideChar)[m [m struct Impl_AttributedText {[m iRangecc source; /* original source text */[m size_t maxLen;[m iFont * font;[m [31m- iColor fgColor;[m [32m+[m[32m int colorId;[m [32m+[m[32m iFont * baseFont;[m [32m+[m[32m int baseColorId;[m [32m+[m[32m iBool isBaseRTL;[m iArray runs;[m iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */[m iArray visual; /* UTF-32 text in visual order (LTR) */[m [36m@@ -881,13 +900,14 @@[m [mstruct Impl_AttributedText {[m iArray visualToLogical;[m iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */[m char * bidiLevels;[m [31m- iBool isBaseRTL;[m };[m [m iDefineTypeConstructionArgs(AttributedText,[m [31m- (iRangecc text, size_t maxLen, iFont *font, iColor fgColor,[m [31m- int baseDir, iChar overrideChar),[m [31m- text, maxLen, font, fgColor, baseDir, overrideChar)[m [32m+[m[32m (iRangecc text, size_t maxLen, iFont *font, int colorId,[m [32m+[m[32m int baseDir, iFont *baseFont, int baseColorId,[m [32m+[m[32m iChar overrideChar),[m [32m+[m[32m text, maxLen, font, colorId, baseDir, baseFont, baseColorId,[m [32m+[m[32m overrideChar)[m [m static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) {[m const int *logToSource = constData_Array(&d->logicalToSourceOffset);[m [36m@@ -916,16 +936,22 @@[m [mstatic void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i[m run->logical.start = endAt;[m }[m [m [31m-static iFont *withStyle_Font_(const iFont *d, enum iFontStyle styleId) {[m [31m- const int fontId = (fontId_Text_(d) / maxVariants_Fonts) * maxVariants_Fonts;[m [31m- const int sizeId = sizeId_Text_(d);[m [31m- return font_Text_(FONT_ID(fontId, styleId, sizeId));[m [32m+[m[32mint fontWithSize_Text(int font, enum iFontSize sizeId) {[m [32m+[m[32m const int familyId = (font / maxVariants_Fonts) * maxVariants_Fonts;[m [32m+[m[32m const int styleId = (font / max_FontSize) % max_FontStyle;[m [32m+[m[32m return FONT_ID(familyId, styleId, sizeId);[m [32m+[m[32m}[m [32m+[m [32m+[m[32mint fontWithStyle_Text(int font, enum iFontStyle styleId) {[m [32m+[m[32m const int familyId = (font / maxVariants_Fonts) * maxVariants_Fonts;[m [32m+[m[32m const int sizeId = font % max_FontSize;[m [32m+[m[32m return FONT_ID(familyId, styleId, sizeId);[m }[m [m [31m-static iFont *withFontId_Font_(const iFont *d, enum iFontId fontId) {[m [31m- const int styleId = styleId_Text_(d);[m [31m- const int sizeId = sizeId_Text_(d);[m [31m- return font_Text_(FONT_ID(fontId, styleId, sizeId));[m [32m+[m[32mint fontWithFamily_Text(int font, enum iFontId familyId) {[m [32m+[m[32m const int styleId = (font / max_FontSize) % max_FontStyle;[m [32m+[m[32m const int sizeId = font % max_FontSize;[m [32m+[m[32m return FONT_ID(familyId, styleId, sizeId);[m }[m [m static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {[m [36m@@ -982,15 +1008,17 @@[m [mstatic void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh[m pushBack_Array(&d->logicalToVisual, &(int){ length });[m pushBack_Array(&d->visualToLogical, &(int){ length });[m }[m [31m- iAttributedRun run = { .logical = { 0, length },[m [31m- .font = d->font,[m [31m- .fgColor = d->fgColor };[m [31m- const int * logToSource = constData_Array(&d->logicalToSourceOffset);[m [32m+[m[32m iAttributedRun run = {[m [32m+[m[32m .logical = { 0, length },[m [32m+[m[32m .attrib = { .colorId = d->colorId, .isBaseRTL = d->isBaseRTL },[m [32m+[m[32m .font = d->font,[m [32m+[m[32m };[m [32m+[m[32m const int *logToSource = constData_Array(&d->logicalToSourceOffset);[m const int * logToVis = constData_Array(&d->logicalToVisual);[m const iChar * logicalText = constData_Array(&d->logical);[m iBool isRTL = d->isBaseRTL;[m int numNonSpace = 0;[m [31m- iFont * activeFont = d->font;[m [32m+[m[32m iFont * attribFont = d->font;[m for (int pos = 0; pos < length; pos++) {[m const iChar ch = logicalText[pos];[m #if defined (LAGRANGE_ENABLE_FRIBIDI)[m [36m@@ -1010,7 +1038,7 @@[m [mstatic void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh[m #else[m const iBool isNeutral = iTrue;[m #endif[m [31m- run.flags.isRTL = isRTL;[m [32m+[m[32m run.attrib.isRTL = isRTL;[m if (ch == 0x1b) { /* ANSI escape. */[m pos++;[m const char *srcPos = d->source.start + logToSource[pos];[m [36m@@ -1020,21 +1048,36 @@[m [mstatic void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh[m if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) {[m finishRun_AttributedText_(d, &run, pos - 1);[m const iRangecc sequence = capturedRange_RegExpMatch(&m, 1);[m [32m+[m[32m /* TODO: Bold/italic attributes are assumed to be inside body text.[m [32m+[m[32m We don't know what the current text style is supposed to be.[m [32m+[m[32m That should be an additional attribute passed to WrapText, or a feature of[m [32m+[m[32m WrapText that can be called both from here and in the run typesetter.[m [32m+[m[32m The styling here is hardcoded to match `typesetOneLine_RunTypesetter_()`. */[m if (equal_Rangecc(sequence, "1")) {[m [31m- activeFont = withStyle_Font_(activeFont, bold_FontStyle);[m [32m+[m[32m run.attrib.bold = iTrue;[m [32m+[m[32m if (d->baseColorId == tmParagraph_ColorId) {[m [32m+[m[32m setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId);[m [32m+[m[32m }[m [32m+[m[32m attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), bold_FontStyle));[m }[m else if (equal_Rangecc(sequence, "3")) {[m [31m- activeFont = withStyle_Font_(activeFont, italic_FontStyle);[m [32m+[m[32m run.attrib.italic = iTrue;[m [32m+[m[32m attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), italic_FontStyle));[m }[m else if (equal_Rangecc(sequence, "4")) {[m [31m- activeFont = withFontId_Font_(activeFont, monospace_FontId);[m [32m+[m[32m run.attrib.monospace = iTrue;[m [32m+[m[32m setFgColor_AttributedRun_(&run, tmPreformatted_ColorId);[m [32m+[m[32m attribFont = font_Text_(fontWithFamily_Text(fontId_Text_(d->baseFont), monospace_FontId));[m }[m else if (equal_Rangecc(sequence, "0")) {[m [31m- activeFont = d->font; /* restore original */[m [31m- run.fgColor = d->fgColor;[m [32m+[m[32m run.attrib.bold = iFalse;[m [32m+[m[32m run.attrib.italic = iFalse;[m [32m+[m[32m run.attrib.monospace = iFalse;[m [32m+[m[32m attribFont = d->baseFont;[m [32m+[m[32m setFgColor_AttributedRun_(&run, d->baseColorId);[m }[m else {[m [31m- run.fgColor = ansiForeground_Color(sequence, tmParagraph_ColorId);[m [32m+[m[32m run.fgColor_ = ansiForeground_Color(sequence, tmParagraph_ColorId);[m }[m pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0));[m iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start);[m [36m@@ -1056,7 +1099,7 @@[m [mstatic void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh[m colorNum = esc - asciiBase_ColorEscape;[m }[m run.logical.start = pos + 1;[m [31m- run.fgColor = (colorNum >= 0 ? get_Color(colorNum) : d->fgColor);[m [32m+[m[32m setFgColor_AttributedRun_(&run, colorNum >= 0 ? colorNum : d->colorId);[m continue;[m }[m if (ch == '\n') {[m [36m@@ -1077,7 +1120,7 @@[m [mstatic void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh[m }[m continue;[m }[m [31m- iFont *currentFont = activeFont;[m [32m+[m[32m iFont *currentFont = attribFont;[m if (run.font->fontSpec->flags & arabic_FontSpecFlag && isPunct_Char(ch)) {[m currentFont = run.font; /* remain as Arabic for whitespace */[m }[m [36m@@ -1113,12 +1156,15 @@[m [mstatic void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh[m #endif[m }[m [m [31m-void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor,[m [31m- int baseDir, iChar overrideChar) {[m [31m- d->source = text;[m [31m- d->maxLen = maxLen ? maxLen : iInvalidSize;[m [31m- d->font = font;[m [31m- d->fgColor = fgColor;[m [32m+[m[32mvoid init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, int colorId,[m [32m+[m[32m int baseDir, iFont *baseFont, int baseColorId, iChar overrideChar) {[m [32m+[m[32m d->source = text;[m [32m+[m[32m d->maxLen = maxLen ? maxLen : iInvalidSize;[m [32m+[m[32m d->font = font;[m [32m+[m[32m d->colorId = colorId;[m [32m+[m[32m d->baseFont = baseFont;[m [32m+[m[32m d->baseColorId = baseColorId;[m [32m+[m[32m d->isBaseRTL = iFalse;[m init_Array(&d->runs, sizeof(iAttributedRun));[m init_Array(&d->logical, sizeof(iChar));[m init_Array(&d->visual, sizeof(iChar));[m [36m@@ -1126,7 +1172,6 @@[m [mvoid init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont[m init_Array(&d->visualToLogical, sizeof(int));[m init_Array(&d->logicalToSourceOffset, sizeof(int));[m d->bidiLevels = NULL;[m [31m- d->isBaseRTL = iFalse;[m prepare_AttributedText_(d, baseDir, overrideChar);[m }[m [m [36m@@ -1278,7 +1323,7 @@[m [mstatic void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {[m iArray glyphIndices;[m init_Array(&glyphIndices, sizeof(uint32_t));[m iAttributedText attrText;[m [31m- init_AttributedText(&attrText, text, 0, d, (iColor){}, 0, 0);[m [32m+[m[32m init_AttributedText(&attrText, text, 0, d, none_ColorId, 0, d, none_ColorId, 0);[m /* We use AttributedText here so the font lookup matches the behavior during text drawing --[m glyphs may be selected from a font that's different than `d`. */[m const iChar *logicalText = constData_Array(&attrText.logical);[m [36m@@ -1333,17 +1378,17 @@[m [mstruct Impl_RunArgs {[m /* TODO: Cleanup using TextMetrics[m Use TextMetrics output pointer instead of return value & cursorAdvance_out. */[m iInt2 * cursorAdvance_out;[m [31m-// const char ** continueFrom_out;[m int * runAdvance_out;[m };[m [m [31m-static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int advance, iBool isBaseRTL) {[m [32m+[m[32mstatic iBool notify_WrapText_(iWrapText *d, const char *ending, iTextAttrib attrib,[m [32m+[m[32m int origin, int advance) {[m if (d && d->wrapFunc && d->wrapRange_.start) {[m /* `wrapRange_` uses logical indices. */[m const char *end = ending ? ending : d->wrapRange_.end;[m iRangecc range = { d->wrapRange_.start, end };[m iAssert(range.start <= range.end);[m [31m- const iBool result = d->wrapFunc(d, range, origin, advance, isBaseRTL);[m [32m+[m[32m const iBool result = d->wrapFunc(d, range, attrib, origin, advance);[m if (result) {[m d->wrapRange_.start = end;[m }[m [36m@@ -1470,8 +1515,11 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m font is used and other attributes such as color. (HarfBuzz shaping is done[m with one specific font.) */[m iAttributedText attrText;[m [31m- init_AttributedText(&attrText, args->text, args->maxLen, d, get_Color(args->color),[m [31m- args->baseDir, wrap ? wrap->overrideChar : 0);[m [32m+[m[32m init_AttributedText(&attrText, args->text, args->maxLen, d, args->color,[m [32m+[m[32m args->baseDir,[m [32m+[m[32m activeText_->baseFontId >= 0 ? font_Text_(activeText_->baseFontId) : d,[m [32m+[m[32m activeText_->baseColorId,[m [32m+[m[32m wrap ? wrap->overrideChar : 0);[m if (wrap) {[m wrap->baseDir = attrText.isBaseRTL ? -1 : +1;[m /* TODO: Duplicated args? */[m [36m@@ -1522,6 +1570,9 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m iRangei wrapPosRange = { 0, textLen };[m int wrapResumePos = textLen; /* logical position where next line resumes */[m size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */[m [32m+[m[32m iTextAttrib attrib = { .colorId = args->color, .isBaseRTL = attrText.isBaseRTL };[m [32m+[m[32m iTextAttrib wrapAttrib = attrib;[m [32m+[m[32m iTextAttrib lastAttrib = attrib;[m const int layoutBound = (wrap ? wrap->maxWidth : 0);[m iBool isFirst = iTrue;[m const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2());[m [36m@@ -1545,6 +1596,7 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m /* Determine ends of wrapRuns and wrapVisRange. */[m for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {[m const iAttributedRun *run = at_Array(&attrText.runs, runIndex);[m [32m+[m[32m /* Update the attributes. */[m if (run->flags.isLineBreak) {[m if (checkHitChar &&[m wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) {[m [36m@@ -1554,7 +1606,6 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m wrapResumePos = run->logical.end;[m wrapRuns.end = runIndex;[m wrapResumeRunIndex = runIndex + 1;[m [31m- //yCursor += d->height;[m break;[m }[m wrapResumeRunIndex = runCount;[m [36m@@ -1564,8 +1615,9 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m shape_GlyphBuffer_(buf);[m int safeBreakPos = -1;[m iChar prevCh = 0;[m [32m+[m[32m lastAttrib = run->attrib;[m[41m [m for (unsigned int ir = 0; ir < buf->glyphCount; ir++) {[m [31m- const int i = (run->flags.isRTL ? buf->glyphCount - ir - 1 : ir);[m [32m+[m[32m const int i = (run->attrib.isRTL ? buf->glyphCount - ir - 1 : ir);[m const hb_glyph_info_t *info = &buf->glyphInfo[i];[m const hb_codepoint_t glyphId = info->codepoint;[m const int logPos = info->cluster;[m [36m@@ -1603,10 +1655,9 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m prevCh = ch;[m }[m else {[m [31m- //if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) {[m [31m- safeBreakPos = logPos;[m [31m- breakAdvance = wrapAdvance;[m [31m- //}[m [32m+[m[32m safeBreakPos = logPos;[m [32m+[m[32m breakAdvance = wrapAdvance;[m [32m+[m[32m wrapAttrib = run->attrib;[m }[m if (isHitPointOnThisLine) {[m if (wrap->hitPoint.x >= orig.x + wrapAdvance &&[m [36m@@ -1638,7 +1689,7 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m wrapPosRange.end = logPos;[m breakAdvance = wrapAdvance;[m }[m [31m- wrapResumePos = wrapPosRange.end;[m [32m+[m[32m wrapResumePos = wrapPosRange.end;[m if (args->wrap->mode != anyCharacter_WrapTextMode) {[m while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) {[m wrapResumePos++; /* skip space */[m [36m@@ -1688,7 +1739,7 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {[m const iAttributedRun *run = at_Array(&attrText.runs, runIndex);[m if (!attrText.isBaseRTL) { /* left-to-right */[m [31m- if (run->flags.isRTL) {[m [32m+[m[32m if (run->attrib.isRTL) {[m if (oppositeInsertIndex == iInvalidPos) {[m oppositeInsertIndex = size_Array(&runOrder);[m }[m [36m@@ -1700,7 +1751,7 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m }[m }[m else { /* right-to-left */[m [31m- if (!run->flags.isRTL) {[m [32m+[m[32m if (!run->attrib.isRTL) {[m if (oppositeInsertIndex == iInvalidPos) {[m oppositeInsertIndex = 0;[m }[m [36m@@ -1739,11 +1790,12 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m if (wrap && wrap->wrapFunc &&[m !notify_WrapText_(args->wrap,[m sourcePtr_AttributedText_(&attrText, wrapResumePos),[m [32m+[m[32m wrapAttrib,[m origin,[m [31m- iRound(wrapAdvance),[m [31m- attrText.isBaseRTL)) {[m [32m+[m[32m iRound(wrapAdvance))) {[m willAbortDueToWrap = iTrue;[m }[m [32m+[m[32m wrapAttrib = lastAttrib;[m xCursor = origin;[m /* We have determined a possible wrap position and alignment for the work runs,[m so now we can process the glyphs. */[m [36m@@ -1796,8 +1848,8 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y,[m glyph->rect[hoff].size.x,[m glyph->rect[hoff].size.y };[m [31m- if (run->font->height < d->height) {[m [31m- dst.y += d->baseline - run->font->baseline;[m [32m+[m[32m if (run->font->height < attrText.baseFont->height) {[m [32m+[m[32m dst.y += attrText.baseFont->baseline - run->font->baseline;[m }[m if (mode & visualFlag_RunMode) {[m if (isEmpty_Rect(bounds)) {[m [36m@@ -1819,7 +1871,7 @@[m [mstatic iRect run_Font_(iFont *d, const iRunArgs *args) {[m iAssert(isRasterized_Glyph_(glyph, hoff));[m }[m if (~mode & permanentColorFlag_RunMode) {[m [31m- const iColor clr = run->fgColor;[m [32m+[m[32m const iColor clr = fgColor_AttributedRun_(run);[m SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);[m if (args->mode & fillBackground_RunMode) {[m SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);[m [36m@@ -1939,9 +1991,9 @@[m [mstatic int runFlagsFromId_(enum iFontId fontId) {[m return runFlags;[m }[m [m [31m-static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int origin, int advance,[m [31m- iBool isBaseRTL) {[m [31m- iUnused(origin, advance, isBaseRTL);[m [32m+[m[32mstatic iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, iTextAttrib attrib, int origin,[m [32m+[m[32m int advance) {[m [32m+[m[32m iUnused(attrib, origin, advance);[m *((const char **) d->context) = range.end;[m return iFalse; /* just one line */[m }[m [1mdiff --git a/src/ui/text.h b/src/ui/text.h[m [1mindex ac59e7c8..51f7754d 100644[m [1m--- a/src/ui/text.h[m [1m+++ b/src/ui/text.h[m [36m@@ -120,6 +120,9 @@[m [mvoid resetFonts_Text (iText *);[m int lineHeight_Text (int fontId);[m float emRatio_Text (int fontId); /* em advance to line height ratio */[m iRect visualBounds_Text (int fontId, iRangecc text);[m [32m+[m[32mint fontWithSize_Text (int fontId, enum iFontSize sizeId);[m [32m+[m[32mint fontWithStyle_Text (int fontId, enum iFontStyle styleId);[m [32m+[m[32mint fontWithFamily_Text (int fontId, enum iFontId familyId);[m [m iDeclareType(TextMetrics)[m [m [36m@@ -149,9 +152,10 @@[m [menum iAlignment {[m right_Alignment,[m };[m [m [31m-void setOpacity_Text (float opacity);[m [32m+[m[32mvoid setOpacity_Text (float opacity);[m [32m+[m[32mvoid setBaseAttributes_Text (int fontId, int colorId); /* current "normal" text attributes */[m [m [31m-void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */[m [32m+[m[32mvoid cache_Text (int fontId, iRangecc text); /* pre-render glyphs */[m [m void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...);[m void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...);[m [36m@@ -173,12 +177,28 @@[m [menum iWrapTextMode {[m word_WrapTextMode,[m };[m [m [32m+[m[32miDeclareType(TextAttrib)[m [32m+[m[41m [m [32m+[m[32m/* Initial attributes at the start of a text string. These may be modified by control[m [32m+[m[32m sequences inside a text run. */[m [32m+[m[32mstruct Impl_TextAttrib {[m [32m+[m[32m int16_t colorId;[m [32m+[m[32m struct {[m [32m+[m[32m uint16_t bold : 1;[m [32m+[m[32m uint16_t italic : 1;[m [32m+[m[32m uint16_t monospace : 1;[m [32m+[m[32m uint16_t isBaseRTL : 1;[m [32m+[m[32m uint16_t isRTL : 1;[m [32m+[m[32m };[m[41m [m [32m+[m[32m};[m [32m+[m struct Impl_WrapText {[m /* arguments */[m iRangecc text;[m int maxWidth;[m enum iWrapTextMode mode;[m [31m- iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int origin, int advance, iBool isBaseRTL);[m [32m+[m[32m iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, iTextAttrib attrib, int origin,[m [32m+[m[32m int advance);[m void * context;[m iChar overrideChar; /* use this for all characters instead of the real ones */[m int baseDir; /* set to +1 for LTR, -1 for RTL */[m
text/gemini; charset=utf-8
This content has been proxied by September (3851b).