summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-06 07:04:29 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-06 07:04:29 +0300
commite1ff98965a4aec72418165424a0183e596df1ecb (patch)
treee01f245348ba39d2a2eb5fdffe5a8600e5928b96 /src/ui/text.c
parent5915a8509827bbad99731b0e8451f484bff1ac61 (diff)
Text run measurement API change; bug fixes
The distinction between measure_Text and advance_Text was not very clear. Now there are only measure_Text functions that return both the bounds and the cursor position advancement, and the appropriate metrics are used by the caller.
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c524
1 files changed, 387 insertions, 137 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
index 73b521fa..1c509735 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -776,7 +776,7 @@ struct Impl_AttributedRun {
776 iRangei visual; /* UTF-32 codepoint indices in the visual-order text */ 776 iRangei visual; /* UTF-32 codepoint indices in the visual-order text */
777 iFont * font; 777 iFont * font;
778 iColor fgColor; 778 iColor fgColor;
779 int lineBreaks; 779 iBool isLineBreak;
780}; 780};
781 781
782iDeclareType(AttributedText) 782iDeclareType(AttributedText)
@@ -819,7 +819,7 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i
819 finishedRun.visual.end = endAt; 819 finishedRun.visual.end = endAt;
820 if (!isEmpty_Range(&finishedRun.visual)) { 820 if (!isEmpty_Range(&finishedRun.visual)) {
821 pushBack_Array(&d->runs, &finishedRun); 821 pushBack_Array(&d->runs, &finishedRun);
822 run->lineBreaks = 0; 822 run->isLineBreak = iFalse;
823 } 823 }
824 run->visual.start = endAt; 824 run->visual.start = endAt;
825} 825}
@@ -930,8 +930,10 @@ static void prepare_AttributedText_(iAttributedText *d) {
930 } 930 }
931 if (ch == '\n') { 931 if (ch == '\n') {
932 finishRun_AttributedText_(d, &run, pos); 932 finishRun_AttributedText_(d, &run, pos);
933 run.visual.start = pos + 1; 933 /* A separate run for the newline. */
934 run.lineBreaks++; 934 run.visual.start = pos;
935 run.isLineBreak = iTrue;
936 finishRun_AttributedText_(d, &run, pos + 1);
935 continue; 937 continue;
936 } 938 }
937 if (isControl_Char_(ch)) { 939 if (isControl_Char_(ch)) {
@@ -1131,12 +1133,11 @@ static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
1131 const iChar *visualText = constData_Array(&attrText.visual); 1133 const iChar *visualText = constData_Array(&attrText.visual);
1132 iConstForEach(Array, i, &attrText.runs) { 1134 iConstForEach(Array, i, &attrText.runs) {
1133 const iAttributedRun *run = i.value; 1135 const iAttributedRun *run = i.value;
1134 //for (const char *chPos = run->text.start; chPos != run->text.end; ) { 1136 if (run->isLineBreak) {
1137 continue;
1138 }
1135 for (int pos = run->visual.start; pos < run->visual.end; pos++) { 1139 for (int pos = run->visual.start; pos < run->visual.end; pos++) {
1136 const iChar ch = visualText[pos]; 1140 const iChar ch = visualText[pos];
1137// const char *oldPos = chPos;
1138// const iChar ch = nextChar_(&chPos, text.end);
1139// if (chPos == oldPos) break; /* don't get stuck */
1140 if (!isSpace_Char(ch) && !isControl_Char_(ch)) { 1141 if (!isSpace_Char(ch) && !isControl_Char_(ch)) {
1141 const uint32_t glyphIndex = glyphIndex_Font_(d, ch); 1142 const uint32_t glyphIndex = glyphIndex_Font_(d, ch);
1142 if (glyphIndex) { 1143 if (glyphIndex) {
@@ -1179,6 +1180,8 @@ struct Impl_RunArgs {
1179 int xposLimit; /* hard limit for wrapping */ 1180 int xposLimit; /* hard limit for wrapping */
1180 int xposLayoutBound; /* visible bound for layout purposes; does not affect wrapping */ 1181 int xposLayoutBound; /* visible bound for layout purposes; does not affect wrapping */
1181 int color; 1182 int color;
1183 /* TODO: Use TextMetrics output pointer instead of return value & cursorAdvance_out */
1184 iInt2 * cursorAdvance_out;
1182 const char ** continueFrom_out; 1185 const char ** continueFrom_out;
1183 int * runAdvance_out; 1186 int * runAdvance_out;
1184}; 1187};
@@ -1225,19 +1228,79 @@ float horizKern_Font_(iFont *d, uint32_t glyph1, uint32_t glyph2) {
1225} 1228}
1226 1229
1227#if defined (LAGRANGE_ENABLE_HARFBUZZ) 1230#if defined (LAGRANGE_ENABLE_HARFBUZZ)
1228static float glyphRunRightEdge_Font_(const iFont *d, const hb_glyph_position_t *glyphPos, size_t count) { 1231
1232iDeclareType(GlyphBuffer)
1233
1234struct Impl_GlyphBuffer {
1235 hb_buffer_t * hb;
1236 iFont * font;
1237 const iChar * visualText;
1238 hb_glyph_info_t * glyphInfo;
1239 hb_glyph_position_t *glyphPos;
1240 unsigned int glyphCount;
1241};
1242
1243static void init_GlyphBuffer_(iGlyphBuffer *d, iFont *font, const iChar *visualText) {
1244 d->hb = hb_buffer_create();
1245 d->font = font;
1246 d->visualText = visualText;
1247 d->glyphInfo = NULL;
1248 d->glyphPos = NULL;
1249 d->glyphCount = 0;
1250}
1251
1252static void deinit_GlyphBuffer_(iGlyphBuffer *d) {
1253 hb_buffer_destroy(d->hb);
1254}
1255
1256static void shape_GlyphBuffer_(iGlyphBuffer *d) {
1257 if (!d->glyphInfo) {
1258 hb_shape(d->font->hbFont, d->hb, NULL, 0);
1259 d->glyphInfo = hb_buffer_get_glyph_infos(d->hb, &d->glyphCount);
1260 d->glyphPos = hb_buffer_get_glyph_positions(d->hb, &d->glyphCount);
1261 }
1262}
1263
1264static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapVisRange) {
1229 float x = 0.0f; 1265 float x = 0.0f;
1230 for (unsigned int i = 0; i < count; i++) { 1266 for (unsigned int i = 0; i < d->glyphCount; i++) {
1231 const float xAdvance = d->xScale * glyphPos[i].x_advance; 1267 const int visPos = d->glyphInfo[i].cluster;
1232 x += xAdvance; 1268 if (visPos < wrapVisRange.start) {
1269 continue;
1270 }
1271 if (visPos >= wrapVisRange.end) {
1272 break;
1273 }
1274 x += d->font->xScale * d->glyphPos[i].x_advance;
1275 if (i + 1 < d->glyphCount) {
1276 x += horizKern_Font_(d->font,
1277 d->glyphInfo[i].codepoint,
1278 d->glyphInfo[i + 1].codepoint);
1279 }
1233 } 1280 }
1234 return x; 1281 return x;
1235} 1282}
1236 1283
1284static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont) {
1285 shape_GlyphBuffer_(d);
1286 const float monoAdvance = glyph_Font_(baseFont, 'M')->advance;
1287 for (unsigned int i = 0; i < d->glyphCount; ++i) {
1288 const hb_glyph_info_t *info = d->glyphInfo + i;
1289 if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) {
1290 const iChar ch = d->visualText[info->cluster];
1291 if (isPictograph_Char(ch) || isEmoji_Char(ch)) {
1292 const float dw = d->font->xScale * d->glyphPos[i].x_advance - monoAdvance;
1293 d->glyphPos[i].x_offset -= dw / 2 / d->font->xScale - 1;
1294 d->glyphPos[i].x_advance -= dw / d->font->xScale - 1;
1295 }
1296 }
1297 }
1298}
1299
1237static iRect run_Font_(iFont *d, const iRunArgs *args) { 1300static iRect run_Font_(iFont *d, const iRunArgs *args) {
1238 const int mode = args->mode; 1301 const int mode = args->mode;
1239 iRect bounds = zero_Rect();
1240 const iInt2 orig = args->pos; 1302 const iInt2 orig = args->pos;
1303 iRect bounds = { orig, init_I2(0, d->height) };
1241 float xCursor = 0.0f; 1304 float xCursor = 0.0f;
1242 float yCursor = 0.0f; 1305 float yCursor = 0.0f;
1243 float xCursorMax = 0.0f; 1306 float xCursorMax = 0.0f;
@@ -1247,7 +1310,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1247 if (args->continueFrom_out) { 1310 if (args->continueFrom_out) {
1248 *args->continueFrom_out = args->text.end; 1311 *args->continueFrom_out = args->text.end;
1249 } 1312 }
1250 hb_buffer_t *hbBuf = hb_buffer_create();
1251 /* Split the text into a number of attributed runs that specify exactly which 1313 /* Split the text into a number of attributed runs that specify exactly which
1252 font is used and other attributes such as color. (HarfBuzz shaping is done 1314 font is used and other attributes such as color. (HarfBuzz shaping is done
1253 with one specific font.) */ 1315 with one specific font.) */
@@ -1259,64 +1321,171 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1259 /* Initialize the wrap range. */ 1321 /* Initialize the wrap range. */
1260 args->wrap->wrapRange_ = args->text; 1322 args->wrap->wrapRange_ = args->text;
1261 } 1323 }
1262 int wrapResumePos = -1; 1324 const iChar *visualText = constData_Array(&attrText.visual);
1325 const size_t runCount = size_Array(&attrText.runs);
1326 iArray buffers;
1327 init_Array(&buffers, sizeof(iGlyphBuffer));
1328 resize_Array(&buffers, runCount);
1329 /* Prepare the HarfBuzz buffers. They will be lazily shaped when needed. */
1330 iConstForEach(Array, i, &attrText.runs) {
1331 const iAttributedRun *run = i.value;
1332 iGlyphBuffer *buf = at_Array(&buffers, index_ArrayConstIterator(&i));
1333 init_GlyphBuffer_(buf, run->font, visualText);
1334 for (int pos = run->visual.start; pos < run->visual.end; pos++) {
1335 hb_buffer_add(buf->hb, visualText[pos], pos);
1336 }
1337 hb_buffer_set_content_type(buf->hb, HB_BUFFER_CONTENT_TYPE_UNICODE);
1338 hb_buffer_set_direction(buf->hb, HB_DIRECTION_LTR);
1339 /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */
1340 }
1341 if (isMonospaced) {
1342 /* Fit borrowed glyphs into the expected monospacing. */
1343 for (size_t runIndex = 0; runIndex < runCount; runIndex++) {
1344 evenMonospaceAdvances_GlyphBuffer_(at_Array(&buffers, runIndex), d);
1345 }
1346 }
1263 iBool willAbortDueToWrap = iFalse; 1347 iBool willAbortDueToWrap = iFalse;
1264 const iRangecc sourceText = attrText.source; 1348 const iRangecc sourceText = attrText.source;
1265 const iChar * visualText = constData_Array(&attrText.visual);
1266 const int * visToSource = constData_Array(&attrText.visualToSourceOffset); 1349 const int * visToSource = constData_Array(&attrText.visualToSourceOffset);
1267 for (size_t runIndex = 0; runIndex < size_Array(&attrText.runs); runIndex++) { 1350 const size_t visSize = size_Array(&attrText.visual);
1268 const iAttributedRun *run = at_Array(&attrText.runs, runIndex); 1351 iRanges wrapRuns = { 0, runCount };
1269 iRangei runVisual = run->visual; 1352 iRangei wrapVisRange = { 0, visSize };
1270 if (wrapResumePos >= 0) { 1353 int wrapResumePos = visSize;
1271 xCursor = 0.0f; 1354 size_t wrapResumeRunIndex = runCount;
1272 yCursor += d->height; 1355 while (!isEmpty_Range(&wrapRuns)) {
1273 runVisual.start = wrapResumePos; 1356 float wrapAdvance = 0.0f;
1274 wrapResumePos = NULL; 1357 if (args->wrap && args->wrap->maxWidth > 0) {
1275 } 1358 iAssert(wrapVisRange.end == visSize);
1276 else if (run->lineBreaks) { 1359 /* Determine ends of wrapRuns and wrapVisRange. */
1277 for (int i = 0; i < run->lineBreaks; i++) { 1360 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1278 /* One callback for each "wrapped" line even if they're empty. */ 1361 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1279 if (!notify_WrapText_(args->wrap, 1362 if (run->isLineBreak) {
1280 sourcePtr_AttributedText_(&attrText, run->visual.start), 1363 wrapVisRange.end = run->visual.start;
1281 iRound(xCursor))) { 1364 wrapResumePos = run->visual.end;
1365 wrapRuns.end = runIndex;
1366 wrapResumeRunIndex = runIndex + 1;
1367 yCursor += d->height;
1282 break; 1368 break;
1283 } 1369 }
1284 xCursor = 0.0f; 1370 wrapResumeRunIndex = runCount;
1285 yCursor += d->height; 1371 wrapResumePos = visSize;
1372 iGlyphBuffer *buf = at_Array(&buffers, runIndex);
1373 iAssert(run->font == buf->font);
1374 shape_GlyphBuffer_(buf);
1375 int safeBreakPos = -1;
1376 iChar prevCh = 0;
1377 for (unsigned int i = 0; i < buf->glyphCount; i++) {
1378 const hb_glyph_info_t *info = &buf->glyphInfo[i];
1379 const hb_codepoint_t glyphId = info->codepoint;
1380 const int visPos = info->cluster;
1381 if (visPos < wrapVisRange.start) {
1382 continue;
1383 }
1384 const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId);
1385 const int glyphFlags = hb_glyph_info_get_glyph_flags(info);
1386 const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset;
1387 const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance;
1388 if (args->wrap->mode == word_WrapTextMode) {
1389 /* When word wrapping, only consider certain places breakable. */
1390 const iChar ch = visualText[info->cluster];
1391 if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) {
1392 safeBreakPos = visPos;
1393 // isSoftHyphenBreak = iFalse;
1394 }
1395 else if (isSpace_Char(ch)) {
1396 safeBreakPos = visPos;
1397 // isSoftHyphenBreak = iFalse;
1398 }
1399 prevCh = ch;
1400 }
1401 else {
1402 if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) {
1403 safeBreakPos = visPos;
1404 }
1405 }
1406 /* Out of room? */
1407 if (wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x >
1408 args->wrap->maxWidth) {
1409 if (safeBreakPos >= 0) {
1410 wrapVisRange.end = safeBreakPos;
1411 }
1412 else {
1413 wrapVisRange.end = visPos;
1414 }
1415 wrapResumePos = wrapVisRange.end;
1416 while (wrapResumePos < visSize && isSpace_Char(visualText[wrapResumePos])) {
1417 wrapResumePos++; /* skip space */
1418 }
1419 wrapRuns.end = runIndex + 1; /* still includes this run */
1420 wrapResumeRunIndex = runIndex; /* ...but continue from the same one */
1421 break;
1422 }
1423 wrapAdvance += xAdvance;
1424 /* Additional kerning tweak. It would be better to use HarfBuzz font callbacks,
1425 but they don't seem to get called? */
1426 if (i + 1 < buf->glyphCount) {
1427 wrapAdvance += horizKern_Font_(buf->font,
1428 glyphId,
1429 buf->glyphInfo[i + 1].codepoint);
1430 }
1431 }
1286 } 1432 }
1287 } 1433 }
1288 const iRangecc runSource = sourceRange_AttributedText_(&attrText, runVisual); 1434 else {
1289 hb_buffer_clear_contents(hbBuf); 1435 /* Calculate total advance without wrapping. */
1290 iBool isRunRTL = iFalse; 1436 for (size_t i = wrapRuns.start; i < wrapRuns.end; i++) {
1291 /* Cluster values are used to determine offset inside the UTF-8 source string. */ 1437 wrapAdvance += advance_GlyphBuffer_(at_Array(&buffers, i), wrapVisRange);
1292 //hb_buffer_set_cluster_level(hbBuf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); 1438 }
1293 for (int pos = runVisual.start; pos < runVisual.end; pos++) {
1294 hb_buffer_add(hbBuf, visualText[pos], pos);
1295 } 1439 }
1296 hb_buffer_set_content_type(hbBuf, HB_BUFFER_CONTENT_TYPE_UNICODE); 1440 /* Make a callback for each wrapped line. */
1297 hb_buffer_set_direction(hbBuf, HB_DIRECTION_LTR); 1441 if (!notify_WrapText_(args->wrap,
1298 /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */ 1442// sourcePtr_AttributedText_(&attrText, wrapVisRange.end),
1299 //hb_buffer_set_language(hbBuf, hb_language_from_string("en", -1)); /* TODO: language from document/UI, if known */ 1443 sourcePtr_AttributedText_(&attrText, wrapResumePos),
1300 hb_shape(run->font->hbFont, hbBuf, NULL, 0); /* TODO: Specify features, too? */ 1444 iRound(wrapAdvance))) {
1301 unsigned int glyphCount = 0; 1445 willAbortDueToWrap = iTrue;
1302 const hb_glyph_info_t * glyphInfo = hb_buffer_get_glyph_infos(hbBuf, &glyphCount); 1446 }
1303 hb_glyph_position_t * glyphPos = hb_buffer_get_glyph_positions(hbBuf, &glyphCount); 1447#if 0
1304 int breakPos = -1; 1448 if (wrapResumePos >= 0) {
1305// iBool isSoftHyphenBreak = iFalse; 1449 xCursor = 0.0f;
1306 /* Fit foreign glyphs into the expected monospacing. */ 1450 yCursor += d->height;
1307 if (isMonospaced) { 1451 runVisual.start = wrapResumePos;
1308 for (unsigned int i = 0; i < glyphCount; ++i) { 1452 wrapResumePos = NULL;
1309 const hb_glyph_info_t *info = &glyphInfo[i]; 1453 }
1310 if (glyphPos[i].x_advance > 0 && run->font != d) { 1454 else if (run->lineBreaks) {
1311 const iChar ch = visualText[info->cluster]; 1455 for (int i = 0; i < run->lineBreaks; i++) {
1312 if (isPictograph_Char(ch) || isEmoji_Char(ch)) { 1456 /* One callback for each "wrapped" line even if they're empty. */
1313 const float dw = run->font->xScale * glyphPos[i].x_advance - monoAdvance; 1457 if (!notify_WrapText_(args->wrap,
1314 glyphPos[i].x_offset -= dw / 2 / run->font->xScale - 1; 1458 sourcePtr_AttributedText_(&attrText, run->visual.start),
1315 glyphPos[i].x_advance -= dw / run->font->xScale - 1; 1459 iRound(xCursor))) {
1460 break;
1316 } 1461 }
1462 xCursor = 0.0f;
1463 yCursor += d->height;
1317 } 1464 }
1318 } 1465 }
1319 } 1466 const iRangecc runSource = sourceRange_AttributedText_(&attrText, runVisual);
1467 hb_buffer_clear_contents(hbBuf);
1468 iBool isRunRTL = attrText.bidiLevels != NULL;
1469 /* Cluster values are used to determine offset inside the UTF-8 source string. */
1470 //hb_buffer_set_cluster_level(hbBuf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
1471 for (int pos = runVisual.start; pos < runVisual.end; pos++) {
1472 hb_buffer_add(hbBuf, visualText[pos], pos);
1473 if (attrText.bidiLevels) {
1474 const char lev = attrText.bidiLevels[pos];
1475 if (!FRIBIDI_IS_NEUTRAL(lev) && !FRIBIDI_IS_RTL(lev)) {
1476 isRunRTL = iFalse;
1477 }
1478 }
1479 }
1480 hb_buffer_set_content_type(hbBuf, HB_BUFFER_CONTENT_TYPE_UNICODE);
1481 hb_buffer_set_direction(hbBuf, HB_DIRECTION_LTR);
1482 /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */
1483 //hb_buffer_set_language(hbBuf, hb_language_from_string("en", -1)); /* TODO: language from document/UI, if known */
1484 hb_shape(run->font->hbFont, hbBuf, NULL, 0); /* TODO: Specify features, too? */
1485 unsigned int glyphCount = 0;
1486 const hb_glyph_info_t * glyphInfo = hb_buffer_get_glyph_infos(hbBuf, &glyphCount);
1487 hb_glyph_position_t * glyphPos = hb_buffer_get_glyph_positions(hbBuf, &glyphCount);
1488// iBool isSoftHyphenBreak = iFalse;
1320 /* Check if this run needs to be wrapped. If so, we'll draw the portion that fits on 1489 /* Check if this run needs to be wrapped. If so, we'll draw the portion that fits on
1321 the line, and re-run the loop resuming the run from the wrap point. */ 1490 the line, and re-run the loop resuming the run from the wrap point. */
1322 if (args->wrap && args->wrap->maxWidth > 0) { 1491 if (args->wrap && args->wrap->maxWidth > 0) {
@@ -1379,25 +1548,48 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1379 xCursor += args->xposLayoutBound - orig.x - 1548 xCursor += args->xposLayoutBound - orig.x -
1380 glyphRunRightEdge_Font_(run->font, glyphPos, glyphCount); 1549 glyphRunRightEdge_Font_(run->font, glyphPos, glyphCount);
1381 } 1550 }
1382 /* We have determined a possible wrap position inside the text run, so now we can 1551#endif
1383 draw the glyphs. */ 1552 /* TODO: Alignment. */
1384 for (unsigned int i = 0; i < glyphCount; i++) { 1553 xCursor = 0.0f;
1385 const hb_glyph_info_t *info = &glyphInfo[i]; 1554 /* We have determined a possible wrap position and alignment for the work runs,
1386 const hb_codepoint_t glyphId = info->codepoint; 1555 so now we can process the glyphs. */
1387 const int visPos = info->cluster; 1556 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1388 const float xOffset = run->font->xScale * glyphPos[i].x_offset; 1557 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1389 const float yOffset = run->font->yScale * glyphPos[i].y_offset; 1558 if (run->isLineBreak) {
1390 const float xAdvance = run->font->xScale * glyphPos[i].x_advance; 1559 xCursor = 0.0f;
1391 const float yAdvance = run->font->yScale * glyphPos[i].y_advance; 1560 yCursor += d->height;
1392 const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); 1561 continue;
1393 const float xf = xCursor + xOffset;
1394 const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0;
1395 if (visPos == breakPos) {
1396 runIndex--; /* stay on the same run */
1397 wrapResumePos = visPos;
1398 break;
1399 } 1562 }
1400 /* Draw the glyph. */ { 1563 iGlyphBuffer *buf = at_Array(&buffers, runIndex);
1564 shape_GlyphBuffer_(buf);
1565 iAssert(run->font == buf->font);
1566 iRangei runVisual = run->visual;
1567 /* Process all the glyphs. */
1568 for (unsigned int i = 0; i < buf->glyphCount; i++) {
1569 const hb_glyph_info_t *info = &buf->glyphInfo[i];
1570 const hb_codepoint_t glyphId = info->codepoint;
1571 const int visPos = info->cluster;
1572 if (visPos < wrapVisRange.start) {
1573 continue;
1574 }
1575 if (visPos >= wrapVisRange.end) {
1576 break;
1577 }
1578 const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset;
1579 const float yOffset = run->font->yScale * buf->glyphPos[i].y_offset;
1580 const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance;
1581 const float yAdvance = run->font->yScale * buf->glyphPos[i].y_advance;
1582 const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId);
1583 const float xf = xCursor + xOffset;
1584 const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0;
1585#if 0
1586 if (visPos == breakPos) {
1587 runIndex--; /* stay on the same run */
1588 wrapResumePos = visPos;
1589 break;
1590 }
1591#endif
1592 /* Output position for the glyph. */
1401 SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x, 1593 SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x,
1402 orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, 1594 orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y,
1403 glyph->rect[hoff].size.x, 1595 glyph->rect[hoff].size.x,
@@ -1414,7 +1606,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1414 bounds.size.x = iMax(bounds.size.x, dst.x + dst.w - orig.x); 1606 bounds.size.x = iMax(bounds.size.x, dst.x + dst.w - orig.x);
1415 bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height); 1607 bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height);
1416 } 1608 }
1417 if (mode & draw_RunMode && visualText[visPos] != 0x20) { 1609 if (mode & draw_RunMode && visualText[visPos] > 0x20) {
1610 /* Draw the glyph. */
1418 if (!isRasterized_Glyph_(glyph, hoff)) { 1611 if (!isRasterized_Glyph_(glyph, hoff)) {
1419 cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ 1612 cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */
1420 glyph = glyphByIndex_Font_(run->font, glyphId); 1613 glyph = glyphByIndex_Font_(run->font, glyphId);
@@ -1437,29 +1630,38 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1437 } 1630 }
1438 SDL_RenderCopy(text_.render, text_.cache, &src, &dst); 1631 SDL_RenderCopy(text_.render, text_.cache, &src, &dst);
1439 } 1632 }
1633 xCursor += xAdvance;
1634 yCursor += yAdvance;
1635 /* Additional kerning tweak. It would be better to use HarfBuzz font callbacks,
1636 but they don't seem to get called? */
1637 if (i + 1 < buf->glyphCount) {
1638 xCursor += horizKern_Font_(run->font, glyphId, buf->glyphInfo[i + 1].codepoint);
1639 }
1640 xCursorMax = iMax(xCursorMax, xCursor);
1440 } 1641 }
1441 xCursor += xAdvance;
1442 yCursor += yAdvance;
1443 /* Additional kerning tweak. It would be better to use HarfBuzz font callbacks,
1444 but they don't seem to get called? */
1445 if (i + 1 < glyphCount) {
1446 xCursor += horizKern_Font_(run->font, glyphId, glyphInfo[i + 1].codepoint);
1447 }
1448 xCursorMax = iMax(xCursorMax, xCursor);
1449 } 1642 }
1450 if (willAbortDueToWrap) { 1643 if (willAbortDueToWrap) {
1451 break; 1644 break;
1452 } 1645 }
1646 wrapRuns.start = wrapResumeRunIndex;
1647 wrapRuns.end = runCount;
1648 wrapVisRange.start = wrapResumePos;
1649 wrapVisRange.end = visSize;
1650 yCursor += d->height;
1453 } 1651 }
1454 if (args->wrap) { 1652 if (args->cursorAdvance_out) {
1455 args->wrap->cursor_out = init_I2(xCursor, yCursor); 1653 *args->cursorAdvance_out = init_I2(xCursor, yCursor);
1456 } 1654 }
1655#if 0
1457 /* The final, unbroken line. */ 1656 /* The final, unbroken line. */
1458 notify_WrapText_(args->wrap, NULL, xCursor); 1657 notify_WrapText_(args->wrap, NULL, xCursor);
1658#endif
1459 if (args->runAdvance_out) { 1659 if (args->runAdvance_out) {
1460 *args->runAdvance_out = xCursorMax; 1660 *args->runAdvance_out = xCursorMax;
1461 } 1661 }
1462 hb_buffer_destroy(hbBuf); 1662 iForEach(Array, b, &buffers) {
1663 deinit_GlyphBuffer_(b.value);
1664 }
1463 deinit_AttributedText(&attrText); 1665 deinit_AttributedText(&attrText);
1464 return bounds; 1666 return bounds;
1465} 1667}
@@ -1476,12 +1678,27 @@ int lineHeight_Text(int fontId) {
1476 return font_Text_(fontId)->height; 1678 return font_Text_(fontId)->height;
1477} 1679}
1478 1680
1681#if 0
1479iInt2 measureRange_Text(int fontId, iRangecc text) { 1682iInt2 measureRange_Text(int fontId, iRangecc text) {
1480 if (isEmpty_Range(&text)) { 1683 if (isEmpty_Range(&text)) {
1481 return init_I2(0, lineHeight_Text(fontId)); 1684 return init_I2(0, lineHeight_Text(fontId));
1482 } 1685 }
1483 return run_Font_(font_Text_(fontId), &(iRunArgs){ .mode = measure_RunMode, .text = text }).size; 1686 return run_Font_(font_Text_(fontId), &(iRunArgs){ .mode = measure_RunMode, .text = text }).size;
1484} 1687}
1688#endif
1689
1690iTextMetrics measureRange_Text(int fontId, iRangecc text) {
1691 if (isEmpty_Range(&text)) {
1692 return (iTextMetrics){ init_Rect(0, 0, 0, lineHeight_Text(fontId)), zero_I2() };
1693 }
1694 iTextMetrics tm;
1695 tm.bounds = run_Font_(font_Text_(fontId), &(iRunArgs){
1696 .mode = measure_RunMode,
1697 .text = text,
1698 .cursorAdvance_out = &tm.advance
1699 });
1700 return tm;
1701}
1485 1702
1486iRect visualBounds_Text(int fontId, iRangecc text) { 1703iRect visualBounds_Text(int fontId, iRangecc text) {
1487 return run_Font_(font_Text_(fontId), 1704 return run_Font_(font_Text_(fontId),
@@ -1491,9 +1708,11 @@ iRect visualBounds_Text(int fontId, iRangecc text) {
1491 }); 1708 });
1492} 1709}
1493 1710
1711#if 0
1494iInt2 measure_Text(int fontId, const char *text) { 1712iInt2 measure_Text(int fontId, const char *text) {
1495 return measureRange_Text(fontId, range_CStr(text)); 1713 return measureRange_Text(fontId, range_CStr(text));
1496} 1714}
1715#endif
1497 1716
1498void cache_Text(int fontId, iRangecc text) { 1717void cache_Text(int fontId, iRangecc text) {
1499 cacheTextGlyphs_Font_(font_Text_(fontId), text); 1718 cacheTextGlyphs_Font_(font_Text_(fontId), text);
@@ -1507,6 +1726,7 @@ static int runFlagsFromId_(enum iFontId fontId) {
1507 return runFlags; 1726 return runFlags;
1508} 1727}
1509 1728
1729#if 0
1510iInt2 advanceRange_Text(int fontId, iRangecc text) { 1730iInt2 advanceRange_Text(int fontId, iRangecc text) {
1511 int advance; 1731 int advance;
1512 const int height = run_Font_(font_Text_(fontId), 1732 const int height = run_Font_(font_Text_(fontId),
@@ -1516,19 +1736,22 @@ iInt2 advanceRange_Text(int fontId, iRangecc text) {
1516 .size.y; 1736 .size.y;
1517 return init_I2(advance, height); 1737 return init_I2(advance, height);
1518} 1738}
1739#endif
1519 1740
1520static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int advance) { 1741static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int advance) {
1521 *((const char **) d->context) = iMin(skipSpace_CStr(range.end), d->text.end); 1742 *((const char **) d->context) = range.end; // iMin(skipSpace_CStr(range.end), d->text.end);
1522 return iFalse; /* just one line */ 1743 return iFalse; /* just one line */
1523} 1744}
1524 1745
1525iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { 1746iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) {
1526 iWrapText wrap = { .mode = word_WrapTextMode, 1747 /* FIXME: Get rid of this. Caller could use WrapText directly? */
1527 .text = text, .maxWidth = width, 1748 iWrapText wrap = { .mode = word_WrapTextMode,
1528 .wrapFunc = cbAdvanceOneLine_, .context = endPos }; 1749 .text = text,
1529 /* The return value is expected to be the horizontal/vertical bounds, while WrapText 1750 .maxWidth = width,
1530 returns the actual advanced cursor position. */ 1751 .wrapFunc = cbAdvanceOneLine_,
1531 return addY_I2(advance_WrapText(&wrap, fontId), lineHeight_Text(fontId)); 1752 .context = endPos };
1753 /* The return value is expected to be the horizontal/vertical bounds. */
1754 return measure_WrapText(&wrap, fontId).bounds.size;
1532 1755
1533#if 0 1756#if 0
1534 int advance; 1757 int advance;
@@ -1546,42 +1769,29 @@ iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos)
1546 1769
1547iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { 1770iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) {
1548 /* TODO: "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ 1771 /* TODO: "NoWrap" means words aren't wrapped; the line is broken at nearest character. */
1549 iWrapText wrap = { .mode = anyCharacter_WrapTextMode, 1772 iWrapText wrap = { .mode = anyCharacter_WrapTextMode,
1550 .text = text, .maxWidth = width, 1773 .text = text,
1551 .wrapFunc = cbAdvanceOneLine_, .context = endPos }; 1774 .maxWidth = width,
1552 const int x = advance_WrapText(&wrap, fontId).x; 1775 .wrapFunc = cbAdvanceOneLine_,
1553 return init_I2(x, lineHeight_Text(fontId)); 1776 .context = endPos };
1777 return measure_WrapText(&wrap, fontId).bounds.size;
1554} 1778}
1555 1779
1556iInt2 advance_WrapText(iWrapText *d, int fontId) { 1780iTextMetrics measureN_Text(int fontId, const char *text, size_t n) {
1557 run_Font_(font_Text_(fontId), 1781 if (n == 0) {
1558 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId), 1782 return (iTextMetrics){ init_Rect(0, 0, 0, lineHeight_Text(fontId)),
1559 .text = d->text, 1783 zero_I2() };
1560 .wrap = d 1784 }
1561 }); 1785 iTextMetrics tm;
1562 return d->cursor_out; 1786 tm.bounds = run_Font_(font_Text_(fontId),
1563} 1787 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId),
1564 1788 .text = range_CStr(text),
1565iRect measure_WrapText(iWrapText *d, int fontId) { 1789 .maxLen = n,
1566 return run_Font_(font_Text_(fontId), 1790 .cursorAdvance_out = &tm.advance });
1567 &(iRunArgs){ .mode = measure_RunMode | visualFlag_RunMode | runFlagsFromId_(fontId), 1791 return tm;
1568 .text = d->text,
1569 .wrap = d
1570 });
1571}
1572
1573void draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {
1574 run_Font_(font_Text_(fontId),
1575 &(iRunArgs){ .mode = draw_RunMode | runFlagsFromId_(fontId) |
1576 (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) |
1577 (color & fillBackground_ColorId ? fillBackground_RunMode : 0),
1578 .text = d->text,
1579 .pos = pos,
1580 .wrap = d,
1581 .color = color,
1582 });
1583} 1792}
1584 1793
1794#if 0
1585iInt2 advance_Text(int fontId, const char *text) { 1795iInt2 advance_Text(int fontId, const char *text) {
1586 return advanceRange_Text(fontId, range_CStr(text)); 1796 return advanceRange_Text(fontId, range_CStr(text));
1587} 1797}
@@ -1591,13 +1801,14 @@ iInt2 advanceN_Text(int fontId, const char *text, size_t n) {
1591 return init_I2(0, lineHeight_Text(fontId)); 1801 return init_I2(0, lineHeight_Text(fontId));
1592 } 1802 }
1593 int advance; 1803 int advance;
1594 run_Font_(font_Text_(fontId), 1804 int height = run_Font_(font_Text_(fontId),
1595 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId), 1805 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId),
1596 .text = range_CStr(text), 1806 .text = range_CStr(text),
1597 .maxLen = n, 1807 .maxLen = n,
1598 .runAdvance_out = &advance }); 1808 .runAdvance_out = &advance }).size.y;
1599 return init_I2(advance, lineHeight_Text(fontId)); 1809 return init_I2(advance, height);
1600} 1810}
1811#endif
1601 1812
1602static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { 1813static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) {
1603 iText *d = &text_; 1814 iText *d = &text_;
@@ -1633,10 +1844,10 @@ void drawAlign_Text(int fontId, iInt2 pos, int color, enum iAlignment align, con
1633 va_end(args); 1844 va_end(args);
1634 } 1845 }
1635 if (align == center_Alignment) { 1846 if (align == center_Alignment) {
1636 pos.x -= measure_Text(fontId, cstr_Block(&chars)).x / 2; 1847 pos.x -= measure_Text(fontId, cstr_Block(&chars)).bounds.size.x / 2;
1637 } 1848 }
1638 else if (align == right_Alignment) { 1849 else if (align == right_Alignment) {
1639 pos.x -= measure_Text(fontId, cstr_Block(&chars)).x; 1850 pos.x -= measure_Text(fontId, cstr_Block(&chars)).bounds.size.x;
1640 } 1851 }
1641 draw_Text_(fontId, pos, color, range_Block(&chars)); 1852 draw_Text_(fontId, pos, color, range_Block(&chars));
1642 deinit_Block(&chars); 1853 deinit_Block(&chars);
@@ -1678,6 +1889,12 @@ void drawOutline_Text(int fontId, iInt2 pos, int outlineColor, int fillColor, iR
1678 } 1889 }
1679} 1890}
1680 1891
1892iTextMetrics measureWrapRange_Text(int fontId, int maxWidth, iRangecc text) {
1893 iWrapText wrap = { .text = text, .maxWidth = maxWidth, .mode = word_WrapTextMode };
1894 return measure_WrapText(&wrap, fontId);
1895}
1896
1897#if 0
1681iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { 1898iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) {
1682 iInt2 size = zero_I2(); 1899 iInt2 size = zero_I2();
1683 const char *endp; 1900 const char *endp;
@@ -1689,6 +1906,7 @@ iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) {
1689 } 1906 }
1690 return size; 1907 return size;
1691} 1908}
1909#endif
1692 1910
1693void drawBoundRange_Text(int fontId, iInt2 pos, int boundWidth, int color, iRangecc text) { 1911void drawBoundRange_Text(int fontId, iInt2 pos, int boundWidth, int color, iRangecc text) {
1694 /* This function is used together with text that has already been wrapped, so we'll know 1912 /* This function is used together with text that has already been wrapped, so we'll know
@@ -1746,12 +1964,44 @@ void drawCenteredOutline_Text(int fontId, iRect rect, iBool alignVisual, int out
1746 1964
1747void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) { 1965void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) {
1748 iRect textBounds = alignVisual ? visualBounds_Text(fontId, text) 1966 iRect textBounds = alignVisual ? visualBounds_Text(fontId, text)
1749 : (iRect){ zero_I2(), advanceRange_Text(fontId, text) }; 1967 : measureRange_Text(fontId, text).bounds;
1750 textBounds.pos = sub_I2(mid_Rect(rect), mid_Rect(textBounds)); 1968 textBounds.pos = sub_I2(mid_Rect(rect), mid_Rect(textBounds));
1751 textBounds.pos.x = iMax(textBounds.pos.x, left_Rect(rect)); /* keep left edge visible */ 1969 textBounds.pos.x = iMax(textBounds.pos.x, left_Rect(rect)); /* keep left edge visible */
1752 draw_Text_(fontId, textBounds.pos, color, text); 1970 draw_Text_(fontId, textBounds.pos, color, text);
1753} 1971}
1754 1972
1973#if 0
1974iInt2 advance_WrapText(iWrapText *d, int fontId) {
1975 const iRect bounds = run_Font_(font_Text_(fontId),
1976 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId),
1977 .text = d->text,
1978 .wrap = d });
1979 return bounds.size;
1980}
1981#endif
1982
1983iTextMetrics measure_WrapText(iWrapText *d, int fontId) {
1984 iTextMetrics tm;
1985 tm.bounds = run_Font_(font_Text_(fontId),
1986 &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId),
1987 .text = d->text,
1988 .wrap = d,
1989 .cursorAdvance_out = &tm.advance });
1990 return tm;
1991}
1992
1993void draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {
1994 run_Font_(font_Text_(fontId),
1995 &(iRunArgs){ .mode = draw_RunMode | runFlagsFromId_(fontId) |
1996 (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) |
1997 (color & fillBackground_ColorId ? fillBackground_RunMode : 0),
1998 .text = d->text,
1999 .pos = pos,
2000 .wrap = d,
2001 .color = color,
2002 });
2003}
2004
1755SDL_Texture *glyphCache_Text(void) { 2005SDL_Texture *glyphCache_Text(void) {
1756 return text_.cache; 2006 return text_.cache;
1757} 2007}
@@ -1868,7 +2118,7 @@ iDefineTypeConstructionArgs(TextBuf, (int font, int color, const char *text), fo
1868static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iBool doWrap, const char *text) { 2118static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iBool doWrap, const char *text) {
1869 SDL_Renderer *render = text_.render; 2119 SDL_Renderer *render = text_.render;
1870 if (maxWidth == 0) { 2120 if (maxWidth == 0) {
1871 d->size = advance_Text(font, text); 2121 d->size = measure_Text(font, text).bounds.size;
1872 } 2122 }
1873 else { 2123 else {
1874 d->size = zero_I2(); 2124 d->size = zero_I2();