summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.c18
-rw-r--r--src/defs.h6
-rw-r--r--src/gmdocument.c98
-rw-r--r--src/gmdocument.h24
-rw-r--r--src/gmtypesetter.c25
-rw-r--r--src/gmtypesetter.h41
-rw-r--r--src/history.c82
-rw-r--r--src/history.h12
-rw-r--r--src/macos.m2
-rw-r--r--src/media.c5
-rw-r--r--src/media.h2
-rw-r--r--src/ui/color.c6
-rw-r--r--src/ui/color.h40
-rw-r--r--src/ui/documentwidget.c102
-rw-r--r--src/ui/inputwidget.c4
-rw-r--r--src/ui/inputwidget.h26
-rw-r--r--src/ui/labelwidget.c2
-rw-r--r--src/ui/text.c4
-rw-r--r--src/ui/widget.c1
19 files changed, 365 insertions, 135 deletions
diff --git a/src/app.c b/src/app.c
index 663fa2e8..71cff954 100644
--- a/src/app.c
+++ b/src/app.c
@@ -918,13 +918,25 @@ const iString *debugInfo_App(void) {
918 extern char **environ; /* The environment variables. */ 918 extern char **environ; /* The environment variables. */
919 iApp *d = &app_; 919 iApp *d = &app_;
920 iString *msg = collectNew_String(); 920 iString *msg = collectNew_String();
921 iObjectList *docs = iClob(listDocuments_App(NULL));
921 format_String(msg, "# Debug information\n"); 922 format_String(msg, "# Debug information\n");
923 appendFormat_String(msg, "## Memory usage\n"); {
924 iMemInfo total = { 0, 0 };
925 iForEach(ObjectList, i, docs) {
926 iDocumentWidget *doc = i.object;
927 iMemInfo usage = memoryUsage_History(history_DocumentWidget(doc));
928 total.cacheSize += usage.cacheSize;
929 total.memorySize += usage.memorySize;
930 }
931 appendFormat_String(msg, "Total cache: %.3f MB\n", total.cacheSize / 1.0e6f);
932 appendFormat_String(msg, "Total memory: %.3f MB\n", total.memorySize / 1.0e6f);
933 }
922 appendFormat_String(msg, "## Documents\n"); 934 appendFormat_String(msg, "## Documents\n");
923 iForEach(ObjectList, k, iClob(listDocuments_App(NULL))) { 935 iForEach(ObjectList, k, docs) {
924 iDocumentWidget *doc = k.object; 936 iDocumentWidget *doc = k.object;
925 appendFormat_String(msg, "### Tab %d.%zu: %s\n", 937 appendFormat_String(msg, "### Tab %d.%zu: %s\n",
926 constAs_Widget(doc)->root == get_Window()->roots[0] ? 0 : 1, 938 constAs_Widget(doc)->root == get_Window()->roots[0] ? 1 : 2,
927 childIndex_Widget(constAs_Widget(doc)->parent, k.object), 939 childIndex_Widget(constAs_Widget(doc)->parent, k.object) + 1,
928 cstr_String(bookmarkTitle_DocumentWidget(doc))); 940 cstr_String(bookmarkTitle_DocumentWidget(doc)));
929 append_String(msg, collect_String(debugInfo_History(history_DocumentWidget(doc)))); 941 append_String(msg, collect_String(debugInfo_History(history_DocumentWidget(doc))));
930 } 942 }
diff --git a/src/defs.h b/src/defs.h
index d16c56f5..ff68e7e5 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -24,6 +24,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
24 24
25#include "lang.h" 25#include "lang.h"
26 26
27enum iSourceFormat {
28 undefined_SourceFormat = -1,
29 gemini_SourceFormat = 0,
30 plainText_SourceFormat,
31};
32
27enum iFileVersion { 33enum iFileVersion {
28 initial_FileVersion = 0, 34 initial_FileVersion = 0,
29 addedResponseTimestamps_FileVersion = 1, 35 addedResponseTimestamps_FileVersion = 1,
diff --git a/src/gmdocument.c b/src/gmdocument.c
index b95f85e7..56953255 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "gmdocument.h" 23#include "gmdocument.h"
24#include "gmtypesetter.h"
24#include "gmutil.h" 25#include "gmutil.h"
25#include "lang.h" 26#include "lang.h"
26#include "ui/color.h" 27#include "ui/color.h"
@@ -48,7 +49,7 @@ iBool isDark_GmDocumentTheme(enum iGmDocumentTheme d) {
48iDeclareType(GmLink) 49iDeclareType(GmLink)
49 50
50struct Impl_GmLink { 51struct Impl_GmLink {
51 iString url; 52 iString url; /* resolved */
52 iRangecc urlRange; /* URL in the source */ 53 iRangecc urlRange; /* URL in the source */
53 iRangecc labelRange; /* label in the source */ 54 iRangecc labelRange; /* label in the source */
54 iRangecc labelIcon; /* special icon defined in the label text */ 55 iRangecc labelIcon; /* special icon defined in the label text */
@@ -74,9 +75,10 @@ iDefineTypeConstruction(GmLink)
74 75
75struct Impl_GmDocument { 76struct Impl_GmDocument {
76 iObject object; 77 iObject object;
77 enum iGmDocumentFormat format; 78 enum iSourceFormat format;
78 iString source; 79 iString unormSource; /* unnormalized source */
79 iString url; /* for resolving relative links */ 80 iString source; /* normalized source */
81 iString url; /* for resolving relative links */
80 iString localHost; 82 iString localHost;
81 iInt2 size; 83 iInt2 size;
82 iArray layout; /* contents of source, laid out in document space */ 84 iArray layout; /* contents of source, laid out in document space */
@@ -95,7 +97,7 @@ struct Impl_GmDocument {
95iDefineObjectConstruction(GmDocument) 97iDefineObjectConstruction(GmDocument)
96 98
97static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { 99static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) {
98 if (d->format == plainText_GmDocumentFormat) { 100 if (d->format == plainText_SourceFormat) {
99 return text_GmLineType; 101 return text_GmLineType;
100 } 102 }
101 return lineType_Rangecc(line); 103 return lineType_Rangecc(line);
@@ -305,7 +307,7 @@ static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo
305 307
306static iBool isNormalized_GmDocument_(const iGmDocument *d) { 308static iBool isNormalized_GmDocument_(const iGmDocument *d) {
307 const iPrefs *prefs = prefs_App(); 309 const iPrefs *prefs = prefs_App();
308 if (d->format == plainText_GmDocumentFormat) { 310 if (d->format == plainText_SourceFormat) {
309 return iTrue; /* tabs are always normalized in plain text */ 311 return iTrue; /* tabs are always normalized in plain text */
310 } 312 }
311 if (startsWithCase_String(&d->url, "gemini:") && prefs->monospaceGemini) { 313 if (startsWithCase_String(&d->url, "gemini:") && prefs->monospaceGemini) {
@@ -434,12 +436,15 @@ static void doLayout_GmDocument_(iGmDocument *d) {
434 enum iGmLineType prevType = text_GmLineType; 436 enum iGmLineType prevType = text_GmLineType;
435 enum iGmLineType prevNonBlankType = text_GmLineType; 437 enum iGmLineType prevNonBlankType = text_GmLineType;
436 iBool followsBlank = iFalse; 438 iBool followsBlank = iFalse;
437 if (d->format == plainText_GmDocumentFormat) { 439 if (d->format == plainText_SourceFormat) {
438 isPreformat = iTrue; 440 isPreformat = iTrue;
439 isFirstText = iFalse; 441 isFirstText = iFalse;
440 } 442 }
441 while (nextSplit_Rangecc(content, "\n", &contentLine)) { 443 while (nextSplit_Rangecc(content, "\n", &contentLine)) {
442 iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */ 444 iRangecc line = contentLine; /* `line` will be trimmed; modifying would confuse `nextSplit_Rangecc` */
445 if (*line.end == '\r') {
446 line.end--; /* trim CR always */
447 }
443 iGmRun run = { .color = white_ColorId }; 448 iGmRun run = { .color = white_ColorId };
444 enum iGmLineType type; 449 enum iGmLineType type;
445 float indent = 0.0f; 450 float indent = 0.0f;
@@ -500,14 +505,14 @@ static void doLayout_GmDocument_(iGmDocument *d) {
500 if (contentLine.start == content.start) { 505 if (contentLine.start == content.start) {
501 prevType = type; 506 prevType = type;
502 } 507 }
503 if (d->format == gemini_GmDocumentFormat && 508 if (d->format == gemini_SourceFormat &&
504 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { 509 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) {
505 isPreformat = iFalse; 510 isPreformat = iFalse;
506 addSiteBanner = iFalse; /* overrides the banner */ 511 addSiteBanner = iFalse; /* overrides the banner */
507 continue; 512 continue;
508 } 513 }
509 run.preId = preId; 514 run.preId = preId;
510 run.font = (d->format == plainText_GmDocumentFormat ? regularMonospace_FontId : preFont); 515 run.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont);
511 indent = indents[type]; 516 indent = indents[type];
512 } 517 }
513 if (addSiteBanner) { 518 if (addSiteBanner) {
@@ -580,7 +585,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
580 } 585 }
581 } 586 }
582 /* Folded blocks are represented by a single run with the alt text. */ 587 /* Folded blocks are represented by a single run with the alt text. */
583 if (isPreformat && d->format != plainText_GmDocumentFormat) { 588 if (isPreformat && d->format != plainText_SourceFormat) {
584 const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); 589 const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1);
585 if (meta->flags & folded_GmPreMetaFlag) { 590 if (meta->flags & folded_GmPreMetaFlag) {
586 const iBool isBlank = isEmpty_Range(&meta->altText); 591 const iBool isBlank = isEmpty_Range(&meta->altText);
@@ -676,7 +681,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
676 pushBack_Array(&d->layout, &icon); 681 pushBack_Array(&d->layout, &icon);
677 } 682 }
678 run.color = colors[type]; 683 run.color = colors[type];
679 if (d->format == plainText_GmDocumentFormat) { 684 if (d->format == plainText_SourceFormat) {
680 run.color = colors[text_GmLineType]; 685 run.color = colors[text_GmLineType];
681 } 686 }
682 /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ 687 /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */
@@ -705,8 +710,8 @@ static void doLayout_GmDocument_(iGmDocument *d) {
705 type == quote_GmLineType ? 4 : 0); 710 type == quote_GmLineType ? 4 : 0);
706 } 711 }
707 const iBool isWordWrapped = 712 const iBool isWordWrapped =
708 (d->format == plainText_GmDocumentFormat ? prefs->plainTextWrap : !isPreformat); 713 (d->format == plainText_SourceFormat ? prefs->plainTextWrap : !isPreformat);
709 if (isPreformat && d->format != plainText_GmDocumentFormat) { 714 if (isPreformat && d->format != plainText_SourceFormat) {
710 /* Remember the top left coordinates of the block (first line of block). */ 715 /* Remember the top left coordinates of the block (first line of block). */
711 iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); 716 iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1);
712 if (~meta->flags & topLeft_GmPreMetaFlag) { 717 if (~meta->flags & topLeft_GmPreMetaFlag) {
@@ -859,10 +864,13 @@ static void doLayout_GmDocument_(iGmDocument *d) {
859 } 864 }
860 } 865 }
861 } 866 }
867 printf("[GmDocument] layout size: %zu runs (%zu bytes)\n",
868 size_Array(&d->layout), size_Array(&d->layout) * sizeof(iGmRun));
862} 869}
863 870
864void init_GmDocument(iGmDocument *d) { 871void init_GmDocument(iGmDocument *d) {
865 d->format = gemini_GmDocumentFormat; 872 d->format = gemini_SourceFormat;
873 init_String(&d->unormSource);
866 init_String(&d->source); 874 init_String(&d->source);
867 init_String(&d->url); 875 init_String(&d->url);
868 init_String(&d->localHost); 876 init_String(&d->localHost);
@@ -893,6 +901,7 @@ void deinit_GmDocument(iGmDocument *d) {
893 deinit_String(&d->localHost); 901 deinit_String(&d->localHost);
894 deinit_String(&d->url); 902 deinit_String(&d->url);
895 deinit_String(&d->source); 903 deinit_String(&d->source);
904 deinit_String(&d->unormSource);
896} 905}
897 906
898iMedia *media_GmDocument(iGmDocument *d) { 907iMedia *media_GmDocument(iGmDocument *d) {
@@ -903,6 +912,7 @@ const iMedia *constMedia_GmDocument(const iGmDocument *d) {
903 return d->media; 912 return d->media;
904} 913}
905 914
915#if 0
906void reset_GmDocument(iGmDocument *d) { 916void reset_GmDocument(iGmDocument *d) {
907 clear_Media(d->media); 917 clear_Media(d->media);
908 clearLinks_GmDocument_(d); 918 clearLinks_GmDocument_(d);
@@ -911,8 +921,11 @@ void reset_GmDocument(iGmDocument *d) {
911 clear_Array(&d->preMeta); 921 clear_Array(&d->preMeta);
912 clear_String(&d->url); 922 clear_String(&d->url);
913 clear_String(&d->localHost); 923 clear_String(&d->localHost);
924 clear_String(&d->source);
925 clear_String(&d->unormSource);
914 d->themeSeed = 0; 926 d->themeSeed = 0;
915} 927}
928#endif
916 929
917static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { 930static void setDerivedThemeColors_(enum iGmDocumentTheme theme) {
918 set_Color(tmQuoteIcon_ColorId, 931 set_Color(tmQuoteIcon_ColorId,
@@ -1387,7 +1400,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
1387#endif 1400#endif
1388} 1401}
1389 1402
1390void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { 1403void setFormat_GmDocument(iGmDocument *d, enum iSourceFormat format) {
1391 d->format = format; 1404 d->format = format;
1392} 1405}
1393 1406
@@ -1429,10 +1442,12 @@ static void normalize_GmDocument(iGmDocument *d) {
1429 iRangecc src = range_String(&d->source); 1442 iRangecc src = range_String(&d->source);
1430 iRangecc line = iNullRange; 1443 iRangecc line = iNullRange;
1431 iBool isPreformat = iFalse; 1444 iBool isPreformat = iFalse;
1432 if (d->format == plainText_GmDocumentFormat) { 1445 if (d->format == plainText_SourceFormat) {
1433 isPreformat = iTrue; /* Cannot be turned off. */ 1446 isPreformat = iTrue; /* Cannot be turned off. */
1434 } 1447 }
1435 const int preTabWidth = 4; /* TODO: user-configurable parameter */ 1448 const int preTabWidth = 4; /* TODO: user-configurable parameter */
1449 iBool wasNormalized = iFalse;
1450 iBool hasTabs = iFalse;
1436 while (nextSplit_Rangecc(src, "\n", &line)) { 1451 while (nextSplit_Rangecc(src, "\n", &line)) {
1437 if (isPreformat) { 1452 if (isPreformat) {
1438 /* Replace any tab characters with spaces for visualization. */ 1453 /* Replace any tab characters with spaces for visualization. */
@@ -1443,13 +1458,19 @@ static void normalize_GmDocument(iGmDocument *d) {
1443 while (numSpaces-- > 0) { 1458 while (numSpaces-- > 0) {
1444 appendCStrN_String(normalized, " ", 1); 1459 appendCStrN_String(normalized, " ", 1);
1445 } 1460 }
1461 hasTabs = iTrue;
1462 wasNormalized = iTrue;
1446 } 1463 }
1447 else if (*ch != '\r') { 1464 else if (*ch != '\v') {
1448 appendCStrN_String(normalized, ch, 1); 1465 appendCStrN_String(normalized, ch, 1);
1449 } 1466 }
1467 else {
1468 hasTabs = iTrue;
1469 wasNormalized = iTrue;
1470 }
1450 } 1471 }
1451 appendCStr_String(normalized, "\n"); 1472 appendCStr_String(normalized, "\n");
1452 if (d->format == gemini_GmDocumentFormat && 1473 if (d->format == gemini_SourceFormat &&
1453 lineType_GmDocument_(d, line) == preformatted_GmLineType) { 1474 lineType_GmDocument_(d, line) == preformatted_GmLineType) {
1454 isPreformat = iFalse; 1475 isPreformat = iFalse;
1455 } 1476 }
@@ -1465,7 +1486,10 @@ static void normalize_GmDocument(iGmDocument *d) {
1465 int spaceCount = 0; 1486 int spaceCount = 0;
1466 for (const char *ch = line.start; ch != line.end; ch++) { 1487 for (const char *ch = line.start; ch != line.end; ch++) {
1467 char c = *ch; 1488 char c = *ch;
1468 if (c == '\r') continue; 1489 if (c == '\v') {
1490 wasNormalized = iTrue;
1491 continue;
1492 }
1469 if (isNormalizableSpace_(c)) { 1493 if (isNormalizableSpace_(c)) {
1470 if (isPrevSpace) { 1494 if (isPrevSpace) {
1471 if (++spaceCount == 8) { 1495 if (++spaceCount == 8) {
@@ -1474,9 +1498,13 @@ static void normalize_GmDocument(iGmDocument *d) {
1474 popBack_Block(&normalized->chars); 1498 popBack_Block(&normalized->chars);
1475 pushBack_Block(&normalized->chars, '\t'); 1499 pushBack_Block(&normalized->chars, '\t');
1476 } 1500 }
1501 wasNormalized = iTrue;
1477 continue; /* skip repeated spaces */ 1502 continue; /* skip repeated spaces */
1478 } 1503 }
1479 c = ' '; 1504 if (c != ' ') {
1505 c = ' ';
1506 wasNormalized = iTrue;
1507 }
1480 isPrevSpace = iTrue; 1508 isPrevSpace = iTrue;
1481 } 1509 }
1482 else { 1510 else {
@@ -1487,8 +1515,14 @@ static void normalize_GmDocument(iGmDocument *d) {
1487 } 1515 }
1488 appendCStr_String(normalized, "\n"); 1516 appendCStr_String(normalized, "\n");
1489 } 1517 }
1518 printf("hasTabs: %d\n", hasTabs);
1519 printf("wasNormalized: %d\n", wasNormalized);
1520 fflush(stdout);
1490 set_String(&d->source, collect_String(normalized)); 1521 set_String(&d->source, collect_String(normalized));
1491 normalize_String(&d->source); /* NFC */ 1522 normalize_String(&d->source); /* NFC */
1523 printf("orig:%zu norm:%zu\n", size_String(&d->unormSource), size_String(&d->source));
1524 /* normalized source has an extra newline at the end */
1525// iAssert(wasNormalized || equal_String(&d->unormSource, &d->source));
1492} 1526}
1493 1527
1494void setUrl_GmDocument(iGmDocument *d, const iString *url) { 1528void setUrl_GmDocument(iGmDocument *d, const iString *url) {
@@ -1499,8 +1533,18 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) {
1499 updateIconBasedOnUrl_GmDocument_(d); 1533 updateIconBasedOnUrl_GmDocument_(d);
1500} 1534}
1501 1535
1502void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { 1536void setSource_GmDocument(iGmDocument *d, const iString *source, int width,
1503 set_String(&d->source, source); 1537 enum iGmDocumentUpdate updateType) {
1538 printf("[GmDocument] source update (%zu bytes), width:%d, final:%d\n",
1539 size_String(source), width, updateType == final_GmDocumentUpdate);
1540 if (size_String(source) == size_String(&d->unormSource)) {
1541 iAssert(equal_String(source, &d->unormSource));
1542 printf("[GmDocument] source is unchanged!\n");
1543 return; /* Nothing to do. */
1544 }
1545 set_String(&d->unormSource, source);
1546 /* Normalize. */
1547 set_String(&d->source, &d->unormSource);
1504 if (isNormalized_GmDocument_(d)) { 1548 if (isNormalized_GmDocument_(d)) {
1505 normalize_GmDocument(d); 1549 normalize_GmDocument(d);
1506 } 1550 }
@@ -1602,6 +1646,14 @@ const iString *source_GmDocument(const iGmDocument *d) {
1602 return &d->source; 1646 return &d->source;
1603} 1647}
1604 1648
1649size_t memorySize_GmDocument(const iGmDocument *d) {
1650 return size_String(&d->unormSource) +
1651 size_String(&d->source) +
1652 size_Array(&d->layout) * sizeof(iGmRun) +
1653 size_Array(&d->links) * sizeof(iGmLink) +
1654 memorySize_Media(d->media);
1655}
1656
1605iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { 1657iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) {
1606 const char * src = constBegin_String(&d->source); 1658 const char * src = constBegin_String(&d->source);
1607 const size_t startPos = (start ? start - src : 0); 1659 const size_t startPos = (start ? start - src : 0);
diff --git a/src/gmdocument.h b/src/gmdocument.h
index fcbb7e59..943a408c 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25#include "defs.h"
25#include "gmutil.h" 26#include "gmutil.h"
26#include "media.h" 27#include "media.h"
27 28
@@ -125,12 +126,12 @@ enum iGmRunMediaType {
125 126
126struct Impl_GmRun { 127struct Impl_GmRun {
127 iRangecc text; 128 iRangecc text;
129 iRect bounds; /* used for hit testing, may extend to edges */
130 iRect visBounds; /* actual visual bounds */
128 uint8_t font; 131 uint8_t font;
129 uint8_t color; 132 uint8_t color;
130 uint8_t flags; 133 uint8_t flags;
131 uint8_t mediaType; 134 uint8_t mediaType;
132 iRect bounds; /* used for hit testing, may extend to edges */
133 iRect visBounds; /* actual visual bounds */
134 uint16_t preId; /* preformatted block ID (sequential) */ 135 uint16_t preId; /* preformatted block ID (sequential) */
135 iGmLinkId linkId; /* zero for non-links */ 136 iGmLinkId linkId; /* zero for non-links */
136 uint16_t mediaId; /* zero if not an image */ 137 uint16_t mediaId; /* zero if not an image */
@@ -148,29 +149,29 @@ iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos);
148iDeclareClass(GmDocument) 149iDeclareClass(GmDocument)
149iDeclareObjectConstruction(GmDocument) 150iDeclareObjectConstruction(GmDocument)
150 151
151enum iGmDocumentFormat {
152 undefined_GmDocumentFormat = -1,
153 gemini_GmDocumentFormat = 0,
154 plainText_GmDocumentFormat,
155};
156
157enum iGmDocumentBanner { 152enum iGmDocumentBanner {
158 none_GmDocumentBanner, 153 none_GmDocumentBanner,
159 siteDomain_GmDocumentBanner, 154 siteDomain_GmDocumentBanner,
160 certificateWarning_GmDocumentBanner, 155 certificateWarning_GmDocumentBanner,
161}; 156};
162 157
158enum iGmDocumentUpdate {
159 partial_GmDocumentUpdate, /* appending more content */
160 final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */
161};
162
163void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); 163void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed);
164void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); 164void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format);
165void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); 165void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type);
166void setWidth_GmDocument (iGmDocument *, int width); 166void setWidth_GmDocument (iGmDocument *, int width);
167void redoLayout_GmDocument (iGmDocument *); 167void redoLayout_GmDocument (iGmDocument *);
168iBool updateOpenURLs_GmDocument(iGmDocument *); 168iBool updateOpenURLs_GmDocument(iGmDocument *);
169void setUrl_GmDocument (iGmDocument *, const iString *url); 169void setUrl_GmDocument (iGmDocument *, const iString *url);
170void setSource_GmDocument (iGmDocument *, const iString *source, int width); 170void setSource_GmDocument (iGmDocument *, const iString *source, int width,
171 enum iGmDocumentUpdate updateType);
171void foldPre_GmDocument (iGmDocument *, uint16_t preId); 172void foldPre_GmDocument (iGmDocument *, uint16_t preId);
172 173
173void reset_GmDocument (iGmDocument *); /* free images */ 174//void reset_GmDocument (iGmDocument *); /* free images */
174 175
175typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); 176typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
176 177
@@ -190,6 +191,7 @@ enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *);
190const iString * bannerText_GmDocument (const iGmDocument *); 191const iString * bannerText_GmDocument (const iGmDocument *);
191const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ 192const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */
192const iString * source_GmDocument (const iGmDocument *); 193const iString * source_GmDocument (const iGmDocument *);
194size_t memorySize_GmDocument (const iGmDocument *); /* bytes */
193 195
194iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); 196iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start);
195iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); 197iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before);
diff --git a/src/gmtypesetter.c b/src/gmtypesetter.c
new file mode 100644
index 00000000..29a1bd93
--- /dev/null
+++ b/src/gmtypesetter.c
@@ -0,0 +1,25 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "gmtypesetter.h"
24#include "gmdocument.h"
25
diff --git a/src/gmtypesetter.h b/src/gmtypesetter.h
new file mode 100644
index 00000000..aba351dd
--- /dev/null
+++ b/src/gmtypesetter.h
@@ -0,0 +1,41 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#pragma once
24
25#include "defs.h"
26
27#include <the_Foundation/array.h>
28#include <the_Foundation/string.h>
29#include <the_Foundation/vec2.h>
30
31/* GmTypesetter has two jobs: it normalizes incoming source text, and typesets it as a
32 sequence of GmRuns. New data can be appended progressively. */
33
34iDeclareType(GmTypesetter)
35iDeclareTypeConstruction(GmTypesetter)
36
37void reset_GmTypesetter (iGmTypesetter *, enum iSourceFormat format);
38void setWidth_GmTypesetter (iGmTypesetter *, int width);
39void addInput_GmTypesetter (iGmTypesetter *, const iString *source);
40iBool getRuns_GmTypesetter (iGmTypesetter *, iArray *runs_out); /* returns false when no output generated */
41void skip_GmTypesetter (iGmTypesetter *, int ySkip);
diff --git a/src/history.c b/src/history.c
index ed8e6725..fdd0ff55 100644
--- a/src/history.c
+++ b/src/history.c
@@ -34,11 +34,13 @@ static const size_t maxStack_History_ = 50; /* back/forward navigable items */
34 34
35void init_RecentUrl(iRecentUrl *d) { 35void init_RecentUrl(iRecentUrl *d) {
36 init_String(&d->url); 36 init_String(&d->url);
37 d->normScrollY = 0; 37 d->normScrollY = 0;
38 d->cachedResponse = NULL; 38 d->cachedResponse = NULL;
39 d->cachedDoc = NULL;
39} 40}
40 41
41void deinit_RecentUrl(iRecentUrl *d) { 42void deinit_RecentUrl(iRecentUrl *d) {
43 iRelease(d->cachedDoc);
42 deinit_String(&d->url); 44 deinit_String(&d->url);
43 delete_GmResponse(d->cachedResponse); 45 delete_GmResponse(d->cachedResponse);
44} 46}
@@ -48,11 +50,29 @@ iDefineTypeConstruction(RecentUrl)
48iRecentUrl *copy_RecentUrl(const iRecentUrl *d) { 50iRecentUrl *copy_RecentUrl(const iRecentUrl *d) {
49 iRecentUrl *copy = new_RecentUrl(); 51 iRecentUrl *copy = new_RecentUrl();
50 set_String(&copy->url, &d->url); 52 set_String(&copy->url, &d->url);
51 copy->normScrollY = d->normScrollY; 53 copy->normScrollY = d->normScrollY;
52 copy->cachedResponse = d->cachedResponse ? copy_GmResponse(d->cachedResponse) : NULL; 54 copy->cachedResponse = d->cachedResponse ? copy_GmResponse(d->cachedResponse) : NULL;
55 copy->cachedDoc = ref_Object(d->cachedDoc);
53 return copy; 56 return copy;
54} 57}
55 58
59size_t cacheSize_RecentUrl(const iRecentUrl *d) {
60 size_t size = 0;
61 if (d->cachedResponse) {
62 size += size_String(&d->cachedResponse->meta);
63 size += size_Block(&d->cachedResponse->body);
64 }
65 return size;
66}
67
68size_t memorySize_RecentUrl(const iRecentUrl *d) {
69 size_t size = cacheSize_RecentUrl(d);
70 if (d->cachedDoc) {
71 size += memorySize_GmDocument(d->cachedDoc);
72 }
73 return size;
74}
75
56/*----------------------------------------------------------------------------------------------*/ 76/*----------------------------------------------------------------------------------------------*/
57 77
58struct Impl_History { 78struct Impl_History {
@@ -88,24 +108,45 @@ iHistory *copy_History(const iHistory *d) {
88 return copy; 108 return copy;
89} 109}
90 110
111iMemInfo memoryUsage_History(const iHistory *d) {
112 iMemInfo mem = { 0, 0 };
113 iConstForEach(Array, i, &d->recent) {
114 const iRecentUrl *item = i.value;
115 mem.cacheSize += cacheSize_RecentUrl(item);
116 mem.memorySize += memorySize_RecentUrl(item);
117 }
118 return mem;
119}
120
91iString *debugInfo_History(const iHistory *d) { 121iString *debugInfo_History(const iHistory *d) {
92 iString *str = new_String(); 122 iString *str = new_String();
93 format_String(str, 123 format_String(str,
94 "```\n" 124 "```\n"
95 "Idx | Size | SP%% | URL\n" 125 "Idx | Cache | Memory | SP%% | URL\n"
96 "----+---------+-----+-----\n"); 126 "----+---------+----------+-----+-----\n");
97 size_t totalSize = 0; 127 size_t totalCache = 0;
128 size_t totalMemory = 0;
98 iConstForEach(Array, i, &d->recent) { 129 iConstForEach(Array, i, &d->recent) {
99 const iRecentUrl *item = i.value; 130 const iRecentUrl *item = i.value;
100 appendFormat_String( 131 appendFormat_String(
101 str, " %2zu | ", size_Array(&d->recent) - index_ArrayConstIterator(&i) - 1); 132 str, " %2zu | ", size_Array(&d->recent) - index_ArrayConstIterator(&i) - 1);
102 if (item->cachedResponse) { 133 const size_t cacheSize = cacheSize_RecentUrl(item);
103 appendFormat_String(str, "%7zu", size_Block(&item->cachedResponse->body)); 134 const size_t memSize = memorySize_RecentUrl(item);
104 totalSize += size_Block(&item->cachedResponse->body); 135 if (cacheSize) {
136 appendFormat_String(str, "%7zu", cacheSize);
137 totalCache += cacheSize;
105 } 138 }
106 else { 139 else {
107 appendFormat_String(str, " --"); 140 appendFormat_String(str, " --");
108 } 141 }
142 appendCStr_String(str, " | ");
143 if (memSize) {
144 appendFormat_String(str, "%8zu", memSize);
145 totalMemory += memSize;
146 }
147 else {
148 appendFormat_String(str, " --");
149 }
109 appendFormat_String(str, 150 appendFormat_String(str,
110 " | %3d | %s\n", 151 " | %3d | %s\n",
111 iRound(100.0f * item->normScrollY), 152 iRound(100.0f * item->normScrollY),
@@ -114,8 +155,10 @@ iString *debugInfo_History(const iHistory *d) {
114 appendFormat_String(str, "\n```\n"); 155 appendFormat_String(str, "\n```\n");
115 appendFormat_String(str, 156 appendFormat_String(str,
116 "Total cached data: %.3f MB\n" 157 "Total cached data: %.3f MB\n"
158 "Total memory usage: %.3f MB\n"
117 "Navigation position: %zu\n\n", 159 "Navigation position: %zu\n\n",
118 totalSize / 1.0e6f, 160 totalCache / 1.0e6f,
161 totalMemory / 1.0e6f,
119 d->recentPos); 162 d->recentPos);
120 return str; 163 return str;
121} 164}
@@ -302,14 +345,22 @@ void setCachedResponse_History(iHistory *d, const iGmResponse *response) {
302 unlock_Mutex(d->mtx); 345 unlock_Mutex(d->mtx);
303} 346}
304 347
348void setCachedDocument_History(iHistory *d, iGmDocument *doc) {
349 lock_Mutex(d->mtx);
350 iRecentUrl *item = mostRecentUrl_History(d);
351 if (item && item->cachedDoc != doc) {
352 iRelease(item->cachedDoc);
353 item->cachedDoc = ref_Object(doc);
354 }
355 unlock_Mutex(d->mtx);
356}
357
305size_t cacheSize_History(const iHistory *d) { 358size_t cacheSize_History(const iHistory *d) {
306 size_t cached = 0; 359 size_t cached = 0;
307 lock_Mutex(d->mtx); 360 lock_Mutex(d->mtx);
308 iConstForEach(Array, i, &d->recent) { 361 iConstForEach(Array, i, &d->recent) {
309 const iRecentUrl *url = i.value; 362 const iRecentUrl *url = i.value;
310 if (url->cachedResponse) { 363 cached += cacheSize_RecentUrl(url);
311 cached += size_Block(&url->cachedResponse->body);
312 }
313 } 364 }
314 unlock_Mutex(d->mtx); 365 unlock_Mutex(d->mtx);
315 return cached; 366 return cached;
@@ -336,9 +387,9 @@ size_t pruneLeastImportant_History(iHistory *d) {
336 lock_Mutex(d->mtx); 387 lock_Mutex(d->mtx);
337 iConstForEach(Array, i, &d->recent) { 388 iConstForEach(Array, i, &d->recent) {
338 const iRecentUrl *url = i.value; 389 const iRecentUrl *url = i.value;
339 if (url->cachedResponse) { 390 if (url->cachedResponse || url->cachedDoc) {
340 const double urlScore = 391 const double urlScore =
341 size_Block(&url->cachedResponse->body) * 392 cacheSize_RecentUrl(url) *
342 pow(secondsSince_Time(&now, &url->cachedResponse->when) / 60.0, 1.25); 393 pow(secondsSince_Time(&now, &url->cachedResponse->when) / 60.0, 1.25);
343 if (urlScore > score) { 394 if (urlScore > score) {
344 chosen = index_ArrayConstIterator(&i); 395 chosen = index_ArrayConstIterator(&i);
@@ -348,9 +399,10 @@ size_t pruneLeastImportant_History(iHistory *d) {
348 } 399 }
349 if (chosen != iInvalidPos) { 400 if (chosen != iInvalidPos) {
350 iRecentUrl *url = at_Array(&d->recent, chosen); 401 iRecentUrl *url = at_Array(&d->recent, chosen);
351 delta = size_Block(&url->cachedResponse->body); 402 delta = cacheSize_RecentUrl(url);
352 delete_GmResponse(url->cachedResponse); 403 delete_GmResponse(url->cachedResponse);
353 url->cachedResponse = NULL; 404 url->cachedResponse = NULL;
405 iReleasePtr(&url->cachedDoc);
354 } 406 }
355 unlock_Mutex(d->mtx); 407 unlock_Mutex(d->mtx);
356 return delta; 408 return delta;
diff --git a/src/history.h b/src/history.h
index 164a61d6..ccc19d27 100644
--- a/src/history.h
+++ b/src/history.h
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25#include "gmdocument.h"
25#include "gmrequest.h" 26#include "gmrequest.h"
26 27
27#include <the_Foundation/ptrarray.h> 28#include <the_Foundation/ptrarray.h>
@@ -37,6 +38,14 @@ struct Impl_RecentUrl {
37 iString url; 38 iString url;
38 float normScrollY; /* normalized to document height */ 39 float normScrollY; /* normalized to document height */
39 iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */ 40 iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */
41 iGmDocument *cachedDoc; /* cached copy of the presentation: layout and media (not serialized) */
42};
43
44iDeclareType(MemInfo)
45
46struct Impl_MemInfo {
47 size_t cacheSize; /* number of bytes stored persistently */
48 size_t memorySize; /* number of bytes stored in RAM */
40}; 49};
41 50
42/*----------------------------------------------------------------------------------------------*/ 51/*----------------------------------------------------------------------------------------------*/
@@ -51,6 +60,7 @@ void clear_History (iHistory *);
51void add_History (iHistory *, const iString *url); 60void add_History (iHistory *, const iString *url);
52void replace_History (iHistory *, const iString *url); 61void replace_History (iHistory *, const iString *url);
53void setCachedResponse_History (iHistory *, const iGmResponse *response); 62void setCachedResponse_History (iHistory *, const iGmResponse *response);
63void setCachedDocument_History (iHistory *, iGmDocument *doc);
54iBool goBack_History (iHistory *); 64iBool goBack_History (iHistory *);
55iBool goForward_History (iHistory *); 65iBool goForward_History (iHistory *);
56iRecentUrl *recentUrl_History (iHistory *, size_t pos); 66iRecentUrl *recentUrl_History (iHistory *, size_t pos);
@@ -75,4 +85,4 @@ const iGmResponse *
75size_t cacheSize_History (const iHistory *); 85size_t cacheSize_History (const iHistory *);
76 86
77iString * debugInfo_History (const iHistory *); 87iString * debugInfo_History (const iHistory *);
78 88iMemInfo memoryUsage_History (const iHistory *);
diff --git a/src/macos.m b/src/macos.m
index bbef94c6..6bacfdd1 100644
--- a/src/macos.m
+++ b/src/macos.m
@@ -480,7 +480,7 @@ void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem *
480 [menu setAutoenablesItems:NO]; 480 [menu setAutoenablesItems:NO];
481 for (size_t i = 0; i < count; ++i) { 481 for (size_t i = 0; i < count; ++i) {
482 const char *label = translateCStr_Lang(items[i].label); 482 const char *label = translateCStr_Lang(items[i].label);
483 if (label[0] == '\r') { 483 if (label[0] == '\v') {
484 /* Skip the formatting escape. */ 484 /* Skip the formatting escape. */
485 label += 2; 485 label += 2;
486 } 486 }
diff --git a/src/media.c b/src/media.c
index 1313b7da..2ec2109d 100644
--- a/src/media.c
+++ b/src/media.c
@@ -261,6 +261,11 @@ void clear_Media(iMedia *d) {
261 clear_PtrArray(&d->downloads); 261 clear_PtrArray(&d->downloads);
262} 262}
263 263
264size_t memorySize_Media(const iMedia *d) {
265 /* TODO: Calculate the actual memory use. */
266 return 0;
267}
268
264iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) { 269iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) {
265 iGmDownload *dl = NULL; 270 iGmDownload *dl = NULL;
266 iMediaId existing = findLinkDownload_Media(d, linkId); 271 iMediaId existing = findLinkDownload_Media(d, linkId);
diff --git a/src/media.h b/src/media.h
index 7cc941d0..c6973b79 100644
--- a/src/media.h
+++ b/src/media.h
@@ -50,6 +50,8 @@ void clear_Media (iMedia *);
50iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url); 50iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url);
51iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); 51iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags);
52 52
53size_t memorySize_Media (const iMedia *);
54
53iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); 55iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId);
54iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out); 56iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out);
55iInt2 imageSize_Media (const iMedia *, iMediaId imageId); 57iInt2 imageSize_Media (const iMedia *, iMediaId imageId);
diff --git a/src/ui/color.c b/src/ui/color.c
index a6ba18e4..6c51bc06 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -468,12 +468,12 @@ const char *escape_Color(int color) {
468 return esc[color]; 468 return esc[color];
469 } 469 }
470 /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */ 470 /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */
471 /* Double-\r is used for range extension. */ 471 /* Double-\v is used for range extension. */
472 if (color + asciiBase_ColorEscape > 127) { 472 if (color + asciiBase_ColorEscape > 127) {
473 iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127); 473 iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127);
474 return format_CStr("\r\r%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape); 474 return format_CStr("\v\v%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape);
475 } 475 }
476 return format_CStr("\r%c", color + asciiBase_ColorEscape); 476 return format_CStr("\v%c", color + asciiBase_ColorEscape);
477} 477}
478 478
479iHSLColor setSat_HSLColor(iHSLColor d, float sat) { 479iHSLColor setSat_HSLColor(iHSLColor d, float sat) {
diff --git a/src/ui/color.h b/src/ui/color.h
index d2fa3c00..aafc1794 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -187,26 +187,26 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) {
187#define asciiBase_ColorEscape 33 187#define asciiBase_ColorEscape 33
188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) 188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape)
189 189
190#define restore_ColorEscape "\r\x24" /* ASCII Cancel */ 190#define restore_ColorEscape "\v\x24" /* ASCII Cancel */
191#define black_ColorEscape "\r!" 191#define black_ColorEscape "\v!"
192#define gray25_ColorEscape "\r\"" 192#define gray25_ColorEscape "\v\""
193#define gray50_ColorEscape "\r#" 193#define gray50_ColorEscape "\v#"
194#define gray75_ColorEscape "\r$" 194#define gray75_ColorEscape "\v$"
195#define white_ColorEscape "\r%" 195#define white_ColorEscape "\v%"
196#define brown_ColorEscape "\r&" 196#define brown_ColorEscape "\v&"
197#define orange_ColorEscape "\r'" 197#define orange_ColorEscape "\v'"
198#define teal_ColorEscape "\r(" 198#define teal_ColorEscape "\v("
199#define cyan_ColorEscape "\r)" 199#define cyan_ColorEscape "\v)"
200#define yellow_ColorEscape "\r*" 200#define yellow_ColorEscape "\v*"
201#define red_ColorEscape "\r+" 201#define red_ColorEscape "\v+"
202#define magenta_ColorEscape "\r," 202#define magenta_ColorEscape "\v,"
203#define blue_ColorEscape "\r-" 203#define blue_ColorEscape "\v-"
204#define green_ColorEscape "\r." 204#define green_ColorEscape "\v."
205#define uiText_ColorEscape "\r4" 205#define uiText_ColorEscape "\v4"
206#define uiTextAction_ColorEscape "\r<" 206#define uiTextAction_ColorEscape "\v<"
207#define uiTextCaution_ColorEscape "\r=" 207#define uiTextCaution_ColorEscape "\v="
208#define uiTextStrong_ColorEscape "\r:" 208#define uiTextStrong_ColorEscape "\v:"
209#define uiHeading_ColorEscape "\rR" 209#define uiHeading_ColorEscape "\vR"
210 210
211iDeclareType(Color) 211iDeclareType(Color)
212iDeclareType(HSLColor) 212iDeclareType(HSLColor)
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 6184a75a..29e264e8 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1016,9 +1016,7 @@ static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) {
1016 isPinned_DocumentWidget_(d)); 1016 isPinned_DocumentWidget_(d));
1017} 1017}
1018 1018
1019void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 1019static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) {
1020 setUrl_GmDocument(d->doc, d->mod.url);
1021 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d));
1022 documentRunsInvalidated_DocumentWidget_(d); 1020 documentRunsInvalidated_DocumentWidget_(d);
1023 updateWindowTitle_DocumentWidget_(d); 1021 updateWindowTitle_DocumentWidget_(d);
1024 updateVisible_DocumentWidget_(d); 1022 updateVisible_DocumentWidget_(d);
@@ -1034,7 +1032,23 @@ void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1034 d->flags |= otherRootByDefault_DocumentWidgetFlag; 1032 d->flags |= otherRootByDefault_DocumentWidgetFlag;
1035 } 1033 }
1036 } 1034 }
1037 showOrHidePinningIndicator_DocumentWidget_(d); 1035 showOrHidePinningIndicator_DocumentWidget_(d);
1036}
1037
1038void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1039 setUrl_GmDocument(d->doc, d->mod.url);
1040 setSource_GmDocument(d->doc,
1041 source,
1042 documentWidth_DocumentWidget_(d),
1043 isFinished_GmRequest(d->request) ? final_GmDocumentUpdate
1044 : partial_GmDocumentUpdate);
1045 documentWasChanged_DocumentWidget_(d);
1046}
1047
1048static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) {
1049 iRelease(d->doc);
1050 d->doc = ref_Object(newDoc);
1051 documentWasChanged_DocumentWidget_(d);
1038} 1052}
1039 1053
1040static void updateTheme_DocumentWidget_(iDocumentWidget *d) { 1054static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
@@ -1144,15 +1158,15 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1144 2); 1158 2);
1145 } 1159 }
1146 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); 1160 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner);
1147 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); 1161 setFormat_GmDocument(d->doc, gemini_SourceFormat);
1148 translate_Lang(src); 1162 translate_Lang(src);
1163 d->state = ready_RequestState;
1149 setSource_DocumentWidget(d, src); 1164 setSource_DocumentWidget(d, src);
1150 updateTheme_DocumentWidget_(d); 1165 updateTheme_DocumentWidget_(d);
1151 reset_SmoothScroll(&d->scrollY); 1166 reset_SmoothScroll(&d->scrollY);
1152 init_Anim(&d->sideOpacity, 0); 1167 init_Anim(&d->sideOpacity, 0);
1153 init_Anim(&d->altTextOpacity, 0); 1168 init_Anim(&d->altTextOpacity, 0);
1154 resetWideRuns_DocumentWidget_(d); 1169 resetWideRuns_DocumentWidget_(d);
1155 d->state = ready_RequestState;
1156} 1170}
1157 1171
1158static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { 1172static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) {
@@ -1264,9 +1278,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1264 2); 1278 2);
1265 } 1279 }
1266 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { 1280 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1267 redoLayout_GmDocument(d->doc); 1281 redoLayout_GmDocument(d->doc);
1268 updateVisible_DocumentWidget_(d); 1282 updateVisible_DocumentWidget_(d);
1269 invalidate_DocumentWidget_(d); 1283 invalidate_DocumentWidget_(d);
1270 } 1284 }
1271 } 1285 }
1272 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1286 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
@@ -1348,7 +1362,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1348 } 1362 }
1349} 1363}
1350 1364
1351static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, 1365static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1366 const iGmResponse *response,
1367 iGmDocument *cachedDoc,
1352 const iBool isInitialUpdate) { 1368 const iBool isInitialUpdate) {
1353 if (d->state == ready_RequestState) { 1369 if (d->state == ready_RequestState) {
1354 return; 1370 return;
@@ -1371,7 +1387,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1371 if (isSuccess_GmStatusCode(statusCode)) { 1387 if (isSuccess_GmStatusCode(statusCode)) {
1372 /* Check the MIME type. */ 1388 /* Check the MIME type. */
1373 iRangecc charset = range_CStr("utf-8"); 1389 iRangecc charset = range_CStr("utf-8");
1374 enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; 1390 enum iSourceFormat docFormat = undefined_SourceFormat;
1375 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ 1391 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */
1376 set_String(&d->sourceMime, mimeStr); 1392 set_String(&d->sourceMime, mimeStr);
1377 iRangecc mime = range_String(mimeStr); 1393 iRangecc mime = range_String(mimeStr);
@@ -1380,20 +1396,20 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1380 iRangecc param = seg; 1396 iRangecc param = seg;
1381 trim_Rangecc(&param); 1397 trim_Rangecc(&param);
1382 if (equal_Rangecc(param, "text/gemini")) { 1398 if (equal_Rangecc(param, "text/gemini")) {
1383 docFormat = gemini_GmDocumentFormat; 1399 docFormat = gemini_SourceFormat;
1384 setRange_String(&d->sourceMime, param); 1400 setRange_String(&d->sourceMime, param);
1385 } 1401 }
1386 else if (startsWith_Rangecc(param, "text/") || 1402 else if (startsWith_Rangecc(param, "text/") ||
1387 equal_Rangecc(param, "application/json") || 1403 equal_Rangecc(param, "application/json") ||
1388 equal_Rangecc(param, "application/x-pem-file") || 1404 equal_Rangecc(param, "application/x-pem-file") ||
1389 equal_Rangecc(param, "application/pem-certificate-chain")) { 1405 equal_Rangecc(param, "application/pem-certificate-chain")) {
1390 docFormat = plainText_GmDocumentFormat; 1406 docFormat = plainText_SourceFormat;
1391 setRange_String(&d->sourceMime, param); 1407 setRange_String(&d->sourceMime, param);
1392 } 1408 }
1393 else if (equal_Rangecc(param, "application/zip") || 1409 else if (equal_Rangecc(param, "application/zip") ||
1394 (startsWith_Rangecc(param, "application/") && 1410 (startsWith_Rangecc(param, "application/") &&
1395 endsWithCase_Rangecc(param, "+zip"))) { 1411 endsWithCase_Rangecc(param, "+zip"))) {
1396 docFormat = gemini_GmDocumentFormat; 1412 docFormat = gemini_SourceFormat;
1397 setRange_String(&d->sourceMime, param); 1413 setRange_String(&d->sourceMime, param);
1398 iString *key = collectNew_String(); 1414 iString *key = collectNew_String();
1399 toString_Sym(SDLK_s, KMOD_PRIMARY, key); 1415 toString_Sym(SDLK_s, KMOD_PRIMARY, key);
@@ -1420,9 +1436,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1420 startsWith_Rangecc(param, "audio/")) { 1436 startsWith_Rangecc(param, "audio/")) {
1421 const iBool isAudio = startsWith_Rangecc(param, "audio/"); 1437 const iBool isAudio = startsWith_Rangecc(param, "audio/");
1422 /* Make a simple document with an image or audio player. */ 1438 /* Make a simple document with an image or audio player. */
1423 docFormat = gemini_GmDocumentFormat; 1439 docFormat = gemini_SourceFormat;
1424 setRange_String(&d->sourceMime, param); 1440 setRange_String(&d->sourceMime, param);
1425 const iGmLinkId imgLinkId = 1; /* there's only the one link */ 1441 const iGmLinkId imgLinkId = 1; /* there's only the one link */
1442 /* TODO: Do the image loading in `postProcessRequestContent_DocumentWidget_()` */
1426 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) { 1443 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) {
1427 const char *linkTitle = 1444 const char *linkTitle =
1428 startsWith_String(mimeStr, "image/") ? "Image" : "Audio"; 1445 startsWith_String(mimeStr, "image/") ? "Image" : "Audio";
@@ -1464,7 +1481,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1464 } 1481 }
1465 } 1482 }
1466 } 1483 }
1467 if (docFormat == undefined_GmDocumentFormat) { 1484 if (docFormat == undefined_SourceFormat) {
1468 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta); 1485 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta);
1469 deinit_String(&str); 1486 deinit_String(&str);
1470 return; 1487 return;
@@ -1476,7 +1493,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1476 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset)))); 1493 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset))));
1477 } 1494 }
1478 } 1495 }
1479 if (setSource) { 1496 if (cachedDoc) {
1497 replaceDocument_DocumentWidget_(d, cachedDoc);
1498 }
1499 else if (setSource) {
1480 setSource_DocumentWidget(d, &str); 1500 setSource_DocumentWidget(d, &str);
1481 } 1501 }
1482 deinit_String(&str); 1502 deinit_String(&str);
@@ -1556,14 +1576,15 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) {
1556} 1576}
1557 1577
1558static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, 1578static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY,
1559 const iGmResponse *resp) { 1579 const iGmResponse *resp, iGmDocument *cachedDoc) {
1560 setLinkNumberMode_DocumentWidget_(d, iFalse); 1580 setLinkNumberMode_DocumentWidget_(d, iFalse);
1561 clear_ObjectList(d->media); 1581 clear_ObjectList(d->media);
1562 delete_Gempub(d->sourceGempub); 1582 delete_Gempub(d->sourceGempub);
1563 d->sourceGempub = NULL; 1583 d->sourceGempub = NULL;
1564 reset_GmDocument(d->doc); 1584 iRelease(d->doc);
1565 destroy_Widget(d->footerButtons); 1585 destroy_Widget(d->footerButtons);
1566 d->footerButtons = NULL; 1586 d->footerButtons = NULL;
1587 d->doc = new_GmDocument();
1567 resetWideRuns_DocumentWidget_(d); 1588 resetWideRuns_DocumentWidget_(d);
1568 d->state = fetching_RequestState; 1589 d->state = fetching_RequestState;
1569 /* Do the fetch. */ { 1590 /* Do the fetch. */ {
@@ -1574,10 +1595,11 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1574 d->sourceStatus = success_GmStatusCode; 1595 d->sourceStatus = success_GmStatusCode;
1575 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); 1596 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached"));
1576 set_Block(&d->sourceContent, &resp->body); 1597 set_Block(&d->sourceContent, &resp->body);
1577 updateDocument_DocumentWidget_(d, resp, iTrue); 1598 updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue);
1578 postProcessRequestContent_DocumentWidget_(d, iTrue); 1599 setCachedDocument_History(d->mod.history, d->doc);
1579 } 1600 }
1580 d->state = ready_RequestState; 1601 d->state = ready_RequestState;
1602 postProcessRequestContent_DocumentWidget_(d, iTrue);
1581 init_Anim(&d->altTextOpacity, 0); 1603 init_Anim(&d->altTextOpacity, 0);
1582 reset_SmoothScroll(&d->scrollY); 1604 reset_SmoothScroll(&d->scrollY);
1583 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); 1605 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y);
@@ -1587,13 +1609,15 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1587 cacheDocumentGlyphs_DocumentWidget_(d); 1609 cacheDocumentGlyphs_DocumentWidget_(d);
1588 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; 1610 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag;
1589 d->flags &= ~urlChanged_DocumentWidgetFlag; 1611 d->flags &= ~urlChanged_DocumentWidgetFlag;
1590 postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 1612 postCommandf_Root(
1613 as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
1591} 1614}
1592 1615
1593static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { 1616static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1594 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); 1617 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url));
1595 if (recent && recent->cachedResponse) { 1618 if (recent && recent->cachedResponse) {
1596 updateFromCachedResponse_DocumentWidget_(d, recent->normScrollY, recent->cachedResponse); 1619 updateFromCachedResponse_DocumentWidget_(
1620 d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc);
1597 return iTrue; 1621 return iTrue;
1598 } 1622 }
1599 else if (!isEmpty_String(d->mod.url)) { 1623 else if (!isEmpty_String(d->mod.url)) {
@@ -1843,13 +1867,14 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1843 /* Keep scroll position when reloading the same page. */ 1867 /* Keep scroll position when reloading the same page. */
1844 reset_SmoothScroll(&d->scrollY); 1868 reset_SmoothScroll(&d->scrollY);
1845 } 1869 }
1846 reset_GmDocument(d->doc); /* new content incoming */ 1870 iRelease(d->doc); /* new content incoming */
1871 d->doc = new_GmDocument();
1847 delete_Gempub(d->sourceGempub); 1872 delete_Gempub(d->sourceGempub);
1848 d->sourceGempub = NULL; 1873 d->sourceGempub = NULL;
1849 destroy_Widget(d->footerButtons); 1874 destroy_Widget(d->footerButtons);
1850 d->footerButtons = NULL; 1875 d->footerButtons = NULL;
1851 resetWideRuns_DocumentWidget_(d); 1876 resetWideRuns_DocumentWidget_(d);
1852 updateDocument_DocumentWidget_(d, resp, iTrue); 1877 updateDocument_DocumentWidget_(d, resp, NULL, iTrue);
1853 break; 1878 break;
1854 case categoryRedirect_GmStatusCode: 1879 case categoryRedirect_GmStatusCode:
1855 if (isEmpty_String(&resp->meta)) { 1880 if (isEmpty_String(&resp->meta)) {
@@ -1900,7 +1925,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1900 switch (category_GmStatusCode(statusCode)) { 1925 switch (category_GmStatusCode(statusCode)) {
1901 case categorySuccess_GmStatusCode: 1926 case categorySuccess_GmStatusCode:
1902 /* More content available. */ 1927 /* More content available. */
1903 updateDocument_DocumentWidget_(d, resp, iFalse); 1928 updateDocument_DocumentWidget_(d, resp, NULL, iFalse);
1904 break; 1929 break;
1905 default: 1930 default:
1906 break; 1931 break;
@@ -2085,16 +2110,16 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime,
2085 exportDownloadedFile_iOS(savePath); 2110 exportDownloadedFile_iOS(savePath);
2086#else 2111#else
2087 if (showDialog) { 2112 if (showDialog) {
2088 const iMenuItem items[2] = { 2113 const iMenuItem items[2] = {
2089 { "${dlg.save.opendownload}", 0, 0, 2114 { "${dlg.save.opendownload}", 0, 0,
2090 format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, 2115 format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) },
2091 { "${dlg.message.ok}", 0, 0, "message.ok" }, 2116 { "${dlg.message.ok}", 0, 0, "message.ok" },
2092 }; 2117 };
2093 makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", 2118 makeMessage_Widget(uiHeading_ColorEscape "${heading.save}",
2094 format_CStr("%s\n${dlg.save.size} %.3f %s", 2119 format_CStr("%s\n${dlg.save.size} %.3f %s",
2095 cstr_String(path_File(f)), 2120 cstr_String(path_File(f)),
2096 isMega ? size / 1.0e6f : (size / 1.0e3f), 2121 isMega ? size / 1.0e6f : (size / 1.0e3f),
2097 isMega ? "${mb}" : "${kb}"), 2122 isMega ? "${mb}" : "${kb}"),
2098 items, 2123 items,
2099 iElemCount(items)); 2124 iElemCount(items));
2100 } 2125 }
@@ -2520,6 +2545,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2520 (startsWithCase_String(meta_GmRequest(d->request), "text/") || 2545 (startsWithCase_String(meta_GmRequest(d->request), "text/") ||
2521 !cmp_String(&d->sourceMime, mimeType_Gempub))) { 2546 !cmp_String(&d->sourceMime, mimeType_Gempub))) {
2522 setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request)); 2547 setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request));
2548 setCachedDocument_History(d->mod.history, d->doc); /* keeps a ref */
2523 unlockResponse_GmRequest(d->request); 2549 unlockResponse_GmRequest(d->request);
2524 } 2550 }
2525 } 2551 }
@@ -3875,9 +3901,9 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3875 int bg = tmBackgroundOpenLink_ColorId; 3901 int bg = tmBackgroundOpenLink_ColorId;
3876 const int frame = tmFrameOpenLink_ColorId; 3902 const int frame = tmFrameOpenLink_ColorId;
3877 iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y), 3903 iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y),
3878 init_I2(width_Rect(d->widgetBounds) + 3904 init_I2(width_Rect(d->widgetBounds) +
3879 width_Widget(d->widget->scroll), 3905 width_Widget(d->widget->scroll),
3880 height_Rect(run->visBounds)) }; 3906 height_Rect(run->visBounds)) };
3881 /* The first line is composed of two runs that may be drawn in either order, so 3907 /* The first line is composed of two runs that may be drawn in either order, so
3882 only draw half of the background. */ 3908 only draw half of the background. */
3883 if (run->flags & decoration_GmRunFlag) { 3909 if (run->flags & decoration_GmRunFlag) {
@@ -3940,7 +3966,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3940 drawCentered_Text(defaultContentSmall_FontId, 3966 drawCentered_Text(defaultContentSmall_FontId,
3941 circleArea, 3967 circleArea,
3942 iTrue, 3968 iTrue,
3943 tmQuote_ColorId, 3969 tmQuote_ColorId,
3944 "%lc", 3970 "%lc",
3945 (int) ordChar); 3971 (int) ordChar);
3946 goto runDrawn; 3972 goto runDrawn;
@@ -4624,8 +4650,8 @@ static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) {
4624 if (!equal_String(d->mod.url, url)) { 4650 if (!equal_String(d->mod.url, url)) {
4625 d->flags |= urlChanged_DocumentWidgetFlag; 4651 d->flags |= urlChanged_DocumentWidgetFlag;
4626 set_String(d->mod.url, url); 4652 set_String(d->mod.url, url);
4627 }
4628} 4653}
4654}
4629 4655
4630void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { 4656void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) {
4631 setLinkNumberMode_DocumentWidget_(d, iFalse); 4657 setLinkNumberMode_DocumentWidget_(d, iFalse);
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 32fb5ccb..cf128017 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -714,12 +714,12 @@ iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine
714} 714}
715 715
716static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { 716static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) {
717 size_t index = line->offset;
717 if (x <= 0) { 718 if (x <= 0) {
718 return line->offset; 719 return index;
719 } 720 }
720 const char *endPos; 721 const char *endPos;
721 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); 722 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos);
722 size_t index = line->offset;
723 if (endPos == constEnd_String(&line->text)) { 723 if (endPos == constEnd_String(&line->text)) {
724 index += line->len; 724 index += line->len;
725 } 725 }
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index 5c39aae0..f8c5bf1e 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -41,20 +41,20 @@ struct Impl_InputWidgetContentPadding {
41 41
42typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); 42typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context);
43 43
44void setHint_InputWidget (iInputWidget *, const char *hintText); 44void setHint_InputWidget (iInputWidget *, const char *hintText);
45void setMode_InputWidget (iInputWidget *, enum iInputMode mode); 45void setMode_InputWidget (iInputWidget *, enum iInputMode mode);
46void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); 46void setMaxLen_InputWidget (iInputWidget *, size_t maxLen);
47void setText_InputWidget (iInputWidget *, const iString *text); 47void setText_InputWidget (iInputWidget *, const iString *text);
48void setTextCStr_InputWidget (iInputWidget *, const char *cstr); 48void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
49void setFont_InputWidget (iInputWidget *, int fontId); 49void setFont_InputWidget (iInputWidget *, int fontId);
50void setCursor_InputWidget (iInputWidget *, size_t pos); 50void setCursor_InputWidget (iInputWidget *, size_t pos);
51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ 51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */
52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); 52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines);
53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); 53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context);
54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); 54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled);
55void begin_InputWidget (iInputWidget *); 55void begin_InputWidget (iInputWidget *);
56void end_InputWidget (iInputWidget *, iBool accept); 56void end_InputWidget (iInputWidget *, iBool accept);
57void selectAll_InputWidget (iInputWidget *); 57void selectAll_InputWidget (iInputWidget *);
58 58
59void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); 59void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus);
60void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); 60void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive);
@@ -62,15 +62,13 @@ void setUrlContent_InputWidget (iInputWidget *, iBool isUrl);
62void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); 62void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits);
63void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); 63void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape);
64 64
65const iString * text_InputWidget (const iInputWidget *); 65iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *);
66const iString * text_InputWidget (const iInputWidget *);
66 67
67iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { 68iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) {
68 return cstr_String(text_InputWidget(d)); 69 return cstr_String(text_InputWidget(d));
69} 70}
70 71
71iInputWidgetContentPadding
72 contentPadding_InputWidget (const iInputWidget *);
73
74iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { 72iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) {
75 iInputWidget *d = new_InputWidget(maxLen); 73 iInputWidget *d = new_InputWidget(maxLen);
76 setHint_InputWidget(d, hint); 74 setHint_InputWidget(d, hint);
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 95f281be..ed023961 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -206,7 +206,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
206 } 206 }
207 } 207 }
208 int colorEscape = none_ColorId; 208 int colorEscape = none_ColorId;
209 if (startsWith_String(&d->label, "\r")) { 209 if (startsWith_String(&d->label, "\v")) {
210 colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ 210 colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */
211 } 211 }
212 if (isHover_LabelWidget_(d)) { 212 if (isHover_LabelWidget_(d)) {
diff --git a/src/ui/text.c b/src/ui/text.c
index 55fd4254..ffe08fca 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -1010,10 +1010,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1010 prevCh = 0; 1010 prevCh = 0;
1011 continue; 1011 continue;
1012 } 1012 }
1013 if (ch == '\r') { /* color change */ 1013 if (ch == '\v') { /* color change */
1014 iChar esc = nextChar_(&chPos, args->text.end); 1014 iChar esc = nextChar_(&chPos, args->text.end);
1015 int colorNum = args->color; 1015 int colorNum = args->color;
1016 if (esc == '\r') { /* Extended range. */ 1016 if (esc == '\v') { /* Extended range. */
1017 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape; 1017 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape;
1018 colorNum = esc - asciiBase_ColorEscape; 1018 colorNum = esc - asciiBase_ColorEscape;
1019 } 1019 }
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 543b8bc9..4eac7ecf 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -321,7 +321,6 @@ static iBool setWidth_Widget_(iWidget *d, int width) {
321 d->rect.size.x = width; 321 d->rect.size.x = width;
322 TRACE(d, "width has changed to %d", width); 322 TRACE(d, "width has changed to %d", width);
323 if (class_Widget(d)->sizeChanged) { 323 if (class_Widget(d)->sizeChanged) {
324 const int oldHeight = d->rect.size.y;
325 class_Widget(d)->sizeChanged(d); 324 class_Widget(d)->sizeChanged(d);
326 } 325 }
327 return iTrue; 326 return iTrue;