How to Find the Actual Printable Area? (Printdocument)

How to find the actual printable area? (PrintDocument)

Your question lacks a little clarity as to what the "best" rectangle is. I'm going to assume you mean the largest rectangle that will be 100% visible when printed.

So lets start by making sure we understand what the print document graphics object "origins" are and how the OriginAtMargins property affects this origin.

OriginAtMargins - Gets or sets a value indicating whether the position
of a graphics object associated with a page is located just inside the
user-specified margins or at the top-left corner of the printable area
of the page.

- PrintDocument Class Definition on MSDN

So with OriginAtMargins set to false (default) the graphics object will be adjusted to the PrintableArea rectangle (about 5/32 from each page edge for my laser printer, old laser printers may be more, new inkjets may print right to the edge, software PDF printers will print right to the edge). So 0,0 in my graphics object is actually 16,16 on the physical page of my laser printer (your printer may be different).

With the default 1 inch page margins and OriginAtMargins set to true, the graphics object will be adjusted to the 100,100,650,1100 rectangle for a normal portrait letter page. This is one inch inside each physical page edge. So 0,0 in your graphics object is actually 100,100 on the physical page.

Margins are also known as "soft margins" as they are defined in software and not affected by the physical printing device. This means they will be applied to the current page size in software and reflect the actual page dimension portrait or landscape.

PrintableArea is also known as "hard margins" which reflect the physical limitations of your printing device. This will vary from printer to printer, from manufacturer to manufacturer. Because these are hardware measurements, they do not rotate when you set the page to landscape/portrait. The physical limitations won't change on the printer regardless of software print settings, so we need to make sure we apply them on the correct axis depending on our software settings for the print document (orientation).

So following the rough model of the sample code you posted, here's a PrintDocument.PrintPage event handler that will draw a rectangle as large as possible while still being visible (with the default PrintDocument.OriginsAtMargins being false). If you set PrintDocument.OriginsAtMargins to true it will draw a rectangle as large as possible while still being visible inside the configured soft margins (defaults to 1" from page edges).

PrintAction printAction = PrintAction.PrintToFile;

private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
// Save our print action so we know if we are printing
// a preview or a real document.
printAction = e.PrintAction;

// Set some preferences, our method should print a box with any
// combination of these properties being true/false.
printDocument.OriginAtMargins = false; //true = soft margins, false = hard margins
printDocument.DefaultPageSettings.Landscape = false;
}

private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;

// If you set printDocumet.OriginAtMargins to 'false' this event
// will print the largest rectangle your printer is physically
// capable of. This is often 1/8" - 1/4" from each page edge.
// ----------
// If you set printDocument.OriginAtMargins to 'false' this event
// will print the largest rectangle permitted by the currently
// configured page margins. By default the page margins are
// usually 1" from each page edge but can be configured by the end
// user or overridden in your code.
// (ex: printDocument.DefaultPageSettings.Margins)

// Grab a copy of our "soft margins" (configured printer settings)
// Defaults to 1 inch margins, but could be configured otherwise by
// the end user. You can also specify some default page margins in
// your printDocument.DefaultPageSetting properties.
RectangleF marginBounds = e.MarginBounds;

// Grab a copy of our "hard margins" (printer's capabilities)
// This varies between printer models. Software printers like
// CutePDF will have no "physical limitations" and so will return
// the full page size 850,1100 for a letter page size.
RectangleF printableArea = e.PageSettings.PrintableArea;

// If we are print to a print preview control, the origin won't have
// been automatically adjusted for the printer's physical limitations.
// So let's adjust the origin for preview to reflect the printer's
// hard margins.
if (printAction == PrintAction.PrintToPreview)
g.TranslateTransform(printableArea.X, printableArea.Y);

// Are we using soft margins or hard margins? Lets grab the correct
// width/height from either the soft/hard margin rectangles. The
// hard margins are usually a little wider than the soft margins.
// ----------
// Note: Margins are automatically applied to the rotated page size
// when the page is set to landscape, but physical hard margins are
// not (the printer is not physically rotating any mechanics inside,
// the paper still travels through the printer the same way. So we
// rotate in software for landscape)
int availableWidth = (int)Math.Floor(printDocument.OriginAtMargins
? marginBounds.Width
: (e.PageSettings.Landscape
? printableArea.Height
: printableArea.Width));
int availableHeight = (int)Math.Floor(printDocument.OriginAtMargins
? marginBounds.Height
: (e.PageSettings.Landscape
? printableArea.Width
: printableArea.Height));

