C# Determining actual print area - c#

In C#, .NET 3.5 with winforms I am working on a print preview control. It seems to work fine, but when I print an A4 page I scanned in at real size on an A3 page, teh scale is out by about 3%...
I am using the following code to compute the actual printable area of the page based on the setting selected by the user and I am wondering if I maybe got those calculations wrong?
public static RectangleF GetPrintArea(PageSettings PageSettings)
{
float[] margins;
RectangleF printArea;
// Get the actual page bounds
printArea = PageSettings.Bounds;
// Calculate the hard margins taking into account page orientation
margins = new float[4];
// Left
margins[0] = !PageSettings.Landscape ? PageSettings.HardMarginX : PageSettings.HardMarginY;
// Top
margins[1] = !PageSettings.Landscape ? PageSettings.HardMarginY : PageSettings.HardMarginX;
// Right
margins[2] = margins[0];
// Bottom
margins[3] = margins[1];
// Calculate the real print margins taking into account teh hard and soft margins
// left
margins[0] = Math.Max(margins[0], PageSettings.Margins.Left);
// Top
margins[1] = Math.Max(margins[1], PageSettings.Margins.Top);
// Right
margins[2] = Math.Max(margins[2], PageSettings.Margins.Right);
// Bottom
margins[3] = Math.Max(margins[3], PageSettings.Margins.Bottom);
return new RectangleF(
new PointF(margins[0], margins[1]),
new SizeF(printArea.Width - (margins[0] + margins[2]), printArea.Height - (margins[1] + margins[3]))
);
}
This should return a rectangle which gives the actual area of the page on printing will happen. I use this rectangle for generating both the preview and the printout.
The code for printing out is as follows:
/// <summary>
/// Draws the image on the printing surface
/// </summary>
/// <param name="Graphics">The graohics object with which to draw</param>
protected virtual void PrintImage(Graphics Graphics)
{
RectangleF imageBoundingBox;
RectangleF visibleImageBoundingBox;
RectangleF visibleImage;
// Offset the visible bounding box location by the position of the print area so as to print right within the margins
Graphics.TranslateTransform(-this.Page.PrintableArea.Left, -this.Page.PrintableArea.Top);
// Calculate the bounding box of the scaled image
imageBoundingBox = new RectangleF(this.Page.PrintAreaOrigin.Add(this.ImagePrintLocation), this.Image.Size.Multiply(this.ImagePrintScale));
// Calculate the position and size of the portion of the image bounding box visible in the viewport
visibleImageBoundingBox = RectangleF.Intersect(imageBoundingBox, this.Page.PrintArea);
// Calculate the portion of the image which corresponds to the visible bounding box
visibleImage = new RectangleF(
new PointF(
imageBoundingBox.X < this.Page.PrintArea.Left ? Math.Min(this.Page.PrintArea.Left - imageBoundingBox.X, imageBoundingBox.Width) : 0,
imageBoundingBox.Y < this.Page.PrintArea.Top ? Math.Min(this.Page.PrintArea.Top - imageBoundingBox.Y, imageBoundingBox.Height) : 0
).Divide(this.ImagePrintScale),
visibleImageBoundingBox.Size.Divide(this.ImagePrintScale)
);
// Draw the image
Graphics.DrawImage(this.Image, visibleImageBoundingBox, visibleImage, GraphicsUnit.Pixel);
}
Where Page is a class which contains the page bounds (PageArea), the printable area PrintableArea and the actual print area (PrintArea). The actual print area is the area which is given by the previous rectangle.
Something must be wrong with this approach, but I can't for the life of me figure out what it is. If anybody can identify what's wrong I would be very grateful...

Related

How can I fix this code to properly give me an array of coordinates where every color provided is detected inside a bitmap?

