diff options
-rw-r--r-- | src/gmdocument.c | 10 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 30 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 17 | ||||
-rw-r--r-- | src/ui/labelwidget.c | 8 | ||||
-rw-r--r-- | src/ui/lookupwidget.c | 2 | ||||
-rw-r--r-- | src/ui/mediaui.c | 6 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 8 | ||||
-rw-r--r-- | src/ui/text.c | 524 | ||||
-rw-r--r-- | src/ui/text.h | 34 | ||||
-rw-r--r-- | src/ui/translation.c | 2 | ||||
-rw-r--r-- | src/ui/util.c | 4 |
11 files changed, 454 insertions, 191 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index cde6f4c9..d49cccd9 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -166,7 +166,7 @@ static iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const ch | |||
166 | } | 166 | } |
167 | contents->end = line.end; | 167 | contents->end = line.end; |
168 | } | 168 | } |
169 | return measureRange_Text(font, *contents); | 169 | return measureRange_Text(font, *contents).bounds.size; |
170 | } | 170 | } |
171 | 171 | ||
172 | static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) { | 172 | static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) { |
@@ -483,7 +483,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
483 | if (meta.pixelRect.size.x > | 483 | if (meta.pixelRect.size.x > |
484 | d->size.x - (enableIndents ? indents[preformatted_GmLineType] : 0) * gap_Text) { | 484 | d->size.x - (enableIndents ? indents[preformatted_GmLineType] : 0) * gap_Text) { |
485 | preFont = preformattedSmall_FontId; | 485 | preFont = preformattedSmall_FontId; |
486 | meta.pixelRect.size = measureRange_Text(preFont, meta.contents); | 486 | meta.pixelRect.size = measureRange_Text(preFont, meta.contents).bounds.size; |
487 | } | 487 | } |
488 | trimLine_Rangecc(&line, type, isNormalized); | 488 | trimLine_Rangecc(&line, type, isNormalized); |
489 | meta.altText = line; /* without the ``` */ | 489 | meta.altText = line; /* without the ``` */ |
@@ -614,8 +614,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
614 | altText.color = tmQuote_ColorId; | 614 | altText.color = tmQuote_ColorId; |
615 | altText.text = isBlank ? range_Lang(range_CStr("doc.pre.nocaption")) | 615 | altText.text = isBlank ? range_Lang(range_CStr("doc.pre.nocaption")) |
616 | : meta->altText; | 616 | : meta->altText; |
617 | iInt2 size = advanceWrapRange_Text(altText.font, d->size.x - 2 * margin.x, | 617 | iInt2 size = measureWrapRange_Text(altText.font, d->size.x - 2 * margin.x, |
618 | altText.text); | 618 | altText.text).bounds.size; |
619 | altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, | 619 | altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, |
620 | size.y + 2 * margin.y); | 620 | size.y + 2 * margin.y); |
621 | altText.preId = preId; | 621 | altText.preId = preId; |
@@ -661,7 +661,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
661 | quoteRun.text = range_CStr(quote); | 661 | quoteRun.text = range_CStr(quote); |
662 | quoteRun.color = tmQuoteIcon_ColorId; | 662 | quoteRun.color = tmQuoteIcon_ColorId; |
663 | iRect vis = visualBounds_Text(quoteRun.font, quoteRun.text); | 663 | iRect vis = visualBounds_Text(quoteRun.font, quoteRun.text); |
664 | quoteRun.visBounds.size = advance_Text(quoteRun.font, quote); | 664 | quoteRun.visBounds.size = measure_Text(quoteRun.font, quote).bounds.size; |
665 | quoteRun.visBounds.pos = | 665 | quoteRun.visBounds.pos = |
666 | add_I2(pos, | 666 | add_I2(pos, |
667 | init_I2((indents[quote_GmLineType] - 5) * gap_Text, | 667 | init_I2((indents[quote_GmLineType] - 5) * gap_Text, |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 6a2173af..5bded6c6 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -942,7 +942,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | |||
942 | prependChar_String(text, siteIcon); | 942 | prependChar_String(text, siteIcon); |
943 | prependCStr_String(text, escape_Color(uiIcon_ColorId)); | 943 | prependCStr_String(text, escape_Color(uiIcon_ColorId)); |
944 | } | 944 | } |
945 | const int width = advanceRange_Text(default_FontId, range_String(text)).x; | 945 | const int width = measureRange_Text(default_FontId, range_String(text)).advance.x; |
946 | if (width <= avail || | 946 | if (width <= avail || |
947 | isEmpty_StringArray(title)) { | 947 | isEmpty_StringArray(title)) { |
948 | updateText_LabelWidget(tabButton, text); | 948 | updateText_LabelWidget(tabButton, text); |
@@ -953,7 +953,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | |||
953 | const char *endPos; | 953 | const char *endPos; |
954 | tryAdvanceNoWrap_Text(default_FontId, | 954 | tryAdvanceNoWrap_Text(default_FontId, |
955 | range_String(text), | 955 | range_String(text), |
956 | avail - advance_Text(default_FontId, "...").x, | 956 | avail - measure_Text(default_FontId, "...").advance.x, |
957 | &endPos); | 957 | &endPos); |
958 | updateText_LabelWidget( | 958 | updateText_LabelWidget( |
959 | tabButton, | 959 | tabButton, |
@@ -3898,17 +3898,17 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol | |||
3898 | contains_Range(&mark, run->text.start))) { | 3898 | contains_Range(&mark, run->text.start))) { |
3899 | int x = 0; | 3899 | int x = 0; |
3900 | if (!*isInside) { | 3900 | if (!*isInside) { |
3901 | x = advanceRange_Text(run->font, | 3901 | x = measureRange_Text(run->font, |
3902 | (iRangecc){ run->text.start, iMax(run->text.start, mark.start) }) | 3902 | (iRangecc){ run->text.start, iMax(run->text.start, mark.start) }) |
3903 | .x; | 3903 | .advance.x; |
3904 | } | 3904 | } |
3905 | int w = width_Rect(run->visBounds) - x; | 3905 | int w = width_Rect(run->visBounds) - x; |
3906 | if (contains_Range(&run->text, mark.end) || mark.end < run->text.start) { | 3906 | if (contains_Range(&run->text, mark.end) || mark.end < run->text.start) { |
3907 | w = advanceRange_Text( | 3907 | w = measureRange_Text( |
3908 | run->font, | 3908 | run->font, |
3909 | !*isInside ? mark | 3909 | !*isInside ? mark |
3910 | : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) }) | 3910 | : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) }) |
3911 | .x; | 3911 | .advance.x; |
3912 | *isInside = iFalse; | 3912 | *isInside = iFalse; |
3913 | } | 3913 | } |
3914 | else { | 3914 | else { |
@@ -4013,8 +4013,8 @@ static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2 | |||
4013 | appendCStr_String(&str, "\n"); | 4013 | appendCStr_String(&str, "\n"); |
4014 | appendCStr_String(&str, cstr_Lang("dlg.certwarn.domain.expired")); | 4014 | appendCStr_String(&str, cstr_Lang("dlg.certwarn.domain.expired")); |
4015 | } | 4015 | } |
4016 | const iInt2 dims = advanceWrapRange_Text( | 4016 | const iInt2 dims = measureWrapRange_Text( |
4017 | uiContent_FontId, width_Rect(rect) - 16 * gap_UI, range_String(&str)); | 4017 | uiContent_FontId, width_Rect(rect) - 16 * gap_UI, range_String(&str)).bounds.size; |
4018 | const int warnHeight = run->visBounds.size.y - domainHeight; | 4018 | const int warnHeight = run->visBounds.size.y - domainHeight; |
4019 | const int yOff = (lineHeight_Text(uiLabelLarge_FontId) - | 4019 | const int yOff = (lineHeight_Text(uiLabelLarge_FontId) - |
4020 | lineHeight_Text(uiContent_FontId)) / 2; | 4020 | lineHeight_Text(uiContent_FontId)) / 2; |
@@ -4224,7 +4224,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
4224 | appendFormat_String( | 4224 | appendFormat_String( |
4225 | &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); | 4225 | &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); |
4226 | } | 4226 | } |
4227 | const iInt2 size = measureRange_Text(metaFont, range_String(&text)); | 4227 | const iInt2 size = measureRange_Text(metaFont, range_String(&text)).bounds.size; |
4228 | fillRect_Paint( | 4228 | fillRect_Paint( |
4229 | &d->paint, | 4229 | &d->paint, |
4230 | (iRect){ add_I2(origin, addX_I2(topRight_Rect(run->bounds), -size.x - gap_UI)), | 4230 | (iRect){ add_I2(origin, addX_I2(topRight_Rect(run->bounds), -size.x - gap_UI)), |
@@ -4293,7 +4293,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
4293 | append_String(&str, collect_String(format_Date(&date, "%b %d"))); | 4293 | append_String(&str, collect_String(format_Date(&date, "%b %d"))); |
4294 | } | 4294 | } |
4295 | if (!isEmpty_String(&str)) { | 4295 | if (!isEmpty_String(&str)) { |
4296 | const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)); | 4296 | const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size; |
4297 | int tx = topRight_Rect(linkRect).x; | 4297 | int tx = topRight_Rect(linkRect).x; |
4298 | const char *msg = cstr_String(&str); | 4298 | const char *msg = cstr_String(&str); |
4299 | if (tx + textSize.x > right_Rect(d->widgetBounds)) { | 4299 | if (tx + textSize.x > right_Rect(d->widgetBounds)) { |
@@ -4301,7 +4301,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
4301 | fillRect_Paint(&d->paint, (iRect){ init_I2(tx, top_Rect(linkRect)), textSize }, | 4301 | fillRect_Paint(&d->paint, (iRect){ init_I2(tx, top_Rect(linkRect)), textSize }, |
4302 | uiBackground_ColorId); | 4302 | uiBackground_ColorId); |
4303 | msg += 4; /* skip the space and dash */ | 4303 | msg += 4; /* skip the space and dash */ |
4304 | tx += measure_Text(metaFont, " \u2014").x / 2; | 4304 | tx += measure_Text(metaFont, " \u2014").advance.x / 2; |
4305 | } | 4305 | } |
4306 | drawAlign_Text(metaFont, | 4306 | drawAlign_Text(metaFont, |
4307 | init_I2(tx, top_Rect(linkRect)), | 4307 | init_I2(tx, top_Rect(linkRect)), |
@@ -4359,8 +4359,8 @@ static void updateSideIconBuf_DocumentWidget_(const iDocumentWidget *d) { | |||
4359 | /* Determine the required size. */ | 4359 | /* Determine the required size. */ |
4360 | iInt2 bufSize = init1_I2(minBannerSize); | 4360 | iInt2 bufSize = init1_I2(minBannerSize); |
4361 | if (isHeadingVisible) { | 4361 | if (isHeadingVisible) { |
4362 | const iInt2 headingSize = advanceWrapRange_Text(heading3_FontId, avail, | 4362 | const iInt2 headingSize = measureWrapRange_Text(heading3_FontId, avail, |
4363 | currentHeading_DocumentWidget_(d)); | 4363 | currentHeading_DocumentWidget_(d)).bounds.size; |
4364 | if (headingSize.x > 0) { | 4364 | if (headingSize.x > 0) { |
4365 | bufSize.y += gap_Text + headingSize.y; | 4365 | bufSize.y += gap_Text + headingSize.y; |
4366 | bufSize.x = iMax(bufSize.x, headingSize.x); | 4366 | bufSize.x = iMax(bufSize.x, headingSize.x); |
@@ -4736,7 +4736,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4736 | if (prefs_App()->hoverLink && d->hoverLink) { | 4736 | if (prefs_App()->hoverLink && d->hoverLink) { |
4737 | const int font = uiLabel_FontId; | 4737 | const int font = uiLabel_FontId; |
4738 | const iRangecc linkUrl = range_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId)); | 4738 | const iRangecc linkUrl = range_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId)); |
4739 | const iInt2 size = measureRange_Text(font, linkUrl); | 4739 | const iInt2 size = measureRange_Text(font, linkUrl).bounds.size; |
4740 | const iRect linkRect = { addY_I2(bottomLeft_Rect(bounds), -size.y), | 4740 | const iRect linkRect = { addY_I2(bottomLeft_Rect(bounds), -size.y), |
4741 | addX_I2(size, 2 * gap_UI) }; | 4741 | addX_I2(size, 2 * gap_UI) }; |
4742 | fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId); | 4742 | fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId); |
@@ -4757,7 +4757,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4757 | const int wrap = docBounds.size.x - 2 * margin; | 4757 | const int wrap = docBounds.size.x - 2 * margin; |
4758 | iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos), | 4758 | iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos), |
4759 | -pos_SmoothScroll(&d->scrollY)); | 4759 | -pos_SmoothScroll(&d->scrollY)); |
4760 | const iInt2 textSize = advanceWrapRange_Text(altFont, wrap, meta->altText); | 4760 | const iInt2 textSize = measureWrapRange_Text(altFont, wrap, meta->altText).bounds.size; |
4761 | pos.y -= textSize.y + gap_UI; | 4761 | pos.y -= textSize.y + gap_UI; |
4762 | pos.y = iMax(pos.y, top_Rect(bounds)); | 4762 | pos.y = iMax(pos.y, top_Rect(bounds)); |
4763 | const iRect altRect = { pos, init_I2(docBounds.size.x, textSize.y) }; | 4763 | const iRect altRect = { pos, init_I2(docBounds.size.x, textSize.y) }; |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 8ea21cff..e65af417 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -211,7 +211,7 @@ static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | |||
211 | int extraHeight = (flags_Widget(as_Widget(d)) & extraPadding_WidgetFlag ? extraPaddingHeight_ : 0); | 211 | int extraHeight = (flags_Widget(as_Widget(d)) & extraPadding_WidgetFlag ? extraPaddingHeight_ : 0); |
212 | setFixedSize_Widget( | 212 | setFixedSize_Widget( |
213 | as_Widget(d), | 213 | as_Widget(d), |
214 | add_I2(measure_Text(d->font, cstr_Block(content)), | 214 | add_I2(measure_Text(d->font, cstr_Block(content)).bounds.size, |
215 | init_I2(6 * gap_UI + d->leftPadding + d->rightPadding, | 215 | init_I2(6 * gap_UI + d->leftPadding + d->rightPadding, |
216 | 2 * gap_UI + extraHeight))); | 216 | 2 * gap_UI + extraHeight))); |
217 | delete_Block(content); | 217 | delete_Block(content); |
@@ -747,7 +747,7 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const | |||
747 | 747 | ||
748 | static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { | 748 | static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { |
749 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); | 749 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); |
750 | int xPos = advanceN_Text(d->font, cstr_String(&line->text), d->cursor - line->offset).x; | 750 | int xPos = measureN_Text(d->font, cstr_String(&line->text), d->cursor - line->offset).advance.x; |
751 | size_t newCursor = iInvalidPos; | 751 | size_t newCursor = iInvalidPos; |
752 | const size_t numLines = size_Array(&d->lines); | 752 | const size_t numLines = size_Array(&d->lines); |
753 | if (dir < 0 && d->cursorLine > 0) { | 753 | if (dir < 0 && d->cursorLine > 0) { |
@@ -1400,14 +1400,14 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1400 | /* Draw the selected range. */ | 1400 | /* Draw the selected range. */ |
1401 | const iRanges mark = mark_InputWidget_(d); | 1401 | const iRanges mark = mark_InputWidget_(d); |
1402 | if (mark.start < lineRange.end && mark.end > lineRange.start) { | 1402 | if (mark.start < lineRange.end && mark.end > lineRange.start) { |
1403 | const int m1 = advanceN_Text(d->font, | 1403 | const int m1 = measureN_Text(d->font, |
1404 | cstr_String(&line->text), | 1404 | cstr_String(&line->text), |
1405 | iMax(lineRange.start, mark.start) - line->offset) | 1405 | iMax(lineRange.start, mark.start) - line->offset) |
1406 | .x; | 1406 | .advance.x; |
1407 | const int m2 = advanceN_Text(d->font, | 1407 | const int m2 = measureN_Text(d->font, |
1408 | cstr_String(&line->text), | 1408 | cstr_String(&line->text), |
1409 | iMin(lineRange.end, mark.end) - line->offset) | 1409 | iMin(lineRange.end, mark.end) - line->offset) |
1410 | .x; | 1410 | .advance.x; |
1411 | fillRect_Paint(&p, | 1411 | fillRect_Paint(&p, |
1412 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), | 1412 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), |
1413 | init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), | 1413 | init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), |
@@ -1437,7 +1437,8 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1437 | else { | 1437 | else { |
1438 | initCStr_String(&cur, " "); | 1438 | initCStr_String(&cur, " "); |
1439 | } | 1439 | } |
1440 | curSize = addX_I2(advance_Text(d->font, cstr_String(&cur)), iMin(2, gap_UI / 4)); | 1440 | curSize = addX_I2(measure_Text(d->font, cstr_String(&cur)).bounds.size, |
1441 | iMin(2, gap_UI / 4)); | ||
1441 | } | 1442 | } |
1442 | else { | 1443 | else { |
1443 | /* Bar cursor. */ | 1444 | /* Bar cursor. */ |
@@ -1447,7 +1448,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1447 | const iString * text = &curLine->text; | 1448 | const iString * text = &curLine->text; |
1448 | /* The `gap_UI` offsets below are a hack. They are used because for some reason the | 1449 | /* The `gap_UI` offsets below are a hack. They are used because for some reason the |
1449 | cursor rect and the glyph inside don't quite position like during `run_Text_()`. */ | 1450 | cursor rect and the glyph inside don't quite position like during `run_Text_()`. */ |
1450 | const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(text), d->cursor - curLine->offset); | 1451 | const iInt2 prefixSize = measureN_Text(d->font, cstr_String(text), d->cursor - curLine->offset).bounds.size; |
1451 | const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font) * d->cursorLine), | 1452 | const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font) * d->cursorLine), |
1452 | prefixSize.x + | 1453 | prefixSize.x + |
1453 | (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); | 1454 | (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); |
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index b68ab793..7d6bac67 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c | |||
@@ -376,9 +376,9 @@ static void sizeChanged_LabelWidget_(iLabelWidget *d) { | |||
376 | if (d->flags.wrap) { | 376 | if (d->flags.wrap) { |
377 | if (flags_Widget(w) & fixedHeight_WidgetFlag) { | 377 | if (flags_Widget(w) & fixedHeight_WidgetFlag) { |
378 | /* Calculate a new height based on the wrapping. */ | 378 | /* Calculate a new height based on the wrapping. */ |
379 | w->rect.size.y = advanceWrapRange_Text( | 379 | w->rect.size.y = measureWrapRange_Text( |
380 | d->font, innerBounds_Widget(w).size.x, range_String(&d->label)) | 380 | d->font, innerBounds_Widget(w).size.x, range_String(&d->label)) |
381 | .y; | 381 | .bounds.size.y; |
382 | } | 382 | } |
383 | } | 383 | } |
384 | } | 384 | } |
@@ -386,13 +386,13 @@ static void sizeChanged_LabelWidget_(iLabelWidget *d) { | |||
386 | iInt2 defaultSize_LabelWidget(const iLabelWidget *d) { | 386 | iInt2 defaultSize_LabelWidget(const iLabelWidget *d) { |
387 | const iWidget *w = constAs_Widget(d); | 387 | const iWidget *w = constAs_Widget(d); |
388 | const int64_t flags = flags_Widget(w); | 388 | const int64_t flags = flags_Widget(w); |
389 | iInt2 size = add_I2(measure_Text(d->font, cstr_String(&d->label)), | 389 | iInt2 size = add_I2(measure_Text(d->font, cstr_String(&d->label)).bounds.size, |
390 | add_I2(padding_LabelWidget_(d, 0), padding_LabelWidget_(d, 2))); | 390 | add_I2(padding_LabelWidget_(d, 0), padding_LabelWidget_(d, 2))); |
391 | if ((flags & drawKey_WidgetFlag) && d->key) { | 391 | if ((flags & drawKey_WidgetFlag) && d->key) { |
392 | iString str; | 392 | iString str; |
393 | init_String(&str); | 393 | init_String(&str); |
394 | keyStr_LabelWidget_(d, &str); | 394 | keyStr_LabelWidget_(d, &str); |
395 | size.x += 2 * gap_UI + measure_Text(uiShortcuts_FontId, cstr_String(&str)).x; | 395 | size.x += 2 * gap_UI + measure_Text(uiShortcuts_FontId, cstr_String(&str)).bounds.size.x; |
396 | deinit_String(&str); | 396 | deinit_String(&str); |
397 | } | 397 | } |
398 | size.x += iconPadding_LabelWidget_(d); | 398 | size.x += iconPadding_LabelWidget_(d); |
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index 254aad93..a0a507ca 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -121,7 +121,7 @@ static void draw_LookupItem_(iLookupItem *d, iPaint *p, iRect rect, const iListW | |||
121 | ? permanent_ColorId | (isPressing || isCursor ? uiTextPressed_ColorId | 121 | ? permanent_ColorId | (isPressing || isCursor ? uiTextPressed_ColorId |
122 | : uiTextFramelessHover_ColorId) | 122 | : uiTextFramelessHover_ColorId) |
123 | : d->fg; | 123 | : d->fg; |
124 | const iInt2 size = measureRange_Text(d->font, range_String(&d->text)); | 124 | const iInt2 size = measureRange_Text(d->font, range_String(&d->text)).bounds.size; |
125 | iInt2 pos = init_I2(left_Rect(rect) + 3 * gap_UI, mid_Rect(rect).y - size.y / 2); | 125 | iInt2 pos = init_I2(left_Rect(rect) + 3 * gap_UI, mid_Rect(rect).y - size.y / 2); |
126 | if (d->listItem.isSeparator) { | 126 | if (d->listItem.isSeparator) { |
127 | pos.y = bottom_Rect(rect) - lineHeight_Text(d->font); | 127 | pos.y = bottom_Rect(rect) - lineHeight_Text(d->font); |
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c index fa09b214..22924876 100644 --- a/src/ui/mediaui.c +++ b/src/ui/mediaui.c | |||
@@ -98,7 +98,7 @@ static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { | |||
98 | appendChar_String(&num, ':'); | 98 | appendChar_String(&num, ':'); |
99 | appendChar_String(&num, sevenSegmentDigit_ + (secs / 10) % 10); | 99 | appendChar_String(&num, sevenSegmentDigit_ + (secs / 10) % 10); |
100 | appendChar_String(&num, sevenSegmentDigit_ + (secs % 10)); | 100 | appendChar_String(&num, sevenSegmentDigit_ + (secs % 10)); |
101 | iInt2 size = advanceRange_Text(font, range_String(&num)); | 101 | iInt2 size = measureRange_Text(font, range_String(&num)).bounds.size; |
102 | if (align == right_Alignment) { | 102 | if (align == right_Alignment) { |
103 | pos.x -= size.x; | 103 | pos.x -= size.x; |
104 | } | 104 | } |
@@ -151,7 +151,7 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) { | |||
151 | drawHLine_Paint(p, init_I2(s1, yMid), part, bright); | 151 | drawHLine_Paint(p, init_I2(s1, yMid), part, bright); |
152 | drawHLine_Paint(p, init_I2(s1 + part, yMid), scrubMax - part, dim); | 152 | drawHLine_Paint(p, init_I2(s1 + part, yMid), scrubMax - part, dim); |
153 | const char *dot = "\u23fa"; | 153 | const char *dot = "\u23fa"; |
154 | const int dotWidth = advance_Text(uiLabel_FontId, dot).x; | 154 | const int dotWidth = measure_Text(uiLabel_FontId, dot).advance.x; |
155 | draw_Text(uiLabel_FontId, | 155 | draw_Text(uiLabel_FontId, |
156 | init_I2(s1 * (1.0f - normPos) + s2 * normPos - dotWidth / 2, | 156 | init_I2(s1 * (1.0f - normPos) + s2 * normPos - dotWidth / 2, |
157 | yMid - lineHeight_Text(uiLabel_FontId) / 2), | 157 | yMid - lineHeight_Text(uiLabel_FontId) / 2), |
@@ -223,7 +223,7 @@ static void drawSevenSegmentBytes_(iInt2 pos, int color, size_t numBytes) { | |||
223 | } | 223 | } |
224 | } | 224 | } |
225 | const int font = uiLabel_FontId; | 225 | const int font = uiLabel_FontId; |
226 | const iInt2 dims = advanceRange_Text(font, range_String(&digits)); | 226 | const iInt2 dims = measureRange_Text(font, range_String(&digits)).bounds.size; |
227 | drawRange_Text(font, addX_I2(pos, -dims.x), color, range_String(&digits)); | 227 | drawRange_Text(font, addX_I2(pos, -dims.x), color, range_String(&digits)); |
228 | deinit_String(&digits); | 228 | deinit_String(&digits); |
229 | } | 229 | } |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 4c2a9d64..767847ac 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -618,7 +618,7 @@ static void updateMetrics_SidebarWidget_(iSidebarWidget *d) { | |||
618 | iMaxi(d->maxButtonLabelWidth, | 618 | iMaxi(d->maxButtonLabelWidth, |
619 | 3 * gap_UI + measure_Text(font_LabelWidget(d->modeButtons[i]), | 619 | 3 * gap_UI + measure_Text(font_LabelWidget(d->modeButtons[i]), |
620 | translateCStr_Lang(normalModeLabels_[i])) | 620 | translateCStr_Lang(normalModeLabels_[i])) |
621 | .x); | 621 | .bounds.size.x); |
622 | } | 622 | } |
623 | } | 623 | } |
624 | updateItemHeight_SidebarWidget_(d); | 624 | updateItemHeight_SidebarWidget_(d); |
@@ -1672,8 +1672,8 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1672 | } | 1672 | } |
1673 | /* Select the layout based on how the title fits. */ | 1673 | /* Select the layout based on how the title fits. */ |
1674 | int metaFg = isPressing ? fg : uiSubheading_ColorId; | 1674 | int metaFg = isPressing ? fg : uiSubheading_ColorId; |
1675 | iInt2 titleSize = advanceRange_Text(titleFont, range_String(&d->label)); | 1675 | iInt2 titleSize = measureRange_Text(titleFont, range_String(&d->label)).bounds.size; |
1676 | const iInt2 metaSize = advanceRange_Text(uiLabel_FontId, range_String(&d->meta)); | 1676 | const iInt2 metaSize = measureRange_Text(uiLabel_FontId, range_String(&d->meta)).bounds.size; |
1677 | pos.x += iconPad; | 1677 | pos.x += iconPad; |
1678 | const int avail = width_Rect(itemRect) - iconPad - 3 * gap_UI; | 1678 | const int avail = width_Rect(itemRect) - iconPad - 3 * gap_UI; |
1679 | const int labelFg = isPressing ? fg : (isUnread ? uiTextStrong_ColorId : uiText_ColorId); | 1679 | const int labelFg = isPressing ? fg : (isUnread ? uiTextStrong_ColorId : uiText_ColorId); |
@@ -1682,7 +1682,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1682 | pos.y += (itemHeight - h2 - h2) / 2; | 1682 | pos.y += (itemHeight - h2 - h2) / 2; |
1683 | draw_Text( | 1683 | draw_Text( |
1684 | uiLabel_FontId, addY_I2(pos, h2 - h1 - gap_UI / 8), metaFg, "%s \u2014 ", cstr_String(&d->meta)); | 1684 | uiLabel_FontId, addY_I2(pos, h2 - h1 - gap_UI / 8), metaFg, "%s \u2014 ", cstr_String(&d->meta)); |
1685 | int skip = metaSize.x + advance_Text(uiLabel_FontId, " \u2014 ").x; | 1685 | int skip = metaSize.x + measure_Text(uiLabel_FontId, " \u2014 ").advance.x; |
1686 | iInt2 cur = addX_I2(pos, skip); | 1686 | iInt2 cur = addX_I2(pos, skip); |
1687 | const char *endPos; | 1687 | const char *endPos; |
1688 | tryAdvance_Text(titleFont, range_String(&d->label), avail - skip, &endPos); | 1688 | tryAdvance_Text(titleFont, range_String(&d->label), avail - skip, &endPos); |
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(); |
diff --git a/src/ui/text.h b/src/ui/text.h index ac9db405..0209e10a 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -148,13 +148,28 @@ void setContentFontSize_Text (float fontSizeFactor); /* affects all except `d | |||
148 | void resetFonts_Text (void); | 148 | void resetFonts_Text (void); |
149 | 149 | ||
150 | int lineHeight_Text (int fontId); | 150 | int lineHeight_Text (int fontId); |
151 | iInt2 measure_Text (int fontId, const char *text); | 151 | //iInt2 measure_Text (int fontId, const char *text); |
152 | iInt2 measureRange_Text (int fontId, iRangecc text); | 152 | //iInt2 measureRange_Text (int fontId, iRangecc text); |
153 | iRect visualBounds_Text (int fontId, iRangecc text); | 153 | iRect visualBounds_Text (int fontId, iRangecc text); |
154 | iInt2 advance_Text (int fontId, const char *text); | 154 | //iInt2 advance_Text (int fontId, const char *text); |
155 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ | 155 | //iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ |
156 | iInt2 advanceRange_Text (int fontId, iRangecc text); | 156 | //iInt2 advanceRange_Text (int fontId, iRangecc text); |
157 | iInt2 advanceWrapRange_Text (int fontId, int maxWidth, iRangecc text); | 157 | //iInt2 advanceWrapRange_Text (int fontId, int maxWidth, iRangecc text); |
158 | |||
159 | iDeclareType(TextMetrics) | ||
160 | |||
161 | struct Impl_TextMetrics { | ||
162 | iRect bounds; /* logical bounds: multiples of line height, horiz. advance */ | ||
163 | iInt2 advance; /* cursor offset */ | ||
164 | }; | ||
165 | |||
166 | iTextMetrics measureRange_Text (int fontId, iRangecc text); | ||
167 | iTextMetrics measureWrapRange_Text (int fontId, int maxWidth, iRangecc text); | ||
168 | iTextMetrics measureN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ | ||
169 | |||
170 | iLocalDef iTextMetrics measure_Text(int fontId, const char *text) { | ||
171 | return measureRange_Text(fontId, range_CStr(text)); | ||
172 | } | ||
158 | 173 | ||
159 | iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); | 174 | iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); |
160 | iInt2 tryAdvanceNoWrap_Text (int fontId, iRangecc text, int width, const char **endPos); | 175 | iInt2 tryAdvanceNoWrap_Text (int fontId, iRangecc text, int width, const char **endPos); |
@@ -196,15 +211,12 @@ struct Impl_WrapText { | |||
196 | enum iWrapTextMode mode; | 211 | enum iWrapTextMode mode; |
197 | iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int advance); | 212 | iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int advance); |
198 | void * context; | 213 | void * context; |
199 | /* output */ | ||
200 | iInt2 cursor_out; | ||
201 | /* internal */ | 214 | /* internal */ |
202 | iRangecc wrapRange_; | 215 | iRangecc wrapRange_; |
203 | }; | 216 | }; |
204 | 217 | ||
205 | iRect measure_WrapText (iWrapText *, int fontId); | 218 | iTextMetrics measure_WrapText (iWrapText *, int fontId); |
206 | iInt2 advance_WrapText (iWrapText *, int fontId); | 219 | void draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); |
207 | void draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); | ||
208 | 220 | ||
209 | SDL_Texture * glyphCache_Text (void); | 221 | SDL_Texture * glyphCache_Text (void); |
210 | 222 | ||
diff --git a/src/ui/translation.c b/src/ui/translation.c index 88edc48b..3ffa961b 100644 --- a/src/ui/translation.c +++ b/src/ui/translation.c | |||
@@ -80,7 +80,7 @@ void init_TranslationProgressWidget(iTranslationProgressWidget *d) { | |||
80 | spr->color = 0; | 80 | spr->color = 0; |
81 | init_String(&spr->text); | 81 | init_String(&spr->text); |
82 | appendChar_String(&spr->text, chars[i]); | 82 | appendChar_String(&spr->text, chars[i]); |
83 | spr->xoff = (width - advanceRange_Text(d->font, range_String(&spr->text)).x) / 2; | 83 | spr->xoff = (width - measureRange_Text(d->font, range_String(&spr->text)).advance.x) / 2; |
84 | spr->size = init_I2(width, lineHeight_Text(d->font)); | 84 | spr->size = init_I2(width, lineHeight_Text(d->font)); |
85 | x += width + gap; | 85 | x += width + gap; |
86 | } | 86 | } |
diff --git a/src/ui/util.c b/src/ui/util.c index e0b05a44..570bae85 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -1648,9 +1648,9 @@ iWidget *makePreferences_Widget(void) { | |||
1648 | size_t widestPos = iInvalidPos; | 1648 | size_t widestPos = iInvalidPos; |
1649 | iConstForEach(Array, i, uiLangs) { | 1649 | iConstForEach(Array, i, uiLangs) { |
1650 | const int width = | 1650 | const int width = |
1651 | advance_Text(uiLabel_FontId, | 1651 | measure_Text(uiLabel_FontId, |
1652 | translateCStr_Lang(((const iMenuItem *) i.value)->label)) | 1652 | translateCStr_Lang(((const iMenuItem *) i.value)->label)) |
1653 | .x; | 1653 | .advance.x; |
1654 | if (widestPos == iInvalidPos || width > widest) { | 1654 | if (widestPos == iInvalidPos || width > widest) { |
1655 | widest = width; | 1655 | widest = width; |
1656 | widestPos = index_ArrayConstIterator(&i); | 1656 | widestPos = index_ArrayConstIterator(&i); |