PDFSharp: Measuring height of long text with word wrap

cheesus picture cheesus · Mar 17, 2013 · Viewed 9.2k times · Source

PDFSharp supports automatic text wrapping when drawing long text portions:

textFormatter.DrawString(text, font, XBrushes.Black, new XRect(x, y, textAreaWidth, 1000), XStringFormats.TopLeft);

This will wrap the text if it is longer than textAreaWidth.

How can I get the height of the text that has just been drawn?

I tried it with gfx.MeasureString(), but there is no overload that supports specifying a maximal width. gfx.MeasureString() returns the size of the text without text wrapping.

Thanks for any hints.

Answer

Eric H. picture Eric H. · Oct 11, 2016

This extension of PdfSharp didn't quite work for me. Don't know why but i was keeping getting a bigger height than expected (almost the double of the neededHeight). So I decided to write an extension method the the XGraphics object where i can specify a maxWidth and internally calculate the soft line breaks. The code uses the default XGraphics.MeasureString(string, XFont) to the inlined text Width and Aggregates with the words from the text to calclulate the Line breaks. The code to calculate the soft Line breaks looks like this :

/// <summary>
/// Calculate the number of soft line breaks
/// </summary>
private static int GetSplittedLineCount(this XGraphics gfx, string content, XFont font, double maxWidth)
{
    //handy function for creating list of string
    Func<string, IList<string>> listFor = val => new List<string> { val };
    // string.IsNullOrEmpty is too long :p
    Func <string, bool> nOe = str => string.IsNullOrEmpty(str);
    // return a space for an empty string (sIe = Space if Empty)
    Func<string, string> sIe = str => nOe(str) ? " " : str;
    // check if we can fit a text in the maxWidth
    Func<string, string, bool> canFitText = (t1, t2) => gfx.MeasureString($"{(nOe(t1) ? "" : $"{t1} ")}{sIe(t2)}", font).Width <= maxWidth;

    Func<IList<string>, string, IList<string>> appendtoLast =
            (list, val) => list.Take(list.Count - 1)
                               .Concat(listFor($"{(nOe(list.Last()) ? "" : $"{list.Last()} ")}{sIe(val)}"))
                               .ToList();

    var splitted = content.Split(' ');

    var lines = splitted.Aggregate(listFor(""),
            (lfeed, next) => canFitText(lfeed.Last(), next) ? appendtoLast(lfeed, next) : lfeed.Concat(listFor(next)).ToList(),
            list => list.Count());

    return lines;
}

See the following Gist for the complete code : https://gist.github.com/erichillah/d198f4a1c9e8f7df0739b955b245512a