2004-04-09
| Rate This Article: | Add This Article To: |
In a
Fitting Graphics on the Page
When printing graphics, the most important question is whether the output will fit on the page and, if not, what to do about it. If the graphic is too large for the page it will be clipped, something you almost never want. Rather, too-large output should be scaled so it does fit on the page. Let's see how that's done.
The Graphics object that you use to send output to the printer has a VisibleClipBounds property that represents, as a rectangle, the printable area of the page. By comparing the size of the graphic to be printed with the size of this rectangle, you can determine if the graphic is too large and by how much. Specifically, you can calculate the ratio of the graphic size to the page size. Here are the steps to follow:
- Create a GraphicsPath object containing the graphical objects to be printed.
- Use the GetBounds method to obtain the bounding rectangle for this GraphicsPath.
- Divide the width of the printable area by the width of the graphic obtained in step 2. This gives you a width ratio.
- Repeat step 3 for the heights of the two rectangles. This gives you a height ratio.
- The smaller of these two values is the factor by which the output must be scaled in order to fit on the page.
Listing 1 shows a method to perform these calculations. Because values for the printable area are not always precise, the code applies a .95 factor to ensure that a graphic that is almost exactly the same size as the printable area is not clipped. Your program would use the return value of this method as follows:
- If the return value is 1 or greater, the graphic is smaller than the printable area. You can print it as-is, taking up less than the full page, or enlarge it to fill the page as much as possible.
- If the return value is less than 1, the graphic is larger than the printable area. You'll have to shrink it to fit it on the page. The procedure's return value tells you how much the size must be reduced.
Listing 1. Determining the scaling factor for graphics output.
private float
GetScaleFactorForPrinting(Graphics g, GraphicsPath gp)
{
//Passed a Graphics object, and a GraphicsPath object,
//determines the magnification factor required to display all
//output in the Graphics object's visible area.
RectangleF GraphicSize = gp.GetBounds();
RectangleF PrintableSize = g.VisibleClipBounds;
float WidthRatio = PrintableSize.Width / GraphicSize.Width;
float HeightRatio = PrintableSize.Height / GraphicSize.Height;
//Return the larger ratio. Because a precise ratio
//sometimes clips output at the edges, a .95 factor
//is applied.
if (WidthRatio < HeightRatio)
return 0.95f * WidthRatio;
else
return 0.95f * HeightRatio;
}
How do you scale graphic output? The most obvious solution is to set the Graphics object's PageScale property, but there's a problem. This will certainly change output size, but it also affects the thickness of lines that are drawn as part of the output which may cause thin lines to disappear. A better approach is to use a scale transformation to modify the size of the GraphicsPath object itself before it is printed. A transformation is defined in a matrix, and a scale transformation is created using the Matrix.Scale() method. The syntax is:
Matrix m = new Matrix(); m.Scale(xfactor, yfactor)
The arguments xfactor and yfactor are the values by which to transform in the X and Y directions. These values will be the same for the current program, and will be the factor obtained from the method in Listing 1. The required steps are as follows:
- Calculate the required scaling factor using the method in Listing 1.
- Create a Matrix object that defines a scale transformation using this scaling factor.
- Create a duplicate of the GraphicsPath to be displayed (to avoid changing the original).
- Apply the transformation matrix to the duplicate GraphicsPath object.
- Print the duplicate GraphicsPath.
Listing 2 shows the code to scale graphics output to fill the page. The code assumes that gp refers to a global GraphicsPath object that contains the graphics to be printed.
Listing 2. Scaling graphics output to fit the page.
private void pDoc_PrintPage(object sender, PrintPageEventArgs e)
{
float scaleFactor = GetScaleFactorForPrinting(e.Graphics, gp);
Matrix m = new Matrix();
m.Scale(scaleFactor, scaleFactor);
GraphicsPath gpNew = (GraphicsPath) gp.Clone();
gpNew.Transform(m);
e.Graphics.DrawPath(Pens.Black, gpNew);
}
Fitting Text on the Page
The scaling technique that I just presented is not appropriate for text-only documents because the user expects text to be printed with the selected font size, not a size that may have been scaled up or down to fit on the page. There are several parts to fitting text on a page. One has to do with line length. If a line of text is too long to fit between the page margins, the text should be wrapped onto additional lines as needed. This is fairly easy to accomplish by defining a bounding rectangle for the text that is the same width as the space between the page margins. The DrawString method will automatically wrap text to fit within this rectangle.
You also need to place the proper number of lines on a page, filling the page between the top and bottom margins. If there is more text than will fir on one page, you must continue printing additional pages until all of the text has been printed. You can determine the number of lines of text that will fit on a page by dividing the height of the page by the height of one line of text, obtained from the Font.GetHeight() method. Things get more complicated however when lines of text are wrapped, which results in one "line" of text (as delineated by carriage returns) taking up two or more lines on the page. This is taken into account by the Graphics.MeasureString() method, which determines the number of lines that a given string will require to print when constrained by a given bounding rectangle.
But wait—we're not done yet. Suppose that printing has progressed to a position near the bottom of the page, and the next line of text must be wrapped over several output lines. What if there is not sufficient space remaining on the page to print all of the required lines? The program should print the part that will fit, and then print the remainder of the text, called overflow text, on the following page. The Graphics.MeasureString() method comes into play here also, because it will provide information on what portion of a string will fit into a given output rectangle, and hence will let you identify the part of the string that does not fit and must be saved for printing on the next page.
- Initialize a Y-position variable to point at the top of the page (within the margins).
- Check for overflow text from the previous page. If there is overflow text, go to step 4. If not continue with step 3.
- Input a line of text from the source (a line is defined as a section of text ending with a carriage return). If there is no more text, end printing.
- Print the text in a bounding rectangle whose top is at the current Y-position and whose width is equal to the space between the left and right margins.
- Keep track of the number of output lines required in step 4 and update the Y-position variable as needed.
- If the entire line of text would not fit on the page, save the overflow text, start a new page, and return to step 1.
- If the maximum number of lines has been printed on this page, start a new page and return to step 1.
While the .Net Framework classes greatly simplify the task of creating text output, it still gets a bit involved. Rather than including code in this article I have provided a
Discuss this article in DevSource's forum!
|
![]() |
|


