diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-13 17:50:11 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-13 17:50:11 +0300 |
commit | d259b1f6205f840a261fb5eb7bae63be6566beef (patch) | |
tree | 7c709f997024c0f6aceda24af8de1ca650900a56 | |
parent | 07156681f635a18f0b4e8e76fce60a723c8cddd8 (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.
-rw-r--r-- | src/gmdocument.c | 3 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 6 | ||||
-rw-r--r-- | src/ui/text.c | 196 | ||||
-rw-r--r-- | src/ui/text.h | 2 |
4 files changed, 160 insertions, 47 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index 06b4fb45..34987beb 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -416,7 +416,8 @@ static const int colors[max_GmLineType] = { | |||
416 | 416 | ||
417 | static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, int origin, int advance) { | 417 | static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, int origin, int advance) { |
418 | iAssert(wrapRange.start <= wrapRange.end); | 418 | iAssert(wrapRange.start <= wrapRange.end); |
419 | printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); | 419 | trimEnd_Rangecc(&wrapRange); |
420 | // printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); | ||
420 | iRunTypesetter *d = wrap->context; | 421 | iRunTypesetter *d = wrap->context; |
421 | const int fontId = d->run.font; | 422 | const int fontId = d->run.font; |
422 | d->run.text = wrapRange; | 423 | d->run.text = wrapRange; |
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 | ||
4333 | static int drawSideRect_(iPaint *p, iRect rect) { | 4333 | static 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 | ||
803 | iDefineTypeConstructionArgs(AttributedText, (iRangecc text, size_t maxLen, iFont *font, iColor fgColor), | 812 | iDefineTypeConstructionArgs(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 | ||
830 | static 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 | |||
821 | static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, int endAt) { | 840 | static 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) | |||
1253 | struct Impl_GlyphBuffer { | 1290 | struct 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 | ||
1263 | static void init_GlyphBuffer_(iGlyphBuffer *d, iFont *font, const iChar *visualText, | 1300 | static 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) { | |||
1286 | static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapPosRange) { | 1322 | static 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 | ||
1355 | iLocalDef 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 | |||
1323 | static iRect run_Font_(iFont *d, const iRunArgs *args) { | 1367 | static 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 | ||
138 | extern int gap_Text; /* affected by content font size */ | 140 | extern int gap_Text; /* affected by content font size */ |