Fog Creek Software
Discussion Board

Why is printing not exact?

Has anyone done any programming where they used the  Windows API routines to print some text and/or graphics?  (i.e. obtain and draw on a Printer Device Context,  etc.)

If you have then you know that by querying the capabilities of the printer you can find the printable and unprintable areas of a page.

I found that even though Windows tells you that you can print in the printable area, when I draw a rectangle around the perimeter of the printable area, only the top and left sides of the rectangle were printed.  I have tested this on two Ink Jet printers.  Maybe it's just ink-jet printers.  Maybe dot-matrix and laser would produce different results.

What this tells me is that the (Top,Left) corner of the printable area is the only guaranteed reference point on the page.  The (Bottom,Right) corner of the printable area cannot be guaranteed even though Windows gives you these limits. 

After fiddling with the (Bottom, Right) coordinates I found that adjusting them by as little as 4 twips allows the printer to print those sides of the rectangle.

Why would the printer not be able to draw the right and bottom sides of the printable area given the limits that Windows (i.e. the device driver) sets? 

Is this because of the physical limitations of the printer or possibly inaccuracies in the Device Driver?

Dave B.
Tuesday, March 04, 2003

The positioned output depends in part on the mechanical capabilities and quality of the printer. When paper is fed into the printer, there are tolerances of about 1 mm where the paper is misplaced in the y-direction. Also there are tolerances of about 0.1mm to 0.5mm in the x-direction. This is very common with Ink Jets.

It could also be that the printer-driver is bad - or has a lax implementation. A poor driver might just return the wrong unprintable area.

I've found that by subtracting a small amount (.10mm) to the printable area you almost always get good results.

Tuesday, March 04, 2003

I have noticed that some very recent printer drivers from HP are extremely defective - placing things in the wrong area, aligning stuff incorrectly, drawing things that should be smooth badly. Worthless junk. Very annoying.

Dennis Atkins
Tuesday, March 04, 2003

Thanks Marc.

I also discovered that the paper I'm using for the tests is not exactly 8.5 in. wide or 11 in. high.  It is about 1/64 - 1/32 of an inch short.  I guess if you could account for all of the inaccuracies which are incurred until and while the printer is ploting the output, you could produce exacting output, but that is probably not feasible.  So, as you suggest, I will have to adjust the printable area by a small amount to account for this build up of error.  I'm attempting to figure out a reasonable generic error term ( you suggested .10mm ) or a formula for calculating one.  The other option might be to simply trust the printer driver and lean on the known point (Left,Top).

Dave B.
Tuesday, March 04, 2003

With all the money printer companies like HP make, you would think they could create a printer that actually works. Printers seem like the most error-prone part of modern computing. Whenever I (try to) print something, I fear:

- mysterious "blinken lights"
- mysteriously stalled printer queues
- paper jams
- paper reloading (easy but then the print job undoutably gets aborted or confused)

I know printers have a lot of moving parts (literally), but can't they make them more error-proof and easier to diagnose errors??

Wednesday, March 05, 2003

I'd say that 60% of all printing problems, and certainly 95% of all page layout problems are due to faulty drivers.

I wonder if HP have an especially bad rep here because they really do write drivers that are that bad, or they write drivers that are average for the printer industry (e.g. Crap) but just sell a lot more printers.

Robert Moir
Wednesday, March 05, 2003

Try it again but using a postscript driver. In particular the HP drivers clip right justified text badly (i.e. a number in a bounding box) I think it's something to do with printer fonts. In general the PS drivers seem to be much better.

Peter Ibbotson
Wednesday, March 05, 2003

From my old days of writing some code to control DM printers - I think the Upper Left Corner was the beginning of axis. So it makes sense that (Top, Left) corner is a guaranteed area - they need to start count from somewhere.

Eugene Patek
Wednesday, March 05, 2003

Dude, why are you bothering using paper?  Paper is so 90s. ;)

Your printer is designed to work for a few years, then die, so you can buy a new one.  Also, dealing with the quirks of paper is a royal pain in general, even with coppiers and laser printers.  The "real" presses use rolls of paper and a cutter at the end so that they have to deal with it all once and never again.  Every page edge is an invitation for trouble.

There is no economic incentive to provide completely correct printer drivers for anything other than a printer that is expensive enough to merit Postscript.  Coincidentally, Postscript drivers tend to be the least buggy.  It needs to work well enough for the users to not throw it away in disgust and not much better.  Because there's so little choice in printers these days (Epson, HP, Lexmark, and Canon) you generally have no better choice anyway.

flamebait sr.
Wednesday, March 05, 2003