For educational purposes, I'm trying to create something called a, "color detection" bot. I am trying to make Array of Point objects where colors I specify match at the exact location so that the program can "click" every single color it detects that matches. So if you want it to detect, red, green and yellow, I want it to click all of those in whatever order it finds them as long as they're part of the specified array.
I want it to add ANY match that fits the array of hexadecimal colors I've passed into the function.
It only works partially even if the same color is visible in multiple areas and doesn't add every color that matches on the screen to the array. It leaves so many colors that match, out. On top of that, it's very very slow to the point where you think the program froze.
I also wish I could add a color tolerance, so colors close to the hexadecimal would be accepted.
To paint a quick picture, I want the bot to click on specific objects in an inventory within a game that match the colors specified.
I've tried for loops inside and out for my hexadecimal array in attempts to try force the program to check each pixel for each color I've specified.
Here's part of my function that calls onto it
//Check what the current action is
if (action.ToLower().Contains("find colors and click"))
{
//Make sure to split up the action that looks like this: "Find Colors and Click|#3E5657 #867E7E #957D7C"
string[] splitString = action.Split('|');
//Make an array of Hexadecimal codes
string[] splitHexes = splitString[1].Split(' ');
//Create an Array of Points where SearchPixels detected a match
Point[] points = SearchPixels(splitHexes);
//Make sure it's not empty
if (points != null)
{
//Iterate through each point so we can click at the location of each point where every color we specified was detected even if it was detected many many times
foreach (Point point in points)
{
//Make sure the current point isn't empty
if (!point.IsEmpty)
{
//Move the mouse while updating the status
Status("Moving mouse to " + point.X + " " + point.Y + ". Waiting...");
SetCursorPos(point.X, point.Y);
Status("Moved mouse to " + point.X + " " + point.Y + ". Waiting...");
//Pause the thread for a moment so it doesn't spam click
new System.Threading.ManualResetEvent(false).WaitOne(250);
//Click while updating the status
Status("Clicked. Waiting...");
DoMouseClick();
}
}
}
Here's the code where the "magic" happens. I'll post both iterations that don't seem to work as they skip over so many colors that match or it doesn't even detect them. I'm not sure.
private Point[] SearchPixels(string[] hexcodes)
{
// Take an image from the screen
// Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); // Create an empty bitmap with the size of the current screen
Bitmap bitmap = new Bitmap(SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height); // Create an empty bitmap with the size of all connected screen
Graphics graphics = Graphics.FromImage(bitmap as Image); // Create a new graphics objects that can capture the screen
graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size); // Screenshot moment → screen content to graphics object
// Create our list of Points that we will eventually return
List<Point> points = new List<Point>();
// Go one to the right and then check from top to bottom every pixel (next round -> go one to right and go down again)
for (int x = 0; x < SystemInformation.VirtualScreen.Width; x++)
{
for (int y = 0; y < SystemInformation.VirtualScreen.Height; y++)
{
// Get the current pixels color
Color currentPixelColor = bitmap.GetPixel(x, y);
// Finally compare the pixels hex color and the desired hex color (if they match we found a pixel)
// Go through each hex code to see if it matches the current pixel as it's one of our desired pixels
foreach (string str in hexcodes)
{
// Get the desired pixel color from the current hexcode
Color desiredPixelColor = ColorTranslator.FromHtml(str);
if (desiredPixelColor == currentPixelColor)
{
// Found Pixel - Now set the location and add it to the array
Point currentPoint = new Point(x, y);
// Make sure it isn't a duplicate.. I wish I could make it not add anything too close either
if (!points.Contains(currentPoint))
{
// Add the current point to the array
points.Add(currentPoint);
}
}
}
}
}
// Return the array
return points.ToArray();
}
Here's the second version of it
private Point[] SearchPixels(string[] hexcodes)
{
// Take an image from the screen
// Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); // Create an empty bitmap with the size of the current screen
Bitmap bitmap = new Bitmap(SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height); // Create an empty bitmap with the size of all connected screen
Graphics graphics = Graphics.FromImage(bitmap as Image); // Create a new graphics objects that can capture the screen
graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size); // Screenshot moment → screen content to graphics object
// Create our list of Points that we will eventually return
List<Point> points = new List<Point>();
// Go through each hex code to see if it matches the current pixel as it's one of our desired pixels
foreach (string str in hexcodes)
{
// Get the desired pixel color from the current hexcode
Color desiredPixelColor = ColorTranslator.FromHtml(str);
// Go one to the right and then check from top to bottom every pixel (next round -> go one to right and go down again)
for (int x = 0; x < SystemInformation.VirtualScreen.Width; x++)
{
for (int y = 0; y < SystemInformation.VirtualScreen.Height; y++)
{
// Get the current pixels color
Color currentPixelColor = bitmap.GetPixel(x, y);
// Finally compare the pixels hex color and the desired hex color (if they match we found a pixel)
// Go through each hex code to see if it matches the current pixel as it's one of our desired pixels
if (desiredPixelColor == currentPixelColor)
{
// Found Pixel - Now set the location and add it to the array
Point currentPoint = new Point(x, y);
// Make sure it isn't a duplicate.. I wish I could make it not add anything too close either
if (!points.Contains(currentPoint))
{
// Add the current point to the array
points.Add(currentPoint);
}
}
}
}
// Return the array
return points.ToArray();
}
I'm expecting the bot to detect and return every single point on the screen that matches the color of any hexadecimal color inside the array of hexadecimals I've provided. It just doesn't do that. It matches maybe 1-5, then it gives up. Sometimes it only detects one color of the three provided, or sometimes only two.
As an example: Even if there are 28 objects of the same color in the inventory, it might only find and provide an array of 8 Points instead of 28.
I also want it to find these coordinates really fast as I don't want the program to be slow.
I'm super frustrated and I thank you for your help.
Edit: I have indeed debugged through my code multiple times.

