summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-02 06:50:52 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-02 06:50:52 +0300
commit93eb4851d708cd152d848ed05b8f2134b0db1a2d (patch)
tree0977416692e6ac3dec722c3d6285c2578bbf1b94 /src/ui
parenta29e262a7a7aeacb2f6a1e34e1efacb440022529 (diff)
Text: Word wrapping; fixed glyph caching
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/text.c84
-rw-r--r--src/ui/text.h6
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
741static iBool isControl_Char_(iChar c) {
742 return isDefaultIgnorable_Char(c) || isVariationSelector_Char(c) || isFitzpatrickType_Char(c);
743}
744
741/*----------------------------------------------------------------------------------------------*/ 745/*----------------------------------------------------------------------------------------------*/
742 746
743iDeclareType(AttributedRun) 747iDeclareType(AttributedRun)
744 748
745/*enum iAttributedRunFlags {
746 newline_AttributedRunFlag = iBit(1),
747};*/
748
749struct Impl_AttributedRun { 749struct 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) {
995static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { 994static 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
1304iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { 1324iInt2 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
1323iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { 1345iInt2 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
177iDeclareType(WrapText) 177iDeclareType(WrapText)
178 178
179enum iWrapTextMode {
180 anyCharacter_WrapTextMode,
181 word_WrapTextMode,
182};
183
179struct Impl_WrapText { 184struct 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 */