[C# Helper]
Index Books FAQ Contact About Rod
[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Flow words around obstacles for document layout in C#

[Flow words around obstacles for document layout in C#]

The example Flow blocks around obstacles for document layout in C# implements a document layout algorithm that flows blocks around obstacles. Moving from there to flowing words around obstacles is relatively simple. The following sections describe the two main places where I modified the previous algorithm to provide document layout for words instead of blocks.

The TextBlock Class

The previous example uses the Block class to represent a rectangle that should be flowed around obstacles. To flow words, this example derives a new TextBlock class from the Block class. The following code shows the new class.

public class TextBlock : Block { public Font Font; public String Text; public TextBlock(string text, Font font, Graphics gr) { Font = font; Text = text; SizeF size = gr.MeasureString(Text, font); Bounds = new RectangleF(new PointF(), size); FontInfo font_info = new FontInfo(gr, font); TopHeight = font_info.AscentPixels; } // Draw the text block. public override void Draw(Graphics gr) { if (Bounds.X < 0) return; // Draw the box (optional). base.Draw(gr); // Draw the text. gr.DrawString(Text, Font, Brushes.Black, Bounds); } }

This class inherits from the Block class. It provides two new fields: Font and Text. You can probably guess that those are the font that the object should use to draw its text and the text that it should draw.

The constructor takes as parameters the block's text, its font, and a Graphics object that it can use to measure the text. The code saves the font and text, and then calls the Graphics object's MeasureString method to measure the text as drawn with the given font. It uses the text's size to define the block's bounds.

Next, the program must figure out where the text's baseline should be. Recall that the previous example's document layout method aligned blocks on their baselines. For the blocks, that was unnecessary. For text, that's important so different words line up properly.

The distance between the top of a piece of text and its baseline is called its ascent. Unfortunately, neither the Font not Graphics objects give you the ascent directly. Fortunately, you can figure it out. For an explanation of how to do that and to see a picture showing how the ascent fits on the text, see my earlier post Get font metrics in C#. That method defines a FontInfo class that gives the font's ascent. The program simply creates a FontInfo object for the font and saves its AscentPixels property in the TextBlock object's TopHeight field.

The final piece of the TextBlock class is its Draw method, which overrides the same method in the Block class. The new method first calls the Draw class's version of the method to draw a green box with a red baseline where the text will be positioned as shown in the picture at the top of the post. In the final program, you should comment out that call so the program only draws the text as in the following picture.

[Flow words around obstacles for document layout in C#]

The method then draws the object's text inside its bounds. Because the constructor defined the bounds so they would fit the text, the text fills the bounds completely. As a side effect, the text is centered both vertically and horizontally within the bounds.

I also made two minor changes to the Block class to make the derived TextBlock class work. First, I have the Block class a new parameterless constructor so the derived class didn't need to pass the base constructor a bounds rectangle that it would not use.

Second, I changed the Draw method so it is virtual (so the TextBlock class can override it) and so it takes the parameters needed by the TextBlock class.

The Main Program

Instead of creating random blocks, the new example creates words that use random fonts. The following code shows how the program initializes its word data.

// Blocks to flow around obstacles. private List<Block> Blocks; // The text to draw. private string Words = "Lorem ipsum dolor sit amet, ..."; // Define some blocks. private void Form1_Load(object sender, EventArgs e) { Obstacles = new List(); Obstacles.Add(new RectangleF(0, 0, 64, 64)); Obstacles.Add(new RectangleF(200, 50, 50, 100)); Obstacles.Add(new RectangleF(50, 150, 100, 70)); Obstacles.Add(new RectangleF(300, 200, 100, 64)); Blocks = new List<Block>(); Graphics gr = CreateGraphics(); foreach (string word in Words.Split(' ')) Blocks.Add(new TextBlock(word, RandomFont(), gr)); }

Notice that the variable Blocks is still a list of Block objects. The TextBlock class is derived from Block, so a TextBlock is a kind of block. That means the program can place TextBlock objects inside a List<Block>.

The string Words contains a long set of Lorem Ipsum words. For more information on this interesting test string, see the site Lorem Ipsum.

The form's Load event handler creates obstacles as before. It then breaks the Words string into words and uses each to create a TextBlock. The only new thing here is that the program calls the RandomFont method to generate each word's font.

The RandomFont method simply picks a random font name, size, and style (bold, italic, regular, strikeout, or underline). Download the example to see the details.

Conclusion

The rest of the example is about the same as before. The Paint event handler calls the FlowBlocks method to arrange the blocks as before. The blocks are actually TextBlock objects instead of Block objects, but FlowBlocks doesn't care.

The result is almost what we need to perform document layout with drop caps. The result shown here looks a bit strange because the obstacles are scattered around the drawing area rather than all places on the left edge. The words are also relatively large, so there are some fairly big gaps between words.

As usual, feel free to download the example and experiment with it. In my final post in this series, I'll show how to handle paragraphs and pull the initial drop caps off of the first word in each paragraph. The result should look a lot more appealing than the one shown here.

Download the example to experiment with it and to see additional details.

© 2009-2023 Rocky Mountain Computer Consulting, Inc. All rights reserved.