#1 2014-03-13 11:26:14

john.friel
Member
From: Waterloo, IA
Registered: 2014-03-13
Posts: 3
Website

Problems with GetDeviceCaps on synPDF canvas

I am having a problem with using GetDeviceCaps and the TPdfDocumentGDI VCLCanvas.  I generate laser Purchase orders and Statements using laser printers.  The GetPrinterParameters procedure takes a Canvas and then tells me everything I need to know about the page so I can take into account non-printable areas and create my margins so the output looks the same no matter what printer or DPI it is set at.  I've used the doPDF printer driver to print to a PDF and that too seems to work fine as it looks like a printer to my system, but it asks a lot of questions and I want to automate generating the PDFs, hence my trying out synPDF. 

The issue I am running into is that the GetDeviceCaps windows function is not returning meaningful data when passing in VCLCanvas.  With the code below, I am getting these values:

myLOGPIXELSX = 96, should be 600
myLOGPIXELSY = 96, should be 600
myHORZRES = 2560, should be 5100
myVERTRES = 1440, should be 6600
myPHYSICALOFFSETX = 0, correct because a PDF has no unprintable left margin, same as the doPDF driver
myPHYSICALOFFSETY = 0, correct and no unprintable top margin
myPHYSICALWIDTH = 0, should be 5100 and match myHORZRES, same as doPDF
myPHYSICALHEIGHT = 0, should be 6600 and match myVERTRES, same as doPDF

I use these core values to figure out what is physical and what is printable so I can correctly calculate top and left margins on the page so it looks the same regardless of the printer driver or printer.  If I want a .5 inch margin all around the page, I can get exactly that.

So, where am I not initializing TPdfDocumentGDI correctly to get the correct dimensions out of the GetDeviceCaps call? 


var
  lPdf  : TPdfDocumentGDI;
  lPage : TPdfPage;
  aCanvas : TCanvas;

procedure GetPrinterParameters(aCanvas : TCanvas);
begin
  with aCanvas do
    begin
      myLOGPIXELSX        := GetDeviceCaps(Handle, LOGPIXELSX); // HORIZONTAL
      myLOGPIXELSY        := GetDeviceCaps(Handle, LOGPIXELSY); // VERTICAL
      myHORZRES           := GetDeviceCaps(Handle, HORZRES);
      myVERTRES           := GetDeviceCaps(Handle, VERTRES);
      myPHYSICALOFFSETX   := GetDeviceCaps(Handle, PHYSICALOFFSETX);
      myPHYSICALOFFSETY   := GetDeviceCaps(Handle, PHYSICALOFFSETY);
      myPHYSICALWIDTH     := GetDeviceCaps(Handle, PHYSICALWIDTH);
      myPHYSICALHEIGHT    := GetDeviceCaps(Handle, PHYSICALHEIGHT);
      printorigin.x       := myPHYSICALOFFSETX;
      printorigin.Y       := myPHYSICALOFFSETY;
      PhysicalPage.Left   := 0;
      PhysicalPage.Right  := myPHYSICALWIDTH;
      PhysicalPage.Top    := 0;
      PhysicalPage.Bottom := myPHYSICALHEIGHT;
    end;
end;

procedure DoNewPage;
begin
  if myToPrint then
    Printer.NewPage
  else
    // assume it is a PDF since that is the only other option right now.
    lPage := lPdf.AddPage;
end;


procedure DoBeginDoc;
begin
  if myToPrint then
    Printer.BeginDoc
  else
    // assume it is a PDF since that is the only other option right now.
    // synPDF does not use BeginDoc
      ;
end;


procedure DoEndDoc;
var
  S : string;
begin
  if myToPrint then
    Printer.EndDoc
  else
    // assume it is a PDF since that is the only other option right now.
    begin
      S := 'c:\Users\John\desktop\synPDF.PDF';
      lPdf.SaveToFile(S);
      ShellExecute(Application.Handle, nil, PChar(S), nil, nil, SW_SHOWNORMAL);
    end;
end;


procedure DoAbort;
begin
  if myToPrint then
    Printer.Abort
  else
    // assume it is a PDF since that is the only other option right now.
      ;
end;

procedure start;
begin
    lPdf := TPdfDocumentGDI.Create;
    try
      if LaserPrinter then
        begin
          GetPrinterParameters(Printer.Canvas);
          aCanvas := Printer.Canvas;
        end
      else
        begin
          lPdf.DefaultPaperSize := psA4;        // setup for standard 8.5x11 Letter
          lPdf.ScreenLogPixels  := 600;         // emulate a printer with 600 DPI
          lPage := lPdf.AddPage;                // add first page here instead of in DoBeginDoc
          GetPrinterParameters(lPfd.VCLcanvas); // get all the page metrics in DPI 
          aCanvas := lPdf.VCLCanvas;
        end;
      DoBeginDoc;  // open printer/pdf
      try
        { rest of code references aCanvas to make it work for both printers and synPDF }
        DoNewPage;  // Add more pages
        DoEndDoc;   // finish and close output
      except
        DoAbort;
      end;
    finally
      lPdf.free;
    end;
