diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-02 06:50:52 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-02 06:50:52 +0300 |
commit | 93eb4851d708cd152d848ed05b8f2134b0db1a2d (patch) | |
tree | 0977416692e6ac3dec722c3d6285c2578bbf1b94 /src/ui | |
parent | a29e262a7a7aeacb2f6a1e34e1efacb440022529 (diff) |
Text: Word wrapping; fixed glyph caching
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/text.c | 84 | ||||
-rw-r--r-- | src/ui/text.h | 6 |
2 files changed, 60 insertions, 30 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index f83f05a9..c8177096 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -738,14 +738,14 @@ static iChar nextChar_(const char **chPos, const char *end) { | |||
738 | return ch; | 738 | return ch; |
739 | } | 739 | } |
740 | 740 | ||
741 | static iBool isControl_Char_(iChar c) { | ||
742 | return isDefaultIgnorable_Char(c) || isVariationSelector_Char(c) || isFitzpatrickType_Char(c); | ||
743 | } | ||
744 | |||
741 | /*----------------------------------------------------------------------------------------------*/ | 745 | /*----------------------------------------------------------------------------------------------*/ |
742 | 746 | ||
743 | iDeclareType(AttributedRun) | 747 | iDeclareType(AttributedRun) |
744 | 748 | ||
745 | /*enum iAttributedRunFlags { | ||
746 | newline_AttributedRunFlag = iBit(1), | ||
747 | };*/ | ||
748 | |||
749 | struct Impl_AttributedRun { | 749 | struct Impl_AttributedRun { |
750 | iRangecc text; | 750 | iRangecc text; |
751 | iFont * font; | 751 | iFont * font; |
@@ -824,8 +824,7 @@ static void prepare_AttributedText_(iAttributedText *d) { | |||
824 | run.lineBreaks++; | 824 | run.lineBreaks++; |
825 | continue; | 825 | continue; |
826 | } | 826 | } |
827 | if (isVariationSelector_Char(ch) || isDefaultIgnorable_Char(ch) || | 827 | if (isControl_Char_(ch)) { |
828 | isFitzpatrickType_Char(ch)) { | ||
829 | continue; | 828 | continue; |
830 | } | 829 | } |
831 | if (avail-- == 0) { | 830 | if (avail-- == 0) { |
@@ -932,7 +931,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
932 | } | 931 | } |
933 | } | 932 | } |
934 | } | 933 | } |
935 | iForIndices(i, surfaces) { | 934 | iForIndices(i, surfaces) { /* cleanup */ |
936 | if (surfaces[i]) { | 935 | if (surfaces[i]) { |
937 | if (surfaces[i]->flags & SDL_PREALLOC) { | 936 | if (surfaces[i]->flags & SDL_PREALLOC) { |
938 | free(surfaces[i]->pixels); | 937 | free(surfaces[i]->pixels); |
@@ -941,7 +940,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
941 | } | 940 | } |
942 | } | 941 | } |
943 | if (outOfSpace) { | 942 | if (outOfSpace) { |
944 | index--; /* do-over */ | 943 | /* Redo this glyph. `index` does not get incremented. */ |
945 | break; | 944 | break; |
946 | } | 945 | } |
947 | } | 946 | } |
@@ -965,7 +964,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
965 | (const SDL_Rect *) &rg->rect, | 964 | (const SDL_Rect *) &rg->rect, |
966 | (const SDL_Rect *) glRect); | 965 | (const SDL_Rect *) glRect); |
967 | setRasterized_Glyph_(rg->glyph, rg->hoff); | 966 | setRasterized_Glyph_(rg->glyph, rg->hoff); |
968 | // printf(" - %u\n", rg->glyph->glyphIndex); | 967 | // printf(" - %u (hoff %d)\n", index_Glyph_(rg->glyph), rg->hoff); |
969 | } | 968 | } |
970 | SDL_DestroyTexture(bufTex); | 969 | SDL_DestroyTexture(bufTex); |
971 | /* Resume with an empty buffer. */ | 970 | /* Resume with an empty buffer. */ |
@@ -995,21 +994,25 @@ static void cacheSingleGlyph_Font_(iFont *d, uint32_t glyphIndex) { | |||
995 | static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { | 994 | static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { |
996 | iArray glyphIndices; | 995 | iArray glyphIndices; |
997 | init_Array(&glyphIndices, sizeof(uint32_t)); | 996 | init_Array(&glyphIndices, sizeof(uint32_t)); |
998 | /* TODO: Do this with AttributedText */ | 997 | iAttributedText attrText; |
999 | for (const char *chPos = text.start; chPos != text.end; ) { | 998 | init_AttributedText(&attrText, text, 0, d, (iColor){}); |
1000 | const char *oldPos = chPos; | 999 | /* We use AttributedText here so the glyph lookup matches the behavior during text drawing -- |
1001 | const iChar ch = nextChar_(&chPos, text.end); | 1000 | glyphs may be selected from a font that's different than `d`. */ |
1002 | if (chPos == oldPos) break; | 1001 | iConstForEach(Array, i, &attrText.runs) { |
1003 | if (!isSpace_Char(ch) && | 1002 | const iAttributedRun *run = i.value; |
1004 | !isDefaultIgnorable_Char(ch) && | 1003 | for (const char *chPos = run->text.start; chPos != run->text.end; ) { |
1005 | !isVariationSelector_Char(ch) && | 1004 | const char *oldPos = chPos; |
1006 | !isFitzpatrickType_Char(ch)) { | 1005 | const iChar ch = nextChar_(&chPos, text.end); |
1007 | const uint32_t glyphIndex = glyphIndex_Font_(d, ch); | 1006 | if (chPos == oldPos) break; /* don't get stuck */ |
1008 | if (glyphIndex) { | 1007 | if (!isSpace_Char(ch) && !isControl_Char_(ch)) { |
1009 | pushBack_Array(&glyphIndices, &glyphIndex); | 1008 | const uint32_t glyphIndex = glyphIndex_Font_(d, ch); |
1009 | if (glyphIndex) { | ||
1010 | pushBack_Array(&glyphIndices, &glyphIndex); | ||
1011 | } | ||
1010 | } | 1012 | } |
1011 | } | 1013 | } |
1012 | } | 1014 | } |
1015 | deinit_AttributedText(&attrText); | ||
1013 | cacheGlyphs_Font_(d, &glyphIndices); | 1016 | cacheGlyphs_Font_(d, &glyphIndices); |
1014 | deinit_Array(&glyphIndices); | 1017 | deinit_Array(&glyphIndices); |
1015 | } | 1018 | } |
@@ -1079,8 +1082,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1079 | /* Split the text into a number of attributed runs that specify exactly which | 1082 | /* Split the text into a number of attributed runs that specify exactly which |
1080 | font is used and other attributes such as color. (HarfBuzz shaping is done | 1083 | font is used and other attributes such as color. (HarfBuzz shaping is done |
1081 | with one specific font.) */ | 1084 | with one specific font.) */ |
1082 | iAttributedText *attrText = | 1085 | iAttributedText attrText; |
1083 | new_AttributedText(args->text, args->maxLen, d, get_Color(args->color)); | 1086 | init_AttributedText(&attrText, args->text, args->maxLen, d, get_Color(args->color)); |
1084 | if (args->wrap) { | 1087 | if (args->wrap) { |
1085 | /* TODO: Duplicated args? */ | 1088 | /* TODO: Duplicated args? */ |
1086 | iAssert(equalRange_Rangecc(args->wrap->text, args->text)); | 1089 | iAssert(equalRange_Rangecc(args->wrap->text, args->text)); |
@@ -1089,8 +1092,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1089 | } | 1092 | } |
1090 | const char *wrapResumePos = NULL; | 1093 | const char *wrapResumePos = NULL; |
1091 | iBool willAbortDueToWrap = iFalse; | 1094 | iBool willAbortDueToWrap = iFalse; |
1092 | for (size_t runIndex = 0; runIndex < size_Array(&attrText->runs); runIndex++) { | 1095 | for (size_t runIndex = 0; runIndex < size_Array(&attrText.runs); runIndex++) { |
1093 | const iAttributedRun *run = at_Array(&attrText->runs, runIndex); | 1096 | const iAttributedRun *run = at_Array(&attrText.runs, runIndex); |
1094 | iRangecc runText = run->text; | 1097 | iRangecc runText = run->text; |
1095 | if (wrapResumePos) { | 1098 | if (wrapResumePos) { |
1096 | xCursor = 0.0f; | 1099 | xCursor = 0.0f; |
@@ -1142,8 +1145,18 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1142 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; | 1145 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; |
1143 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; | 1146 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; |
1144 | const char *textPos = runText.start + info->cluster; | 1147 | const char *textPos = runText.start + info->cluster; |
1145 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { | 1148 | if (args->wrap->mode == word_WrapTextMode) { |
1146 | safeBreak = textPos; | 1149 | /* When word wrapping, only consider certain places breakable. */ |
1150 | iChar ch = 0; | ||
1151 | decodeBytes_MultibyteChar(textPos, runText.end, &ch); | ||
1152 | if (isSpace_Char(ch)) { | ||
1153 | safeBreak = textPos; | ||
1154 | } | ||
1155 | } | ||
1156 | else { | ||
1157 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { | ||
1158 | safeBreak = textPos; | ||
1159 | } | ||
1147 | } | 1160 | } |
1148 | if (x + xOffset + glyph->d[0].x + glyph->rect[0].size.x > args->wrap->maxWidth) { | 1161 | if (x + xOffset + glyph->d[0].x + glyph->rect[0].size.x > args->wrap->maxWidth) { |
1149 | if (safeBreak) { | 1162 | if (safeBreak) { |
@@ -1202,6 +1215,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1202 | if (!isRasterized_Glyph_(glyph, hoff)) { | 1215 | if (!isRasterized_Glyph_(glyph, hoff)) { |
1203 | cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ | 1216 | cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ |
1204 | glyph = glyphByIndex_Font_(run->font, glyphId); | 1217 | glyph = glyphByIndex_Font_(run->font, glyphId); |
1218 | #if 0 | ||
1219 | if (!isRasterized_Glyph_(glyph, hoff)) { | ||
1220 | /* TODO: Should not be needed! The glyph cache should retry automatically if running out of buffer. */ | ||
1221 | cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ | ||
1222 | glyph = glyphByIndex_Font_(run->font, glyphId); | ||
1223 | } | ||
1224 | #endif | ||
1205 | iAssert(isRasterized_Glyph_(glyph, hoff)); | 1225 | iAssert(isRasterized_Glyph_(glyph, hoff)); |
1206 | } | 1226 | } |
1207 | if (~mode & permanentColorFlag_RunMode) { | 1227 | if (~mode & permanentColorFlag_RunMode) { |
@@ -1239,7 +1259,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1239 | *args->runAdvance_out = xCursorMax; | 1259 | *args->runAdvance_out = xCursorMax; |
1240 | } | 1260 | } |
1241 | hb_buffer_destroy(hbBuf); | 1261 | hb_buffer_destroy(hbBuf); |
1242 | delete_AttributedText(attrText); | 1262 | deinit_AttributedText(&attrText); |
1243 | return bounds; | 1263 | return bounds; |
1244 | } | 1264 | } |
1245 | 1265 | ||
@@ -1302,10 +1322,12 @@ static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int advance) { | |||
1302 | } | 1322 | } |
1303 | 1323 | ||
1304 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1324 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { |
1305 | iWrapText wrap = { .text = text, .maxWidth = width, | 1325 | iWrapText wrap = { .mode = word_WrapTextMode, |
1326 | .text = text, .maxWidth = width, | ||
1306 | .wrapFunc = cbAdvanceOneLine_, .context = endPos }; | 1327 | .wrapFunc = cbAdvanceOneLine_, .context = endPos }; |
1307 | const int x = advance_WrapText(&wrap, fontId).x; | 1328 | const int x = advance_WrapText(&wrap, fontId).x; |
1308 | return init_I2(x, lineHeight_Text(fontId)); | 1329 | return init_I2(x, lineHeight_Text(fontId)); |
1330 | |||
1309 | #if 0 | 1331 | #if 0 |
1310 | int advance; | 1332 | int advance; |
1311 | const int height = run_Font_(font_Text_(fontId), | 1333 | const int height = run_Font_(font_Text_(fontId), |
@@ -1322,10 +1344,12 @@ iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) | |||
1322 | 1344 | ||
1323 | iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1345 | iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { |
1324 | /* TODO: "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ | 1346 | /* TODO: "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ |
1325 | iWrapText wrap = { .text = text, .maxWidth = width, | 1347 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, |
1348 | .text = text, .maxWidth = width, | ||
1326 | .wrapFunc = cbAdvanceOneLine_, .context = endPos }; | 1349 | .wrapFunc = cbAdvanceOneLine_, .context = endPos }; |
1327 | const int x = advance_WrapText(&wrap, fontId).x; | 1350 | const int x = advance_WrapText(&wrap, fontId).x; |
1328 | return init_I2(x, lineHeight_Text(fontId)); | 1351 | return init_I2(x, lineHeight_Text(fontId)); |
1352 | |||
1329 | #if 0 | 1353 | #if 0 |
1330 | int advance; | 1354 | int advance; |
1331 | const int height = run_Font_(font_Text_(fontId), | 1355 | const int height = run_Font_(font_Text_(fontId), |
diff --git a/src/ui/text.h b/src/ui/text.h index fe42a308..26965f52 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -176,10 +176,16 @@ int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, | |||
176 | 176 | ||
177 | iDeclareType(WrapText) | 177 | iDeclareType(WrapText) |
178 | 178 | ||
179 | enum iWrapTextMode { | ||
180 | anyCharacter_WrapTextMode, | ||
181 | word_WrapTextMode, | ||
182 | }; | ||
183 | |||
179 | struct Impl_WrapText { | 184 | struct Impl_WrapText { |
180 | /* arguments */ | 185 | /* arguments */ |
181 | iRangecc text; | 186 | iRangecc text; |
182 | int maxWidth; | 187 | int maxWidth; |
188 | enum iWrapTextMode mode; | ||
183 | iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int advance); | 189 | iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int advance); |
184 | void * context; | 190 | void * context; |
185 | /* output */ | 191 | /* output */ |