diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-28 22:04:24 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-28 22:04:24 +0300 |
commit | de3463f5d9ac3a8b7c0f3e23ff11ade2b19fdb68 (patch) | |
tree | 195d147125afb9db3a4b9775849aea30017dd876 /src/gmdocument.c | |
parent | 3a881ec009c9c8b6030d2f9bdf404423108e3019 (diff) |
Folding preformatted blocks and showing alt text
The alt text of preformatted blocks is shown on mouse hover.
The blocks can be clicked to toggle folding.
IssueID #180
Diffstat (limited to 'src/gmdocument.c')
-rw-r--r-- | src/gmdocument.c | 137 |
1 files changed, 91 insertions, 46 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index f1471f0f..874a117e 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #include "gmdocument.h" | 23 | #include "gmdocument.h" |
24 | #include "gmutil.h" | 24 | #include "gmutil.h" |
25 | #include "lang.h" | ||
25 | #include "ui/color.h" | 26 | #include "ui/color.h" |
26 | #include "ui/text.h" | 27 | #include "ui/text.h" |
27 | #include "ui/metrics.h" | 28 | #include "ui/metrics.h" |
@@ -82,6 +83,7 @@ struct Impl_GmDocument { | |||
82 | iString bannerText; | 83 | iString bannerText; |
83 | iString title; /* the first top-level title */ | 84 | iString title; /* the first top-level title */ |
84 | iArray headings; | 85 | iArray headings; |
86 | iArray preMeta; /* metadata about preformatted blocks */ | ||
85 | uint32_t themeSeed; | 87 | uint32_t themeSeed; |
86 | iChar siteIcon; | 88 | iChar siteIcon; |
87 | iMedia * media; | 89 | iMedia * media; |
@@ -143,19 +145,21 @@ static int lastVisibleRunBottom_GmDocument_(const iGmDocument *d) { | |||
143 | return 0; | 145 | return 0; |
144 | } | 146 | } |
145 | 147 | ||
146 | static iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font) { | 148 | static iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font, |
149 | iRangecc *contents, const char **endPos) { | ||
147 | const iRangecc content = { start, constEnd_String(&d->source) }; | 150 | const iRangecc content = { start, constEnd_String(&d->source) }; |
148 | iRangecc line = iNullRange; | 151 | iRangecc line = iNullRange; |
149 | nextSplit_Rangecc(content, "\n", &line); | 152 | nextSplit_Rangecc(content, "\n", &line); |
150 | iAssert(startsWith_Rangecc(line, "```")); | 153 | iAssert(startsWith_Rangecc(line, "```")); |
151 | iRangecc preBlock = { line.end + 1, line.end + 1 }; | 154 | *contents = (iRangecc){ line.end + 1, line.end + 1 }; |
152 | while (nextSplit_Rangecc(content, "\n", &line)) { | 155 | while (nextSplit_Rangecc(content, "\n", &line)) { |
153 | if (startsWith_Rangecc(line, "```")) { | 156 | if (startsWith_Rangecc(line, "```")) { |
157 | if (endPos) *endPos = line.end; | ||
154 | break; | 158 | break; |
155 | } | 159 | } |
156 | preBlock.end = line.end; | 160 | contents->end = line.end; |
157 | } | 161 | } |
158 | return measureRange_Text(font, preBlock); | 162 | return measureRange_Text(font, *contents); |
159 | } | 163 | } |
160 | 164 | ||
161 | static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) { | 165 | static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) { |
@@ -279,8 +283,8 @@ static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) { | |||
279 | return iFalse; | 283 | return iFalse; |
280 | } | 284 | } |
281 | 285 | ||
282 | static void linkContentLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo, | 286 | static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo, |
283 | uint16_t linkId) { | 287 | uint16_t linkId) { |
284 | iGmLink *link = at_PtrArray(&d->links, linkId - 1); | 288 | iGmLink *link = at_PtrArray(&d->links, linkId - 1); |
285 | link->flags |= content_GmLinkFlag; | 289 | link->flags |= content_GmLinkFlag; |
286 | if (mediaInfo && mediaInfo->isPermanent) { | 290 | if (mediaInfo && mediaInfo->isPermanent) { |
@@ -370,6 +374,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
370 | clear_Array(&d->layout); | 374 | clear_Array(&d->layout); |
371 | clearLinks_GmDocument_(d); | 375 | clearLinks_GmDocument_(d); |
372 | clear_Array(&d->headings); | 376 | clear_Array(&d->headings); |
377 | const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ | ||
378 | clear_Array(&d->preMeta); | ||
373 | clear_String(&d->title); | 379 | clear_String(&d->title); |
374 | clear_String(&d->bannerText); | 380 | clear_String(&d->bannerText); |
375 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { | 381 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { |
@@ -381,7 +387,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
381 | iBool isFirstText = prefs->bigFirstParagraph; | 387 | iBool isFirstText = prefs->bigFirstParagraph; |
382 | iBool addQuoteIcon = prefs->quoteIcon; | 388 | iBool addQuoteIcon = prefs->quoteIcon; |
383 | iBool isPreformat = iFalse; | 389 | iBool isPreformat = iFalse; |
384 | iRangecc preAltText = iNullRange; /* TODO: alt text is being ignored */ | ||
385 | int preFont = preformatted_FontId; | 390 | int preFont = preformatted_FontId; |
386 | uint16_t preId = 0; | 391 | uint16_t preId = 0; |
387 | iBool enableIndents = iFalse; | 392 | iBool enableIndents = iFalse; |
@@ -408,17 +413,26 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
408 | } | 413 | } |
409 | indent = indents[type]; | 414 | indent = indents[type]; |
410 | if (type == preformatted_GmLineType) { | 415 | if (type == preformatted_GmLineType) { |
416 | /* Begin a new preformatted block. */ | ||
411 | isPreformat = iTrue; | 417 | isPreformat = iTrue; |
412 | preId++; | 418 | const size_t preIndex = preId++; |
413 | preFont = preformatted_FontId; | 419 | preFont = preformatted_FontId; |
414 | /* Use a smaller font if the block contents are wide. */ | 420 | /* Use a smaller font if the block contents are wide. */ |
415 | if (measurePreformattedBlock_GmDocument_(d, line.start, preFont).x > | 421 | iGmPreMeta meta = { .bounds = line }; |
422 | meta.pixelRect.size = measurePreformattedBlock_GmDocument_( | ||
423 | d, line.start, preFont, &meta.contents, &meta.bounds.end); | ||
424 | if (meta.pixelRect.size.x > | ||
416 | d->size.x /*- indents[preformatted_GmLineType] * gap_Text*/) { | 425 | d->size.x /*- indents[preformatted_GmLineType] * gap_Text*/) { |
417 | preFont = preformattedSmall_FontId; | 426 | preFont = preformattedSmall_FontId; |
418 | } | 427 | } |
419 | trimLine_Rangecc(&line, type, isNormalized); | 428 | trimLine_Rangecc(&line, type, isNormalized); |
420 | preAltText = line; | 429 | meta.altText = line; /* without the ``` */ |
421 | /* TODO: store and link the alt text to this run */ | 430 | /* Reuse previous state. */ |
431 | if (preIndex < size_Array(oldPreMeta)) { | ||
432 | meta.flags = constValue_Array(oldPreMeta, preIndex, iGmPreMeta).flags & | ||
433 | folded_GmPreMetaFlag; | ||
434 | } | ||
435 | pushBack_Array(&d->preMeta, &meta); | ||
422 | continue; | 436 | continue; |
423 | } | 437 | } |
424 | else if (type == link_GmLineType) { | 438 | else if (type == link_GmLineType) { |
@@ -446,7 +460,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
446 | if (d->format == gemini_GmDocumentFormat && | 460 | if (d->format == gemini_GmDocumentFormat && |
447 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { | 461 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { |
448 | isPreformat = iFalse; | 462 | isPreformat = iFalse; |
449 | preAltText = iNullRange; | ||
450 | addSiteBanner = iFalse; /* overrides the banner */ | 463 | addSiteBanner = iFalse; /* overrides the banner */ |
451 | continue; | 464 | continue; |
452 | } | 465 | } |
@@ -523,6 +536,30 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
523 | pos.y += required - delta; | 536 | pos.y += required - delta; |
524 | } | 537 | } |
525 | } | 538 | } |
539 | /* Folded blocks are represented by a single run with the alt text. */ | ||
540 | if (isPreformat) { | ||
541 | const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); | ||
542 | if (meta->flags & folded_GmPreMetaFlag) { | ||
543 | const iBool isBlank = isEmpty_Range(&meta->altText); | ||
544 | iGmRun altText = { .font = paragraph_FontId, | ||
545 | .flags = (isBlank ? decoration_GmRunFlag : 0) | altText_GmRunFlag }; | ||
546 | const iInt2 margin = preRunMargin_GmDocument(d, 0); | ||
547 | altText.color = tmQuote_ColorId; | ||
548 | altText.text = isBlank ? range_Lang(range_CStr("doc.pre.nocaption")) | ||
549 | : meta->altText; | ||
550 | iInt2 size = advanceWrapRange_Text(altText.font, d->size.x - 2 * margin.x, | ||
551 | altText.text); | ||
552 | altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, | ||
553 | size.y + 2 * margin.y); | ||
554 | altText.preId = preId; | ||
555 | pushBack_Array(&d->layout, &altText); | ||
556 | pos.y += height_Rect(altText.bounds); | ||
557 | contentLine = meta->bounds; /* Skip the whole thing. */ | ||
558 | isPreformat = iFalse; | ||
559 | prevType = preformatted_GmLineType; | ||
560 | continue; | ||
561 | } | ||
562 | } | ||
526 | /* Save the document title (first high-level heading). */ | 563 | /* Save the document title (first high-level heading). */ |
527 | if ((type == heading1_GmLineType || type == heading2_GmLineType) && | 564 | if ((type == heading1_GmLineType || type == heading2_GmLineType) && |
528 | isEmpty_String(&d->title)) { | 565 | isEmpty_String(&d->title)) { |
@@ -632,14 +669,27 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
632 | type == quote_GmLineType ? 4 : 0); | 669 | type == quote_GmLineType ? 4 : 0); |
633 | const iBool isWordWrapped = | 670 | const iBool isWordWrapped = |
634 | (d->format == plainText_GmDocumentFormat ? prefs->plainTextWrap : !isPreformat); | 671 | (d->format == plainText_GmDocumentFormat ? prefs->plainTextWrap : !isPreformat); |
672 | if (isPreformat) { | ||
673 | /* Remember the top left coordinates of the block (first line of block). */ | ||
674 | iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); | ||
675 | if (~meta->flags & topLeft_GmPreMetaFlag) { | ||
676 | meta->pixelRect.pos = pos; //, indent * gap_Text); | ||
677 | meta->flags |= topLeft_GmPreMetaFlag; | ||
678 | } | ||
679 | /* Collapse indentation if too wide. */ | ||
680 | if (width_Rect(meta->pixelRect) > d->size.x - (indent + rightMargin) * gap_Text) { | ||
681 | indent = 0; | ||
682 | } | ||
683 | } | ||
635 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ | 684 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ |
636 | while (!isEmpty_Range(&runLine)) { | 685 | while (!isEmpty_Range(&runLine)) { |
637 | run.bounds.pos = addX_I2(pos, indent * gap_Text); | 686 | run.bounds.pos = addX_I2(pos, indent * gap_Text); |
638 | const int avail = isWordWrapped ? d->size.x - run.bounds.pos.x - rightMargin * gap_Text : 0; | 687 | const int wrapAvail = d->size.x - run.bounds.pos.x - rightMargin * gap_Text; |
688 | const int avail = isWordWrapped ? wrapAvail : 0; | ||
639 | const char *contPos; | 689 | const char *contPos; |
640 | const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); | 690 | const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); |
641 | iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x)); | 691 | iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x)); |
642 | run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */ | 692 | run.bounds.size.x = iMax(wrapAvail, dims.x); /* Extends to the right edge for selection. */ |
643 | run.bounds.size.y = dims.y; | 693 | run.bounds.size.y = dims.y; |
644 | run.visBounds = run.bounds; | 694 | run.visBounds = run.bounds; |
645 | run.visBounds.size.x = dims.x; | 695 | run.visBounds.size.x = dims.x; |
@@ -671,7 +721,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
671 | iGmMediaInfo img; | 721 | iGmMediaInfo img; |
672 | imageInfo_Media(d->media, imageId, &img); | 722 | imageInfo_Media(d->media, imageId, &img); |
673 | const iInt2 imgSize = imageSize_Media(d->media, imageId); | 723 | const iInt2 imgSize = imageSize_Media(d->media, imageId); |
674 | linkContentLaidOut_GmDocument_(d, &img, run.linkId); | 724 | linkContentWasLaidOut_GmDocument_(d, &img, run.linkId); |
675 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | 725 | const int margin = lineHeight_Text(paragraph_FontId) / 2; |
676 | pos.y += margin; | 726 | pos.y += margin; |
677 | run.bounds.pos = pos; | 727 | run.bounds.pos = pos; |
@@ -699,7 +749,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
699 | else if (audioId) { | 749 | else if (audioId) { |
700 | iGmMediaInfo info; | 750 | iGmMediaInfo info; |
701 | audioInfo_Media(d->media, audioId, &info); | 751 | audioInfo_Media(d->media, audioId, &info); |
702 | linkContentLaidOut_GmDocument_(d, &info, run.linkId); | 752 | linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); |
703 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | 753 | const int margin = lineHeight_Text(paragraph_FontId) / 2; |
704 | pos.y += margin; | 754 | pos.y += margin; |
705 | run.bounds.pos = pos; | 755 | run.bounds.pos = pos; |
@@ -716,7 +766,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
716 | else if (downloadId) { | 766 | else if (downloadId) { |
717 | iGmMediaInfo info; | 767 | iGmMediaInfo info; |
718 | downloadInfo_Media(d->media, downloadId, &info); | 768 | downloadInfo_Media(d->media, downloadId, &info); |
719 | linkContentLaidOut_GmDocument_(d, &info, run.linkId); | 769 | linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); |
720 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | 770 | const int margin = lineHeight_Text(paragraph_FontId) / 2; |
721 | pos.y += margin; | 771 | pos.y += margin; |
722 | run.bounds.pos = pos; | 772 | run.bounds.pos = pos; |
@@ -773,6 +823,7 @@ void init_GmDocument(iGmDocument *d) { | |||
773 | init_String(&d->bannerText); | 823 | init_String(&d->bannerText); |
774 | init_String(&d->title); | 824 | init_String(&d->title); |
775 | init_Array(&d->headings, sizeof(iGmHeading)); | 825 | init_Array(&d->headings, sizeof(iGmHeading)); |
826 | init_Array(&d->preMeta, sizeof(iGmPreMeta)); | ||
776 | d->themeSeed = 0; | 827 | d->themeSeed = 0; |
777 | d->siteIcon = 0; | 828 | d->siteIcon = 0; |
778 | d->media = new_Media(); | 829 | d->media = new_Media(); |
@@ -784,6 +835,7 @@ void deinit_GmDocument(iGmDocument *d) { | |||
784 | deinit_String(&d->title); | 835 | deinit_String(&d->title); |
785 | clearLinks_GmDocument_(d); | 836 | clearLinks_GmDocument_(d); |
786 | deinit_PtrArray(&d->links); | 837 | deinit_PtrArray(&d->links); |
838 | deinit_Array(&d->preMeta); | ||
787 | deinit_Array(&d->headings); | 839 | deinit_Array(&d->headings); |
788 | deinit_Array(&d->layout); | 840 | deinit_Array(&d->layout); |
789 | deinit_String(&d->localHost); | 841 | deinit_String(&d->localHost); |
@@ -804,6 +856,7 @@ void reset_GmDocument(iGmDocument *d) { | |||
804 | clearLinks_GmDocument_(d); | 856 | clearLinks_GmDocument_(d); |
805 | clear_Array(&d->layout); | 857 | clear_Array(&d->layout); |
806 | clear_Array(&d->headings); | 858 | clear_Array(&d->headings); |
859 | clear_Array(&d->preMeta); | ||
807 | clear_String(&d->url); | 860 | clear_String(&d->url); |
808 | clear_String(&d->localHost); | 861 | clear_String(&d->localHost); |
809 | d->themeSeed = 0; | 862 | d->themeSeed = 0; |
@@ -815,6 +868,8 @@ static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | |||
815 | set_Color(tmBannerSideTitle_ColorId, | 868 | set_Color(tmBannerSideTitle_ColorId, |
816 | mix_Color(get_Color(tmBannerTitle_ColorId), get_Color(tmBackground_ColorId), | 869 | mix_Color(get_Color(tmBannerTitle_ColorId), get_Color(tmBackground_ColorId), |
817 | theme == colorfulDark_GmDocumentTheme ? 0.55f : 0)); | 870 | theme == colorfulDark_GmDocumentTheme ? 0.55f : 0)); |
871 | set_Color(tmAltTextBackground_ColorId, mix_Color(get_Color(tmQuoteIcon_ColorId), | ||
872 | get_Color(tmBackground_ColorId), 0.85f)); | ||
818 | if (theme == colorfulDark_GmDocumentTheme) { | 873 | if (theme == colorfulDark_GmDocumentTheme) { |
819 | /* Ensure paragraph text and link text aren't too similarly colored. */ | 874 | /* Ensure paragraph text and link text aren't too similarly colored. */ |
820 | if (delta_Color(get_Color(tmLinkText_ColorId), get_Color(tmParagraph_ColorId)) < 100) { | 875 | if (delta_Color(get_Color(tmLinkText_ColorId), get_Color(tmParagraph_ColorId)) < 100) { |
@@ -824,35 +879,6 @@ static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | |||
824 | } | 879 | } |
825 | set_Color(tmLinkCustomIconVisited_ColorId, | 880 | set_Color(tmLinkCustomIconVisited_ColorId, |
826 | mix_Color(get_Color(tmLinkIconVisited_ColorId), get_Color(tmLinkIcon_ColorId), 0.5f)); | 881 | mix_Color(get_Color(tmLinkIconVisited_ColorId), get_Color(tmLinkIcon_ColorId), 0.5f)); |
827 | #if 0 | ||
828 | set_Color(tmOutlineHeadingAbove_ColorId, get_Color(white_ColorId)); | ||
829 | set_Color(tmOutlineHeadingBelow_ColorId, get_Color(black_ColorId)); | ||
830 | switch (theme) { | ||
831 | case colorfulDark_GmDocumentTheme: | ||
832 | set_Color(tmOutlineHeadingBelow_ColorId, get_Color(tmBannerTitle_ColorId)); | ||
833 | if (equal_Color(get_Color(tmOutlineHeadingAbove_ColorId), | ||
834 | get_Color(tmOutlineHeadingBelow_ColorId))) { | ||
835 | set_Color(tmOutlineHeadingBelow_ColorId, get_Color(tmHeading3_ColorId)); | ||
836 | } | ||
837 | break; | ||
838 | case colorfulLight_GmDocumentTheme: | ||
839 | case sepia_GmDocumentTheme: | ||
840 | set_Color(tmOutlineHeadingAbove_ColorId, get_Color(black_ColorId)); | ||
841 | set_Color(tmOutlineHeadingBelow_ColorId, mix_Color(get_Color(tmBackground_ColorId), get_Color(black_ColorId), 0.6f)); | ||
842 | break; | ||
843 | case gray_GmDocumentTheme: | ||
844 | set_Color(tmOutlineHeadingBelow_ColorId, get_Color(gray75_ColorId)); | ||
845 | break; | ||
846 | case white_GmDocumentTheme: | ||
847 | set_Color(tmOutlineHeadingBelow_ColorId, mix_Color(get_Color(tmBannerIcon_ColorId), get_Color(white_ColorId), 0.6f)); | ||
848 | break; | ||
849 | case highContrast_GmDocumentTheme: | ||
850 | set_Color(tmOutlineHeadingAbove_ColorId, get_Color(black_ColorId)); | ||
851 | break; | ||
852 | default: | ||
853 | break; | ||
854 | } | ||
855 | #endif | ||
856 | } | 882 | } |
857 | 883 | ||
858 | static void updateIconBasedOnUrl_GmDocument_(iGmDocument *d) { | 884 | static void updateIconBasedOnUrl_GmDocument_(iGmDocument *d) { |
@@ -1406,6 +1432,20 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | |||
1406 | setWidth_GmDocument(d, width); /* re-do layout */ | 1432 | setWidth_GmDocument(d, width); /* re-do layout */ |
1407 | } | 1433 | } |
1408 | 1434 | ||
1435 | void foldPre_GmDocument(iGmDocument *d, uint16_t preId) { | ||
1436 | if (preId > 0 && preId <= size_Array(&d->preMeta)) { | ||
1437 | iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); | ||
1438 | meta->flags ^= folded_GmPreMetaFlag; | ||
1439 | } | ||
1440 | } | ||
1441 | |||
1442 | const iGmPreMeta *preMeta_GmDocument(const iGmDocument *d, uint16_t preId) { | ||
1443 | if (preId > 0 && preId <= size_Array(&d->preMeta)) { | ||
1444 | return constAt_Array(&d->preMeta, preId - 1); | ||
1445 | } | ||
1446 | return NULL; | ||
1447 | } | ||
1448 | |||
1409 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, | 1449 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, |
1410 | void *context) { | 1450 | void *context) { |
1411 | iBool isInside = iFalse; | 1451 | iBool isInside = iFalse; |
@@ -1684,4 +1724,9 @@ iRangecc findLoc_GmRun(const iGmRun *d, iInt2 pos) { | |||
1684 | return loc; | 1724 | return loc; |
1685 | } | 1725 | } |
1686 | 1726 | ||
1727 | iInt2 preRunMargin_GmDocument(const iGmDocument *d, uint16_t preId) { | ||
1728 | iUnused(d, preId); | ||
1729 | return init_I2(3 * gap_Text, 2 * gap_Text); | ||
1730 | } | ||
1731 | |||
1687 | iDefineClass(GmDocument) | 1732 | iDefineClass(GmDocument) |