GDI+ in C# is fun!
While on the topic of doing some printing I ran into the following situation:
- Graphics.DrawString allows us to render a complete paragraph inside of an arbitrarily defined bounding box, complete with sensible linebreaks
- Graphics.MeasureString even allows us to get the height needed for a text given a specified width (or vice versa) with the aforementioned perks of nice linebreaks
- A paragraph of text can contain words that have a different FontStyle than the words surrounding it (think of making a word italic for emphasis)
- There is no way to get proper layout information as in "the text you rendered did not fill the last line completely, here is a point where you can start rendering the next text that might have a different FontStyle and continue over the proper beginning of the next line in this given layout rectangle"
I am trying to render text (think multiple lines of the stuff) with interspersed FontStyle changes (not the font or size, just the style). I need a way to determine where each block of individually styled text starts and begins and handle graceful line transitions.
Graphics.MeasureCharacterRanges to the rescue? I fear not.
I found an example doing roughly what I want to do. They are trying to render text with each word a different color. And from the answer it seems they are succeeding. But.
I have adapted the code and came up with the following (I made this in LINQPad for ease of fiddeling)
void Main()
{
string lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.";
Image img = new Bitmap(400, 220); //Resulting graphics
Font font = new Font("Arial", 12); //Font to use
Brush brush = Brushes.Black; //Default braush
Pen pen = Pens.Black; //Default pen
PointF point = new PointF(0, 0); //start point for area to render to
RectangleF rectangle = new RectangleF(point, img.Size); //area to render to
StringFormat stringFormat = StringFormat.GenericTypographic;
Brush[] RegionBrushes = { Brushes.LightGray, Brushes.LightBlue }; //for display purposes
Pen[] RegionPens = { Pens.Red, Pens.Blue }; //for display purposes
using(Graphics g = Graphics.FromImage(img))
{
g.Clear(Color.LightGreen); //give contrast against white background
g.DrawString(lorem, font, Brushes.Gray, rectangle, stringFormat); //the whole string, with proper linebreaks for reference
CharacterRange[] characterRanges = { new CharacterRange(42, 20), new CharacterRange(68, 38) }; //the desired portions
stringFormat.SetMeasurableCharacterRanges(characterRanges); //set these portions as important
Region[] stringRegions = g.MeasureCharacterRanges(lorem, font, rectangle, stringFormat); //get the regions where the desired portions are
for(int i = 0; i < characterRanges.Length; i++) //for each desired portion
{
g.FillRegion(RegionBrushes[i], stringRegions[i]); //fill the corresponding region with an individual color (as defined above, to ilustrate that the measuring does in fact work)
RectangleF bounds = stringRegions[i].GetBounds(g); //the bounds of the region, I probably go wrong here
string word = lorem.Substring(characterRanges[i].First, characterRanges[i].Length); //the portion of the overall string to draw
g.DrawRectangles(RegionPens[i],new RectangleF[]{ bounds}); //draw the outline of the bounds
g.DrawString(word, font, brush, bounds, stringFormat); //draw the actual portion of the string, only one of the strings gets drawn and to the wrong place
}
}
img.Dump(); //LINQPad-specific, shows the image in the result window
}
in principle the code works sort of, but not satisfactory.
- depending on the actual character ranges chosen, either the first or second range get drawn as text
- while
g.FillRegion()
correctly fills the region where the text is supposed to be, the text is nowhere near this region
I don't have any idea why the 1st is happening. The 2nd naturally comes from the fact, that DrawString
is given a rectangle that is derived from the bounds of the region. While the bounds of the second range (with the current values from the example) are also the outline of the region, they are not for the first range of characters spanning multiple lines and ending before they begin.
Is there a way to get useful position information for use in DrawString
?