summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-10-25 22:10:04 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-10-25 22:10:04 +0300
commitb4e13bb58f644276469a69c4bd93e22381aa0002 (patch)
tree476693d897b2ac0f26b263bb871f65a2140c6637 /src
parentb80c958840a3ac0f824de3ae12717963ba679b20 (diff)
Document presentation warnings
Warn the user about missing glyphs and potentially unsupported ANSI escapes. TODO: Site-specific setting for dismissed warning; fonts preference about missing glyph warnings.
Diffstat (limited to 'src')
-rw-r--r--src/gmdocument.c47
-rw-r--r--src/gmdocument.h6
-rw-r--r--src/gmutil.c8
-rw-r--r--src/gmutil.h2
-rw-r--r--src/ui/banner.c77
-rw-r--r--src/ui/documentwidget.c27
-rw-r--r--src/ui/labelwidget.c8
-rw-r--r--src/ui/text.c9
-rw-r--r--src/ui/text.h1
9 files changed, 156 insertions, 29 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index bd3fa788..c7d5501f 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -107,6 +107,7 @@ struct Impl_GmDocument {
107 iChar siteIcon; 107 iChar siteIcon;
108 iMedia * media; 108 iMedia * media;
109 iStringSet *openURLs; /* currently open URLs for highlighting links */ 109 iStringSet *openURLs; /* currently open URLs for highlighting links */
110 int warnings;
110 iBool isPaletteValid; 111 iBool isPaletteValid;
111 iColor palette[tmMax_ColorId]; /* copy of the color palette */ 112 iColor palette[tmMax_ColorId]; /* copy of the color palette */
112}; 113};
@@ -596,6 +597,8 @@ static void doLayout_GmDocument_(iGmDocument *d) {
596 isPreformat = iTrue; 597 isPreformat = iTrue;
597 isFirstText = iFalse; 598 isFirstText = iFalse;
598 } 599 }
600 d->warnings &= ~missingGlyphs_GmDocumentWarning;
601 checkMissing_Text(); /* clear the flag */
599 setAnsiFlags_Text(d->theme.ansiEscapes); 602 setAnsiFlags_Text(d->theme.ansiEscapes);
600 while (nextSplit_Rangecc(content, "\n", &contentLine)) { 603 while (nextSplit_Rangecc(content, "\n", &contentLine)) {
601 iRangecc line = contentLine; /* `line` will be trimmed; modifying would confuse `nextSplit_Rangecc` */ 604 iRangecc line = contentLine; /* `line` will be trimmed; modifying would confuse `nextSplit_Rangecc` */
@@ -1045,6 +1048,9 @@ static void doLayout_GmDocument_(iGmDocument *d) {
1045 } 1048 }
1046#endif 1049#endif
1047 d->size.y = pos.y; 1050 d->size.y = pos.y;
1051 if (checkMissing_Text()) {
1052 d->warnings |= missingGlyphs_GmDocumentWarning;
1053 }
1048 /* Go over the preformatted blocks and mark them wide if at least one run is wide. */ { 1054 /* Go over the preformatted blocks and mark them wide if at least one run is wide. */ {
1049 /* TODO: Store the dimensions and ranges for later access. */ 1055 /* TODO: Store the dimensions and ranges for later access. */
1050 iForEach(Array, i, &d->layout) { 1056 iForEach(Array, i, &d->layout) {
@@ -1084,6 +1090,7 @@ void init_GmDocument(iGmDocument *d) {
1084 d->siteIcon = 0; 1090 d->siteIcon = 0;
1085 d->media = new_Media(); 1091 d->media = new_Media();
1086 d->openURLs = NULL; 1092 d->openURLs = NULL;
1093 d->warnings = 0;
1087 d->isPaletteValid = iFalse; 1094 d->isPaletteValid = iFalse;
1088 iZap(d->palette); 1095 iZap(d->palette);
1089} 1096}
@@ -1123,16 +1130,28 @@ static void setDerivedThemeColors_(enum iGmDocumentTheme theme) {
1123 mix_Color(get_Color(tmBannerTitle_ColorId), 1130 mix_Color(get_Color(tmBannerTitle_ColorId),
1124 get_Color(tmBackground_ColorId), 1131 get_Color(tmBackground_ColorId),
1125 theme == colorfulDark_GmDocumentTheme ? 0.55f : 0)); 1132 theme == colorfulDark_GmDocumentTheme ? 0.55f : 0));
1126 const int bannerItemFg = isDark_GmDocumentTheme(currentTheme_()) ? white_ColorId : black_ColorId; 1133 /* Banner colors. */
1127 set_Color(tmBannerItemBackground_ColorId, mix_Color(get_Color(tmBannerBackground_ColorId), 1134 if (theme == highContrast_GmDocumentTheme) {
1128 get_Color(tmBannerTitle_ColorId), 0.1f)); 1135 set_Color(tmBannerItemBackground_ColorId, get_Color(tmBannerBackground_ColorId));
1129 set_Color(tmBannerItemFrame_ColorId, mix_Color(get_Color(tmBannerBackground_ColorId), 1136 set_Color(tmBannerItemFrame_ColorId, get_Color(tmBannerIcon_ColorId));
1130 get_Color(tmBannerTitle_ColorId), 0.4f)); 1137 set_Color(tmBannerItemTitle_ColorId, get_Color(tmBannerTitle_ColorId));
1131 set_Color(tmBannerItemText_ColorId, mix_Color(get_Color(tmBannerBackground_ColorId), 1138 set_Color(tmBannerItemText_ColorId, get_Color(tmBannerTitle_ColorId));
1132 get_Color(bannerItemFg), 0.75f)); 1139 }
1133 set_Color(tmBannerItemTitle_ColorId, get_Color(bannerItemFg)); 1140 else {
1141 const int bannerItemFg = isDark_GmDocumentTheme(currentTheme_()) ? white_ColorId : black_ColorId;
1142 set_Color(tmBannerItemBackground_ColorId, mix_Color(get_Color(tmBannerBackground_ColorId),
1143 get_Color(tmBannerTitle_ColorId), 0.1f));
1144 set_Color(tmBannerItemFrame_ColorId, mix_Color(get_Color(tmBannerBackground_ColorId),
1145 get_Color(tmBannerTitle_ColorId), 0.4f));
1146 set_Color(tmBannerItemText_ColorId, mix_Color(get_Color(tmBannerTitle_ColorId),
1147 get_Color(bannerItemFg), 0.5f));
1148 set_Color(tmBannerItemTitle_ColorId, get_Color(bannerItemFg));
1149 }
1150 /* Modified backgrounds. */
1134 set_Color(tmBackgroundAltText_ColorId, 1151 set_Color(tmBackgroundAltText_ColorId,
1135 mix_Color(get_Color(tmQuoteIcon_ColorId), get_Color(tmBackground_ColorId), 0.85f)); 1152 mix_Color(get_Color(tmQuoteIcon_ColorId), get_Color(tmBackground_ColorId), 0.85f));
1153 set_Color(tmFrameAltText_ColorId,
1154 mix_Color(get_Color(tmQuoteIcon_ColorId), get_Color(tmBackground_ColorId), 0.4f));
1136 set_Color(tmBackgroundOpenLink_ColorId, 1155 set_Color(tmBackgroundOpenLink_ColorId,
1137 mix_Color(get_Color(tmLinkText_ColorId), get_Color(tmBackground_ColorId), 0.90f)); 1156 mix_Color(get_Color(tmLinkText_ColorId), get_Color(tmBackground_ColorId), 0.90f));
1138 set_Color(tmFrameOpenLink_ColorId, 1157 set_Color(tmFrameOpenLink_ColorId,
@@ -2008,6 +2027,14 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int
2008 /* Normalize and convert to Gemtext if needed. */ 2027 /* Normalize and convert to Gemtext if needed. */
2009 set_String(&d->unormSource, source); 2028 set_String(&d->unormSource, source);
2010 set_String(&d->source, source); 2029 set_String(&d->source, source);
2030 /* Detect use of ANSI escapes. */ {
2031 iRegExp *ansiEsc = new_RegExp("\x1b[[()]([0-9;AB]*?)m", 0);
2032 iRegExpMatch m;
2033 init_RegExpMatch(&m);
2034 const iBool found = matchString_RegExp(ansiEsc, &d->unormSource, &m);
2035 iChangeFlags(d->warnings, ansiEscapes_GmDocumentWarning, found);
2036 iRelease(ansiEsc);
2037 }
2011 if (d->format == gemini_SourceFormat) { 2038 if (d->format == gemini_SourceFormat) {
2012 d->theme.ansiEscapes = prefs_App()->gemtextAnsiEscapes; 2039 d->theme.ansiEscapes = prefs_App()->gemtextAnsiEscapes;
2013 } 2040 }
@@ -2157,6 +2184,10 @@ size_t memorySize_GmDocument(const iGmDocument *d) {
2157 memorySize_Media(d->media); 2184 memorySize_Media(d->media);
2158} 2185}
2159 2186
2187int warnings_GmDocument(const iGmDocument *d) {
2188 return d->warnings;
2189}
2190
2160iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { 2191iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) {
2161 const char * src = constBegin_String(&d->source); 2192 const char * src = constBegin_String(&d->source);
2162 const size_t startPos = (start ? start - src : 0); 2193 const size_t startPos = (start ? start - src : 0);
diff --git a/src/gmdocument.h b/src/gmdocument.h
index e010ad43..0c24302f 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -172,6 +172,11 @@ iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos);
172iDeclareClass(GmDocument) 172iDeclareClass(GmDocument)
173iDeclareObjectConstruction(GmDocument) 173iDeclareObjectConstruction(GmDocument)
174 174
175enum iGmDocumentWarning {
176 ansiEscapes_GmDocumentWarning = iBit(1),
177 missingGlyphs_GmDocumentWarning = iBit(2),
178};
179
175/* 180/*
176enum iGmDocumentBanner { 181enum iGmDocumentBanner {
177 none_GmDocumentBanner, 182 none_GmDocumentBanner,
@@ -221,6 +226,7 @@ iInt2 size_GmDocument (const iGmDocument *);
221const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ 226const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */
222const iString * source_GmDocument (const iGmDocument *); 227const iString * source_GmDocument (const iGmDocument *);
223size_t memorySize_GmDocument (const iGmDocument *); /* bytes */ 228size_t memorySize_GmDocument (const iGmDocument *); /* bytes */
229int warnings_GmDocument (const iGmDocument *);
224 230
225iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); 231iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start);
226iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); 232iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before);
diff --git a/src/gmutil.c b/src/gmutil.c
index 264e7ac8..22219221 100644
--- a/src/gmutil.c
+++ b/src/gmutil.c
@@ -755,6 +755,14 @@ static const struct {
755 { 0x1f645, /* no good */ 755 { 0x1f645, /* no good */
756 "${error.certverify}", 756 "${error.certverify}",
757 "${error.certverify.msg}" } }, 757 "${error.certverify.msg}" } },
758 { ansiEscapes_GmStatusCode,
759 { 0x1f5b3, /* old computer */
760 "${error.ansi}",
761 "${error.ansi.msg}" } },
762 { missingGlyphs_GmStatusCode,
763 { 0x1f520, /* ABCD */
764 "${error.glyphs}",
765 "${error.glyphs.msg}" } },
758 { temporaryFailure_GmStatusCode, 766 { temporaryFailure_GmStatusCode,
759 { 0x1f50c, /* electric plug */ 767 { 0x1f50c, /* electric plug */
760 "${error.temporary}", 768 "${error.temporary}",
diff --git a/src/gmutil.h b/src/gmutil.h
index c65a17e6..6b10b444 100644
--- a/src/gmutil.h
+++ b/src/gmutil.h
@@ -45,6 +45,8 @@ enum iGmStatusCode {
45 tlsFailure_GmStatusCode, 45 tlsFailure_GmStatusCode,
46 tlsServerCertificateExpired_GmStatusCode, 46 tlsServerCertificateExpired_GmStatusCode,
47 tlsServerCertificateNotVerified_GmStatusCode, 47 tlsServerCertificateNotVerified_GmStatusCode,
48 ansiEscapes_GmStatusCode,
49 missingGlyphs_GmStatusCode,
48 50
49 none_GmStatusCode = 0, 51 none_GmStatusCode = 0,
50 /* general status code categories */ 52 /* general status code categories */
diff --git a/src/ui/banner.c b/src/ui/banner.c
index d95c853b..e817f1bb 100644
--- a/src/ui/banner.c
+++ b/src/ui/banner.c
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
27#include "lang.h" 27#include "lang.h"
28#include "paint.h" 28#include "paint.h"
29#include "util.h" 29#include "util.h"
30#include "app.h"
30 31
31iDeclareType(BannerItem) 32iDeclareType(BannerItem)
32 33
@@ -53,27 +54,33 @@ struct Impl_Banner {
53 iRect rect; 54 iRect rect;
54 iString site; 55 iString site;
55 iString icon; 56 iString icon;
57 int siteHeight;
56 iArray items; 58 iArray items;
57 iBool isClick; 59 iBool isClick;
58}; 60};
59 61
60iDefineTypeConstruction(Banner) 62iDefineTypeConstruction(Banner)
61 63
64#define itemGap_Banner_ (3 * gap_UI)
65#define itemVPad_Banner_ (2 * gap_UI)
66#define itemHPad_Banner_ (3 * gap_UI)
67#define bottomPad_Banner_ (4 * gap_UI)
68
62static void updateHeight_Banner_(iBanner *d) { 69static void updateHeight_Banner_(iBanner *d) {
63 d->rect.size.y = 0; 70 d->rect.size.y = 0;
64 if (!isEmpty_String(&d->site)) { 71 if (!isEmpty_String(&d->site)) {
65 d->rect.size.y += lineHeight_Text(banner_FontId) * 2; 72 d->siteHeight = lineHeight_Text(banner_FontId) * 2;
73 d->rect.size.y += d->siteHeight;
66 } 74 }
67 const size_t numItems = size_Array(&d->items); 75 const size_t numItems = size_Array(&d->items);
68 if (numItems) { 76 if (numItems) {
69 const int outerPad = 2 * gap_UI;
70 const int innerPad = gap_UI; 77 const int innerPad = gap_UI;
71 iConstForEach(Array, i, &d->items) { 78 iConstForEach(Array, i, &d->items) {
72 const iBannerItem *item = i.value; 79 const iBannerItem *item = i.value;
73 d->rect.size.y += item->height; 80 d->rect.size.y += item->height;
74 } 81 }
75 d->rect.size.y += (numItems - 1) * innerPad; 82 d->rect.size.y += (numItems - 1) * itemGap_Banner_;
76 d->rect.size.y += outerPad; 83 d->rect.size.y += bottomPad_Banner_;
77 } 84 }
78} 85}
79 86
@@ -99,9 +106,9 @@ void setOwner_Banner(iBanner *d, iDocumentWidget *owner) {
99 106
100static void updateItemHeight_Banner_(const iBanner *d, iBannerItem *item) { 107static void updateItemHeight_Banner_(const iBanner *d, iBannerItem *item) {
101 item->height = measureWrapRange_Text(uiContent_FontId, 108 item->height = measureWrapRange_Text(uiContent_FontId,
102 width_Rect(d->rect) - 6 * gap_UI, 109 width_Rect(d->rect) - 2 * itemHPad_Banner_,
103 range_String(&item->text)) 110 range_String(&item->text))
104 .bounds.size.y + 4 * gap_UI; 111 .bounds.size.y + 2 * itemVPad_Banner_;
105} 112}
106 113
107void setWidth_Banner(iBanner *d, int width) { 114void setWidth_Banner(iBanner *d, int width) {
@@ -186,6 +193,7 @@ void draw_Banner(const iBanner *d) {
186 return; 193 return;
187 } 194 }
188 iRect bounds = d->rect; 195 iRect bounds = d->rect;
196 /* TODO: use d->siteHeight */
189 iInt2 pos = addY_I2(topLeft_Rect(bounds), lineHeight_Text(banner_FontId) / 2); 197 iInt2 pos = addY_I2(topLeft_Rect(bounds), lineHeight_Text(banner_FontId) / 2);
190 iPaint p; 198 iPaint p;
191 init_Paint(&p); 199 init_Paint(&p);
@@ -218,16 +226,28 @@ void draw_Banner(const iBanner *d) {
218 setBaseAttributes_Text(uiContent_FontId, tmBannerItemText_ColorId); 226 setBaseAttributes_Text(uiContent_FontId, tmBannerItemText_ColorId);
219 iWrapText wt = { 227 iWrapText wt = {
220 .text = range_String(&item->text), 228 .text = range_String(&item->text),
221 .maxWidth = width_Rect(itemRect) - 6 * gap_UI, 229 .maxWidth = width_Rect(itemRect) - 2 * itemHPad_Banner_,
222 .mode = word_WrapTextMode 230 .mode = word_WrapTextMode
223 }; 231 };
224 draw_WrapText(&wt, uiContent_FontId, add_I2(pos, init_I2(3 * gap_UI, 2 * gap_UI)), 232 draw_WrapText(&wt, uiContent_FontId, add_I2(pos, init_I2(itemHPad_Banner_, itemVPad_Banner_)),
225 tmBannerItemText_ColorId); 233 tmBannerItemText_ColorId);
226 pos.y += innerPad; 234 pos.y += item->height + itemGap_Banner_;
227 } 235 }
228 setBaseAttributes_Text(-1, -1); 236 setBaseAttributes_Text(-1, -1);
229} 237}
230 238
239static size_t itemAtCoord_Banner_(const iBanner *d, iInt2 coord) {
240 iInt2 pos = addY_I2(topLeft_Rect(d->rect), lineHeight_Text(banner_FontId) * 2);
241 iConstForEach(Array, i, &d->items) {
242 const iBannerItem *item = i.value;
243 if (contains_Rect((iRect){ pos, init_I2(d->rect.size.x, item->height)}, coord)) {
244 return index_ArrayConstIterator(&i);
245 }
246 pos.y += itemGap_Banner_ + item->height;
247 }
248 return iInvalidPos;
249}
250
231iBool processEvent_Banner(iBanner *d, const SDL_Event *ev) { 251iBool processEvent_Banner(iBanner *d, const SDL_Event *ev) {
232 iWidget *w = as_Widget(d->doc); 252 iWidget *w = as_Widget(d->doc);
233 switch (ev->type) { 253 switch (ev->type) {
@@ -240,14 +260,49 @@ iBool processEvent_Banner(iBanner *d, const SDL_Event *ev) {
240 case SDL_MOUSEBUTTONUP: 260 case SDL_MOUSEBUTTONUP:
241 /* Clicking on the top/side banner navigates to site root. */ 261 /* Clicking on the top/side banner navigates to site root. */
242 if (ev->button.button == SDL_BUTTON_LEFT) { 262 if (ev->button.button == SDL_BUTTON_LEFT) {
243 const iBool isInside = contains_Rect(d->rect, init_I2(ev->button.x, ev->button.y)); 263 const iInt2 coord = init_I2(ev->button.x, ev->button.y);
264 const iBool isInside = contains_Rect(d->rect, coord);
244 if (isInside && ev->button.state == SDL_PRESSED) { 265 if (isInside && ev->button.state == SDL_PRESSED) {
245 d->isClick = iTrue; 266 d->isClick = iTrue;
246 return iTrue; 267 return iTrue;
247 } 268 }
248 else if (ev->button.state == SDL_RELEASED) { 269 else if (ev->button.state == SDL_RELEASED) {
249 if (d->isClick && isInside) { 270 if (d->isClick && isInside) {
250 postCommand_Widget(d->doc, "navigate.root"); 271 const size_t index = itemAtCoord_Banner_(d, coord);
272 if (index == iInvalidPos) {
273 if (coord.y < top_Rect(d->rect) + d->siteHeight) {
274 postCommand_Widget(d->doc, "navigate.root");
275 }
276 }
277 else {
278 const iBannerItem *item = constAt_Array(&d->items, index);
279 if (item->type == error_BannerType) {
280 postCommand_Widget(d->doc, "document.info");
281 }
282 else {
283 switch (item->code) {
284 case missingGlyphs_GmStatusCode:
285 postCommandf_App("open newtab:1 url:about:fonts");
286 break;
287 case ansiEscapes_GmStatusCode:
288 makeQuestion_Widget(uiHeading_ColorEscape "${heading.dismiss.warning}",
289 format_Lang("${dlg.dismiss.ansi}",
290 format_CStr(uiTextStrong_ColorEscape "%s"
291 restore_ColorEscape, cstr_Rangecc(urlHost_String(url_DocumentWidget(d->doc))))),
292 (iMenuItem[]){ { "${cancel}" },
293 { uiTextAction_ColorEscape "${dlg.dismiss.warning}",
294 SDLK_RETURN, 0,
295 format_CStr("document.dismiss warning:%d",
296 ansiEscapes_GmDocumentWarning)
297 }
298 }, 2);
299 break;
300 default:
301 postCommand_Widget(d->doc, "document.info");
302 break;
303 }
304 }
305 }
251 } 306 }
252 d->isClick = iFalse; 307 d->isClick = iFalse;
253 } 308 }
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 13a8dae7..755cec6a 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -934,7 +934,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
934 updateSideOpacity_DocumentWidget_(d, iTrue); 934 updateSideOpacity_DocumentWidget_(d, iTrue);
935 animateMedia_DocumentWidget_(d); 935 animateMedia_DocumentWidget_(d);
936 setPos_Banner(d->banner, addY_I2(topLeft_Rect(documentBounds_DocumentWidget_(d)), 936 setPos_Banner(d->banner, addY_I2(topLeft_Rect(documentBounds_DocumentWidget_(d)),
937 -pos_SmoothScroll(&d->scrollY))); 937 -pos_SmoothScroll(&d->scrollY)));
938 /*init_I2(documentBounds_DocumentWidget_(d).pos.x, 938 /*init_I2(documentBounds_DocumentWidget_(d).pos.x,
939 viewPos_DocumentWidget_(d) - 939 viewPos_DocumentWidget_(d) -
940 documentTopPad_DocumentWidget_(d)));*/ 940 documentTopPad_DocumentWidget_(d)));*/
@@ -1291,7 +1291,6 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1291 iRelease(errorDoc); 1291 iRelease(errorDoc);
1292 clear_Banner(d->banner); 1292 clear_Banner(d->banner);
1293 add_Banner(d->banner, error_BannerType, code, meta); 1293 add_Banner(d->banner, error_BannerType, code, meta);
1294// translate_Lang(src);
1295 d->state = ready_RequestState; 1294 d->state = ready_RequestState;
1296 setSource_DocumentWidget(d, src); 1295 setSource_DocumentWidget(d, src);
1297 updateTheme_DocumentWidget_(d); 1296 updateTheme_DocumentWidget_(d);
@@ -1798,6 +1797,16 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) {
1798 } 1797 }
1799} 1798}
1800 1799
1800static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) {
1801 if (warnings_GmDocument(d->doc) & missingGlyphs_GmDocumentWarning) {
1802 add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL);
1803 /* TODO: List one or more of the missing characters and/or their Unicode blocks? */
1804 }
1805 if (warnings_GmDocument(d->doc) & ansiEscapes_GmDocumentWarning) {
1806 add_Banner(d->banner, warning_BannerType, ansiEscapes_GmStatusCode, NULL);
1807 }
1808}
1809
1801static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, 1810static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY,
1802 const iGmResponse *resp, iGmDocument *cachedDoc) { 1811 const iGmResponse *resp, iGmDocument *cachedDoc) {
1803 setLinkNumberMode_DocumentWidget_(d, iFalse); 1812 setLinkNumberMode_DocumentWidget_(d, iFalse);
@@ -1824,6 +1833,7 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1824// (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0); 1833// (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0);
1825 clear_Banner(d->banner); 1834 clear_Banner(d->banner);
1826 updateBanner_DocumentWidget_(d); 1835 updateBanner_DocumentWidget_(d);
1836 addBannerWarnings_DocumentWidget_(d);
1827 } 1837 }
1828 d->state = ready_RequestState; 1838 d->state = ready_RequestState;
1829 postProcessRequestContent_DocumentWidget_(d, iTrue); 1839 postProcessRequestContent_DocumentWidget_(d, iTrue);
@@ -2864,10 +2874,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2864 size_Array(items)); 2874 size_Array(items));
2865 delete_Array(items); 2875 delete_Array(items);
2866 /* Enforce a minimum size. */ 2876 /* Enforce a minimum size. */
2867 iWidget *sizer = new_Widget(); 2877// iWidget *sizer = new_Widget();
2868 setFixedSize_Widget(sizer, init_I2(gap_UI * 65, 1)); 2878// setFixedSize_Widget(sizer, init_I2(gap_UI * 65, 1));
2869 addChildFlags_Widget(dlg, iClob(sizer), frameless_WidgetFlag); 2879// addChildFlags_Widget(dlg, iClob(sizer), frameless_WidgetFlag);
2870 setFlags_Widget(dlg, centerHorizontal_WidgetFlag, iFalse); 2880// setFlags_Widget(dlg, centerHorizontal_WidgetFlag, iFalse);
2871 if (deviceType_App() != phone_AppDeviceType) { 2881 if (deviceType_App() != phone_AppDeviceType) {
2872 const iWidget *lockButton = findWidget_Root("navbar.lock"); 2882 const iWidget *lockButton = findWidget_Root("navbar.lock");
2873 setPos_Widget(dlg, windowToLocal_Widget(dlg, bottomLeft_Rect(bounds_Widget(lockButton)))); 2883 setPos_Widget(dlg, windowToLocal_Widget(dlg, bottomLeft_Rect(bounds_Widget(lockButton))));
@@ -2989,6 +2999,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2989 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { 2999 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) {
2990 init_Anim(&d->scrollY.pos, d->initNormScrollY * pageHeight_DocumentWidget_(d)); /* TODO: unless user already scrolled! */ 3000 init_Anim(&d->scrollY.pos, d->initNormScrollY * pageHeight_DocumentWidget_(d)); /* TODO: unless user already scrolled! */
2991 } 3001 }
3002 addBannerWarnings_DocumentWidget_(d);
2992 iChangeFlags(d->flags, 3003 iChangeFlags(d->flags,
2993 urlChanged_DocumentWidgetFlag | drawDownloadCounter_DocumentWidgetFlag, 3004 urlChanged_DocumentWidgetFlag | drawDownloadCounter_DocumentWidgetFlag,
2994 iFalse); 3005 iFalse);
@@ -4377,7 +4388,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4377 if (run->flags & altText_GmRunFlag) { 4388 if (run->flags & altText_GmRunFlag) {
4378 const iInt2 margin = preRunMargin_GmDocument(doc, preId_GmRun(run)); 4389 const iInt2 margin = preRunMargin_GmDocument(doc, preId_GmRun(run));
4379 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId); 4390 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId);
4380 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId); 4391 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmFrameAltText_ColorId);
4381 drawWrapRange_Text(run->font, 4392 drawWrapRange_Text(run->font,
4382 add_I2(visPos, margin), 4393 add_I2(visPos, margin),
4383 run->visBounds.size.x - 2 * margin.x, 4394 run->visBounds.size.x - 2 * margin.x,
@@ -5053,7 +5064,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
5053 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); 5064 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
5054 } 5065 }
5055 fillRect_Paint(&ctx.paint, altRect, tmBackgroundAltText_ColorId); 5066 fillRect_Paint(&ctx.paint, altRect, tmBackgroundAltText_ColorId);
5056 drawRect_Paint(&ctx.paint, altRect, tmQuoteIcon_ColorId); 5067 drawRect_Paint(&ctx.paint, altRect, tmFrameAltText_ColorId);
5057 setOpacity_Text(altTextOpacity); 5068 setOpacity_Text(altTextOpacity);
5058 drawWrapRange_Text(altFont, addX_I2(pos, margin), wrap, 5069 drawWrapRange_Text(altFont, addX_I2(pos, margin), wrap,
5059 tmQuote_ColorId, meta->altText); 5070 tmQuote_ColorId, meta->altText);
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index fbcd24b9..daca05d1 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -380,8 +380,12 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
380 } 380 }
381 if (d->flags.wrap) { 381 if (d->flags.wrap) {
382 const iRect cont = contentBounds_LabelWidget_(d); 382 const iRect cont = contentBounds_LabelWidget_(d);
383 drawWrapRange_Text( 383 iWrapText wt = {
384 d->font, topLeft_Rect(cont), width_Rect(cont), fg, range_String(&d->label)); 384 .text = range_String(&d->label),
385 .maxWidth = width_Rect(cont),
386 .mode = word_WrapTextMode,
387 };
388 draw_WrapText(&wt, d->font, topLeft_Rect(cont), fg);
385 } 389 }
386 else if (flags & alignLeft_WidgetFlag) { 390 else if (flags & alignLeft_WidgetFlag) {
387 draw_Text(d->font, add_I2(bounds.pos, addX_I2(padding_LabelWidget_(d, 0), iconPad)), 391 draw_Text(d->font, add_I2(bounds.pos, addX_I2(padding_LabelWidget_(d, 0), iconPad)),
diff --git a/src/ui/text.c b/src/ui/text.c
index 106c55e9..2ef54d39 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -264,6 +264,7 @@ struct Impl_Text {
264 int ansiFlags; 264 int ansiFlags;
265 int baseFontId; /* base attributes (for restoring via escapes) */ 265 int baseFontId; /* base attributes (for restoring via escapes) */
266 int baseColorId; 266 int baseColorId;
267 iBool missingGlyphs; /* true if a glyph couldn't be found */
267}; 268};
268 269
269iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) 270iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
@@ -797,6 +798,7 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
797 } 798 }
798#endif // 0 799#endif // 0
799 if (!*glyphIndex) { 800 if (!*glyphIndex) {
801 activeText_->missingGlyphs = iTrue;
800 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); 802 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr);
801 } 803 }
802 return d; 804 return d;
@@ -2260,6 +2262,13 @@ iTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {
2260 return tm; 2262 return tm;
2261} 2263}
2262 2264
2265iBool checkMissing_Text(void) {
2266 iText *d = activeText_;
2267 const iBool missing = d->missingGlyphs;
2268 d->missingGlyphs = iFalse;
2269 return missing;
2270}
2271
2263SDL_Texture *glyphCache_Text(void) { 2272SDL_Texture *glyphCache_Text(void) {
2264 return activeText_->cache; 2273 return activeText_->cache;
2265} 2274}
diff --git a/src/ui/text.h b/src/ui/text.h
index 99ddb704..13a636d4 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -220,6 +220,7 @@ struct Impl_WrapText {
220iTextMetrics measure_WrapText (iWrapText *, int fontId); 220iTextMetrics measure_WrapText (iWrapText *, int fontId);
221iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); 221iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color);
222 222
223iBool checkMissing_Text (void); /* returns the flag, and clears it */
223SDL_Texture * glyphCache_Text (void); 224SDL_Texture * glyphCache_Text (void);
224 225
225enum iTextBlockMode { quadrants_TextBlockMode, shading_TextBlockMode }; 226enum iTextBlockMode { quadrants_TextBlockMode, shading_TextBlockMode };