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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Unreleased

- [BREAKING CHANGE] Remove the virtual file system (`pdfkit/virtual-fs`). Browser builds no longer depend on `fs`: pass a `Uint8Array`, `ArrayBuffer` or data URL to `registerFont`, `image` and `file` instead of a path
- [BREAKING CHANGE] Restrict AcroForm options to documented mappings and explicit escape hatches.
- [BREAKING CHANGE] Stop automatically uppercasing annotation option keys.
- Do not mutate options passed to `doc.annotate()` and its convenience methods (link, note, strike, lineAnnotation, rectAnnotation, ellipseAnnotation, textAnnotation, fileAnnotation)
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,9 @@ stream.on('finish', function() {

You can see an interactive in-browser demo of PDFKit [here](http://pdfkit.org/demo/browser.html).

Note that in order to Browserify a project using PDFKit, you need to install the `brfs` module,
which is used to load built-in font data into the package. It is listed as a `devDependency` in
PDFKit's `package.json`, so it isn't installed by default for Node users.
If you forget to install it, Browserify will print an error message.
Note that the browser build has no access to the file system: passing a file path to
`registerFont`, `image` or `file` throws. Pass a `Uint8Array`, an `ArrayBuffer` or a
data URL instead.

## Documentation

Expand Down
10 changes: 4 additions & 6 deletions examples/webpack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,20 @@ Simple example of using PdfKit with webpack
### Features

- Minimal webpack 5 setup
- Automatically register binary files added to static-assets folder
- Register AFM fonts provided by pdfkit
- Shows how to load and register files lazily
- Bundle binary files added to static-assets folder
- Register standard fonts provided by pdfkit
- Shows how to load files lazily

### Technical details

[`webpack.config.js`](webpack.config.js)

- add alias to map `fs` calls to pdfkit virtual file system [implementation](../../lib/virtual-fs.js)
- ignore crypto package to save bundle file size
- add aliases to native node packages (buffer, stream, zlib, util, assert)
- configure `*.afm` files to be imported as text
- configure all files in `src/static-assets` folder to be imported encoded as base64
- configure all files in `src/lazy-assets` folder to be imported as URLs
- convert binary files used by linebreak and fontkit to base64

### Caveats

The strategy to register binary files and AFM fonts inlines them in source code, increasing the bundle size significantly
The strategy to bundle binary files and standard fonts inlines them in source code, increasing the bundle size significantly
21 changes: 21 additions & 0 deletions examples/webpack/src/assets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { registerStdFonts } from 'pdfkit';
import Courier from 'pdfkit/standard-fonts/Courier';
import CourierBold from 'pdfkit/standard-fonts/CourierBold';
import Helvetica from 'pdfkit/standard-fonts/Helvetica';
// webpack is configured to load files in static-assets as base64
import robotoRegular from './static-assets/fonts/Roboto-Regular.ttf';
import bee from './static-assets/images/bee.png';

// is good practice to register only required fonts to avoid the bundle size increase too much
registerStdFonts(Courier, CourierBold, Helvetica);

const toBytes = base64 =>
Uint8Array.from(atob(base64), char => char.charCodeAt(0));

export const fonts = {
Roboto: toBytes(robotoRegular)
};

export const images = {
bee: `data:image/png;base64,${bee}`
};
25 changes: 16 additions & 9 deletions examples/webpack/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import fs from 'fs';
import PDFDocument from 'pdfkit';
import ace from 'brace';
import 'brace/mode/javascript';
Expand All @@ -7,15 +6,15 @@ import { waitForData } from './pdfkitHelpers.js';
import { fetchFile } from './httpHelpers.js';
// testImage is an URL
import testImageURL from './lazy-assets/test.jpeg';
// bundle font and image files and register them in the virtual fs
import './registerStaticFiles.js';
// bundled font and image files
import { fonts, images } from './assets.js';

var lorem =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;\nMauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.';

fetchFile(testImageURL)
.then(testImageData => {
fs.writeFileSync('images/test.jpg', testImageData);
images.test = testImageData;
})
.catch(error => {
console.error(error);
Expand All @@ -24,7 +23,7 @@ fetchFile(testImageURL)
var initialFnCode = `// create a document
var doc = new PDFDocument();

doc.registerFont('Roboto', 'fonts/Roboto-Regular.ttf');
doc.registerFont('Roboto', fonts.Roboto);

// draw some text
doc.fontSize(25).text('Here is some vector graphics...', 100, 80);
Expand Down Expand Up @@ -68,7 +67,7 @@ doc
.fontSize(25)
.font('Courier')
.text('And an image...')
.image('images/bee.png');
.image(images.bee);

doc.font('Courier-Bold').text('Finish...');

Expand All @@ -80,7 +79,7 @@ doc
.text('Not yet. Lets try to show an image lazy loaded');

try {
doc.image('images/test.jpg');
doc.image(images.test);
} catch (error) {
doc.moveDown().text(\`\${error}\`);
doc.text('Image not loaded. Try again later.');
Expand All @@ -99,8 +98,16 @@ waitForData(doc)
doc.end();`;

function executeFn(code, PDFDocument, lorem, waitForData, iframe) {
var fn = new Function('PDFDocument', 'lorem', 'waitForData', 'iframe', code);
fn(PDFDocument, lorem, waitForData, iframe);
var fn = new Function(
'PDFDocument',
'lorem',
'waitForData',
'iframe',
'fonts',
'images',
code
);
fn(PDFDocument, lorem, waitForData, iframe, fonts, images);
}

var editor = ace.edit('editor');
Expand Down
33 changes: 0 additions & 33 deletions examples/webpack/src/registerStaticFiles.js

This file was deleted.

6 changes: 0 additions & 6 deletions examples/webpack/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ module.exports = {
],
resolve: {
symlinks: false,
alias: {
// maps fs to a virtual one allowing to register file content dynamically
fs: __dirname + '/../../js/virtual-fs.js'
},
fallback: {
// crypto module is not necessary at browser
crypto: false,
Expand All @@ -31,8 +27,6 @@ module.exports = {
},
module: {
rules: [
// bundle and load afm files verbatim
{ test: /\.afm$/, type: 'asset/source' },
// bundle and load binary files inside static-assets folder as base64
{
test: /src[/\\]static-assets/,
Expand Down
2 changes: 1 addition & 1 deletion lib/font_factory.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fs from 'fs';
import fs from '#fs';
import { create } from 'fontkit';
import StandardFont from './font/standard';
import EmbeddedFont from './font/embedded';
Expand Down
11 changes: 11 additions & 0 deletions lib/fs/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const unsupported = (path) => {
throw new Error(
`Cannot read '${path}': file paths are not supported outside of Node. ` +
'Pass a Uint8Array, ArrayBuffer or a data URL instead.',
);
};

export default {
readFileSync: unsupported,
statSync: unsupported,
};
1 change: 1 addition & 0 deletions lib/fs/node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'fs';
2 changes: 1 addition & 1 deletion lib/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ PDFImage - embeds images in PDF documents
By Devon Govett
*/

import fs from 'fs';
import fs from '#fs';
import { fromBase64 } from './binary';
import JPEG from './image/jpeg';
import PNG from './image/png';
Expand Down
2 changes: 1 addition & 1 deletion lib/mixins/attachments.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fs from 'fs';
import fs from '#fs';
import { md5Hex } from '../crypto/md5';
import { escapeName } from '../object.js';

Expand Down
11 changes: 7 additions & 4 deletions lib/mixins/pdfa.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import fs from 'fs';
import { fromBase64 } from '../binary';
import iccProfileBase64 from './data/sRGB_IEC61966_2_1.icc';

let iccProfile;

export default {
initPDFA(pSubset) {
Expand All @@ -20,9 +23,9 @@ export default {
},

_addColorOutputIntent() {
const iccProfile = fs.readFileSync(
`${__dirname}/data/sRGB_IEC61966_2_1.icc`,
);
if (!iccProfile) {
iccProfile = fromBase64(iccProfileBase64);
}

const colorProfileRef = this.ref({
Length: iccProfile.length,
Expand Down
34 changes: 0 additions & 34 deletions lib/virtual-fs.js

This file was deleted.

14 changes: 4 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"@rollup/plugin-node-resolve": "^16.0.3",
"blob-stream": "^0.1.3",
"brace": "^0.11.1",
"brfs": "~2.0.2",
"browserify": "^17.0.1",
"canvas": "^3.2.3",
"codemirror": "~5.65.21",
Expand All @@ -45,7 +44,6 @@
"prettier": "3.4.2",
"pug": "^3.0.4",
"rollup": "^4.61.1",
"rollup-plugin-copy": "^3.5.0",
"vitest": "^4.1.8"
},
"dependencies": {
Expand Down Expand Up @@ -82,9 +80,6 @@
},
"default": "./js/pdfkit.browser.mjs"
},
"./virtual-fs": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets keep virtual fs for now. Is not bundled / used by default and does not hurt at all.

You can remove the usage in webpack

Having a vfs is the only way to register a globally accessible resource in browser.

So my idea is using one in browser #fs and exporting as vfs. This can be done later

@diegomura diegomura Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont mind restoring virtual fs but after this change what purpose would it serve if runtime doesn't actually use fs in browser?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we want for this library to be truly isomorphic we should stop relying on node deps at all, that includes virtual-fs workarounds

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think better. You can keep removal. I have another idea to provide a global file registration

"default": "./js/virtual-fs.js"
},
"./standard-fonts/Courier": {
"require": "./js/standard-fonts/Courier.cjs",
"default": "./js/standard-fonts/Courier.mjs"
Expand Down Expand Up @@ -143,6 +138,10 @@
}
},
"imports": {
"#fs": {
"node": "./lib/fs/node.js",
"default": "./lib/fs/browser.js"
},
"#zlib": {
"node": "./lib/zlib/node.js",
"default": "./lib/zlib/browser.js"
Expand All @@ -152,11 +151,6 @@
"default": "./js/standard-fonts/*.mjs"
}
},
"browserify": {
"transform": [
"brfs"
]
},
"engine": [
"node >= v20.0.0"
],
Expand Down
Loading
Loading