Page 1 of 1

DrawPageAt to bitmap, metafile

Posted: Mon Aug 25, 2014 8:58 am
by Lucian
Hi Sergey,

I am trying to print preview. Our reporting engine works like this. It uses and informational device context (CreateIC) compatible with the default printer. Than all output methods are overridden in such way, that the actual "output" happens in a memory stream. So the DC is only used for measuring draw items.

To connect the engine with TRichView control, I decided to output the content into a bitmap (I also tried with a metafile) than, I would "Draw" the bitmap with our reporting engine. Thus, I should get an image of the richview at the coordinates computed in the printing engine. However the image is not coming out at all. To debug, intermediary I printed the image to some external file (bmp, emf) and I found out that the text is very very small

I do not know what I am doing wrong. This is how I implemented it:

I create a TRVReportHelper object, load its RichView from a database blob (LoadRVFFromStream):
- call Helper.Init(SomeCanvas, DocWidth), somecanvas is either a tbitmap.canvas or a TMetafileCanvas;
- call Helper.FormatNextPage(DocHeight)
- call Helper.DrawPageAt(0, 0, PageNo, somecanvas, True, DocHeight);

Any idea what I may be doing wrong. For example I understand that in DrawPageAt I should use true for Preview parameter and also that the Canvas should be a "true" output Canvas (like RV_GetPrinterDC or for a bitmap, TBitmap.Canvas)...?

Thx

Posted: Mon Aug 25, 2014 10:55 am
by Sergey Tkachenko
Make sure that Canvas.Font.PixelsPerInch is set correctly to GetDeviceCaps(Canvas.Handle, LOGPIXELSY)

Posted: Mon Aug 25, 2014 2:31 pm
by Lucian
All right, it started to work. Now I have another problem. For me, FormatNextPage always returns true, even though the full content for the richview fits on the first page. Here is a piece from how I coded it (you'll the WHILE loop should break when FormatNextPage would return false, however it never happens for me):

while TRUE do
begin
if fPrinter.LinesLeft < 6 then
fPrinter.NewPage;
Inc(PageNo);

DocTop := fPrinter.CursorYPos;
DocHeight := DocBottom - DocTop;

G := PrintWithMeta(fPrinter.Canvas.Handle);
if Assigned(G) then
try
ImageBounds := Rect(DocLeft, DocTop, DocLeft+DocWidth, DocTop+lRVHelper.EndAt);
fPrinter.StretchDraw(ImageBounds, G);
NewPos := fPrinter.XD2U(lRVHelper.EndAt);
fPrinter.YPos := fPrinter.YPos + NewPos;
finally
FreeAndNil(G);
end
else
BREAK;
end;

function PrintWithMeta(DC: HDC): TBitmap;
var
M: TMetafile;
MC: TMetafileCanvas;
begin
Result := nil;
M := TMetafile.Create;
try
M.Height := DocHeight;
M.Width := DocWidth;

MC := TMetafileCanvas.Create(M, DC);
try
MC.Font.PixelsPerInch := GetDeviceCaps(DC, LOGPIXELSY);

lRVHelper.Init(MC, DocWidth);
if not lRVHelper.FormatNextPage(DocHeight) then
EXIT
else
lRVHelper.DrawPageAt(0, 0, PageNo, MC, True, DocHeight);
finally
MC.Free;
end;
Result := GetImage(M);
finally
M.Free;
end;
end;

function GetImage(Drawing: TGraphic): TBitmap;
begin
Result := TBitmap.Create;
Result.Width := DocWidth;
Result.Height := lRVHelper.EndAt;
Result.Canvas.Draw(0, 0, Drawing);
//Result.SaveToFile('p:\test.bmp');
end;

forget about last question

Posted: Mon Aug 25, 2014 2:38 pm
by Lucian
I think I got it. The Init call must happen only once, I call it for page one, but than since I "print" to TMetafileCanvas I must update PrinterCanvas property, new code something like:

TReportRVData(TReportRichView(lRVHelper.RichView).RVData).PrinterCanvas := MC;

Pretty ugly looks this :-), do you have a nicer suggestion Sergey?

Thx,
Lucian

Posted: Mon Aug 25, 2014 3:41 pm
by Sergey Tkachenko
It's not necessary to reassign canvas, but you must not destroy this canvas until you finish working with document formatted by the last call of Init.

Posted: Mon Aug 25, 2014 6:14 pm
by Lucian
I have to reassign it. After I the output of the richview, there are other things to be printed and there may be a NewPage happening, different height available for the richview, a different metafile, different canvas... no?

Posted: Mon Aug 25, 2014 7:55 pm
by Sergey Tkachenko
The canvas passed in Init is not necessary the same canvas as used for DrawPage. They should only be compatible.

For example, in the demo Demos\DelphiUnicode\Assorted\ToImage\, Init is called for the form's Canvas, while DrawPage is called for a metafile or a bitmap canvas.

It started to work

Posted: Tue Aug 26, 2014 7:48 am
by Lucian
His Sergey,

It works for small data. However I have a 2-3 pages piece which doesn't work correctly. It only shows last page with my code. On the first page, the data should start at about 8 cm from the top of the page, than, the eventual other pages should start at about 3.5 cm. For whatever reason, the TRVReportHelper doesn't want to start with the beginning of the data (not sure how to explain). My code is something like this.

function PrintWithMeta(DC: HDC): TGraphic;
var
M: TMetafile;
MC: TMetafileCanvas;
begin
Result := nil;
M := TMetafile.Create;
try
M.Height := DocHeight; // 4786
M.Width := DocWidth; // 3922

MC := TMetafileCanvas.Create(M, DC);
try
MC.Font.PixelsPerInch := GetDeviceCaps(DC, LOGPIXELSY); // 600

if PageNo = 1 then
lRVHelper.Init(MC, DocWidth);

if not lRVHelper.FormatNextPage(DocHeight) then
EXIT
else
lRVHelper.DrawPageAt(0, 0, PageNo, MC, True, DocHeight);

(*
the problem seems to be that after first run of this method,
lRVHelper.EndAt is still 0, as if nothing fits
*)
finally
MC.Free;
end;

// use another metafile with correct height
Result := TMetafile.Create;
Result.Height := lRVHelper.EndAt;
Result.Width := DocWidth;
MC := TMetafileCanvas.Create(TMetafile(Result), DC);
try
MC.Draw(0, 0, M);
finally
MC.Free;
end;

finally
M.Free;
end;
end;

Any ideas?
Thx
Lucian

Posted: Tue Aug 26, 2014 9:11 am
by Lucian
I got it to work. It's all fine if I don't rely on "EndAt". If I use the Height that I know, it's ok.

So I don't know why after a successful call to FormatNextPage, the EndAt call returns 0. Is it a bug?

Posted: Tue Aug 26, 2014 12:00 pm
by Sergey Tkachenko
EndAt can be used only for the last page, when all FormatNextPage are called.
It's better to use GetLastPageHeight instead of EndAt, it includes all necessary margins and indents.