diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-12-02 15:07:17 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-12-02 15:07:17 +0200 |
commit | 849fd39d08bd4164ed486c2f4addc2cab7a85f9c (patch) | |
tree | 2e4c82c362c042abfe610b3d1c2bca1a2e21134e | |
parent | 9c384de6e9aa02a628b54b9930c22730f0a22dad (diff) |
Show a banner warning about certificate issues
-rw-r--r-- | src/gmcerts.c | 15 | ||||
-rw-r--r-- | src/gmcerts.h | 2 | ||||
-rw-r--r-- | src/gmdocument.c | 19 | ||||
-rw-r--r-- | src/gmdocument.h | 9 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 130 | ||||
-rw-r--r-- | src/ui/text.c | 4 |
6 files changed, 137 insertions, 42 deletions
diff --git a/src/gmcerts.c b/src/gmcerts.c index f7475348..a1df1d20 100644 --- a/src/gmcerts.c +++ b/src/gmcerts.c | |||
@@ -432,6 +432,21 @@ void setTrusted_GmCerts(iGmCerts *d, iRangecc domain, const iBlock *fingerprint, | |||
432 | unlock_Mutex(d->mtx); | 432 | unlock_Mutex(d->mtx); |
433 | } | 433 | } |
434 | 434 | ||
435 | iTime domainValidUntil_GmCerts(const iGmCerts *d, iRangecc domain) { | ||
436 | iTime expiry; | ||
437 | iZap(expiry); | ||
438 | lock_Mutex(d->mtx); | ||
439 | iString key; | ||
440 | initRange_String(&key, domain); | ||
441 | const iTrustEntry *trust = constValue_StringHash(d->trusted, &key); | ||
442 | if (trust) { | ||
443 | expiry = trust->validUntil; | ||
444 | } | ||
445 | deinit_String(&key); | ||
446 | unlock_Mutex(d->mtx); | ||
447 | return expiry; | ||
448 | } | ||
449 | |||
435 | iGmIdentity *identity_GmCerts(iGmCerts *d, unsigned int id) { | 450 | iGmIdentity *identity_GmCerts(iGmCerts *d, unsigned int id) { |
436 | return at_PtrArray(&d->idents, id); | 451 | return at_PtrArray(&d->idents, id); |
437 | } | 452 | } |
diff --git a/src/gmcerts.h b/src/gmcerts.h index 2fd1023a..a28c44b4 100644 --- a/src/gmcerts.h +++ b/src/gmcerts.h | |||
@@ -62,6 +62,8 @@ typedef iBool (*iGmCertsIdentityFilterFunc)(void *context, const iGmIdentity *); | |||
62 | iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert); | 62 | iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert); |
63 | void setTrusted_GmCerts (iGmCerts *, iRangecc domain, const iBlock *fingerprint, | 63 | void setTrusted_GmCerts (iGmCerts *, iRangecc domain, const iBlock *fingerprint, |
64 | const iDate *validUntil); | 64 | const iDate *validUntil); |
65 | iTime domainValidUntil_GmCerts(const iGmCerts *, iRangecc domain); | ||
66 | |||
65 | /** | 67 | /** |
66 | * Create a new self-signed TLS client certificate for identifying the user. | 68 | * Create a new self-signed TLS client certificate for identifying the user. |
67 | * @a commonName and the other name parameters are inserted in the subject field | 69 | * @a commonName and the other name parameters are inserted in the subject field |
diff --git a/src/gmdocument.c b/src/gmdocument.c index 9e78c2e0..d01ba0b9 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -64,10 +64,10 @@ struct Impl_GmDocument { | |||
64 | iString source; | 64 | iString source; |
65 | iString url; /* for resolving relative links */ | 65 | iString url; /* for resolving relative links */ |
66 | iString localHost; | 66 | iString localHost; |
67 | iBool siteBannerEnabled; | ||
68 | iInt2 size; | 67 | iInt2 size; |
69 | iArray layout; /* contents of source, laid out in document space */ | 68 | iArray layout; /* contents of source, laid out in document space */ |
70 | iPtrArray links; | 69 | iPtrArray links; |
70 | enum iGmDocumentBanner bannerType; | ||
71 | iString bannerText; | 71 | iString bannerText; |
72 | iString title; /* the first top-level title */ | 72 | iString title; /* the first top-level title */ |
73 | iArray headings; | 73 | iArray headings; |
@@ -310,7 +310,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
310 | int preFont = preformatted_FontId; | 310 | int preFont = preformatted_FontId; |
311 | uint16_t preId = 0; | 311 | uint16_t preId = 0; |
312 | iBool enableIndents = iFalse; | 312 | iBool enableIndents = iFalse; |
313 | iBool addSiteBanner = d->siteBannerEnabled; | 313 | iBool addSiteBanner = d->bannerType != none_GmDocumentBanner; |
314 | enum iGmLineType prevType = text_GmLineType; | 314 | enum iGmLineType prevType = text_GmLineType; |
315 | if (d->format == plainText_GmDocumentFormat) { | 315 | if (d->format == plainText_GmDocumentFormat) { |
316 | isPreformat = iTrue; | 316 | isPreformat = iTrue; |
@@ -383,6 +383,10 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
383 | iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag }; | 383 | iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag }; |
384 | banner.bounds = zero_Rect(); | 384 | banner.bounds = zero_Rect(); |
385 | banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2); | 385 | banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2); |
386 | if (d->bannerType == certificateWarning_GmDocumentBanner) { | ||
387 | banner.visBounds.size.y += iMaxi(6000 * lineHeight_Text(uiLabel_FontId) / | ||
388 | d->size.x, lineHeight_Text(uiLabel_FontId) * 5); | ||
389 | } | ||
386 | banner.font = banner_FontId; | 390 | banner.font = banner_FontId; |
387 | banner.text = bannerText; | 391 | banner.text = bannerText; |
388 | banner.color = tmBannerTitle_ColorId; | 392 | banner.color = tmBannerTitle_ColorId; |
@@ -620,7 +624,7 @@ void init_GmDocument(iGmDocument *d) { | |||
620 | init_String(&d->source); | 624 | init_String(&d->source); |
621 | init_String(&d->url); | 625 | init_String(&d->url); |
622 | init_String(&d->localHost); | 626 | init_String(&d->localHost); |
623 | d->siteBannerEnabled = iTrue; | 627 | d->bannerType = siteDomain_GmDocumentBanner; |
624 | d->size = zero_I2(); | 628 | d->size = zero_I2(); |
625 | init_Array(&d->layout, sizeof(iGmRun)); | 629 | init_Array(&d->layout, sizeof(iGmRun)); |
626 | init_PtrArray(&d->links); | 630 | init_PtrArray(&d->links); |
@@ -661,7 +665,6 @@ void reset_GmDocument(iGmDocument *d) { | |||
661 | clear_String(&d->url); | 665 | clear_String(&d->url); |
662 | clear_String(&d->localHost); | 666 | clear_String(&d->localHost); |
663 | d->themeSeed = 0; | 667 | d->themeSeed = 0; |
664 | d->siteBannerEnabled = iTrue; | ||
665 | } | 668 | } |
666 | 669 | ||
667 | static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | 670 | static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { |
@@ -1085,8 +1088,8 @@ void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { | |||
1085 | d->format = format; | 1088 | d->format = format; |
1086 | } | 1089 | } |
1087 | 1090 | ||
1088 | void setSiteBannerEnabled_GmDocument(iGmDocument *d, iBool siteBannerEnabled) { | 1091 | void setBanner_GmDocument(iGmDocument *d, enum iGmDocumentBanner type) { |
1089 | d->siteBannerEnabled = siteBannerEnabled; | 1092 | d->bannerType = type; |
1090 | } | 1093 | } |
1091 | 1094 | ||
1092 | void setWidth_GmDocument(iGmDocument *d, int width) { | 1095 | void setWidth_GmDocument(iGmDocument *d, int width) { |
@@ -1203,6 +1206,10 @@ iInt2 size_GmDocument(const iGmDocument *d) { | |||
1203 | return d->size; | 1206 | return d->size; |
1204 | } | 1207 | } |
1205 | 1208 | ||
1209 | enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *d) { | ||
1210 | return d->bannerType; | ||
1211 | } | ||
1212 | |||
1206 | iBool hasSiteBanner_GmDocument(const iGmDocument *d) { | 1213 | iBool hasSiteBanner_GmDocument(const iGmDocument *d) { |
1207 | return siteBanner_GmDocument(d) != NULL; | 1214 | return siteBanner_GmDocument(d) != NULL; |
1208 | } | 1215 | } |
diff --git a/src/gmdocument.h b/src/gmdocument.h index c6f64e1d..b1121d85 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -115,9 +115,15 @@ enum iGmDocumentFormat { | |||
115 | plainText_GmDocumentFormat, | 115 | plainText_GmDocumentFormat, |
116 | }; | 116 | }; |
117 | 117 | ||
118 | enum iGmDocumentBanner { | ||
119 | none_GmDocumentBanner, | ||
120 | siteDomain_GmDocumentBanner, | ||
121 | certificateWarning_GmDocumentBanner, | ||
122 | }; | ||
123 | |||
118 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); | 124 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); |
119 | void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); | 125 | void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); |
120 | void setSiteBannerEnabled_GmDocument(iGmDocument *, iBool siteBannerEnabled); | 126 | void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); |
121 | void setWidth_GmDocument (iGmDocument *, int width); | 127 | void setWidth_GmDocument (iGmDocument *, int width); |
122 | void redoLayout_GmDocument (iGmDocument *); | 128 | void redoLayout_GmDocument (iGmDocument *); |
123 | void setUrl_GmDocument (iGmDocument *, const iString *url); | 129 | void setUrl_GmDocument (iGmDocument *, const iString *url); |
@@ -135,6 +141,7 @@ void render_GmDocument (const iGmDocument *, iRangei visRan | |||
135 | iInt2 size_GmDocument (const iGmDocument *); | 141 | iInt2 size_GmDocument (const iGmDocument *); |
136 | const iGmRun * siteBanner_GmDocument (const iGmDocument *); | 142 | const iGmRun * siteBanner_GmDocument (const iGmDocument *); |
137 | iBool hasSiteBanner_GmDocument (const iGmDocument *); | 143 | iBool hasSiteBanner_GmDocument (const iGmDocument *); |
144 | enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *); | ||
138 | const iString * bannerText_GmDocument (const iGmDocument *); | 145 | const iString * bannerText_GmDocument (const iGmDocument *); |
139 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ | 146 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ |
140 | const iString * source_GmDocument (const iGmDocument *); | 147 | const iString * source_GmDocument (const iGmDocument *); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 3f952411..432dd290 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -754,6 +754,16 @@ static void updateTheme_DocumentWidget_(iDocumentWidget *d) { | |||
754 | updateTimestampBuf_DocumentWidget_(d); | 754 | updateTimestampBuf_DocumentWidget_(d); |
755 | } | 755 | } |
756 | 756 | ||
757 | static enum iGmDocumentBanner bannerType_DocumentWidget_(const iDocumentWidget *d) { | ||
758 | if (d->certFlags & available_GmCertFlag) { | ||
759 | const int req = domainVerified_GmCertFlag | timeVerified_GmCertFlag | trusted_GmCertFlag; | ||
760 | if ((d->certFlags & req) != req) { | ||
761 | return certificateWarning_GmDocumentBanner; | ||
762 | } | ||
763 | } | ||
764 | return siteDomain_GmDocumentBanner; | ||
765 | } | ||
766 | |||
757 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, | 767 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, |
758 | const iString *meta) { | 768 | const iString *meta) { |
759 | iString *src = collectNewCStr_String("# "); | 769 | iString *src = collectNewCStr_String("# "); |
@@ -794,7 +804,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
794 | break; | 804 | break; |
795 | } | 805 | } |
796 | } | 806 | } |
797 | setSiteBannerEnabled_GmDocument(d->doc, useBanner); | 807 | setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); |
798 | setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); | 808 | setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); |
799 | setSource_DocumentWidget_(d, src); | 809 | setSource_DocumentWidget_(d, src); |
800 | updateTheme_DocumentWidget_(d); | 810 | updateTheme_DocumentWidget_(d); |
@@ -956,15 +966,18 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r | |||
956 | return; | 966 | return; |
957 | } | 967 | } |
958 | setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iFalse); | 968 | setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iFalse); |
969 | const iBool isDarkMode = isDark_ColorTheme(colorTheme_App()); | ||
959 | if (~d->certFlags & domainVerified_GmCertFlag) { | 970 | if (~d->certFlags & domainVerified_GmCertFlag) { |
960 | updateTextCStr_LabelWidget(lock, red_ColorEscape closedLock_CStr); | 971 | updateTextCStr_LabelWidget(lock, red_ColorEscape openLock_CStr); |
961 | } | 972 | } |
962 | else if (d->certFlags & trusted_GmCertFlag) { | 973 | else if (d->certFlags & trusted_GmCertFlag) { |
963 | updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_CStr); | 974 | updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_CStr); |
964 | } | 975 | } |
965 | else { | 976 | else { |
966 | updateTextCStr_LabelWidget(lock, orange_ColorEscape closedLock_CStr); | 977 | updateTextCStr_LabelWidget(lock, isDarkMode ? orange_ColorEscape closedLock_CStr |
978 | : black_ColorEscape closedLock_CStr); | ||
967 | } | 979 | } |
980 | setBanner_GmDocument(d->doc, bannerType_DocumentWidget_(d)); | ||
968 | } | 981 | } |
969 | 982 | ||
970 | static void parseUser_DocumentWidget_(iDocumentWidget *d) { | 983 | static void parseUser_DocumentWidget_(iDocumentWidget *d) { |
@@ -1490,6 +1503,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1490 | } | 1503 | } |
1491 | else if (equal_Command(cmd, "theme.changed") && document_App() == d) { | 1504 | else if (equal_Command(cmd, "theme.changed") && document_App() == d) { |
1492 | updateTheme_DocumentWidget_(d); | 1505 | updateTheme_DocumentWidget_(d); |
1506 | updateTrust_DocumentWidget_(d, NULL); | ||
1493 | updateSideIconBuf_DocumentWidget_(d); | 1507 | updateSideIconBuf_DocumentWidget_(d); |
1494 | invalidate_DocumentWidget_(d); | 1508 | invalidate_DocumentWidget_(d); |
1495 | refresh_Widget(w); | 1509 | refresh_Widget(w); |
@@ -2507,6 +2521,83 @@ static void drawMark_DrawContext_(void *context, const iGmRun *run) { | |||
2507 | } | 2521 | } |
2508 | } | 2522 | } |
2509 | 2523 | ||
2524 | static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2 visPos) { | ||
2525 | const iGmDocument *doc = d->widget->doc; | ||
2526 | const iChar icon = siteIcon_GmDocument(doc); | ||
2527 | iString str; | ||
2528 | init_String(&str); | ||
2529 | iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2)); | ||
2530 | if (icon) { | ||
2531 | appendChar_String(&str, icon); | ||
2532 | const iRect iconRect = visualBounds_Text(run->font, range_String(&str)); | ||
2533 | drawRange_Text( | ||
2534 | run->font, | ||
2535 | addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2), | ||
2536 | tmBannerIcon_ColorId, | ||
2537 | range_String(&str)); | ||
2538 | bpos.x += right_Rect(iconRect) + 3 * gap_Text; | ||
2539 | } | ||
2540 | drawRange_Text(run->font, | ||
2541 | bpos, | ||
2542 | tmBannerTitle_ColorId, | ||
2543 | bannerText_DocumentWidget_(d->widget)); | ||
2544 | if (bannerType_GmDocument(doc) == certificateWarning_GmDocumentBanner) { | ||
2545 | const int domainHeight = lineHeight_Text(banner_FontId) * 2; | ||
2546 | iRect rect = { add_I2(visPos, init_I2(0, domainHeight)), | ||
2547 | addY_I2(run->visBounds.size, -domainHeight - lineHeight_Text(uiContent_FontId)) }; | ||
2548 | format_String(&str, "UNTRUSTED CERTIFICATE"); | ||
2549 | const int certFlags = d->widget->certFlags; | ||
2550 | if (certFlags & timeVerified_GmCertFlag && certFlags & domainVerified_GmCertFlag) { | ||
2551 | iUrl parts; | ||
2552 | init_Url(&parts, d->widget->mod.url); | ||
2553 | const iTime oldUntil = domainValidUntil_GmCerts(certs_App(), parts.host); | ||
2554 | iDate exp; | ||
2555 | init_Date(&exp, &oldUntil); | ||
2556 | iTime now; | ||
2557 | initCurrent_Time(&now); | ||
2558 | const int days = secondsSince_Time(&oldUntil, &now) / 3600 / 24; | ||
2559 | if (days <= 30) { | ||
2560 | appendFormat_String(&str, | ||
2561 | "\nThe received certificate may have been recently renewed \u2014 it is " | ||
2562 | "for the correct domain and has not expired. The currently trusted " | ||
2563 | "certificate will expire on %s, in %d days.", | ||
2564 | cstrCollect_String(format_Date(&exp, "%Y-%m-%d")), | ||
2565 | days); | ||
2566 | } | ||
2567 | else { | ||
2568 | appendFormat_String(&str, "\nThe received certificate is valid but different than " | ||
2569 | "the one we trust."); | ||
2570 | } | ||
2571 | } | ||
2572 | else if (certFlags & domainVerified_GmCertFlag) { | ||
2573 | appendFormat_String(&str, "\nThe received certificate has expired on %s.", | ||
2574 | cstrCollect_String(format_Date(&d->widget->certExpiry, "%Y-%m-%d"))); | ||
2575 | } | ||
2576 | if (certFlags & haveFingerprint_GmCertFlag) { | ||
2577 | |||
2578 | } | ||
2579 | const iInt2 dims = advanceWrapRange_Text( | ||
2580 | uiContent_FontId, width_Rect(rect) - 16 * gap_UI, range_String(&str)); | ||
2581 | fillRect_Paint(&d->paint, | ||
2582 | init_Rect(0, | ||
2583 | visPos.y + domainHeight, | ||
2584 | d->widgetBounds.size.x, | ||
2585 | dims.y + lineHeight_Text(uiContent_FontId) / 2), | ||
2586 | orange_ColorId); | ||
2587 | const int fg = black_ColorId; | ||
2588 | bpos = topLeft_Rect(rect); | ||
2589 | draw_Text(uiLabelLarge_FontId, bpos, fg, "\u26a0"); | ||
2590 | adjustEdges_Rect(&rect, 0, -8 * gap_UI, 0, 8 * gap_UI); | ||
2591 | drawWrapRange_Text(uiContent_FontId, | ||
2592 | addY_I2(topLeft_Rect(rect), (lineHeight_Text(uiLabelLarge_FontId) - | ||
2593 | lineHeight_Text(uiContent_FontId)) / 2), | ||
2594 | width_Rect(rect), | ||
2595 | fg, | ||
2596 | range_String(&str)); | ||
2597 | } | ||
2598 | deinit_String(&str); | ||
2599 | } | ||
2600 | |||
2510 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { | 2601 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { |
2511 | iDrawContext *d = context; | 2602 | iDrawContext *d = context; |
2512 | const iInt2 origin = d->viewPos; | 2603 | const iInt2 origin = d->viewPos; |
@@ -2540,41 +2631,14 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
2540 | } | 2631 | } |
2541 | } | 2632 | } |
2542 | if (run->flags & siteBanner_GmRunFlag) { | 2633 | if (run->flags & siteBanner_GmRunFlag) { |
2543 | /* Draw the site banner. */ | 2634 | /* Banner background. */ |
2544 | fillRect_Paint( | 2635 | fillRect_Paint( |
2545 | &d->paint, | 2636 | &d->paint, |
2546 | initCorners_Rect(topLeft_Rect(d->widgetBounds), | 2637 | initCorners_Rect(topLeft_Rect(d->widgetBounds), |
2547 | init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))), | 2638 | init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))), |
2548 | visPos.y + height_Rect(run->visBounds))), | 2639 | visPos.y + height_Rect(run->visBounds))), |
2549 | tmBannerBackground_ColorId); | 2640 | tmBannerBackground_ColorId); |
2550 | const iChar icon = siteIcon_GmDocument(doc); | 2641 | drawBannerRun_DrawContext_(d, run, visPos); |
2551 | iString bannerText; | ||
2552 | init_String(&bannerText); | ||
2553 | iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2)); | ||
2554 | if (icon) { | ||
2555 | // appendChar_String(&bannerText, 0x2b24); // icon); | ||
2556 | // const iRect iconRect = visualBounds_Text(hugeBold_FontId, range_String(&bannerText)); | ||
2557 | // drawRange_Text(hugeBold_FontId, /*run->font,*/ | ||
2558 | // addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2), | ||
2559 | // tmBannerIcon_ColorId, | ||
2560 | // range_String(&bannerText)); | ||
2561 | // clear_String(&bannerText); | ||
2562 | appendChar_String(&bannerText, icon); | ||
2563 | const iRect iconRect = visualBounds_Text(run->font, range_String(&bannerText)); | ||
2564 | drawRange_Text( | ||
2565 | run->font, | ||
2566 | addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2), | ||
2567 | tmBannerIcon_ColorId, | ||
2568 | range_String(&bannerText)); | ||
2569 | bpos.x += right_Rect(iconRect) + 3 * gap_Text; | ||
2570 | } | ||
2571 | drawRange_Text(run->font, | ||
2572 | bpos, | ||
2573 | tmBannerTitle_ColorId, | ||
2574 | bannerText_DocumentWidget_(d->widget)); | ||
2575 | // isEmpty_String(d->widget->titleUser) ? run->text | ||
2576 | // : range_String(d->widget->titleUser)); | ||
2577 | deinit_String(&bannerText); | ||
2578 | } | 2642 | } |
2579 | else { | 2643 | else { |
2580 | if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { | 2644 | if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { |
diff --git a/src/ui/text.c b/src/ui/text.c index 3a9b6983..a8be1778 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -919,10 +919,10 @@ iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { | |||
919 | int drawWrapRange_Text(int fontId, iInt2 pos, int maxWidth, int color, iRangecc text) { | 919 | int drawWrapRange_Text(int fontId, iInt2 pos, int maxWidth, int color, iRangecc text) { |
920 | const char *endp; | 920 | const char *endp; |
921 | while (!isEmpty_Range(&text)) { | 921 | while (!isEmpty_Range(&text)) { |
922 | tryAdvance_Text(fontId, text, maxWidth, &endp); | 922 | const iInt2 adv = tryAdvance_Text(fontId, text, maxWidth, &endp); |
923 | drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp }); | 923 | drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp }); |
924 | text.start = endp; | 924 | text.start = endp; |
925 | pos.y += lineHeight_Text(fontId); | 925 | pos.y += adv.y; |
926 | } | 926 | } |
927 | return pos.y; | 927 | return pos.y; |
928 | } | 928 | } |