summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-13 10:40:35 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-13 10:40:35 +0300
commit0076a605540337bd89d37b7887541144e44b20f3 (patch)
tree591ed3fb39d1ee6590609657d2e6ca1dd2255caa /src/ui
parentf49a2ed900aab6ff81f0006bd0b5875c39f454c3 (diff)
Text: Working on bidi text wrapping
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/text.c520
-rw-r--r--src/ui/text.h8
2 files changed, 181 insertions, 347 deletions
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) {
773iDeclareType(AttributedRun) 773iDeclareType(AttributedRun)
774 774
775struct Impl_AttributedRun { 775struct 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
782iDeclareType(AttributedText) 786iDeclareType(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
796iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor), 803iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor),
797 text, maxLen, font, fgColor) 804 text, maxLen, font, fgColor)
798 805
799static const char *sourcePtr_AttributedText_(const iAttributedText *d, int visualPos) { 806static 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
804static iRangecc sourceRange_AttributedText_(const iAttributedText *d, iRangei visual) { 811static 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
816static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) { 821static 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
827size_t length_Rangecc(const iRangecc d) { 832size_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
844static enum iFontId fontId_Text_(const iFont *font) {
845 return (enum iFontId) (font - text_.fonts);
846}
847
839static void prepare_AttributedText_(iAttributedText *d) { 848static 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
985void deinit_AttributedText(iAttributedText *d) { 999void 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
1168static enum iFontId fontId_Text_(const iFont *font) {
1169 return (enum iFontId) (font - text_.fonts);
1170}
1171
1172iDeclareType(RunArgs) 1187iDeclareType(RunArgs)
1173 1188
1174struct Impl_RunArgs { 1189struct 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
1189static iBool notify_WrapText_(iWrapText *d, const char *ending, int advance) { 1205static 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
1243static void init_GlyphBuffer_(iGlyphBuffer *d, iFont *font, const iChar *visualText) { 1263static 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
1264static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapVisRange) { 1286static 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
1682iInt2 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
1690iTextMetrics measureRange_Text(int fontId, iRangecc text) { 1603iTextMetrics 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
1712iInt2 measure_Text(int fontId, const char *text) {
1713 return measureRange_Text(fontId, range_CStr(text));
1714}
1715#endif
1716
1717void cache_Text(int fontId, iRangecc text) { 1624void 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 1636static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int origin, int advance) {
1730iInt2 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
1741static 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
1770iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { 1653iInt2 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
1795iInt2 advance_Text(int fontId, const char *text) {
1796 return advanceRange_Text(fontId, range_CStr(text));
1797}
1798
1799iInt2 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
1813static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { 1677static 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
1898iInt2 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
1911void drawBoundRange_Text(int fontId, iInt2 pos, int boundWidth, int color, iRangecc text) { 1761void 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
1974iInt2 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
1983iTextMetrics measure_WrapText(iWrapText *d, int fontId) { 1823iTextMetrics 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
148void resetFonts_Text (void); 148void resetFonts_Text (void);
149 149
150int lineHeight_Text (int fontId); 150int lineHeight_Text (int fontId);
151//iInt2 measure_Text (int fontId, const char *text);
152//iInt2 measureRange_Text (int fontId, iRangecc text);
153iRect visualBounds_Text (int fontId, iRangecc text); 151iRect 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
159iDeclareType(TextMetrics) 153iDeclareType(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_;