summaryrefslogtreecommitdiff
path: root/src/gmdocument.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-06-25 16:26:53 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-06-25 16:26:53 +0300
commit5dbc85eaaa1bd0a0fc11dd76a75ece2efe763df5 (patch)
tree9721fb7aced603adb10b9bb3f3beb3f8d5fba973 /src/gmdocument.c
parent95c527db1484f7758a180c6de051d0182c3b2e81 (diff)
parentf99a9111170f2ff28383fd3172fdaf4b9a1ba069 (diff)
Merge branch 'work/v1.6' into work/serious-unicode
# Conflicts: # res/fonts/SmolEmoji-Regular.ttf
Diffstat (limited to 'src/gmdocument.c')
-rw-r--r--src/gmdocument.c137
1 files changed, 112 insertions, 25 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index b95f85e7..f15d9d1d 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 */
@@ -90,12 +92,14 @@ struct Impl_GmDocument {
90 iChar siteIcon; 92 iChar siteIcon;
91 iMedia * media; 93 iMedia * media;
92 iStringSet *openURLs; /* currently open URLs for highlighting links */ 94 iStringSet *openURLs; /* currently open URLs for highlighting links */
95 iBool isPaletteValid;
96 iColor palette[tmMax_ColorId]; /* copy of the color palette */
93}; 97};
94 98
95iDefineObjectConstruction(GmDocument) 99iDefineObjectConstruction(GmDocument)
96 100
97static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { 101static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) {
98 if (d->format == plainText_GmDocumentFormat) { 102 if (d->format == plainText_SourceFormat) {
99 return text_GmLineType; 103 return text_GmLineType;
100 } 104 }
101 return lineType_Rangecc(line); 105 return lineType_Rangecc(line);
@@ -254,9 +258,12 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li
254 if ((len = decodeBytes_MultibyteChar(desc.start, desc.end, &icon)) > 0) { 258 if ((len = decodeBytes_MultibyteChar(desc.start, desc.end, &icon)) > 0) {
255 if (desc.start + len < desc.end && 259 if (desc.start + len < desc.end &&
256 (isPictograph_Char(icon) || isEmoji_Char(icon) || 260 (isPictograph_Char(icon) || isEmoji_Char(icon) ||
261 /* TODO: Add range(s) of 0x2nnn symbols. */
262 icon == 0x2139 /* info */ ||
257 icon == 0x2191 /* up arrow */ || 263 icon == 0x2191 /* up arrow */ ||
264 icon == 0x2022 /* bullet */ ||
258 icon == 0x2a2f /* close X */ || 265 icon == 0x2a2f /* close X */ ||
259 icon == 0x2022 /* bullet */) && 266 icon == 0x2b50) &&
260 !isFitzpatrickType_Char(icon)) { 267 !isFitzpatrickType_Char(icon)) {
261 link->flags |= iconFromLabel_GmLinkFlag; 268 link->flags |= iconFromLabel_GmLinkFlag;
262 link->labelIcon = (iRangecc){ desc.start, desc.start + len }; 269 link->labelIcon = (iRangecc){ desc.start, desc.start + len };
@@ -282,6 +289,12 @@ static void clearLinks_GmDocument_(iGmDocument *d) {
282 clear_PtrArray(&d->links); 289 clear_PtrArray(&d->links);
283} 290}
284 291
292static iBool isGopher_GmDocument_(const iGmDocument *d) {
293 const iRangecc scheme = urlScheme_String(&d->url);
294 return (equalCase_Rangecc(scheme, "gopher") ||
295 equalCase_Rangecc(scheme, "finger"));
296}
297
285static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) { 298static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) {
286 const iRangecc scheme = urlScheme_String(&d->url); 299 const iRangecc scheme = urlScheme_String(&d->url);
287 if (equalCase_Rangecc(scheme, "gemini")) { 300 if (equalCase_Rangecc(scheme, "gemini")) {
@@ -305,7 +318,7 @@ static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo
305 318
306static iBool isNormalized_GmDocument_(const iGmDocument *d) { 319static iBool isNormalized_GmDocument_(const iGmDocument *d) {
307 const iPrefs *prefs = prefs_App(); 320 const iPrefs *prefs = prefs_App();
308 if (d->format == plainText_GmDocumentFormat) { 321 if (d->format == plainText_SourceFormat) {
309 return iTrue; /* tabs are always normalized in plain text */ 322 return iTrue; /* tabs are always normalized in plain text */
310 } 323 }
311 if (startsWithCase_String(&d->url, "gemini:") && prefs->monospaceGemini) { 324 if (startsWithCase_String(&d->url, "gemini:") && prefs->monospaceGemini) {
@@ -357,6 +370,7 @@ static void updateOpenURLs_GmDocument_(iGmDocument *d) {
357static void doLayout_GmDocument_(iGmDocument *d) { 370static void doLayout_GmDocument_(iGmDocument *d) {
358 const iPrefs *prefs = prefs_App(); 371 const iPrefs *prefs = prefs_App();
359 const iBool isMono = isForcedMonospace_GmDocument_(d); 372 const iBool isMono = isForcedMonospace_GmDocument_(d);
373 const iBool isGopher = isGopher_GmDocument_(d);
360 const iBool isNarrow = d->size.x < 90 * gap_Text; 374 const iBool isNarrow = d->size.x < 90 * gap_Text;
361 const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; 375 const iBool isVeryNarrow = d->size.x <= 70 * gap_Text;
362 const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; 376 const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text;
@@ -434,12 +448,15 @@ static void doLayout_GmDocument_(iGmDocument *d) {
434 enum iGmLineType prevType = text_GmLineType; 448 enum iGmLineType prevType = text_GmLineType;
435 enum iGmLineType prevNonBlankType = text_GmLineType; 449 enum iGmLineType prevNonBlankType = text_GmLineType;
436 iBool followsBlank = iFalse; 450 iBool followsBlank = iFalse;
437 if (d->format == plainText_GmDocumentFormat) { 451 if (d->format == plainText_SourceFormat) {
438 isPreformat = iTrue; 452 isPreformat = iTrue;
439 isFirstText = iFalse; 453 isFirstText = iFalse;
440 } 454 }
441 while (nextSplit_Rangecc(content, "\n", &contentLine)) { 455 while (nextSplit_Rangecc(content, "\n", &contentLine)) {
442 iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */ 456 iRangecc line = contentLine; /* `line` will be trimmed; modifying would confuse `nextSplit_Rangecc` */
457 if (*line.end == '\r') {
458 line.end--; /* trim CR always */
459 }
443 iGmRun run = { .color = white_ColorId }; 460 iGmRun run = { .color = white_ColorId };
444 enum iGmLineType type; 461 enum iGmLineType type;
445 float indent = 0.0f; 462 float indent = 0.0f;
@@ -472,7 +489,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
472 meta.flags = constValue_Array(oldPreMeta, preIndex, iGmPreMeta).flags & 489 meta.flags = constValue_Array(oldPreMeta, preIndex, iGmPreMeta).flags &
473 folded_GmPreMetaFlag; 490 folded_GmPreMetaFlag;
474 } 491 }
475 else if (prefs->collapsePreOnLoad) { 492 else if (prefs->collapsePreOnLoad && !isGopher) {
476 meta.flags |= folded_GmPreMetaFlag; 493 meta.flags |= folded_GmPreMetaFlag;
477 } 494 }
478 pushBack_Array(&d->preMeta, &meta); 495 pushBack_Array(&d->preMeta, &meta);
@@ -500,14 +517,14 @@ static void doLayout_GmDocument_(iGmDocument *d) {
500 if (contentLine.start == content.start) { 517 if (contentLine.start == content.start) {
501 prevType = type; 518 prevType = type;
502 } 519 }
503 if (d->format == gemini_GmDocumentFormat && 520 if (d->format == gemini_SourceFormat &&
504 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { 521 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) {
505 isPreformat = iFalse; 522 isPreformat = iFalse;
506 addSiteBanner = iFalse; /* overrides the banner */ 523 addSiteBanner = iFalse; /* overrides the banner */
507 continue; 524 continue;
508 } 525 }
509 run.preId = preId; 526 run.preId = preId;
510 run.font = (d->format == plainText_GmDocumentFormat ? regularMonospace_FontId : preFont); 527 run.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont);
511 indent = indents[type]; 528 indent = indents[type];
512 } 529 }
513 if (addSiteBanner) { 530 if (addSiteBanner) {
@@ -580,7 +597,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
580 } 597 }
581 } 598 }
582 /* Folded blocks are represented by a single run with the alt text. */ 599 /* Folded blocks are represented by a single run with the alt text. */
583 if (isPreformat && d->format != plainText_GmDocumentFormat) { 600 if (isPreformat && d->format != plainText_SourceFormat) {
584 const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); 601 const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1);
585 if (meta->flags & folded_GmPreMetaFlag) { 602 if (meta->flags & folded_GmPreMetaFlag) {
586 const iBool isBlank = isEmpty_Range(&meta->altText); 603 const iBool isBlank = isEmpty_Range(&meta->altText);
@@ -676,7 +693,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
676 pushBack_Array(&d->layout, &icon); 693 pushBack_Array(&d->layout, &icon);
677 } 694 }
678 run.color = colors[type]; 695 run.color = colors[type];
679 if (d->format == plainText_GmDocumentFormat) { 696 if (d->format == plainText_SourceFormat) {
680 run.color = colors[text_GmLineType]; 697 run.color = colors[text_GmLineType];
681 } 698 }
682 /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ 699 /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */
@@ -705,8 +722,8 @@ static void doLayout_GmDocument_(iGmDocument *d) {
705 type == quote_GmLineType ? 4 : 0); 722 type == quote_GmLineType ? 4 : 0);
706 } 723 }
707 const iBool isWordWrapped = 724 const iBool isWordWrapped =
708 (d->format == plainText_GmDocumentFormat ? prefs->plainTextWrap : !isPreformat); 725 (d->format == plainText_SourceFormat ? prefs->plainTextWrap : !isPreformat);
709 if (isPreformat && d->format != plainText_GmDocumentFormat) { 726 if (isPreformat && d->format != plainText_SourceFormat) {
710 /* Remember the top left coordinates of the block (first line of block). */ 727 /* Remember the top left coordinates of the block (first line of block). */
711 iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); 728 iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1);
712 if (~meta->flags & topLeft_GmPreMetaFlag) { 729 if (~meta->flags & topLeft_GmPreMetaFlag) {
@@ -859,10 +876,13 @@ static void doLayout_GmDocument_(iGmDocument *d) {
859 } 876 }
860 } 877 }
861 } 878 }
879 printf("[GmDocument] layout size: %zu runs (%zu bytes)\n",
880 size_Array(&d->layout), size_Array(&d->layout) * sizeof(iGmRun));
862} 881}
863 882
864void init_GmDocument(iGmDocument *d) { 883void init_GmDocument(iGmDocument *d) {
865 d->format = gemini_GmDocumentFormat; 884 d->format = gemini_SourceFormat;
885 init_String(&d->unormSource);
866 init_String(&d->source); 886 init_String(&d->source);
867 init_String(&d->url); 887 init_String(&d->url);
868 init_String(&d->localHost); 888 init_String(&d->localHost);
@@ -878,6 +898,8 @@ void init_GmDocument(iGmDocument *d) {
878 d->siteIcon = 0; 898 d->siteIcon = 0;
879 d->media = new_Media(); 899 d->media = new_Media();
880 d->openURLs = NULL; 900 d->openURLs = NULL;
901 d->isPaletteValid = iFalse;
902 iZap(d->palette);
881} 903}
882 904
883void deinit_GmDocument(iGmDocument *d) { 905void deinit_GmDocument(iGmDocument *d) {
@@ -893,6 +915,7 @@ void deinit_GmDocument(iGmDocument *d) {
893 deinit_String(&d->localHost); 915 deinit_String(&d->localHost);
894 deinit_String(&d->url); 916 deinit_String(&d->url);
895 deinit_String(&d->source); 917 deinit_String(&d->source);
918 deinit_String(&d->unormSource);
896} 919}
897 920
898iMedia *media_GmDocument(iGmDocument *d) { 921iMedia *media_GmDocument(iGmDocument *d) {
@@ -903,6 +926,11 @@ const iMedia *constMedia_GmDocument(const iGmDocument *d) {
903 return d->media; 926 return d->media;
904} 927}
905 928
929const iString *url_GmDocument(const iGmDocument *d) {
930 return &d->url;
931}
932
933#if 0
906void reset_GmDocument(iGmDocument *d) { 934void reset_GmDocument(iGmDocument *d) {
907 clear_Media(d->media); 935 clear_Media(d->media);
908 clearLinks_GmDocument_(d); 936 clearLinks_GmDocument_(d);
@@ -911,8 +939,11 @@ void reset_GmDocument(iGmDocument *d) {
911 clear_Array(&d->preMeta); 939 clear_Array(&d->preMeta);
912 clear_String(&d->url); 940 clear_String(&d->url);
913 clear_String(&d->localHost); 941 clear_String(&d->localHost);
942 clear_String(&d->source);
943 clear_String(&d->unormSource);
914 d->themeSeed = 0; 944 d->themeSeed = 0;
915} 945}
946#endif
916 947
917static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { 948static void setDerivedThemeColors_(enum iGmDocumentTheme theme) {
918 set_Color(tmQuoteIcon_ColorId, 949 set_Color(tmQuoteIcon_ColorId,
@@ -1385,9 +1416,23 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
1385 } 1416 }
1386 printf("---\n"); 1417 printf("---\n");
1387#endif 1418#endif
1419 /* Color functions operate on the global palette for convenience, but we may need to switch
1420 palettes on the fly if more than one GmDocument is being displayed simultaneously. */
1421 memcpy(d->palette, get_Root()->tmPalette, sizeof(d->palette));
1422 d->isPaletteValid = iTrue;
1423}
1424
1425void makePaletteGlobal_GmDocument(const iGmDocument *d) {
1426 if (d->isPaletteValid) {
1427 memcpy(get_Root()->tmPalette, d->palette, sizeof(d->palette));
1428 }
1388} 1429}
1389 1430
1390void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { 1431void invalidatePalette_GmDocument(iGmDocument *d) {
1432 d->isPaletteValid = iFalse;
1433}
1434
1435void setFormat_GmDocument(iGmDocument *d, enum iSourceFormat format) {
1391 d->format = format; 1436 d->format = format;
1392} 1437}
1393 1438
@@ -1413,6 +1458,9 @@ iBool updateOpenURLs_GmDocument(iGmDocument *d) {
1413 const iBool isOpen = contains_StringSet(d->openURLs, &link->url); 1458 const iBool isOpen = contains_StringSet(d->openURLs, &link->url);
1414 if (isOpen ^ ((link->flags & isOpen_GmLinkFlag) != 0)) { 1459 if (isOpen ^ ((link->flags & isOpen_GmLinkFlag) != 0)) {
1415 iChangeFlags(link->flags, isOpen_GmLinkFlag, isOpen); 1460 iChangeFlags(link->flags, isOpen_GmLinkFlag, isOpen);
1461 if (isOpen) {
1462 link->flags |= visited_GmLinkFlag;
1463 }
1416 wasChanged = iTrue; 1464 wasChanged = iTrue;
1417 } 1465 }
1418 } 1466 }
@@ -1429,10 +1477,12 @@ static void normalize_GmDocument(iGmDocument *d) {
1429 iRangecc src = range_String(&d->source); 1477 iRangecc src = range_String(&d->source);
1430 iRangecc line = iNullRange; 1478 iRangecc line = iNullRange;
1431 iBool isPreformat = iFalse; 1479 iBool isPreformat = iFalse;
1432 if (d->format == plainText_GmDocumentFormat) { 1480 if (d->format == plainText_SourceFormat) {
1433 isPreformat = iTrue; /* Cannot be turned off. */ 1481 isPreformat = iTrue; /* Cannot be turned off. */
1434 } 1482 }
1435 const int preTabWidth = 4; /* TODO: user-configurable parameter */ 1483 const int preTabWidth = 4; /* TODO: user-configurable parameter */
1484 iBool wasNormalized = iFalse;
1485 iBool hasTabs = iFalse;
1436 while (nextSplit_Rangecc(src, "\n", &line)) { 1486 while (nextSplit_Rangecc(src, "\n", &line)) {
1437 if (isPreformat) { 1487 if (isPreformat) {
1438 /* Replace any tab characters with spaces for visualization. */ 1488 /* Replace any tab characters with spaces for visualization. */
@@ -1443,13 +1493,19 @@ static void normalize_GmDocument(iGmDocument *d) {
1443 while (numSpaces-- > 0) { 1493 while (numSpaces-- > 0) {
1444 appendCStrN_String(normalized, " ", 1); 1494 appendCStrN_String(normalized, " ", 1);
1445 } 1495 }
1496 hasTabs = iTrue;
1497 wasNormalized = iTrue;
1446 } 1498 }
1447 else if (*ch != '\r') { 1499 else if (*ch != '\v') {
1448 appendCStrN_String(normalized, ch, 1); 1500 appendCStrN_String(normalized, ch, 1);
1449 } 1501 }
1502 else {
1503 hasTabs = iTrue;
1504 wasNormalized = iTrue;
1505 }
1450 } 1506 }
1451 appendCStr_String(normalized, "\n"); 1507 appendCStr_String(normalized, "\n");
1452 if (d->format == gemini_GmDocumentFormat && 1508 if (d->format == gemini_SourceFormat &&
1453 lineType_GmDocument_(d, line) == preformatted_GmLineType) { 1509 lineType_GmDocument_(d, line) == preformatted_GmLineType) {
1454 isPreformat = iFalse; 1510 isPreformat = iFalse;
1455 } 1511 }
@@ -1465,7 +1521,10 @@ static void normalize_GmDocument(iGmDocument *d) {
1465 int spaceCount = 0; 1521 int spaceCount = 0;
1466 for (const char *ch = line.start; ch != line.end; ch++) { 1522 for (const char *ch = line.start; ch != line.end; ch++) {
1467 char c = *ch; 1523 char c = *ch;
1468 if (c == '\r') continue; 1524 if (c == '\v') {
1525 wasNormalized = iTrue;
1526 continue;
1527 }
1469 if (isNormalizableSpace_(c)) { 1528 if (isNormalizableSpace_(c)) {
1470 if (isPrevSpace) { 1529 if (isPrevSpace) {
1471 if (++spaceCount == 8) { 1530 if (++spaceCount == 8) {
@@ -1474,9 +1533,13 @@ static void normalize_GmDocument(iGmDocument *d) {
1474 popBack_Block(&normalized->chars); 1533 popBack_Block(&normalized->chars);
1475 pushBack_Block(&normalized->chars, '\t'); 1534 pushBack_Block(&normalized->chars, '\t');
1476 } 1535 }
1536 wasNormalized = iTrue;
1477 continue; /* skip repeated spaces */ 1537 continue; /* skip repeated spaces */
1478 } 1538 }
1479 c = ' '; 1539 if (c != ' ') {
1540 c = ' ';
1541 wasNormalized = iTrue;
1542 }
1480 isPrevSpace = iTrue; 1543 isPrevSpace = iTrue;
1481 } 1544 }
1482 else { 1545 else {
@@ -1487,8 +1550,14 @@ static void normalize_GmDocument(iGmDocument *d) {
1487 } 1550 }
1488 appendCStr_String(normalized, "\n"); 1551 appendCStr_String(normalized, "\n");
1489 } 1552 }
1553 printf("hasTabs: %d\n", hasTabs);
1554 printf("wasNormalized: %d\n", wasNormalized);
1555 fflush(stdout);
1490 set_String(&d->source, collect_String(normalized)); 1556 set_String(&d->source, collect_String(normalized));
1491 normalize_String(&d->source); /* NFC */ 1557 normalize_String(&d->source); /* NFC */
1558 printf("orig:%zu norm:%zu\n", size_String(&d->unormSource), size_String(&d->source));
1559 /* normalized source has an extra newline at the end */
1560// iAssert(wasNormalized || equal_String(&d->unormSource, &d->source));
1492} 1561}
1493 1562
1494void setUrl_GmDocument(iGmDocument *d, const iString *url) { 1563void setUrl_GmDocument(iGmDocument *d, const iString *url) {
@@ -1499,8 +1568,18 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) {
1499 updateIconBasedOnUrl_GmDocument_(d); 1568 updateIconBasedOnUrl_GmDocument_(d);
1500} 1569}
1501 1570
1502void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { 1571void setSource_GmDocument(iGmDocument *d, const iString *source, int width,
1503 set_String(&d->source, source); 1572 enum iGmDocumentUpdate updateType) {
1573 printf("[GmDocument] source update (%zu bytes), width:%d, final:%d\n",
1574 size_String(source), width, updateType == final_GmDocumentUpdate);
1575 if (size_String(source) == size_String(&d->unormSource)) {
1576 iAssert(equal_String(source, &d->unormSource));
1577 printf("[GmDocument] source is unchanged!\n");
1578 return; /* Nothing to do. */
1579 }
1580 set_String(&d->unormSource, source);
1581 /* Normalize. */
1582 set_String(&d->source, &d->unormSource);
1504 if (isNormalized_GmDocument_(d)) { 1583 if (isNormalized_GmDocument_(d)) {
1505 normalize_GmDocument(d); 1584 normalize_GmDocument(d);
1506 } 1585 }
@@ -1602,6 +1681,14 @@ const iString *source_GmDocument(const iGmDocument *d) {
1602 return &d->source; 1681 return &d->source;
1603} 1682}
1604 1683
1684size_t memorySize_GmDocument(const iGmDocument *d) {
1685 return size_String(&d->unormSource) +
1686 size_String(&d->source) +
1687 size_Array(&d->layout) * sizeof(iGmRun) +
1688 size_Array(&d->links) * sizeof(iGmLink) +
1689 memorySize_Media(d->media);
1690}
1691
1605iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { 1692iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) {
1606 const char * src = constBegin_String(&d->source); 1693 const char * src = constBegin_String(&d->source);
1607 const size_t startPos = (start ? start - src : 0); 1694 const size_t startPos = (start ? start - src : 0);