summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-11-28 14:23:18 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-11-28 14:23:18 +0200
commitedf1c0bb8b112879433f2e31fd9750c30e2d5144 (patch)
tree9e7e3207e8e926ae1dfe47992e9055ba21fb16ed /src
parent0ccabb2c8e0e894be520c621bca8023a6d5de2e2 (diff)
Scrolling wide preformatted blocks horizontally
Not entirely glitch-free but should be good enough for now. IssueID #44
Diffstat (limited to 'src')
-rw-r--r--src/gmdocument.c43
-rw-r--r--src/gmdocument.h13
-rw-r--r--src/ui/documentwidget.c92
-rw-r--r--src/ui/text.c11
4 files changed, 126 insertions, 33 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 2f3a006f..1ff085c7 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -304,6 +304,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
304 iBool isPreformat = iFalse; 304 iBool isPreformat = iFalse;
305 iRangecc preAltText = iNullRange; 305 iRangecc preAltText = iNullRange;
306 int preFont = preformatted_FontId; 306 int preFont = preformatted_FontId;
307 uint16_t preId = 0;
307 iBool enableIndents = iFalse; 308 iBool enableIndents = iFalse;
308 iBool addSiteBanner = d->siteBannerEnabled; 309 iBool addSiteBanner = d->siteBannerEnabled;
309 enum iGmLineType prevType = text_GmLineType; 310 enum iGmLineType prevType = text_GmLineType;
@@ -313,12 +314,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
313 } 314 }
314 while (nextSplit_Rangecc(content, "\n", &contentLine)) { 315 while (nextSplit_Rangecc(content, "\n", &contentLine)) {
315 iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */ 316 iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */
316 iGmRun run; 317 iGmRun run = { .color = white_ColorId };
317 run.flags = 0;
318 run.color = white_ColorId;
319 run.linkId = 0;
320 run.imageId = 0;
321 run.audioId = 0;
322 enum iGmLineType type; 318 enum iGmLineType type;
323 int indent = 0; 319 int indent = 0;
324 /* Detect the type of the line. */ 320 /* Detect the type of the line. */
@@ -330,6 +326,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
330 indent = indents[type]; 326 indent = indents[type];
331 if (type == preformatted_GmLineType) { 327 if (type == preformatted_GmLineType) {
332 isPreformat = iTrue; 328 isPreformat = iTrue;
329 preId++;
333 preFont = preformatted_FontId; 330 preFont = preformatted_FontId;
334 /* Use a smaller font if the block contents are wide. */ 331 /* Use a smaller font if the block contents are wide. */
335 if (measurePreformattedBlock_GmDocument_(d, line.start, preFont).x > 332 if (measurePreformattedBlock_GmDocument_(d, line.start, preFont).x >
@@ -370,6 +367,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
370 addSiteBanner = iFalse; /* overrides the banner */ 367 addSiteBanner = iFalse; /* overrides the banner */
371 continue; 368 continue;
372 } 369 }
370 run.preId = preId;
373 run.font = (d->format == plainText_GmDocumentFormat ? regularMonospace_FontId : preFont); 371 run.font = (d->format == plainText_GmDocumentFormat ? regularMonospace_FontId : preFont);
374 indent = indents[type]; 372 indent = indents[type];
375 } 373 }
@@ -509,8 +507,9 @@ static void doLayout_GmDocument_(iGmDocument *d) {
509 } 507 }
510 run.bounds.pos = addX_I2(pos, indent * gap_Text); 508 run.bounds.pos = addX_I2(pos, indent * gap_Text);
511 const char *contPos; 509 const char *contPos;
512 const int avail = d->size.x - run.bounds.pos.x; 510 const int avail = isPreformat ? 0 : (d->size.x - run.bounds.pos.x);
513 const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); 511 const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos);
512 iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x));
514 run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */ 513 run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */
515 run.bounds.size.y = dims.y; 514 run.bounds.size.y = dims.y;
516 run.visBounds = run.bounds; 515 run.visBounds = run.bounds;
@@ -596,6 +595,19 @@ static void doLayout_GmDocument_(iGmDocument *d) {
596 prevType = type; 595 prevType = type;
597 } 596 }
598 d->size.y = pos.y; 597 d->size.y = pos.y;
598 /* Go over the preformatted blocks and mark them wide if at least one run is wide. */ {
599 iForEach(Array, i, &d->layout) {
600 iGmRun *run = i.value;
601 if (run->preId && run->flags & wide_GmRunFlag) {
602 iGmRunRange block = findPreformattedRange_GmDocument(d, run);
603 for (const iGmRun *j = block.start; j != block.end; j++) {
604 iConstCast(iGmRun *, j)->flags |= wide_GmRunFlag;
605 }
606 /* Skip to the end of the block. */
607 i.pos = block.end - (const iGmRun *) constData_Array(&d->layout) - 1;
608 }
609 }
610 }
599} 611}
600 612
601void init_GmDocument(iGmDocument *d) { 613void init_GmDocument(iGmDocument *d) {
@@ -1237,6 +1249,23 @@ iRangecc findTextBefore_GmDocument(const iGmDocument *d, const iString *text, co
1237 return found; 1249 return found;
1238} 1250}
1239 1251
1252iGmRunRange findPreformattedRange_GmDocument(const iGmDocument *d, const iGmRun *run) {
1253 iAssert(run->preId);
1254 iGmRunRange range = { run, run };
1255 /* Find the beginning. */
1256 while (range.start > (const iGmRun *) constData_Array(&d->layout)) {
1257 const iGmRun *prev = range.start - 1;
1258 if (prev->preId != run->preId) break;
1259 range.start = prev;
1260 }
1261 /* Find the ending. */
1262 while (range.end < (const iGmRun *) constEnd_Array(&d->layout)) {
1263 if (range.end->preId != run->preId) break;
1264 range.end++;
1265 }
1266 return range;
1267}
1268
1240const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) { 1269const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) {
1241 /* TODO: Perf optimization likely needed; use a block map? */ 1270 /* TODO: Perf optimization likely needed; use a block map? */
1242 const iGmRun *last = NULL; 1271 const iGmRun *last = NULL;
diff --git a/src/gmdocument.h b/src/gmdocument.h
index c2a4b272..6804d772 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -90,11 +90,19 @@ struct Impl_GmRun {
90 uint8_t flags; 90 uint8_t flags;
91 iRect bounds; /* used for hit testing, may extend to edges */ 91 iRect bounds; /* used for hit testing, may extend to edges */
92 iRect visBounds; /* actual visual bounds */ 92 iRect visBounds; /* actual visual bounds */
93 uint16_t preId; /* preformatted block ID (sequential) */
93 iGmLinkId linkId; /* zero for non-links */ 94 iGmLinkId linkId; /* zero for non-links */
94 uint16_t imageId; /* zero if not an image */ 95 uint16_t imageId; /* zero if not an image */
95 uint16_t audioId; /* zero if not audio */ 96 uint16_t audioId; /* zero if not audio */
96}; 97};
97 98
99iDeclareType(GmRunRange)
100
101struct Impl_GmRunRange {
102 const iGmRun *start;
103 const iGmRun *end;
104};
105
98const char * findLoc_GmRun (const iGmRun *, iInt2 pos); 106const char * findLoc_GmRun (const iGmRun *, iInt2 pos);
99 107
100iDeclareClass(GmDocument) 108iDeclareClass(GmDocument)
@@ -130,8 +138,9 @@ const iString * bannerText_GmDocument (const iGmDocument *);
130const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ 138const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */
131const iString * source_GmDocument (const iGmDocument *); 139const iString * source_GmDocument (const iGmDocument *);
132 140
133iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); 141iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start);
134iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); 142iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before);
143iGmRunRange findPreformattedRange_GmDocument (const iGmDocument *, const iGmRun *run);
135 144
136enum iGmLinkPart { 145enum iGmLinkPart {
137 icon_GmLinkPart, 146 icon_GmLinkPart,
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 4de25b58..a1b26e7f 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -157,6 +157,8 @@ struct Impl_DocumentWidget {
157 iRangecc foundMark; 157 iRangecc foundMark;
158 int pageMargin; 158 int pageMargin;
159 iPtrArray visibleLinks; 159 iPtrArray visibleLinks;
160 iPtrArray visibleWideRuns; /* scrollable blocks */
161 iArray wideRunOffsets;
160 iPtrArray visiblePlayers; /* currently playing audio */ 162 iPtrArray visiblePlayers; /* currently playing audio */
161 const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ 163 const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */
162 float grabbedStartVolume; 164 float grabbedStartVolume;
@@ -218,6 +220,8 @@ void init_DocumentWidget(iDocumentWidget *d) {
218 init_Block(&d->sourceContent, 0); 220 init_Block(&d->sourceContent, 0);
219 iZap(d->sourceTime); 221 iZap(d->sourceTime);
220 init_PtrArray(&d->visibleLinks); 222 init_PtrArray(&d->visibleLinks);
223 init_PtrArray(&d->visibleWideRuns);
224 init_Array(&d->wideRunOffsets, sizeof(int));
221 init_PtrArray(&d->visiblePlayers); 225 init_PtrArray(&d->visiblePlayers);
222 d->grabbedPlayer = NULL; 226 d->grabbedPlayer = NULL;
223 d->playerTimer = 0; 227 d->playerTimer = 0;
@@ -256,7 +260,9 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
256 if (d->playerTimer) { 260 if (d->playerTimer) {
257 SDL_RemoveTimer(d->playerTimer); 261 SDL_RemoveTimer(d->playerTimer);
258 } 262 }
263 deinit_Array(&d->wideRunOffsets);
259 deinit_PtrArray(&d->visiblePlayers); 264 deinit_PtrArray(&d->visiblePlayers);
265 deinit_PtrArray(&d->visibleWideRuns);
260 deinit_PtrArray(&d->visibleLinks); 266 deinit_PtrArray(&d->visibleLinks);
261 delete_Block(d->certFingerprint); 267 delete_Block(d->certFingerprint);
262 delete_String(d->certSubject); 268 delete_String(d->certSubject);
@@ -317,20 +323,6 @@ static iRect siteBannerRect_DocumentWidget_(const iDocumentWidget *d) {
317 return moved_Rect(banner->visBounds, origin); 323 return moved_Rect(banner->visBounds, origin);
318} 324}
319 325
320#if 0
321static int forceBreakWidth_DocumentWidget_(const iDocumentWidget *d) {
322 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "gopher")) {
323 return documentWidth_DocumentWidget_(d);
324 }
325 if (forceLineWrap_App()) {
326 const iRect bounds = bounds_Widget(constAs_Widget(d));
327 const iRect docBounds = documentBounds_DocumentWidget_(d);
328 return right_Rect(bounds) - left_Rect(docBounds) - gap_UI * d->pageMargin;
329 }
330 return 0;
331}
332#endif
333
334static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { 326static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) {
335 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), 327 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))),
336 value_Anim(&d->scrollY)); 328 value_Anim(&d->scrollY));
@@ -351,6 +343,9 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
351 } 343 }
352 d->lastVisibleRun = run; 344 d->lastVisibleRun = run;
353 } 345 }
346 if (run->preId && run->flags & wide_GmRunFlag) {
347 pushBack_PtrArray(&d->visibleWideRuns, run);
348 }
354 if (run->audioId) { 349 if (run->audioId) {
355 pushBack_PtrArray(&d->visiblePlayers, run); 350 pushBack_PtrArray(&d->visiblePlayers, run);
356 } 351 }
@@ -538,6 +533,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
538 value_Anim(&d->scrollY), 533 value_Anim(&d->scrollY),
539 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); 534 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0);
540 clear_PtrArray(&d->visibleLinks); 535 clear_PtrArray(&d->visibleLinks);
536 clear_PtrArray(&d->visibleWideRuns);
541 clear_PtrArray(&d->visiblePlayers); 537 clear_PtrArray(&d->visiblePlayers);
542 const iRangecc oldHeading = currentHeading_DocumentWidget_(d); 538 const iRangecc oldHeading = currentHeading_DocumentWidget_(d);
543 /* Scan for visible runs. */ { 539 /* Scan for visible runs. */ {
@@ -766,6 +762,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
766 updateTheme_DocumentWidget_(d); 762 updateTheme_DocumentWidget_(d);
767 init_Anim(&d->scrollY, 0); 763 init_Anim(&d->scrollY, 0);
768 init_Anim(&d->sideOpacity, 0); 764 init_Anim(&d->sideOpacity, 0);
765 clear_Array(&d->wideRunOffsets);
769 d->state = ready_RequestState; 766 d->state = ready_RequestState;
770} 767}
771 768
@@ -903,7 +900,6 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) {
903 d->request = new_GmRequest(certs_App()); 900 d->request = new_GmRequest(certs_App());
904 setUrl_GmRequest(d->request, d->mod.url); 901 setUrl_GmRequest(d->request, d->mod.url);
905 iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); 902 iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_);
906// iConnect(GmRequest, d->request, timeout, d, requestTimedOut_DocumentWidget_);
907 iConnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_); 903 iConnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_);
908 submit_GmRequest(d->request); 904 submit_GmRequest(d->request);
909} 905}
@@ -955,6 +951,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
955 reset_GmDocument(d->doc); 951 reset_GmDocument(d->doc);
956 d->state = fetching_RequestState; 952 d->state = fetching_RequestState;
957 d->initNormScrollY = recent->normScrollY; 953 d->initNormScrollY = recent->normScrollY;
954 clear_Array(&d->wideRunOffsets);
958 /* Use the cached response data. */ 955 /* Use the cached response data. */
959 updateTrust_DocumentWidget_(d, resp); 956 updateTrust_DocumentWidget_(d, resp);
960 d->sourceTime = resp->when; 957 d->sourceTime = resp->when;
@@ -986,6 +983,9 @@ static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) {
986} 983}
987 984
988static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { 985static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) {
986 if (offset == 0) {
987 return;
988 }
989 /* Get rid of link numbers when scrolling. */ 989 /* Get rid of link numbers when scrolling. */
990 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { 990 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) {
991 d->flags &= ~showLinkNumbers_DocumentWidgetFlag; 991 d->flags &= ~showLinkNumbers_DocumentWidgetFlag;
@@ -1030,6 +1030,39 @@ static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool ce
1030 scroll_DocumentWidget_(d, 0); /* clamp it */ 1030 scroll_DocumentWidget_(d, 0); /* clamp it */
1031} 1031}
1032 1032
1033static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, int delta) {
1034 if (delta == 0) {
1035 return;
1036 }
1037 const iInt2 docPos = documentPos_DocumentWidget_(d, mousePos);
1038 iConstForEach(PtrArray, i, &d->visibleWideRuns) {
1039 const iGmRun *run = i.ptr;
1040 if (docPos.y >= top_Rect(run->bounds) && docPos.y <= bottom_Rect(run->bounds)) {
1041 /* We can scroll this run. First find out how much is allowed. */
1042 const iGmRunRange range = findPreformattedRange_GmDocument(d->doc, run);
1043 int maxWidth = 0;
1044 for (const iGmRun *r = range.start; r != range.end; r++) {
1045 maxWidth = iMax(maxWidth, width_Rect(r->visBounds));
1046 }
1047 const int maxOffset = maxWidth - documentWidth_DocumentWidget_(d) + d->pageMargin * gap_UI;
1048 if (size_Array(&d->wideRunOffsets) <= run->preId) {
1049 resize_Array(&d->wideRunOffsets, run->preId + 1);
1050 }
1051 int *offset = at_Array(&d->wideRunOffsets, run->preId - 1);
1052 const int oldOffset = *offset;
1053 *offset = iClamp(*offset + delta, 0, maxOffset);
1054 /* Make sure the whole block gets redraw. */
1055 if (oldOffset != *offset) {
1056 for (const iGmRun *r = range.start; r != range.end; r++) {
1057 insert_PtrSet(d->invalidRuns, r);
1058 }
1059 refresh_Widget(d);
1060 }
1061 break;
1062 }
1063 }
1064}
1065
1033static void checkResponse_DocumentWidget_(iDocumentWidget *d) { 1066static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1034 if (!d->request) { 1067 if (!d->request) {
1035 return; 1068 return;
@@ -1064,6 +1097,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1064 case categorySuccess_GmStatusCode: 1097 case categorySuccess_GmStatusCode:
1065 init_Anim(&d->scrollY, 0); 1098 init_Anim(&d->scrollY, 0);
1066 reset_GmDocument(d->doc); /* new content incoming */ 1099 reset_GmDocument(d->doc); /* new content incoming */
1100 clear_Array(&d->wideRunOffsets);
1067 updateDocument_DocumentWidget_(d, resp, iTrue); 1101 updateDocument_DocumentWidget_(d, resp, iTrue);
1068 break; 1102 break;
1069 case categoryRedirect_GmStatusCode: 1103 case categoryRedirect_GmStatusCode:
@@ -2015,8 +2049,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2015 } 2049 }
2016 else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { 2050 else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
2017 float acceleration = 1.0f; 2051 float acceleration = 1.0f;
2052 const iInt2 mouseCoord = mouseCoord_Window(get_Window());
2018 if (prefs_App()->hoverOutline && 2053 if (prefs_App()->hoverOutline &&
2019 contains_Widget(constAs_Widget(d->scroll), mouseCoord_Window(get_Window()))) { 2054 contains_Widget(constAs_Widget(d->scroll), mouseCoord)) {
2020 const int outHeight = outlineHeight_DocumentWidget_(d); 2055 const int outHeight = outlineHeight_DocumentWidget_(d);
2021 if (outHeight > height_Rect(bounds_Widget(w))) { 2056 if (outHeight > height_Rect(bounds_Widget(w))) {
2022 acceleration = (float) size_GmDocument(d->doc).y / (float) outHeight; 2057 acceleration = (float) size_GmDocument(d->doc).y / (float) outHeight;
@@ -2027,7 +2062,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2027 which device is sending the event. */ 2062 which device is sending the event. */
2028 if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */ 2063 if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */
2029 stop_Anim(&d->scrollY); 2064 stop_Anim(&d->scrollY);
2030 scroll_DocumentWidget_(d, -ev->wheel.y * get_Window()->pixelRatio * acceleration); 2065 iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y);
2066 if (iAbs(wheel.x) > iAbs(wheel.y)) {
2067 wheel.y = 0;
2068 }
2069 else {
2070 wheel.x = 0;
2071 }
2072 scroll_DocumentWidget_(d, -wheel.y * get_Window()->pixelRatio * acceleration);
2073 scrollWideBlock_DocumentWidget_(d, mouseCoord, wheel.x * get_Window()->pixelRatio);
2031 } 2074 }
2032 else 2075 else
2033#endif 2076#endif
@@ -2046,8 +2089,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2046 d, 2089 d,
2047 -3 * amount * lineHeight_Text(paragraph_FontId) * acceleration, 2090 -3 * amount * lineHeight_Text(paragraph_FontId) * acceleration,
2048 smoothDuration_DocumentWidget_ * 2091 smoothDuration_DocumentWidget_ *
2092 /* accelerated speed for repeated wheelings */
2049 (!isFinished_Anim(&d->scrollY) && pos_Anim(&d->scrollY) < 0.25f ? 0.5f : 1.0f)); 2093 (!isFinished_Anim(&d->scrollY) && pos_Anim(&d->scrollY) < 0.25f ? 0.5f : 1.0f));
2050 /* accelerated speed for repeated wheelings */ 2094 scrollWideBlock_DocumentWidget_(
2095 d, mouseCoord, ev->wheel.x * lineHeight_Text(paragraph_FontId));
2051 } 2096 }
2052 iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); 2097 iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue);
2053 return iTrue; 2098 return iTrue;
@@ -2399,7 +2444,15 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
2399 const iBool isHover = 2444 const iBool isHover =
2400 (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && 2445 (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId &&
2401 ~run->flags & decoration_GmRunFlag); 2446 ~run->flags & decoration_GmRunFlag);
2402 const iInt2 visPos = add_I2(run->visBounds.pos, origin); 2447 iInt2 visPos = add_I2(run->visBounds.pos, origin);
2448 /* Preformatted runs can be scrolled. */
2449 if (run->preId && run->flags & wide_GmRunFlag) {
2450 const size_t numOffsets = size_Array(&d->widget->wideRunOffsets);
2451 const int *offsets = constData_Array(&d->widget->wideRunOffsets);
2452 if (run->preId <= numOffsets) {
2453 visPos.x -= offsets[run->preId - 1];
2454 }
2455 }
2403 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackground_ColorId); 2456 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackground_ColorId);
2404 if (run->linkId && ~run->flags & decoration_GmRunFlag) { 2457 if (run->linkId && ~run->flags & decoration_GmRunFlag) {
2405 fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart); 2458 fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart);
@@ -2962,6 +3015,7 @@ iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) {
2962 3015
2963void updateSize_DocumentWidget(iDocumentWidget *d) { 3016void updateSize_DocumentWidget(iDocumentWidget *d) {
2964 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); 3017 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d));
3018 clear_Array(&d->wideRunOffsets);
2965 updateSideIconBuf_DocumentWidget_(d); 3019 updateSideIconBuf_DocumentWidget_(d);
2966 updateOutline_DocumentWidget_(d); 3020 updateOutline_DocumentWidget_(d);
2967 updateVisible_DocumentWidget_(d); 3021 updateVisible_DocumentWidget_(d);
diff --git a/src/ui/text.c b/src/ui/text.c
index 0b8c98e7..e047bbce 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -165,6 +165,7 @@ static iText text_;
165static void initFonts_Text_(iText *d) { 165static void initFonts_Text_(iText *d) {
166 const float textSize = fontSize_UI * d->contentFontSize; 166 const float textSize = fontSize_UI * d->contentFontSize;
167 const float monoSize = fontSize_UI * d->contentFontSize / contentScale_Text_ * 0.866f; 167 const float monoSize = fontSize_UI * d->contentFontSize / contentScale_Text_ * 0.866f;
168 const float smallMonoSize = monoSize * 0.866f;
168 const iBlock *regularFont = &fontNunitoRegular_Embedded; 169 const iBlock *regularFont = &fontNunitoRegular_Embedded;
169 const iBlock *italicFont = &fontNunitoLightItalic_Embedded; 170 const iBlock *italicFont = &fontNunitoLightItalic_Embedded;
170 const iBlock *h12Font = &fontNunitoExtraBold_Embedded; 171 const iBlock *h12Font = &fontNunitoExtraBold_Embedded;
@@ -218,7 +219,7 @@ static void initFonts_Text_(iText *d) {
218 /* content fonts */ 219 /* content fonts */
219 { regularFont, textSize, scaling, symbols_FontId }, 220 { regularFont, textSize, scaling, symbols_FontId },
220 { &fontFiraMonoRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, 221 { &fontFiraMonoRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
221 { &fontFiraMonoRegular_Embedded, monoSize * 0.750f, 1.0f, monospaceSmallSymbols_FontId }, 222 { &fontFiraMonoRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
222 { regularFont, textSize * 1.200f, scaling, mediumSymbols_FontId }, 223 { regularFont, textSize * 1.200f, scaling, mediumSymbols_FontId },
223 { h3Font, textSize * 1.333f, h123Scaling, bigSymbols_FontId }, 224 { h3Font, textSize * 1.333f, h123Scaling, bigSymbols_FontId },
224 { italicFont, textSize, scaling, symbols_FontId }, 225 { italicFont, textSize, scaling, symbols_FontId },
@@ -237,7 +238,7 @@ static void initFonts_Text_(iText *d) {
237 { &fontSymbola_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId }, 238 { &fontSymbola_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId },
238 { &fontSymbola_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, 239 { &fontSymbola_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId },
239 { &fontSymbola_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, 240 { &fontSymbola_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
240 { &fontSymbola_Embedded, monoSize * 0.750f, 1.0f, monospaceSmallSymbols_FontId }, 241 { &fontSymbola_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
241 /* emoji fonts */ 242 /* emoji fonts */
242 { &fontNotoEmojiRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, 243 { &fontNotoEmojiRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
243 { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId }, 244 { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId },
@@ -248,10 +249,10 @@ static void initFonts_Text_(iText *d) {
248 { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId }, 249 { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId },
249 { &fontNotoEmojiRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, 250 { &fontNotoEmojiRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId },
250 { &fontNotoEmojiRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, 251 { &fontNotoEmojiRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
251 { &fontNotoEmojiRegular_Embedded, monoSize * 0.750f, 1.0f, monospaceSmallSymbols_FontId }, 252 { &fontNotoEmojiRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
252 /* japanese fonts */ 253 /* japanese fonts */
253 { &fontNotoSansJPRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, 254 { &fontNotoSansJPRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
254 { &fontNotoSansJPRegular_Embedded, monoSize * 0.750, 1.0f, monospaceSmallSymbols_FontId }, 255 { &fontNotoSansJPRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
255 { &fontNotoSansJPRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, 256 { &fontNotoSansJPRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
256 { &fontNotoSansJPRegular_Embedded, textSize, 1.0f, symbols_FontId }, 257 { &fontNotoSansJPRegular_Embedded, textSize, 1.0f, symbols_FontId },
257 { &fontNotoSansJPRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId }, 258 { &fontNotoSansJPRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId },
@@ -260,7 +261,7 @@ static void initFonts_Text_(iText *d) {
260 { &fontNotoSansJPRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, 261 { &fontNotoSansJPRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId },
261 /* korean fonts */ 262 /* korean fonts */
262 { &fontNanumGothicRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, 263 { &fontNanumGothicRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
263 { &fontNanumGothicRegular_Embedded, monoSize * 0.750, 1.0f, monospaceSmallSymbols_FontId }, 264 { &fontNanumGothicRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
264 { &fontNanumGothicRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, 265 { &fontNanumGothicRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
265 { &fontNanumGothicRegular_Embedded, textSize, 1.0f, symbols_FontId }, 266 { &fontNanumGothicRegular_Embedded, textSize, 1.0f, symbols_FontId },
266 { &fontNanumGothicRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId }, 267 { &fontNanumGothicRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId },