synopse / mORMot2

OpenSource RESTful ORM/SOA/MVC Framework for Delphi and FreePascal
https://synopse.info
Other
485 stars 122 forks source link

Feature request: Create embedded file for PDF #264

Closed tueddy closed 1 week ago

tueddy commented 1 week ago

Hi,

first thank's for the great library! Just updated from SynPdf to mormot.ui.pdf without any issues!

I want to generate a PDF invoice conforming german ZugFERD / XRechnung standard. So i have to attach XML document to PDF/A file. It's currently not possible so i have created a descandant class with this new method:

type
  TPdfDocumentEx = class(TPdfDocumentGDI)
  public
    //: add a file attachment
    function CreateFileAttachment(const AFileName: String; const ADescription: String = ''): TPdfDictionary; overload;
    function CreateFileAttachment(const AMStream: TMemoryStream; const ATitle, ADescription, AMimeType: String; const ACreationDate, AModDate: TDateTime): TPdfDictionary; overload;
  end;

{ TPdfDocumentEx }

function TPdfDocumentEx.CreateFileAttachment(const AFileName: String; const ADescription: String = ''): TPdfDictionary;
var
  MStream: TMemoryStream;
begin
  MStream:= TMemoryStream.Create;
  try
    MStream.LoadFromFile(AFileName);
    Result:= CreateFileAttachment(MStream, ExtractFileName(AFilename), ADescription, GetMIMETypeFromFile(AFilename), TFile.GetCreationTime(AFilename), TFile.GetLastWriteTime(AFilename));
  finally
    MStream.Free;
  end;
end;

function TPdfDocumentEx.CreateFileAttachment(const AMStream: TMemoryStream; const ATitle, ADescription, AMimeType: String; const ACreationDate, AModDate: TDateTime): TPdfDictionary;
var
  Filespec, EFDict, NamesDict, EmbeddedFilesDict, ParamsDict: TPdfDictionary;
  NamesArray: TPdfArray;
  EFStream: TPdfStream;
  NameText: TPdfTextString;
begin
  // create embedded file
  EFStream:= TPdfStream.Create(Self);
  EFStream.Attributes.AddItem('Type', 'EmbeddedFile');
  EFStream.Attributes.AddItem('Subtype', AMimeType);
  EFStream.Writer.Add(PAnsiChar(AMStream.Memory), AMStream.Size);
  // file params
  ParamsDict:= TPdfDictionary.Create(FXref);
  ParamsDict.AddItem('Size', AMStream.Size);
  if ACreationDate > 0 then
    ParamsDict.AddItemText('CreationDate', DateTimeToPdfDate(ACreationDate));
  if AModDate > 0 then
    ParamsDict.AddItemText('ModDate', DateTimeToPdfDate(AModDate));
  EFStream.Attributes.AddItem('Params', ParamsDict);

  // create filespec
  Filespec:= TPdfDictionary.Create(FXref);
  FXref.AddObject(Filespec);
  Filespec.AddItem('Type', 'Filespec');
  Filespec.AddItemTextString('F', ATitle);
  Filespec.AddItemTextString('UF', ATitle);
  Filespec.AddItemTextString('Desc', ADescription);
  Filespec.AddItem('AFRelationship', 'Alternative'); // alternative or source

  EFDict:= TPdfDictionary.Create(FXref);
  EFDict.AddItem('F', EFStream);
  EFDict.AddItem('UF', EFStream);
  Filespec.AddItem('EF', EFDict);

  // add to catalog
  NamesArray:= TPdfArray.Create(FXref);
  NameText:= TPdfTextString.Create(ATitle);
  NamesArray.AddItem(NameText);
  NamesArray.AddItem(Filespec);

  NamesDict:= TPdfDictionary.Create(FXref);
  NamesDict.AddItem('Names', NamesArray);

  EmbeddedFilesDict:= TPdfDictionary.Create(FXref);
  EmbeddedFilesDict.AddItem('EmbeddedFiles', NamesDict);
  Root.Data.AddItem('Names', EmbeddedFilesDict);
  Result:= EmbeddedFilesDict;
end;

With this function i can attach a file to PDF globaly: grafik

Hope this helps to enhance the library!

synopse commented 1 week ago

Nice! I will see how to include it to the trunk. Thanks for sharing.

How do you generate the XML? I was asked to add such native support to the library, and I guess we could do that in mORMOt 2 with the new cryptographic features.

synopse commented 1 week ago

Please try the above commit.

tueddy commented 1 week ago

That was lightning fast, just tested, function is working fine! Attachment is shown in tab & preflight doesn't report any errors. Thanks!

What about the result of function? I don't need it but it would be possible to create also a visible file attachment annotaion? So first create a document attachment, then a visible file annot for a specific page and link the attachment ref to the annot? Like this https://tcpdf.org/examples/example_041/