PDFsharp includes an XTextFormatter class that allows to draw text with line breaks.
A frequently requested feature: how to measure the height of the text before drawing it? I decided to add this measuring functionality. I found it was more difficult than I expected.
The unexpected problems were of a technical kind: the class XTextFormatter uses internal members of the XFont class. Therefore you cannot use my new class XTextFormatterEx with the NuGet packages from PDFsharp.
As of now (July 17, 2015) there is one way to use my XTextFormatterEx: download the source package of PDFsharp 1,32 and copy XTextFormatterEx.cs into the folder where XTextFormatter.cs is: PDFsharpcodePdfSharpPdfSharp.Drawing.Layout
In your Visual Studio solution you have to add references to the PDFsharp projects using Add => Existing Project. Do not forget to include the file XTextFormatterEx.cs in the project “PdfSharp”.
Sounds complicated? Sorry, it is complicated. I was hoping that it would be possible to include the class XTextFormatterEx.cs in the application project and use it with either the NuGet package, the pre-compiled DLLs, or the source package. But the references to internal members of the XFont class rule out two of these three ways. Maybe all three methods will work with future versions of PDFsharp.
And now for the good news: let’s talk about the new features.
A normal call to the XTextFormatter class looks like this:
tf.DrawString(text, font, XBrushes.Black, rect, XStringFormats.TopLeft);
If the rectangle is too small, the text will be truncated. But you cannot find out how much text was drawn and how much was truncated.
And now we look at the new methods:
XTextFormatterEx tf = new XTextFormatterEx(gfx); int lastCharIndex; double neededHeight; // Draw the text in a box with the optimal height // (magic: we know that one page is enough). XRect rect = new XRect(40, 100, 250, double.MaxValue); //tf.Alignment = ParagraphAlignment.Left; tf.PrepareDrawString(text, font, rect, out lastCharIndex, out neededHeight); rect = new XRect(40, 100, 250, neededHeight); gfx.DrawRectangle(XBrushes.SeaShell, rect); tf.DrawString(XBrushes.Black, XStringFormats.TopLeft);
When calling PrepareDrawString you specify the text and provide a rectangle with the available space (a height of double.MaxValue won’t be a good idea for real world applications).
“lastCharIndex” returns the index of the last character that can be drawn in the rectangle. This will be -1 if the complete text was drawn.
“neededHeight” will return the space that is needed for the text. This will be a negative value if no text can be drawn. If the rectangle is too small even for a single line, this will be the height of the first line; in this case “neededHeight” will be larger than the height of the original rectangle. If the text will be truncated, this will be the height needed for the truncated string.
To draw the text, you can and should use a new variation of DrawString that only takes a brush and an XStringFormat as parameters. This will draw the text that was most recently prepared. This is more efficient than using the DrawString method that also takes the text and the font as this version will layout the text again.
You can download the complete XTextFormatterEx class as a ZIP file (about 5 kiB in size).
Download XTextFormatterEx.zip
Please note that this post from 2015 applies to PDFsharp 1.32.
With PDFsharp 1.50 and later you need a slightly modified version, XTextFormatterEx2, that no longer needs “internals” from PDFsharp.
XTextFormatterEx2 for PDFsharp 1.50 and later.
Thank you so much for this!!!
Thank you! This makes the library so much more usable.
(also seems to compile under the Silverlight version of the library)
This is brilliant, and so handy. PDFSharp just gets better and better.
Thanks this is great. I recommend using reflection to access private fields in order to be able to use pdf sharp from the nuget packages.
Thanks for your help.
To make this class work in a custom project there are some minor changes that needs to be done to get around the problem with the internal declared properties used.
The code also uses two consts that are internal but if you look at what the represent they could easy and pretty safe moved to you you custom project.
The consts represent Linefeed and Carriage return and I guess that they won’t change. If they do I guess there will be bigger problems.
So what did I change:
Added these const to the class.
private const char CarriageReturn = ‘\x0D’; // ignored by lexer
private const char Linefeed = ‘\x0A’; // Line feed
Then there are some references to internal variables that need to be changed, some easier than other.
The easy ones just replace .width with .Width. Same goes for height. There are also some .x and .y that needs to be changed to .X and .Y to access public properties instead of local
The hard parts are these two:
this.cyAscent = lineSpace * font.cellAscent / font.cellSpace;
this.cyDescent = lineSpace * font.cellDescent / font.cellSpace;
They should look like this instead:
this.cyAscent = lineSpace * font.FontFamily.GetCellAscent(font.Style) / font.FontFamily.GetLineSpacing(font.Style);
this.cyDescent = lineSpace * font.FontFamily.GetCellAscent(font.Style) / font.FontFamily.GetLineSpacing(font.Style);
I hope this could be to any help.
You don’t have to make these changes if you use the latest source PDFsharp 1.50 and my latest revision XTextFormatterEx2. The current version of PDFsharp makes everything “public” that XTextFormatterEx2 needs. Not everything was “public” when I implemented the XTextFormatterEx class shown in this post.