You are not logged in.
Pages: 1
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
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
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
Pages: 1