I would like to ask what units does column.width return when using Openxml? (IS it in EMU, metres, cm or dpi and how do i get its DPI equivalent?
From SpreadsheetOpenXmlFromScratch:
Note that the width of the digits depends on the font and the font size used. I'll be using the MeasureString() function of the Graphics class to measure pixel width.
Everything Vincent does is based on the font type and size. You will have to retrieve the font type and font size to run through the calculations.
EDIT: pp 40-50
No, it is using the font measurements, not the column text. Another excerpt:
Note that the width of the digits depends on the font and the font size used. I'll be using the MeasureString() function of the Graphics class to measure pixel width.
System.Drawing.Font drawfont = new System.Drawing.Font("Calibri", 11, FontStyle.Regular);
// I just need a Graphics object. Any reasonable bitmap size will do.
Graphics g = Graphics.FromImage(new Bitmap(256, 256));
Dim drawfont As New System.Drawing.Font("Calibri", 11, FontStyle.Regular)
' I just need a Graphics object. Any reasonable bitmap size will do.
Dim g As Graphics = Graphics.FromImage(New Bitmap(256, 256))
The font used should be the same as that used in your stylesheet. Otherwise you would be measuring for one font or font size, but using another font or font size to render your actual text in the Excel cell. I know it's obvious, but it's important to state here.
Specifically, it should be the font used in the Normal style. That word is capitalised for a reason. It refers to the cell style named Normal (together with "Good", "Bad" and "Neutral"). Get your friendly neighbourhood Excel geek to help you find the cell style option in Excel.
Some of his other methods do require text when he actually measures the width of a zero flanked by two underscores, then subtracts the width of the underscores to get a more accurate width, but this just requires the font name and style.
Now the MeasureString() function has a tiny problem. This is from the official documentation:
The MeasureString method is designed for use with individual strings and includes a small amount of extra space before and after the string to allow for overhanging glyphs.
What this means is that the following code will not exactly give you the pixel width of "0":
fWidthOfZero = (double)g.MeasureString("0", drawfont).Width;
fWidthOfZero = Convert.ToDouble(g.MeasureString("0", drawfont).Width)
Give it a try.
A dashed line has a different count of dashes depending on the resolution of printer. Why isn't it dpi independent? How can I fix it?
var line = new Line();
line.Stroke = Brushes.Black;
line.StrokeThickness = 1;
line.X1 = line.Y1 = 100;
line.X2 = line.Y2 = 200;
line.StrokeDashArray = new DoubleCollection(new[] { 8.0 });
var dialog = new PrintDialog();
if (dialog.ShowDialog() == true)
{
Size pageSize = new Size(
dialog.PrintableAreaWidth, dialog.PrintableAreaHeight);
line.Measure(pageSize);
line.Arrange(new Rect(0, 0, pageSize.Width, pageSize.Height));
dialog.PrintVisual(line, "description");
}
I've used PrintVisual, but you can to create FixedDocument with dashed lines and use PrintDocument. The result will be the same.
WPF is inherently responsive to DPI changes, and pretty much every control (unless specified otherwise) is a vector graphic - I would expect your printer is smart enough to interpret these vector graphics rather than print a dump of what is on your screen.
From my experience with WPF there are at least two options:
Option 1 - Create a bitmap and print it
You can capture WPF controls as bitmap objects, which will be completely unaware of DPI using RenderTargetBitmap and then print them as normal. It is worth noting that the generated bitmap will be different depending on the display resolution/scaling of the system is was generated on & anything you want to capture as a bitmap must be visible on screen (not off screen in a scrollbox etc).
Option 2 - Make your app DPI unaware
You probably don't want to use this option, as it will prevent scaling on high resolution displays (There may be a way to achieve this temporarily just for printing, however your printer may ignore this option as it's more of a UI thing, i'm not currently able to test it) but there are several ways to prevent your app from acknowledging DPI all together.
I'm not entirely sure these options will work as I can't currently test them. I would have posted this as a comment if I could. Good luck!
I am currently creating a bitmap of 1632x1056 (17x11 at 96 dpi) pixels. I want to print this image on an 11in x 17in paper.
I can open this file up in Windows Photo Viewer and print it perfectly, but when I use the print function in c#, there is always a small margin that appears so it shifts my image to not fit on the whole page.
This is my code settings for the print document
Image glControlBits;
private void PrintImage()
{
//print the document
PrintDocument pd = new PrintDocument();
pd.PrintPage += pd_PrintPage;
pd.DefaultPageSettings.PaperSize = new PaperSize("PDI", 1100, 1700);
pd.DefaultPageSettings.Margins = new Margins(0, 0, 0, 0);
pd.DefaultPageSettings.Landscape = true;
pd.OriginAtMargins = true;
//pd.DefaultPageSettings.PrintableArea = new RectangleF(0, 0, viewSize.X, viewSize.Y);
//pd.DefaultPageSettings.HardMarginX = 0;
//pd.DefaultPageSettings.HardMarginY = 0;
//pd.DefaultPageSettings.PrinterResolution
PrintDialog pdialog = new PrintDialog();
pdialog.Document = pd;
if (pdialog.ShowDialog() == DialogResult.OK)
{
//set the print image to be the bitmap of the glcontrol
glControlBits = GrabScreenshot();
//save the bitmap, for debugging purposes
glControlBits.Save(#"C:\Users\Shane\Desktop\testbitmap.bmp");
pd.Print();
}
else return;
}
I think the problem might be that the hardmargin values are set by the printer, and the values are 25 for hardmarginx and 16 for hardmarginy. Is there a way to set these to zero so that is the no margin offset when I print? Thanks!
It appears you are not using the PageBorderless property. Try using the property as indicated in the MSDN documentation.
Let me know if the problem persists.
There's a reason why printers set a hard margin. For some printers, they really mean it.
For laser printers in particular, there's usually a signal for page top detect from the engine and the controller for the printer will get that and carefully time when to start lighting up the video circuitry (seriously - it's called video circuitry because it works the same as a CRT display) and write a charge onto the paper. Start too soon and you are hitting the internals for the first scanline. Scanlines are written by scanning the beam over the page and feeding the output of a FIFO into the beam control circuitry. There is only so much area that the beam can cover on the paper and it has a hard limit. You can't change it.
Ink jet printers can do pretty well L/R on a page but there are often mechanical reasons why they can't manage T/B as the paper may (for example) not be held firmly enough to the platen to spray ink onto the top 3 mm or so.
Why is finding out this magic Rectangle so difficult?
In the OnPrintPage event I have PrintPageEventArgs and I am trying to draw using the Graphics within the bounds of the maximum printable area.
I have tried using PageBounds, PrintableArea, Graphics.VisibleClipBounds, etc. All fail to consistently get the drawing area, especially when switching from Landscape to Portrait layout. PrintableArea does not seem to ever change when you switch from Landscape to Portrait.
I have also noticed that there is a difference in how Graphics.VisibleClipBounds is set depending on if I'm doing a print preview and an actual print. In a preview it always shows Portrait width/height, so I have to check if it is a preview and I have to manually swap the width/height when it is a Landscape.
I need an algorithm to calculate the printable area as it relates to the current Graphics context, not an arbitrary theoretical print area that isn't used in actual drawing.
My concern is dealing with the Graphics matrix offset. So far I have noticed severe inconsistencies between how the Graphics context is pre-translated using the hard margins depending on factors like:
If OriginAtMargins is true or false (not behaving as I would think)
If I'm printing to a printer, or using the PrintPreviewControl (I have to check if this is a print to preview or a print to page to handle the translation properly)
If I'm using my printer at home or my printer at work (both behave differently)
Is there a standard way to handle this? Should I just reset the matrix? When I set OriginAtMargins to true, the Graphics is pre-translated to 84,84, but my margins are 100,100. The hard margins are 16,16. Shouldn't it be translated to 100,100? Since 0,0 should be at the page bounds, not the hard margins.
Basically my method should always work at getting the best printable rectangle. I just need a consistent, device-independent way of making sure that my drawing origin (0, 0) is at the top-left of the page in order for the above Rectangle to be of any use to me.
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);
}
Currently the following is working on my printer. I have OriginAtMargins set to false. This causes automatic translation to the HardMarginX and HardMarginY when I'm printing to my printer, but NO translation when I'm printing to the PrintPreviewControl. Therefore, I have to check for this case.
private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
printAction = e.PrintAction;
printDocument.OriginAtMargins = false;
}
private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
if (printAction != PrintAction.PrintToPreview)
g.TranslateTransform(-e.PageSettings.HardMarginX, -e.PageSettings.HardMarginY);
RectangleF printArea = GetBestPrintableArea(e);
g.DrawRectangle(Pens.Red, printArea.X, printArea.Y, printArea.Width - 1, printArea.Height - 1);
}
public RectangleF GetBestPrintableArea(PrintPageEventArgs e)
{
RectangleF marginBounds = e.MarginBounds;
RectangleF printableArea = e.PageSettings.PrintableArea;
RectangleF pageBounds = e.PageBounds;
if (e.PageSettings.Landscape)
printableArea = new RectangleF(printableArea.Y, printableArea.X, printableArea.Height, printableArea.Width);
RectangleF bestArea = RectangleF.FromLTRB(
(float)Math.Max(marginBounds.Left, printableArea.Left),
(float)Math.Max(marginBounds.Top, printableArea.Top),
(float)Math.Min(marginBounds.Right, printableArea.Right),
(float)Math.Min(marginBounds.Bottom, printableArea.Bottom)
);
float bestMarginX = (float)Math.Max(bestArea.Left, pageBounds.Right - bestArea.Right);
float bestMarginY = (float)Math.Max(bestArea.Top, pageBounds.Bottom - bestArea.Bottom);
bestArea = RectangleF.FromLTRB(
bestMarginX,
bestMarginY,
pageBounds.Right - bestMarginX,
pageBounds.Bottom - bestMarginY
);
return bestArea;
}
If anyone can try this code on their printer to verify that it works universally, or correct it if I'm wrong, that would be great.
I don't know if pre-translation of the origin to the hard margins when OriginAtMargins is false is standard with all printers, or if it's just doing this on my printer.
I think what you need is simply redraw the image to fit whatever the paper size being used. Here's my code:
Protected Overrides Sub OnPrintPage(ByVal e As System.Drawing.Printing.PrintPageEventArgs)
Dim img As Image = Nothing 'Your image source
Dim ps As PaperSize = MyBase.PrinterSettings.DefaultPageSettings.PaperSize
Dim pF As RectangleF = MyBase.PrinterSettings.DefaultPageSettings.PrintableArea
Dim srcF As New RectangleF(0, 0, pg.ImageSize.Width, pg.ImageSize.Height)
Dim dstF As New RectangleF(0, 0, pF.Width, pF.Height)
e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
e.Graphics.DrawImage(img, dstF, srcF, GraphicsUnit.Pixel)
MyBase.OnPrintPage(e)
End Sub
I am drawing a rectangle in millimeter on a panel using the following code in c# by entering the width and length in mm at runtime. However the resultant rectangle drawn varies in size in different monitors. I want the rectangle to appear same size irrespective of the running the app in any monitor. Can any1 help me?. currently the width for 10mm measures 12mm and length for 10mm shows 11mm using a scale. I tested the app on different monitors, there again it shows different length. Is their anyway that I can show it to be of same width and length?
void panel1_Paint(object sender, PaintEventArgs e)
{
SolidBrush ygBrush = new SolidBrush(Color.YellowGreen);
g = panel1.CreateGraphics();
g.PageUnit = GraphicsUnit.Millimeter;
int w = Int32.Parse(textBox1.Text.ToString());
int h = Int32.Parse(textBox2.Text.ToString());
rct = new Rectangle(94, 27, w, h);
g.FillRectangle(ygBrush, rct);
}
Most displays aren't properly configured so that the computer knows the DPI (dots-per-inch). Physical units like millimeters only work if the computer knows how many pixels are in a millimeter, both horizontally and vertically.
Essentially, the method you're using is correct - but you're very unlikely in the real world to come across properly-configured machines.
If you needed to solve this problem in your software you could perhaps include a "configuration" option where the user would be responsible for setting the scale of the application.