summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-12-18 11:53:55 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-12-18 11:53:55 +0200
commit96d5db2560e9acf9f6437285fab5165329a20dd1 (patch)
tree3eaf72d7ae1cf467b2bc44668b343fd870318b4f /src/ui/text.c
parentc8791b2a0537142a124347cabd95eac36bb59c70 (diff)
Text: Improved handling of tab stops
Text drawing can now be made aware of the available horizontal space.
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c202
1 files changed, 108 insertions, 94 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
index 96799c9e..6b42610f 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -631,33 +631,48 @@ iLocalDef iBool isMeasuring_(enum iRunMode mode) {
631 return (mode & modeMask_RunMode) == measure_RunMode; 631 return (mode & modeMask_RunMode) == measure_RunMode;
632} 632}
633 633
634static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLen, iInt2 pos, 634iDeclareType(RunArgs)
635 int xposLimit, const char **continueFrom_out, int *runAdvance_out) { 635
636 iRect bounds = zero_Rect(); 636struct Impl_RunArgs {
637 const iInt2 orig = pos; 637 enum iRunMode mode;
638 float xpos = pos.x; 638 iRangecc text;
639 float xposExtend = pos.x; /* allows wide glyphs to use more space; restored by whitespace */ 639 size_t maxLen; /* max characters to process */
640 float xposMax = xpos; 640 iInt2 pos;
641 float monoAdvance = 0; 641 int xposLimit; /* hard limit for wrapping */
642 iAssert(xposLimit == 0 || isMeasuring_(mode)); 642 int xposLayoutBound; /* visible bound for layout purposes; does not affect wrapping */
643 const char *lastWordEnd = text.start; 643 const char ** continueFrom_out;
644 if (continueFrom_out) { 644 int * runAdvance_out;
645 *continueFrom_out = text.end; 645};
646
647static iRect run_Font_(iFont *d, const iRunArgs *args) {
648 iRect bounds = zero_Rect();
649 const iInt2 orig = args->pos;
650 float xpos = orig.x;
651 float xposMax = xpos;
652 float monoAdvance = 0;
653 int ypos = orig.y;
654 size_t maxLen = args->maxLen ? args->maxLen : iInvalidSize;
655 float xposExtend = orig.x; /* allows wide glyphs to use more space; restored by whitespace */
656 const enum iRunMode mode = args->mode;
657 const char * lastWordEnd = args->text.start;
658 iAssert(args->xposLimit == 0 || isMeasuring_(mode));
659 if (args->continueFrom_out) {
660 *args->continueFrom_out = args->text.end;
646 } 661 }
647 iChar prevCh = 0; 662 iChar prevCh = 0;
648 const iBool isMonospaced = d->isMonospaced && !(mode & alwaysVariableWidthFlag_RunMode); 663 const iBool isMonospaced = d->isMonospaced && !(mode & alwaysVariableWidthFlag_RunMode);
649 if (isMonospaced) { 664 if (isMonospaced) {
650 monoAdvance = glyph_Font_(d, 'M')->advance; 665 monoAdvance = glyph_Font_(d, 'M')->advance;
651 } 666 }
652 for (const char *chPos = text.start; chPos != text.end; ) { 667 for (const char *chPos = args->text.start; chPos != args->text.end; ) {
653 iAssert(chPos < text.end); 668 iAssert(chPos < args->text.end);
654 const char *currentPos = chPos; 669 const char *currentPos = chPos;
655 if (*chPos == 0x1b) { 670 if (*chPos == 0x1b) {
656 /* ANSI escape. */ 671 /* ANSI escape. */
657 chPos++; 672 chPos++;
658 iRegExpMatch m; 673 iRegExpMatch m;
659 init_RegExpMatch(&m); 674 init_RegExpMatch(&m);
660 if (match_RegExp(text_.ansiEscape, chPos, text.end - chPos, &m)) { 675 if (match_RegExp(text_.ansiEscape, chPos, args->text.end - chPos, &m)) {
661 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { 676 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
662 /* Change the color. */ 677 /* Change the color. */
663 const iColor clr = 678 const iColor clr =
@@ -668,14 +683,14 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
668 continue; 683 continue;
669 } 684 }
670 } 685 }
671 iChar ch = nextChar_(&chPos, text.end); 686 iChar ch = nextChar_(&chPos, args->text.end);
672 iBool isEmoji = isEmoji_Char(ch); 687 iBool isEmoji = isEmoji_Char(ch);
673 if (ch == 0x200d) { /* zero-width joiner */ 688 if (ch == 0x200d) { /* zero-width joiner */
674 /* We don't have the composited Emojis. */ 689 /* We don't have the composited Emojis. */
675 if (isEmoji_Char(prevCh)) { 690 if (isEmoji_Char(prevCh)) {
676 /* skip */ 691 /* skip */
677 ch = nextChar_(&chPos, text.end); 692 ch = nextChar_(&chPos, args->text.end);
678 ch = nextChar_(&chPos, text.end); 693 ch = nextChar_(&chPos, args->text.end);
679 } 694 }
680 } 695 }
681#if 0 696#if 0
@@ -690,17 +705,17 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
690 } 705 }
691#endif 706#endif
692 if (isVariationSelector_Char(ch)) { 707 if (isVariationSelector_Char(ch)) {
693 ch = nextChar_(&chPos, text.end); /* skip it */ 708 ch = nextChar_(&chPos, args->text.end); /* skip it */
694 } 709 }
695 /* Special instructions. */ { 710 /* Special instructions. */ {
696 if (ch == 0xad) { /* soft hyphen */ 711 if (ch == 0xad) { /* soft hyphen */
697 lastWordEnd = chPos; 712 lastWordEnd = chPos;
698 if (isMeasuring_(mode)) { 713 if (isMeasuring_(mode)) {
699 if (xposLimit > 0) { 714 if (args->xposLimit > 0) {
700 const char *postHyphen = chPos; 715 const char *postHyphen = chPos;
701 iChar nextCh = nextChar_(&postHyphen, text.end); 716 iChar nextCh = nextChar_(&postHyphen, args->text.end);
702 if ((int) xpos + glyph_Font_(d, ch)->rect[0].size.x + 717 if ((int) xpos + glyph_Font_(d, ch)->rect[0].size.x +
703 glyph_Font_(d, nextCh)->rect[0].size.x > xposLimit) { 718 glyph_Font_(d, nextCh)->rect[0].size.x > args->xposLimit) {
704 /* Wraps after hyphen, should show it. */ 719 /* Wraps after hyphen, should show it. */
705 } 720 }
706 else continue; 721 else continue;
@@ -709,26 +724,37 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
709 } 724 }
710 else { 725 else {
711 /* Only show it at the end. */ 726 /* Only show it at the end. */
712 if (chPos != text.end) { 727 if (chPos != args->text.end) {
713 continue; 728 continue;
714 } 729 }
715 } 730 }
716 } 731 }
717 if (ch == '\n') { 732 if (ch == '\n') {
718 xpos = xposExtend = pos.x; 733 xpos = xposExtend = orig.x;
719 pos.y += d->height; 734 ypos += d->height;
720 prevCh = ch; 735 prevCh = ch;
721 continue; 736 continue;
722 } 737 }
723 if (ch == '\t') { 738 if (ch == '\t') {
724 const int tabStopWidth = d->height * 8; 739 const int tabStopWidth = d->height * 10;
725 xpos = pos.x + ((int) ((xpos - pos.x) / tabStopWidth) + 1) * tabStopWidth; 740 const int halfWidth = (iMax(args->xposLimit, args->xposLayoutBound) - orig.x) / 2;
741 const int xRel = xpos - orig.x;
742 /* First stop is always to half width. */
743 if (halfWidth > 0 && xRel < halfWidth) {
744 xpos = orig.x + halfWidth;
745 }
746 else if (halfWidth > 0 && xRel < halfWidth * 3 / 2) {
747 xpos = orig.x + halfWidth * 3 / 2;
748 }
749 else {
750 xpos = orig.x + ((xRel / tabStopWidth) + 1) * tabStopWidth;
751 }
726 xposExtend = iMax(xposExtend, xpos); 752 xposExtend = iMax(xposExtend, xpos);
727 prevCh = 0; 753 prevCh = 0;
728 continue; 754 continue;
729 } 755 }
730 if (ch == '\r') { 756 if (ch == '\r') {
731 const iChar esc = nextChar_(&chPos, text.end); 757 const iChar esc = nextChar_(&chPos, args->text.end);
732 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { 758 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
733 const iColor clr = get_Color(esc - asciiBase_ColorEscape); 759 const iColor clr = get_Color(esc - asciiBase_ColorEscape);
734 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); 760 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);
@@ -745,18 +771,18 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
745 const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; 771 const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0;
746 int x2 = x1 + glyph->rect[hoff].size.x; 772 int x2 = x1 + glyph->rect[hoff].size.x;
747 /* Out of the allotted space? */ 773 /* Out of the allotted space? */
748 if (xposLimit > 0 && x2 > xposLimit) { 774 if (args->xposLimit > 0 && x2 > args->xposLimit) {
749 if (lastWordEnd != text.start) { 775 if (lastWordEnd != args->text.start) {
750 *continueFrom_out = lastWordEnd; 776 *args->continueFrom_out = lastWordEnd;
751 } 777 }
752 else { 778 else {
753 *continueFrom_out = currentPos; /* forced break */ 779 *args->continueFrom_out = currentPos; /* forced break */
754 } 780 }
755 break; 781 break;
756 } 782 }
757 const int yLineMax = pos.y + d->height; 783 const int yLineMax = ypos + d->height;
758 SDL_Rect dst = { x1 + glyph->d[hoff].x, 784 SDL_Rect dst = { x1 + glyph->d[hoff].x,
759 pos.y + glyph->font->baseline + glyph->d[hoff].y, 785 ypos + glyph->font->baseline + glyph->d[hoff].y,
760 glyph->rect[hoff].size.x, 786 glyph->rect[hoff].size.x,
761 glyph->rect[hoff].size.y }; 787 glyph->rect[hoff].size.y };
762 if (glyph->font != d) { 788 if (glyph->font != d) {
@@ -776,7 +802,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
776 } 802 }
777 else { 803 else {
778 bounds.size.x = iMax(bounds.size.x, x2 - orig.x); 804 bounds.size.x = iMax(bounds.size.x, x2 - orig.x);
779 bounds.size.y = iMax(bounds.size.y, pos.y + glyph->font->height - orig.y); 805 bounds.size.y = iMax(bounds.size.y, ypos + glyph->font->height - orig.y);
780 } 806 }
781 /* Symbols and emojis are NOT monospaced, so must conform when the primary font 807 /* Symbols and emojis are NOT monospaced, so must conform when the primary font
782 is monospaced. Except with Japanese script, that's larger than the normal monospace. */ 808 is monospaced. Except with Japanese script, that's larger than the normal monospace. */
@@ -797,8 +823,8 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
797 src.h -= over; 823 src.h -= over;
798 dst.h -= over; 824 dst.h -= over;
799 } 825 }
800 if (dst.y < pos.y) { 826 if (dst.y < ypos) {
801 const int over = pos.y - dst.y; 827 const int over = ypos - dst.y;
802 dst.y += over; 828 dst.y += over;
803 dst.h -= over; 829 dst.h -= over;
804 src.y += over; 830 src.y += over;
@@ -812,7 +838,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
812 } 838 }
813 xposExtend = iMax(xposExtend, xpos); 839 xposExtend = iMax(xposExtend, xpos);
814 xposMax = iMax(xposMax, xposExtend); 840 xposMax = iMax(xposMax, xposExtend);
815 if (continueFrom_out && ((mode & noWrapFlag_RunMode) || isWrapBoundary_(prevCh, ch))) { 841 if (args->continueFrom_out && ((mode & noWrapFlag_RunMode) || isWrapBoundary_(prevCh, ch))) {
816 lastWordEnd = chPos; 842 lastWordEnd = chPos;
817 } 843 }
818#if defined (LAGRANGE_ENABLE_KERNING) 844#if defined (LAGRANGE_ENABLE_KERNING)
@@ -820,19 +846,19 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
820 if (!isMonospaced && glyph->font == d) { 846 if (!isMonospaced && glyph->font == d) {
821 /* TODO: No need to decode the next char twice; check this on the next iteration. */ 847 /* TODO: No need to decode the next char twice; check this on the next iteration. */
822 const char *peek = chPos; 848 const char *peek = chPos;
823 const iChar next = nextChar_(&peek, text.end); 849 const iChar next = nextChar_(&peek, args->text.end);
824 if (enableKerning_Text && !d->manualKernOnly && next) { 850 if (enableKerning_Text && !d->manualKernOnly && next) {
825 xpos += d->xScale * stbtt_GetGlyphKernAdvance(&d->font, glyph->glyphIndex, next); 851 xpos += d->xScale * stbtt_GetGlyphKernAdvance(&d->font, glyph->glyphIndex, next);
826 } 852 }
827 } 853 }
828#endif 854#endif
829 prevCh = ch; 855 prevCh = ch;
830 if (--maxLen == 0) { 856 if (maxLen == 0) {
831 break; 857 break;
832 } 858 }
833 } 859 }
834 if (runAdvance_out) { 860 if (args->runAdvance_out) {
835 *runAdvance_out = xposMax - orig.x; 861 *args->runAdvance_out = xposMax - orig.x;
836 } 862 }
837 return bounds; 863 return bounds;
838} 864}
@@ -845,25 +871,15 @@ iInt2 measureRange_Text(int fontId, iRangecc text) {
845 if (isEmpty_Range(&text)) { 871 if (isEmpty_Range(&text)) {
846 return init_I2(0, lineHeight_Text(fontId)); 872 return init_I2(0, lineHeight_Text(fontId));
847 } 873 }
848 return run_Font_(font_Text_(fontId), 874 return run_Font_(font_Text_(fontId), &(iRunArgs){ .mode = measure_RunMode, .text = text }).size;
849 measure_RunMode,
850 text,
851 iInvalidSize,
852 zero_I2(),
853 0,
854 NULL,
855 NULL).size;
856} 875}
857 876
858iRect visualBounds_Text(int fontId, iRangecc text) { 877iRect visualBounds_Text(int fontId, iRangecc text) {
859 return run_Font_(font_Text_(fontId), 878 return run_Font_(font_Text_(fontId),
860 measure_RunMode | visualFlag_RunMode, 879 &(iRunArgs){
861 text, 880 .mode = measure_RunMode | visualFlag_RunMode,
862 iInvalidSize, 881 .text = text,
863 zero_I2(), 882 });
864 0,
865 NULL,
866 NULL);
867} 883}
868 884
869iInt2 measure_Text(int fontId, const char *text) { 885iInt2 measure_Text(int fontId, const char *text) {
@@ -881,13 +897,9 @@ static int runFlagsFromId_(enum iFontId fontId) {
881iInt2 advanceRange_Text(int fontId, iRangecc text) { 897iInt2 advanceRange_Text(int fontId, iRangecc text) {
882 int advance; 898 int advance;
883 const int height = run_Font_(font_Text_(fontId), 899 const int height = run_Font_(font_Text_(fontId),
884 measure_RunMode | runFlagsFromId_(fontId), 900 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId),
885 text, 901 .text = text,
886 iInvalidSize, 902 .runAdvance_out = &advance })
887 zero_I2(),
888 0,
889 NULL,
890 &advance)
891 .size.y; 903 .size.y;
892 return init_I2(advance, height); 904 return init_I2(advance, height);
893} 905}
@@ -895,13 +907,11 @@ iInt2 advanceRange_Text(int fontId, iRangecc text) {
895iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { 907iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) {
896 int advance; 908 int advance;
897 const int height = run_Font_(font_Text_(fontId), 909 const int height = run_Font_(font_Text_(fontId),
898 measure_RunMode | runFlagsFromId_(fontId), 910 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId),
899 text, 911 .text = text,
900 iInvalidSize, 912 .xposLimit = width,
901 zero_I2(), 913 .continueFrom_out = endPos,
902 width, 914 .runAdvance_out = &advance })
903 endPos,
904 &advance)
905 .size.y; 915 .size.y;
906 return init_I2(advance, height); 916 return init_I2(advance, height);
907} 917}
@@ -909,13 +919,12 @@ iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos)
909iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { 919iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) {
910 int advance; 920 int advance;
911 const int height = run_Font_(font_Text_(fontId), 921 const int height = run_Font_(font_Text_(fontId),
912 measure_RunMode | noWrapFlag_RunMode | runFlagsFromId_(fontId), 922 &(iRunArgs){ .mode = measure_RunMode | noWrapFlag_RunMode |
913 text, 923 runFlagsFromId_(fontId),
914 iInvalidSize, 924 .text = text,
915 zero_I2(), 925 .xposLimit = width,
916 width, 926 .continueFrom_out = endPos,
917 endPos, 927 .runAdvance_out = &advance })
918 &advance)
919 .size.y; 928 .size.y;
920 return init_I2(advance, height); 929 return init_I2(advance, height);
921} 930}
@@ -930,29 +939,28 @@ iInt2 advanceN_Text(int fontId, const char *text, size_t n) {
930 } 939 }
931 int advance; 940 int advance;
932 run_Font_(font_Text_(fontId), 941 run_Font_(font_Text_(fontId),
933 measure_RunMode | runFlagsFromId_(fontId), 942 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId),
934 range_CStr(text), 943 .text = range_CStr(text),
935 n, 944 .maxLen = n,
936 zero_I2(), 945 .runAdvance_out = &advance });
937 0,
938 NULL,
939 &advance);
940 return init_I2(advance, lineHeight_Text(fontId)); 946 return init_I2(advance, lineHeight_Text(fontId));
941} 947}
942 948
943static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { 949static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) {
944 iText *d = &text_; 950 iText *d = &text_;
945 const iColor clr = get_Color(color & mask_ColorId); 951 const iColor clr = get_Color(color & mask_ColorId);
946 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); 952 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b);
947 run_Font_(font_Text_(fontId), 953 run_Font_(font_Text_(fontId),
948 draw_RunMode | (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) | 954 &(iRunArgs){ .mode = draw_RunMode |
949 runFlagsFromId_(fontId), 955 (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) |
950 text, 956 runFlagsFromId_(fontId),
951 iInvalidSize, 957 .text = text,
952 pos, 958 .pos = pos,
953 0, 959 .xposLayoutBound = xposBound });
954 NULL, 960}
955 NULL); 961
962static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) {
963 drawBounded_Text_(fontId, pos, 0, color, text);
956} 964}
957 965
958void drawAlign_Text(int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...) { 966void drawAlign_Text(int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...) {
@@ -1005,6 +1013,12 @@ iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) {
1005 return size; 1013 return size;
1006} 1014}
1007 1015
1016void drawBoundRange_Text(int fontId, iInt2 pos, int boundWidth, int color, iRangecc text) {
1017 /* This function is used together with text that has already been wrapped, so we'll know
1018 the bound width but don't have to re-wrap the text. */
1019 drawBounded_Text_(fontId, pos, pos.x + boundWidth, color, text);
1020}
1021
1008int drawWrapRange_Text(int fontId, iInt2 pos, int maxWidth, int color, iRangecc text) { 1022int drawWrapRange_Text(int fontId, iInt2 pos, int maxWidth, int color, iRangecc text) {
1009 const char *endp; 1023 const char *endp;
1010 while (!isEmpty_Range(&text)) { 1024 while (!isEmpty_Range(&text)) {