iTextSharp watermark not appearing on a scanned PDF

I have some code used to watermark PDFs using iTextSharp. The code works fine for most PDFs, but there has been one test case where the watermark is not visible on a PDF of a scanned document. (I have other scanned documents where it does appear though).
I am using the GetOverContent() method.
This is my code for adding the watermark;
using (PdfReader reader = new PdfReader(this.inputFilename))
{
// Set transparent - 1
PdfGState gstate = new PdfGState();
gstate.FillOpacity = 0.4f;
gstate.StrokeOpacity = 0.5f;
// 2
BaseFont baseFont = BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, Encoding.ASCII.EncodingName, false);
using (var stream = new MemoryStream())
{
var pdfStamper = new PdfStamper(reader, stream);
// Must start at 1 because 0 is not an actual page.
for (int i = 1; i <= reader.NumberOfPages; i++)
{
Rectangle pageSize = reader.GetPageSizeWithRotation(i);
// Gets the content ABOVE the PDF, Another option is GetUnderContent(...)
// which will place the text below the PDF content.
PdfContentByte pdfPageContents = pdfStamper.GetOverContent(i);
pdfPageContents.BeginText(); // Start working with text.
// 1
pdfPageContents.SaveState();
pdfPageContents.SetGState(gstate);
float hypotenuse = (float)Math.Sqrt(Math.Pow(pageSize.Width, 2) + Math.Pow(pageSize.Height, 2));
float glyphWidth = baseFont.GetWidth("My watermark text");
float fontSize = 1000 * (hypotenuse * 0.8f) / glyphWidth;
float angle = (float)(Math.Atan(pageSize.Height / pageSize.Width) * (180 / Math.PI));
// Create a font to work with
pdfPageContents.SetFontAndSize(baseFont, fontSize);
pdfPageContents.SetRGBColorFill(128, 128, 128); // Sets the color of the font, GRAY in this instance
// Note: The x,y of the Pdf Matrix is from bottom left corner.
// This command tells iTextSharp to write the text at a certain location with a certain angle.
// Again, this will angle the text from bottom left corner to top right corner and it will
// place the text in the middle of the page.
pdfPageContents.ShowTextAligned(PdfContentByte.ALIGN_CENTER, "My watermark text", pageSize.Width / 2, pageSize.Height / 2, angle);
pdfPageContents.EndText(); // Done working with text
pdfPageContents.RestoreState();
}
pdfStamper.FormFlattening = true; // enable this if you want the PDF flattened.
pdfStamper.FreeTextFlattening = true; // enable this if you want the PDF flattened.
pdfStamper.Close(); // Always close the stamper or you'll have a 0 byte stream.
return stream.ToArray();
}
}
Does anyone have any ideas as to why the watermark may not be appearing and what I can try to fix it?
Kind regards.
The code is based on an assumption it even documents as a fact:
// Note: The x,y of the Pdf Matrix is from bottom left corner.
// This command tells iTextSharp to write the text at a certain location with a certain angle.
// Again, this will angle the text from bottom left corner to top right corner and it will
// place the text in the middle of the page.
pdfPageContents.ShowTextAligned(PdfContentByte.ALIGN_CENTER, "My watermark text", pageSize.Width / 2, pageSize.Height / 2, angle);
The assumption that the x,y of the Pdf Matrix is from bottom left corner unfortunately is wrong: While it indeed is very often the case that the origin of the PDF coordinate system (the default user space coordinate system, to be more precise) is in the lower left corner of the page, this is not required, the origin actually can be literally anywhere (within reasonable limits).
Thus, one has to take the lower left coordinates of the Rectangle pageSize into consideration, too.
The OP meanwhile has confirmed:
I had assumed that the bottom left of the page would have co-ordinates of (0,0) but for this document the co-ordinates were (0, 7022).