end;   

Offline

#2 2014-03-13 16:57:45

john.friel
Member
From: Waterloo, IA
Registered: 2014-03-13
Posts: 3
Website

Re: Problems with GetDeviceCaps on synPDF canvas

Still trying to figure out what it is doing, so I thought I would "lie" to the rest of my code and substitute the values I expected, like this:

procedure GetPrinterParameters(aCanvas : TCanvas);
begin
  if myToPrint then
    begin
      with aCanvas do
        begin
          myLOGPIXELSX      := GetDeviceCaps(Handle, LOGPIXELSX); // HORIZONTAL
          myLOGPIXELSY      := GetDeviceCaps(Handle, LOGPIXELSY); // VERTICAL
          myHORZRES         := GetDeviceCaps(Handle, HORZRES);
          myVERTRES         := GetDeviceCaps(Handle, VERTRES);
          myPHYSICALOFFSETX := GetDeviceCaps(Handle, PHYSICALOFFSETX);
          myPHYSICALOFFSETY := GetDeviceCaps(Handle, PHYSICALOFFSETY);
          myPHYSICALWIDTH   := GetDeviceCaps(Handle, PHYSICALWIDTH);
          myPHYSICALHEIGHT  := GetDeviceCaps(Handle, PHYSICALHEIGHT);
        end;
    end
  else
    begin
      myLOGPIXELSX      := 600;
      myLOGPIXELSY      := 600;
      myHORZRES         := 5100;
      myVERTRES         := 6600;
      myPHYSICALOFFSETX := 0;
      myPHYSICALOFFSETY := 0;
      myPHYSICALWIDTH   := 5100;
      myPHYSICALHEIGHT  := 6600;
    end;
  printorigin.x       := myPHYSICALOFFSETX;
  printorigin.Y       := myPHYSICALOFFSETY;
  PhysicalPage.Left   := 0;
  PhysicalPage.Right  := myPHYSICALWIDTH;
  PhysicalPage.Top    := 0;
  PhysicalPage.Bottom := myPHYSICALHEIGHT;
end;

Running this rewarded me with this PDF file

What I found interesting:
   1. That the green border should have been the outline of the entire page.  This is drawn with the code and should have been the outline of the PDF page.  if this was sent to a laser, it would have been the entire printable area of the page.  As you can see, the left and top are correct, but the bottom appears that the VCLcanvas thinks the page is taller than normal A4 paper.

  aRect.Left   := 0;
  aRect.Top    := 0;
  aRect.Right  := myHORZRES;
  aRect.Bottom := myVERTRES;
  if debughook <> 0 then
    DrawRectangle(aCanvas, aRect, clWebForestGreen, RectPenWidth);

     
  2. I command a .5 inch margin from the physical page dimensions using the printable dimensions.  The code that computes this is:

procedure CalcRects;
var
  max : Integer;
begin
  { Figure textrect in paper coordinates }
  myTextRect.Left := Round(LeftMargin * myLOGPIXELSX);
  if myTextRect.Left < printorigin.x then
    myTextRect.Left := printorigin.x;

  myTextRect.Top := Round(TopMargin * myLOGPIXELSY);
  if myTextRect.Top < printorigin.Y then
    myTextRect.Top := printorigin.Y;

      { Printer.PageWidth and PageHeight return the size of the
        printable area, we need to add the printorigin to get the
        edge of the printable area in paper coordinates. }

  myTextRect.Right := PhysicalPage.Right - Round(RightMargin * myLOGPIXELSX);
  max              := myHORZRES + printorigin.x;
  if myTextRect.Right > max then
    myTextRect.Right := max;

  myTextRect.Bottom := PhysicalPage.Bottom - Round(BottomMargin * myLOGPIXELSY);
  max               := myVERTRES + printorigin.Y;
  if myTextRect.Bottom > max then
    myTextRect.Bottom := max;

  { Validate the margins. }
  if (myTextRect.Left >= myTextRect.Right) or (myTextRect.Top >= myTextRect.Bottom) then
    raise EPrinter.Create('PrintString: the supplied margins are too large, there' +
        ' is no area to print left on the page.');

  { Convert myTextrect to canvas coordinates. }
  OffsetRect(myTextRect, -printorigin.x, -printorigin.Y);
