Skip to content

Commit 6e46cbf

Browse files
committed
Fix type issues in the engine
1 parent a27203b commit 6e46cbf

13 files changed

Lines changed: 233 additions & 338 deletions

File tree

packages/engines/src/lib/pdfium/image-converter.ts renamed to packages/engines/src/lib/converters/browser.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
import { ImageConversionTypes } from '@embedpdf/models';
2+
import { ImageDataConverter, LazyImageData } from './types';
23
import { ImageEncoderWorkerPool } from '../image-encoder';
34

4-
/**
5-
* Lazy image data getter function
6-
*/
7-
export type LazyImageData = () => { data: Uint8ClampedArray; width: number; height: number };
8-
9-
/**
10-
* Image data converter function type
11-
*/
12-
export type ImageDataConverter<T> = (
13-
getImageData: LazyImageData,
14-
imageType?: ImageConversionTypes,
15-
quality?: number,
16-
) => Promise<T>;
5+
// ============================================================================
6+
// Error Classes
7+
// ============================================================================
178

189
export class OffscreenCanvasError extends Error {
1910
constructor(message: string) {
@@ -22,9 +13,13 @@ export class OffscreenCanvasError extends Error {
2213
}
2314
}
2415

16+
// ============================================================================
17+
// Browser Converters
18+
// ============================================================================
19+
2520
/**
2621
* Browser-based image converter using OffscreenCanvas in the same thread
27-
* This is the legacy approach that blocks the PDFium worker
22+
* This is the simplest approach but blocks the thread during encoding
2823
*/
2924
export const browserImageDataToBlobConverter: ImageDataConverter<Blob> = (
3025
getImageData: LazyImageData,
@@ -51,7 +46,7 @@ export const browserImageDataToBlobConverter: ImageDataConverter<Blob> = (
5146

5247
/**
5348
* Create an image converter that uses a dedicated worker pool for encoding
54-
* This prevents blocking the PDFium worker thread
49+
* This prevents blocking the main/PDFium worker thread
5550
*
5651
* @param workerPool - Instance of ImageEncoderWorkerPool
5752
* @returns ImageDataConverter function
Lines changed: 6 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,170 +1,8 @@
1-
import { PdfImage } from '@embedpdf/models';
2-
import { toArrayBuffer } from '../utils';
3-
import { ImageDataConverter, ImageConversionTypes, LazyImageData } from './types';
1+
// Types
2+
export * from './types';
43

5-
/**
6-
* Node.js implementation using Sharp
7-
* This requires the 'sharp' package to be installed
8-
*
9-
* @example
10-
* ```typescript
11-
* import sharp from 'sharp';
12-
* import { createNodeImageDataToBufferConverter } from '@embedpdf/engines/pdfium/image-converters';
13-
*
14-
* const imageDataConverter = createNodeImageDataToBufferConverter(sharp);
15-
* const engine = new PdfiumEngine(pdfiumModule, { logger, imageDataConverter });
16-
* ```
17-
*/
18-
export function createNodeImageDataToBufferConverter(
19-
sharp: any, // Using 'any' to avoid requiring sharp as a dependency
20-
): ImageDataConverter<Buffer> {
21-
return async (
22-
getImageData: LazyImageData,
23-
imageType: ImageConversionTypes = 'image/webp',
24-
imageQuality?: number,
25-
): Promise<Buffer> => {
26-
const imageData = getImageData();
27-
const { width, height, data } = imageData;
4+
// Browser converters (safe for all environments)
5+
export * from './browser';
286

29-
// Convert ImageData to Sharp format
30-
// ImageData uses RGBA format, Sharp expects the same
31-
let sharpInstance = sharp(Buffer.from(data), {
32-
raw: {
33-
width,
34-
height,
35-
channels: 4, // RGBA
36-
},
37-
});
38-
39-
// Apply the appropriate format conversion based on imageType
40-
let buffer: Buffer;
41-
switch (imageType) {
42-
case 'image/webp':
43-
buffer = await sharpInstance
44-
.webp({
45-
quality: imageQuality,
46-
})
47-
.toBuffer();
48-
break;
49-
case 'image/png':
50-
buffer = await sharpInstance.png().toBuffer();
51-
break;
52-
case 'image/jpeg':
53-
// JPEG doesn't support transparency, so we need to composite onto a white background
54-
buffer = await sharpInstance
55-
.flatten({ background: { r: 255, g: 255, b: 255 } }) // Remove alpha channel with white background
56-
.jpeg({
57-
quality: imageQuality,
58-
})
59-
.toBuffer();
60-
break;
61-
default:
62-
throw new Error(`Unsupported image type: ${imageType}`);
63-
}
64-
65-
return buffer;
66-
};
67-
}
68-
69-
/**
70-
* Alternative Node.js implementation using canvas (node-canvas)
71-
* This requires the 'canvas' package to be installed
72-
*
73-
* @example
74-
* ```typescript
75-
* import { createCanvas } from 'canvas';
76-
* import { createNodeCanvasImageDataToBlobConverter } from '@embedpdf/engines/pdfium/image-converters';
77-
*
78-
* const imageDataConverter = createNodeCanvasImageDataToBlobConverter(createCanvas);
79-
* const engine = new PdfiumEngine(pdfiumModule, { logger, imageDataConverter });
80-
* ```
81-
*/
82-
export function createNodeCanvasImageDataToBlobConverter(
83-
createCanvas: any, // Using 'any' to avoid requiring canvas as a dependency
84-
): ImageDataConverter<Buffer> {
85-
return async (
86-
getImageData: LazyImageData,
87-
imageType: ImageConversionTypes = 'image/webp',
88-
_imageQuality?: number,
89-
): Promise<Buffer> => {
90-
const imageData = getImageData();
91-
const { width, height } = imageData;
92-
93-
// Create a canvas and put the image data
94-
const canvas = createCanvas(width, height);
95-
const ctx = canvas.getContext('2d');
96-
ctx.putImageData(imageData, 0, 0);
97-
98-
// Convert to buffer and create blob based on the requested type
99-
let buffer: Buffer;
100-
switch (imageType) {
101-
case 'image/webp':
102-
buffer = canvas.toBuffer('image/webp');
103-
break;
104-
case 'image/png':
105-
buffer = canvas.toBuffer('image/png');
106-
break;
107-
case 'image/jpeg':
108-
buffer = canvas.toBuffer('image/jpeg');
109-
break;
110-
default:
111-
throw new Error(`Unsupported image type: ${imageType}`);
112-
}
113-
114-
return buffer;
115-
};
116-
}
117-
118-
/**
119-
* Generic Node.js implementation that works with any image processing library
120-
* that can handle raw RGBA data
121-
*
122-
* @example
123-
* ```typescript
124-
* const converter = createCustomImageDataToBlobConverter(async (imageData) => {
125-
* // Your custom image processing logic here
126-
* // Return a Buffer that will be wrapped in a Blob
127-
* return processImageWithYourLibrary(imageData);
128-
* });
129-
* ```
130-
*/
131-
export function createCustomImageDataToBlobConverter(
132-
processor: (
133-
imageData: PdfImage,
134-
imageType?: ImageConversionTypes,
135-
imageQuality?: number,
136-
) => Promise<Buffer>,
137-
): ImageDataConverter {
138-
return async (
139-
getImageData: LazyImageData,
140-
imageType: ImageConversionTypes = 'image/webp',
141-
imageQuality?: number,
142-
) => {
143-
const imageData = getImageData();
144-
const bytes = await processor(imageData, imageType, imageQuality);
145-
return new Blob([toArrayBuffer(bytes)], { type: imageType });
146-
};
147-
}
148-
149-
/**
150-
* Create a custom converter that returns a Buffer
151-
* @param processor - function to process the image data
152-
* @param imageType - image type
153-
* @returns ImageDataToBlobConverter<Buffer>
154-
*/
155-
export function createCustomImageDataToBufferConverter(
156-
processor: (
157-
imageData: PdfImage,
158-
imageType: ImageConversionTypes,
159-
imageQuality?: number,
160-
) => Promise<Buffer>,
161-
): ImageDataConverter<Buffer> {
162-
return async (
163-
getImageData: LazyImageData,
164-
imageType: ImageConversionTypes = 'image/webp',
165-
imageQuality?: number,
166-
): Promise<Buffer> => {
167-
const imageData = getImageData();
168-
return await processor(imageData, imageType, imageQuality);
169-
};
170-
}
7+
// Node.js converters (only for Node.js environments)
8+
export * from './node';
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { PdfImage, ImageConversionTypes } from '@embedpdf/models';
2+
import { toArrayBuffer } from '../utils';
3+
import { ImageDataConverter, LazyImageData } from './types';
4+
5+
// ============================================================================
6+
// Node.js Converters
7+
// ============================================================================
8+
9+
/**
10+
* Node.js implementation using Sharp
11+
* This requires the 'sharp' package to be installed
12+
*
13+
* @example
14+
* ```typescript
15+
* import sharp from 'sharp';
16+
* import { createNodeImageDataToBufferConverter } from '@embedpdf/engines';
17+
*
18+
* const imageDataConverter = createNodeImageDataToBufferConverter(sharp);
19+
* const engine = new PdfiumEngine(pdfiumModule, { logger, imageDataConverter });
20+
* ```
21+
*/
22+
export function createNodeImageDataToBufferConverter(
23+
sharp: any, // Using 'any' to avoid requiring sharp as a dependency
24+
): ImageDataConverter<Buffer> {
25+
return async (
26+
getImageData: LazyImageData,
27+
imageType: ImageConversionTypes = 'image/webp',
28+
imageQuality?: number,
29+
): Promise<Buffer> => {
30+
const imageData = getImageData();
31+
const { width, height, data } = imageData;
32+
33+
// Convert ImageData to Sharp format
34+
// ImageData uses RGBA format, Sharp expects the same
35+
let sharpInstance = sharp(Buffer.from(data), {
36+
raw: {
37+
width,
38+
height,
39+
channels: 4, // RGBA
40+
},
41+
});
42+
43+
// Apply the appropriate format conversion based on imageType
44+
let buffer: Buffer;
45+
switch (imageType) {
46+
case 'image/webp':
47+
buffer = await sharpInstance
48+
.webp({
49+
quality: imageQuality,
50+
})
51+
.toBuffer();
52+
break;
53+
case 'image/png':
54+
buffer = await sharpInstance.png().toBuffer();
55+
break;
56+
case 'image/jpeg':
57+
// JPEG doesn't support transparency, so we need to composite onto a white background
58+
buffer = await sharpInstance
59+
.flatten({ background: { r: 255, g: 255, b: 255 } }) // Remove alpha channel with white background
60+
.jpeg({
61+
quality: imageQuality,
62+
})
63+
.toBuffer();
64+
break;
65+
default:
66+
throw new Error(`Unsupported image type: ${imageType}`);
67+
}
68+
69+
return buffer;
70+
};
71+
}
72+
73+
/**
74+
* Alternative Node.js implementation using canvas (node-canvas)
75+
* This requires the 'canvas' package to be installed
76+
*
77+
* @example
78+
* ```typescript
79+
* import { createCanvas } from 'canvas';
80+
* import { createNodeCanvasImageDataToBlobConverter } from '@embedpdf/engines';
81+
*
82+
* const imageDataConverter = createNodeCanvasImageDataToBlobConverter(createCanvas);
83+
* const engine = new PdfiumEngine(pdfiumModule, { logger, imageDataConverter });
84+
* ```
85+
*/
86+
export function createNodeCanvasImageDataToBlobConverter(
87+
createCanvas: any, // Using 'any' to avoid requiring canvas as a dependency
88+
): ImageDataConverter<Buffer> {
89+
return async (
90+
getImageData: LazyImageData,
91+
imageType: ImageConversionTypes = 'image/webp',
92+
_imageQuality?: number,
93+
): Promise<Buffer> => {
94+
const imageData = getImageData();
95+
const { width, height } = imageData;
96+
97+
// Create a canvas and put the image data
98+
const canvas = createCanvas(width, height);
99+
const ctx = canvas.getContext('2d');
100+
ctx.putImageData(imageData, 0, 0);
101+
102+
// Convert to buffer and create blob based on the requested type
103+
let buffer: Buffer;
104+
switch (imageType) {
105+
case 'image/webp':
106+
buffer = canvas.toBuffer('image/webp');
107+
break;
108+
case 'image/png':
109+
buffer = canvas.toBuffer('image/png');
110+
break;
111+
case 'image/jpeg':
112+
buffer = canvas.toBuffer('image/jpeg');
113+
break;
114+
default:
115+
throw new Error(`Unsupported image type: ${imageType}`);
116+
}
117+
118+
return buffer;
119+
};
120+
}
121+
122+
/**
123+
* Generic Node.js implementation that works with any image processing library
124+
* that can handle raw RGBA data
125+
*
126+
* @example
127+
* ```typescript
128+
* const converter = createCustomImageDataToBlobConverter(async (imageData) => {
129+
* // Your custom image processing logic here
130+
* // Return a Buffer that will be wrapped in a Blob
131+
* return processImageWithYourLibrary(imageData);
132+
* });
133+
* ```
134+
*/
135+
export function createCustomImageDataToBlobConverter(
136+
processor: (
137+
imageData: PdfImage,
138+
imageType?: ImageConversionTypes,
139+
imageQuality?: number,
140+
) => Promise<Buffer>,
141+
): ImageDataConverter {
142+
return async (
143+
getImageData: LazyImageData,
144+
imageType: ImageConversionTypes = 'image/webp',
145+
imageQuality?: number,
146+
) => {
147+
const imageData = getImageData();
148+
const bytes = await processor(imageData, imageType, imageQuality);
149+
return new Blob([toArrayBuffer(bytes)], { type: imageType });
150+
};
151+
}
152+
153+
/**
154+
* Create a custom converter that returns a Buffer
155+
* @param processor - function to process the image data
156+
* @param imageType - image type
157+
* @returns ImageDataToBlobConverter<Buffer>
158+
*/
159+
export function createCustomImageDataToBufferConverter(
160+
processor: (
161+
imageData: PdfImage,
162+
imageType: ImageConversionTypes,
163+
imageQuality?: number,
164+
) => Promise<Buffer>,
165+
): ImageDataConverter<Buffer> {
166+
return async (
167+
getImageData: LazyImageData,
168+
imageType: ImageConversionTypes = 'image/webp',
169+
imageQuality?: number,
170+
): Promise<Buffer> => {
171+
const imageData = getImageData();
172+
return await processor(imageData, imageType, imageQuality);
173+
};
174+
}

0 commit comments

Comments
 (0)