Pdf-lib: [Feature Request]: Embed files

Created on 1 Nov 2019  路  5Comments  路  Source: Hopding/pdf-lib

I want to generate ZUGFeRD invoices, which is a German standard for XML files with a certain structure embedded in a PDF/A-3 file.

I'll create feature requests for both requirements but here I'm asking for attachment embedding support. I couldn't find this in any other JS pdf library and think it's something quite some people might want.

feature-request

Most helpful comment

Version 1.7.0 is now published with this feature (thanks @sebastinez!). The full release notes are available here.

You can install this new version with npm:

npm install [email protected]

It's also available on unpkg:

As well as jsDelivr:

All 5 comments

Hello @zimt28! This certainly seems like an interesting and useful feature. It shouldn't be terribly difficult to implement either. The PDF specification documents file attachments in section 7.11.4 Embedded File Streams and 12.5.6.15 File Attachment Annotations.

To be clear, creating a file attachment is already possible to do with pdf-lib. It just entails creating particular dictionaries and streams, and wiring them up in the right way. pdf-lib already provides access to the low-level primitives necessary to do this. So this feature would just involve creating a nice API to abstract away the nitty gritty of what's involved here.

Hello @Hopding, I would like to contribute to this library with this feature.

I've read the sections you mentioned in the PDF specification, and tried some different things, but I'm kind of lost with all the dictionaries and streams of the library.

It seems that the embedded file streams consist of 2 objects:

A descriptor, which defines de EF operator
<</Desc()/EF<</F 15 0 R>>/F(ExampleFileName.docx)/Type/Filespec/UF(ExampleFileName.docx)>>

And a FlatDecode Stream, which has the file contents it seems.
<</DL 4664/Filter/FlateDecode/Length 4274/Params<</CheckSum<1411DA765B6BE9B8DF0EEA5B8C4B02BD>/CreationDate(D:20200521183112-03'00')/ModDate(D:20200406151931-03'00')/Size 4664>>/Subtype/application#2Fvnd.openxmlformats-officedocument.wordprocessingml.document>>stream

Could you help me a bit out? A hint which low-level primitives I should take a look at? And eventually how to wire them up?
I'm learning as I go about the PDF specification, but I'm really new to this topic.

Thank you very much!

Hello @sebastinez!

Adding file embedding functionality to pdf-lib would be fantastic. I look forward to a PR!

Here's a basic working example that creates a new document and attaches an image file to it. Let me know if you have any questions.

import {
  PDFDocument,
  PDFName,
  PDFDict,
  PDFArray,
  PDFString,
  PDFHexString,
  PDFRef,
} from 'pdf-lib';

const createEmbeddedFile = (
  pdfDoc: PDFDocument,
  file: Uint8Array,
  fileName: string,
  mimeType: string,
) => {
  const embeddedFileStream = pdfDoc.context.flateStream(file, {
    Type: 'EmbeddedFile',
    Subtype: PDFName.of(mimeType),
  });
  const embeddedFileStreamRef = pdfDoc.context.register(embeddedFileStream);

  const fileSpecDict = pdfDoc.context.obj({
    Type: 'Filespec',
    F: PDFString.of(fileName),
    UF: PDFHexString.fromText(fileName),
    EF: { F: embeddedFileStreamRef },
  });
  const fileSpecDictRef = pdfDoc.context.register(fileSpecDict);

  return fileSpecDictRef;
};

const insertEmbeddedFile = (
  pdfDoc: PDFDocument,
  fileName: string,
  fileSpecRef: PDFRef,
) => {
  if (!pdfDoc.catalog.has(PDFName.of('Names'))) {
    pdfDoc.catalog.set(PDFName.of('Names'), pdfDoc.context.obj({}));
  }
  const Names = pdfDoc.catalog.lookup(PDFName.of('Names'), PDFDict);

  if (!Names.has(PDFName.of('EmbeddedFiles'))) {
    Names.set(PDFName.of('EmbeddedFiles'), pdfDoc.context.obj({}));
  }
  const EmbeddedFiles = Names.lookup(PDFName.of('EmbeddedFiles'), PDFDict);

  if (!EmbeddedFiles.has(PDFName.of('Names'))) {
    EmbeddedFiles.set(PDFName.of('Names'), pdfDoc.context.obj([]));
  }
  const EFNames = EmbeddedFiles.lookup(PDFName.of('Names'), PDFArray);

  EFNames.push(PDFHexString.fromText(fileName));
  EFNames.push(fileSpecRef);
};

(async () => {
  const pdfDoc = await PDFDocument.create();

  const page = pdfDoc.addPage();
  page.drawText('This is a document with an attachment', { x: 100, y: 700 });

  const jpgUrl = 'https://pdf-lib.js.org/assets/cat_riding_unicorn.jpg';
  const jpgBuffer = await fetch(jpgUrl).then((res) => res.arrayBuffer());

  const file = new Uint8Array(jpgBuffer);
  const fileName = 'cat_riding_unicorn.jpg';
  const mimeType = 'image/jpeg';

  const fileSpecRef = createEmbeddedFile(pdfDoc, file, fileName, mimeType);
  insertEmbeddedFile(pdfDoc, fileName, fileSpecRef);

  const pdfBytes = await pdfDoc.save();
})();

Version 1.7.0 is now published with this feature (thanks @sebastinez!). The full release notes are available here.

You can install this new version with npm:

npm install [email protected]

It's also available on unpkg:

As well as jsDelivr:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lepidotteri picture lepidotteri  路  3Comments

sdfereday picture sdfereday  路  4Comments

DanielJackson-Oslo picture DanielJackson-Oslo  路  3Comments

keyhoffman picture keyhoffman  路  4Comments

BoLaMN picture BoLaMN  路  5Comments