Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to get/set Annot or Widget from pdf #39

Closed
ssleptsov opened this issue Oct 20, 2018 · 17 comments
Closed

How to get/set Annot or Widget from pdf #39

ssleptsov opened this issue Oct 20, 2018 · 17 comments

Comments

@ssleptsov
Copy link

ssleptsov commented Oct 20, 2018

Hey, is it possible to get/set Annot or Widget for existing pdf file?
I'm trying to replace this pdfkit code with your library

const signature = pdf.ref({
  Type: 'Sig',
  Filter: 'Adobe.PPKLite',
  SubFilter: 'adbe.pkcs7.detached',
  ByteRange: [
    0,
    DEFAULT_BYTE_RANGE_PLACEHOLDER,
    DEFAULT_BYTE_RANGE_PLACEHOLDER,
    DEFAULT_BYTE_RANGE_PLACEHOLDER,
  ],
  Contents: Buffer.from(String.fromCharCode(0).repeat(signatureLength)),
  Reason: new String(reason), // eslint-disable-line no-new-wrappers
  M: new Date(),
});

// Generate signature annotation widget
const widget = pdf.ref({
  Type: 'Annot',
  Subtype: 'Widget',
  FT: 'Sig',
  Rect: [0, 0, 0, 0],
  V: signature,
  T: new String('Signature1'), // eslint-disable-line no-new-wrappers
  F: 4,
  P: pdf._root.data.Pages.data.Kids[0], // eslint-disable-line no-underscore-dangle
});

// Include the widget in a page
pdf._root.data.Pages.data.Kids[0].data.Annots = [widget];

// Create a form (with the widget) and link in the _root
const form = pdf.ref({
  SigFlags: 3,
  Fields: [widget],
});
pdf._root.data.AcroForm = form;

Thanks!

@Hopding
Copy link
Owner

Hopding commented Oct 22, 2018

Hello @ssleptsov. This is certainly possible, as pdf-lib provides low level access to pdf objects. However, there is not currently a nice high level API for manipulating annotations or acroform objects, so you'll have to put up with some verbose boilerplate for now 😄

Here's how to do it:

const pdfBytes = /* A Uint8Array containing the PDF you wish to modify */
const pdfDoc = PDFDocumentFactory.load(pdfBytes);

const [FontHelvetica] = pdfDoc.embedStandardFont('Helvetica');

// Define the signature dictionary (Detailed in Table 252 of PDF specification)
// Note that the dict below isn't actually valid. I just made up its `ByteRange`
// and `Contents` entries. It's just an example.
const signatureDict = PDFDictionary.from({
  Type: PDFName.from('Sig'),
  Filter: PDFName.from('Adobe.PPKLite'),
  SubFilter: PDFName.from('adbe.pkcs7.detached'),
  ByteRange: PDFArray.fromArray([
    PDFNumber.fromNumber(0),
    PDFNumber.fromNumber(0),
    PDFNumber.fromNumber(0),
    PDFNumber.fromNumber(0),
  ], pdfDoc.index),
  Contents: PDFHexString.fromString('00000000000'),
  Reason: PDFString.fromString('We need your signature for reasons...'),
  // M: PDFString.fromString('D:YYYYMMDDHHmmSSOHH'mm')
  // See section "7.9.4 Dates" of the PDF specification for details on the format of date strings
}, pdfDoc.index);

// Define a content stream that defines how the signature field should appear
// on the PDF.
//
// Note: This isn't necessary if you define the signature widget's "Rect" entry
// as [0, 0, 0, 0] like in the snippet you provided. But I've implemented it 
// just in case you (or others) might find it useful.
const sigAppearanceStream = PDFContentStream.of(
  PDFDictionary.from({
    Type: PDFName.from('XObject'),
    Subtype: PDFName.from('Form'),
    BBox: PDFArray.fromArray([
      PDFNumber.fromNumber(0),
      PDFNumber.fromNumber(0),
      PDFNumber.fromNumber(200),
      PDFNumber.fromNumber(50),
    ], pdfDoc.index),
    Resources: PDFDictionary.from({
      Font: PDFDictionary.from({
        Helvetica: FontHelvetica
      }, pdfDoc.index)
    }, pdfDoc.index),
  }, pdfDoc.index),
  drawRectangle({
    x: 0,
    y: 0,
    width: 200,
    height: 50,
    colorRgb: [0.95, 0.95, 0.95],
    borderWidth: 3,
    borderColorRgb: [0, 0, 0],
  }),
  drawText('Sign Here', {
    x: 10,
    y: 15,
    font: 'Helvetica',
    size: 30,
    colorRgb: [0.5, 0.5, 0.5],
  }),
  drawRectangle({
    x: 4,
    y: 4,
    width: 192,
    height: 2,
    colorRgb: [0.5, 0.5, 0.5],
  }),
);
const sigAppearanceStreamRef = pdfDoc.register(sigAppearanceStream);

