feat(initial): add comprehensive PDF to JPEG preview library with dual-environment support
- Add Node.js implementation using @push.rocks/smartpdf - Add browser implementation with PDF.js and Web Workers - Support configurable quality, dimensions, and page selection - Include comprehensive TypeScript definitions and error handling - Provide extensive test coverage for both environments - Add download functionality and browser compatibility checking
This commit is contained in:
191
ts_web/pdfworker.ts
Normal file
191
ts_web/pdfworker.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* PDF.js worker for processing PDFs in the browser
|
||||
* This file runs in a Web Worker context
|
||||
*/
|
||||
|
||||
// Import types for worker context
|
||||
import type {
|
||||
IWorkerMessage,
|
||||
IPdfProcessRequest,
|
||||
IPdfProcessResponse,
|
||||
TWorkerMessageType
|
||||
} from './interfaces.js';
|
||||
import { PreviewError } from './interfaces.js';
|
||||
|
||||
// PDF.js library (loaded from CDN or bundled)
|
||||
declare const pdfjsLib: any;
|
||||
|
||||
/**
|
||||
* Worker context interface
|
||||
*/
|
||||
declare const self: any;
|
||||
|
||||
/**
|
||||
* PDF.js configuration
|
||||
*/
|
||||
const PDFJS_CDN_URL = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js';
|
||||
const WORKER_CDN_URL = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||
|
||||
/**
|
||||
* Worker state
|
||||
*/
|
||||
let isInitialized = false;
|
||||
let pdfjsWorker: any = null;
|
||||
|
||||
/**
|
||||
* Initialize PDF.js in worker context
|
||||
*/
|
||||
async function initializePdfJs(): Promise<void> {
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load PDF.js library
|
||||
self.importScripts(PDFJS_CDN_URL);
|
||||
|
||||
if (typeof pdfjsLib === 'undefined') {
|
||||
throw new Error('Failed to load PDF.js library');
|
||||
}
|
||||
|
||||
// Configure PDF.js worker
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = WORKER_CDN_URL;
|
||||
|
||||
isInitialized = true;
|
||||
postMessage({
|
||||
type: 'WORKER_READY',
|
||||
id: 'init',
|
||||
data: { version: pdfjsLib.version }
|
||||
} as IWorkerMessage);
|
||||
} catch (error) {
|
||||
postMessage({
|
||||
type: 'PROCESS_ERROR',
|
||||
id: 'init',
|
||||
error: `Failed to initialize PDF.js: ${error instanceof Error ? error.message : String(error)}`
|
||||
} as IWorkerMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process PDF and generate JPEG preview
|
||||
*/
|
||||
async function processPdf(requestId: string, request: IPdfProcessRequest): Promise<void> {
|
||||
if (!isInitialized) {
|
||||
postMessage({
|
||||
type: 'PROCESS_ERROR',
|
||||
id: requestId,
|
||||
error: 'Worker not initialized'
|
||||
} as IWorkerMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load PDF document
|
||||
const loadingTask = pdfjsLib.getDocument({
|
||||
data: request.pdfData,
|
||||
cMapUrl: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/cmaps/',
|
||||
cMapPacked: true,
|
||||
});
|
||||
|
||||
const pdf = await loadingTask.promise;
|
||||
|
||||
// Validate page number
|
||||
if (request.options.page > pdf.numPages || request.options.page < 1) {
|
||||
throw new Error(`Page ${request.options.page} not found. Document has ${pdf.numPages} pages.`);
|
||||
}
|
||||
|
||||
// Get the specified page
|
||||
const page = await pdf.getPage(request.options.page);
|
||||
|
||||
// Calculate viewport
|
||||
const viewport = page.getViewport({ scale: request.options.scale });
|
||||
|
||||
// Adjust viewport to fit within max dimensions
|
||||
let { width, height } = viewport;
|
||||
if (request.options.width && width > request.options.width) {
|
||||
const scale = request.options.width / width;
|
||||
width = request.options.width;
|
||||
height = height * scale;
|
||||
}
|
||||
if (request.options.height && height > request.options.height) {
|
||||
const scale = request.options.height / height;
|
||||
height = request.options.height;
|
||||
width = width * scale;
|
||||
}
|
||||
|
||||
const scaledViewport = page.getViewport({
|
||||
scale: Math.min(width / viewport.width, height / viewport.height) * request.options.scale
|
||||
});
|
||||
|
||||
// Create canvas and render page
|
||||
const canvas = new OffscreenCanvas(scaledViewport.width, scaledViewport.height);
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Failed to get 2D rendering context');
|
||||
}
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: scaledViewport,
|
||||
};
|
||||
|
||||
await page.render(renderContext).promise;
|
||||
|
||||
// Convert to JPEG
|
||||
const blob = await canvas.convertToBlob({
|
||||
type: 'image/jpeg',
|
||||
quality: request.options.quality / 100,
|
||||
});
|
||||
|
||||
// Convert blob to ArrayBuffer
|
||||
const arrayBuffer = await blob.arrayBuffer();
|
||||
|
||||
// Send response
|
||||
const response: IPdfProcessResponse = {
|
||||
imageData: arrayBuffer,
|
||||
width: scaledViewport.width,
|
||||
height: scaledViewport.height,
|
||||
};
|
||||
|
||||
postMessage({
|
||||
type: 'PROCESS_COMPLETE',
|
||||
id: requestId,
|
||||
data: response
|
||||
} as IWorkerMessage);
|
||||
|
||||
} catch (error) {
|
||||
postMessage({
|
||||
type: 'PROCESS_ERROR',
|
||||
id: requestId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
} as IWorkerMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle worker messages
|
||||
*/
|
||||
self.addEventListener('message', async (event: MessageEvent<IWorkerMessage>) => {
|
||||
const { type, id, data } = event.data;
|
||||
|
||||
switch (type) {
|
||||
case 'INIT':
|
||||
await initializePdfJs();
|
||||
break;
|
||||
|
||||
case 'PROCESS_PDF':
|
||||
await processPdf(id, data as IPdfProcessRequest);
|
||||
break;
|
||||
|
||||
default:
|
||||
postMessage({
|
||||
type: 'PROCESS_ERROR',
|
||||
id: id || 'unknown',
|
||||
error: `Unknown message type: ${type}`
|
||||
} as IWorkerMessage);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-initialize when worker starts
|
||||
initializePdfJs();
|
Reference in New Issue
Block a user