Skip to content

Commit

Permalink
✨ Support embedded files
Browse files Browse the repository at this point in the history
Embedding files in a PDF document can be useful for archival purposes.
This commit adds support for embedded files via the `embeddedFiles`
property in the document definition.
  • Loading branch information
ralfstx committed Jan 18, 2025
1 parent 24cc6d9 commit 8fa013c
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [0.5.6] - Unreleased

### Added

- Support for embedded files via the `embeddedFiles` property in the
document definition.

## [0.5.5] - 2024-12-18

The minimum EcmaScript version has been raised to ES2022.
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,39 @@ The following properties are supported:
- `producer`: The name of the application that converted the original
content into a PDF.
## Embedded files
Supplementary files can be stored directly within a PDF document. This
can be useful for creating self-contained documents, such as for
archival purposes. Those files can be added to the document using the
`embeddedFiles` property, which accepts an array of objects, each
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.
- `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
```ts
const document = {
content: [text('Hello World!')],
embeddedFiles: [
{
'logo.png': {
content: await readFile('path/to/logo.png'),
description: 'The company logo',
mimeType: 'image/png',
},
},
],
};
```
## Dev tools
### Visual Debugging
Expand Down
40 changes: 40 additions & 0 deletions src/api/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ export type DocumentDefinition = {
*/
customData?: Record<`XX${string}`, string | Uint8Array>;

/**
* Files to be stored directly within a PDF document. These files can
* be displayed and extracted by PDF viewers and other tools.
*/
embeddedFiles?: EmbeddedFile[];

dev?: {
/**
* When set to true, additional guides are drawn to help analyzing
Expand All @@ -95,6 +101,40 @@ export type DocumentDefinition = {
};
};

export type EmbeddedFile = {
/**
* The binary content of the file.
*/
content: Uint8Array;

/**
* The name of the file as it will appear in the list of attachments
* in the PDF viewer.
*/
fileName: string;

/**
* The MIME type of the file.
*/
mimeType: string;

/**
* A brief description of the file's content or purpose. This
* information an be displayed to the user in the PDF viewer.
*/
description?: string;

/**
* The date and time when the file was created.
*/
creationDate?: Date;

/**
* The date and time when the file was last modified.
*/
modificationDate?: Date;
};

/**
* @deprecated Use `InfoProps` instead.
*/
Expand Down
26 changes: 26 additions & 0 deletions src/read-document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export type DocumentDefinition = {
footer?: (info: PageInfo) => Block;
content: Block[];
customData?: Record<string, string | Uint8Array>;
embeddedFiles?: {
content: Uint8Array;
fileName: string;
mimeType: string;
description?: string;
creationDate?: Date;
modificationDate?: Date;
}[];
};

export type Metadata = {
Expand Down Expand Up @@ -52,6 +60,7 @@ export function readDocumentDefinition(input: unknown): DocumentDefinition {
defaultStyle: optional(readInheritableAttrs),
dev: optional(types.object({ guides: optional(types.boolean()) })),
customData: optional(readCustomData),
embeddedFiles: optional(types.array(readEmbeddedFiles)),
});
const tBlock = (block: unknown) => readBlock(block, def1.defaultStyle);
const def2 = readObject(input, {
Expand Down Expand Up @@ -92,3 +101,20 @@ function readCustomData(input: unknown) {
Object.entries(readObject(input)).map(([key, value]) => [key, readAs(value, key, readValue)]),
);
}

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()),
});
}

function readData(input: unknown): Uint8Array {
if (input instanceof Uint8Array) return input;
throw typeError('Uint8Array', input);
}
10 changes: 10 additions & 0 deletions src/render/render-document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ export async function renderDocument(def: DocumentDefinition, pages: Page[]): Pr
setCustomData(def.customData, pdfDoc);
}
pages.forEach((page) => renderPage(page, pdfDoc));

for (const file of def.embeddedFiles ?? []) {
await pdfDoc.attach(file.content, file.fileName, {
mimeType: file.mimeType,
description: file.description,
creationDate: file.creationDate,
modificationDate: file.modificationDate,
});
}

const idInfo = {
creator: 'pdfmkr',
time: new Date().toISOString(),
Expand Down

0 comments on commit 8fa013c

Please sign in to comment.