#1 Re: PDF Engine » Code contribution for EMF Record EMR_FILLRGN » 2013-04-22 15:40:47

Hi Ab,

Your more than welcome I'm glad I can give something back. I do use the version from code.google.com although I'm using version 11.2 not 11.4 which is the latest version.

I've have opened a ticket on that project asking for two code changes if possible. The ticket number is 240. In that I mention this project and why I'm asking for the changes. If they make them then you don't need to rely on TMetaFilePrinter which is part of that project any more.

For those that want the modification to that project this is what I did:

File: vwPrinter.pas

Make all of the private variable protected i.e

  ThtPrinter = class(TComponent)
  private
    FOffsetX: Integer;      // Physical Printable Area x margin
    FOffsetY: Integer;      // Physical Printable Area y margin
    FPaperHeight: Integer;  // Physical Height in device units
    FPaperWidth: Integer;   // Physical Width in device units
    FPgHeight: Integer;     // Vertical height in pixels
    FPgWidth: Integer;      // Horizontal width in pixels
    FPPIX: Integer;         // Logical pixels per inch in X
    FPPIY: Integer;         // Logical pixels per inch in Y
    FPrinting: Boolean;
    FTitle: ThtString;      // Printed Document's Title
  protected
    function GetCanvas: TCanvas; virtual; abstract;
    function GetPageNum: Integer; virtual; abstract;
    procedure CheckPrinting(Value: Boolean);
    procedure GetPrinterCapsOf(Printer: TPrinter);
    procedure SetPrinting(Value: Boolean);
  public
    procedure BeginDoc; virtual; abstract;
    procedure NewPage; virtual; abstract;
    procedure EndDoc; virtual; abstract;
    procedure Abort; virtual; abstract;
    procedure Assign(Source: TPersistent); override;
    property Canvas: TCanvas read GetCanvas;
    property OffsetX: Integer read FOffsetX;
    property OffsetY: Integer read FOffsetY;
    property PageNumber: Integer read GetPageNum;
    property PageHeight: Integer read FPgHeight;
    property PageWidth: Integer read FPgWidth;
    property PaperHeight: Integer read FPaperHeight;
    property PaperWidth: Integer read FPaperWidth;
    property PixelsPerInchX: Integer read FPPIX;
    property PixelsPerInchY: Integer read FPPIY;
    property Printing: Boolean read FPrinting; // becomes True in BeginDoc and back to False in EndDoc.
    property Title: ThtString read FTitle write FTitle;
  end;

becomes

  ThtPrinter = class(TComponent)
  protected
    FOffsetX: Integer;      // Physical Printable Area x margin
    FOffsetY: Integer;      // Physical Printable Area y margin
    FPaperHeight: Integer;  // Physical Height in device units
    FPaperWidth: Integer;   // Physical Width in device units
    FPgHeight: Integer;     // Vertical height in pixels
    FPgWidth: Integer;      // Horizontal width in pixels
    FPPIX: Integer;         // Logical pixels per inch in X
    FPPIY: Integer;         // Logical pixels per inch in Y
    FPrinting: Boolean;
    FTitle: ThtString;      // Printed Document's Title
    function GetCanvas: TCanvas; virtual; abstract;
    function GetPageNum: Integer; virtual; abstract;
    procedure CheckPrinting(Value: Boolean);
    procedure GetPrinterCapsOf(Printer: TPrinter);
    procedure SetPrinting(Value: Boolean);
  public
    procedure BeginDoc; virtual; abstract;
    procedure NewPage; virtual; abstract;
    procedure EndDoc; virtual; abstract;
    procedure Abort; virtual; abstract;
    procedure Assign(Source: TPersistent); override;
    property Canvas: TCanvas read GetCanvas;
    property OffsetX: Integer read FOffsetX;
    property OffsetY: Integer read FOffsetY;
    property PageNumber: Integer read GetPageNum;
    property PageHeight: Integer read FPgHeight;
    property PageWidth: Integer read FPgWidth;
    property PaperHeight: Integer read FPaperHeight;
    property PaperWidth: Integer read FPaperWidth;
    property PixelsPerInchX: Integer read FPPIX;
    property PixelsPerInchY: Integer read FPPIY;
    property Printing: Boolean read FPrinting; // becomes True in BeginDoc and back to False in EndDoc.
    property Title: ThtString read FTitle write FTitle;
  end;

