Skip to content
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
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -603,14 +603,18 @@ archival purposes. Those files can be added to the document using the
representing a file with the following properties:

- `content`: The binary content of the file as a `Uint8Array`.
- `fileName`: The name of the file as it will appear in the
list of attachments in the PDF viewer.
- `fileName`: The name of the file as it will appear in the list of
attachments in the PDF viewer.
- `mimeType`: The MIME type of the file.
- `description` (optional): A brief description of the file's content or
purpose. This information can be displayed to the user in the PDF
viewer.
- `creationDate` (optional): The date and time when the file was created.
- `modificationDate` (optional): The date and time when the file was last
- `creationDate` (optional): The date and time when the file was
created.
- `modificationDate` (optional): The date and time when the file was
last modified.
- `relationship` (optional): A name that specifies the relationship
between the file and the document.

```ts
const document = {
Expand Down
29 changes: 29 additions & 0 deletions src/api/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,30 @@ export type DocumentDefinition = {
};
};

/**
* Describes the relationship between the embedded file and the PDF
* document.
* - `Source`: The embedded file is the source material for the
* document.
* - `Data`: The embedded file contains data related to the document.
* - `Alternative`: The embedded file is an alternative representation
* of the document.
* - `Supplement`: The embedded file supplements the document.
* - `EncryptedPayload`: The embedded file is an encrypted payload.
* - `FormData`: The embedded file contains form data.
* - `Schema`: The embedded file contains a schema.
* - `Unspecified`: No specific relationship.
*/
export type FileRelationShip =
| 'Source'
| 'Data'
| 'Alternative'
| 'Supplement'
| 'EncryptedPayload'
| 'FormData'
| 'Schema'
| 'Unspecified';

export type EmbeddedFile = {
/**
* The binary content of the file.
Expand Down Expand Up @@ -133,6 +157,11 @@ export type EmbeddedFile = {
* The date and time when the file was last modified.
*/
modificationDate?: Date;

/**
* The relationship between the file and the PDF document.
*/
relationship?: FileRelationShip;
};

/**
Expand Down
7 changes: 7 additions & 0 deletions src/read-document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ describe('readDocumentDefinition', () => {
]);
});

it(`includes onRenderDocument hook`, () => {
const onRenderDocument = () => {};
const def = readDocumentDefinition({ ...input, onRenderDocument });

expect(def.onRenderDocument).toBe(onRenderDocument);
});

(['header', 'footer'] as const).forEach((name) => {
it(`supports dynamic ${name}`, () => {
const defaultStyle = { fontSize: 23 };
Expand Down
26 changes: 25 additions & 1 deletion src/read-document.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type { PDFDocument } from 'pdf-lib';

import type { FileRelationShip } from './api/document.ts';
import type { BoxEdges, Size } from './box.ts';
import { parseEdges } from './box.ts';
import type { FontDef } from './fonts.ts';
Expand Down Expand Up @@ -30,7 +33,9 @@ export type DocumentDefinition = {
description?: string;
creationDate?: Date;
modificationDate?: Date;
relationship?: FileRelationShip;
}[];
onRenderDocument?: (pdfDoc: PDFDocument) => void;
};

export type Metadata = {
Expand Down Expand Up @@ -61,6 +66,7 @@ export function readDocumentDefinition(input: unknown): DocumentDefinition {
dev: optional(types.object({ guides: optional(types.boolean()) })),
customData: optional(readCustomData),
embeddedFiles: optional(types.array(readEmbeddedFiles)),
onRenderDocument: optional(),
});
const tBlock = (block: unknown) => readBlock(block, def1.defaultStyle);
const def2 = readObject(input, {
Expand Down Expand Up @@ -102,15 +108,33 @@ function readCustomData(input: unknown) {
);
}

/**
* From the PDF-A3 specification, section **3.1. Requirements -
* General**. See:
* https://www.pdfa.org/wp-content/uploads/2018/10/PDF20_AN002-AF.pdf
*/
const readFileRelationship = types.string({
enum: [
'Source',
'Data',
'Alternative',
'Supplement',
'EncryptedPayload',
'FormData',
'Schema',
'Unspecified',
],
});

function readEmbeddedFiles(input: unknown) {
return readObject(input, {
url: optional(types.string()),
content: required(readData),
fileName: required(types.string()),
mimeType: required(types.string()),
description: optional(types.string()),
creationDate: optional(types.date()),
modificationDate: optional(types.date()),
relationship: optional(readFileRelationship),
});
}

Expand Down
16 changes: 16 additions & 0 deletions src/render/render-document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,20 @@ describe('renderDocument', () => {
expect(lookup('XXFoo').getContentsString()).toBe('Foo');
expect(lookup('XXBar').getContents()).toEqual(Uint8Array.of(1, 2, 3));
});

it('calls custom render hook', async () => {
const def = {
content: [],
onRenderDocument: (pdfDoc: PDFDocument) => {
pdfDoc.setTitle('Test Title');
},
};

const pdfData = await renderDocument(def, []);

const pdfDoc = await PDFDocument.load(pdfData, { updateMetadata: false });
const infoDict = pdfDoc.context.lookup(pdfDoc.context.trailerInfo.Info) as PDFDict;
const getInfo = (name: string) => infoDict.get(PDFName.of(name));
expect(getInfo('Title')).toEqual(PDFHexString.fromText('Test Title'));
});
});
4 changes: 3 additions & 1 deletion src/render/render-document.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PDFDict } from 'pdf-lib';
import type { AFRelationship, PDFDict } from 'pdf-lib';
import { PDFDocument, PDFHexString, PDFName } from 'pdf-lib';

import type { Page } from '../page.ts';
Expand All @@ -19,9 +19,11 @@ export async function renderDocument(def: DocumentDefinition, pages: Page[]): Pr
description: file.description,
creationDate: file.creationDate,
modificationDate: file.modificationDate,
afRelationship: file.relationship as AFRelationship | undefined,
});
}

def.onRenderDocument?.(pdfDoc);
const idInfo = {
creator: 'pdfmkr',
time: new Date().toISOString(),
Expand Down
Loading