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

Allow passing in widgetRect to make the signature widget visible and provide an example using pdfkit #200

Merged
merged 6 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ yarn-error.log*

coverage
.coveralls.yml
packages/examples/output
packages/examples/output/*

node_modules/
.npm
Expand Down
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

* [utils] Added SIG_FLAGS and ANNOTATION_FLAGS to improve readability;
* [utils] Reworked findByteRange to match in more cases where it was incompatible so far (it didn't allow optional spaces in the array).
* [pdfkit010] Uses SIG_FLAGS and ANNOTATION_FLAGS instead of magic numbers;
* [placeholder-pdfkit010] Uses SIG_FLAGS and ANNOTATION_FLAGS instead of magic numbers;
* [placeholder-pdfkit010] Allow passing in widgetRect to override the default [0, 0, 0, 0] one;
* [placeholder-plain] Allow passing in widgetRect to override the default [0, 0, 0, 0] one;
* [signpdf] Use the BR position findByteRange provides to spare a search for it;
* Bumped version of axios
* [examples] Introduce [an example that provides a visible widget](packages/examples/src/pdfkit010-with-visual.js) (implemented with pdfkit);
* Bumped version of axios;

## [3.0.0]

Expand Down
130 changes: 130 additions & 0 deletions packages/examples/src/pdfkit010-with-visual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
var fs = require('fs');
var path = require('path');
var PDFDocument = require('pdfkit');
var signpdf = require('@signpdf/signpdf').default;
var P12Signer = require('@signpdf/signer-p12').P12Signer;
var pdfkitAddPlaceholder = require('@signpdf/placeholder-pdfkit010').pdfkitAddPlaceholder;

/**
* Transform coordinates from top/left to bottom/left coordinate system
*/
function topLeftToBottomLeft(coords, page) {
return [
coords[0], // x1
page.height - coords[1], // y1
coords[2], // x2
page.height - coords[3], // y2
];
}

function addVisual(pdf) {
// Go to first page
pdf.switchToPage(0);

var margin = 30;
var padding = 10;
var label = 'Signed with @signpdf';
pdf
.fillColor('#008B93')
.fontSize(10);
var text = {
width: pdf.widthOfString(label),
height: pdf.heightOfString(label)
};
text.x = pdf.page.width - text.width - margin;
text.y = pdf.page.height - text.height - margin;

pdf.text(label, text.x, text.y, {width: text.width, height: text.height});

return [
text.x - padding,
text.y - padding,
text.x + text.width + padding,
text.y + text.height + padding,
];
}

function work() {
// Start a PDFKit document
var pdf = new PDFDocument({
autoFirstPage: false,
size: 'A4',
layout: 'portrait',
bufferPages: true,
});;
pdf.info.CreationDate = '';

// At the end we want to convert the PDFKit to a string/Buffer and store it in a file.
// Here is how this is going to happen:
var pdfReady = new Promise(function (resolve) {
// Collect the ouput PDF
// and, when done, resolve with it stored in a Buffer
var pdfChunks = [];
pdf.on('data', function (data) {
pdfChunks.push(data);
});
pdf.on('end', function () {
resolve(Buffer.concat(pdfChunks));
});
});

// Add some content to the page(s)
pdf
.addPage()
.fillColor('#333')
.fontSize(25)
.moveDown()
.text('@signpdf');

// !!! ADDING VISUALS AND APPLYING TO SIGNATURE WIDGET ==>

// Add a some visuals and make sure to get their dimensions.
var visualRect = addVisual(pdf);
// Convert these dimension as Widgets' (0,0) is bottom-left based while the
// rest of the coordinates on the page are top-left.
var widgetRect = topLeftToBottomLeft(visualRect, pdf.page);

// Here comes the signing. We need to add the placeholder so that we can later sign.
var refs = pdfkitAddPlaceholder({
pdf: pdf,
pdfBuffer: Buffer.from([pdf]), // FIXME: This shouldn't be needed.
reason: 'Showing off.',
contactInfo: '[email protected]',
name: 'Sign PDF',
location: 'The digital world.',
signatureLength: 1612,
widgetRect: widgetRect, // <== !!! This is where we tell the widget to be visible
});

// <== !!! ADDING VISUALS AND APPLYING TO SIGNATURE WIDGET

// `refs` here contains PDFReference objects to signature, form and widget.
// PDFKit doesn't know much about them, so it won't .end() them. We need to do that for it.
Object.keys(refs).forEach(function (key) {
refs[key].end()
});

// Once we .end the PDFDocument, the `pdfReady` Promise will resolve with
// the Buffer of a PDF that has a placeholder for signature that we need.
// Other that we will also need a certificate
// certificate.p12 is the certificate that is going to be used to sign
var certificatePath = path.join(__dirname, '/../../../resources/certificate.p12');
var certificateBuffer = fs.readFileSync(certificatePath);
var signer = new P12Signer(certificateBuffer);

// Once the PDF is ready we need to sign it and eventually store it on disc.
pdfReady
.then(function (pdfWithPlaceholder) {
return signpdf.sign(pdfWithPlaceholder, signer);
})
.then(function (signedPdf) {
var targetPath = path.join(__dirname, '/../output/pdfkit010-with-visual.pdf');
fs.writeFileSync(targetPath, signedPdf);
});

// Finally end the PDFDocument stream.
pdf.end();
// This has just triggered the `pdfReady` Promise to be resolved.
};

