diff options
-rw-r--r-- | res/about/version.gmi | 4 | ||||
-rw-r--r-- | src/gmdocument.c | 72 | ||||
-rw-r--r-- | src/gmutil.c | 38 | ||||
-rw-r--r-- | src/macos.m | 2 | ||||
-rw-r--r-- | src/ui/text.c | 12 | ||||
-rw-r--r-- | src/ui/text.h | 2 |
6 files changed, 59 insertions, 71 deletions
diff --git a/res/about/version.gmi b/res/about/version.gmi index 438ff950..8de16a87 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi | |||
@@ -8,7 +8,9 @@ | |||
8 | 8 | ||
9 | ## 1.10.4 | 9 | ## 1.10.4 |
10 | * Added missing ANSI background color codes 100-107 (high-intensity VGA). | 10 | * Added missing ANSI background color codes 100-107 (high-intensity VGA). |
11 | * Fixed how the ANSI FG color is adjusted to keep text legible on bright backgrounds when BG color is unset. | 11 | * Fixed how the ANSI FG color is adjusted to keep text legible on dark or bright backgrounds when BG color is unset. |
12 | * Fixed possible crash when there are ANSI escapes in the alt text of a preformatted block. | ||
13 | * Fixed tab/window titles containing ANSI escapes (escapes are removed). | ||
12 | 14 | ||
13 | ## 1.10.3 | 15 | ## 1.10.3 |
14 | * Unix: Added a lagrange(1) manual page. | 16 | * Unix: Added a lagrange(1) manual page. |
diff --git a/src/gmdocument.c b/src/gmdocument.c index 5bb1dd21..b5e71e21 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -613,6 +613,10 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, | |||
613 | } | 613 | } |
614 | 614 | ||
615 | static void doLayout_GmDocument_(iGmDocument *d) { | 615 | static void doLayout_GmDocument_(iGmDocument *d) { |
616 | static iRegExp *ansiPattern_; | ||
617 | if (!ansiPattern_) { | ||
618 | ansiPattern_ = makeAnsiEscapePattern_Text(iTrue /* with ESC */); | ||
619 | } | ||
616 | const iPrefs *prefs = prefs_App(); | 620 | const iPrefs *prefs = prefs_App(); |
617 | const iBool isMono = isForcedMonospace_GmDocument_(d); | 621 | const iBool isMono = isForcedMonospace_GmDocument_(d); |
618 | const iBool isGopher = isGopher_GmDocument_(d); | 622 | const iBool isGopher = isGopher_GmDocument_(d); |
@@ -620,8 +624,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
620 | const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; | 624 | const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; |
621 | const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; | 625 | const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; |
622 | const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); | 626 | const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); |
623 | // const iBool isDarkBg = isDark_GmDocumentTheme( | 627 | |
624 | // isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); | ||
625 | initTheme_GmDocument_(d); | 628 | initTheme_GmDocument_(d); |
626 | d->isLayoutInvalidated = iFalse; | 629 | d->isLayoutInvalidated = iFalse; |
627 | /* TODO: Collect these parameters into a GmTheme. */ | 630 | /* TODO: Collect these parameters into a GmTheme. */ |
@@ -659,7 +662,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
659 | const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ | 662 | const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ |
660 | clear_Array(&d->preMeta); | 663 | clear_Array(&d->preMeta); |
661 | clear_String(&d->title); | 664 | clear_String(&d->title); |
662 | // clear_String(&d->bannerText); | ||
663 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { | 665 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { |
664 | return; | 666 | return; |
665 | } | 667 | } |
@@ -673,7 +675,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
673 | int preFont = preformatted_FontId; | 675 | int preFont = preformatted_FontId; |
674 | uint16_t preId = 0; | 676 | uint16_t preId = 0; |
675 | iBool enableIndents = iFalse; | 677 | iBool enableIndents = iFalse; |
676 | // iBool addSiteBanner = d->bannerType != none_GmDocumentBanner; | ||
677 | const iBool isNormalized = isNormalized_GmDocument_(d); | 678 | const iBool isNormalized = isNormalized_GmDocument_(d); |
678 | enum iGmLineType prevType = text_GmLineType; | 679 | enum iGmLineType prevType = text_GmLineType; |
679 | enum iGmLineType prevNonBlankType = text_GmLineType; | 680 | enum iGmLineType prevNonBlankType = text_GmLineType; |
@@ -757,7 +758,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
757 | if (d->format == gemini_SourceFormat && | 758 | if (d->format == gemini_SourceFormat && |
758 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { | 759 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { |
759 | isPreformat = iFalse; | 760 | isPreformat = iFalse; |
760 | // addSiteBanner = iFalse; /* overrides the banner */ | ||
761 | continue; | 761 | continue; |
762 | } | 762 | } |
763 | run.mediaType = max_MediaType; /* preformatted block */ | 763 | run.mediaType = max_MediaType; /* preformatted block */ |
@@ -765,28 +765,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
765 | run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); | 765 | run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); |
766 | indent = indents[type]; | 766 | indent = indents[type]; |
767 | } | 767 | } |
768 | #if 0 | ||
769 | if (addSiteBanner) { | ||
770 | addSiteBanner = iFalse; | ||
771 | const iRangecc bannerText = urlHost_String(&d->url); | ||
772 | if (!isEmpty_Range(&bannerText)) { | ||
773 | setRange_String(&d->bannerText, bannerText); | ||
774 | iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag }; | ||
775 | banner.bounds = zero_Rect(); | ||
776 | banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2); | ||
777 | if (d->bannerType == certificateWarning_GmDocumentBanner) { | ||
778 | banner.visBounds.size.y += iMaxi(6000 * lineHeight_Text(uiLabel_FontId) / | ||
779 | d->size.x, lineHeight_Text(uiLabel_FontId) * 5); | ||
780 | } | ||
781 | banner.text = bannerText; | ||
782 | banner.font = banner_FontId; | ||
783 | banner.color = tmBannerTitle_ColorId; | ||
784 | pushBack_Array(&d->layout, &banner); | ||
785 | pos.y += height_Rect(banner.visBounds) + | ||
786 | 1.5f * lineHeight_Text(paragraph_FontId) * prefs->lineSpacing; | ||
787 | } | ||
788 | } | ||
789 | #endif | ||
790 | /* Empty lines don't produce text runs. */ | 768 | /* Empty lines don't produce text runs. */ |
791 | if (isEmpty_Range(&line)) { | 769 | if (isEmpty_Range(&line)) { |
792 | if (type == quote_GmLineType && !prefs->quoteIcon) { | 770 | if (type == quote_GmLineType && !prefs->quoteIcon) { |
@@ -867,6 +845,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
867 | if ((type == heading1_GmLineType || type == heading2_GmLineType) && | 845 | if ((type == heading1_GmLineType || type == heading2_GmLineType) && |
868 | isEmpty_String(&d->title)) { | 846 | isEmpty_String(&d->title)) { |
869 | setRange_String(&d->title, line); | 847 | setRange_String(&d->title, line); |
848 | /* Get rid of ANSI escapes. */ | ||
849 | replaceRegExp_String(&d->title, ansiPattern_, "", NULL, NULL); | ||
870 | } | 850 | } |
871 | /* List bullet. */ | 851 | /* List bullet. */ |
872 | if (type == bullet_GmLineType) { | 852 | if (type == bullet_GmLineType) { |
@@ -1959,44 +1939,6 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) { | |||
1959 | } | 1939 | } |
1960 | } | 1940 | } |
1961 | 1941 | ||
1962 | int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, | ||
1963 | void (*matchHandler)(void *, const iRegExpMatch *), | ||
1964 | void *context) { | ||
1965 | iRegExpMatch m; | ||
1966 | iString result; | ||
1967 | int numMatches = 0; | ||
1968 | const char *pos = constBegin_String(d); | ||
1969 | init_RegExpMatch(&m); | ||
1970 | init_String(&result); | ||
1971 | while (matchString_RegExp(regexp, d, &m)) { | ||
1972 | appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) }); | ||
1973 | /* Replace any capture group back-references. */ | ||
1974 | for (const char *ch = replacement; *ch; ch++) { | ||
1975 | if (*ch == '\\') { | ||
1976 | ch++; | ||
1977 | if (*ch == '\\') { | ||
1978 | appendCStr_String(&result, "\\"); | ||
1979 | } | ||
1980 | else if (*ch >= '0' && *ch <= '9') { | ||
1981 | appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0')); | ||
1982 | } | ||
1983 | } | ||
1984 | else { | ||
1985 | appendData_Block(&result.chars, ch, 1); | ||
1986 | } | ||
1987 | } | ||
1988 | if (matchHandler) { | ||
1989 | matchHandler(context, &m); | ||
1990 | } | ||
1991 | pos = end_RegExpMatch(&m); | ||
1992 | numMatches++; | ||
1993 | } | ||
1994 | appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) }); | ||
1995 | set_String(d, &result); | ||
1996 | deinit_String(&result); | ||
1997 | return numMatches; | ||
1998 | } | ||
1999 | |||
2000 | iDeclareType(PendingLink) | 1942 | iDeclareType(PendingLink) |
2001 | struct Impl_PendingLink { | 1943 | struct Impl_PendingLink { |
2002 | iString *url; | 1944 | iString *url; |
diff --git a/src/gmutil.c b/src/gmutil.c index ce1b68c7..e59e6649 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -904,3 +904,41 @@ const iGmError *get_GmError(enum iGmStatusCode code) { | |||
904 | iAssert(errors_[0].code == unknownStatusCode_GmStatusCode); | 904 | iAssert(errors_[0].code == unknownStatusCode_GmStatusCode); |
905 | return &errors_[0].err; /* unknown */ | 905 | return &errors_[0].err; /* unknown */ |
906 | } | 906 | } |
907 | |||
908 | int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, | ||
909 | void (*matchHandler)(void *, const iRegExpMatch *), | ||
910 | void *context) { | ||
911 | iRegExpMatch m; | ||
912 | iString result; | ||
913 | int numMatches = 0; | ||
914 | const char *pos = constBegin_String(d); | ||
915 | init_RegExpMatch(&m); | ||
916 | init_String(&result); | ||
917 | while (matchString_RegExp(regexp, d, &m)) { | ||
918 | appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) }); | ||
919 | /* Replace any capture group back-references. */ | ||
920 | for (const char *ch = replacement; *ch; ch++) { | ||
921 | if (*ch == '\\') { | ||
922 | ch++; | ||
923 | if (*ch == '\\') { | ||
924 | appendCStr_String(&result, "\\"); | ||
925 | } | ||
926 | else if (*ch >= '0' && *ch <= '9') { | ||
927 | appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0')); | ||
928 | } | ||
929 | } | ||
930 | else { | ||
931 | appendData_Block(&result.chars, ch, 1); | ||
932 | } | ||
933 | } | ||
934 | if (matchHandler) { | ||
935 | matchHandler(context, &m); | ||
936 | } | ||
937 | pos = end_RegExpMatch(&m); | ||
938 | numMatches++; | ||
939 | } | ||
940 | appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) }); | ||
941 | set_String(d, &result); | ||
942 | deinit_String(&result); | ||
943 | return numMatches; | ||
944 | } | ||
diff --git a/src/macos.m b/src/macos.m index 4ad267c1..1019d13d 100644 --- a/src/macos.m +++ b/src/macos.m | |||
@@ -734,7 +734,7 @@ enum iColorId removeColorEscapes_String(iString *d) { | |||
734 | static NSString *cleanString_(const iString *ansiEscapedText) { | 734 | static NSString *cleanString_(const iString *ansiEscapedText) { |
735 | iString mod; | 735 | iString mod; |
736 | initCopy_String(&mod, ansiEscapedText); | 736 | initCopy_String(&mod, ansiEscapedText); |
737 | iRegExp *ansi = makeAnsiEscapePattern_Text(); | 737 | iRegExp *ansi = makeAnsiEscapePattern_Text(iTrue /* with ESC */); |
738 | replaceRegExp_String(&mod, ansi, "", NULL, NULL); | 738 | replaceRegExp_String(&mod, ansi, "", NULL, NULL); |
739 | iRelease(ansi); | 739 | iRelease(ansi); |
740 | NSString *clean = [NSString stringWithUTF8String:cstr_String(&mod)]; | 740 | NSString *clean = [NSString stringWithUTF8String:cstr_String(&mod)]; |
diff --git a/src/ui/text.c b/src/ui/text.c index b610d3b8..200108ed 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -390,8 +390,12 @@ static void deinitCache_Text_(iText *d) { | |||
390 | SDL_DestroyTexture(d->cache); | 390 | SDL_DestroyTexture(d->cache); |
391 | } | 391 | } |
392 | 392 | ||
393 | iRegExp *makeAnsiEscapePattern_Text(void) { | 393 | iRegExp *makeAnsiEscapePattern_Text(iBool includeEscChar) { |
394 | return new_RegExp("[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])", 0); | 394 | const char *pattern = "\x1b[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])"; |
395 | if (!includeEscChar) { | ||
396 | pattern++; | ||
397 | } | ||
398 | return new_RegExp(pattern, 0); | ||
395 | } | 399 | } |
396 | 400 | ||
397 | void init_Text(iText *d, SDL_Renderer *render) { | 401 | void init_Text(iText *d, SDL_Renderer *render) { |
@@ -399,7 +403,7 @@ void init_Text(iText *d, SDL_Renderer *render) { | |||
399 | activeText_ = d; | 403 | activeText_ = d; |
400 | init_Array(&d->fonts, sizeof(iFont)); | 404 | init_Array(&d->fonts, sizeof(iFont)); |
401 | d->contentFontSize = contentScale_Text_; | 405 | d->contentFontSize = contentScale_Text_; |
402 | d->ansiEscape = makeAnsiEscapePattern_Text(); | 406 | d->ansiEscape = makeAnsiEscapePattern_Text(iFalse /* no ESC */); |
403 | d->baseFontId = -1; | 407 | d->baseFontId = -1; |
404 | d->baseFgColorId = -1; | 408 | d->baseFgColorId = -1; |
405 | d->missingGlyphs = iFalse; | 409 | d->missingGlyphs = iFalse; |
@@ -1988,6 +1992,7 @@ static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, iTextAttrib attrib, | |||
1988 | } | 1992 | } |
1989 | 1993 | ||
1990 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1994 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { |
1995 | *endPos = text.end; | ||
1991 | iWrapText wrap = { .mode = word_WrapTextMode, | 1996 | iWrapText wrap = { .mode = word_WrapTextMode, |
1992 | .text = text, | 1997 | .text = text, |
1993 | .maxWidth = width, | 1998 | .maxWidth = width, |
@@ -2002,6 +2007,7 @@ iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **e | |||
2002 | *endPos = text.start; | 2007 | *endPos = text.start; |
2003 | return zero_I2(); | 2008 | return zero_I2(); |
2004 | } | 2009 | } |
2010 | *endPos = text.end; | ||
2005 | /* "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ | 2011 | /* "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ |
2006 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, | 2012 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, |
2007 | .text = text, | 2013 | .text = text, |
diff --git a/src/ui/text.h b/src/ui/text.h index c8bb6f85..b952df84 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -235,7 +235,7 @@ enum iTextBlockMode { quadrants_TextBlockMode, shading_TextBlockMode }; | |||
235 | iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, | 235 | iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, |
236 | const iString *text); | 236 | const iString *text); |
237 | 237 | ||
238 | iRegExp * makeAnsiEscapePattern_Text (void); | 238 | iRegExp * makeAnsiEscapePattern_Text (iBool includeEscChar); |
239 | 239 | ||
240 | /*-----------------------------------------------------------------------------------------------*/ | 240 | /*-----------------------------------------------------------------------------------------------*/ |
241 | 241 | ||