Skip to content
Draft
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
5 changes: 2 additions & 3 deletions packages/pdfkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@
],
"dependencies": {
"@babel/runtime": "^7.20.13",
"browserify-zlib": "^0.2.0",
"@noble/ciphers": "^1.0.0",
"@noble/hashes": "^1.6.0",
"fontkit": "^2.0.2",
"js-md5": "^0.8.3",
"linebreak": "^1.1.0",
"png-js": "^2.0.0",
"vite-compatible-readable-stream": "^3.6.1"
"pako": "^2.1.0",
"png-js": "^2.0.0"
},
"devDependencies": {
"iconv-lite": "^0.4.13"
Expand Down
11 changes: 2 additions & 9 deletions packages/pdfkit/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ const babelConfig = () => ({
});

const getExternal = ({ browser }) => [
...Object.keys(pkg.dependencies).filter(
(dep) =>
!browser ||
!['vite-compatible-readable-stream', 'browserify-zlib'].includes(dep)
),
...Object.keys(pkg.dependencies),
/\/node_modules\/pako\//,
/@babel\/runtime/,
'js-md5',
Expand All @@ -38,17 +34,14 @@ const getPlugins = ({ browser }) => [
ignore(['fs']),
alias({
entries: [
// See https://github.com/browserify/browserify-zlib/pull/45
{
find: 'pako/lib/zlib/zstream',
replacement: 'pako/lib/zlib/zstream.js'
},
{
find: 'pako/lib/zlib/constants',
replacement: 'pako/lib/zlib/constants.js'
},
{ find: 'stream', replacement: 'vite-compatible-readable-stream' },
{ find: 'zlib', replacement: 'browserify-zlib' }
}
]
}),
commonjs(),
Expand Down
77 changes: 77 additions & 0 deletions packages/pdfkit/src/binary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Binary helpers — Uint8Array-native replacements for Node Buffer operations.
*/

const HEX = '0123456789abcdef';

export const fromBinaryString = (str) => {
const out = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) out[i] = str.charCodeAt(i) & 0xff;
return out;
};

export const toBinaryString = (bytes) => {
const chunkSize = 0x8000;
let out = '';
for (let i = 0; i < bytes.length; i += chunkSize) {
const end = Math.min(i + chunkSize, bytes.length);
out += String.fromCharCode.apply(null, bytes.subarray(i, end));
}
return out;
};

export const fromUtf8String = (str) => new TextEncoder().encode(str);

// Matches Buffer.from(`\ufeff${str}`, 'utf16le') + byte swap: UTF-16BE with BOM.
export const fromUtf16BEWithBOM = (str) => {
const withBom = `\ufeff${str}`;
const out = new Uint8Array(withBom.length * 2);
for (let i = 0; i < withBom.length; i++) {
const code = withBom.charCodeAt(i);
out[i * 2] = (code >> 8) & 0xff;
out[i * 2 + 1] = code & 0xff;
}
return out;
};

export const fromBase64 = (str) => fromBinaryString(atob(str));

export const toHex = (bytes) => {
let out = '';
for (let i = 0; i < bytes.length; i++) {
out += HEX[bytes[i] >> 4] + HEX[bytes[i] & 0xf];
}
return out;
};

export const concat = (arrays) => {
let total = 0;
for (const a of arrays) total += a.length;
const out = new Uint8Array(total);
let offset = 0;
for (const a of arrays) {
out.set(a, offset);
offset += a.length;
}
return out;
};

export const readUInt16BE = (bytes, offset = 0) =>
((bytes[offset] << 8) | bytes[offset + 1]) >>> 0;

export const readUInt16LE = (bytes, offset = 0) =>
((bytes[offset + 1] << 8) | bytes[offset]) >>> 0;

export const readUInt32BE = (bytes, offset = 0) =>
(bytes[offset] * 0x1000000 +
((bytes[offset + 1] << 16) |
(bytes[offset + 2] << 8) |
bytes[offset + 3])) >>>
0;

export const readUInt32LE = (bytes, offset = 0) =>
((bytes[offset] |
(bytes[offset + 1] << 8) |
(bytes[offset + 2] << 16)) +
bytes[offset + 3] * 0x1000000) >>>
0;
14 changes: 6 additions & 8 deletions packages/pdfkit/src/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ PDFDocument - represents an entire PDF document
By Devon Govett
*/

import stream from 'stream';
import MiniReadable from './mini-stream';
import { fromBinaryString } from './binary';
import PDFObject from './object';
import PDFReference from './reference';
import PDFPage from './page';
Expand All @@ -24,9 +25,9 @@ import SubsetMixin from './mixins/subsets';
import TableMixin from './mixins/table';
import MetadataMixin from './mixins/metadata';

class PDFDocument extends stream.Readable {
class PDFDocument extends MiniReadable {
constructor(options = {}) {
super(options);
super();
this.options = options;

// PDF version
Expand Down Expand Up @@ -254,12 +255,9 @@ class PDFDocument extends stream.Readable {
return ref;
}

_read() {}
// do nothing, but this method is required by node

_write(data) {
if (!Buffer.isBuffer(data)) {
data = Buffer.from(data + '\n', 'binary');
if (!(data instanceof Uint8Array)) {
data = fromBinaryString(data + '\n');
}

this.push(data);
Expand Down
14 changes: 10 additions & 4 deletions packages/pdfkit/src/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ By Devon Govett
*/

import fs from 'fs';
import { fromBase64 } from './binary';
import JPEG from './image/jpeg';
import PNG from './image/png';

class PDFImage {
static open(src, label) {
let data;
if (Buffer.isBuffer(src)) {
if (src instanceof Uint8Array) {
data = src;
} else if (src instanceof ArrayBuffer) {
data = Buffer.from(new Uint8Array(src));
data = new Uint8Array(src);
} else {
const match = /^data:.+?;base64,(.*)$/.exec(src);
if (match) {
data = Buffer.from(match[1], 'base64');
data = fromBase64(match[1]);
} else {
data = fs.readFileSync(src);
if (!data) {
Expand All @@ -28,7 +29,12 @@ class PDFImage {

if (data[0] === 0xff && data[1] === 0xd8) {
return new JPEG(data, label);
} else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
} else if (
data[0] === 0x89 &&
data[1] === 0x50 &&
data[2] === 0x4e &&
data[3] === 0x47
) {
return new PNG(data, label);
} else {
throw new Error('Unknown image format.');
Expand Down
40 changes: 24 additions & 16 deletions packages/pdfkit/src/image/jpeg.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import {
readUInt16BE,
readUInt16LE,
readUInt32BE,
readUInt32LE,
toBinaryString,
} from '../binary';

/**
* Parse EXIF orientation from JPEG buffer
* @param {Buffer} data - JPEG image data
* @param {Uint8Array} data - JPEG image data
* @returns {number|null} Orientation value (1-8) or null if not found
*/
const parseExifOrientation = (data) => {
Expand All @@ -13,7 +21,7 @@ const parseExifOrientation = (data) => {
while (pos < data.length && data[pos] !== 0xff) pos++;
if (pos >= data.length - 4) return null;

const marker = data.readUInt16BE(pos);
const marker = readUInt16BE(data, pos);
pos += 2;

// SOS marker - image data starts, stop searching
Expand All @@ -23,28 +31,28 @@ const parseExifOrientation = (data) => {
if ((marker >= 0xffd0 && marker <= 0xffd9) || marker === 0xff01) continue;

if (pos + 2 > data.length) return null;
const segmentLength = data.readUInt16BE(pos);
const segmentLength = readUInt16BE(data, pos);

// APP1 (EXIF) marker
if (marker === 0xffe1 && pos + 8 <= data.length) {
const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
const exifHeader = toBinaryString(data.subarray(pos + 2, pos + 8));
if (exifHeader === 'Exif\x00\x00') {
const tiffStart = pos + 8;
if (tiffStart + 8 > data.length) return null;

// Byte order
const byteOrder = data
.subarray(tiffStart, tiffStart + 2)
.toString('ascii');
const byteOrder = toBinaryString(
data.subarray(tiffStart, tiffStart + 2),
);
const isLittleEndian = byteOrder === 'II';
if (!isLittleEndian && byteOrder !== 'MM') return null;

const read16 = isLittleEndian
? (o) => data.readUInt16LE(o)
: (o) => data.readUInt16BE(o);
? (o) => readUInt16LE(data, o)
: (o) => readUInt16BE(data, o);
const read32 = isLittleEndian
? (o) => data.readUInt32LE(o)
: (o) => data.readUInt32BE(o);
? (o) => readUInt32LE(data, o)
: (o) => readUInt32BE(data, o);

// Verify TIFF magic number (42)
if (read16(tiffStart + 2) !== 42) return null;
Expand Down Expand Up @@ -91,7 +99,7 @@ class JPEG {
let marker;
this.data = data;
this.label = label;
if (this.data.readUInt16BE(0) !== 0xffd8) {
if (readUInt16BE(this.data, 0) !== 0xffd8) {
throw 'SOI not found in JPEG';
}

Expand All @@ -104,12 +112,12 @@ class JPEG {
while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
if (pos >= this.data.length) break;

marker = this.data.readUInt16BE(pos);
marker = readUInt16BE(this.data, pos);
pos += 2;
if (MARKERS.includes(marker)) {
break;
}
pos += this.data.readUInt16BE(pos);
pos += readUInt16BE(this.data, pos);
}

if (!MARKERS.includes(marker)) {
Expand All @@ -118,10 +126,10 @@ class JPEG {
pos += 2;

this.bits = this.data[pos++];
this.height = this.data.readUInt16BE(pos);
this.height = readUInt16BE(this.data, pos);
pos += 2;

this.width = this.data.readUInt16BE(pos);
this.width = readUInt16BE(this.data, pos);
pos += 2;

const channels = this.data[pos++];
Expand Down
20 changes: 10 additions & 10 deletions packages/pdfkit/src/image/png.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import zlib from 'zlib';
import pako from 'pako';
import PNG from 'png-js';

class PNGImage {
Expand Down Expand Up @@ -48,7 +48,7 @@ class PNGImage {
} else {
// embed the color palette in the PDF as an object stream
const palette = this.document.ref();
palette.end(Buffer.from(this.image.palette));
palette.end(new Uint8Array(this.image.palette));

// build the color space array for the image
this.obj.data['ColorSpace'] = [
Expand Down Expand Up @@ -126,8 +126,8 @@ class PNGImage {
let a, p;
const colorCount = this.image.colors;
const pixelCount = this.width * this.height;
const imgData = Buffer.alloc(pixelCount * colorCount);
const alphaChannel = Buffer.alloc(pixelCount);
const imgData = new Uint8Array(pixelCount * colorCount);
const alphaChannel = new Uint8Array(pixelCount);

let i = (p = a = 0);
const len = pixels.length;
Expand All @@ -142,8 +142,8 @@ class PNGImage {
i += skipByteCount;
}

this.imgData = zlib.deflateSync(imgData);
this.alphaChannel = zlib.deflateSync(alphaChannel);
this.imgData = pako.deflate(imgData);
this.alphaChannel = pako.deflate(alphaChannel);
return this.finalize();
});
}
Expand All @@ -152,7 +152,7 @@ class PNGImage {
const transparency = this.image.transparency.indexed;
const isInterlaced = this.image.interlaceMethod === 1;
return this.image.decodePixels((pixels) => {
const alphaChannel = Buffer.alloc(this.width * this.height);
const alphaChannel = new Uint8Array(this.width * this.height);

let i = 0;
for (let j = 0, end = pixels.length; j < end; j++) {
Expand All @@ -161,17 +161,17 @@ class PNGImage {

// For interlaced images, re-encode the decoded pixel data
if (isInterlaced) {
this.imgData = zlib.deflateSync(Buffer.from(pixels));
this.imgData = pako.deflate(pixels);
}

this.alphaChannel = zlib.deflateSync(alphaChannel);
this.alphaChannel = pako.deflate(alphaChannel);
return this.finalize();
});
}

decodeData() {
this.image.decodePixels((pixels) => {
this.imgData = zlib.deflateSync(pixels);
this.imgData = pako.deflate(pixels);
this.finalize();
});
}
Expand Down
Loading
Loading