diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 18 | ||||
-rw-r--r-- | src/defs.h | 6 | ||||
-rw-r--r-- | src/gmdocument.c | 98 | ||||
-rw-r--r-- | src/gmdocument.h | 24 | ||||
-rw-r--r-- | src/gmtypesetter.c | 25 | ||||
-rw-r--r-- | src/gmtypesetter.h | 41 | ||||
-rw-r--r-- | src/history.c | 82 | ||||
-rw-r--r-- | src/history.h | 12 | ||||
-rw-r--r-- | src/macos.m | 2 | ||||
-rw-r--r-- | src/media.c | 5 | ||||
-rw-r--r-- | src/media.h | 2 | ||||
-rw-r--r-- | src/ui/color.c | 6 | ||||
-rw-r--r-- | src/ui/color.h | 40 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 102 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 4 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 26 | ||||
-rw-r--r-- | src/ui/labelwidget.c | 2 | ||||
-rw-r--r-- | src/ui/text.c | 4 | ||||
-rw-r--r-- | src/ui/widget.c | 1 |
19 files changed, 365 insertions, 135 deletions
@@ -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 | } |
@@ -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 | ||
27 | enum iSourceFormat { | ||
28 | undefined_SourceFormat = -1, | ||
29 | gemini_SourceFormat = 0, | ||
30 | plainText_SourceFormat, | ||
31 | }; | ||
32 | |||
27 | enum iFileVersion { | 33 | enum 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 | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, 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) { | |||
48 | iDeclareType(GmLink) | 49 | iDeclareType(GmLink) |
49 | 50 | ||
50 | struct Impl_GmLink { | 51 | struct 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 | ||
75 | struct Impl_GmDocument { | 76 | struct 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 { | |||
95 | iDefineObjectConstruction(GmDocument) | 97 | iDefineObjectConstruction(GmDocument) |
96 | 98 | ||
97 | static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { | 99 | static 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 | ||
306 | static iBool isNormalized_GmDocument_(const iGmDocument *d) { | 308 | static 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 | ||
864 | void init_GmDocument(iGmDocument *d) { | 871 | void 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 | ||
898 | iMedia *media_GmDocument(iGmDocument *d) { | 907 | iMedia *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 | ||
906 | void reset_GmDocument(iGmDocument *d) { | 916 | void 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 | ||
917 | static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | 930 | static 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 | ||
1390 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { | 1403 | void 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 | ||
1494 | void setUrl_GmDocument(iGmDocument *d, const iString *url) { | 1528 | void 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 | ||
1502 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | 1536 | void 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 | ||
1649 | size_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 | |||
1605 | iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { | 1657 | iRangecc 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 | ||
126 | struct Impl_GmRun { | 127 | struct 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); | |||
148 | iDeclareClass(GmDocument) | 149 | iDeclareClass(GmDocument) |
149 | iDeclareObjectConstruction(GmDocument) | 150 | iDeclareObjectConstruction(GmDocument) |
150 | 151 | ||
151 | enum iGmDocumentFormat { | ||
152 | undefined_GmDocumentFormat = -1, | ||
153 | gemini_GmDocumentFormat = 0, | ||
154 | plainText_GmDocumentFormat, | ||
155 | }; | ||
156 | |||
157 | enum iGmDocumentBanner { | 152 | enum iGmDocumentBanner { |
158 | none_GmDocumentBanner, | 153 | none_GmDocumentBanner, |
159 | siteDomain_GmDocumentBanner, | 154 | siteDomain_GmDocumentBanner, |
160 | certificateWarning_GmDocumentBanner, | 155 | certificateWarning_GmDocumentBanner, |
161 | }; | 156 | }; |
162 | 157 | ||
158 | enum iGmDocumentUpdate { | ||
159 | partial_GmDocumentUpdate, /* appending more content */ | ||
160 | final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */ | ||
161 | }; | ||
162 | |||
163 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); | 163 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); |
164 | void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); | 164 | void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); |
165 | void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); | 165 | void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); |
166 | void setWidth_GmDocument (iGmDocument *, int width); | 166 | void setWidth_GmDocument (iGmDocument *, int width); |
167 | void redoLayout_GmDocument (iGmDocument *); | 167 | void redoLayout_GmDocument (iGmDocument *); |
168 | iBool updateOpenURLs_GmDocument(iGmDocument *); | 168 | iBool updateOpenURLs_GmDocument(iGmDocument *); |
169 | void setUrl_GmDocument (iGmDocument *, const iString *url); | 169 | void setUrl_GmDocument (iGmDocument *, const iString *url); |
170 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); | 170 | void setSource_GmDocument (iGmDocument *, const iString *source, int width, |
171 | enum iGmDocumentUpdate updateType); | ||
171 | void foldPre_GmDocument (iGmDocument *, uint16_t preId); | 172 | void foldPre_GmDocument (iGmDocument *, uint16_t preId); |
172 | 173 | ||
173 | void reset_GmDocument (iGmDocument *); /* free images */ | 174 | //void reset_GmDocument (iGmDocument *); /* free images */ |
174 | 175 | ||
175 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); | 176 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); |
176 | 177 | ||
@@ -190,6 +191,7 @@ enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *); | |||
190 | const iString * bannerText_GmDocument (const iGmDocument *); | 191 | const iString * bannerText_GmDocument (const iGmDocument *); |
191 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ | 192 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ |
192 | const iString * source_GmDocument (const iGmDocument *); | 193 | const iString * source_GmDocument (const iGmDocument *); |
194 | size_t memorySize_GmDocument (const iGmDocument *); /* bytes */ | ||
193 | 195 | ||
194 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); | 196 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); |
195 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); | 197 | iRangecc 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 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. 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 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY 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 | ||
21 | SOFTWARE, 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 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. 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 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY 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 | ||
21 | SOFTWARE, 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 | |||
34 | iDeclareType(GmTypesetter) | ||
35 | iDeclareTypeConstruction(GmTypesetter) | ||
36 | |||
37 | void reset_GmTypesetter (iGmTypesetter *, enum iSourceFormat format); | ||
38 | void setWidth_GmTypesetter (iGmTypesetter *, int width); | ||
39 | void addInput_GmTypesetter (iGmTypesetter *, const iString *source); | ||
40 | iBool getRuns_GmTypesetter (iGmTypesetter *, iArray *runs_out); /* returns false when no output generated */ | ||
41 | void 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 | ||
35 | void init_RecentUrl(iRecentUrl *d) { | 35 | void 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 | ||
41 | void deinit_RecentUrl(iRecentUrl *d) { | 42 | void 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) | |||
48 | iRecentUrl *copy_RecentUrl(const iRecentUrl *d) { | 50 | iRecentUrl *copy_RecentUrl(const iRecentUrl *d) { |
49 | iRecentUrl *copy = new_RecentUrl(); | 51 | iRecentUrl *copy = new_RecentUrl(); |
50 | set_String(©->url, &d->url); | 52 | set_String(©->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 | ||
59 | size_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 | |||
68 | size_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 | ||
58 | struct Impl_History { | 78 | struct Impl_History { |
@@ -88,24 +108,45 @@ iHistory *copy_History(const iHistory *d) { | |||
88 | return copy; | 108 | return copy; |
89 | } | 109 | } |
90 | 110 | ||
111 | iMemInfo 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 | |||
91 | iString *debugInfo_History(const iHistory *d) { | 121 | iString *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 | ||
348 | void 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 | |||
305 | size_t cacheSize_History(const iHistory *d) { | 358 | size_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 | |||
44 | iDeclareType(MemInfo) | ||
45 | |||
46 | struct 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 *); | |||
51 | void add_History (iHistory *, const iString *url); | 60 | void add_History (iHistory *, const iString *url); |
52 | void replace_History (iHistory *, const iString *url); | 61 | void replace_History (iHistory *, const iString *url); |
53 | void setCachedResponse_History (iHistory *, const iGmResponse *response); | 62 | void setCachedResponse_History (iHistory *, const iGmResponse *response); |
63 | void setCachedDocument_History (iHistory *, iGmDocument *doc); | ||
54 | iBool goBack_History (iHistory *); | 64 | iBool goBack_History (iHistory *); |
55 | iBool goForward_History (iHistory *); | 65 | iBool goForward_History (iHistory *); |
56 | iRecentUrl *recentUrl_History (iHistory *, size_t pos); | 66 | iRecentUrl *recentUrl_History (iHistory *, size_t pos); |
@@ -75,4 +85,4 @@ const iGmResponse * | |||
75 | size_t cacheSize_History (const iHistory *); | 85 | size_t cacheSize_History (const iHistory *); |
76 | 86 | ||
77 | iString * debugInfo_History (const iHistory *); | 87 | iString * debugInfo_History (const iHistory *); |
78 | 88 | iMemInfo 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 | ||
264 | size_t memorySize_Media(const iMedia *d) { | ||
265 | /* TODO: Calculate the actual memory use. */ | ||
266 | return 0; | ||
267 | } | ||
268 | |||
264 | iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) { | 269 | iBool 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 *); | |||
50 | iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url); | 50 | iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url); |
51 | iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); | 51 | iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); |
52 | 52 | ||
53 | size_t memorySize_Media (const iMedia *); | ||
54 | |||
53 | iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); | 55 | iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); |
54 | iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out); | 56 | iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out); |
55 | iInt2 imageSize_Media (const iMedia *, iMediaId imageId); | 57 | iInt2 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 | ||
479 | iHSLColor setSat_HSLColor(iHSLColor d, float sat) { | 479 | iHSLColor 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 | ||
211 | iDeclareType(Color) | 211 | iDeclareType(Color) |
212 | iDeclareType(HSLColor) | 212 | iDeclareType(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 | ||
1019 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | 1019 | static 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 | |||
1038 | void 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 | |||
1048 | static 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 | ||
1040 | static void updateTheme_DocumentWidget_(iDocumentWidget *d) { | 1054 | static 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 | ||
1158 | static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { | 1172 | static 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 | ||
1351 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, | 1365 | static 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(¶m); | 1397 | trim_Rangecc(¶m); |
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 | ||
1558 | static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, | 1578 | static 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 | ||
1593 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | 1616 | static 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 | ||
4630 | void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { | 4656 | void 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 | ||
716 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | 716 | static 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 | ||
42 | typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); | 42 | typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); |
43 | 43 | ||
44 | void setHint_InputWidget (iInputWidget *, const char *hintText); | 44 | void setHint_InputWidget (iInputWidget *, const char *hintText); |
45 | void setMode_InputWidget (iInputWidget *, enum iInputMode mode); | 45 | void setMode_InputWidget (iInputWidget *, enum iInputMode mode); |
46 | void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); | 46 | void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); |
47 | void setText_InputWidget (iInputWidget *, const iString *text); | 47 | void setText_InputWidget (iInputWidget *, const iString *text); |
48 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); | 48 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); |
49 | void setFont_InputWidget (iInputWidget *, int fontId); | 49 | void setFont_InputWidget (iInputWidget *, int fontId); |
50 | void setCursor_InputWidget (iInputWidget *, size_t pos); | 50 | void setCursor_InputWidget (iInputWidget *, size_t pos); |
51 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ | 51 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ |
52 | void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); | 52 | void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); |
53 | void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); | 53 | void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); |
54 | void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); | 54 | void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); |
55 | void begin_InputWidget (iInputWidget *); | 55 | void begin_InputWidget (iInputWidget *); |
56 | void end_InputWidget (iInputWidget *, iBool accept); | 56 | void end_InputWidget (iInputWidget *, iBool accept); |
57 | void selectAll_InputWidget (iInputWidget *); | 57 | void selectAll_InputWidget (iInputWidget *); |
58 | 58 | ||
59 | void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); | 59 | void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); |
60 | void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); | 60 | void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); |
@@ -62,15 +62,13 @@ void setUrlContent_InputWidget (iInputWidget *, iBool isUrl); | |||
62 | void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); | 62 | void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); |
63 | void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); | 63 | void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); |
64 | 64 | ||
65 | const iString * text_InputWidget (const iInputWidget *); | 65 | iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *); |
66 | const iString * text_InputWidget (const iInputWidget *); | ||
66 | 67 | ||
67 | iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { | 68 | iLocalDef 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 | ||
71 | iInputWidgetContentPadding | ||
72 | contentPadding_InputWidget (const iInputWidget *); | ||
73 | |||
74 | iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { | 72 | iLocalDef 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; |