// Define the signature widget annotation
const widgetDict = PDFDictionary.from({
  Type: PDFName.from('Annot'),
  Subtype: PDFName.from('Widget'),
  FT: PDFName.from('Sig'),
  Rect: PDFArray.fromArray([
    PDFNumber.fromNumber(50),
    PDFNumber.fromNumber(50),
    PDFNumber.fromNumber(300),
    PDFNumber.fromNumber(100),
  ], pdfDoc.index),
  
  // Uncomment this if you've created a valid signatureDict
  // V: signatureDict,  
  
  T: PDFString.fromString('Signature1'),
  F: PDFNumber.fromNumber(4),
  P: (pdfDoc.catalog.Pages.get('Kids') as PDFArray).get(0),
  AP: PDFDictionary.from({
    N: sigAppearanceStreamRef,
  }, pdfDoc.index),
}, pdfDoc.index);
const widgetDictRef = pdfDoc.register(widgetDict);

// Add our signature widget to the first page
const pages = pdfDoc.getPages();
pages[0].set(
  'Annots',
  PDFArray.fromArray([widgetDictRef], pdfDoc.index),
);

// Create an AcroForm object containing our signature widget
const formDict = PDFDictionary.from({
  SigFlags: PDFNumber.fromNumber(3),
  Fields: PDFArray.fromArray([widgetDictRef], pdfDoc.index),
}, pdfDoc.index);

pdfDoc.catalog.set('AcroForm', formDict);

const modifiedPdfBytes = PDFDocumentWriter.saveToBytes(pdfDoc);

Here's a sample PDF that I modified using the script above (notice the "Sign Here" field at the bottom of the first page): pdf-with-signature-field-unsigned.pdf.

And here's the original pdf.

Please let me know if this is what you're looking for, or if you need any additional help with pdf-lib!

@ssleptsov
Copy link
Author

wow, that's awesome! Thanks for your help and the great library!

@Hopding
Copy link
Owner

Hopding commented Oct 23, 2018

You bet!

(I updated the pdf-with-signature-field-unsigned.pdf file - I noticed I had attached the wrong one).

@Hopding Hopding closed this as completed Oct 23, 2018
@pspeter3
Copy link

pspeter3 commented Nov 9, 2018

Does this work for filling form fields? I think I do not know PDFs well enough yet.

@Hopding
Copy link
Owner

Hopding commented Nov 10, 2018

Yes, it should be able to fill form fields. If you can share a document that you'd like to fill out, I can provide some sample code that does so. If not, I can provide an example of filling out a form field with one of the repo's test PDFs, if you let me know which types of form fields you're working with.

@Hopding
Copy link
Owner

Hopding commented Nov 13, 2018 via email

@pspeter3
Copy link

pspeter3 commented Nov 15, 2018 via email

@Hopding
Copy link
Owner

Hopding commented Nov 22, 2018

@pspeter3 I created an example script that fills out a DoD character sheet here: #48.

My apologies for not getting around to this sooner. Had to deal with some fiascos at work this past week.

@john-attrium-204
Copy link

wow, that's awesome! Thanks for your help and the great library!

Hey ! I am trying to signed my existing pdf file which i have generated using filling form with hummusjs. But i was not able to set default ByteRange for signature placeholder.
I am getting this error