how to extend draw area in Graphics.DrawImage c#

I have a Rectangle (rec) that contains the area in which a smaller image is contained within a larger image. I want to display this smaller image on a Picturebox. However, what I really am doing is using the smaller image as a picture detector for a larger image that is 333x324. So what I want to do is use the coordinates of the smaller image rectangle, and then draw to the Picturebox, starting from lefthand side of the rectangle, going outwards by 333 width and 324 height.
Currently my code works but it only displays the small image that was being used for detection purposes. I want it to display the smaller image + 300 width and + 300 height.
I fiddled with this code for hours and I must be doing something extremely basic wrong. If anyone can help me I would appreciate it so much!
My code for the class:
public static class Worker
{
public static void doWork(object myForm)
{
//infinitely search for maps
for (;;)
{
//match type signature for Threading
var myForm1 = (Form1)myForm;
//capture screen
Bitmap currentBitmap = new Bitmap(CaptureScreen.capture());
//detect map
Detector detector = new Detector();
Rectangle rec = detector.searchBitmap(currentBitmap, 0.1);
//if it actually found something
if(rec.Width != 0)
{
// Create the new bitmap and associated graphics object
Bitmap bmp = new Bitmap(rec.X, rec.Y);
Graphics g = Graphics.FromImage(bmp);
// Draw the specified section of the source bitmap to the new one
g.DrawImage(currentBitmap, 0,0, rec, GraphicsUnit.Pixel);
// send to the picture box &refresh;
myForm1.Invoke(new Action(() =>
{
myForm1.getPicturebox().Image = bmp;
myForm1.getPicturebox().Refresh();
myForm1.Update();
}));
// Clean up
g.Dispose();
bmp.Dispose();
}
//kill
currentBitmap.Dispose();
//do 10 times per second
System.Threading.Thread.Sleep(100);
}
}
}
If I understand correctly, the rec variable contains a rectangle with correct X and Y which identifies a rectangle with Width=333 and Height=324.
So inside the if statement, start by setting the desired size:
rec.Width = 333;
rec.Height = 324;
Then, note that the Bitmap constructor expects the width and height, so change
Bitmap bmp = new Bitmap(rec.X, rec.Y);
to
Bitmap bmp = new Bitmap(rec.Width, rec.Height);
and that's it - the rest of the code can stay the way it is now.

How zoom into a drawn rectangle of picturebox which is inside a panel?

