diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-13 10:40:35 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-13 10:40:35 +0300 |
commit | 0076a605540337bd89d37b7887541144e44b20f3 (patch) | |
tree | 591ed3fb39d1ee6590609657d2e6ca1dd2255caa | |
parent | f49a2ed900aab6ff81f0006bd0b5875c39f454c3 (diff) |
Text: Working on bidi text wrapping
-rw-r--r-- | src/gmdocument.c | 10 | ||||
-rw-r--r-- | src/ui/text.c | 520 | ||||
-rw-r--r-- | src/ui/text.h | 8 |
3 files changed, 187 insertions, 351 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index a499bf18..06b4fb45 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -414,17 +414,19 @@ static const int colors[max_GmLineType] = { | |||
414 | tmLinkText_ColorId, | 414 | tmLinkText_ColorId, |
415 | }; | 415 | }; |
416 | 416 | ||
417 | static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, int wrapAdvance) { | 417 | static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, int origin, int advance) { |
418 | iAssert(wrapRange.start <= wrapRange.end); | ||
419 | printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); | ||
418 | iRunTypesetter *d = wrap->context; | 420 | iRunTypesetter *d = wrap->context; |
419 | const int fontId = d->run.font; | 421 | const int fontId = d->run.font; |
420 | d->run.text = wrapRange; | 422 | d->run.text = wrapRange; |
421 | if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) { | 423 | if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) { |
422 | d->pos.y -= d->lineHeightReduction * lineHeight_Text(fontId); | 424 | d->pos.y -= d->lineHeightReduction * lineHeight_Text(fontId); |
423 | } | 425 | } |
424 | d->run.bounds.pos = addX_I2(d->pos, d->indent); | 426 | d->run.bounds.pos = addX_I2(d->pos, origin + d->indent); |
425 | const iInt2 dims = init_I2(wrapAdvance, lineHeight_Text(fontId)); | 427 | const iInt2 dims = init_I2(advance, lineHeight_Text(fontId)); |
426 | iChangeFlags(d->run.flags, wide_GmRunFlag, (d->isPreformat && dims.x > d->layoutWidth)); | 428 | iChangeFlags(d->run.flags, wide_GmRunFlag, (d->isPreformat && dims.x > d->layoutWidth)); |
427 | d->run.bounds.size.x = iMax(wrap->maxWidth, dims.x); /* Extends to the right edge for selection. */ | 429 | d->run.bounds.size.x = iMax(wrap->maxWidth, dims.x) - origin; /* Extends to the right edge for selection. */ |
428 | d->run.bounds.size.y = dims.y; | 430 | d->run.bounds.size.y = dims.y; |
429 | d->run.visBounds = d->run.bounds; | 431 | d->run.visBounds = d->run.bounds; |
430 | d->run.visBounds.size.x = dims.x; | 432 | d->run.visBounds.size.x = dims.x; |
diff --git a/src/ui/text.c b/src/ui/text.c index 1c509735..f5ee9860 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -773,10 +773,14 @@ static iBool isControl_Char_(iChar c) { | |||
773 | iDeclareType(AttributedRun) | 773 | iDeclareType(AttributedRun) |
774 | 774 | ||
775 | struct Impl_AttributedRun { | 775 | struct Impl_AttributedRun { |
776 | iRangei visual; /* UTF-32 codepoint indices in the visual-order text */ | 776 | iRangei logical; /* UTF-32 codepoint indices in the logical-order text */ |
777 | //iRangei visual; /* UTF-32 codepoint indices in the visual-order text */ | ||
777 | iFont * font; | 778 | iFont * font; |
778 | iColor fgColor; | 779 | iColor fgColor; |
779 | iBool isLineBreak; | 780 | struct { |
781 | uint8_t isLineBreak : 1; | ||
782 | uint8_t isRTL : 1; | ||
783 | } flags; | ||
780 | }; | 784 | }; |
781 | 785 | ||
782 | iDeclareType(AttributedText) | 786 | iDeclareType(AttributedText) |
@@ -788,40 +792,41 @@ struct Impl_AttributedText { | |||
788 | iFont * font; | 792 | iFont * font; |
789 | iColor fgColor; | 793 | iColor fgColor; |
790 | iArray runs; | 794 | iArray runs; |
795 | iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */ | ||
791 | iArray visual; /* UTF-32 text in visual order (LTR) */ | 796 | iArray visual; /* UTF-32 text in visual order (LTR) */ |
792 | iArray visualToSourceOffset; /* map visual character to an UTF-8 offset in the source text */ | 797 | iArray logicalToVisual; /* map visual index to logical index */ |
798 | iArray visualToLogical; /* map visual index to logical index */ | ||
799 | iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */ | ||
793 | char * bidiLevels; | 800 | char * bidiLevels; |
794 | }; | 801 | }; |
795 | 802 | ||
796 | iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor), | 803 | iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor), |
797 | text, maxLen, font, fgColor) | 804 | text, maxLen, font, fgColor) |
798 | 805 | ||
799 | static const char *sourcePtr_AttributedText_(const iAttributedText *d, int visualPos) { | 806 | static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) { |
800 | const int *mapToSource = constData_Array(&d->visualToSourceOffset); | 807 | const int *logToSource = constData_Array(&d->logicalToSourceOffset); |
801 | return d->source.start + mapToSource[visualPos]; | 808 | return d->source.start + logToSource[logicalPos]; |
802 | } | 809 | } |
803 | 810 | ||
804 | static iRangecc sourceRange_AttributedText_(const iAttributedText *d, iRangei visual) { | 811 | static iRangecc sourceRange_AttributedText_(const iAttributedText *d, iRangei logical) { |
805 | const int *mapToSource = constData_Array(&d->visualToSourceOffset); | 812 | const int *logToSource = constData_Array(&d->logicalToSourceOffset); |
806 | iRangecc range = { | 813 | iRangecc range = { |
807 | d->source.start + mapToSource[visual.start], | 814 | d->source.start + logToSource[logical.start], |
808 | d->source.start + mapToSource[visual.end] | 815 | d->source.start + logToSource[logical.end] |
809 | }; | 816 | }; |
810 | if (range.start > range.end) { | 817 | iAssert(range.start <= range.end); |
811 | iSwap(const char *, range.start, range.end); | ||
812 | } | ||
813 | return range; | 818 | return range; |
814 | } | 819 | } |
815 | 820 | ||
816 | static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) { | 821 | static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) { |
817 | iAttributedRun finishedRun = *run; | 822 | iAttributedRun finishedRun = *run; |
818 | iAssert(endAt >= 0 && endAt <= size_Array(&d->visual)); | 823 | iAssert(endAt >= 0 && endAt <= size_Array(&d->logical)); |
819 | finishedRun.visual.end = endAt; | 824 | finishedRun.logical.end = endAt; |
820 | if (!isEmpty_Range(&finishedRun.visual)) { | 825 | if (!isEmpty_Range(&finishedRun.logical)) { |
821 | pushBack_Array(&d->runs, &finishedRun); | 826 | pushBack_Array(&d->runs, &finishedRun); |
822 | run->isLineBreak = iFalse; | 827 | run->flags.isLineBreak = iFalse; |
823 | } | 828 | } |
824 | run->visual.start = endAt; | 829 | run->logical.start = endAt; |
825 | } | 830 | } |
826 | 831 | ||
827 | size_t length_Rangecc(const iRangecc d) { | 832 | size_t length_Rangecc(const iRangecc d) { |
@@ -836,69 +841,74 @@ size_t length_Rangecc(const iRangecc d) { | |||
836 | return n; | 841 | return n; |
837 | } | 842 | } |
838 | 843 | ||
844 | static enum iFontId fontId_Text_(const iFont *font) { | ||
845 | return (enum iFontId) (font - text_.fonts); | ||
846 | } | ||
847 | |||
839 | static void prepare_AttributedText_(iAttributedText *d) { | 848 | static void prepare_AttributedText_(iAttributedText *d) { |
840 | iAssert(isEmpty_Array(&d->runs)); | 849 | iAssert(isEmpty_Array(&d->runs)); |
841 | /* Prepare the visual (LTR) string. */ { | 850 | size_t length = 0; |
851 | /* Prepare the UTF-32 logical string. */ { | ||
842 | for (const char *ch = d->source.start; ch < d->source.end; ) { | 852 | for (const char *ch = d->source.start; ch < d->source.end; ) { |
843 | iChar u32; | 853 | iChar u32; |
844 | int len = decodeBytes_MultibyteChar(ch, d->source.end, &u32); | 854 | int len = decodeBytes_MultibyteChar(ch, d->source.end, &u32); |
845 | if (len <= 0) break; | 855 | if (len <= 0) break; |
846 | pushBack_Array(&d->visual, &u32); | 856 | pushBack_Array(&d->logical, &u32); |
847 | pushBack_Array(&d->visualToSourceOffset, &(int){ ch - d->source.start }); | 857 | length++; |
858 | /* Remember the byte offset to each character. We will need this to communicate | ||
859 | back the wrapped UTF-8 ranges. */ | ||
860 | pushBack_Array(&d->logicalToSourceOffset, &(int){ ch - d->source.start }); | ||
848 | ch += len; | 861 | ch += len; |
849 | } | 862 | } |
850 | #if defined (LAGRANGE_ENABLE_FRIBIDI) | 863 | #if defined (LAGRANGE_ENABLE_FRIBIDI) |
851 | /* Use FriBidi to reorder the codepoints. */ | 864 | /* Use FriBidi to reorder the codepoints. */ |
852 | const size_t len = size_Array(&d->visual); | 865 | resize_Array(&d->visual, length); |
853 | iArray ordered; | 866 | resize_Array(&d->logicalToVisual, length); |
854 | iArray visToLog; | 867 | resize_Array(&d->visualToLogical, length); |
855 | init_Array(&ordered, sizeof(uint32_t)); | 868 | d->bidiLevels = malloc(length); |
856 | init_Array(&visToLog, sizeof(FriBidiStrIndex)); | ||
857 | resize_Array(&ordered, len); | ||
858 | resize_Array(&visToLog, len); | ||
859 | d->bidiLevels = malloc(len); | ||
860 | FriBidiParType baseDir = (FriBidiParType) FRIBIDI_TYPE_ON; | 869 | FriBidiParType baseDir = (FriBidiParType) FRIBIDI_TYPE_ON; |
861 | fribidi_log2vis(constData_Array(&d->visual), | 870 | fribidi_log2vis(constData_Array(&d->logical), |
862 | len, | 871 | length, |
863 | &baseDir, | 872 | &baseDir, |
864 | data_Array(&ordered), | 873 | data_Array(&d->visual), |
865 | NULL, | 874 | data_Array(&d->logicalToVisual), |
866 | data_Array(&visToLog), | 875 | data_Array(&d->visualToLogical), |
867 | (FriBidiLevel *) d->bidiLevels); | 876 | (FriBidiLevel *) d->bidiLevels); |
868 | /* Replace with the visually ordered codepoints. */ | ||
869 | setCopy_Array(&d->visual, &ordered); | ||
870 | deinit_Array(&ordered); | ||
871 | /* Remap the source positions for visual order. */ | ||
872 | const int *mapToSource = constData_Array(&d->visualToSourceOffset); | ||
873 | const FriBidiStrIndex *visToLogIndex = constData_Array(&visToLog); | ||
874 | iArray orderedMapToSource; | ||
875 | init_Array(&orderedMapToSource, sizeof(int)); | ||
876 | resize_Array(&orderedMapToSource, len); | ||
877 | for (size_t i = 0; i < len; i++) { | ||
878 | *(int *) at_Array(&orderedMapToSource, i) = mapToSource[visToLogIndex[i]]; | ||
879 | } | ||
880 | setCopy_Array(&d->visualToSourceOffset, &orderedMapToSource); | ||
881 | deinit_Array(&orderedMapToSource); | ||
882 | deinit_Array(&visToLog); | ||
883 | #endif | 877 | #endif |
884 | } | 878 | } |
885 | /* The mapping needs to include the terminating NULL position. */ { | 879 | /* The mapping needs to include the terminating NULL position. */ { |
886 | pushBack_Array(&d->visualToSourceOffset, &(int){ d->source.end - d->source.start }); | 880 | pushBack_Array(&d->logicalToSourceOffset, &(int){ d->source.end - d->source.start }); |
887 | } | 881 | } |
888 | iRangei visText = { 0, size_Array(&d->visual) }; | 882 | size_t avail = d->maxLen; |
889 | size_t avail = d->maxLen; | 883 | iAttributedRun run = { .logical = { 0, length }, |
890 | iAttributedRun run = { .visual = visText, | 884 | .font = d->font, |
891 | .font = d->font, | 885 | .fgColor = d->fgColor }; |
892 | .fgColor = d->fgColor }; | 886 | const int * logToSource = constData_Array(&d->logicalToSourceOffset); |
893 | const int * mapToSource = constData_Array(&d->visualToSourceOffset); | 887 | const int * logToVis = constData_Array(&d->logicalToVisual); |
894 | const iChar *visualText = constData_Array(&d->visual); | 888 | const iChar * logicalText = constData_Array(&d->logical); |
895 | for (int pos = 0; pos < visText.end; pos++) { | 889 | // const iChar * visualText = constData_Array(&d->visual); |
896 | const iChar ch = visualText[pos]; | 890 | iBool isRTL = iFalse; |
891 | for (int pos = 0; pos < length; pos++) { | ||
892 | const iChar ch = logicalText[pos]; | ||
893 | const int visPos = logToVis[pos]; | ||
894 | #if defined (LAGRANGE_ENABLE_FRIBIDI) | ||
895 | const iBool isNeutral = FRIBIDI_IS_NEUTRAL(d->bidiLevels[visPos]); | ||
896 | if (d->bidiLevels && !isNeutral) { | ||
897 | iBool rtl = FRIBIDI_IS_RTL(d->bidiLevels[visPos]) != 0; | ||
898 | if (rtl != isRTL) { | ||
899 | /* Direction changes; must end the current run. */ | ||
900 | finishRun_AttributedText_(d, &run, pos); | ||
901 | isRTL = rtl; | ||
902 | } | ||
903 | } | ||
904 | #else | ||
905 | const iBool isNeutral = iTrue; | ||
906 | #endif | ||
907 | run.flags.isRTL = isRTL; | ||
897 | if (ch == 0x1b) { /* ANSI escape. */ | 908 | if (ch == 0x1b) { /* ANSI escape. */ |
898 | pos++; | 909 | pos++; |
899 | const char *srcPos = d->source.start + mapToSource[pos]; | 910 | const char *srcPos = d->source.start + logToSource[pos]; |
900 | /* Do a regexp match in the source text. */ | 911 | /* Do a regexp match in the source text. */ |
901 | /* TODO: Does this work in RTL regions?! */ | ||
902 | iRegExpMatch m; | 912 | iRegExpMatch m; |
903 | init_RegExpMatch(&m); | 913 | init_RegExpMatch(&m); |
904 | if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) { | 914 | if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) { |
@@ -906,33 +916,33 @@ static void prepare_AttributedText_(iAttributedText *d) { | |||
906 | run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), | 916 | run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), |
907 | tmParagraph_ColorId); | 917 | tmParagraph_ColorId); |
908 | pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); | 918 | pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); |
909 | iAssert(mapToSource[pos] == end_RegExpMatch(&m) - d->source.start); | 919 | iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start); |
910 | /* The run continues after the escape sequence. */ | 920 | /* The run continues after the escape sequence. */ |
911 | run.visual.start = pos--; /* loop increments `pos` */ | 921 | run.logical.start = pos--; /* loop increments `pos` */ |
912 | continue; | 922 | continue; |
913 | } | 923 | } |
914 | } | 924 | } |
915 | if (ch == '\v') { | 925 | if (ch == '\v') { |
916 | finishRun_AttributedText_(d, &run, pos); | 926 | finishRun_AttributedText_(d, &run, pos); |
917 | /* An internal color escape. */ | 927 | /* An internal color escape. */ |
918 | iChar esc = visualText[++pos]; | 928 | iChar esc = logicalText[++pos]; |
919 | int colorNum = none_ColorId; /* default color */ | 929 | int colorNum = none_ColorId; /* default color */ |
920 | if (esc == '\v') { /* Extended range. */ | 930 | if (esc == '\v') { /* Extended range. */ |
921 | esc = visualText[++pos] + asciiExtended_ColorEscape; | 931 | esc = logicalText[++pos] + asciiExtended_ColorEscape; |
922 | colorNum = esc - asciiBase_ColorEscape; | 932 | colorNum = esc - asciiBase_ColorEscape; |
923 | } | 933 | } |
924 | else if (esc != 0x24) { /* ASCII Cancel */ | 934 | else if (esc != 0x24) { /* ASCII Cancel */ |
925 | colorNum = esc - asciiBase_ColorEscape; | 935 | colorNum = esc - asciiBase_ColorEscape; |
926 | } | 936 | } |
927 | run.visual.start = pos + 1; | 937 | run.logical.start = pos + 1; |
928 | run.fgColor = (colorNum >= 0 ? get_Color(colorNum) : d->fgColor); | 938 | run.fgColor = (colorNum >= 0 ? get_Color(colorNum) : d->fgColor); |
929 | continue; | 939 | continue; |
930 | } | 940 | } |
931 | if (ch == '\n') { | 941 | if (ch == '\n') { |
932 | finishRun_AttributedText_(d, &run, pos); | 942 | finishRun_AttributedText_(d, &run, pos); |
933 | /* A separate run for the newline. */ | 943 | /* A separate run for the newline. */ |
934 | run.visual.start = pos; | 944 | run.logical.start = pos; |
935 | run.isLineBreak = iTrue; | 945 | run.flags.isLineBreak = iTrue; |
936 | finishRun_AttributedText_(d, &run, pos + 1); | 946 | finishRun_AttributedText_(d, &run, pos + 1); |
937 | continue; | 947 | continue; |
938 | } | 948 | } |
@@ -941,31 +951,32 @@ static void prepare_AttributedText_(iAttributedText *d) { | |||
941 | } | 951 | } |
942 | if (avail-- == 0) { | 952 | if (avail-- == 0) { |
943 | /* TODO: Check the combining class; only count base characters here. */ | 953 | /* TODO: Check the combining class; only count base characters here. */ |
944 | run.visual.end = pos; | 954 | run.logical.end = pos; |
945 | break; | 955 | break; |
946 | } | 956 | } |
947 | iFont *currentFont = d->font; | 957 | iFont *currentFont = d->font; |
948 | if (run.font->family == notoSansArabic_TextFont && ch == 0x20) { | 958 | if (run.font->family == notoSansArabic_TextFont && (isSpace_Char(ch) || isPunct_Char(ch))) { |
949 | currentFont = run.font; /* remain as Arabic for whitespace */ | 959 | currentFont = run.font; /* remain as Arabic for whitespace */ |
950 | /* TODO: FriBidi should be applied before this loop, but how to map the positions? */ | ||
951 | } | 960 | } |
952 | const iGlyph *glyph = glyph_Font_(currentFont, ch); | 961 | const iGlyph *glyph = glyph_Font_(currentFont, ch); |
953 | if (index_Glyph_(glyph) && glyph->font != run.font) { | 962 | if (index_Glyph_(glyph) && glyph->font != run.font) { |
954 | /* A different font is being used for this character. */ | 963 | /* A different font is being used for this character. */ |
955 | finishRun_AttributedText_(d, &run, pos); | 964 | finishRun_AttributedText_(d, &run, pos); |
956 | run.font = glyph->font; | 965 | run.font = glyph->font; |
966 | printf("changing font to %d at pos %u (%lc)\n", fontId_Text_(run.font), pos, (int)logicalText[pos]); | ||
957 | } | 967 | } |
958 | } | 968 | } |
959 | if (!isEmpty_Range(&run.visual)) { | 969 | if (!isEmpty_Range(&run.logical)) { |
960 | pushBack_Array(&d->runs, &run); | 970 | pushBack_Array(&d->runs, &run); |
961 | } | 971 | } |
962 | #if 0 | 972 | #if 1 |
963 | printf("[AttributedText] %zu runs:\n", size_Array(&d->runs)); | 973 | printf("[AttributedText] %zu runs:\n", size_Array(&d->runs)); |
964 | iConstForEach(Array, i, &d->runs) { | 974 | iConstForEach(Array, i, &d->runs) { |
965 | const iAttributedRun *run = i.value; | 975 | const iAttributedRun *run = i.value; |
966 | printf(" %zu %d...%d {%s}\n", index_ArrayConstIterator(&i), | 976 | printf(" %zu %s %d...%d {%s}\n", index_ArrayConstIterator(&i), |
967 | run->visual.start, run->visual.end, | 977 | run->flags.isRTL ? "<-" : "->", |
968 | cstr_Rangecc(sourceRange_AttributedText_(d, run->visual))); | 978 | run->logical.start, run->logical.end, |
979 | !run->flags.isRTL? cstr_Rangecc(sourceRange_AttributedText_(d, run->logical)) : "---"); | ||
969 | } | 980 | } |
970 | #endif | 981 | #endif |
971 | } | 982 | } |
@@ -976,16 +987,22 @@ void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont | |||
976 | d->font = font; | 987 | d->font = font; |
977 | d->fgColor = fgColor; | 988 | d->fgColor = fgColor; |
978 | init_Array(&d->runs, sizeof(iAttributedRun)); | 989 | init_Array(&d->runs, sizeof(iAttributedRun)); |
990 | init_Array(&d->logical, sizeof(iChar)); | ||
979 | init_Array(&d->visual, sizeof(iChar)); | 991 | init_Array(&d->visual, sizeof(iChar)); |
980 | init_Array(&d->visualToSourceOffset, sizeof(int)); | 992 | init_Array(&d->logicalToVisual, sizeof(int)); |
993 | init_Array(&d->visualToLogical, sizeof(int)); | ||
994 | init_Array(&d->logicalToSourceOffset, sizeof(int)); | ||
981 | d->bidiLevels = NULL; | 995 | d->bidiLevels = NULL; |
982 | prepare_AttributedText_(d); | 996 | prepare_AttributedText_(d); |
983 | } | 997 | } |
984 | 998 | ||
985 | void deinit_AttributedText(iAttributedText *d) { | 999 | void deinit_AttributedText(iAttributedText *d) { |
986 | free(d->bidiLevels); | 1000 | free(d->bidiLevels); |
1001 | deinit_Array(&d->logicalToSourceOffset); | ||
1002 | deinit_Array(&d->logicalToVisual); | ||
1003 | deinit_Array(&d->visualToLogical); | ||
987 | deinit_Array(&d->visual); | 1004 | deinit_Array(&d->visual); |
988 | deinit_Array(&d->visualToSourceOffset); | 1005 | deinit_Array(&d->logical); |
989 | deinit_Array(&d->runs); | 1006 | deinit_Array(&d->runs); |
990 | } | 1007 | } |
991 | 1008 | ||
@@ -1128,17 +1145,18 @@ static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { | |||
1128 | init_Array(&glyphIndices, sizeof(uint32_t)); | 1145 | init_Array(&glyphIndices, sizeof(uint32_t)); |
1129 | iAttributedText attrText; | 1146 | iAttributedText attrText; |
1130 | init_AttributedText(&attrText, text, 0, d, (iColor){}); | 1147 | init_AttributedText(&attrText, text, 0, d, (iColor){}); |
1131 | /* We use AttributedText here so the glyph lookup matches the behavior during text drawing -- | 1148 | /* We use AttributedText here so the font lookup matches the behavior during text drawing -- |
1132 | glyphs may be selected from a font that's different than `d`. */ | 1149 | glyphs may be selected from a font that's different than `d`. */ |
1133 | const iChar *visualText = constData_Array(&attrText.visual); | 1150 | const iChar *logicalText = constData_Array(&attrText.logical); |
1134 | iConstForEach(Array, i, &attrText.runs) { | 1151 | iConstForEach(Array, i, &attrText.runs) { |
1135 | const iAttributedRun *run = i.value; | 1152 | const iAttributedRun *run = i.value; |
1136 | if (run->isLineBreak) { | 1153 | if (run->flags.isLineBreak) { |
1137 | continue; | 1154 | continue; |
1138 | } | 1155 | } |
1139 | for (int pos = run->visual.start; pos < run->visual.end; pos++) { | 1156 | for (int pos = run->logical.start; pos < run->logical.end; pos++) { |
1140 | const iChar ch = visualText[pos]; | 1157 | const iChar ch = logicalText[pos]; |
1141 | if (!isSpace_Char(ch) && !isControl_Char_(ch)) { | 1158 | if (!isSpace_Char(ch) && !isControl_Char_(ch)) { |
1159 | /* TODO: Use `run->font`; the glyph may be selected from a different font. */ | ||
1142 | const uint32_t glyphIndex = glyphIndex_Font_(d, ch); | 1160 | const uint32_t glyphIndex = glyphIndex_Font_(d, ch); |
1143 | if (glyphIndex) { | 1161 | if (glyphIndex) { |
1144 | pushBack_Array(&glyphIndices, &glyphIndex); | 1162 | pushBack_Array(&glyphIndices, &glyphIndex); |
@@ -1147,6 +1165,7 @@ static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { | |||
1147 | } | 1165 | } |
1148 | } | 1166 | } |
1149 | deinit_AttributedText(&attrText); | 1167 | deinit_AttributedText(&attrText); |
1168 | /* TODO: Cache glyphs from ALL the fonts we encountered above. */ | ||
1150 | cacheGlyphs_Font_(d, &glyphIndices); | 1169 | cacheGlyphs_Font_(d, &glyphIndices); |
1151 | deinit_Array(&glyphIndices); | 1170 | deinit_Array(&glyphIndices); |
1152 | } | 1171 | } |
@@ -1165,10 +1184,6 @@ enum iRunMode { | |||
1165 | stopAtNewline_RunMode = iBit(14), /* don't advance past \n, consider it a wrap pos */ | 1184 | stopAtNewline_RunMode = iBit(14), /* don't advance past \n, consider it a wrap pos */ |
1166 | }; | 1185 | }; |
1167 | 1186 | ||
1168 | static enum iFontId fontId_Text_(const iFont *font) { | ||
1169 | return (enum iFontId) (font - text_.fonts); | ||
1170 | } | ||
1171 | |||
1172 | iDeclareType(RunArgs) | 1187 | iDeclareType(RunArgs) |
1173 | 1188 | ||
1174 | struct Impl_RunArgs { | 1189 | struct Impl_RunArgs { |
@@ -1180,18 +1195,22 @@ struct Impl_RunArgs { | |||
1180 | int xposLimit; /* hard limit for wrapping */ | 1195 | int xposLimit; /* hard limit for wrapping */ |
1181 | int xposLayoutBound; /* visible bound for layout purposes; does not affect wrapping */ | 1196 | int xposLayoutBound; /* visible bound for layout purposes; does not affect wrapping */ |
1182 | int color; | 1197 | int color; |
1183 | /* TODO: Use TextMetrics output pointer instead of return value & cursorAdvance_out */ | 1198 | /* TODO: Cleanup using TextMetrics |
1199 | Use TextMetrics output pointer instead of return value & cursorAdvance_out. */ | ||
1184 | iInt2 * cursorAdvance_out; | 1200 | iInt2 * cursorAdvance_out; |
1185 | const char ** continueFrom_out; | 1201 | const char ** continueFrom_out; |
1186 | int * runAdvance_out; | 1202 | int * runAdvance_out; |
1187 | }; | 1203 | }; |
1188 | 1204 | ||
1189 | static iBool notify_WrapText_(iWrapText *d, const char *ending, int advance) { | 1205 | static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int advance) { |
1190 | if (d && d->wrapFunc && d->wrapRange_.start) { | 1206 | if (d && d->wrapFunc && d->wrapRange_.start) { |
1207 | /* `wrapRange_` uses logical indices. */ | ||
1191 | const char *end = ending ? ending : d->wrapRange_.end; | 1208 | const char *end = ending ? ending : d->wrapRange_.end; |
1192 | const iBool result = d->wrapFunc(d, (iRangecc){ d->wrapRange_.start, end }, advance); | 1209 | iRangecc range = { d->wrapRange_.start, end }; |
1210 | iAssert(range.start <= range.end); | ||
1211 | const iBool result = d->wrapFunc(d, range, origin, advance); | ||
1193 | if (result) { | 1212 | if (result) { |
1194 | d->wrapRange_.start = end; | 1213 | d->wrapRange_.start = range.end; |
1195 | } | 1214 | } |
1196 | else { | 1215 | else { |
1197 | d->wrapRange_ = iNullRange; | 1216 | d->wrapRange_ = iNullRange; |
@@ -1235,15 +1254,18 @@ struct Impl_GlyphBuffer { | |||
1235 | hb_buffer_t * hb; | 1254 | hb_buffer_t * hb; |
1236 | iFont * font; | 1255 | iFont * font; |
1237 | const iChar * visualText; | 1256 | const iChar * visualText; |
1257 | const int * visToLog; | ||
1238 | hb_glyph_info_t * glyphInfo; | 1258 | hb_glyph_info_t * glyphInfo; |
1239 | hb_glyph_position_t *glyphPos; | 1259 | hb_glyph_position_t *glyphPos; |
1240 | unsigned int glyphCount; | 1260 | unsigned int glyphCount; |
1241 | }; | 1261 | }; |
1242 | 1262 | ||
1243 | static void init_GlyphBuffer_(iGlyphBuffer *d, iFont *font, const iChar *visualText) { | 1263 | static void init_GlyphBuffer_(iGlyphBuffer *d, iFont *font, const iChar *visualText, |
1264 | const int *visToLog) { | ||
1244 | d->hb = hb_buffer_create(); | 1265 | d->hb = hb_buffer_create(); |
1245 | d->font = font; | 1266 | d->font = font; |
1246 | d->visualText = visualText; | 1267 | d->visualText = visualText; |
1268 | d->visToLog = visToLog; | ||
1247 | d->glyphInfo = NULL; | 1269 | d->glyphInfo = NULL; |
1248 | d->glyphPos = NULL; | 1270 | d->glyphPos = NULL; |
1249 | d->glyphCount = 0; | 1271 | d->glyphCount = 0; |
@@ -1261,14 +1283,15 @@ static void shape_GlyphBuffer_(iGlyphBuffer *d) { | |||
1261 | } | 1283 | } |
1262 | } | 1284 | } |
1263 | 1285 | ||
1264 | static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapVisRange) { | 1286 | static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapPosRange) { |
1265 | float x = 0.0f; | 1287 | float x = 0.0f; |
1266 | for (unsigned int i = 0; i < d->glyphCount; i++) { | 1288 | for (unsigned int i = 0; i < d->glyphCount; i++) { |
1267 | const int visPos = d->glyphInfo[i].cluster; | 1289 | const int visPos = d->glyphInfo[i].cluster; |
1268 | if (visPos < wrapVisRange.start) { | 1290 | const int logPos = d->visToLog[visPos]; |
1291 | if (logPos < wrapPosRange.start) { | ||
1269 | continue; | 1292 | continue; |
1270 | } | 1293 | } |
1271 | if (visPos >= wrapVisRange.end) { | 1294 | if (logPos >= wrapPosRange.end) { |
1272 | break; | 1295 | break; |
1273 | } | 1296 | } |
1274 | x += d->font->xScale * d->glyphPos[i].x_advance; | 1297 | x += d->font->xScale * d->glyphPos[i].x_advance; |
@@ -1305,7 +1328,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1305 | float yCursor = 0.0f; | 1328 | float yCursor = 0.0f; |
1306 | float xCursorMax = 0.0f; | 1329 | float xCursorMax = 0.0f; |
1307 | const iBool isMonospaced = d->isMonospaced; | 1330 | const iBool isMonospaced = d->isMonospaced; |
1308 | const float monoAdvance = isMonospaced ? glyph_Font_(d, 'M')->advance : 0.0f; | ||
1309 | iAssert(args->text.end >= args->text.start); | 1331 | iAssert(args->text.end >= args->text.start); |
1310 | if (args->continueFrom_out) { | 1332 | if (args->continueFrom_out) { |
1311 | *args->continueFrom_out = args->text.end; | 1333 | *args->continueFrom_out = args->text.end; |
@@ -1321,8 +1343,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1321 | /* Initialize the wrap range. */ | 1343 | /* Initialize the wrap range. */ |
1322 | args->wrap->wrapRange_ = args->text; | 1344 | args->wrap->wrapRange_ = args->text; |
1323 | } | 1345 | } |
1324 | const iChar *visualText = constData_Array(&attrText.visual); | 1346 | const iChar *logicalText = constData_Array(&attrText.logical); |
1325 | const size_t runCount = size_Array(&attrText.runs); | 1347 | const iChar *visualText = constData_Array(&attrText.visual); |
1348 | const int * logToVis = constData_Array(&attrText.logicalToVisual); | ||
1349 | const int * visToLog = constData_Array(&attrText.visualToLogical); | ||
1350 | const size_t runCount = size_Array(&attrText.runs); | ||
1326 | iArray buffers; | 1351 | iArray buffers; |
1327 | init_Array(&buffers, sizeof(iGlyphBuffer)); | 1352 | init_Array(&buffers, sizeof(iGlyphBuffer)); |
1328 | resize_Array(&buffers, runCount); | 1353 | resize_Array(&buffers, runCount); |
@@ -1330,12 +1355,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1330 | iConstForEach(Array, i, &attrText.runs) { | 1355 | iConstForEach(Array, i, &attrText.runs) { |
1331 | const iAttributedRun *run = i.value; | 1356 | const iAttributedRun *run = i.value; |
1332 | iGlyphBuffer *buf = at_Array(&buffers, index_ArrayConstIterator(&i)); | 1357 | iGlyphBuffer *buf = at_Array(&buffers, index_ArrayConstIterator(&i)); |
1333 | init_GlyphBuffer_(buf, run->font, visualText); | 1358 | init_GlyphBuffer_(buf, run->font, visualText, visToLog); |
1334 | for (int pos = run->visual.start; pos < run->visual.end; pos++) { | 1359 | for (int pos = run->logical.start; pos < run->logical.end; pos++) { |
1335 | hb_buffer_add(buf->hb, visualText[pos], pos); | 1360 | const int visPos = logToVis[pos]; |
1361 | hb_buffer_add(buf->hb, visualText[visPos], visPos); | ||
1336 | } | 1362 | } |
1337 | hb_buffer_set_content_type(buf->hb, HB_BUFFER_CONTENT_TYPE_UNICODE); | 1363 | hb_buffer_set_content_type(buf->hb, HB_BUFFER_CONTENT_TYPE_UNICODE); |
1338 | hb_buffer_set_direction(buf->hb, HB_DIRECTION_LTR); | 1364 | hb_buffer_set_direction(buf->hb, run->flags.isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); /* visual is LTR */ |
1339 | /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */ | 1365 | /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */ |
1340 | } | 1366 | } |
1341 | if (isMonospaced) { | 1367 | if (isMonospaced) { |
@@ -1344,31 +1370,34 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1344 | evenMonospaceAdvances_GlyphBuffer_(at_Array(&buffers, runIndex), d); | 1370 | evenMonospaceAdvances_GlyphBuffer_(at_Array(&buffers, runIndex), d); |
1345 | } | 1371 | } |
1346 | } | 1372 | } |
1347 | iBool willAbortDueToWrap = iFalse; | 1373 | iBool willAbortDueToWrap = iFalse; |
1348 | const iRangecc sourceText = attrText.source; | 1374 | const size_t textLen = size_Array(&attrText.logical); |
1349 | const int * visToSource = constData_Array(&attrText.visualToSourceOffset); | 1375 | iRanges wrapRuns = { 0, runCount }; |
1350 | const size_t visSize = size_Array(&attrText.visual); | 1376 | iRangei wrapPosRange = { 0, textLen }; |
1351 | iRanges wrapRuns = { 0, runCount }; | 1377 | int wrapResumePos = textLen; |
1352 | iRangei wrapVisRange = { 0, visSize }; | 1378 | size_t wrapResumeRunIndex = runCount; |
1353 | int wrapResumePos = visSize; | 1379 | const int layoutBound = (args->wrap ? args->wrap->maxWidth : 0); |
1354 | size_t wrapResumeRunIndex = runCount; | ||
1355 | while (!isEmpty_Range(&wrapRuns)) { | 1380 | while (!isEmpty_Range(&wrapRuns)) { |
1356 | float wrapAdvance = 0.0f; | 1381 | float wrapAdvance = 0.0f; |
1382 | iBool isLineRTL = iFalse; | ||
1383 | /* First we need to figure out how much text fits on the current line. */ | ||
1357 | if (args->wrap && args->wrap->maxWidth > 0) { | 1384 | if (args->wrap && args->wrap->maxWidth > 0) { |
1358 | iAssert(wrapVisRange.end == visSize); | 1385 | float breakAdvance = -1.0f; |
1386 | iAssert(wrapPosRange.end == textLen); | ||
1359 | /* Determine ends of wrapRuns and wrapVisRange. */ | 1387 | /* Determine ends of wrapRuns and wrapVisRange. */ |
1360 | for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { | 1388 | for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { |
1361 | const iAttributedRun *run = at_Array(&attrText.runs, runIndex); | 1389 | const iAttributedRun *run = at_Array(&attrText.runs, runIndex); |
1362 | if (run->isLineBreak) { | 1390 | if (run->flags.isLineBreak) { |
1363 | wrapVisRange.end = run->visual.start; | 1391 | wrapPosRange.end = run->logical.start; |
1364 | wrapResumePos = run->visual.end; | 1392 | wrapResumePos = run->logical.end; |
1365 | wrapRuns.end = runIndex; | 1393 | wrapRuns.end = runIndex; |
1366 | wrapResumeRunIndex = runIndex + 1; | 1394 | wrapResumeRunIndex = runIndex + 1; |
1367 | yCursor += d->height; | 1395 | yCursor += d->height; |
1368 | break; | 1396 | break; |
1369 | } | 1397 | } |
1398 | isLineRTL |= run->flags.isRTL; | ||
1370 | wrapResumeRunIndex = runCount; | 1399 | wrapResumeRunIndex = runCount; |
1371 | wrapResumePos = visSize; | 1400 | wrapResumePos = textLen; |
1372 | iGlyphBuffer *buf = at_Array(&buffers, runIndex); | 1401 | iGlyphBuffer *buf = at_Array(&buffers, runIndex); |
1373 | iAssert(run->font == buf->font); | 1402 | iAssert(run->font == buf->font); |
1374 | shape_GlyphBuffer_(buf); | 1403 | shape_GlyphBuffer_(buf); |
@@ -1377,8 +1406,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1377 | for (unsigned int i = 0; i < buf->glyphCount; i++) { | 1406 | for (unsigned int i = 0; i < buf->glyphCount; i++) { |
1378 | const hb_glyph_info_t *info = &buf->glyphInfo[i]; | 1407 | const hb_glyph_info_t *info = &buf->glyphInfo[i]; |
1379 | const hb_codepoint_t glyphId = info->codepoint; | 1408 | const hb_codepoint_t glyphId = info->codepoint; |
1380 | const int visPos = info->cluster; | 1409 | const int visPos_ = info->cluster; |
1381 | if (visPos < wrapVisRange.start) { | 1410 | const int logPos = visToLog[visPos_]; |
1411 | if (logPos < wrapPosRange.start) { | ||
1382 | continue; | 1412 | continue; |
1383 | } | 1413 | } |
1384 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); | 1414 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); |
@@ -1387,37 +1417,42 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1387 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; | 1417 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; |
1388 | if (args->wrap->mode == word_WrapTextMode) { | 1418 | if (args->wrap->mode == word_WrapTextMode) { |
1389 | /* When word wrapping, only consider certain places breakable. */ | 1419 | /* When word wrapping, only consider certain places breakable. */ |
1390 | const iChar ch = visualText[info->cluster]; | 1420 | const iChar ch = logicalText[logPos]; |
1391 | if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { | 1421 | if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { |
1392 | safeBreakPos = visPos; | 1422 | safeBreakPos = logPos; |
1423 | breakAdvance = wrapAdvance; | ||
1393 | // isSoftHyphenBreak = iFalse; | 1424 | // isSoftHyphenBreak = iFalse; |
1394 | } | 1425 | } |
1395 | else if (isSpace_Char(ch)) { | 1426 | else if (isSpace_Char(ch)) { |
1396 | safeBreakPos = visPos; | 1427 | safeBreakPos = logPos; |
1428 | breakAdvance = wrapAdvance; | ||
1397 | // isSoftHyphenBreak = iFalse; | 1429 | // isSoftHyphenBreak = iFalse; |
1398 | } | 1430 | } |
1399 | prevCh = ch; | 1431 | prevCh = ch; |
1400 | } | 1432 | } |
1401 | else { | 1433 | else { |
1402 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { | 1434 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { |
1403 | safeBreakPos = visPos; | 1435 | safeBreakPos = logPos; |
1436 | breakAdvance = wrapAdvance; | ||
1404 | } | 1437 | } |
1405 | } | 1438 | } |
1406 | /* Out of room? */ | 1439 | /* Out of room? */ |
1407 | if (wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > | 1440 | if (wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > |
1408 | args->wrap->maxWidth) { | 1441 | args->wrap->maxWidth) { |
1409 | if (safeBreakPos >= 0) { | 1442 | if (safeBreakPos >= 0) { |
1410 | wrapVisRange.end = safeBreakPos; | 1443 | wrapPosRange.end = safeBreakPos; |
1411 | } | 1444 | } |
1412 | else { | 1445 | else { |
1413 | wrapVisRange.end = visPos; | 1446 | wrapPosRange.end = logPos; |
1447 | breakAdvance = wrapAdvance; | ||
1414 | } | 1448 | } |
1415 | wrapResumePos = wrapVisRange.end; | 1449 | wrapResumePos = wrapPosRange.end; |
1416 | while (wrapResumePos < visSize && isSpace_Char(visualText[wrapResumePos])) { | 1450 | while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) { |
1417 | wrapResumePos++; /* skip space */ | 1451 | wrapResumePos++; /* skip space */ |
1418 | } | 1452 | } |
1419 | wrapRuns.end = runIndex + 1; /* still includes this run */ | 1453 | wrapRuns.end = runIndex + 1; /* still includes this run */ |
1420 | wrapResumeRunIndex = runIndex; /* ...but continue from the same one */ | 1454 | wrapResumeRunIndex = runIndex; /* ...but continue from the same one */ |
1455 | wrapAdvance = breakAdvance; | ||
1421 | break; | 1456 | break; |
1422 | } | 1457 | } |
1423 | wrapAdvance += xAdvance; | 1458 | wrapAdvance += xAdvance; |
@@ -1432,130 +1467,30 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1432 | } | 1467 | } |
1433 | } | 1468 | } |
1434 | else { | 1469 | else { |
1435 | /* Calculate total advance without wrapping. */ | 1470 | /* Not wrapped so everything fits! Calculate total advance without wrapping. */ |
1436 | for (size_t i = wrapRuns.start; i < wrapRuns.end; i++) { | 1471 | for (size_t i = wrapRuns.start; i < wrapRuns.end; i++) { |
1437 | wrapAdvance += advance_GlyphBuffer_(at_Array(&buffers, i), wrapVisRange); | 1472 | wrapAdvance += advance_GlyphBuffer_(at_Array(&buffers, i), wrapPosRange); |
1438 | } | 1473 | } |
1439 | } | 1474 | } |
1475 | /* Alignment. */ | ||
1476 | int origin = 0; | ||
1477 | /* TODO: Is everything on the line RTL? If so, right-align. */ | ||
1478 | if (isLineRTL && layoutBound > 0 && wrapAdvance < layoutBound) { | ||
1479 | origin = layoutBound - wrapAdvance; | ||
1480 | } | ||
1440 | /* Make a callback for each wrapped line. */ | 1481 | /* Make a callback for each wrapped line. */ |
1441 | if (!notify_WrapText_(args->wrap, | 1482 | if (!notify_WrapText_(args->wrap, |
1442 | // sourcePtr_AttributedText_(&attrText, wrapVisRange.end), | ||
1443 | sourcePtr_AttributedText_(&attrText, wrapResumePos), | 1483 | sourcePtr_AttributedText_(&attrText, wrapResumePos), |
1484 | origin, | ||
1444 | iRound(wrapAdvance))) { | 1485 | iRound(wrapAdvance))) { |
1445 | willAbortDueToWrap = iTrue; | 1486 | willAbortDueToWrap = iTrue; |
1446 | } | 1487 | } |
1447 | #if 0 | 1488 | xCursor = origin; |
1448 | if (wrapResumePos >= 0) { | ||
1449 | xCursor = 0.0f; | ||
1450 | yCursor += d->height; | ||
1451 | runVisual.start = wrapResumePos; | ||
1452 | wrapResumePos = NULL; | ||
1453 | } | ||
1454 | else if (run->lineBreaks) { | ||
1455 | for (int i = 0; i < run->lineBreaks; i++) { | ||
1456 | /* One callback for each "wrapped" line even if they're empty. */ | ||
1457 | if (!notify_WrapText_(args->wrap, | ||
1458 | sourcePtr_AttributedText_(&attrText, run->visual.start), | ||
1459 | iRound(xCursor))) { | ||
1460 | break; | ||
1461 | } | ||
1462 | xCursor = 0.0f; | ||
1463 | yCursor += d->height; | ||
1464 | } | ||
1465 | } | ||
1466 | const iRangecc runSource = sourceRange_AttributedText_(&attrText, runVisual); | ||
1467 | hb_buffer_clear_contents(hbBuf); | ||
1468 | iBool isRunRTL = attrText.bidiLevels != NULL; | ||
1469 | /* Cluster values are used to determine offset inside the UTF-8 source string. */ | ||
1470 | //hb_buffer_set_cluster_level(hbBuf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); | ||
1471 | for (int pos = runVisual.start; pos < runVisual.end; pos++) { | ||
1472 | hb_buffer_add(hbBuf, visualText[pos], pos); | ||
1473 | if (attrText.bidiLevels) { | ||
1474 | const char lev = attrText.bidiLevels[pos]; | ||
1475 | if (!FRIBIDI_IS_NEUTRAL(lev) && !FRIBIDI_IS_RTL(lev)) { | ||
1476 | isRunRTL = iFalse; | ||
1477 | } | ||
1478 | } | ||
1479 | } | ||
1480 | hb_buffer_set_content_type(hbBuf, HB_BUFFER_CONTENT_TYPE_UNICODE); | ||
1481 | hb_buffer_set_direction(hbBuf, HB_DIRECTION_LTR); | ||
1482 | /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */ | ||
1483 | //hb_buffer_set_language(hbBuf, hb_language_from_string("en", -1)); /* TODO: language from document/UI, if known */ | ||
1484 | hb_shape(run->font->hbFont, hbBuf, NULL, 0); /* TODO: Specify features, too? */ | ||
1485 | unsigned int glyphCount = 0; | ||
1486 | const hb_glyph_info_t * glyphInfo = hb_buffer_get_glyph_infos(hbBuf, &glyphCount); | ||
1487 | hb_glyph_position_t * glyphPos = hb_buffer_get_glyph_positions(hbBuf, &glyphCount); | ||
1488 | // iBool isSoftHyphenBreak = iFalse; | ||
1489 | /* Check if this run needs to be wrapped. If so, we'll draw the portion that fits on | ||
1490 | the line, and re-run the loop resuming the run from the wrap point. */ | ||
1491 | if (args->wrap && args->wrap->maxWidth > 0) { | ||
1492 | float x = xCursor; | ||
1493 | int safeBreak = -1; | ||
1494 | iChar prevCh = 0; | ||
1495 | for (unsigned int i = 0; i < glyphCount; i++) { | ||
1496 | const hb_glyph_info_t *info = &glyphInfo[i]; | ||
1497 | const hb_codepoint_t glyphId = info->codepoint; | ||
1498 | const int visPos = info->cluster; | ||
1499 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); | ||
1500 | const int glyphFlags = hb_glyph_info_get_glyph_flags(info); | ||
1501 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; | ||
1502 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; | ||
1503 | if (args->wrap->mode == word_WrapTextMode) { | ||
1504 | /* When word wrapping, only consider certain places breakable. */ | ||
1505 | const iChar ch = visualText[info->cluster]; | ||
1506 | if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { | ||
1507 | safeBreak = visPos; | ||
1508 | // isSoftHyphenBreak = iFalse; | ||
1509 | } | ||
1510 | else if (isSpace_Char(ch)) { | ||
1511 | safeBreak = visPos; | ||
1512 | // isSoftHyphenBreak = iFalse; | ||
1513 | } | ||
1514 | prevCh = ch; | ||
1515 | } | ||
1516 | else { | ||
1517 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { | ||
1518 | safeBreak = visPos; | ||
1519 | } | ||
1520 | } | ||
1521 | if (x + xOffset + glyph->d[0].x + glyph->rect[0].size.x > args->wrap->maxWidth) { | ||
1522 | if (safeBreak >= 0) { | ||
1523 | breakPos = safeBreak; | ||
1524 | } | ||
1525 | else { | ||
1526 | breakPos = visPos; | ||
1527 | } | ||
1528 | break; | ||
1529 | } | ||
1530 | x += xAdvance; | ||
1531 | /* Additional kerning tweak. It would be better to use HarfBuzz font callbacks, | ||
1532 | but they don't seem to get called? */ | ||
1533 | if (i + 1 < glyphCount) { | ||
1534 | x += horizKern_Font_(run->font, glyphId, glyphInfo[i + 1].codepoint); | ||
1535 | } | ||
1536 | } | ||
1537 | /* Make a callback for each wrapped line. */ | ||
1538 | if (breakPos >= 0) { | ||
1539 | if (!notify_WrapText_(args->wrap, | ||
1540 | sourcePtr_AttributedText_(&attrText, breakPos), | ||
1541 | iRound(x))) { | ||
1542 | willAbortDueToWrap = iTrue; | ||
1543 | } | ||
1544 | } | ||
1545 | } | ||
1546 | /* Right-align RTL runs. */ | ||
1547 | if (isRunRTL && xCursor < 1 && args->xposLayoutBound > 0) { | ||
1548 | xCursor += args->xposLayoutBound - orig.x - | ||
1549 | glyphRunRightEdge_Font_(run->font, glyphPos, glyphCount); | ||
1550 | } | ||
1551 | #endif | ||
1552 | /* TODO: Alignment. */ | ||
1553 | xCursor = 0.0f; | ||
1554 | /* We have determined a possible wrap position and alignment for the work runs, | 1489 | /* We have determined a possible wrap position and alignment for the work runs, |
1555 | so now we can process the glyphs. */ | 1490 | so now we can process the glyphs. */ |
1556 | for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { | 1491 | for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { |
1557 | const iAttributedRun *run = at_Array(&attrText.runs, runIndex); | 1492 | const iAttributedRun *run = at_Array(&attrText.runs, runIndex); |
1558 | if (run->isLineBreak) { | 1493 | if (run->flags.isLineBreak) { |
1559 | xCursor = 0.0f; | 1494 | xCursor = 0.0f; |
1560 | yCursor += d->height; | 1495 | yCursor += d->height; |
1561 | continue; | 1496 | continue; |
@@ -1563,18 +1498,16 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1563 | iGlyphBuffer *buf = at_Array(&buffers, runIndex); | 1498 | iGlyphBuffer *buf = at_Array(&buffers, runIndex); |
1564 | shape_GlyphBuffer_(buf); | 1499 | shape_GlyphBuffer_(buf); |
1565 | iAssert(run->font == buf->font); | 1500 | iAssert(run->font == buf->font); |
1566 | iRangei runVisual = run->visual; | ||
1567 | /* Process all the glyphs. */ | 1501 | /* Process all the glyphs. */ |
1568 | for (unsigned int i = 0; i < buf->glyphCount; i++) { | 1502 | for (unsigned int i = 0; i < buf->glyphCount; i++) { |
1569 | const hb_glyph_info_t *info = &buf->glyphInfo[i]; | 1503 | const hb_glyph_info_t *info = &buf->glyphInfo[i]; |
1570 | const hb_codepoint_t glyphId = info->codepoint; | 1504 | const hb_codepoint_t glyphId = info->codepoint; |
1571 | const int visPos = info->cluster; | 1505 | const int visPos = info->cluster; |
1572 | if (visPos < wrapVisRange.start) { | 1506 | const int logPos = visToLog[visPos]; |
1507 | if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) { | ||
1508 | /* Already handled this part of the run. */ | ||
1573 | continue; | 1509 | continue; |
1574 | } | 1510 | } |
1575 | if (visPos >= wrapVisRange.end) { | ||
1576 | break; | ||
1577 | } | ||
1578 | const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; | 1511 | const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; |
1579 | const float yOffset = run->font->yScale * buf->glyphPos[i].y_offset; | 1512 | const float yOffset = run->font->yScale * buf->glyphPos[i].y_offset; |
1580 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; | 1513 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; |
@@ -1582,13 +1515,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1582 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); | 1515 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); |
1583 | const float xf = xCursor + xOffset; | 1516 | const float xf = xCursor + xOffset; |
1584 | const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0; | 1517 | const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0; |
1585 | #if 0 | ||
1586 | if (visPos == breakPos) { | ||
1587 | runIndex--; /* stay on the same run */ | ||
1588 | wrapResumePos = visPos; | ||
1589 | break; | ||
1590 | } | ||
1591 | #endif | ||
1592 | /* Output position for the glyph. */ | 1518 | /* Output position for the glyph. */ |
1593 | SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x, | 1519 | SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x, |
1594 | orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, | 1520 | orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, |
@@ -1645,17 +1571,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1645 | } | 1571 | } |
1646 | wrapRuns.start = wrapResumeRunIndex; | 1572 | wrapRuns.start = wrapResumeRunIndex; |
1647 | wrapRuns.end = runCount; | 1573 | wrapRuns.end = runCount; |
1648 | wrapVisRange.start = wrapResumePos; | 1574 | wrapPosRange.start = wrapResumePos; |
1649 | wrapVisRange.end = visSize; | 1575 | wrapPosRange.end = textLen; |
1650 | yCursor += d->height; | 1576 | yCursor += d->height; |
1651 | } | 1577 | } |
1652 | if (args->cursorAdvance_out) { | 1578 | if (args->cursorAdvance_out) { |
1653 | *args->cursorAdvance_out = init_I2(xCursor, yCursor); | 1579 | *args->cursorAdvance_out = init_I2(xCursor, yCursor); |
1654 | } | 1580 | } |
1655 | #if 0 | ||
1656 | /* The final, unbroken line. */ | ||
1657 | notify_WrapText_(args->wrap, NULL, xCursor); | ||
1658 | #endif | ||
1659 | if (args->runAdvance_out) { | 1581 | if (args->runAdvance_out) { |
1660 | *args->runAdvance_out = xCursorMax; | 1582 | *args->runAdvance_out = xCursorMax; |
1661 | } | 1583 | } |
@@ -1678,15 +1600,6 @@ int lineHeight_Text(int fontId) { | |||
1678 | return font_Text_(fontId)->height; | 1600 | return font_Text_(fontId)->height; |
1679 | } | 1601 | } |
1680 | 1602 | ||
1681 | #if 0 | ||
1682 | iInt2 measureRange_Text(int fontId, iRangecc text) { | ||
1683 | if (isEmpty_Range(&text)) { | ||
1684 | return init_I2(0, lineHeight_Text(fontId)); | ||
1685 | } | ||
1686 | return run_Font_(font_Text_(fontId), &(iRunArgs){ .mode = measure_RunMode, .text = text }).size; | ||
1687 | } | ||
1688 | #endif | ||
1689 | |||
1690 | iTextMetrics measureRange_Text(int fontId, iRangecc text) { | 1603 | iTextMetrics measureRange_Text(int fontId, iRangecc text) { |
1691 | if (isEmpty_Range(&text)) { | 1604 | if (isEmpty_Range(&text)) { |
1692 | return (iTextMetrics){ init_Rect(0, 0, 0, lineHeight_Text(fontId)), zero_I2() }; | 1605 | return (iTextMetrics){ init_Rect(0, 0, 0, lineHeight_Text(fontId)), zero_I2() }; |
@@ -1708,12 +1621,6 @@ iRect visualBounds_Text(int fontId, iRangecc text) { | |||
1708 | }); | 1621 | }); |
1709 | } | 1622 | } |
1710 | 1623 | ||
1711 | #if 0 | ||
1712 | iInt2 measure_Text(int fontId, const char *text) { | ||
1713 | return measureRange_Text(fontId, range_CStr(text)); | ||
1714 | } | ||
1715 | #endif | ||
1716 | |||
1717 | void cache_Text(int fontId, iRangecc text) { | 1624 | void cache_Text(int fontId, iRangecc text) { |
1718 | cacheTextGlyphs_Font_(font_Text_(fontId), text); | 1625 | cacheTextGlyphs_Font_(font_Text_(fontId), text); |
1719 | } | 1626 | } |
@@ -1726,20 +1633,9 @@ static int runFlagsFromId_(enum iFontId fontId) { | |||
1726 | return runFlags; | 1633 | return runFlags; |
1727 | } | 1634 | } |
1728 | 1635 | ||
1729 | #if 0 | 1636 | static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int origin, int advance) { |
1730 | iInt2 advanceRange_Text(int fontId, iRangecc text) { | 1637 | iUnused(origin, advance); |
1731 | int advance; | 1638 | *((const char **) d->context) = range.end; |
1732 | const int height = run_Font_(font_Text_(fontId), | ||
1733 | &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId), | ||
1734 | .text = text, | ||
1735 | .runAdvance_out = &advance }) | ||
1736 | .size.y; | ||
1737 | return init_I2(advance, height); | ||
1738 | } | ||
1739 | #endif | ||
1740 | |||
1741 | static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int advance) { | ||
1742 | *((const char **) d->context) = range.end; // iMin(skipSpace_CStr(range.end), d->text.end); | ||
1743 | return iFalse; /* just one line */ | 1639 | return iFalse; /* just one line */ |
1744 | } | 1640 | } |
1745 | 1641 | ||
@@ -1752,19 +1648,6 @@ iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) | |||
1752 | .context = endPos }; | 1648 | .context = endPos }; |
1753 | /* The return value is expected to be the horizontal/vertical bounds. */ | 1649 | /* The return value is expected to be the horizontal/vertical bounds. */ |
1754 | return measure_WrapText(&wrap, fontId).bounds.size; | 1650 | return measure_WrapText(&wrap, fontId).bounds.size; |
1755 | |||
1756 | #if 0 | ||
1757 | int advance; | ||
1758 | const int height = run_Font_(font_Text_(fontId), | ||
1759 | &(iRunArgs){ .mode = measure_RunMode | stopAtNewline_RunMode | | ||
1760 | runFlagsFromId_(fontId), | ||
1761 | .text = text, | ||
1762 | .xposLimit = width, | ||
1763 | .continueFrom_out = endPos, | ||
1764 | .runAdvance_out = &advance }) | ||
1765 | .size.y; | ||
1766 | return init_I2(advance, height); | ||
1767 | #endif | ||
1768 | } | 1651 | } |
1769 | 1652 | ||
1770 | iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1653 | iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { |
@@ -1791,25 +1674,6 @@ iTextMetrics measureN_Text(int fontId, const char *text, size_t n) { | |||
1791 | return tm; | 1674 | return tm; |
1792 | } | 1675 | } |
1793 | 1676 | ||
1794 | #if 0 | ||
1795 | iInt2 advance_Text(int fontId, const char *text) { | ||
1796 | return advanceRange_Text(fontId, range_CStr(text)); | ||
1797 | } | ||
1798 | |||
1799 | iInt2 advanceN_Text(int fontId, const char *text, size_t n) { | ||
1800 | if (n == 0) { | ||
1801 | return init_I2(0, lineHeight_Text(fontId)); | ||
1802 | } | ||
1803 | int advance; | ||
1804 | int height = run_Font_(font_Text_(fontId), | ||
1805 | &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId), | ||
1806 | .text = range_CStr(text), | ||
1807 | .maxLen = n, | ||
1808 | .runAdvance_out = &advance }).size.y; | ||
1809 | return init_I2(advance, height); | ||
1810 | } | ||
1811 | #endif | ||
1812 | |||
1813 | static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { | 1677 | static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { |
1814 | iText *d = &text_; | 1678 | iText *d = &text_; |
1815 | iFont *font = font_Text_(fontId); | 1679 | iFont *font = font_Text_(fontId); |
@@ -1894,20 +1758,6 @@ iTextMetrics measureWrapRange_Text(int fontId, int maxWidth, iRangecc text) { | |||
1894 | return measure_WrapText(&wrap, fontId); | 1758 | return measure_WrapText(&wrap, fontId); |
1895 | } | 1759 | } |
1896 | 1760 | ||
1897 | #if 0 | ||
1898 | iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { | ||
1899 | iInt2 size = zero_I2(); | ||
1900 | const char *endp; | ||
1901 | while (!isEmpty_Range(&text)) { | ||
1902 | iInt2 line = tryAdvance_Text(fontId, text, maxWidth, &endp); | ||
1903 | text.start = endp; | ||
1904 | size.x = iMax(size.x, line.x); | ||
1905 | size.y += line.y; | ||
1906 | } | ||
1907 | return size; | ||
1908 | } | ||
1909 | #endif | ||
1910 | |||
1911 | void drawBoundRange_Text(int fontId, iInt2 pos, int boundWidth, int color, iRangecc text) { | 1761 | void drawBoundRange_Text(int fontId, iInt2 pos, int boundWidth, int color, iRangecc text) { |
1912 | /* This function is used together with text that has already been wrapped, so we'll know | 1762 | /* This function is used together with text that has already been wrapped, so we'll know |
1913 | the bound width but don't have to re-wrap the text. */ | 1763 | the bound width but don't have to re-wrap the text. */ |
@@ -1970,16 +1820,6 @@ void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color | |||
1970 | draw_Text_(fontId, textBounds.pos, color, text); | 1820 | draw_Text_(fontId, textBounds.pos, color, text); |
1971 | } | 1821 | } |
1972 | 1822 | ||
1973 | #if 0 | ||
1974 | iInt2 advance_WrapText(iWrapText *d, int fontId) { | ||
1975 | const iRect bounds = run_Font_(font_Text_(fontId), | ||
1976 | &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId), | ||
1977 | .text = d->text, | ||
1978 | .wrap = d }); | ||
1979 | return bounds.size; | ||
1980 | } | ||
1981 | #endif | ||
1982 | |||
1983 | iTextMetrics measure_WrapText(iWrapText *d, int fontId) { | 1823 | iTextMetrics measure_WrapText(iWrapText *d, int fontId) { |
1984 | iTextMetrics tm; | 1824 | iTextMetrics tm; |
1985 | tm.bounds = run_Font_(font_Text_(fontId), | 1825 | tm.bounds = run_Font_(font_Text_(fontId), |
diff --git a/src/ui/text.h b/src/ui/text.h index 0209e10a..8be5a373 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -148,13 +148,7 @@ void setContentFontSize_Text (float fontSizeFactor); /* affects all except `d | |||
148 | void resetFonts_Text (void); | 148 | void resetFonts_Text (void); |
149 | 149 | ||
150 | int lineHeight_Text (int fontId); | 150 | int lineHeight_Text (int fontId); |
151 | //iInt2 measure_Text (int fontId, const char *text); | ||
152 | //iInt2 measureRange_Text (int fontId, iRangecc text); | ||
153 | iRect visualBounds_Text (int fontId, iRangecc text); | 151 | iRect visualBounds_Text (int fontId, iRangecc text); |
154 | //iInt2 advance_Text (int fontId, const char *text); | ||
155 | //iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ | ||
156 | //iInt2 advanceRange_Text (int fontId, iRangecc text); | ||
157 | //iInt2 advanceWrapRange_Text (int fontId, int maxWidth, iRangecc text); | ||
158 | 152 | ||
159 | iDeclareType(TextMetrics) | 153 | iDeclareType(TextMetrics) |
160 | 154 | ||
@@ -209,7 +203,7 @@ struct Impl_WrapText { | |||
209 | iRangecc text; | 203 | iRangecc text; |
210 | int maxWidth; | 204 | int maxWidth; |
211 | enum iWrapTextMode mode; | 205 | enum iWrapTextMode mode; |
212 | iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int advance); | 206 | iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int origin, int advance); |
213 | void * context; | 207 | void * context; |
214 | /* internal */ | 208 | /* internal */ |
215 | iRangecc wrapRange_; | 209 | iRangecc wrapRange_; |