// Draw our rectangle which will either be the soft margin rectangle
// or the hard margin (printer capabilities) rectangle.
// ----------
// Note: we adjust the width and height minus one as it is a zero,
// zero based co-ordinates system. This will put the rectangle just
// inside the available width and height.
g.DrawRectangle(Pens.Red, 0, 0, availableWidth - 1, availableHeight - 1);
}

The two lines that determine available width and available height are what I think you were looking for in your question. Those two lines take into account whether you want soft margins or hard margins and whether the print document is configured for landscape or portrait.

I used Math.Floor() for the easy way out to just drop anything past the decimal (ex: 817.96 -> 817) just to make sure the available width and height was just inside the available dimensions. I'm "failing safe" here, if you wanted to you could maintain float based co-ordinates (instead of int), just be careful to watch for rounding errors that will result in the clipped graphics (if it rounds 817.96 up to 818 and then the printer driver decides that's no longer visible).

I tested this procedure in both portrait and landscape with both hard margins and soft margins on a Dell 3115CN, a Samsung SCX-4x28 and CutePDF software printer. If this didn't adequately address your question, consider revising your question to clarify "magic rectangle" and "best rectangle".


EDIT: Notes About "Soft Margins"

Soft margins are applied in software and do not take into consideration the hardware limitations of the printer. This is intentional and by design. You can set the soft margins outside the printable area if you want and the output may be clipped by your printer's driver. If this is undesirable for your application, you need to adjust the margins in your program code. Either you can prevent the user from selecting margins outside the printable area (or warn them if they do) or you can enforce some min/max conditions in your code when you actually start printing (drawing) the document.

Example Case: If you set the page margins to 0,0,0,0 in Microsoft Word 2007 a warning dialog pops up that reads "One or more margins are set outside the printable area of the page. Choose the Fix button to increase the appropriate margins." If you click fix, Word will simply copy the hard margins into the soft margins, so the dialog now shows 0.16" for all margins (my laser printer's capabilities).

This is expected behavior. It is not a bug/problem with Microsoft Word if the printed page is clipped because the user ignored this warning and used 0,0,0,0 page margins. This is the same in your application. You need to enforce the limits for whatever if appropriate in your use case. Either with a warning dialog, or you can force the limit more strongly in code (don't offer a choice to the user).


Alternative Strategy

Alright so maybe you don't want to just get the hard margins, but rather get the soft margins and then enforce that the soft margins remain inside the printable area when printing. Let's develop another strategy here.

In this example I will use the origins at margins, and allow the user to select any margin they want, but I'm going to enforce in code that the selected margin not be outside the printable area. If the selected margins are outside the printable area, I'm simply going to adjust them to be inside the printable area.

PrintAction printAction = PrintAction.PrintToFile;

private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
// Save our print action so we know if we are printing
// a preview or a real document.
printAction = e.PrintAction;

// We ALWAYS want true here, as we will implement the
// margin limitations later in code.
printDocument.OriginAtMargins = true;

// Set some preferences, our method should print a box with any
// combination of these properties being true/false.
printDocument.DefaultPageSettings.Landscape = false;
printDocument.DefaultPageSettings.Margins.Top = 100;
printDocument.DefaultPageSettings.Margins.Left = 0;
printDocument.DefaultPageSettings.Margins.Right = 50;
printDocument.DefaultPageSettings.Margins.Bottom = 0;
}

private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;

// If you set printDocumet.OriginAtMargins to 'false' this event
// will print the largest rectangle your printer is physically
// capable of. This is often 1/8" - 1/4" from each page edge.
// ----------
// If you set printDocument.OriginAtMargins to 'false' this event
// will print the largest rectangle permitted by the currently
// configured page margins. By default the page margins are
// usually 1" from each page edge but can be configured by the end
// user or overridden in your code.
// (ex: printDocument.DefaultPageSettings.Margins)

// Grab a copy of our "hard margins" (printer's capabilities)
// This varies between printer models. Software printers like
// CutePDF will have no "physical limitations" and so will return
// the full page size 850,1100 for a letter page size.
RectangleF printableArea = e.PageSettings.PrintableArea;
RectangleF realPrintableArea = new RectangleF(
(e.PageSettings.Landscape ? printableArea.Y : printableArea.X),
(e.PageSettings.Landscape ? printableArea.X : printableArea.Y),
(e.PageSettings.Landscape ? printableArea.Height : printableArea.Width),
(e.PageSettings.Landscape ? printableArea.Width : printableArea.Height)
);