I have a PictureBox inside a Panel in order to zoom and pan. I created the possibility to select 4 points with the mouse click and draw a rectangle on the PictureBox. Once the rectangle is over my picture I pass the coordinates of the rectangle to the method "cropRectangle". This method crops the rectangle and replace the old image with the cropped one. This works very well:
(OriginalImage is the bitmap of the actual image in the pictureBox)
private void cropRectangle(Rectangle rect){
double left = (rect.X) * originalImage.Width / pictureBox.Width,
top = (rect.Y) * originalImage.Width / pictureBox.Height,
right = (rect.Width) * originalImage.Width / pictureBox.Width,
bottom = (rect.Height) * originalImage.Height / pictureBox.Height;
rect = new Rectangle (Convert.ToInt32(left), Convert.ToInt32(top), Convert.ToInt32(right), Convert.ToInt32(bottom));
Bitmap bitmap = orignalImage.Clone(rect, originalImage.PixelFormat);
pictureBox.Image = (Image)bitmap;
centerPictureBox();
// fit image into pictureBox with respect to the ratio
float ratio = orignalImage.Width / orignalImage.Height;
pictureBox.Width = panel.Width;
pictureBox.Height = Convert.ToInt32(pictureBox.Width * ratio);
centerPictureBox();
}
What I am trying to do now is to zoom the selected area instead to crop it. The rectangle of the picturebox has to match with the panel.
How can I show only the selected area (rectangle) of the picturebox through the panel without cropping the image?
You should stick with modifying the existing Bitmap using the Graphics object instead of changing the size of the PictureBox. You don't want to be tied to a UI control when the desired functionality is already available elsewhere.
Here are rough steps to achieve that:
Create a temporary Bitmap object that will store the zoomed image. Bitmap tBitmap = new Bitmap(zoomX, zoomY, PixelFormat.Format24bppRgb);
Calculate the zoom factors and stuff like you already do (I didn't check if the code is correct but I assume that it is) when you want to zoom.
Create a new Graphics object from the temporary bitmap. Graphics graphics = Graphics.FromImage(tBitmap);
Set the InterpolationMode so that the image is scaled with a good quality. graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
Draw the zoomed image using the DrawImage method (using the original image from the PictureBox). graphics.DrawImage(pictureBox.Image, new Rectangle(0, 0, pictureBox.Width, pictureBox.Height), new Rectangle(/*The crop rectangle you draw already*/), GraphicsUnit.Pixel);
Set the newly drawn bitmap as the Image in your PictureBox. pictureBox.Image = tBitmap;
Remember to dispose of the graphics object we used for drawing. graphics.Dispose();
You might need to refresh the PictureBox to force it to redraw itself. pictureBox.Refresh();
Those are the basic steps to follow. I didn't have time to go through your existing code that deeply so you might need to change some additional things to make it work.
Here's also an MSDN article that covers the same stuff: Cropping and Scaling Images in GDI+
You might be interested in this control (ZoomPicBox) which allows for zooming and panning a picture box.
All credit for this code is Bob Powell and it was taken from his site (which seems to be down now and has been for a long time now.).
I copied the code from archive.org at this link:
https://web.archive.org/web/20080313161349/http://www.bobpowell.net/zoompicbox.htm
That link has additional information and is worth a read. The code is available in VB.Net as well.
I don't know why Bob Powell's site is down, but it was a great site for Windows Graphics information.
I felt this code was worth repeating. This control can be dragged onto the form.
namespace bobpowell.net
{
/// <summary>
/// ZoomPicBox does what it says on the wrapper.
/// </summary>
/// <remarks>
/// PictureBox doesn't lend itself well to overriding. Why not start with something basic and do the job properly?
/// </remarks>
public class ZoomPicBox : ScrollableControl
{
Image _image;
[
Category("Appearance"),
Description("The image to be displayed")
]
public Image Image
{
get{return _image;}
set
{
_image=value;
UpdateScaleFactor();
Invalidate();
}
}
float _zoom=1.0f;
[
Category("Appearance"),
Description("The zoom factor. Less than 1 to reduce. More than 1 to magnify.")
]
public float Zoom
{
get{return _zoom;}
set
{
if(value<0 || value<0.00001)
value=0.00001f;
_zoom=value;
UpdateScaleFactor();
Invalidate();
}
}
/// <summary>
/// Calculates the effective size of the image
///after zooming and updates the AutoScrollSize accordingly
/// </summary>
private void UpdateScaleFactor()
{
if(_image==null)
this.AutoScrollMinSize=this.Size;
else
{
this.AutoScrollMinSize=new Size(
(int)(this._image.Width*_zoom+0.5f),
(int)(this._image.Height*_zoom+0.5f)
);
}
}
InterpolationMode _interpolationMode=InterpolationMode.High;
[
Category("Appearance"),
Description("The interpolation mode used to smooth the drawing")
]
public InterpolationMode InterpolationMode
{
get{return _interpolationMode;}
set{_interpolationMode=value;}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
// do nothing.
}
protected override void OnPaint(PaintEventArgs e)
{
//if no image, don't bother
if(_image==null)
{
base.OnPaintBackground(e);
return;
}
//Set up a zoom matrix
Matrix mx=new Matrix(_zoom,0,0,_zoom,0,0);
//now translate the matrix into position for the scrollbars
mx.Translate(this.AutoScrollPosition.X / _zoom, this.AutoScrollPosition.Y / _zoom);
//use the transform
e.Graphics.Transform=mx;
//and the desired interpolation mode
e.Graphics.InterpolationMode=_interpolationMode;
//Draw the image ignoring the images resolution settings.
e.Graphics.DrawImage(_image,new Rectangle(0,0,this._image.Width,this._image.Height),0,0,_image.Width, _image.Height,GraphicsUnit.Pixel);
base.OnPaint (e);
}
public ZoomPicBox()
{
//Double buffer the control
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint |
ControlStyles.DoubleBuffer, true);
this.AutoScroll=true;
}
}
}
I have found a elegant solution to my problem:
private void zoomInsideRectangle(Rectangle rect){
float zoomFactor = ((float)panel.Width / rect.Width) - 1;
pictureBox.Width = pictureBox.Width + convertToIntPerfect(pictureBox.Width * zoomFactor);
pictureBox.Height = pictureBox.Height + convertToIntPerfect(pictureBox.Height * zoomFactor);
rect.X = rect.X + convertToIntPerfect(rect.X * zoomFactor);
rect.Y = rect.Y + convertToIntPerfect(rect.Y * zoomFactor);
pictureBox.Left = convertToIntPerfect(-rect.X);
pictureBox.Top = convertToIntPerfect(-rect.Y);
}
Since I know the length of the panel where I can see the picturebox. I take the ratio of the panel and the width of my rectangle that I want to zoom in. This ratio is my zoomratio.
I multiply the size of the picturebox with the ratio that I calculated.
I anchor the picturebox by the left and top with the coordinates of my rectangle. But right before doing that I have to multiply my coordinates of my rectangle with the zoomratio since I changed the size of the picturebox.
I didn't implemented the Y transformation since the original ratio of the image will be damaged.

