This repository contains the webpack bundler, npm package manager, and tests for the vector appearances API. It also includes sample code and a local npm package tester.
Annotation appearances are a special way of rendering an annotation inside a PDF and can be useful in scenarios where users require custom annotations. Usually appearances involve rasterization, leading to blurriness of the annotation when you zoom in. Note that this library generates actual PDF drawing calls to create a PDF with vector graphics.
The canvasToPDF api allows a user to call canvas methods on a pdf to create vector appearances. It takes in a function containing canvas methods as a parameter and outputs a blob. A user can then optionally use the FileSaver dependency to convert the blob to a pdf and download it as it does in this repo. Or a user can use the blob with other apis such as Pdftron's WebViewer to make further modifications.
const blob = await canvasToPDF(draw);
const doc = await Core.createDocument(blob, { extension: "pdf" });
annotation.addCustomAppearance(doc, { pageNumber: 1 });
If you would like to see how this api works, a demo with React and WebViewer is available here
Internally, CanvasToPDF uses modified versions of canvas2pdf and PDFKit to call on actual PDF drawing methods. These PDF drawing methods are what enables vector appearances to be created. Canvas2pdf initializes a PDFDocument and calls on PDF drawing methods from PDFKit that are roughly equivalent to regular canvas methods. Note that because they are not perfectly equivalent, appearances produced by CanvasToPDF may be slightly off as in the case of calling arc
or bezierCurveTo
.
If a function follows this.doc
like the stroke
function below, then this is calling a PDFKit function. On the other hand, this.restorePath
is not, so the restorePath
is a function attached to the canvas2pdf.PdfContext
itself.
canvas2pdf.PdfContext.prototype.stroke = function () {
this.doc.stroke();
this.restorePath();
};
CanvasToPDF has several improvements compared to canvas2pdf. One such improvement is that it does not have problem where calling fill or stroke consecutively only executes the first method. In this case, the obstacle arises from the fact that fill and stroke close the path, so the idea was that we would restore the path after either of them is called. How we can implement it is to have a stack variable that would keep track of our current path where every time a path modifying canvas method is called, we would add the method to the stack. When either fill or stroke is called, we would call all methods inside the stack to restore the path. Since methods that close the path such as fill or stroke are not added to the stack, the path would be “open” for the next fill or stroke to take effect.
Unmodified Canvas2PDF moveTo method
canvas2pdf.PdfContext.prototype.moveTo = function (x, y) {
this.doc.moveTo(x, y);
};
Modified Canvas2PDF moveTo method
canvas2pdf.PdfContext.prototype.moveTo = function (x, y) {
this.addToPath("moveTo", arguments);
this.doc.moveTo(x, y);
};
Custom addToPath and restorePath implementation
canvas2pdf.PdfContext.prototype.addToPath = function (command, params) {
this.stack.push({ command, params });
};
canvas2pdf.PdfContext.prototype.restorePath = function () {
this.stack.forEach((key) => {
this.doc[key.command].apply(this.doc, key.params);
});
};
Stroke and Fill using restorePath function
canvas2pdf.PdfContext.prototype.stroke = function () {
this.doc.stroke();
this.restorePath();
};
canvas2pdf.PdfContext.prototype.fill = function () {
this.doc.fill();
this.restorePath();
};
For more information, read Developing Client Side Tool to Create Vector PDF Appearances on Confluence.
Requires:
- Node v16+
- VSCode Live Server or http-server for testing local npm packages in npm/test-folders
The bundle folder has all the dependencies of the CanvasToPDF api, such as canvas2pdf and pdfkit. It is responsible for bundling all dependencies as well as the api into a single canvasToPDF.js file under npm/package. This canvasToPDF.js file under npm/package is used by both the jasmine-tests and npm package manager.
Both the canvas2pdf and pdfkit dependencies have been modified. Thus, they're reliant on forked versions of the originals held by PDFTron. You will find both dependencies in PDFTron/canvas2pdf in PDFTron's GitHub.
You must rebuild the bundle folder each time you make change to any of its files. Changes in the canvasToPDF.js file will be reflected immediately in both these folders, provided you've run the setup and npm local publish commands respectively.
To verify the CanvasToPDF api is working as expected, you can run the automated tests in the jasmine-tests folder. The tests are implemented using WebViewer's loadCanvas method, thus converting the blob that is returned by the CanvasToPDF api into a canvas. The data URL of each test case's annotation was stored to form regression tests.
To check that the npm package is working as expected, you can open live server and go to npm/test-folder in the browser. A PDF download should occur immediately with the expected vector appearance. The code responsible for the actual drawing is in the script.js under npm/test-folder. Modifying the draw command will change the annotation drawn the CanvasToPDF api in the live server for test-folder.
Example of modified draw function in script.js:
const draw = (ctx) => {
for (let i = 0; i < 12; i++) {
for (let j = 0; j < 12; j++) {
ctx.strokeStyle = `rgb(
0,
${Math.floor(255 - 42.5 * i)},
${Math.floor(255 - 42.5 * j)})`;
ctx.beginPath();
ctx.arc(25 + j * 40, 25 + i * 40, 15, 0, Math.PI * 2, true);
ctx.stroke();
}
}
};
Expected annotation from modified draw function
The npm/test-folder is also a sample for how you may setup your code to use CanvasToPDF. If not using the CanvasToPDF npm package, you will likely have to use a module bundler such as Webpack to load the api in.
From the project root directory run
npm run setup
Note you must run this command every time you change any file under the bundle folder.
npm run build
Will run jasmine tests. Expect an automated browser to open.
npm run test
- Update the draw function in script.js file
- Run
npm run build
from thenpm/test-folder
directory - Open live server or http-server on npm/test-folder
Will link files in npm/package to the node_modules in npm/test-folder. Any changes in npm/package will be reflected real time in test-folder.
npm run local-publish
Will publish the package privately to npm. Make sure to update the version in package.json in npm/package or publishing will fail.
npm run publish-private
Will publish the package publicly to npm. Make sure to update the version in package.json in npm/package or publishing will fail.
npm run publish-public