// If we are printing to a print preview control, the origin won't have
// been automatically adjusted for the printer's physical limitations.
// So let's adjust the origin for preview to reflect the printer's
// hard margins.
// ----------
// Otherwise if we really are printing, just use the soft margins.
g.TranslateTransform(
((printAction == PrintAction.PrintToPreview)
? realPrintableArea.X : 0) - e.MarginBounds.X,
((printAction == PrintAction.PrintToPreview)
? realPrintableArea.Y : 0) - e.MarginBounds.Y
);

// Draw the printable area rectangle in PURPLE
Rectangle printedPrintableArea = Rectangle.Truncate(realPrintableArea);
printedPrintableArea.Width--;
printedPrintableArea.Height--;
g.DrawRectangle(Pens.Purple, printedPrintableArea);

// Grab a copy of our "soft margins" (configured printer settings)
// Defaults to 1 inch margins, but could be configured otherwise by
// the end user. You can also specify some default page margins in
// your printDocument.DefaultPageSetting properties.
RectangleF marginBounds = e.MarginBounds;

// This intersects the desired margins with the printable area rectangle.
// If the margins go outside the printable area on any edge, it will be
// brought in to the appropriate printable area.
marginBounds.Intersect(realPrintableArea);

// Draw the margin rectangle in RED
Rectangle printedMarginArea = Rectangle.Truncate(marginBounds);
printedMarginArea.Width--;
printedMarginArea.Height--;
g.DrawRectangle(Pens.Red, printedMarginArea);
}

Printing does not start at the top edge of the page

You have to set the PrintDocument.OriginAtMargins property to true to consider your margins.

From MSDN,

When OriginAtMargins is true, the Graphics object location takes into account the PageSettings.Margins property value and the printable area of the page

But printing from the exact edge depends on the printable area which is defined by the physical limitations of the printing device. Check the HardMarginX and HardMarginY to get the physical origin of the printer. For more information refer the answer of this question.

How do I set the margins for the PrintDocument?

In your PrintPage handler, you have the statement e.Graphics.PageUnit = GraphicsUnit.Inch and are thus causing the problem. The received e.Graphics.PageUnit is set to Display and the Graphics has a coordinate transform applied to it that reflects your OriginAtMargins = True setting. The transforms' OffsetX and OffsetY values were computed using the Display PageUnit setting (in 1/100 inch). When you change the PageUnit to Inch those offsets are now interpreted as Inches and whatever you are drawing on the Graphics is drawn out of bounds. This is why you get an empty page.

You should be able to eliminate the OriginAtMargins = True statement and modify the PrintPage handler code like this:

e.Graphics.PageUnit = GraphicsUnit.Inch
e.Graphics.ResetTransform() ' clear any previous transforms
e.Graphics.TranslateTransform((e.MarginBounds.X - e.PageSettings.HardMarginX) / 100.0F, (e.MarginBounds.X - e.PageSettings.HardMarginY) / 100.0F)

Edit: I corrected the TranslateTransform arguments to account for the printer's hard margin values that I originally neglected. This is based on the transform performed by the StandardPrintController.OnStartPage method when then PrintDocument.OriginAtMargins property is true.

PrintableArea in C# - Bug?

This is a pretty fundamental issue with .NET programming, every programmer falls for it at least once. The PrintableArea property type is RectangleF. That's a structure, a value type. When you use the property, you get back a copy of the value.

The compiler will notice that you are trying to modify a member of the copy, like when you try to assign the Height property. But it gets noddy when you use the Inflate() method. You are inflating the copy, not the original value and the compiler isn't smart enough to notice.

Key problem here is that the PrintableArea property only has a getter, it doesn't have a setter. Which means that you can't change it. Which makes sense if you think about it, you can't change the size of the piece of paper nor change the design of the printer. You probably want to use the Margins property.

C# PrintDocument: Phantom print to count total number of pages

This is already answered in the below question...

Is there a better way to get the page count from a PrintDocument than this?

Hope this helps...

Finding out max available print area

First one doesn't work because you are trying to print out of margins. Second one fails because you are trying to print over the margin, so the right and bottom lines fall 1 pixel off the bounds. Now 3rd one works IMO because, printing rectangle coordinates are floating point, and you are casting them to integers, thus rounding them down, so the rectangle falls inside the print area.

EDIT
Some additional info I found regarding your comment:
"If the Graphics object is using a nondefault PageUnit,[2] then VisibleClipBounds will be in different units than PageBounds (which is always in units of 100 dpi). To handle these variables, it's useful to have a helper method to return the "real" page bounds in a consistent unit of measure"

Check out this article , I believe it covers everything.



Related Topics



Leave a reply



Submit