PrintDocument always has margin

I am having a problem when using PrintDocument with margins.
No matter what I do there is always a margin around everything I print, this means that nothing is aligned where it needs to be.
Here is the code I am using to create the PrintDocument
public void Print()
{
PrintDocument printDocument = new PrintDocument();
printDocument.DefaultPageSettings.PaperSize = new PaperSize("A5",583,827);
printDocument.OriginAtMargins = true;
printDocument.DefaultPageSettings.Margins.Top = 0;
printDocument.DefaultPageSettings.Margins.Left = 0;
printDocument.DefaultPageSettings.Margins.Right = 0;
printDocument.DefaultPageSettings.Margins.Bottom = 0;
if (!string.IsNullOrWhiteSpace(PrinterName))
{
printDocument.PrinterSettings.PrinterName = PrinterName;
}
printDocument.PrintController = new StandardPrintController();
printDocument.PrintPage += On_PrintPage;
printDocument.Print();
}
The On_PrintPage method, has various calls to e.Graphics.Draw... methods.
How can I make it so that something I print at 0,0 will print in the very top left of the page. I know that if the printer cannot print that far to the edge of the page it will be blank, however it should do that rather than print 0,0 not in the top left of the page.
I'm really lost here
interestingly the print function is too late to set most of the properties and would only apply to subsequent pages
you need to use PrintDocument.QueryPageSettings event instead and set the properties there and I always set the page settings not just defaults. then drawing at 0,0 should be as close as you can get (printer + driver allows)
The OriginAtMargins property of the PrintDocument, when set to true, it will sets the origin of the provided Graphics object at the top-left corner of the margin. From the MSDN document Remarks section:
Calculating the area available to print requires knowing the physical
size of the paper, the margins for the page, and the location of the
Graphics object origin. When OriginAtMargins is true, the Graphics
object location takes into account the PageSettings.Margins property
value and the printable area of the page. When OriginAtMargins is
false, only the printable area of the page is used to determine the
location of the Graphics object origin, the PageSettings.Margins value
is ignored.
Set it to false, to change it to the top-left corner of the printable area. If you need it to be at the top-left corner of the page, you will need to transform the graphics object to mach that coordinate; there isn't really a way to get around this:
void PrintDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
var printArea = e.PageSettings.PrintableArea;
e.Graphics.TranslateTransform(-printArea.X, -printArea.Y);
// Build up the page here.
}
If you want to print landscape pages as well, this becomes tricky, as none of the properties of PageSettings are affected by this setting. This requires to know the angle the page is rotated by—as PrintableArea isn't always centered on the page—which can be acquired with PageSettings.PrinterSettings.LandscapeAngle. The value can only be either 90 or 270, and the code would look like this:
void PrintDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
var printArea = e.PageSettings.PrintableArea;
if (e.PageSettings.Landscape)
{
var pageSize = e.PageSettings.PageSize;
switch (e.PageSettings.PrinterSettings.LandscapeAngle)
{
case 90:
e.Graphics.TranslateTransform(
dx: -printArea.Y,
dy: -(pageSize.Width - (printArea.X + printArea.Width)));
break;
case 270:
e.Graphics.TranslateTransform(
dx: -(pageSize.Height - (printArea.Y + printArea.Height)),
dy: -printArea.X);
break;
}
}
else
{
e.Graphics.TranslateTransform(-printArea.X, -printArea.Y);
}
// Build up the page here.
}
If you later want to use another transformation on a portion of the page, do not forget to use var gs = e.Save() before transforming and e.Restore(gs) to restore that transformation, instead of calling e.ResetTransform(), as the later will reset the transformation you did in the beginning.

Categories

Resources