Skip to content

Commit 73b2fad

Browse files
committed
✨ Support embedded files
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.
1 parent 24cc6d9 commit 73b2fad

File tree

5 files changed

+115
-0
lines changed

5 files changed

+115
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## [0.5.6] - Unreleased
44

5+
### Added
6+
7+
- Support for embedded files via the `embeddedFiles` property in the
8+
document definition.
9+
510
## [0.5.5] - 2024-12-18
611

712
The minimum EcmaScript version has been raised to ES2022.

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,40 @@ The following properties are supported:
594594
- `producer`: The name of the application that converted the original
595595
content into a PDF.
596596
597+
## Embedded files
598+
599+
Supplementary files can be stored directly within a PDF document. This
600+
can be useful for creating self-contained documents, such as for
601+
archival purposes. Those files can be added to the document using the
602+
`embeddedFiles` property, which accepts an array of objects, each
603+
representing a file with the following properties:
604+
605+
- `content`: The binary content of the file as a `Uint8Array`.
606+
- `fileName`: The name of the file as it will appear in the
607+
list of attachments in the PDF viewer.
608+
- `mimeType`: The MIME type of the file.
609+
- `description` (optional): A brief description of the file's content or
610+
purpose. This information can be displayed to the user in the PDF
611+
viewer.
612+
- `creationDate` (optional): The date and time when the file was created.
613+
- `modificationDate` (optional): The date and time when the file was last
614+
615+
```ts
616+
const document = {
617+
content: [text('Hello World!')],
618+
embeddedFiles: [
619+
{
620+
fileName: "Study-Results-2025.csv",
621+
mimeType: "text/csv",
622+
content: /* binary data of the data */,
623+
mimeType: 'image/png',
624+
description: "CSV file containing the result data of the 2025 study.",
625+
creationDate: new Date("2025-01-12"),
626+
},
627+
],
628+
};
629+
```
630+
597631
## Dev tools
598632
599633
### Visual Debugging

src/api/document.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ export type DocumentDefinition = {
8484
*/
8585
customData?: Record<`XX${string}`, string | Uint8Array>;
8686

87+
/**
88+
* Files to be stored directly within a PDF document. These files can
89+
* be displayed and extracted by PDF viewers and other tools.
90+
*/
91+
embeddedFiles?: EmbeddedFile[];
92+
8793
dev?: {
8894
/**
8995
* When set to true, additional guides are drawn to help analyzing
@@ -95,6 +101,40 @@ export type DocumentDefinition = {
95101
};
96102
};
97103

104+
export type EmbeddedFile = {
105+
/**
106+
* The binary content of the file.
107+
*/
108+
content: Uint8Array;
109+
110+
/**
111+
* The MIME type of the file.
112+
*/
113+
mimeType: string;
114+
115+
/**
116+
* The name of the file as it will appear in the list of attachments
117+
* in the PDF viewer.
118+
*/
119+
fileName: string;
120+
121+
/**
122+
* A brief description of the file's content or purpose. This text
123+
* can also be displayed by the PDF viewer.
124+
*/
125+
description?: string;
126+
127+
/**
128+
* The date and time when the file was created.
129+
*/
130+
creationDate?: Date;
131+
132+
/**
133+
* The date and time when the file was last modified.
134+
*/
135+
modificationDate?: Date;
136+
};
137+
98138
/**
99139
* @deprecated Use `InfoProps` instead.
100140
*/

src/read-document.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ export type DocumentDefinition = {
2323
footer?: (info: PageInfo) => Block;
2424
content: Block[];
2525
customData?: Record<string, string | Uint8Array>;
26+
embeddedFiles?: {
27+
content: Uint8Array;
28+
fileName: string;
29+
mimeType: string;
30+
description?: string;
31+
creationDate?: Date;
32+
modificationDate?: Date;
33+
}[];
2634
};
2735

2836
export type Metadata = {
@@ -52,6 +60,7 @@ export function readDocumentDefinition(input: unknown): DocumentDefinition {
5260
defaultStyle: optional(readInheritableAttrs),
5361
dev: optional(types.object({ guides: optional(types.boolean()) })),
5462
customData: optional(readCustomData),
63+
embeddedFiles: optional(types.array(readEmbeddedFiles)),
5564
});
5665
const tBlock = (block: unknown) => readBlock(block, def1.defaultStyle);
5766
const def2 = readObject(input, {
@@ -92,3 +101,20 @@ function readCustomData(input: unknown) {
92101
Object.entries(readObject(input)).map(([key, value]) => [key, readAs(value, key, readValue)]),
93102
);
94103
}
104+
105+
function readEmbeddedFiles(input: unknown) {
106+
return readObject(input, {
107+
url: optional(types.string()),
108+
content: required(readData),
109+
fileName: required(types.string()),
110+
mimeType: required(types.string()),
111+
description: optional(types.string()),
112+
creationDate: optional(types.date()),
113+
modificationDate: optional(types.date()),
114+
});
115+
}
116+
117+
function readData(input: unknown): Uint8Array {
118+
if (input instanceof Uint8Array) return input;
119+
throw typeError('Uint8Array', input);
120+
}

src/render/render-document.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ export async function renderDocument(def: DocumentDefinition, pages: Page[]): Pr
1212
setCustomData(def.customData, pdfDoc);
1313
}
1414
pages.forEach((page) => renderPage(page, pdfDoc));
15+
16+
for (const file of def.embeddedFiles ?? []) {
17+
await pdfDoc.attach(file.content, file.fileName, {
18+
mimeType: file.mimeType,
19+
description: file.description,
20+
creationDate: file.creationDate,
21+
modificationDate: file.modificationDate,
22+
});
23+
}
24+
1525
const idInfo = {
1626
creator: 'pdfmkr',
1727
time: new Date().toISOString(),

0 commit comments

Comments
 (0)