File: htmlview.pas

Search for the line:

function THtmlViewer.Print(Prn: ThtPrinter; FromPage: Integer; ToPage: Integer; Mode: ThtPrintPreviewMode): Integer;

Blow this you will find a case statement which reads:

  case Mode of
    ppAuto:
      if not (Prn is TMetaFilePrinter) then
        Mode := ppMultiPrint
      else
        Mode := ppPreview;

    ppPreview:
      if not (Prn is TMetaFilePrinter) then
        raise EIllegalArgument.CreateFmt('Previewing a print requires a printer based on TMetaFilePrinter but not a %s', [Prn.ClassName]);

    ppNoOutput:
      if not (Prn is TMetaFilePrinter) then
        raise EIllegalArgument.CreateFmt('Getting the total number of pages to print requires a printer based on TMetaFilePrinter but not a %s', [Prn.ClassName]);
  end;

Change it to

  case Mode of
    ppAuto:
      if not (Prn is ThtPrinter) then
        Mode := ppMultiPrint
      else
        Mode := ppPreview;

    ppPreview:
      if not (Prn is ThtPrinter) then
        raise EIllegalArgument.CreateFmt('Previewing a print requires a printer based on TMetaFilePrinter but not a %s', [Prn.ClassName]);

    ppNoOutput:
      if not (Prn is ThtPrinter) then
        raise EIllegalArgument.CreateFmt('Getting the total number of pages to print requires a printer based on TMetaFilePrinter but not a %s', [Prn.ClassName]);
  end;

This will allow anything derived from ThtPrinter to be used in this function.


I hope this helps people who need it.

The company I work for use HTMLView and SynPDF a lot on the projects we write for customers. As I come across issues and am able to fix them I'll post the code back. This is the first time I've been able to contribute something useful back to an open source project big_smile


Take care,
Ryan

#2 PDF Engine » Code contribution for EMF Record EMR_FILLRGN » 2013-04-22 12:55:04

RyanC
Replies: 2

Hi All,

It has been a while since I posted but wanted to contribute some code back. I have been using SynPDF with THTMLView for a while but hit an issue with tables and HTML header and footers. First HTMLView table borders, when using SynPDF 1.18 I didn't get any standard table borders. Having checked the EMF output from HTMLView I discovered the borders are drawn using EMR_FILLRGN. Here is the code to add to EnumEMFFunc to deal with this EMF record:

  EMR_FILLRGN: begin
    // Code Copied from EMR_SELECTOBJECT
    if integer(PEMRFillRgn(R)^.ihBrush)<0 then begin // stock object?
      num := PEMRFillRgn(R)^.ihBrush and $7fffffff;
      case num of
        NULL_BRUSH:
          brush.null := true;
        WHITE_BRUSH..BLACK_BRUSH: begin
          brush.color := STOCKBRUSHCOLOR[num];
          brush.null := false;
        end;
        NULL_PEN: begin
          pen.style := PS_NULL;
          pen.null := true;
        end;
        WHITE_PEN, BLACK_PEN: begin
          pen.color := STOCKPENCOLOR[num];
          pen.null := false;
        end;
      end;
    end else
    if PEMRFillRgn(R)^.ihBrush-1<cardinal(length(E.Obj)) then // avoid GPF
      with E.Obj[PEMRFillRgn(R)^.ihBrush-1] do
      case Kind of // ignore any invalid reference
        OBJ_PEN: begin
          if E.fInLined and
            ((pen.color<>PenColor) or (pen.width<>PenWidth) or
             (pen.style<>PenStyle)) then begin
            E.fInLined := False;
            if not pen.null then
              E.Canvas.Stroke;
          end;
          pen.null := (PenWidth<0) or (PenStyle = PS_NULL); // !! 0 means as thick as possible
          pen.color := PenColor;
          pen.width := PenWidth;
          pen.style := PenStyle;
        end;
        OBJ_BRUSH: begin
          brush.null := BrushNull;
          brush.color := BrushColor;
          brush.style := BrushStyle;
        end;
        OBJ_FONT: begin
          font.spec := FontSpec;
          move(LogFont,font.LogFont,sizeof(LogFont));
        end;
      end;
    // New code to fill the region
    E.FillRectangle(PRgnDataHeader(@PEMRFillRgn(R)^.RgnData[0])^.rcBound);
  end;

