diff --git a/apps/deno/tests/test1.ts b/apps/deno/tests/test1.ts index 42e423b78..ec80625d0 100644 --- a/apps/deno/tests/test1.ts +++ b/apps/deno/tests/test1.ts @@ -67,6 +67,11 @@ export default async (assets: Assets) => { const size = 750; + pdfDoc.addJavascript( + 'main', + 'console.show(); console.println("Hello World!")', + ); + /********************** Page 1 **********************/ // This page tests different drawing operations as well as adding custom diff --git a/apps/node/tests/test1.ts b/apps/node/tests/test1.ts index 01d60a1b8..35ab26c27 100644 --- a/apps/node/tests/test1.ts +++ b/apps/node/tests/test1.ts @@ -67,6 +67,11 @@ export default async (assets: Assets) => { const size = 750; + pdfDoc.addJavascript( + 'main', + 'console.show(); console.println("Hello World!")', + ); + /********************** Page 1 **********************/ // This page tests different drawing operations as well as adding custom diff --git a/apps/rn/src/tests/test1.js b/apps/rn/src/tests/test1.js index 3ded89aa6..be1a50128 100644 --- a/apps/rn/src/tests/test1.js +++ b/apps/rn/src/tests/test1.js @@ -72,6 +72,11 @@ export default async () => { const size = 750; + pdfDoc.addJavascript( + 'main', + 'console.show(); console.println("Hello World!")', + ); + /********************** Page 1 **********************/ // This page tests different drawing operations as well as adding custom diff --git a/apps/web/test1.html b/apps/web/test1.html index f0ef47c43..d739b21d9 100644 --- a/apps/web/test1.html +++ b/apps/web/test1.html @@ -120,6 +120,11 @@ const size = 750; + pdfDoc.addJavascript( + 'main', + 'console.show(); console.println("Hello World!")', + ); + /********************** Page 1 **********************/ // This page tests different drawing operations as well as adding custom diff --git a/src/api/PDFDocument.ts b/src/api/PDFDocument.ts index 646addfc0..c9c525643 100644 --- a/src/api/PDFDocument.ts +++ b/src/api/PDFDocument.ts @@ -61,6 +61,7 @@ import { } from 'src/utils'; import FileEmbedder from 'src/core/embedders/FileEmbedder'; import PDFEmbeddedFile from 'src/api/PDFEmbeddedFile'; +import PDFArray from 'src/core/objects/PDFArray'; /** * Represents a PDF document. @@ -713,6 +714,44 @@ export default class PDFDocument { return copiedPages; } + /** + * Add document JavaScript. The script is executed when the document is opened. + * See the [JavaScript™ for Acrobat® API Reference](https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/js_api_reference.pdf) + * for details. For example: + * + * ```js + * pdfDoc.addJavascript('main', 'console.show(); console.println("Hello World")'); + * ``` + * @param name The name of the script. Must be unique per document. + * @param script The JavaScript to execute. + */ + addJavascript(name: string, script: string) { + const jsActionDict = this.context.obj({ + Type: 'Action', + S: 'JavaScript', + JS: PDFHexString.fromText(script), + }); + + const jsActionRef = this.context.register(jsActionDict); + if (!this.catalog.has(PDFName.of('Names'))) { + this.catalog.set(PDFName.of('Names'), PDFDict.withContext(this.context)); + } + + const Names = this.catalog.lookup(PDFName.of('Names'), PDFDict); + if (!Names.has(PDFName.of('JavaScript'))) { + Names.set(PDFName.of('JavaScript'), this.context.obj({})); + } + + const Javascript = Names.lookup(PDFName.of('JavaScript'), PDFDict); + if (!Javascript.has(PDFName.of('Names'))) { + Javascript.set(PDFName.of('Names'), this.context.obj([])); + } + + const JSNames = Javascript.lookup(PDFName.of('Names'), PDFArray); + JSNames.push(PDFName.of(name)); + JSNames.push(jsActionRef); + } + /** * Add an attachment to this document. Attachments are visible in the * "Attachments" panel of Adobe Acrobat and some other PDF readers. Any diff --git a/tests/api/PDFDocument.spec.ts b/tests/api/PDFDocument.spec.ts index 2d88d6515..4524685e5 100644 --- a/tests/api/PDFDocument.spec.ts +++ b/tests/api/PDFDocument.spec.ts @@ -3,6 +3,8 @@ import fs from 'fs'; import { EncryptedPDFError, ParseSpeeds, + PDFArray, + PDFDict, PDFDocument, PDFName, PDFPage, @@ -299,4 +301,34 @@ describe(`PDFDocument`, () => { ); }); }); + describe(`addJavascript method`, () => { + it(`adds the script to the catalog`, async () => { + const pdfDoc = await PDFDocument.create(); + pdfDoc.addJavascript( + 'main', + 'console.show(); console.println("Hello World"', + ); + expect(pdfDoc.catalog.has(PDFName.of('Names'))); + const Names = pdfDoc.catalog.lookup(PDFName.of('Names'), PDFDict); + expect(Names.has(PDFName.of('JavaScript'))); + const Javascript = Names.lookup(PDFName.of('JavaScript'), PDFDict); + expect(Javascript.has(PDFName.of('Names'))); + const JSNames = Javascript.lookup(PDFName.of('Names'), PDFArray); + expect(JSNames.get(0)).toBe(PDFName.of('main')); + }); + + it(`does not overwrite scripts`, async () => { + const pdfDoc = await PDFDocument.create(); + pdfDoc.addJavascript('first', 'console.show(); console.println("First"'); + pdfDoc.addJavascript( + 'second', + 'console.show(); console.println("Second"', + ); + const Names = pdfDoc.catalog.lookup(PDFName.of('Names'), PDFDict); + const Javascript = Names.lookup(PDFName.of('JavaScript'), PDFDict); + const JSNames = Javascript.lookup(PDFName.of('Names'), PDFArray); + expect(JSNames.get(0)).toBe(PDFName.of('first')); + expect(JSNames.get(2)).toBe(PDFName.of('second')); + }); + }); });