summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-04 16:37:08 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-04 16:37:08 +0300
commit68b67c1de0754a763a138377b2b1250313a7eaf0 (patch)
treefc00cc6394e46cf33c319273c95b50006216a35a
parente53a1e52ee84b07397bff9ce92a59554fc8a1431 (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.c232
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) {
773iDeclareType(AttributedRun) 773iDeclareType(AttributedRun)
774 774
775struct Impl_AttributedRun { 775struct 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)
783iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, iColor fgColor) 784iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, iColor fgColor)
784 785
785struct Impl_AttributedText { 786struct 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
793iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor), 796iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor),
794 text, maxLen, font, fgColor) 797 text, maxLen, font, fgColor)
795 798
796static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, 799static 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
804static 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
816static 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
829size_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
807static void prepare_AttributedText_(iAttributedText *d) { 841static 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
878void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor) { 949void 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
887void deinit_AttributedText(iAttributedText *d) { 960void 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);