I’m aware
- In this case I have the UMG widget and its [FONT=courier new]UFont object / [FONT=courier new]FSlateFontInfo structure and the canvas size is an absolutely static, unscaled 280 x 120 pixels.
As I discovered in the sources, [FONT=courier new]FSlateFontMeasure::MeasureStringInternal() does 100% of everything I need. It’s also private in C++ and not exposed to Blueprint.
Because I don’t want to run on modified engine sources, I’ve now written my own method and put it in a Blueprint function library. If anyone runs into the same issue, here’s a code snippet that calculates the unscaled text size:
[FONT=courier new]// --------------------------------------------------------------------------------------------- //
FVector2D UFontHelper::MeasureString(
UTextBlock *textBlock,
const FString &text
) {
TSharedRef<FSlateFontMeasure> fontMeasureService = (
FSlateApplication::Get().GetRenderer()->GetFontMeasureService()
);
return fontMeasureService->Measure(text, textBlock->Font);
}
// --------------------------------------------------------------------------------------------- //
FVector2D UFontHelper::MeasureWrappedString(
UTextBlock *textBlock,
const FString &text,
float wrapWidth
) {
FVector2D size(0.0f, 0.0f);
// Everything we do here duplicates what FSlateFontMeasure::MeasureStringInternal()
// already does. Sadly, the existing method is private and there's no exposed method
// that happens to call it in a way we can work with...
// Scan the entire string, keeping track of where lines begin and checking every time
// a whitespace is encountered how long the line would be when wrapped there.
// We could do this in a more fancy way with binary search and by guessing lengths,
// but it's typically done just one when new text is displayed, so this is good enough.
{
TSharedRef<FSlateFontMeasure> fontMeasureService = (
FSlateApplication::Get().GetRenderer()->GetFontMeasureService()
);
int32 lineCount = 1;
bool lastWasWhitespace = true;
bool foundLineStartIndex = false;
int32 lineStartIndex = 0;
int32 lastGoodWrapIndex = -1;
float lastGoodWrapWidth = 0.0f;
// Scanning loop, steps through the string character by character
int32 textLength = text.Len();
for(int32 index = 0; index < textLength; ++index) {
// Check if the current character is a whitespace character (and thus, the line
// can be broken at this point)
TCHAR character = text[index];
bool isWhitespace = (
(character == TEXT(' ')) || (character == TEXT(' ')) || (character == TEXT('
'))
);
// If we have a line start index (meaning there was a non-whitespace character),
// do the line break checking
if(foundLineStartIndex) {
// Don't re-check line breaks on consecutive whitespaces
if(isWhitespace && lastWasWhitespace) {
continue;
}
lastWasWhitespace = isWhitespace;
// If this is no whitespace, we can't wrap here, so continue scanning
if(!isWhitespace) {
continue;
}
// Measure the line up until the whitespace we just encountered
FVector2D potentialLineSize = fontMeasureService->Measure(
text, lineStartIndex, index - 1, textBlock->Font, false
);
// If it still fits in the line, remember this as the most recent good wrap ppoint
if(potentialLineSize.X < wrapWidth) {
lastGoodWrapIndex = index;
lastGoodWrapWidth = potentialLineSize.X;
} else {
++lineCount;
if(lastGoodWrapIndex == -1) { // First whitespace and it's already too long...
size.X = FMath::Max(size.X, potentialLineSize.X);
} else { // Phew... we have a good wrapping position remembered
size.X = FMath::Max(size.X, lastGoodWrapWidth);
}
// Reset all trackers to scan for a new line from here
lastGoodWrapIndex = -1;
lineStartIndex = index;
foundLineStartIndex = false;
}
} else if(!isWhitespace) {
foundLineStartIndex = true; // The first non-whitespace character marks the line start
lineStartIndex = index;
}
} // for
// If there are characters remaining on the last line, measure them, too
// (we also know it doesn't end in a space/newline because otherwise this
// property would have a value of false, thus this final check is really basic)
if(foundLineStartIndex) {
FVector2D finalLineSize = fontMeasureService->Measure(
text, lineStartIndex, textLength - 1, textBlock->Font, false
);
size.X = FMath::Max(size.X, finalLineSize.X);
}
size.Y = (
static_cast<float>(fontMeasureService->GetMaxCharacterHeight(textBlock->Font)) * lineCount
);
}
return size;
}
// --------------------------------------------------------------------------------------------- //