The other issues I has was with converting the HTML to a PDF with HTML headers and footers. The sample code reference on this site, and others, make use of the THTMLView function MakePagedMetaFiles. Unfortunately this function doesn't support HTML headers and footers. To get around this I created my own Printer class which was derived from the ThtPrinter base class. With a small modification to HTMLView you can then pass this printer class into the standard Print function and get a PDF document with HTML headers and footers inserted correctly.

uses Forms, SysUtils, Classes, Graphics, vwPrint, SynPDF;

type
  TPDFPrinter = class(ThtPrinter)
  protected
    PDFDocGDI : TPdfDocumentGDI;
    function GetCanvas: TCanvas; override;
    function GetPageNum: integer; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    // Printer Methods
    procedure BeginDoc; override;
    procedure NewPage; override;
    procedure EndDoc; override;
    procedure Abort; override;
  end;

implementation

{ TPDFPrinter }

procedure TPDFPrinter.Abort;
begin
  Self.SetPrinting(False);
end;

procedure TPDFPrinter.BeginDoc;
var
  NewCanvas : TCanvas;
begin
  Self.SetPrinting(True);

  Self.PDFDocGDI.NewDoc;

  Self.PDFDocGDI.AddPage;

  NewCanvas := Self.GetCanvas;

  // This code replaces GetPrinterCapsOf
  FPPIX := Self.PDFDocGDI.ScreenLogPixels;
  FPPIY := Self.PDFDocGDI.ScreenLogPixels;
  FPaperWidth := Self.PDFDocGDI.VCLCanvasSize.cx;
  FPaperHeight := Self.PDFDocGDI.VCLCanvasSize.cy;
  FOffsetX := 0;
  FOffsetY := 0;
  FPgWidth := Self.PDFDocGDI.VCLCanvasSize.cx;
  FPgHeight := Self.PDFDocGDI.VCLCanvasSize.cy;
end;

constructor TPDFPrinter.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  PDFDocGDI := TPdfDocumentGDI.Create;
end;

destructor TPDFPrinter.Destroy;
begin
  FreeAndNil(PDFDocGDI);
  inherited;
end;

procedure TPDFPrinter.EndDoc;
begin
  Self.SetPrinting(False);
end;

function TPDFPrinter.GetCanvas: TCanvas;
begin
  Result := Self.PDFDocGDI.VCLCanvas;
end;

function TPDFPrinter.GetPageNum: integer;
begin
  Result := Self.PDFDocGDI.RawPages.Count;
end;

procedure TPDFPrinter.NewPage;
var
  NewCanvas : TCanvas;
begin
  Self.PDFDocGDI.AddPage;

  NewCanvas := Self.GetCanvas;
  NewCanvas.Brush.Color := clWhite;
  NewCanvas.Pen.Color := clWhite;
  NewCanvas.Brush.Style := bsSolid;
  NewCanvas.Rectangle(0, 0, Self.PDFDocGDI.DefaultPageWidth, Self.PDFDocGDI.DefaultPageHeight);

  NewCanvas.Font.PixelsPerInch := Screen.PixelsPerInch;
  NewCanvas.Font.Name := 'Arial';
  NewCanvas.Font.Size := 10;
  NewCanvas.Brush.Style := bsClear;
end;

