diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index 7333440bd..d7708724f 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -1,4 +1,4 @@ -import { Color, rgb } from 'src/api/colors'; +import { Color, colorToComponents, rgb } from 'src/api/colors'; import { drawImage, drawLine, @@ -24,6 +24,7 @@ import { PDFPageDrawEllipseOptions, PDFPageDrawImageOptions, PDFPageDrawLineOptions, + PDFPageDrawLinkOptions, PDFPageDrawPageOptions, PDFPageDrawRectangleOptions, PDFPageDrawSquareOptions, @@ -42,6 +43,7 @@ import { PDFRef, PDFDict, PDFArray, + PDFString, } from 'src/core'; import { assertEachIs, @@ -1325,6 +1327,79 @@ export default class PDFPage { ); } + /** + * Draw a link on this page. + * + * In the PDF specification,you define a rectangular section of the page + * that when clicked should activate the link + * + * For example: + * ```js + * page.drawLink('https://pdf-lib.js.org', { + * x: 50, + * y: 50, + * width: 200, + * height: 100 + * }) + * ``` + * @param options The options to be used when drawing the line. + * @see https://github.com/Hopding/pdf-lib/issues/555#issuecomment-670195238 + */ + drawLink(link: string, options: PDFPageDrawLinkOptions): void { + assertIs(options.x, 'options.x', ['number']); + assertIs(options.y, 'options.y', ['number']); + assertIs(options.width, 'options.width', ['number']); + assertIs(options.height, 'options.height', ['number']); + + assertOrUndefined(options.borderWidth, 'options.borderWidth', ['number']); + assertOrUndefined(options.color, 'options.color', [[Object, 'Color']]); + + const pdfRef: PDFRef = this.doc.context.register( + this.doc.context.obj({ + Type: 'Annot', + Subtype: 'Link', + Rect: [ + // lower left x coord + options.x, + // lower left y coord + options.y, + // upper right x coord + options.x + options.width, + // upper right y coord + options.y + options.height, + ], + + // The three parameters are: + // * horizontal corner radius, + // * vertical corner radius, and + // * border width + // + // Default to a square border, 0 width. + Border: [0, 0, options.borderWidth ?? 0], + + /* Default to transparent */ + C: options.color ? colorToComponents(options.color) : [], + + // Name unique identifier + // NM: string + + /* Page to be visited when the link is clicked */ + A: { + Type: 'Action', + S: 'URI', + URI: PDFString.of(link), + }, + }), + ); + + // Annots Dictionary may or may not exist--create it if it doesn't. + if (this.node.Annots()) { + this.node.Annots()?.push(pdfRef); + } else { + this.node.set(PDFName.Annots, this.doc.context.obj([pdfRef])); + } + } + /** * Draw a rectangle on this page. For example: * ```js diff --git a/src/api/PDFPageOptions.ts b/src/api/PDFPageOptions.ts index 6ddd8357f..c35113cc0 100644 --- a/src/api/PDFPageOptions.ts +++ b/src/api/PDFPageOptions.ts @@ -88,6 +88,15 @@ export interface PDFPageDrawLineOptions { blendMode?: BlendMode; } +export interface PDFPageDrawLinkOptions { + x: number; + y: number; + width: number; + height: number; + borderWidth?: number; + color?: Color; +} + export interface PDFPageDrawRectangleOptions { x?: number; y?: number; diff --git a/tests/api/PDFPage.spec.ts b/tests/api/PDFPage.spec.ts index 3a5f0a949..e386ac7aa 100644 --- a/tests/api/PDFPage.spec.ts +++ b/tests/api/PDFPage.spec.ts @@ -1,9 +1,15 @@ import fs from 'fs'; -import { PDFArray, PDFDocument, PDFName, StandardFonts } from 'src/index'; +import { + PDFArray, + PDFDocument, + PDFName, + PDFRef, + StandardFonts, +} from 'src/index'; const birdPng = fs.readFileSync('assets/images/greyscale_bird.png'); -describe(`PDFDocument`, () => { +describe(`PDFPage`, () => { describe(`getSize() method`, () => { it(`returns the width and height of the the page's MediaBox`, async () => { const pdfDoc = await PDFDocument.create(); @@ -148,4 +154,22 @@ describe(`PDFDocument`, () => { expect(key1).not.toEqual(key2); expect(page2.node.normalizedEntries().Font.keys()).toEqual([key1, key2]); }); + + it(`drawLink() creates a PDFRef inside the Annots node`, async () => { + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage(); + + page.drawLink('https://pdf-lib.js.org', { + x: 5, + y: 5, + width: 20, + height: 50, + }); + + const annots = page.node.normalizedEntries().Annots; + expect(annots).toBeInstanceOf(PDFArray); + expect(annots.size()).toEqual(1); + const pdfRef = annots.asArray().pop(); + expect(pdfRef).toBeInstanceOf(PDFRef); + }); });