diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-14 13:03:05 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-14 13:03:05 +0300 |
commit | efb12e24a2cdf311c8aeee9627da89398c3aa132 (patch) | |
tree | 62fccb381c094e1f8ccf83c876ac995bf8f50525 | |
parent | 1ee3fd5f9168ac080f7ab2558a6e6cb5cf9657c4 (diff) |
Fixed text wrapping when HarfBuzz is disabled
Updated the old simple text renderer for the new WrapText wrapping.
-rw-r--r-- | src/ui/text.c | 35 | ||||
-rw-r--r-- | src/ui/text_simple.c | 72 |
2 files changed, 67 insertions, 40 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index 3683aee4..4b82191f 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -895,6 +895,14 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir) { | |||
895 | NULL, | 895 | NULL, |
896 | (FriBidiLevel *) d->bidiLevels); | 896 | (FriBidiLevel *) d->bidiLevels); |
897 | d->isBaseRTL = (overrideBaseDir == 0 ? FRIBIDI_IS_RTL(baseDir) : (overrideBaseDir < 0)); | 897 | d->isBaseRTL = (overrideBaseDir == 0 ? FRIBIDI_IS_RTL(baseDir) : (overrideBaseDir < 0)); |
898 | #else | ||
899 | /* 1:1 mapping. */ | ||
900 | setCopy_Array(&d->visual, &d->logical); | ||
901 | resize_Array(&d->logicalToVisual, length); | ||
902 | for (size_t i = 0; i < length; i++) { | ||
903 | set_Array(&d->logicalToVisual, i, &(int){ i }); | ||
904 | } | ||
905 | d->isBaseRTL = iFalse; | ||
898 | #endif | 906 | #endif |
899 | } | 907 | } |
900 | /* The mapping needs to include the terminating NULL position. */ { | 908 | /* The mapping needs to include the terminating NULL position. */ { |
@@ -1208,13 +1216,13 @@ enum iRunMode { | |||
1208 | draw_RunMode = 1, | 1216 | draw_RunMode = 1, |
1209 | modeMask_RunMode = 0x00ff, | 1217 | modeMask_RunMode = 0x00ff, |
1210 | flagsMask_RunMode = 0xff00, | 1218 | flagsMask_RunMode = 0xff00, |
1211 | noWrapFlag_RunMode = iBit(9), | 1219 | // noWrapFlag_RunMode = iBit(9), |
1212 | visualFlag_RunMode = iBit(10), /* actual visible bounding box of the glyph, | 1220 | visualFlag_RunMode = iBit(10), /* actual visible bounding box of the glyph, |
1213 | e.g., for icons */ | 1221 | e.g., for icons */ |
1214 | permanentColorFlag_RunMode = iBit(11), | 1222 | permanentColorFlag_RunMode = iBit(11), |
1215 | alwaysVariableWidthFlag_RunMode = iBit(12), | 1223 | alwaysVariableWidthFlag_RunMode = iBit(12), |
1216 | fillBackground_RunMode = iBit(13), | 1224 | fillBackground_RunMode = iBit(13), |
1217 | stopAtNewline_RunMode = iBit(14), /* don't advance past \n, consider it a wrap pos */ | 1225 | // stopAtNewline_RunMode = iBit(14), /* don't advance past \n, consider it a wrap pos */ |
1218 | }; | 1226 | }; |
1219 | 1227 | ||
1220 | iDeclareType(RunArgs) | 1228 | iDeclareType(RunArgs) |
@@ -1225,14 +1233,14 @@ struct Impl_RunArgs { | |||
1225 | size_t maxLen; /* max characters to process */ | 1233 | size_t maxLen; /* max characters to process */ |
1226 | iInt2 pos; | 1234 | iInt2 pos; |
1227 | iWrapText * wrap; | 1235 | iWrapText * wrap; |
1228 | int xposLimit; /* hard limit for wrapping */ | 1236 | // int xposLimit; /* hard limit for wrapping */ |
1229 | int xposLayoutBound; /* visible bound for layout purposes; does not affect wrapping */ | 1237 | // int xposLayoutBound; /* visible bound for layout purposes; does not affect wrapping */ |
1230 | int color; | 1238 | int color; |
1231 | int baseDir; | 1239 | int baseDir; |
1232 | /* TODO: Cleanup using TextMetrics | 1240 | /* TODO: Cleanup using TextMetrics |
1233 | Use TextMetrics output pointer instead of return value & cursorAdvance_out. */ | 1241 | Use TextMetrics output pointer instead of return value & cursorAdvance_out. */ |
1234 | iInt2 * cursorAdvance_out; | 1242 | iInt2 * cursorAdvance_out; |
1235 | const char ** continueFrom_out; | 1243 | // const char ** continueFrom_out; |
1236 | int * runAdvance_out; | 1244 | int * runAdvance_out; |
1237 | }; | 1245 | }; |
1238 | 1246 | ||
@@ -1368,9 +1376,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1368 | float xCursorMax = 0.0f; | 1376 | float xCursorMax = 0.0f; |
1369 | const iBool isMonospaced = d->isMonospaced; | 1377 | const iBool isMonospaced = d->isMonospaced; |
1370 | iAssert(args->text.end >= args->text.start); | 1378 | iAssert(args->text.end >= args->text.start); |
1371 | if (args->continueFrom_out) { | 1379 | // if (args->continueFrom_out) { |
1372 | *args->continueFrom_out = args->text.end; | 1380 | // *args->continueFrom_out = args->text.end; |
1373 | } | 1381 | // } |
1374 | /* Split the text into a number of attributed runs that specify exactly which | 1382 | /* Split the text into a number of attributed runs that specify exactly which |
1375 | font is used and other attributes such as color. (HarfBuzz shaping is done | 1383 | font is used and other attributes such as color. (HarfBuzz shaping is done |
1376 | with one specific font.) */ | 1384 | with one specific font.) */ |
@@ -1575,10 +1583,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1575 | if (layoutBound > 0) { | 1583 | if (layoutBound > 0) { |
1576 | origin = layoutBound - wrapAdvance; | 1584 | origin = layoutBound - wrapAdvance; |
1577 | } | 1585 | } |
1578 | else if (args->xposLayoutBound > 0) { | 1586 | // else if (args->xposLayoutBound > 0) { |
1579 | iAssert(mode & draw_RunMode); | 1587 | // iAssert(mode & draw_RunMode); |
1580 | // origin = args->xposLayoutBound - orig.x - wrapAdvance * 2; | 1588 | //// origin = args->xposLayoutBound - orig.x - wrapAdvance * 2; |
1581 | } | 1589 | // } |
1582 | } | 1590 | } |
1583 | /* Make a callback for each wrapped line. */ | 1591 | /* Make a callback for each wrapped line. */ |
1584 | if (!notify_WrapText_(args->wrap, | 1592 | if (!notify_WrapText_(args->wrap, |
@@ -1768,6 +1776,7 @@ iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) | |||
1768 | 1776 | ||
1769 | iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1777 | iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { |
1770 | /* TODO: "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ | 1778 | /* TODO: "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ |
1779 | /* FIXME: Get rid of this. Caller could use WrapText directly? */ | ||
1771 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, | 1780 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, |
1772 | .text = text, | 1781 | .text = text, |
1773 | .maxWidth = width, | 1782 | .maxWidth = width, |
@@ -1803,7 +1812,7 @@ static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, | |||
1803 | .text = text, | 1812 | .text = text, |
1804 | .maxLen = maxLen, | 1813 | .maxLen = maxLen, |
1805 | .pos = pos, | 1814 | .pos = pos, |
1806 | .xposLayoutBound = xposBound, | 1815 | // .xposLayoutBound = xposBound, |
1807 | .color = color & mask_ColorId, | 1816 | .color = color & mask_ColorId, |
1808 | .baseDir = xposBound ? iSign(xposBound - pos.x) : 0 }); | 1817 | .baseDir = xposBound ? iSign(xposBound - pos.x) : 0 }); |
1809 | } | 1818 | } |
diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c index 575d00cb..b843f2e8 100644 --- a/src/ui/text_simple.c +++ b/src/ui/text_simple.c | |||
@@ -68,13 +68,19 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
68 | int ypos = orig.y; | 68 | int ypos = orig.y; |
69 | size_t maxLen = args->maxLen ? args->maxLen : iInvalidSize; | 69 | size_t maxLen = args->maxLen ? args->maxLen : iInvalidSize; |
70 | float xposExtend = orig.x; /* allows wide glyphs to use more space; restored by whitespace */ | 70 | float xposExtend = orig.x; /* allows wide glyphs to use more space; restored by whitespace */ |
71 | iWrapText * wrap = args->wrap; | ||
72 | int wrapAdvance = 0; | ||
73 | const int xposLimit = (wrap && wrap->maxWidth ? orig.x + wrap->maxWidth : 0); | ||
71 | const enum iRunMode mode = args->mode; | 74 | const enum iRunMode mode = args->mode; |
72 | const char * lastWordEnd = args->text.start; | 75 | const char * lastWordEnd = args->text.start; |
73 | iAssert(args->xposLimit == 0 || isMeasuring_(mode)); | 76 | iAssert(xposLimit == 0 || isMeasuring_(mode)); |
74 | iAssert(args->text.end >= args->text.start); | 77 | iAssert(args->text.end >= args->text.start); |
75 | if (args->continueFrom_out) { | 78 | if (wrap) { |
76 | *args->continueFrom_out = args->text.end; | 79 | wrap->wrapRange_ = args->text; |
77 | } | 80 | } |
81 | // if (args->continueFrom_out) { | ||
82 | // *args->continueFrom_out = args->text.end; | ||
83 | // } | ||
78 | iChar prevCh = 0; | 84 | iChar prevCh = 0; |
79 | const iBool isMonospaced = d->isMonospaced && !(mode & alwaysVariableWidthFlag_RunMode); | 85 | const iBool isMonospaced = d->isMonospaced && !(mode & alwaysVariableWidthFlag_RunMode); |
80 | if (isMonospaced) { | 86 | if (isMonospaced) { |
@@ -85,7 +91,8 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
85 | SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0); | 91 | SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0); |
86 | } | 92 | } |
87 | /* Text rendering is not very straightforward! Let's dive in... */ | 93 | /* Text rendering is not very straightforward! Let's dive in... */ |
88 | for (const char *chPos = args->text.start; chPos != args->text.end; ) { | 94 | const char *chPos; |
95 | for (chPos = args->text.start; chPos != args->text.end; ) { | ||
89 | iAssert(chPos < args->text.end); | 96 | iAssert(chPos < args->text.end); |
90 | const char *currentPos = chPos; | 97 | const char *currentPos = chPos; |
91 | if (*chPos == 0x1b) { /* ANSI escape. */ | 98 | if (*chPos == 0x1b) { /* ANSI escape. */ |
@@ -123,11 +130,11 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
123 | if (ch == 0xad) { /* soft hyphen */ | 130 | if (ch == 0xad) { /* soft hyphen */ |
124 | lastWordEnd = chPos; | 131 | lastWordEnd = chPos; |
125 | if (isMeasuring_(mode)) { | 132 | if (isMeasuring_(mode)) { |
126 | if (args->xposLimit > 0) { | 133 | if (xposLimit > 0) { |
127 | const char *postHyphen = chPos; | 134 | const char *postHyphen = chPos; |
128 | iChar nextCh = nextChar_(&postHyphen, args->text.end); | 135 | iChar nextCh = nextChar_(&postHyphen, args->text.end); |
129 | if ((int) xpos + glyph_Font_(d, ch)->rect[0].size.x + | 136 | if ((int) xpos + glyph_Font_(d, ch)->rect[0].size.x + |
130 | glyph_Font_(d, nextCh)->rect[0].size.x > args->xposLimit) { | 137 | glyph_Font_(d, nextCh)->rect[0].size.x > xposLimit) { |
131 | /* Wraps after hyphen, should show it. */ | 138 | /* Wraps after hyphen, should show it. */ |
132 | } | 139 | } |
133 | else continue; | 140 | else continue; |
@@ -143,11 +150,8 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
143 | } | 150 | } |
144 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ | 151 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ |
145 | if (ch == '\n') { | 152 | if (ch == '\n') { |
146 | if (args->xposLimit > 0 && mode & stopAtNewline_RunMode) { | 153 | /* Notify about the wrap. */ |
147 | /* Stop the line here, this is a hard warp. */ | 154 | if (!notify_WrapText_(wrap, chPos, 0, iMax(xpos, xposExtend) - orig.x, iFalse)) { |
148 | if (args->continueFrom_out) { | ||
149 | *args->continueFrom_out = chPos; | ||
150 | } | ||
151 | break; | 155 | break; |
152 | } | 156 | } |
153 | xpos = xposExtend = orig.x; | 157 | xpos = xposExtend = orig.x; |
@@ -157,7 +161,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
157 | } | 161 | } |
158 | if (ch == '\t') { | 162 | if (ch == '\t') { |
159 | const int tabStopWidth = d->height * 10; | 163 | const int tabStopWidth = d->height * 10; |
160 | const int halfWidth = (iMax(args->xposLimit, args->xposLayoutBound) - orig.x) / 2; | 164 | const int halfWidth = (xposLimit - orig.x) / 2; |
161 | const int xRel = xpos - orig.x; | 165 | const int xRel = xpos - orig.x; |
162 | /* First stop is always to half width. */ | 166 | /* First stop is always to half width. */ |
163 | if (halfWidth > 0 && xRel < halfWidth) { | 167 | if (halfWidth > 0 && xRel < halfWidth) { |
@@ -204,23 +208,32 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
204 | if (mode & draw_RunMode && ch != 0x20 && ch != 0 && !isRasterized_Glyph_(glyph, hoff)) { | 208 | if (mode & draw_RunMode && ch != 0x20 && ch != 0 && !isRasterized_Glyph_(glyph, hoff)) { |
205 | /* Need to pause here and make sure all glyphs have been cached in the text. */ | 209 | /* Need to pause here and make sure all glyphs have been cached in the text. */ |
206 | // printf("[Text] missing from cache: %lc (%x)\n", (int) ch, ch); | 210 | // printf("[Text] missing from cache: %lc (%x)\n", (int) ch, ch); |
207 | cacheTextGlyphs_Font_(d, args->text); | 211 | //cacheTextGlyphs_Font_(d, args->text); |
212 | cacheSingleGlyph_Font_(glyph->font, index_Glyph_(glyph)); | ||
208 | glyph = glyph_Font_(d, ch); /* cache may have been reset */ | 213 | glyph = glyph_Font_(d, ch); /* cache may have been reset */ |
209 | } | 214 | } |
210 | int x2 = x1 + glyph->rect[hoff].size.x; | 215 | int x2 = x1 + glyph->rect[hoff].size.x; |
211 | /* Out of the allotted space on the line? */ | 216 | /* Out of the allotted space on the line? */ |
212 | if (args->xposLimit > 0 && x2 > args->xposLimit) { | 217 | if (xposLimit > 0 && x2 > xposLimit) { |
213 | if (args->continueFrom_out) { | 218 | iAssert(wrap); |
214 | if (lastWordEnd != args->text.start && ~mode & noWrapFlag_RunMode) { | 219 | const char *wrapPos = currentPos; |
215 | *args->continueFrom_out = skipSpace_CStr(lastWordEnd); | 220 | int advance = x1 - orig.x; |
216 | *args->continueFrom_out = iMin(*args->continueFrom_out, | 221 | if (lastWordEnd != args->text.start && wrap->mode == word_WrapTextMode) { |
217 | args->text.end); | 222 | wrapPos = skipSpace_CStr(lastWordEnd); |
218 | } | 223 | wrapPos = iMin(wrapPos, args->text.end); |
219 | else { | 224 | advance = wrapAdvance; |
220 | *args->continueFrom_out = currentPos; /* forced break */ | ||
221 | } | ||
222 | } | 225 | } |
223 | break; | 226 | // if (args->continueFrom_out) { |
227 | // *args->continueFrom_out = wrapPos; | ||
228 | // } | ||
229 | if (!notify_WrapText_(wrap, wrapPos, 0, advance, iFalse)) { | ||
230 | break; | ||
231 | } | ||
232 | xpos = xposExtend = orig.x; | ||
233 | ypos += d->height; | ||
234 | prevCh = 0; | ||
235 | chPos = wrapPos; /* go back */ | ||
236 | continue; | ||
224 | } | 237 | } |
225 | const int yLineMax = ypos + d->height; | 238 | const int yLineMax = ypos + d->height; |
226 | SDL_Rect dst = { x1 + glyph->d[hoff].x, | 239 | SDL_Rect dst = { x1 + glyph->d[hoff].x, |
@@ -289,10 +302,10 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
289 | /* TODO: No need to decode the next char twice; check this on the next iteration. */ | 302 | /* TODO: No need to decode the next char twice; check this on the next iteration. */ |
290 | const char *peek = chPos; | 303 | const char *peek = chPos; |
291 | const iChar next = nextChar_(&peek, args->text.end); | 304 | const iChar next = nextChar_(&peek, args->text.end); |
292 | if (enableKerning_Text && !d->manualKernOnly && next) { | 305 | if (enableKerning_Text && next) { |
293 | const uint32_t nextGlyphIndex = glyphIndex_Font_(glyph->font, next); | 306 | const uint32_t nextGlyphIndex = glyphIndex_Font_(glyph->font, next); |
294 | int kern = stbtt_GetGlyphKernAdvance( | 307 | int kern = stbtt_GetGlyphKernAdvance( |
295 | &glyph->font->font, glyph->glyphIndex, nextGlyphIndex); | 308 | &glyph->font->font, index_Glyph_(glyph), nextGlyphIndex); |
296 | /* Nunito needs some kerning fixes. */ | 309 | /* Nunito needs some kerning fixes. */ |
297 | if (glyph->font->family == nunito_TextFont) { | 310 | if (glyph->font->family == nunito_TextFont) { |
298 | if (ch == 'W' && (next == 'i' || next == 'h')) { | 311 | if (ch == 'W' && (next == 'i' || next == 'h')) { |
@@ -317,14 +330,19 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
317 | #endif | 330 | #endif |
318 | xposExtend = iMax(xposExtend, xpos); | 331 | xposExtend = iMax(xposExtend, xpos); |
319 | xposMax = iMax(xposMax, xposExtend); | 332 | xposMax = iMax(xposMax, xposExtend); |
320 | if (args->continueFrom_out && ((mode & noWrapFlag_RunMode) || isWrapBoundary_(prevCh, ch))) { | 333 | if ((wrap && wrap->mode == anyCharacter_WrapTextMode) || isWrapBoundary_(prevCh, ch)) { |
321 | lastWordEnd = currentPos; /* mark word wrap position */ | 334 | lastWordEnd = currentPos; /* mark word wrap position */ |
335 | wrapAdvance = x2 - orig.x; | ||
322 | } | 336 | } |
323 | prevCh = ch; | 337 | prevCh = ch; |
324 | if (--maxLen == 0) { | 338 | if (--maxLen == 0) { |
325 | break; | 339 | break; |
326 | } | 340 | } |
327 | } | 341 | } |
342 | notify_WrapText_(wrap, chPos, 0, xpos - orig.x, iFalse); | ||
343 | if (args->cursorAdvance_out) { | ||
344 | *args->cursorAdvance_out = sub_I2(init_I2(xpos, ypos), orig); | ||
345 | } | ||
328 | if (args->runAdvance_out) { | 346 | if (args->runAdvance_out) { |
329 | *args->runAdvance_out = xposMax - orig.x; | 347 | *args->runAdvance_out = xposMax - orig.x; |
330 | } | 348 | } |