Then all you do is call HTMLPrinter.Print(PDFPrinter, 1, HTMLPrinter.NumPrinterPages, ppPreview); to generate your PDF

EMF processing is all new to me so please update my code as you see fit.

Take care,
Ryan

#3 Re: PDF Engine » My metafile renders a blank PDF page. » 2013-01-18 09:09:51

Hi Cohiba,

I don't know it anybody has been able to help you out. I've just made a post about connect the DevExpress printing system and SynPDF together. You can see the details here http://synopse.info/forum/viewtopic.php?id=1036. I hope it helps you out.

Take care,
Ryan

#4 PDF Engine » DevExpress Printing System to SynPDF » 2013-01-18 09:05:24

RyanC
Replies: 1

Hi All,

I have had the need to combine several jobs to produce a document pack from our application. This application takes data from both HTMLView and the DevExpress ExpressQuantumGrid Suite / DevExpress Printing system. I've been looking for a long time to find the best way to do this and I have now found it. Html2Pdf covers getting the data from HTML View. I hope to demonstrate one way of getting the data from the DevExpress printing system.

First the print code for DevExpress. The key here is to use the function EnumPagesAsImages. The first parameter is an array of integers indicating the pages you want to convert to an image. To keep things simple I do one page at a time. The second parameter determines the image type to use. As the SynPDF system works with well metafiles that is the graphic type I have used. The third determines if the background should be painted. The forth parameter is a call back function to receive the rendered image. Parameters 5, 6 & 7 are data pointers which I don't use.

var
   PageCount: Integer;
begin
  // Other printing preparation code goes here

  // Force the printout to regenerate
  Self.dxGridLink.RebuildReport;
  // Copy each page to the PDF document
  for PageCount := 0 To dxComponentPrinter1.GetPageCount - 1 do begin
    dxComponentPrinter1.EnumPagesAsImages([PageCount], TMetaFile, False, PDFPrinter.AppendDevExpressGrid, nil, nil, nil);
  end;

Now the PDF document class. This just simple code so far. I hope to improve it to scale the metafile so I can add my own borders and additional headers and footers.

type
  TDevExpress2SynPDF = class(TPdfDocumentGDI)
  private
  public
    constructor Create(AUseOutlines: Boolean=false; ACodePage: integer=0; APDFA1: boolean=false);
    destructor Destroy; override;
    procedure AppendDevExpressGrid(AComponentPrinter: TCustomdxComponentPrinter; AReportLink: TBasedxReportLink; 
                                   AIndex, APageIndex: Integer; const AGraphic: TGraphic; 
                                   AData: Pointer; var AContinue: Boolean);
  end;

implementation

uses SynGdiPlus;

procedure TDevExpress2SynPDF.AppendDevExpressGrid(
  AComponentPrinter: TCustomdxComponentPrinter; AReportLink: TBasedxReportLink;
  AIndex, APageIndex: Integer; const AGraphic: TGraphic; AData: Pointer;
  var AContinue: Boolean);
var
  Emf     : TMetafile;
  R       : TRect;
  PdfPage : TPdfpage;
begin
  if AGraphic is TMetaFile then begin
    Emf := AGraphic as TMetaFile;

    // Make this page landscape
    PdfPage := Self.AddPage;
    PdfPage.PageWidth := Self.DefaultPageHeight;
    PdfPage.PageHeight := Self.DefaultPageWidth;

    R.Left := 0;
    R.Top := 0;
    R.Bottom := Emf.Height;
    R.Width := Emf.Width;
    Gdip.DrawAntiAliased(Emf, Self.VCLCanvas.Handle, R, smAntiAlias, trhClearTypeGridFit);
  end;
end;

constructor TDevExpress2SynPDF.Create(AUseOutlines: Boolean; ACodePage: integer;
  APDFA1: boolean);
begin
  inherited Create(AUseOutlines, ACodePage, APDFA1);

  Gdip := TGDIPlusFull.Create;
end;

destructor TDevExpress2SynPDF.Destroy;
begin
  FreeAndNil(Gdip);
  inherited;