work();
8 changes: 6 additions & 2 deletions packages/placeholder-pdfkit010/dist/pdfkitAddPlaceholder.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function pdfkitAddPlaceholder({ pdf, pdfBuffer, reason, contactInfo, name, location, signatureLength, byteRangePlaceholder, subFilter, }: InputType): ReturnType;
export function pdfkitAddPlaceholder({ pdf, pdfBuffer, reason, contactInfo, name, location, signatureLength, byteRangePlaceholder, subFilter, widgetRect, }: InputType): ReturnType;
export type InputType = {
/**
* PDFDocument
Expand All @@ -12,9 +12,13 @@ export type InputType = {
signatureLength?: number;
byteRangePlaceholder?: string;
/**
* One of SUBFILTER_* from
* One of SUBFILTER_* from \@signpdf/utils
*/
subFilter?: string;
/**
* [x1, y1, x2, y2] widget rectangle
*/
widgetRect?: number[];
};
export type ReturnType = {
signature: any;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions packages/placeholder-pdfkit010/dist/pdfkitAddPlaceholder.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ var _pdfkitReferenceMock = require("./pdfkitReferenceMock");
* @property {string} location
* @property {number} [signatureLength]
* @property {string} [byteRangePlaceholder]
* @property {string} [subFilter] One of SUBFILTER_* from @signpdf/utils
* @property {string} [subFilter] One of SUBFILTER_* from \@signpdf/utils
* @property {number[]} [widgetRect] [x1, y1, x2, y2] widget rectangle
*/

/**
Expand All @@ -44,7 +45,8 @@ const pdfkitAddPlaceholder = ({
location,
signatureLength = _utils.DEFAULT_SIGNATURE_LENGTH,
byteRangePlaceholder = _utils.DEFAULT_BYTE_RANGE_PLACEHOLDER,
subFilter = _utils.SUBFILTER_ADOBE_PKCS7_DETACHED
subFilter = _utils.SUBFILTER_ADOBE_PKCS7_DETACHED,
widgetRect = [0, 0, 0, 0]
}) => {
/* eslint-disable no-underscore-dangle,no-param-reassign */
// Generate the signature placeholder
Expand Down Expand Up @@ -105,7 +107,7 @@ const pdfkitAddPlaceholder = ({
Type: 'Annot',
Subtype: 'Widget',
FT: 'Sig',
Rect: [0, 0, 0, 0],
Rect: widgetRect,
V: signature,
T: new String(signatureName + (fieldIds.length + 1)),
// eslint-disable-line no-new-wrappers
Expand Down
6 changes: 4 additions & 2 deletions packages/placeholder-pdfkit010/src/pdfkitAddPlaceholder.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {PDFKitReferenceMock} from './pdfkitReferenceMock';
* @property {string} location
* @property {number} [signatureLength]
* @property {string} [byteRangePlaceholder]
* @property {string} [subFilter] One of SUBFILTER_* from @signpdf/utils
* @property {string} [subFilter] One of SUBFILTER_* from \@signpdf/utils
* @property {number[]} [widgetRect] [x1, y1, x2, y2] widget rectangle
*/

/**
Expand All @@ -45,6 +46,7 @@ export const pdfkitAddPlaceholder = ({
signatureLength = DEFAULT_SIGNATURE_LENGTH,
byteRangePlaceholder = DEFAULT_BYTE_RANGE_PLACEHOLDER,
subFilter = SUBFILTER_ADOBE_PKCS7_DETACHED,
widgetRect = [0, 0, 0, 0],
}) => {
/* eslint-disable no-underscore-dangle,no-param-reassign */
// Generate the signature placeholder
Expand Down Expand Up @@ -114,7 +116,7 @@ export const pdfkitAddPlaceholder = ({
Type: 'Annot',
Subtype: 'Widget',
FT: 'Sig',
Rect: [0, 0, 0, 0],
Rect: widgetRect,
V: signature,
T: new String(signatureName + (fieldIds.length + 1)), // eslint-disable-line no-new-wrappers
F: ANNOTATION_FLAGS.PRINT,
Expand Down
54 changes: 48 additions & 6 deletions packages/placeholder-pdfkit010/src/pdfkitAddPlaceholder.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {SIG_FLAGS, SUBFILTER_ETSI_CADES_DETACHED} from '@signpdf/utils';
import {SIG_FLAGS, SUBFILTER_ADOBE_PKCS7_DETACHED, SUBFILTER_ETSI_CADES_DETACHED} from '@signpdf/utils';
import {createPdfkitDocument} from '@signpdf/internal-utils';
import {pdfkitAddPlaceholder} from './pdfkitAddPlaceholder';
import PDFObject from './pdfkit/pdfobject';
Expand Down Expand Up @@ -74,10 +74,10 @@ describe(pdfkitAddPlaceholder, () => {
expect(pdf.page.dictionary.data.Annots[0].data.Subtype).toEqual('Widget');
const widgetData = pdf.page.dictionary.data.Annots[0].data.V.data;
expect(PDFObject.convert(widgetData.Reason)).toEqual('(test reason)');
expect(PDFObject.convert(widgetData.ContactInfo)).toEqual('([email protected])');
expect(PDFObject.convert(widgetData.Name)).toEqual('(test name)');
expect(PDFObject.convert(widgetData.Location)).toEqual('(test Location)');
expect(widgetData.SubFilter).toEqual('adbe.pkcs7.detached');
expect(PDFObject.convert(widgetData.ContactInfo)).toEqual(`(${defaults.contactInfo})`);
expect(PDFObject.convert(widgetData.Name)).toEqual(`(${defaults.name})`);
expect(PDFObject.convert(widgetData.Location)).toEqual(`(${defaults.location})`);
expect(widgetData.SubFilter).toEqual(SUBFILTER_ADOBE_PKCS7_DETACHED);
});

it('allows defining signature SubFilter', () => {
Expand All @@ -100,7 +100,49 @@ describe(pdfkitAddPlaceholder, () => {
expect(widget.data.Subtype).toEqual('Widget');
const widgetData = widget.data.V.data;
expect(PDFObject.convert(widgetData.Reason)).toEqual('(test reason)');
expect(widgetData.SubFilter).toEqual('ETSI.CAdES.detached');
expect(widgetData.SubFilter).toEqual(SUBFILTER_ETSI_CADES_DETACHED);
});

it('sets the widget rectange to invisible by default', () => {
const {pdf} = createPdfkitDocument();
const refs = pdfkitAddPlaceholder({
...defaults,
pdf,
pdfBuffer: Buffer.from([pdf]),
reason: 'test reason',
});
expect(Object.keys(refs)).toEqual(expect.arrayContaining([
'signature',
'form',
'widget',
]));
expect(pdf.page.dictionary.data.Annots).toHaveLength(1);
const widget = pdf.page.dictionary.data.Annots[0];
const rect = widget.data.Rect;
expect(Array.isArray(rect)).toBe(true);
expect(rect).toEqual([0, 0, 0, 0]);
});

it('allows defining widget rectange', () => {
const {pdf} = createPdfkitDocument();
const widgetRect = [100, 100, 200, 200];
const refs = pdfkitAddPlaceholder({
...defaults,
pdf,
pdfBuffer: Buffer.from([pdf]),
reason: 'test reason',
widgetRect,
});
expect(Object.keys(refs)).toEqual(expect.arrayContaining([
'signature',
'form',
'widget',
]));
expect(pdf.page.dictionary.data.Annots).toHaveLength(1);
const widget = pdf.page.dictionary.data.Annots[0];
const rect = widget.data.Rect;
expect(Array.isArray(rect)).toBe(true);
expect(rect).toEqual(widgetRect);
});

it('adds placeholder to PDFKit document when AcroForm is already there', () => {
Expand Down
8 changes: 6 additions & 2 deletions packages/placeholder-plain/dist/plainAddPlaceholder.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function plainAddPlaceholder({ pdfBuffer, reason, contactInfo, name, location, signatureLength, subFilter, }: InputType): Buffer;
export function plainAddPlaceholder({ pdfBuffer, reason, contactInfo, name, location, signatureLength, subFilter, widgetRect, }: InputType): Buffer;
export type InputType = {
pdfBuffer: Buffer;
reason: string;
Expand All @@ -7,8 +7,12 @@ export type InputType = {
location: string;
signatureLength?: number;
/**
* One of SUBFILTER_* from
* One of SUBFILTER_* from \@signpdf/utils
*/
subFilter?: string;
/**
* [x1, y1, x2, y2] widget rectangle
*/
widgetRect?: number[];
};
//# sourceMappingURL=plainAddPlaceholder.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions packages/placeholder-plain/dist/plainAddPlaceholder.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const getAcroFormRef = slice => {
* @property {string} name
* @property {string} location
* @property {number} [signatureLength]
* @property {string} [subFilter] One of SUBFILTER_* from @signpdf/utils
* @property {string} [subFilter] One of SUBFILTER_* from \@signpdf/utils
* @property {number[]} [widgetRect] [x1, y1, x2, y2] widget rectangle
*/

/**
Expand All @@ -58,7 +59,8 @@ const plainAddPlaceholder = ({
name,
location,
signatureLength = _utils.DEFAULT_SIGNATURE_LENGTH,
subFilter = _utils.SUBFILTER_ADOBE_PKCS7_DETACHED
subFilter = _utils.SUBFILTER_ADOBE_PKCS7_DETACHED,
widgetRect = [0, 0, 0, 0]
}) => {
let pdf = (0, _utils.removeTrailingNewLine)(pdfBuffer);
const info = (0, _readPdf.default)(pdf);
Expand Down Expand Up @@ -100,7 +102,8 @@ const plainAddPlaceholder = ({
name,
location,
signatureLength,
subFilter
subFilter,
widgetRect
});
if (!getAcroFormRef(pdf.toString())) {
const rootIndex = (0, _getIndexFromRef.default)(info.xref, info.rootRef);
Expand Down
5 changes: 4 additions & 1 deletion packages/placeholder-plain/src/plainAddPlaceholder.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const getAcroFormRef = (slice) => {
* @property {string} name
* @property {string} location
* @property {number} [signatureLength]
* @property {string} [subFilter] One of SUBFILTER_* from @signpdf/utils
* @property {string} [subFilter] One of SUBFILTER_* from \@signpdf/utils
* @property {number[]} [widgetRect] [x1, y1, x2, y2] widget rectangle
*/

/**
Expand All @@ -62,6 +63,7 @@ export const plainAddPlaceholder = ({
location,
signatureLength = DEFAULT_SIGNATURE_LENGTH,
subFilter = SUBFILTER_ADOBE_PKCS7_DETACHED,
widgetRect = [0, 0, 0, 0],
}) => {
let pdf = removeTrailingNewLine(pdfBuffer);
const info = readPdf(pdf);
Expand Down Expand Up @@ -118,6 +120,7 @@ export const plainAddPlaceholder = ({
location,
signatureLength,
subFilter,
widgetRect,
});

if (!getAcroFormRef(pdf.toString())) {
Expand Down
Loading
Loading