Maybe Joel knows the answer to this. How does Microsoft handle this? I've never had a problem with any Microsoft product when I go to print. Are they storing the offsets for each make/model someplace? They must compensate for this some how.

Wednesday, March 05, 2003

Microsoft uses the offsets provided by the printer driver.  This is evidenced when you use MS-Word.  If you go into word and attempt to set the margins, it will not let you set them outside of the unprintable area ( i.e. the values returned by Win32 API GetDeviceCaps(), which are from the driver ). 

If you set the margins to their mimimum in MS-Word.  Then draw a box (i.e. a table) on the page and expand it to fill the page, when you print it, it exhibits the same behavior as my program (i.e. the bottom and right lines of the box do not print (on my printers) ).  This tells me that Microsoft uses the (Left, Top) anchor and trusts the driver manufacturer for the (Right, Bottom) margins.

As for the reason I'm using paper ( I know you were probably being sarcastic, although maybe not. ), a physical copy of the output is needed for proofing and "waxing".  As for accuracy, I'm trying my best to understand the physical limitations of the printing devices so that I have a better understanding when a customer asks me, "Why is my printer printing a quarter of an inch off?" and I can say with honesty, "It's a limitation of the printer and the printer driver. Contact the Manufacturer for an updated printer driver or upgrade your printer."  ;)

Dave B.
Wednesday, March 05, 2003

Applications that Really Really Care about exact positioning on the page, like forms-filling-out applications and Quicken's check writing feature, always have an alignment feature, where they print a special page with some kind of marks on it and you tell them how wrong it is.

That said, computer hardware companies are notoriously awful at writing software. They often assign an electrical engineer to do it, someone with no software skills or talent. The history of Windows device drivers is the history of Microsoft trying to make it easier and easier to write a driver because it became clear that the hardware companies just couldn't do it. ATI's video drivers crashed Windows 3.x a lot more often then 3.x crashed by itself, and Microsoft always got blamed.

Joel Spolsky
Wednesday, March 05, 2003

The problem with using a table full of "published offsets" for each printer would be that it wouldn't take into account

i) manufacturing tolerances

ii) newer models of the printer

So those applications that need precise printing because you are trying to print onto headed stationary with them are back to either printing test pages as Joel says, or sucking like a Kirby vacuum cleaner.

Robert Moir
Wednesday, March 05, 2003

And aren't I dealing with this very problem myself, today!

Without a doubt, iffy tolerances and bad paper handling can be contributing factors, but my vote goes to bad printer drivers.  Prosise points out that "...printer drivers are maddeningly inconsistent in the information they report and the output they produce. For example, some printer drivers return the same values for PHYSICALWIDTH and PHYSICALHEIGHT as they return for HORZRES and VERTRES."

For those of you who haven't called GetDeviceCaps on a printer before, PHYSICALWIDTH and PHYSICALHEIGHT are supposed to be just that -- the physical width/height of the page (in pixels). HORZRES and VERTRES are supposed to be the width of the printable area, also in pixels. In other words, they're supposed to take the printer's need for margin space into account.

If your printer tells you that the physical and printable areas of the page are identical, you either have a VERY nice printer or a bad printer driver. No prizes for guessing which is more likely.

My solution? I'm fudging by a few pixels, so I can be (more) certain that the entire image will print.


Chuck McKinnon
Wednesday, March 05, 2003

I have been pondering the "print a sheet with special marks and let the customer measure it approach", but come on, I mean we're talking about the customer here.  Do you think they know how to use a ruler?

All joking aside, I believe it is a good idea.  Unfortunately ( or fortunately, I'm not sure which ), it's now in the hands of the customer if they want exact output or not.

Dave B.
Wednesday, March 05, 2003

I struck the same problem many years ago, but reasoned my way around it by considering the thickness and origin of the line being drawn.

In this case, I made an assumption that the dot/pixel is drawn with its origin at the top-left specified by the co-ordinates. Remember that the dot has its own height and width.

I then assumed that the co-ordinates returned by the API represent the outer dimensions of the bounding rectangle. That is, not the right-most co-ordinate at which at dot can be drawn, but the maximum value at which printing-origin + dot-width can be printed.

So ... subtract the dot-width (or an approximation) from the rectangle ... and you will have an area  in which you can print.

This may not be the CORRECT answer, but it does work :)

Wednesday, March 05, 2003

The best solution is an alignment page that requires no ruler.

An example of this is a good alignment page to make sure that color and b&w cartrages are aligned properly.  It prints a series of color lines between black lines so you just feed in the line number that looks the straightest.

Or RGB convergence tests that draw a grid out of alternating red, green, and blue lines.

For your purposes you could draw a slightly diagonal dashed line around the margin so that you can have the user count the number of dashes on the test page to determine what the margins actually are.

flamebait sr.
Thursday, March 06, 2003

I've been trying to get my graphics application application to print 'correctly' for a few days.  Finally I believe it works.  Here are the issues I encountered.

PageSetupDialog and metric measurement
The PageSettingDialog has a bug; when in metric mode the margins are incorrectly translated.

Use the following to code to show the dialog:

      const double f = 2.54000000259d;
      if (RegionInfo.CurrentRegion.IsMetric)
        pageSettings.Margins.Top = (int) Math.Round(pageSettings.Margins.Top * f);
        pageSettings.Margins.Bottom = (int) Math.Round(pageSettings.Margins.Bottom * f);
        pageSettings.Margins.Left = (int) Math.Round(pageSettings.Margins.Left * f);
        pageSettings.Margins.Right = (int) Math.Round(pageSettings.Margins.Right * f);
      if (pageSetupDialog.ShowDialog() != DialogResult.OK && RegionInfo.CurrentRegion.IsMetric)
        pageSettings.Margins.Top = (int) Math.Round(pageSettings.Margins.Top / f);
        pageSettings.Margins.Bottom = (int) Math.Round(pageSettings.Margins.Bottom / f);
        pageSettings.Margins.Left = (int) Math.Round(pageSettings.Margins.Left / f);
        pageSettings.Margins.Right = (int) Math.Round(pageSettings.Margins.Right / f);

Print to a printer vs print preview
If we are printing to a printer, then the origin point (0,0) is at the 'hard margin' origin; otherwise, for print preview the origin is at the top of the page.

The following code assumes that we are working in inches!

      Margins hard = HardMargins;
      RectangleF hardBounds = new RectangleF
        (e.PageBounds.X + hard.Left) / 100.0f,
        (e.PageBounds.Y + hard.Top) / 100.0f,
        (e.PageBounds.Width - hard.Left - hard.Right) / 100.0f,
        (e.PageBounds.Height - hard.Top - hard.Bottom) / 100.0f
      bool isPreview = e.Graphics.VisibleClipBounds.Width > e.PageBounds.Width;
      if (!isPreview)
        e.Graphics.TranslateTransform(-hard.Left / 100.0f, -hard.Top / 100.0f);

Obtaining the hard magins for a printer
The HardMargins defines the physical area of the page that can be printed on.  The following code returns the margins in hundreds of inches.

  public Margins HardMargins
        using (Graphics g = PrinterSettings.CreateMeasurementGraphics())
            IntPtr hdc = g.GetHdc();
            int dc = hdc.ToInt32();

            float dpiX = (float) GetDeviceCaps(dc, LOGPIXELSX);
            float dpiY = (float) GetDeviceCaps(dc, LOGPIXELSY);
            float offsetX = GetDeviceCaps(dc, PHYSICALOFFSETX) / dpiX;
            float offsetY = GetDeviceCaps(dc, PHYSICALOFFSETY) / dpiY;
            float width = GetDeviceCaps(dc, PHYSICALWIDTH) / dpiX;
            float height = GetDeviceCaps(dc, PHYSICALHEIGHT) / dpiX;
            float hres = GetDeviceCaps(dc, HORZRES) / dpiX;
            float vres = GetDeviceCaps(dc, VERTRES) / dpiX;

            return new Margins(
              (int) Math.Round(offsetX * 100),
              (int) Math.Round((width - hres - offsetX) * 100),
              (int) Math.Round(offsetY * 100),
              (int) Math.Round((height - vres - offsetY) * 100)

#region GetDeviceCaps
  public static extern Int16 GetDeviceCaps(
      [In] [MarshalAs (UnmanagedType.U4)] int hDc,
      [In] [MarshalAs (UnmanagedType.U2)] Int16 funct);

  private const short HORZRES        = 8;    // Horizontal width in pixels
  private const short VERTRES        = 10;    // Vertical height in pixels
  private const short PHYSICALWIDTH  = 110;  // Physical Width in device units
  private const short PHYSICALHEIGHT  = 111;  // Physical Height in device units
  private const short PHYSICALOFFSETX = 112;  // Physical Printable Area x margin
  private const short PHYSICALOFFSETY = 113;  // Physical Printable Area y margin
  private const short LOGPIXELSX      = 88;    // Horizontal pixels per inch
  private const short LOGPIXELSY      = 90;    // Vertical pixels per inch


Richard Schneider
Wednesday, February 18, 2004

*  Recent Topics

*  Fog Creek Home