summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-13 17:50:11 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-13 17:50:11 +0300
commitd259b1f6205f840a261fb5eb7bae63be6566beef (patch)
tree7c709f997024c0f6aceda24af8de1ca650900a56 /src/ui
parent07156681f635a18f0b4e8e76fce60a723c8cddd8 (diff)
Text: Bidi text wrapping
There is still some weirdness with wraps that occur inside a bidi region. The problem is that text drawing is done later, in smaller segments, without knowledge of the paragraph base direction. The base direction should be saved into each GmRun as a flag.
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c6
-rw-r--r--src/ui/text.c196
-rw-r--r--src/ui/text.h2
3 files changed, 158 insertions, 46 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 962ccc3b..3c6c0039 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -4194,7 +4194,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4194 height_Rect(run->visBounds), 4194 height_Rect(run->visBounds),
4195 tmQuoteIcon_ColorId); 4195 tmQuoteIcon_ColorId);
4196 } 4196 }
4197 drawRange_Text(run->font, visPos, /*width_Rect(run->bounds),*/ fg, run->text); 4197 drawBoundRange_Text(run->font, visPos, width_Rect(run->visBounds), fg, run->text);
4198 runDrawn:; 4198 runDrawn:;
4199 } 4199 }
4200 /* Presentation of links. */ 4200 /* Presentation of links. */
@@ -4326,8 +4326,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4326 } 4326 }
4327 } 4327 }
4328 } 4328 }
4329 drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); 4329// drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId);
4330 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); 4330// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId);
4331} 4331}
4332 4332
4333static int drawSideRect_(iPaint *p, iRect rect) { 4333static int drawSideRect_(iPaint *p, iRect rect) {
diff --git a/src/ui/text.c b/src/ui/text.c
index f5ee9860..e1638368 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -166,6 +166,12 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale,
166 else if (data == &fontNotoSansArabicUIRegular_Embedded) { 166 else if (data == &fontNotoSansArabicUIRegular_Embedded) {
167 d->family = notoSansArabic_TextFont; 167 d->family = notoSansArabic_TextFont;
168 } 168 }
169 else if (data == &fontNotoSansSymbolsRegular_Embedded ||
170 data == &fontNotoSansSymbols2Regular_Embedded ||
171 data == &fontNotoEmojiRegular_Embedded ||
172 data == &fontSmolEmojiRegular_Embedded) {
173 d->family = emojiAndSymbols_TextFont;
174 }
169 d->isMonospaced = isMonospaced; 175 d->isMonospaced = isMonospaced;
170 d->height = height; 176 d->height = height;
171 iZap(d->font); 177 iZap(d->font);
@@ -659,10 +665,12 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
659 symbols_FontId 665 symbols_FontId
660 }; 666 };
661 /* First fallback is Smol Emoji. */ 667 /* First fallback is Smol Emoji. */
662 iForIndices(i, fallbacks) { 668 if (ch != 0x20) {
663 iFont *fallback = font_Text_(fallbacks[i] + d->sizeId); 669 iForIndices(i, fallbacks) {
664 if (fallback != d && (*glyphIndex = glyphIndex_Font_(fallback, ch)) != 0) { 670 iFont *fallback = font_Text_(fallbacks[i] + d->sizeId);
665 return fallback; 671 if (fallback != d && (*glyphIndex = glyphIndex_Font_(fallback, ch)) != 0) {
672 return fallback;
673 }
666 } 674 }
667 } 675 }
668 /* Try Simplified Chinese. */ 676 /* Try Simplified Chinese. */
@@ -798,6 +806,7 @@ struct Impl_AttributedText {
798 iArray visualToLogical; /* map visual index to logical index */ 806 iArray visualToLogical; /* map visual index to logical index */
799 iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */ 807 iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */
800 char * bidiLevels; 808 char * bidiLevels;
809 iBool isBaseRTL;
801}; 810};
802 811
803iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor), 812iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor),
@@ -818,10 +827,23 @@ static iRangecc sourceRange_AttributedText_(const iAttributedText *d, iRangei lo
818 return range; 827 return range;
819} 828}
820 829
830static iBool isAllSpace_AttributedText_(const iAttributedText *d, iRangei range) {
831 const iChar *logicalText = constData_Array(&d->logical);
832 for (size_t i = range.start; i < range.end; i++) {
833 if (logicalText[i] != 0x20) {
834 return iFalse;
835 }
836 }
837 return iTrue;
838}
839
821static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) { 840static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) {
822 iAttributedRun finishedRun = *run; 841 iAttributedRun finishedRun = *run;
823 iAssert(endAt >= 0 && endAt <= size_Array(&d->logical)); 842 iAssert(endAt >= 0 && endAt <= size_Array(&d->logical));
824 finishedRun.logical.end = endAt; 843 finishedRun.logical.end = endAt;
844// if (isAllSpace_AttributedText_(d, finishedRun.logical)) {
845// return;
846// }
825 if (!isEmpty_Range(&finishedRun.logical)) { 847 if (!isEmpty_Range(&finishedRun.logical)) {
826 pushBack_Array(&d->runs, &finishedRun); 848 pushBack_Array(&d->runs, &finishedRun);
827 run->flags.isLineBreak = iFalse; 849 run->flags.isLineBreak = iFalse;
@@ -874,6 +896,7 @@ static void prepare_AttributedText_(iAttributedText *d) {
874 data_Array(&d->logicalToVisual), 896 data_Array(&d->logicalToVisual),
875 data_Array(&d->visualToLogical), 897 data_Array(&d->visualToLogical),
876 (FriBidiLevel *) d->bidiLevels); 898 (FriBidiLevel *) d->bidiLevels);
899 d->isBaseRTL = FRIBIDI_IS_RTL(baseDir);
877#endif 900#endif
878 } 901 }
879 /* The mapping needs to include the terminating NULL position. */ { 902 /* The mapping needs to include the terminating NULL position. */ {
@@ -887,16 +910,19 @@ static void prepare_AttributedText_(iAttributedText *d) {
887 const int * logToVis = constData_Array(&d->logicalToVisual); 910 const int * logToVis = constData_Array(&d->logicalToVisual);
888 const iChar * logicalText = constData_Array(&d->logical); 911 const iChar * logicalText = constData_Array(&d->logical);
889// const iChar * visualText = constData_Array(&d->visual); 912// const iChar * visualText = constData_Array(&d->visual);
890 iBool isRTL = iFalse; 913 iBool isRTL = d->isBaseRTL;
914 int numNonSpace = 0;
891 for (int pos = 0; pos < length; pos++) { 915 for (int pos = 0; pos < length; pos++) {
892 const iChar ch = logicalText[pos]; 916 const iChar ch = logicalText[pos];
893 const int visPos = logToVis[pos]; 917 const int visPos = logToVis[pos];
894#if defined (LAGRANGE_ENABLE_FRIBIDI) 918#if defined (LAGRANGE_ENABLE_FRIBIDI)
895 const iBool isNeutral = FRIBIDI_IS_NEUTRAL(d->bidiLevels[visPos]); 919 const char lev = d->bidiLevels[pos];
920 const iBool isNeutral = FRIBIDI_IS_NEUTRAL(lev);
896 if (d->bidiLevels && !isNeutral) { 921 if (d->bidiLevels && !isNeutral) {
897 iBool rtl = FRIBIDI_IS_RTL(d->bidiLevels[visPos]) != 0; 922 iBool rtl = FRIBIDI_IS_RTL(lev) != 0;
898 if (rtl != isRTL) { 923 if (rtl != isRTL) {
899 /* Direction changes; must end the current run. */ 924 /* Direction changes; must end the current run. */
925// printf("dir change at %zu: %lc U+%04X\n", pos, ch, ch);
900 finishRun_AttributedText_(d, &run, pos); 926 finishRun_AttributedText_(d, &run, pos);
901 isRTL = rtl; 927 isRTL = rtl;
902 } 928 }
@@ -954,8 +980,15 @@ static void prepare_AttributedText_(iAttributedText *d) {
954 run.logical.end = pos; 980 run.logical.end = pos;
955 break; 981 break;
956 } 982 }
983 if (ch == 0x20) {
984 if (run.font->family == emojiAndSymbols_TextFont) {
985 finishRun_AttributedText_(d, &run, pos);
986 run.font = d->font; /* never use space from the symbols font, it's too wide */
987 }
988 continue;
989 }
957 iFont *currentFont = d->font; 990 iFont *currentFont = d->font;
958 if (run.font->family == notoSansArabic_TextFont && (isSpace_Char(ch) || isPunct_Char(ch))) { 991 if (run.font->family == notoSansArabic_TextFont && isPunct_Char(ch)) {
959 currentFont = run.font; /* remain as Arabic for whitespace */ 992 currentFont = run.font; /* remain as Arabic for whitespace */
960 } 993 }
961 const iGlyph *glyph = glyph_Font_(currentFont, ch); 994 const iGlyph *glyph = glyph_Font_(currentFont, ch);
@@ -963,20 +996,23 @@ static void prepare_AttributedText_(iAttributedText *d) {
963 /* A different font is being used for this character. */ 996 /* A different font is being used for this character. */
964 finishRun_AttributedText_(d, &run, pos); 997 finishRun_AttributedText_(d, &run, pos);
965 run.font = glyph->font; 998 run.font = glyph->font;
966 printf("changing font to %d at pos %u (%lc)\n", fontId_Text_(run.font), pos, (int)logicalText[pos]); 999#if 0
1000 printf("changing font to %d at pos %u (%lc) U+%04X\n", fontId_Text_(run.font), pos, (int)logicalText[pos],
1001 (int)logicalText[pos]);
1002#endif
967 } 1003 }
968 } 1004 }
969 if (!isEmpty_Range(&run.logical)) { 1005 if (!isEmpty_Range(&run.logical)) {
970 pushBack_Array(&d->runs, &run); 1006 pushBack_Array(&d->runs, &run);
971 } 1007 }
972#if 1 1008#if 0
973 printf("[AttributedText] %zu runs:\n", size_Array(&d->runs)); 1009 printf("[AttributedText] %zu runs:\n", size_Array(&d->runs));
974 iConstForEach(Array, i, &d->runs) { 1010 iConstForEach(Array, i, &d->runs) {
975 const iAttributedRun *run = i.value; 1011 const iAttributedRun *run = i.value;
976 printf(" %zu %s %d...%d {%s}\n", index_ArrayConstIterator(&i), 1012 printf(" %zu %s %d...%d {%s}\n", index_ArrayConstIterator(&i),
977 run->flags.isRTL ? "<-" : "->", 1013 run->flags.isRTL ? "<-" : "->",
978 run->logical.start, run->logical.end, 1014 run->logical.start, run->logical.end,
979 !run->flags.isRTL? cstr_Rangecc(sourceRange_AttributedText_(d, run->logical)) : "---"); 1015 cstr_Rangecc(sourceRange_AttributedText_(d, run->logical)));
980 } 1016 }
981#endif 1017#endif
982} 1018}
@@ -993,6 +1029,7 @@ void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont
993 init_Array(&d->visualToLogical, sizeof(int)); 1029 init_Array(&d->visualToLogical, sizeof(int));
994 init_Array(&d->logicalToSourceOffset, sizeof(int)); 1030 init_Array(&d->logicalToSourceOffset, sizeof(int));
995 d->bidiLevels = NULL; 1031 d->bidiLevels = NULL;
1032 d->isBaseRTL = iFalse;
996 prepare_AttributedText_(d); 1033 prepare_AttributedText_(d);
997} 1034}
998 1035
@@ -1210,7 +1247,7 @@ static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int
1210 iAssert(range.start <= range.end); 1247 iAssert(range.start <= range.end);
1211 const iBool result = d->wrapFunc(d, range, origin, advance); 1248 const iBool result = d->wrapFunc(d, range, origin, advance);
1212 if (result) { 1249 if (result) {
1213 d->wrapRange_.start = range.end; 1250 d->wrapRange_.start = end;
1214 } 1251 }
1215 else { 1252 else {
1216 d->wrapRange_ = iNullRange; 1253 d->wrapRange_ = iNullRange;
@@ -1253,19 +1290,18 @@ iDeclareType(GlyphBuffer)
1253struct Impl_GlyphBuffer { 1290struct Impl_GlyphBuffer {
1254 hb_buffer_t * hb; 1291 hb_buffer_t * hb;
1255 iFont * font; 1292 iFont * font;
1256 const iChar * visualText; 1293 const iChar * logicalText;
1257 const int * visToLog; 1294// const int * visToLog;
1258 hb_glyph_info_t * glyphInfo; 1295 hb_glyph_info_t * glyphInfo;
1259 hb_glyph_position_t *glyphPos; 1296 hb_glyph_position_t *glyphPos;
1260 unsigned int glyphCount; 1297 unsigned int glyphCount;
1261}; 1298};
1262 1299
1263static void init_GlyphBuffer_(iGlyphBuffer *d, iFont *font, const iChar *visualText, 1300static void init_GlyphBuffer_(iGlyphBuffer *d, iFont *font, const iChar *logicalText) {
1264 const int *visToLog) {
1265 d->hb = hb_buffer_create(); 1301 d->hb = hb_buffer_create();
1266 d->font = font; 1302 d->font = font;
1267 d->visualText = visualText; 1303 d->logicalText = logicalText;
1268 d->visToLog = visToLog; 1304// d->visToLog = visToLog;
1269 d->glyphInfo = NULL; 1305 d->glyphInfo = NULL;
1270 d->glyphPos = NULL; 1306 d->glyphPos = NULL;
1271 d->glyphCount = 0; 1307 d->glyphCount = 0;
@@ -1286,14 +1322,10 @@ static void shape_GlyphBuffer_(iGlyphBuffer *d) {
1286static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapPosRange) { 1322static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapPosRange) {
1287 float x = 0.0f; 1323 float x = 0.0f;
1288 for (unsigned int i = 0; i < d->glyphCount; i++) { 1324 for (unsigned int i = 0; i < d->glyphCount; i++) {
1289 const int visPos = d->glyphInfo[i].cluster; 1325 const int logPos = d->glyphInfo[i].cluster;
1290 const int logPos = d->visToLog[visPos]; 1326 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) {
1291 if (logPos < wrapPosRange.start) {
1292 continue; 1327 continue;
1293 } 1328 }
1294 if (logPos >= wrapPosRange.end) {
1295 break;
1296 }
1297 x += d->font->xScale * d->glyphPos[i].x_advance; 1329 x += d->font->xScale * d->glyphPos[i].x_advance;
1298 if (i + 1 < d->glyphCount) { 1330 if (i + 1 < d->glyphCount) {
1299 x += horizKern_Font_(d->font, 1331 x += horizKern_Font_(d->font,
@@ -1310,7 +1342,7 @@ static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont)
1310 for (unsigned int i = 0; i < d->glyphCount; ++i) { 1342 for (unsigned int i = 0; i < d->glyphCount; ++i) {
1311 const hb_glyph_info_t *info = d->glyphInfo + i; 1343 const hb_glyph_info_t *info = d->glyphInfo + i;
1312 if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) { 1344 if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) {
1313 const iChar ch = d->visualText[info->cluster]; 1345 const iChar ch = d->logicalText[info->cluster];
1314 if (isPictograph_Char(ch) || isEmoji_Char(ch)) { 1346 if (isPictograph_Char(ch) || isEmoji_Char(ch)) {
1315 const float dw = d->font->xScale * d->glyphPos[i].x_advance - monoAdvance; 1347 const float dw = d->font->xScale * d->glyphPos[i].x_advance - monoAdvance;
1316 d->glyphPos[i].x_offset -= dw / 2 / d->font->xScale - 1; 1348 d->glyphPos[i].x_offset -= dw / 2 / d->font->xScale - 1;
@@ -1320,6 +1352,18 @@ static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont)
1320 } 1352 }
1321} 1353}
1322 1354
1355iLocalDef iChar flipBracket_(iChar c) {
1356 if (c == '(') return ')';
1357 if (c == ')') return '(';
1358 if (c == '{') return '}';
1359 if (c == '}') return '{';
1360 if (c == '[') return ']';
1361 if (c == ']') return '[';
1362 if (c == '<') return '>';
1363 if (c == '>') return '<';
1364 return c;
1365}
1366
1323static iRect run_Font_(iFont *d, const iRunArgs *args) { 1367static iRect run_Font_(iFont *d, const iRunArgs *args) {
1324 const int mode = args->mode; 1368 const int mode = args->mode;
1325 const iInt2 orig = args->pos; 1369 const iInt2 orig = args->pos;
@@ -1355,13 +1399,20 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1355 iConstForEach(Array, i, &attrText.runs) { 1399 iConstForEach(Array, i, &attrText.runs) {
1356 const iAttributedRun *run = i.value; 1400 const iAttributedRun *run = i.value;
1357 iGlyphBuffer *buf = at_Array(&buffers, index_ArrayConstIterator(&i)); 1401 iGlyphBuffer *buf = at_Array(&buffers, index_ArrayConstIterator(&i));
1358 init_GlyphBuffer_(buf, run->font, visualText, visToLog); 1402 init_GlyphBuffer_(buf, run->font, logicalText);
1359 for (int pos = run->logical.start; pos < run->logical.end; pos++) { 1403 for (int pos = run->logical.start; pos < run->logical.end; pos++) {
1360 const int visPos = logToVis[pos]; 1404 const int visPos = logToVis[pos];
1361 hb_buffer_add(buf->hb, visualText[visPos], visPos); 1405 iChar ch = visualText[visPos];
1406 if (run->flags.isRTL) {
1407 /* Something odd with brackets... My guess is that because the font is not
1408 RTL (Noto Sans Arabic seems to lack brackets), they are not flipped
1409 as expected. */
1410 ch = flipBracket_(ch);
1411 }
1412 hb_buffer_add(buf->hb, ch, pos);
1362 } 1413 }
1363 hb_buffer_set_content_type(buf->hb, HB_BUFFER_CONTENT_TYPE_UNICODE); 1414 hb_buffer_set_content_type(buf->hb, HB_BUFFER_CONTENT_TYPE_UNICODE);
1364 hb_buffer_set_direction(buf->hb, run->flags.isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); /* visual is LTR */ 1415 hb_buffer_set_direction(buf->hb, run->flags.isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
1365 /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */ 1416 /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */
1366 } 1417 }
1367 if (isMonospaced) { 1418 if (isMonospaced) {
@@ -1374,12 +1425,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1374 const size_t textLen = size_Array(&attrText.logical); 1425 const size_t textLen = size_Array(&attrText.logical);
1375 iRanges wrapRuns = { 0, runCount }; 1426 iRanges wrapRuns = { 0, runCount };
1376 iRangei wrapPosRange = { 0, textLen }; 1427 iRangei wrapPosRange = { 0, textLen };
1377 int wrapResumePos = textLen; 1428 int wrapResumePos = textLen; /* logical position where next line resumes */
1378 size_t wrapResumeRunIndex = runCount; 1429 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */
1379 const int layoutBound = (args->wrap ? args->wrap->maxWidth : 0); 1430 const int layoutBound = (args->wrap ? args->wrap->maxWidth : 0);
1380 while (!isEmpty_Range(&wrapRuns)) { 1431 while (!isEmpty_Range(&wrapRuns)) {
1381 float wrapAdvance = 0.0f; 1432 float wrapAdvance = 0.0f;
1382 iBool isLineRTL = iFalse;
1383 /* First we need to figure out how much text fits on the current line. */ 1433 /* First we need to figure out how much text fits on the current line. */
1384 if (args->wrap && args->wrap->maxWidth > 0) { 1434 if (args->wrap && args->wrap->maxWidth > 0) {
1385 float breakAdvance = -1.0f; 1435 float breakAdvance = -1.0f;
@@ -1395,7 +1445,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1395 yCursor += d->height; 1445 yCursor += d->height;
1396 break; 1446 break;
1397 } 1447 }
1398 isLineRTL |= run->flags.isRTL;
1399 wrapResumeRunIndex = runCount; 1448 wrapResumeRunIndex = runCount;
1400 wrapResumePos = textLen; 1449 wrapResumePos = textLen;
1401 iGlyphBuffer *buf = at_Array(&buffers, runIndex); 1450 iGlyphBuffer *buf = at_Array(&buffers, runIndex);
@@ -1403,18 +1452,19 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1403 shape_GlyphBuffer_(buf); 1452 shape_GlyphBuffer_(buf);
1404 int safeBreakPos = -1; 1453 int safeBreakPos = -1;
1405 iChar prevCh = 0; 1454 iChar prevCh = 0;
1406 for (unsigned int i = 0; i < buf->glyphCount; i++) { 1455 for (unsigned int ir = 0; ir < buf->glyphCount; ir++) {
1456 const int i = (run->flags.isRTL ? buf->glyphCount - ir - 1 : ir);
1407 const hb_glyph_info_t *info = &buf->glyphInfo[i]; 1457 const hb_glyph_info_t *info = &buf->glyphInfo[i];
1408 const hb_codepoint_t glyphId = info->codepoint; 1458 const hb_codepoint_t glyphId = info->codepoint;
1409 const int visPos_ = info->cluster; 1459 const int logPos = info->cluster;
1410 const int logPos = visToLog[visPos_]; 1460 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) {
1411 if (logPos < wrapPosRange.start) {
1412 continue; 1461 continue;
1413 } 1462 }
1414 const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); 1463 const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId);
1415 const int glyphFlags = hb_glyph_info_get_glyph_flags(info); 1464 const int glyphFlags = hb_glyph_info_get_glyph_flags(info);
1416 const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; 1465 const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset;
1417 const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; 1466 const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance;
1467 iAssert(xAdvance >= 0);
1418 if (args->wrap->mode == word_WrapTextMode) { 1468 if (args->wrap->mode == word_WrapTextMode) {
1419 /* When word wrapping, only consider certain places breakable. */ 1469 /* When word wrapping, only consider certain places breakable. */
1420 const iChar ch = logicalText[logPos]; 1470 const iChar ch = logicalText[logPos];
@@ -1443,6 +1493,15 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1443 wrapPosRange.end = safeBreakPos; 1493 wrapPosRange.end = safeBreakPos;
1444 } 1494 }
1445 else { 1495 else {
1496 if (args->wrap->mode == word_WrapTextMode && run->logical.start > wrapPosRange.start) {
1497 /* Don't have a word break position, so the whole run needs
1498 to be cut. */
1499 wrapPosRange.end = run->logical.start;
1500 wrapResumePos = run->logical.start;
1501 wrapRuns.end = runIndex + 1;
1502 wrapResumeRunIndex = runIndex;
1503 break;
1504 }
1446 wrapPosRange.end = logPos; 1505 wrapPosRange.end = logPos;
1447 breakAdvance = wrapAdvance; 1506 breakAdvance = wrapAdvance;
1448 } 1507 }
@@ -1472,11 +1531,50 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1472 wrapAdvance += advance_GlyphBuffer_(at_Array(&buffers, i), wrapPosRange); 1531 wrapAdvance += advance_GlyphBuffer_(at_Array(&buffers, i), wrapPosRange);
1473 } 1532 }
1474 } 1533 }
1534 /* Reorder the run indices according to text direction. */
1535 iArray runOrder;
1536 init_Array(&runOrder, sizeof(size_t));
1537 size_t rtlStartIndex = iInvalidPos;
1538 iBool wasRtl = attrText.isBaseRTL;
1539 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1540 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1541 if (wasRtl) { //} || run->flags.isRTL) {
1542 if (rtlStartIndex == iInvalidPos) {
1543 rtlStartIndex = size_Array(&runOrder);
1544 }
1545 insert_Array(&runOrder, rtlStartIndex, &runIndex);
1546 }
1547 else {
1548 pushBack_Array(&runOrder, &runIndex);
1549 }
1550// wasRtl = run->flags.isRTL;
1551 }
1552#if 0
1553 printf("Run order: ");
1554 iConstForEach(Array, ro, &runOrder) {
1555 const size_t *idx = ro.value;
1556 printf("%zu {%s}\n", *idx,
1557 cstr_Rangecc(sourceRange_AttributedText_(&attrText, ((const iAttributedRun *) at_Array(&attrText.runs, *idx))->logical)));
1558 }
1559 printf("\n");
1560#endif
1561 iAssert(size_Array(&runOrder) == size_Range(&wrapRuns));
1475 /* Alignment. */ 1562 /* Alignment. */
1476 int origin = 0; 1563 int origin = 0;
1477 /* TODO: Is everything on the line RTL? If so, right-align. */ 1564 iBool isRightAligned = attrText.isBaseRTL; // iFalse;
1478 if (isLineRTL && layoutBound > 0 && wrapAdvance < layoutBound) { 1565// /* Right-align if the last run is RTL. */ {
1479 origin = layoutBound - wrapAdvance; 1566// const size_t lastIndex = *(size_t *) back_Array(&runOrder);
1567// const iAttributedRun *run = at_Array(&attrText.runs, lastIndex);
1568// isRightAligned = run->flags.isRTL;
1569// }
1570 if (isRightAligned) {
1571 if (layoutBound > 0) {
1572 origin = layoutBound - wrapAdvance;
1573 }
1574 else if (args->xposLayoutBound > 0) {
1575 iAssert(mode & draw_RunMode);
1576// origin = args->xposLayoutBound - orig.x - wrapAdvance * 2;
1577 }
1480 } 1578 }
1481 /* Make a callback for each wrapped line. */ 1579 /* Make a callback for each wrapped line. */
1482 if (!notify_WrapText_(args->wrap, 1580 if (!notify_WrapText_(args->wrap,
@@ -1488,7 +1586,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1488 xCursor = origin; 1586 xCursor = origin;
1489 /* We have determined a possible wrap position and alignment for the work runs, 1587 /* We have determined a possible wrap position and alignment for the work runs,
1490 so now we can process the glyphs. */ 1588 so now we can process the glyphs. */
1491 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { 1589 for (size_t logRunIndex = 0; logRunIndex < size_Array(&runOrder); logRunIndex++) {
1590 const size_t runIndex = constValue_Array(&runOrder, logRunIndex, size_t);
1492 const iAttributedRun *run = at_Array(&attrText.runs, runIndex); 1591 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1493 if (run->flags.isLineBreak) { 1592 if (run->flags.isLineBreak) {
1494 xCursor = 0.0f; 1593 xCursor = 0.0f;
@@ -1502,8 +1601,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1502 for (unsigned int i = 0; i < buf->glyphCount; i++) { 1601 for (unsigned int i = 0; i < buf->glyphCount; i++) {
1503 const hb_glyph_info_t *info = &buf->glyphInfo[i]; 1602 const hb_glyph_info_t *info = &buf->glyphInfo[i];
1504 const hb_codepoint_t glyphId = info->codepoint; 1603 const hb_codepoint_t glyphId = info->codepoint;
1505 const int visPos = info->cluster; 1604 const int logPos = info->cluster;
1506 const int logPos = visToLog[visPos];
1507 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) { 1605 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) {
1508 /* Already handled this part of the run. */ 1606 /* Already handled this part of the run. */
1509 continue; 1607 continue;
@@ -1532,7 +1630,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1532 bounds.size.x = iMax(bounds.size.x, dst.x + dst.w - orig.x); 1630 bounds.size.x = iMax(bounds.size.x, dst.x + dst.w - orig.x);
1533 bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height); 1631 bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height);
1534 } 1632 }
1535 if (mode & draw_RunMode && visualText[visPos] > 0x20) { 1633 if (mode & draw_RunMode && logicalText[logPos] > 0x20) {
1536 /* Draw the glyph. */ 1634 /* Draw the glyph. */
1537 if (!isRasterized_Glyph_(glyph, hoff)) { 1635 if (!isRasterized_Glyph_(glyph, hoff)) {
1538 cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ 1636 cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */
@@ -1555,6 +1653,17 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1555 SDL_RenderFillRect(text_.render, &dst); 1653 SDL_RenderFillRect(text_.render, &dst);
1556 } 1654 }
1557 SDL_RenderCopy(text_.render, text_.cache, &src, &dst); 1655 SDL_RenderCopy(text_.render, text_.cache, &src, &dst);
1656#if 0
1657 /* Show spaces and direction. */
1658 if (logicalText[logPos] == 0x20) {
1659 const iColor debug = get_Color(run->flags.isRTL ? yellow_ColorId : red_ColorId);
1660 SDL_SetRenderDrawColor(text_.render, debug.r, debug.g, debug.b, 255);
1661 dst.w = xAdvance;
1662 dst.h = d->height / 2;
1663 dst.y -= d->height / 2;
1664 SDL_RenderFillRect(text_.render, &dst);
1665 }
1666#endif
1558 } 1667 }
1559 xCursor += xAdvance; 1668 xCursor += xAdvance;
1560 yCursor += yAdvance; 1669 yCursor += yAdvance;
@@ -1566,6 +1675,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1566 xCursorMax = iMax(xCursorMax, xCursor); 1675 xCursorMax = iMax(xCursorMax, xCursor);
1567 } 1676 }
1568 } 1677 }
1678 deinit_Array(&runOrder);
1569 if (willAbortDueToWrap) { 1679 if (willAbortDueToWrap) {
1570 break; 1680 break;
1571 } 1681 }
diff --git a/src/ui/text.h b/src/ui/text.h
index 8be5a373..341f94da 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -132,7 +132,9 @@ enum iTextFont {
132 tinos_TextFont, 132 tinos_TextFont,
133 sourceSans3_TextFont, 133 sourceSans3_TextFont,
134 iosevka_TextFont, 134 iosevka_TextFont,
135 /* families: */
135 notoSansArabic_TextFont, 136 notoSansArabic_TextFont,
137 emojiAndSymbols_TextFont,
136}; 138};
137 139
138extern int gap_Text; /* affected by content font size */ 140extern int gap_Text; /* affected by content font size */