end;

I use the Gdip.DrawAntiAliased as the normal metafile SynPDF functions produce either a black and white image with some backgrounds and lines missing or the PDF file is blank.

I hope this code helps somebody out but please post comments, suggestions or improvements to my code.

Take care,
Ryan

#5 Re: PDF Engine » Metafile comes out black and white or blank. » 2013-01-18 08:40:24

Hi All,

Just following up on my post from yesterday. I've been able to render the metafile to PDF using the following code:

const
  OutputFileName = 'schedule.pdf';
  InputFileName = 'schedule.emf';
var
  PdfDoc   : TPDFDocumentGDI;
  PdfPage : TPdfpage;
  Emf       : TMetafile;
  R          : TRect;
begin
  Emf := TMetafile.Create();
  Emf.LoadFromFile(InputFileName);

  Gdip := TGDIPlusFull.Create;

  PdfDoc := TPdfDocumentGDI.Create;
  PdfDoc.CompressionMethod := cmFlateDecode;
  PdfDoc.DefaultPaperSize := psA4;
  PdfDoc.DefaultPageLandscape := True;
  PdfPage := PdfDoc.AddPage();

  R.Left := 0;
  R.Top := 0;
  R.Bottom := Emf.Height;
  R.Width := Emf.Width;
  Gdip.DrawAntiAliased(Emf, PdfDoc.VCLCanvas.Handle, R, smAntiAlias, trhClearTypeGridFit);

  PdfDoc.SaveToFile(OutputFileName);
  PdfDoc.Free();

  FreeAndNil(Emf);
end.

This produces a complete colour copy of the PDF document. The metafile has come from the DevExpress printing system. I'll post some code in another thread showing how to get the output from this into your PDF document.

Take care,
Ryan

#6 Re: PDF Engine » Metafile comes out black and white or blank. » 2013-01-17 16:44:42

Apologies for that it looks like our server won't accept EMF files. I've zipped it up and it can be downloaded from:

http://www.lmp.co.uk/files/sample.zip

#7 PDF Engine » Metafile comes out black and white or blank. » 2013-01-17 16:25:12

RyanC
Replies: 3

Hi All,

I've come across this library and it is doing everything I'm asking of it so far. I've hit a small snag though. This metafile http://www.lmp.co.uk/files/schedule.emf is causing me some headaches. I create a small test program for this metafile and I'm confused with the output.

const
  OutputFileName = 'schedule.pdf';
  InputFileName = 'schedule.emf';
var
  PdfDoc  : TPDFDocumentGDI;
  PdfPage : TPdfpage;
  Emf     : TMetafile;
begin
  Emf := TMetafile.Create();
  Emf.LoadFromFile(InputFileName);

  PdfDoc := TPdfDocumentGDI.Create;
  PdfDoc.CompressionMethod := cmFlateDecode;
  PdfDoc.DefaultPaperSize := psA4;
  PdfDoc.DefaultPageLandscape := True;
  PdfPage := PdfDoc.AddPage();
  //PdfDoc.VCLCanvas.Draw(0, 0, Emf);         // This gives black and white output only
  //PdfDoc.Canvas.RenderMetaFile(Emf, 1);    // This gives a blank PDF

  PdfDoc.SaveToFile(OutputFileName);
  PdfDoc.Free();

  FreeAndNil(Emf);
end.

When run with PdfDoc.VCLCanvas.Draw uncommented I get a black and white output. Some lines are also missing. When PdfDoc.Canvas.RenderMetaFile is uncommented I get a blank PDF. Am I missing something obvious?

Looking though the forum somebody recommended looking at EMFExplorer to see if the meta file had some odd coding in it. I've done this and as long as I switch off GDI+ rendering then is shows OK. Look through the EMF data it appears all calls are supported in the PDF engine except EMR_SETROP2. I'm just trying to work out what this does and implement it if necessary .... although this isn't my area of expertise and might take some time smile

Many thanks for any help.
Ryan

Board footer

Powered by FluxBB