Could not find ByteRange placeholder: /ByteRange [0 /********** /********** /**********]

Can you please help me get this done.

Thanks in advance.

@Hopding
Copy link
Owner

Hopding commented May 8, 2019

Hello @john-attrium-204!

I need more information in order to help you with this. Can you please provide a code sample and a PDF that I can use to reproduce the issue?

@therpobinski
Copy link

therpobinski commented Nov 13, 2019

HI, I have been trying to put a signature mark on the pdf, but using this library I modify the PDF with the drawText function, but I realize that it removes the trailer from the PDF Buffer. Then I want to sign and he won't let me, because he can't find the trailer.
To better understand what I am saying, I provide the library with which I am signing and an pull-request where I am occupying pdf-lib.
I hope you understand me and help me.

  1. node-signpdf
  2. pull-request, created by me for node-signpdf, here I explain a bit the problem that is happening to me with pdf-lib

According to the example you proposed, you occupy functions that do not exist in pdf-lib, such as:

  • PDFDocumentFactory
  • embedStandardFont
  • PDFContentStream
  • etc..

And you talk about a 252 table of the PDF specification that I can't find.

@vbuch
Copy link
Sponsor

vbuch commented Nov 14, 2019

@therpobinski I am pretty sure the original snippet in this issue that @ssleptsov posted is a piece of code from the node-signpdf package. So he was trying to do the same thing you are.
What @Hopding gave him is a PDF with a placeholder for signature, so the first part of the task is done. Since the signing code does not care about trailers I think it just worked from here on. @ssleptsov should confirm if that worked for him. But, yes, this is a great snippet that, if @Hopding allows, we can include as a pdflibAddPlaceholder helper in our lib.

I think @john-attrium-204 was dealing with the same thing.

Sorry @Hopding. Just saw we are commenting on a closed issue.
And just because it's my first time to write in your lib, I want to congratulate you on it. I haven't had the time to play around with it too much but it looks awesome from what I see. Awesome work! Awesome share!

@SnigBhaumik
Copy link

@therpobinski I am pretty sure the original snippet in this issue that @ssleptsov posted is a piece of code from the node-signpdf package. So he was trying to do the same thing you are.
What @Hopding gave him is a PDF with a placeholder for signature, so the first part of the task is done. Since the signing code does not care about trailers I think it just worked from here on. @ssleptsov should confirm if that worked for him. But, yes, this is a great snippet that, if @Hopding allows, we can include as a pdflibAddPlaceholder helper in our lib.

I think @john-attrium-204 was dealing with the same thing.

Sorry @Hopding. Just saw we are commenting on a closed issue.
And just because it's my first time to write in your lib, I want to congratulate you on it. I haven't had the time to play around with it too much but it looks awesome from what I see. Awesome work! Awesome share!

It will be great if both libraries join hands - both pdf-lib and node-signpdf libraries would offer signing of new and existing pdfs. @Hopding and @vbuch - adding a pdflibAddPlaceholder in addition to pdfkit and plan one would surely resolve a major missing feature request.

@therpobinski
Copy link

wow, that's awesome! Thanks for your help and the great library!

Hey ! I am trying to signed my existing pdf file which i have generated using filling form with hummusjs. But i was not able to set default ByteRange for signature placeholder.
I am getting this error

Could not find ByteRange placeholder: /ByteRange [0 /********** /********** /**********]

Can you please help me get this done.

Thanks in advance.

Hi, Hello, I have the same problem and I think I know what it is, I am reviewing it and I see that the placeholder has never been embedded in the PDF, so signing the document does not find the ByteRange and the error occurs.
Have you been able to find a solution?
I doubt that the example that @Hopding gave us is working, I leave you my codecommit so you can review it and see if we can find a solution.

@brasycad
Copy link

Hello @ssleptsov. This is certainly possible, as pdf-lib provides low level access to pdf objects. However, there is not currently a nice high level API for manipulating annotations or acroform objects, so you'll have to put up with some verbose boilerplate for now 😄

Here's how to do it:

const pdfBytes = /* A Uint8Array containing the PDF you wish to modify */
const pdfDoc = PDFDocumentFactory.load(pdfBytes);

const [FontHelvetica] = pdfDoc.embedStandardFont('Helvetica');

// Define the signature dictionary (Detailed in Table 252 of PDF specification)
// Note that the dict below isn't actually valid. I just made up its `ByteRange`
// and `Contents` entries. It's just an example.
const signatureDict = PDFDictionary.from({
  Type: PDFName.from('Sig'),
  Filter: PDFName.from('Adobe.PPKLite'),
  SubFilter: PDFName.from('adbe.pkcs7.detached'),
  ByteRange: PDFArray.fromArray([
    PDFNumber.fromNumber(0),
    PDFNumber.fromNumber(0),
    PDFNumber.fromNumber(0),
    PDFNumber.fromNumber(0),
  ], pdfDoc.index),
  Contents: PDFHexString.fromString('00000000000'),
  Reason: PDFString.fromString('We need your signature for reasons...'),
  // M: PDFString.fromString('D:YYYYMMDDHHmmSSOHH'mm')
  // See section "7.9.4 Dates" of the PDF specification for details on the format of date strings
}, pdfDoc.index);

// Define a content stream that defines how the signature field should appear
// on the PDF.
//
// Note: This isn't necessary if you define the signature widget's "Rect" entry
// as [0, 0, 0, 0] like in the snippet you provided. But I've implemented it 
// just in case you (or others) might find it useful.
const sigAppearanceStream = PDFContentStream.of(
  PDFDictionary.from({
    Type: PDFName.from('XObject'),
    Subtype: PDFName.from('Form'),
    BBox: PDFArray.fromArray([
      PDFNumber.fromNumber(0),
      PDFNumber.fromNumber(0),
      PDFNumber.fromNumber(200),
      PDFNumber.fromNumber(50),
    ], pdfDoc.index),
    Resources: PDFDictionary.from({
      Font: PDFDictionary.from({
        Helvetica: FontHelvetica
      }, pdfDoc.index)
    }, pdfDoc.index),
  }, pdfDoc.index),
  drawRectangle({
    x: 0,
    y: 0,
    width: 200,
    height: 50,
    colorRgb: [0.95, 0.95, 0.95],
    borderWidth: 3,
    borderColorRgb: [0, 0, 0],
  }),
  drawText('Sign Here', {
    x: 10,
    y: 15,
    font: 'Helvetica',
    size: 30,
    colorRgb: [0.5, 0.5, 0.5],
  }),
  drawRectangle({
    x: 4,
    y: 4,
    width: 192,
    height: 2,
    colorRgb: [0.5, 0.5, 0.5],
  }),
);
const sigAppearanceStreamRef = pdfDoc.register(sigAppearanceStream);

// Define the signature widget annotation
const widgetDict = PDFDictionary.from({
  Type: PDFName.from('Annot'),
  Subtype: PDFName.from('Widget'),
  FT: PDFName.from('Sig'),
  Rect: PDFArray.fromArray([
    PDFNumber.fromNumber(50),
    PDFNumber.fromNumber(50),
    PDFNumber.fromNumber(300),
    PDFNumber.fromNumber(100),
  ], pdfDoc.index),
  
  // Uncomment this if you've created a valid signatureDict
  // V: signatureDict,  
  
  T: PDFString.fromString('Signature1'),
  F: PDFNumber.fromNumber(4),
  P: (pdfDoc.catalog.Pages.get('Kids') as PDFArray).get(0),
  AP: PDFDictionary.from({
    N: sigAppearanceStreamRef,
  }, pdfDoc.index),
}, pdfDoc.index);
const widgetDictRef = pdfDoc.register(widgetDict);

// Add our signature widget to the first page
const pages = pdfDoc.getPages();
pages[0].set(
  'Annots',
  PDFArray.fromArray([widgetDictRef], pdfDoc.index),
);

// Create an AcroForm object containing our signature widget
const formDict = PDFDictionary.from({
  SigFlags: PDFNumber.fromNumber(3),
  Fields: PDFArray.fromArray([widgetDictRef], pdfDoc.index),
}, pdfDoc.index);

pdfDoc.catalog.set('AcroForm', formDict);

const modifiedPdfBytes = PDFDocumentWriter.saveToBytes(pdfDoc);

Here's a sample PDF that I modified using the script above (notice the "Sign Here" field at the bottom of the first page): pdf-with-signature-field-unsigned.pdf.

And here's the original pdf.

Please let me know if this is what you're looking for, or if you need any additional help with pdf-lib!

Hi Andrew.
Is it possible to have this code adapted to the latest version?
Well, I try to use it and it gives me multiple errors.
Thank you

@Hopding Hopding mentioned this issue Dec 16, 2019
@cshenks cshenks mentioned this issue Dec 17, 2019
@vemundeldegard
Copy link

@brasycad @Hopding Would also love to see this adapted to the latest version.

@Hopding
Copy link
Owner

Hopding commented Dec 27, 2019

@therpobinski @brasycad @vemundeldegard Please see #112 (comment). I've updated my example for version 1.3.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants