Files
smartpreview/ts_web/pdfworker.ts

191 lines
4.7 KiB
TypeScript
Raw Permalink Normal View History

/**
* 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();