summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--res/about/version.gmi4
-rw-r--r--src/gmdocument.c72
-rw-r--r--src/gmutil.c38
-rw-r--r--src/macos.m2
-rw-r--r--src/ui/text.c12
-rw-r--r--src/ui/text.h2
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
615static void doLayout_GmDocument_(iGmDocument *d) { 615static 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
1962int 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
2000iDeclareType(PendingLink) 1942iDeclareType(PendingLink)
2001struct Impl_PendingLink { 1943struct 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
908int 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) {
734static NSString *cleanString_(const iString *ansiEscapedText) { 734static 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
393iRegExp *makeAnsiEscapePattern_Text(void) { 393iRegExp *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
397void init_Text(iText *d, SDL_Renderer *render) { 401void 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
1990iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { 1994iInt2 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 };
235iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, 235iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode,
236 const iString *text); 236 const iString *text);
237 237
238iRegExp * makeAnsiEscapePattern_Text (void); 238iRegExp * makeAnsiEscapePattern_Text (iBool includeEscChar);
239 239
240/*-----------------------------------------------------------------------------------------------*/ 240/*-----------------------------------------------------------------------------------------------*/
241 241