Skip to content

Commit 4eb4692

Browse files
authored
Support Deno (#448)
* Add deno tests * Add deno usage section
1 parent ff7e0f5 commit 4eb4692

19 files changed

+1913
-14
lines changed

README.md

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<strong>Create and modify PDF documents in any JavaScript environment.</strong>
99
</div>
1010
<div align="center">
11-
Designed to work in any modern JavaScript runtime. Tested in Node, Browser, and React Native environments.
11+
Designed to work in any modern JavaScript runtime. Tested in Node, Browser, Deno, and React Native environments.
1212
</div>
1313

1414
<br />
@@ -55,6 +55,7 @@
5555
- [Set Document Metadata](#set-document-metadata)
5656
- [Read Document Metadata](#read-document-metadata)
5757
- [Draw SVG Paths](#draw-svg-paths)
58+
- [Deno Usage](#deno-usage)
5859
- [Complete Examples](#complete-examples)
5960
- [Installation](#installation)
6061
- [Documentation](#documentation)
@@ -555,15 +556,115 @@ const pdfBytes = await pdfDoc.save()
555556
// • Rendered in an <iframe>
556557
```
557558

559+
## Deno Usage
560+
561+
`pdf-lib` fully supports the exciting new [Deno](https://deno.land/) runtime! All of the [usage examples](#usage-examples) work in Deno. The only thing you need to do is change the imports for `pdf-lib` and `@pdf-lib/fontkit` to use the [Pika](https://www.pika.dev/) CDN, because Deno requires all modules to be referenced via URLs.
562+
563+
### Creating a Document with Deno
564+
565+
Below is the [**create document**](#create-document) example modified for Deno:
566+
567+
```js
568+
import {
569+
PDFDocument,
570+
StandardFonts,
571+
rgb,
572+
} from 'https://cdn.pika.dev/pdf-lib@^1.6.0';
573+
574+
const pdfDoc = await PDFDocument.create();
575+
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman);
576+
577+
const page = pdfDoc.addPage();
578+
const { width, height } = page.getSize();
579+
const fontSize = 30;
580+
page.drawText('Creating PDFs in JavaScript is awesome!', {
581+
x: 50,
582+
y: height - 4 * fontSize,
583+
size: fontSize,
584+
font: timesRomanFont,
585+
color: rgb(0, 0.53, 0.71),
586+
});
587+
588+
const pdfBytes = await pdfDoc.save();
589+
590+
await Deno.writeFile('out.pdf', pdfBytes);
591+
```
592+
593+
If you save this script as `create-document.ts`, you can execute it using Deno with the following command:
594+
595+
```
596+
deno run --allow-write create-document.ts
597+
```
598+
599+
The resulting `out.pdf` file will look like [this PDF](assets/pdfs/examples/create_document.pdf).
600+
601+
### Embedding a Font with Deno
602+
603+
Here's a slightly more complicated example demonstrating how to embed a font and measure text in Deno:
604+
605+
```js
606+
import {
607+
degrees,
608+
PDFDocument,
609+
rgb,
610+
StandardFonts,
611+
} from 'https://cdn.pika.dev/pdf-lib@^1.6.0';
612+
import fontkit from 'https://cdn.pika.dev/@pdf-lib/fontkit@^1.0.0';
613+
614+
const url = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf';
615+
const fontBytes = await fetch(url).then((res) => res.arrayBuffer());
616+
617+
const pdfDoc = await PDFDocument.create();
618+
619+
pdfDoc.registerFontkit(fontkit);
620+
const customFont = await pdfDoc.embedFont(fontBytes);
621+
622+
const page = pdfDoc.addPage();
623+
624+
const text = 'This is text in an embedded font!';
625+
const textSize = 35;
626+
const textWidth = customFont.widthOfTextAtSize(text, textSize);
627+
const textHeight = customFont.heightAtSize(textSize);
628+
629+
page.drawText(text, {
630+
x: 40,
631+
y: 450,
632+
size: textSize,
633+
font: customFont,
634+
color: rgb(0, 0.53, 0.71),
635+
});
636+
page.drawRectangle({
637+
x: 40,
638+
y: 450,
639+
width: textWidth,
640+
height: textHeight,
641+
borderColor: rgb(1, 0, 0),
642+
borderWidth: 1.5,
643+
});
644+
645+
const pdfBytes = await pdfDoc.save();
646+
647+
await Deno.writeFile('out.pdf', pdfBytes);
648+
```
649+
650+
If you save this script as `custom-font.ts`, you can execute it with the following command:
651+
652+
```
653+
deno run --allow-write --allow-net custom-font.ts
654+
```
655+
656+
The resulting `out.pdf` file will look like [this PDF](assets/pdfs/examples/embed_font_and_measure_text.pdf).
657+
558658
## Complete Examples
559659

560660
The [usage examples](#usage-examples) provide code that is brief and to the point, demonstrating the different features of `pdf-lib`. You can find complete working examples in the [`apps/`](apps/) directory. These apps are used to do manual testing of `pdf-lib` before every release (in addition to the [automated tests](tests/)).
561661

562-
There are currently three apps:
662+
There are currently four apps:
563663

564664
- [**`node`**](apps/node/) - contains [tests](apps/node/tests/) for `pdf-lib` in Node environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with `pdf-lib` from the filesystem. They also allow you to quickly open your PDFs in different viewers (Acrobat, Preview, Foxit, Chrome, Firefox, etc...) to ensure compatibility.
565665
- [**`web`**](apps/web/) - contains [tests](apps/web/) for `pdf-lib` in browser environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with `pdf-lib` in a browser environment.
566666
- [**`rn`**](apps/rn) - contains [tests](apps/rn/src/tests/) for `pdf-lib` in React Native environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with `pdf-lib` in a React Native environment.
667+
- [**`deno`**](apps/deno) - contains [tests](apps/deno/tests/) for `pdf-lib` in Deno environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with `pdf-lib` from the filesystem.
567668

568669
## Installation
569670

apps/deno/index.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { dirname } from 'https://deno.land/[email protected]/path/mod.ts';
2+
import { readLines } from 'https://deno.land/[email protected]/io/bufio.ts';
3+
4+
import test1 from './tests/test1.ts';
5+
import test10 from './tests/test10.ts';
6+
import test11 from './tests/test11.ts';
7+
import test12 from './tests/test12.ts';
8+
import test13 from './tests/test13.ts';
9+
import test2 from './tests/test2.ts';
10+
import test3 from './tests/test3.ts';
11+
import test4 from './tests/test4.ts';
12+
import test5 from './tests/test5.ts';
13+
import test6 from './tests/test6.ts';
14+
import test7 from './tests/test7.ts';
15+
import test8 from './tests/test8.ts';
16+
import test9 from './tests/test9.ts';
17+
18+
const promptToContinue = () => {
19+
const prompt = 'Press <enter> to run the next test...';
20+
Deno.stdout.write(new TextEncoder().encode(prompt));
21+
return readLines(Deno.stdin).next();
22+
};
23+
24+
// This needs to be more sophisticated to work on Linux and Windows as well.
25+
const openPdf = (path: string) => {
26+
if (Deno.build.os === 'darwin') {
27+
// TODO: Make this a CLI argument
28+
Deno.run({ cmd: ['open', '-a', 'Preview', path] });
29+
// Deno.run({ cmd: ['open', '-a', 'Adobe Acrobat', path] });
30+
// Deno.run({ cmd: ['open', '-a', 'Foxit Reader', path] });
31+
// Deno.run({ cmd: ['open', '-a', 'Google Chrome', path] });
32+
// Deno.run({ cmd: ['open', '-a', 'Firefox', path] });
33+
} else {
34+
const msg1 = `Note: Automatically opening PDFs currently only works on Macs. If you're using a Windows or Linux machine, please consider contributing to expand support for this feature`;
35+
const msg2 = `(https://github.com/Hopding/pdf-lib/blob/master/apps/node/index.ts#L8-L17)\n`;
36+
console.warn(msg1);
37+
console.warn(msg2);
38+
}
39+
};
40+
41+
const tempDir = () => dirname(Deno.makeTempDirSync());
42+
43+
const writePdfToTmp = (pdf: Uint8Array) => {
44+
const path = `${tempDir()}/${Date.now()}.pdf`;
45+
Deno.writeFileSync(path, pdf);
46+
return path;
47+
};
48+
49+
const readFont = (font: string) => Deno.readFileSync(`assets/fonts/${font}`);
50+
51+
const readImage = (image: string) =>
52+
Deno.readFileSync(`assets/images/${image}`);
53+
54+
const readPdf = (pdf: string) => Deno.readFileSync(`assets/pdfs/${pdf}`);
55+
56+
const decoder = new TextDecoder('utf-8');
57+
const readBase64Font = (font: string) => decoder.decode(readFont(font));
58+
const readBase64Image = (image: string) => decoder.decode(readImage(image));
59+
const readBase64Pdf = (pdf: string) => decoder.decode(readPdf(pdf));
60+
61+
const assets = {
62+
fonts: {
63+
ttf: {
64+
ubuntu_r: readFont('ubuntu/Ubuntu-R.ttf'),
65+
ubuntu_r_base64: readBase64Font('ubuntu/Ubuntu-R.ttf.base64'),
66+
bio_rhyme_r: readFont('bio_rhyme/BioRhymeExpanded-Regular.ttf'),
67+
press_start_2p_r: readFont('press_start_2p/PressStart2P-Regular.ttf'),
68+
indie_flower_r: readFont('indie_flower/IndieFlower.ttf'),
69+
great_vibes_r: readFont('great_vibes/GreatVibes-Regular.ttf'),
70+
},
71+
otf: {
72+
fantasque_sans_mono_bi: readFont(
73+
'fantasque/OTF/FantasqueSansMono-BoldItalic.otf',
74+
),
75+
apple_storm_r: readFont('apple_storm/AppleStormCBo.otf'),
76+
hussar_3d_r: readFont('hussar_3d/Hussar3DFour.otf'),
77+
source_hans_jp: readFont('source_hans_jp/SourceHanSerifJP-Regular.otf'),
78+
},
79+
},
80+
images: {
81+
jpg: {
82+
cat_riding_unicorn: readImage('cat_riding_unicorn.jpg'),
83+
cat_riding_unicorn_base64: readBase64Image(
84+
'cat_riding_unicorn.jpg.base64',
85+
),
86+
minions_laughing: readImage('minions_laughing.jpg'),
87+
cmyk_colorspace: readImage('cmyk_colorspace.jpg'),
88+
},
89+
png: {
90+
greyscale_bird: readImage('greyscale_bird.png'),
91+
greyscale_bird_base64_uri: readBase64Image(
92+
'greyscale_bird.png.base64.uri',
93+
),
94+
minions_banana_alpha: readImage('minions_banana_alpha.png'),
95+
minions_banana_no_alpha: readImage('minions_banana_no_alpha.png'),
96+
small_mario: readImage('small_mario.png'),
97+
etwe: readImage('etwe.png'),
98+
self_drive: readImage('self_drive.png'),
99+
},
100+
},
101+
pdfs: {
102+
normal: readPdf('normal.pdf'),
103+
normal_base64: readBase64Pdf('normal.pdf.base64'),
104+
with_update_sections: readPdf('with_update_sections.pdf'),
105+
with_update_sections_base64_uri: readBase64Pdf(
106+
'with_update_sections.pdf.base64.uri',
107+
),
108+
linearized_with_object_streams: readPdf(
109+
'linearized_with_object_streams.pdf',
110+
),
111+
with_large_page_count: readPdf('with_large_page_count.pdf'),
112+
with_missing_endstream_eol_and_polluted_ctm: readPdf(
113+
'with_missing_endstream_eol_and_polluted_ctm.pdf',
114+
),
115+
with_newline_whitespace_in_indirect_object_numbers: readPdf(
116+
'with_newline_whitespace_in_indirect_object_numbers.pdf',
117+
),
118+
with_comments: readPdf('with_comments.pdf'),
119+
with_cropbox: readPdf('with_cropbox.pdf'),
120+
},
121+
};
122+
123+
export type Assets = typeof assets;
124+
125+
const main = async () => {
126+
const testIdx = Deno.args[0] ? Number(Deno.args[0]) : undefined;
127+
128+
// prettier-ignore
129+
const allTests = [
130+
test1, test2, test3, test4, test5, test6, test7, test8, test9, test10,
131+
test11, test12, test13
132+
];
133+
134+
const tests = testIdx ? [allTests[testIdx - 1]] : allTests;
135+
136+
let idx = testIdx || 1;
137+
for (const test of tests) {
138+
console.log(`Running test #${idx}`);
139+
const pdfBytes = await test(assets);
140+
const path = writePdfToTmp(pdfBytes);
141+
console.log(`> PDF file written to: ${path}`);
142+
openPdf(path);
143+
idx += 1;
144+
await promptToContinue();
145+
console.log();
146+
}
147+
148+
console.log('Done!');
149+
};
150+
151+
main();

0 commit comments

Comments
 (0)