end; { CalcRects }

   With this, myTextRect is the TRect area that I can print in that has a .5 inch margin from the physical page taking into account the printable area.  I then draw a RED rectangle using this as a ruler so I can tell if I've written anything to the canvas in the wrong way, by checking to see nothing goes outside this boundary.  This code is:

  // outline the printable area inside margins.
  if debughook <> 0 then
    DrawRectangle(aCanvas, myTextRect, clRed, RectPenWidth);

  You can see that the portions of the red rectangle that are still visible at the top and left are in the correct place and have a .5 in margin. 
 
  3. From 1 and 2 above, I can see that the right and bottom of my outlines are not .5 inches from the borders, but they are close.  So, lying was almost what should have been generated from the GetDeviceCaps calls, but not quite.  Where it really goes crazy is the size of the text output, in that it is microscopic compared to the rest of the document.  The logo was placed where it should have been, and using the DPI I lied about it is the correct size and in the correct place.  The outlines are also (mostly) in their correct places and dimensionally correct.  This tells me all the the TRect's that I create to generate the DrawRectangles are being done correctly.  So why are the fonts all so small?  It is like they were assumed to use the 96 DPI that GetDeviceCaps returned when it should have used 600 DPI.  That would account for the percentage of them being so small.  I use the following code to put the text on the page:

      // Page Number
  aText             := 'PAGE';
  PageNoRect.Top    := myTextRect.Top + Round(1.35 * myLOGPIXELSY);
  PageNoRect.Left   := StubTopRect.Left - Round(1.5 * myLOGPIXELSX);
  PageNoRect.Right  := StubTopRect.Left - Round(0.09375 * myLOGPIXELSX);
  PageNoRect.Bottom := PageNoRect.Top + Round(0.25 * myLOGPIXELSY);

  vFormat := [tfLeft, tfVerticalCenter, tfSingleLine];
  with aCanvas do
    begin
      Font.Name  := 'Segeo UI';
      Font.Size  := 8;
      Font.Style := [];
      Font.Color := OutlineColor;
      aRect      := PageNoRect;
      aCanvas.textRect(aRect, aText, vFormat);
    end;

  While this is not exactly like this everywhere, I do create TRect's and then use Canvas.textRect to put the text on the canvas. 

I hope this helps shed light on what I may be doing wrong.

Thank you in advance.

john

Offline

#3 2014-03-18 12:41:04

john.friel
Member
From: Waterloo, IA
Registered: 2014-03-13
Posts: 3
Website

Re: Problems with GetDeviceCaps on synPDF canvas

I am getting closer.

Here is a comparison between the doPDF output (300dpi, on the left) and SynPDF (92dpi, on the right):   Link to image

A couple things since my last post.. 

1. I had incorrectly specified psA4 as the form instead of psLetter.  This cleaned up the issue I had with the pages looking too tall and not wide enough.  Never too old to learn something new.  haha.

2. To get the text to display, I had to use the default 96 DPI that was returned from the GetDeviceCaps(Handle, LOGPIXELSX) call.  I still had to lie about the other parameters to get it to match the psLetter format, and here's the code I used for that:

      with aCanvas do
        begin
          myLOGPIXELSX      := GetDeviceCaps(Handle, LOGPIXELSX); // HORIZONTAL dpi
          myLOGPIXELSY      := GetDeviceCaps(Handle, LOGPIXELSY); // VERTICAL dpi
          myHORZRES         := Round(myLOGPIXELSX * 8.5);         // Printable width in pixels
          myVERTRES         := Round(myLOGPIXELSY * 11.0);        // printable height in pixels
          myPHYSICALOFFSETX := 0;                                 // starting point from left
          myPHYSICALOFFSETY := 0;                                 // starting point from top
          myPHYSICALWIDTH   := myHORZRES;                         // physical page width
          myPHYSICALHEIGHT  := myVERTRES;                         // physical page height
        end;

I finally made the connection to why I was getting 96 DPI, 2560 for HORZRES and 1440 for VERTRES, those are the dimensions of my primary screen.  Digging through the code I see a call to CreateCompatibleDC(0) that is using my display as the source to create the canvas.  Once I realized this, the above code then uses the 96 DPI and calculates what the page should be for the GetDeviceCaps calls that fail.  It would be nice if this could reference the default Printer to get the source dimensions from so we could use 300 dpi or 600 dpi like the left picture uses.

Now, here is the current list of issues that I do not know how to fix:

1. Fonts.  I use the "Segoe UI", "Consolas" and "Wingdings 3" fonts.  Of these, only Consolas seems to be picked up, or at least it *looks* like Consolas.  The Segoe UI font seems to have been substituted for something else entirely.  Also, if you look at the special characters I printed in the upper right side where there should be a downward pointing arrow and a checkmark below it, that is the characters from the Wingdings 3 font. 

2. Spacing?  I cannot be sure if it is the substituted font or that there is a bug in the spacing of text, but everywhere I've printed something it appears to be padded more than what it should be. I guess until I figure out how to get it to use my missing fonts I'll just have to wait to test this.

3. In the detail lines of the statement, it appears that the top 5 rows where the Inv./RGA# is only 5 digits that it is using the wrong size font.  This is generated in a loop so either they should all be the wrong font or they should all be correct like most of the other columns.  In the Reference column, all of them appear to be too small except the single row near the bottom of the list (circled). 

4. The borders of the detail header row, all but the right-most rectangle have too small of a top, left pen and the right sides are missing entirely.  This happened in both the left detail header and the right detail header.  I have no clue what went wrong there, but again, the same code that made one rectangle made all of them.

I sure would like to try using 600 DPI again, but I am sure there is a scaling issue when using TextRect and it causes the fonts to be extremely tiny by a factor of 0.16% of what they should be (96 / 600).  I was not able to trace through the code to understand where this scaling would need to be applied.

Looking forward to your comments and suggestions.

John

Offline

Board footer

Powered by FluxBB