diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-04 16:37:08 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-04 16:37:08 +0300 |
commit | 68b67c1de0754a763a138377b2b1250313a7eaf0 (patch) | |
tree | fc00cc6394e46cf33c319273c95b50006216a35a | |
parent | e53a1e52ee84b07397bff9ce92a59554fc8a1431 (diff) |
Text: Refactoring AttributedText
AttributedText converts the text to a visual UTF-32 string. This allows it to be processed in a more straightforward fashion, and the indirection allows reordering via FriBidi.
-rw-r--r-- | src/ui/text.c | 232 |
1 files changed, 157 insertions, 75 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index ea53539e..aa2125b1 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -773,7 +773,8 @@ static iBool isControl_Char_(iChar c) { | |||
773 | iDeclareType(AttributedRun) | 773 | iDeclareType(AttributedRun) |
774 | 774 | ||
775 | struct Impl_AttributedRun { | 775 | struct Impl_AttributedRun { |
776 | iRangecc text; | 776 | iRangei visual; /* UTF-32 codepoint indices */ |
777 | // iRangecc source; | ||
777 | iFont * font; | 778 | iFont * font; |
778 | iColor fgColor; | 779 | iColor fgColor; |
779 | int lineBreaks; | 780 | int lineBreaks; |
@@ -783,70 +784,139 @@ iDeclareType(AttributedText) | |||
783 | iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, iColor fgColor) | 784 | iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, iColor fgColor) |
784 | 785 | ||
785 | struct Impl_AttributedText { | 786 | struct Impl_AttributedText { |
786 | iRangecc text; | 787 | iRangecc source; /* original source text */ |
787 | size_t maxLen; | 788 | size_t maxLen; |
788 | iFont * font; | 789 | iFont * font; |
789 | iColor fgColor; | 790 | iColor fgColor; |
790 | iArray runs; | 791 | iArray runs; |
792 | iArray visual; /* UTF-32 text in visual order (LTR) */ | ||
793 | iArray visualToSourceOffset; /* map visual character to an UTF-8 offset in the source text */ | ||
791 | }; | 794 | }; |
792 | 795 | ||
793 | iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor), | 796 | iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor), |
794 | text, maxLen, font, fgColor) | 797 | text, maxLen, font, fgColor) |
795 | 798 | ||
796 | static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, | 799 | static const char *sourcePtr_AttributedText_(const iAttributedText *d, int visualPos) { |
797 | const char *endAt) { | 800 | const int *mapToSource = constData_Array(&d->visualToSourceOffset); |
801 | return d->source.start + mapToSource[visualPos]; | ||
802 | } | ||
803 | |||
804 | static iRangecc sourceRange_AttributedText_(const iAttributedText *d, iRangei visual) { | ||
805 | const int *mapToSource = constData_Array(&d->visualToSourceOffset); | ||
806 | iRangecc range = { | ||
807 | d->source.start + mapToSource[visual.start], | ||
808 | d->source.start + mapToSource[visual.end] | ||
809 | }; | ||
810 | if (range.start > range.end) { | ||
811 | iSwap(const char *, range.start, range.end); | ||
812 | } | ||
813 | return range; | ||
814 | } | ||
815 | |||
816 | static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) { | ||
798 | iAttributedRun finishedRun = *run; | 817 | iAttributedRun finishedRun = *run; |
799 | finishedRun.text.end = endAt; | 818 | iAssert(endAt >= 0 && endAt <= size_Array(&d->visual)); |
800 | if (!isEmpty_Range(&finishedRun.text)) { | 819 | finishedRun.visual.end = endAt; |
820 | // finishedRun.source.end = sourcePtr_AttributedText_(d, endAt); | ||
821 | if (!isEmpty_Range(&finishedRun.visual)) { | ||
801 | pushBack_Array(&d->runs, &finishedRun); | 822 | pushBack_Array(&d->runs, &finishedRun); |
802 | run->lineBreaks = 0; | 823 | run->lineBreaks = 0; |
803 | } | 824 | } |
804 | run->text.start = endAt; | 825 | run->visual.start = endAt; |
826 | // run->source.start = finishedRun.source.end; | ||
827 | } | ||
828 | |||
829 | size_t length_Rangecc(const iRangecc d) { | ||
830 | size_t n = 0; | ||
831 | for (const char *i = d.start; i < d.end; ) { | ||
832 | iChar ch; | ||
833 | const int chLen = decodeBytes_MultibyteChar(i, d.end, &ch); | ||
834 | if (chLen <= 0) break; | ||
835 | i += chLen; | ||
836 | n++; | ||
837 | } | ||
838 | return n; | ||
805 | } | 839 | } |
806 | 840 | ||
807 | static void prepare_AttributedText_(iAttributedText *d) { | 841 | static void prepare_AttributedText_(iAttributedText *d) { |
808 | iAssert(isEmpty_Array(&d->runs)); | 842 | iAssert(isEmpty_Array(&d->runs)); |
809 | size_t avail = d->maxLen; | 843 | /* Prepare the visual (LTR) string. */ { |
810 | const char *chPos = d->text.start; | 844 | for (const char *ch = d->source.start; ch < d->source.end; ) { |
811 | iAttributedRun run = { .text = d->text, | 845 | iChar u32; |
812 | .font = d->font, | 846 | int len = decodeBytes_MultibyteChar(ch, d->source.end, &u32); |
847 | if (len <= 0) break; | ||
848 | pushBack_Array(&d->visual, &u32); | ||
849 | pushBack_Array(&d->visualToSourceOffset, &(int){ ch - d->source.start }); | ||
850 | ch += len; | ||
851 | } | ||
852 | /* Location of the terminating null. */ | ||
853 | pushBack_Array(&d->visualToSourceOffset, &(int){ d->source.end - d->source.start }); | ||
854 | #if defined (LAGRANGE_ENABLE_FRIBIDI) | ||
855 | /* TODO: Use FriBidi to reorder the codepoints. */ | ||
856 | |||
857 | #endif | ||
858 | } | ||
859 | // iRangecc srcText = d->source; | ||
860 | iRangei visText = { 0, size_Array(&d->visual) }; | ||
861 | size_t avail = d->maxLen; | ||
862 | //const char *srcPos = srcText.start; | ||
863 | iAttributedRun run = { .visual = visText, | ||
864 | // .source = d->source, | ||
865 | .font = d->font, | ||
813 | .fgColor = d->fgColor }; | 866 | .fgColor = d->fgColor }; |
814 | while (chPos < d->text.end) { | 867 | //while (chPos < visText.end) { |
815 | const char *currentPos = chPos; | 868 | const int *mapToSource = constData_Array(&d->visualToSourceOffset); |
816 | if (*chPos == 0x1b) { /* ANSI escape. */ | 869 | const iChar *visualText = constData_Array(&d->visual); |
817 | chPos++; | 870 | for (int pos = 0; pos < visText.end; pos++) { |
871 | const iChar ch = visualText[pos]; | ||
872 | //const char *currentPos = chPos; | ||
873 | if (ch == 0x1b) { /* ANSI escape. */ | ||
874 | pos++; | ||
875 | const char *srcPos = d->source.start + mapToSource[pos]; | ||
876 | /* Do a regexp match in the source text. */ | ||
818 | iRegExpMatch m; | 877 | iRegExpMatch m; |
819 | init_RegExpMatch(&m); | 878 | init_RegExpMatch(&m); |
820 | if (match_RegExp(text_.ansiEscape, chPos, d->text.end - chPos, &m)) { | 879 | if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) { |
821 | finishRun_AttributedText_(d, &run, currentPos); | 880 | finishRun_AttributedText_(d, &run, pos - 1); |
822 | run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), | 881 | run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), |
823 | tmParagraph_ColorId); | 882 | tmParagraph_ColorId); |
824 | chPos = end_RegExpMatch(&m); | 883 | //chPos = end_RegExpMatch(&m); |
825 | run.text.start = chPos; | 884 | //run.source.start = end_RegExpMatch(&m);// chPos; |
885 | pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); | ||
886 | iAssert(mapToSource[pos] == end_RegExpMatch(&m) - d->source.start); | ||
887 | /* The run continues after the escape sequence. */ | ||
888 | run.visual.start = pos--; /* loop increments `pos` */ | ||
889 | // const char *endPos = d->source.start + mapToSource[pos]; | ||
890 | // printf("ANSI Escape: {%s}\n", cstr_Rangecc((iRangecc){ srcPos, endPos })); | ||
826 | continue; | 891 | continue; |
827 | } | 892 | } |
828 | } | 893 | } |
829 | const iChar ch = nextChar_(&chPos, d->text.end); | 894 | //const iChar ch = nextChar_(&chPos, visText.end); |
830 | if (ch == '\v') { | 895 | if (ch == '\v') { |
831 | finishRun_AttributedText_(d, &run, currentPos); | 896 | finishRun_AttributedText_(d, &run, pos); |
832 | /* An internal color escape. */ | 897 | /* An internal color escape. */ |
833 | iChar esc = nextChar_(&chPos, d->text.end); | 898 | //iChar esc = nextChar_(&chPos, visText.end); |
899 | iChar esc = visualText[++pos]; | ||
900 | // srcPos++; | ||
834 | int colorNum = none_ColorId; /* default color */ | 901 | int colorNum = none_ColorId; /* default color */ |
835 | if (esc == '\v') { /* Extended range. */ | 902 | if (esc == '\v') { /* Extended range. */ |
836 | esc = nextChar_(&chPos, d->text.end) + asciiExtended_ColorEscape; | 903 | esc = visualText[++pos] + asciiExtended_ColorEscape; |
904 | // srcPos++; | ||
837 | colorNum = esc - asciiBase_ColorEscape; | 905 | colorNum = esc - asciiBase_ColorEscape; |
838 | } | 906 | } |
839 | else if (esc != 0x24) { /* ASCII Cancel */ | 907 | else if (esc != 0x24) { /* ASCII Cancel */ |
840 | colorNum = esc - asciiBase_ColorEscape; | 908 | colorNum = esc - asciiBase_ColorEscape; |
841 | } | 909 | } |
842 | run.text.start = chPos; | 910 | run.visual.start = pos + 1; |
911 | // run.source.start = srcPos; | ||
843 | run.fgColor = (colorNum >= 0 ? get_Color(colorNum) : d->fgColor); | 912 | run.fgColor = (colorNum >= 0 ? get_Color(colorNum) : d->fgColor); |
844 | //prevCh = 0; | 913 | //prevCh = 0; |
845 | continue; | 914 | continue; |
846 | } | 915 | } |
847 | if (ch == '\n') { | 916 | if (ch == '\n') { |
848 | finishRun_AttributedText_(d, &run, currentPos); | 917 | finishRun_AttributedText_(d, &run, pos); |
849 | run.text.start = chPos; | 918 | run.visual.start = pos + 1; |
919 | // run.source.start = srcPos + 1; | ||
850 | run.lineBreaks++; | 920 | run.lineBreaks++; |
851 | continue; | 921 | continue; |
852 | } | 922 | } |
@@ -855,7 +925,8 @@ static void prepare_AttributedText_(iAttributedText *d) { | |||
855 | } | 925 | } |
856 | if (avail-- == 0) { | 926 | if (avail-- == 0) { |
857 | /* TODO: Check the combining class; only count base characters here. */ | 927 | /* TODO: Check the combining class; only count base characters here. */ |
858 | run.text.end = currentPos; | 928 | run.visual.end = pos; |
929 | // run.source.end = srcPos; | ||
859 | break; | 930 | break; |
860 | } | 931 | } |
861 | iFont *currentFont = d->font; | 932 | iFont *currentFont = d->font; |
@@ -866,25 +937,29 @@ static void prepare_AttributedText_(iAttributedText *d) { | |||
866 | const iGlyph *glyph = glyph_Font_(currentFont, ch); | 937 | const iGlyph *glyph = glyph_Font_(currentFont, ch); |
867 | if (index_Glyph_(glyph) && glyph->font != run.font) { | 938 | if (index_Glyph_(glyph) && glyph->font != run.font) { |
868 | /* A different font is being used for this character. */ | 939 | /* A different font is being used for this character. */ |
869 | finishRun_AttributedText_(d, &run, currentPos); | 940 | finishRun_AttributedText_(d, &run, pos); |
870 | run.font = glyph->font; | 941 | run.font = glyph->font; |
871 | } | 942 | } |
872 | } | 943 | } |
873 | if (!isEmpty_Range(&run.text)) { | 944 | if (!isEmpty_Range(&run.visual)) { |
874 | pushBack_Array(&d->runs, &run); | 945 | pushBack_Array(&d->runs, &run); |
875 | } | 946 | } |
876 | } | 947 | } |
877 | 948 | ||
878 | void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor) { | 949 | void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor) { |
879 | d->text = text; | 950 | d->source = text; |
880 | d->maxLen = maxLen ? maxLen : iInvalidSize; | 951 | d->maxLen = maxLen ? maxLen : iInvalidSize; |
881 | d->font = font; | 952 | d->font = font; |
882 | d->fgColor = fgColor; | 953 | d->fgColor = fgColor; |
883 | init_Array(&d->runs, sizeof(iAttributedRun)); | 954 | init_Array(&d->runs, sizeof(iAttributedRun)); |
955 | init_Array(&d->visual, sizeof(iChar)); | ||
956 | init_Array(&d->visualToSourceOffset, sizeof(int)); | ||
884 | prepare_AttributedText_(d); | 957 | prepare_AttributedText_(d); |
885 | } | 958 | } |
886 | 959 | ||
887 | void deinit_AttributedText(iAttributedText *d) { | 960 | void deinit_AttributedText(iAttributedText *d) { |
961 | deinit_Array(&d->visual); | ||
962 | deinit_Array(&d->visualToSourceOffset); | ||
888 | deinit_Array(&d->runs); | 963 | deinit_Array(&d->runs); |
889 | } | 964 | } |
890 | 965 | ||
@@ -1029,12 +1104,15 @@ static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { | |||
1029 | init_AttributedText(&attrText, text, 0, d, (iColor){}); | 1104 | init_AttributedText(&attrText, text, 0, d, (iColor){}); |
1030 | /* We use AttributedText here so the glyph lookup matches the behavior during text drawing -- | 1105 | /* We use AttributedText here so the glyph lookup matches the behavior during text drawing -- |
1031 | glyphs may be selected from a font that's different than `d`. */ | 1106 | glyphs may be selected from a font that's different than `d`. */ |
1107 | const iChar *visualText = constData_Array(&attrText.visual); | ||
1032 | iConstForEach(Array, i, &attrText.runs) { | 1108 | iConstForEach(Array, i, &attrText.runs) { |
1033 | const iAttributedRun *run = i.value; | 1109 | const iAttributedRun *run = i.value; |
1034 | for (const char *chPos = run->text.start; chPos != run->text.end; ) { | 1110 | //for (const char *chPos = run->text.start; chPos != run->text.end; ) { |
1035 | const char *oldPos = chPos; | 1111 | for (int pos = run->visual.start; pos < run->visual.end; pos++) { |
1036 | const iChar ch = nextChar_(&chPos, text.end); | 1112 | const iChar ch = visualText[pos]; |
1037 | if (chPos == oldPos) break; /* don't get stuck */ | 1113 | // const char *oldPos = chPos; |
1114 | // const iChar ch = nextChar_(&chPos, text.end); | ||
1115 | // if (chPos == oldPos) break; /* don't get stuck */ | ||
1038 | if (!isSpace_Char(ch) && !isControl_Char_(ch)) { | 1116 | if (!isSpace_Char(ch) && !isControl_Char_(ch)) { |
1039 | const uint32_t glyphIndex = glyphIndex_Font_(d, ch); | 1117 | const uint32_t glyphIndex = glyphIndex_Font_(d, ch); |
1040 | if (glyphIndex) { | 1118 | if (glyphIndex) { |
@@ -1157,32 +1235,39 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1157 | /* Initialize the wrap range. */ | 1235 | /* Initialize the wrap range. */ |
1158 | args->wrap->wrapRange_ = args->text; | 1236 | args->wrap->wrapRange_ = args->text; |
1159 | } | 1237 | } |
1160 | const char *wrapResumePos = NULL; | 1238 | int wrapResumePos = -1; |
1161 | iBool willAbortDueToWrap = iFalse; | 1239 | iBool willAbortDueToWrap = iFalse; |
1240 | const iRangecc sourceText = attrText.source; | ||
1241 | const iChar * visualText = constData_Array(&attrText.visual); | ||
1242 | const int * visToSource = constData_Array(&attrText.visualToSourceOffset); | ||
1162 | for (size_t runIndex = 0; runIndex < size_Array(&attrText.runs); runIndex++) { | 1243 | for (size_t runIndex = 0; runIndex < size_Array(&attrText.runs); runIndex++) { |
1163 | const iAttributedRun *run = at_Array(&attrText.runs, runIndex); | 1244 | const iAttributedRun *run = at_Array(&attrText.runs, runIndex); |
1164 | iRangecc runText = run->text; | 1245 | iRangei runVisual = run->visual; |
1165 | if (wrapResumePos) { | 1246 | //iRangecc runSource = run->source; |
1247 | if (wrapResumePos >= 0) { | ||
1166 | xCursor = 0.0f; | 1248 | xCursor = 0.0f; |
1167 | yCursor += d->height; | 1249 | yCursor += d->height; |
1168 | runText.start = wrapResumePos; | 1250 | runVisual.start = wrapResumePos; |
1169 | wrapResumePos = NULL; | 1251 | wrapResumePos = NULL; |
1170 | } | 1252 | } |
1171 | else if (run->lineBreaks) { | 1253 | else if (run->lineBreaks) { |
1172 | for (int i = 0; i < run->lineBreaks; i++) { | 1254 | for (int i = 0; i < run->lineBreaks; i++) { |
1173 | /* One callback for each "wrapped" line even if they're empty. */ | 1255 | /* One callback for each "wrapped" line even if they're empty. */ |
1174 | if (!notify_WrapText_(args->wrap, run->text.start, iRound(xCursor))) { | 1256 | if (!notify_WrapText_(args->wrap, |
1257 | sourcePtr_AttributedText_(&attrText, run->visual.start), | ||
1258 | iRound(xCursor))) { | ||
1175 | break; | 1259 | break; |
1176 | } | 1260 | } |
1177 | xCursor = 0.0f; | 1261 | xCursor = 0.0f; |
1178 | yCursor += d->height; | 1262 | yCursor += d->height; |
1179 | } | 1263 | } |
1180 | } | 1264 | } |
1265 | const iRangecc runSource = sourceRange_AttributedText_(&attrText, runVisual); | ||
1181 | hb_buffer_clear_contents(hbBuf); | 1266 | hb_buffer_clear_contents(hbBuf); |
1182 | iBool isRunRTL = iFalse; | 1267 | iBool isRunRTL = iFalse; |
1183 | /* Cluster values are used to determine offset inside the UTF-8 source string. */ | 1268 | /* Cluster values are used to determine offset inside the UTF-8 source string. */ |
1184 | //hb_buffer_set_cluster_level(hbBuf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); | 1269 | //hb_buffer_set_cluster_level(hbBuf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); |
1185 | #if defined (LAGRANGE_ENABLE_FRIBIDI) | 1270 | #if 0 && defined (LAGRANGE_ENABLE_FRIBIDI) |
1186 | /* Reorder to visual order. */ { | 1271 | /* Reorder to visual order. */ { |
1187 | iArray u32; | 1272 | iArray u32; |
1188 | iArray logToRun; | 1273 | iArray logToRun; |
@@ -1228,14 +1313,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1228 | deinit_Array(&u32); | 1313 | deinit_Array(&u32); |
1229 | } | 1314 | } |
1230 | #else /* !defined (LAGRANGE_ENABLE_FRIBIDI) */ | 1315 | #else /* !defined (LAGRANGE_ENABLE_FRIBIDI) */ |
1231 | for (const char *pos = runText.start; pos < runText.end; ) { | 1316 | for (int pos = runVisual.start; pos < runVisual.end; pos++) { |
1232 | iChar ucp = 0; | 1317 | hb_buffer_add(hbBuf, visualText[pos], pos); |
1233 | const int len = decodeBytes_MultibyteChar(pos, runText.end, &ucp); | ||
1234 | if (len > 0) { | ||
1235 | hb_buffer_add(hbBuf, ucp, pos - runText.start); | ||
1236 | pos += len; | ||
1237 | } | ||
1238 | else break; | ||
1239 | } | 1318 | } |
1240 | #endif | 1319 | #endif |
1241 | hb_buffer_set_content_type(hbBuf, HB_BUFFER_CONTENT_TYPE_UNICODE); | 1320 | hb_buffer_set_content_type(hbBuf, HB_BUFFER_CONTENT_TYPE_UNICODE); |
@@ -1246,17 +1325,17 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1246 | unsigned int glyphCount = 0; | 1325 | unsigned int glyphCount = 0; |
1247 | const hb_glyph_info_t * glyphInfo = hb_buffer_get_glyph_infos(hbBuf, &glyphCount); | 1326 | const hb_glyph_info_t * glyphInfo = hb_buffer_get_glyph_infos(hbBuf, &glyphCount); |
1248 | hb_glyph_position_t * glyphPos = hb_buffer_get_glyph_positions(hbBuf, &glyphCount); | 1327 | hb_glyph_position_t * glyphPos = hb_buffer_get_glyph_positions(hbBuf, &glyphCount); |
1249 | const char *breakPos = NULL; | 1328 | int breakPos = -1; |
1250 | iBool isSoftHyphenBreak = iFalse; | 1329 | // iBool isSoftHyphenBreak = iFalse; |
1251 | /* Fit foreign glyphs into the expected monospacing. */ | 1330 | /* Fit foreign glyphs into the expected monospacing. */ |
1252 | if (isMonospaced) { | 1331 | if (isMonospaced) { |
1253 | for (unsigned int i = 0; i < glyphCount; ++i) { | 1332 | for (unsigned int i = 0; i < glyphCount; ++i) { |
1254 | const hb_glyph_info_t *info = &glyphInfo[i]; | 1333 | const hb_glyph_info_t *info = &glyphInfo[i]; |
1255 | const hb_codepoint_t glyphId = info->codepoint; | 1334 | // const hb_codepoint_t glyphId = info->codepoint; |
1256 | const char *textPos = runText.start + info->cluster; | 1335 | // const char *textPos = sourceText.start + visToSource[info->cluster]; |
1257 | if (run->font != d) { | 1336 | if (glyphPos[i].x_advance > 0 && run->font != d) { |
1258 | iChar ch = 0; | 1337 | const iChar ch = visualText[info->cluster]; |
1259 | decodeBytes_MultibyteChar(textPos, runText.end, &ch); | 1338 | // decodeBytes_MultibyteChar(textPos, runSource.end, &ch); |
1260 | if (isPictograph_Char(ch) || isEmoji_Char(ch)) { | 1339 | if (isPictograph_Char(ch) || isEmoji_Char(ch)) { |
1261 | const float dw = run->font->xScale * glyphPos[i].x_advance - monoAdvance; | 1340 | const float dw = run->font->xScale * glyphPos[i].x_advance - monoAdvance; |
1262 | glyphPos[i].x_offset -= dw / 2 / run->font->xScale - 1; | 1341 | glyphPos[i].x_offset -= dw / 2 / run->font->xScale - 1; |
@@ -1269,46 +1348,46 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1269 | the line, and re-run the loop resuming the run from the wrap point. */ | 1348 | the line, and re-run the loop resuming the run from the wrap point. */ |
1270 | if (args->wrap && args->wrap->maxWidth > 0) { | 1349 | if (args->wrap && args->wrap->maxWidth > 0) { |
1271 | float x = xCursor; | 1350 | float x = xCursor; |
1272 | const char *safeBreak = NULL; | 1351 | int safeBreak = -1; |
1273 | iChar prevCh = 0; | 1352 | iChar prevCh = 0; |
1274 | for (unsigned int i = 0; i < glyphCount; i++) { | 1353 | for (unsigned int i = 0; i < glyphCount; i++) { |
1275 | const hb_glyph_info_t *info = &glyphInfo[i]; | 1354 | const hb_glyph_info_t *info = &glyphInfo[i]; |
1276 | const hb_codepoint_t glyphId = info->codepoint; | 1355 | const hb_codepoint_t glyphId = info->codepoint; |
1277 | const char *textPos = runText.start + info->cluster; | 1356 | // const char *textPos = sourceText.start + info->cluster; |
1278 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); | 1357 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); |
1279 | const int glyphFlags = hb_glyph_info_get_glyph_flags(info); | 1358 | const int glyphFlags = hb_glyph_info_get_glyph_flags(info); |
1280 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; | 1359 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; |
1281 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; | 1360 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; |
1282 | if (args->wrap->mode == word_WrapTextMode) { | 1361 | if (args->wrap->mode == word_WrapTextMode) { |
1283 | /* When word wrapping, only consider certain places breakable. */ | 1362 | /* When word wrapping, only consider certain places breakable. */ |
1284 | iChar ch = 0; | 1363 | const iChar ch = visualText[info->cluster]; |
1285 | decodeBytes_MultibyteChar(textPos, runText.end, &ch); | 1364 | // decodeBytes_MultibyteChar(textPos, runSource.end, &ch); |
1286 | /*if (prevCh == 0xad) { | 1365 | /*if (prevCh == 0xad) { |
1287 | safeBreak = textPos; | 1366 | safeBreak = textPos; |
1288 | isSoftHyphenBreak = iTrue; | 1367 | isSoftHyphenBreak = iTrue; |
1289 | } | 1368 | } |
1290 | else*/ | 1369 | else*/ |
1291 | if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { | 1370 | if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { |
1292 | safeBreak = textPos; | 1371 | safeBreak = i; |
1293 | isSoftHyphenBreak = iFalse; | 1372 | // isSoftHyphenBreak = iFalse; |
1294 | } | 1373 | } |
1295 | else if (isSpace_Char(ch)) { | 1374 | else if (isSpace_Char(ch)) { |
1296 | safeBreak = textPos; | 1375 | safeBreak = i; |
1297 | isSoftHyphenBreak = iFalse; | 1376 | // isSoftHyphenBreak = iFalse; |
1298 | } | 1377 | } |
1299 | prevCh = ch; | 1378 | prevCh = ch; |
1300 | } | 1379 | } |
1301 | else { | 1380 | else { |
1302 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { | 1381 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { |
1303 | safeBreak = textPos; | 1382 | safeBreak = i; |
1304 | } | 1383 | } |
1305 | } | 1384 | } |
1306 | if (x + xOffset + glyph->d[0].x + glyph->rect[0].size.x > args->wrap->maxWidth) { | 1385 | if (x + xOffset + glyph->d[0].x + glyph->rect[0].size.x > args->wrap->maxWidth) { |
1307 | if (safeBreak) { | 1386 | if (safeBreak) { |
1308 | breakPos = safeBreak; | 1387 | breakPos = safeBreak + 1; |
1309 | } | 1388 | } |
1310 | else { | 1389 | else { |
1311 | breakPos = textPos; | 1390 | breakPos = i + 1; |
1312 | } | 1391 | } |
1313 | break; | 1392 | break; |
1314 | } | 1393 | } |
@@ -1320,8 +1399,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1320 | } | 1399 | } |
1321 | } | 1400 | } |
1322 | /* Make a callback for each wrapped line. */ | 1401 | /* Make a callback for each wrapped line. */ |
1323 | if (breakPos) { | 1402 | if (breakPos >= 0) { |
1324 | if (!notify_WrapText_(args->wrap, breakPos, iRound(x))) { | 1403 | if (!notify_WrapText_(args->wrap, |
1404 | sourcePtr_AttributedText_(&attrText, breakPos), | ||
1405 | iRound(x))) { | ||
1325 | willAbortDueToWrap = iTrue; | 1406 | willAbortDueToWrap = iTrue; |
1326 | } | 1407 | } |
1327 | } | 1408 | } |
@@ -1336,9 +1417,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1336 | for (unsigned int i = 0; i < glyphCount; i++) { | 1417 | for (unsigned int i = 0; i < glyphCount; i++) { |
1337 | const hb_glyph_info_t *info = &glyphInfo[i]; | 1418 | const hb_glyph_info_t *info = &glyphInfo[i]; |
1338 | const hb_codepoint_t glyphId = info->codepoint; | 1419 | const hb_codepoint_t glyphId = info->codepoint; |
1339 | const char *textPos = runText.start + info->cluster; | 1420 | // const char *textPos = runText.start + info->cluster; |
1340 | iAssert(textPos >= runText.start); | 1421 | // iAssert(textPos >= runText.start); |
1341 | iAssert(textPos < runText.end); | 1422 | // iAssert(textPos < runText.end); |
1423 | const int visPos = info->cluster; | ||
1342 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; | 1424 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; |
1343 | const float yOffset = run->font->yScale * glyphPos[i].y_offset; | 1425 | const float yOffset = run->font->yScale * glyphPos[i].y_offset; |
1344 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; | 1426 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; |
@@ -1346,9 +1428,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1346 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); | 1428 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); |
1347 | const float xf = xCursor + xOffset; | 1429 | const float xf = xCursor + xOffset; |
1348 | const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0; | 1430 | const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0; |
1349 | if (textPos == breakPos) { | 1431 | if (visPos == breakPos) { |
1350 | runIndex--; /* stay on the same run */ | 1432 | runIndex--; /* stay on the same run */ |
1351 | wrapResumePos = textPos; | 1433 | wrapResumePos = visPos; |
1352 | break; | 1434 | break; |
1353 | } | 1435 | } |
1354 | /* Draw the glyph. */ { | 1436 | /* Draw the glyph. */ { |
@@ -1368,7 +1450,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1368 | bounds.size.x = iMax(bounds.size.x, dst.x + dst.w - orig.x); | 1450 | bounds.size.x = iMax(bounds.size.x, dst.x + dst.w - orig.x); |
1369 | bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height); | 1451 | bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height); |
1370 | } | 1452 | } |
1371 | if (mode & draw_RunMode && *textPos != 0x20) { | 1453 | if (mode & draw_RunMode && visualText[visPos] != 0x20) { |
1372 | if (!isRasterized_Glyph_(glyph, hoff)) { | 1454 | if (!isRasterized_Glyph_(glyph, hoff)) { |
1373 | cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ | 1455 | cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ |
1374 | glyph = glyphByIndex_Font_(run->font, glyphId); | 1456 | glyph = glyphByIndex_Font_(run->font, glyphId); |
@@ -1757,7 +1839,7 @@ iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo | |||
1757 | strRemain--; | 1839 | strRemain--; |
1758 | continue; | 1840 | continue; |
1759 | } | 1841 | } |
1760 | iCharBuf buf; | 1842 | iCharBuf buf = { .size = zero_I2() }; |
1761 | buf.pixels = stbtt_GetCodepointBitmap( | 1843 | buf.pixels = stbtt_GetCodepointBitmap( |
1762 | &font, xScale, scale, i.value, &buf.size.x, &buf.size.y, 0, &buf.dy); | 1844 | &font, xScale, scale, i.value, &buf.size.x, &buf.size.y, 0, &buf.dy); |
1763 | stbtt_GetCodepointHMetrics(&font, i.value, &buf.advance, NULL); | 1845 | stbtt_GetCodepointHMetrics(&font, i.value, &buf.advance, NULL); |