mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Stirling 2.0 (#3928)
# Description of Changes <!-- File context for managing files between tools and views Optimisation for large files Updated Split to work with new file system and match Matts stepped design closer --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Anthony Stirling <[email protected]>
This commit is contained in:
co-authored by
Anthony Stirling
parent
584e2ecee7
commit
922bbc9076
@@ -0,0 +1,157 @@
|
||||
// Web Worker for parallel thumbnail generation
|
||||
console.log('🔧 Thumbnail worker starting up...');
|
||||
|
||||
let pdfJsLoaded = false;
|
||||
|
||||
// Import PDF.js properly for worker context
|
||||
try {
|
||||
console.log('📦 Loading PDF.js locally...');
|
||||
importScripts('/pdf.js');
|
||||
|
||||
// PDF.js exports to globalThis, check both self and globalThis
|
||||
const pdfjsLib = self.pdfjsLib || globalThis.pdfjsLib;
|
||||
|
||||
if (pdfjsLib) {
|
||||
// Make it available on self for consistency
|
||||
self.pdfjsLib = pdfjsLib;
|
||||
|
||||
// Set up PDF.js worker
|
||||
self.pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
|
||||
pdfJsLoaded = true;
|
||||
console.log('✓ PDF.js loaded successfully from local files');
|
||||
console.log('✓ PDF.js version:', self.pdfjsLib.version || 'unknown');
|
||||
} else {
|
||||
throw new Error('pdfjsLib not available after import - neither self.pdfjsLib nor globalThis.pdfjsLib found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('✗ Failed to load local PDF.js:', error.message || error);
|
||||
console.error('✗ Available globals:', Object.keys(self).filter(key => key.includes('pdf')));
|
||||
pdfJsLoaded = false;
|
||||
}
|
||||
|
||||
// Log the final status
|
||||
if (pdfJsLoaded) {
|
||||
console.log('✅ Thumbnail worker ready for PDF processing');
|
||||
} else {
|
||||
console.log('❌ Thumbnail worker failed to initialize - PDF.js not available');
|
||||
}
|
||||
|
||||
self.onmessage = async function(e) {
|
||||
const { type, data, jobId } = e.data;
|
||||
|
||||
try {
|
||||
// Handle PING for worker health check
|
||||
if (type === 'PING') {
|
||||
console.log('🏓 Worker PING received, checking PDF.js status...');
|
||||
|
||||
// Check if PDF.js is loaded before responding
|
||||
if (pdfJsLoaded && self.pdfjsLib) {
|
||||
console.log('✓ Worker PONG - PDF.js ready');
|
||||
self.postMessage({ type: 'PONG', jobId });
|
||||
} else {
|
||||
console.error('✗ PDF.js not loaded - worker not ready');
|
||||
console.error('✗ pdfJsLoaded:', pdfJsLoaded);
|
||||
console.error('✗ self.pdfjsLib:', !!self.pdfjsLib);
|
||||
self.postMessage({
|
||||
type: 'ERROR',
|
||||
jobId,
|
||||
data: { error: 'PDF.js not loaded in worker' }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'GENERATE_THUMBNAILS') {
|
||||
console.log('🖼️ Starting thumbnail generation for', data.pageNumbers.length, 'pages');
|
||||
|
||||
if (!pdfJsLoaded || !self.pdfjsLib) {
|
||||
const error = 'PDF.js not available in worker';
|
||||
console.error('✗', error);
|
||||
throw new Error(error);
|
||||
}
|
||||
const { pdfArrayBuffer, pageNumbers, scale = 0.2, quality = 0.8 } = data;
|
||||
|
||||
console.log('📄 Loading PDF document, size:', pdfArrayBuffer.byteLength, 'bytes');
|
||||
// Load PDF in worker using imported PDF.js
|
||||
const pdf = await self.pdfjsLib.getDocument({ data: pdfArrayBuffer }).promise;
|
||||
console.log('✓ PDF loaded, total pages:', pdf.numPages);
|
||||
|
||||
const thumbnails = [];
|
||||
|
||||
// Process pages in smaller batches for smoother UI
|
||||
const batchSize = 3; // Process 3 pages at once for smoother UI
|
||||
for (let i = 0; i < pageNumbers.length; i += batchSize) {
|
||||
const batch = pageNumbers.slice(i, i + batchSize);
|
||||
|
||||
const batchPromises = batch.map(async (pageNumber) => {
|
||||
try {
|
||||
console.log(`🎯 Processing page ${pageNumber}...`);
|
||||
const page = await pdf.getPage(pageNumber);
|
||||
const viewport = page.getViewport({ scale });
|
||||
console.log(`📐 Page ${pageNumber} viewport:`, viewport.width, 'x', viewport.height);
|
||||
|
||||
// Create OffscreenCanvas for better performance
|
||||
const canvas = new OffscreenCanvas(viewport.width, viewport.height);
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Failed to get 2D context from OffscreenCanvas');
|
||||
}
|
||||
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
console.log(`✓ Page ${pageNumber} rendered`);
|
||||
|
||||
// Convert to blob then to base64 (more efficient than toDataURL)
|
||||
const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality });
|
||||
const arrayBuffer = await blob.arrayBuffer();
|
||||
const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
|
||||
const thumbnail = `data:image/jpeg;base64,${base64}`;
|
||||
console.log(`✓ Page ${pageNumber} thumbnail generated (${base64.length} chars)`);
|
||||
|
||||
return { pageNumber, thumbnail, success: true };
|
||||
} catch (error) {
|
||||
console.error(`✗ Failed to generate thumbnail for page ${pageNumber}:`, error.message || error);
|
||||
return { pageNumber, error: error.message || String(error), success: false };
|
||||
}
|
||||
});
|
||||
|
||||
const batchResults = await Promise.all(batchPromises);
|
||||
thumbnails.push(...batchResults);
|
||||
|
||||
// Send progress update
|
||||
console.log(`📊 Worker: Sending progress update - ${thumbnails.length}/${pageNumbers.length} completed, ${batchResults.filter(r => r.success).length} new thumbnails`);
|
||||
self.postMessage({
|
||||
type: 'PROGRESS',
|
||||
jobId,
|
||||
data: {
|
||||
completed: thumbnails.length,
|
||||
total: pageNumbers.length,
|
||||
thumbnails: batchResults.filter(r => r.success)
|
||||
}
|
||||
});
|
||||
|
||||
// Small delay between batches to keep UI smooth
|
||||
if (i + batchSize < pageNumbers.length) {
|
||||
console.log(`⏸️ Worker: Pausing 100ms before next batch (${i + batchSize}/${pageNumbers.length})`);
|
||||
await new Promise(resolve => setTimeout(resolve, 100)); // Increased to 100ms pause between batches for smoother scrolling
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
pdf.destroy();
|
||||
|
||||
self.postMessage({
|
||||
type: 'COMPLETE',
|
||||
jobId,
|
||||
data: { thumbnails: thumbnails.filter(r => r.success) }
|
||||
});
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
type: 'ERROR',
|
||||
jobId,
|
||||
data: { error: error.message }
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user