@BrokenGlass's answer is great, but depending on the characteristics of your application, you may find you get better performance with a binary search. If the majority of your strings fit in the available width, or generally only need to be trimmed by a character or two, then a linear search is best. However, if you have a lot of long strings that will be severely truncated, the following binary search will work well.
Note that both availableWidth and fontSize are specified in device-independent units (1/96ths of an inch). Also, use the TextFormattingMode that matches the way you draw your text.
public static string TruncateTextToFitAvailableWidth(
string text,
double availableWidth,
string fontName,
double fontSize)
{
if(availableWidth <= 0)
return string.Empty;
Typeface typeface = new Typeface(fontName);
int foundCharIndex = BinarySearch(
text.Length,
availableWidth,
predicate: (idxValue1, value2) =>
{
FormattedText ft = new FormattedText(
text.Substring(0, idxValue1 + 1),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
fontSize,
Brushes.Black,
numberSubstitution: null,
textFormattingMode: TextFormattingMode.Ideal);
return ft.WidthIncludingTrailingWhitespace.CompareTo(value2);
});
int numChars = (foundCharIndex < 0) ? ~foundCharIndex : foundCharIndex + 1;
return text.Substring(0, numChars);
}
/**
<summary>
See <see cref="T:System.Array.BinarySearch"/>. This implementation is exactly the same,
except that it is not bound to any specific type of collection. The behavior of the
supplied predicate should match that of the T.Compare method (for example,
<see cref="T:System.String.Compare"/>).
</summary>
*/
public static int BinarySearch<T>(
int length,
T value,
Func<int, T, int> predicate) // idxValue1, value2, compareResult
{
return BinarySearch(0, length, value, predicate);
}
public static int BinarySearch<T>(
int index,
int length,
T value,
Func<int, T, int> predicate)
{
int lo = index;
int hi = (index + length) - 1;
while(lo <= hi)
{
int mid = lo + ((hi - lo) / 2);
int compareResult = predicate(mid, value);
if(compareResult == 0)
return mid;
else if(compareResult < 0)
lo = mid + 1;
else
hi = mid - 1;
}
return ~lo;
}