diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-06 07:04:29 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-06 07:04:29 +0300 |
commit | e1ff98965a4aec72418165424a0183e596df1ecb (patch) | |
tree | e01f245348ba39d2a2eb5fdffe5a8600e5928b96 /src/ui/text.c | |
parent | 5915a8509827bbad99731b0e8451f484bff1ac61 (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.c | 524 |
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 | ||
782 | iDeclareType(AttributedText) | 782 | iDeclareType(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) |
1228 | static float glyphRunRightEdge_Font_(const iFont *d, const hb_glyph_position_t *glyphPos, size_t count) { | 1231 | |
1232 | iDeclareType(GlyphBuffer) | ||
1233 | |||
1234 | struct 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 | |||
1243 | static 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 | |||
1252 | static void deinit_GlyphBuffer_(iGlyphBuffer *d) { | ||
1253 | hb_buffer_destroy(d->hb); | ||
1254 | } | ||
1255 | |||
1256 | static 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 | |||
1264 | static 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 | ||
1284 | static 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 | |||
1237 | static iRect run_Font_(iFont *d, const iRunArgs *args) { | 1300 | static 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 | ||
1479 | iInt2 measureRange_Text(int fontId, iRangecc text) { | 1682 | iInt2 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 | |||
1690 | iTextMetrics 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 | ||
1486 | iRect visualBounds_Text(int fontId, iRangecc text) { | 1703 | iRect 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 | ||
1494 | iInt2 measure_Text(int fontId, const char *text) { | 1712 | iInt2 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 | ||
1498 | void cache_Text(int fontId, iRangecc text) { | 1717 | void 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 | ||
1510 | iInt2 advanceRange_Text(int fontId, iRangecc text) { | 1730 | iInt2 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 | ||
1520 | static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int advance) { | 1741 | static 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 | ||
1525 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1746 | iInt2 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 | ||
1547 | iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1770 | iInt2 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 | ||
1556 | iInt2 advance_WrapText(iWrapText *d, int fontId) { | 1780 | iTextMetrics 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), | |
1565 | iRect 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 | |||
1573 | void 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 | ||
1585 | iInt2 advance_Text(int fontId, const char *text) { | 1795 | iInt2 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 | ||
1602 | static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { | 1813 | static 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 | ||
1892 | iTextMetrics 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 | ||
1681 | iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { | 1898 | iInt2 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 | ||
1693 | void drawBoundRange_Text(int fontId, iInt2 pos, int boundWidth, int color, iRangecc text) { | 1911 | void 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 | ||
1747 | void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) { | 1965 | void 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 | ||
1974 | iInt2 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 | |||
1983 | iTextMetrics 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 | |||
1993 | void 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 | |||
1755 | SDL_Texture *glyphCache_Text(void) { | 2005 | SDL_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 | |||
1868 | static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iBool doWrap, const char *text) { | 2118 | static 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(); |