#pragma region CPL License /* Nuclex Unreal Module Copyright (C) 2014-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #include "Text/FontHelper.h" #include #include // UFont has some presents for us. // This could be used in place of the font measure service // //ENGINE_API float GetMaxCharHeight() const; //ENGINE_API void GetStringHeightAndWidth(const FString& InString, int32& Height, int32& Width) const; //ENGINE_API void GetStringHeightAndWidth(const TCHAR *Text, int32& Height, int32& Width) const; // --------------------------------------------------------------------------------------------- // FVector2D UFontHelper::MeasureString( UTextBlock *textBlock, const FString &text ) { TSharedRef 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 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) bool isWhitespace; if(index == textLength) { isWhitespace = true; // Act as if the text ended with a whitespace } else { TCHAR character = text[index]; isWhitespace = ( (character == TEXT(' ')) || (character == TEXT('\t')) || (character == TEXT('\r')) || (character == TEXT('\n')) ); } // 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 point if(potentialLineSize.X < wrapWidth) { lastGoodWrapIndex = index; lastGoodWrapWidth = potentialLineSize.X; } else { // Line is now larger thanthe desired length, use previous wrap point ++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; foundLineStartIndex = false; } } else if(!isWhitespace) { foundLineStartIndex = true; // The first non-whitespace character marks the line start lineStartIndex = index; } } // for // In case the string fit in a single line, we won't have updated size.X yet. // Also if the last line happens to be the longest. size.X = FMath::Max(size.X, lastGoodWrapWidth); size.Y = ( static_cast(fontMeasureService->GetMaxCharacterHeight(textBlock->Font)) * lineCount ); } return size; } // --------------------------------------------------------------------------------------------- //