From 388f2ee19584ad171027a9e6d8d523feb8a1a54f Mon Sep 17 00:00:00 2001
From: Sumit-5002 <173078713+Sumit-5002@users.noreply.github.com>
Date: Sat, 28 Mar 2026 06:24:06 +0000
Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Parallelized=20PDF=20to=20J?=
=?UTF-8?q?PG=20conversion=20with=20Blob=20optimization?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Refactored the `pdfToJpg` service to use chunked parallel rendering (concurrency of 4) and replaced `canvas.toDataURL()` with `canvas.toBlob()`.
Improvements:
- Reduces total conversion time for multi-page PDFs by processing pages in parallel chunks.
- Eliminates CPU and memory overhead of Base64 encoding by using binary Blobs directly with JSZip.
- Maintains accurate progress reporting during concurrent processing.
---
.jules/bolt.md | 4 +++
src/services/pdf/pdfImageConverter.js | 49 +++++++++++++++++++++------
2 files changed, 43 insertions(+), 10 deletions(-)
diff --git a/.jules/bolt.md b/.jules/bolt.md
index b732f64..05d9d3c 100644
--- a/.jules/bolt.md
+++ b/.jules/bolt.md
@@ -25,3 +25,7 @@
## 2026-03-26 - Optimized Line Number Rendering in TextToolRunner
**Learning:** Rendering line numbers by mapping over an array of indices to create individual `div` elements (e.g., `{lines.map((_, i) =>
{i+1}
)}`) creates O(N) DOM nodes. This becomes a significant performance bottleneck for large text files, increasing memory usage and React reconciliation time. Using a single `div` with `white-space: pre` and a newline-separated string reduces DOM nodes to O(1) per editor.
**Action:** Always prefer a single pre-formatted string for line numbers or similar repeating indicators instead of individual DOM nodes per line to ensure consistent performance with large inputs.
+
+## 2026-03-28 - Parallelized and Blob-Optimized PDF to JPG Conversion
+**Learning:** Sequential page rendering in `pdfToJpg` creates a linear bottleneck for large PDFs. Furthermore, using `canvas.toDataURL()` incurs significant overhead due to Base64 encoding/decoding, which is unnecessary since `JSZip` can handle Blobs directly.
+**Action:** Implement chunked parallel rendering (concurrency of 4) for PDF pages and switch from `toDataURL()` to `toBlob()` to minimize CPU and memory usage during batch image extraction.
diff --git a/src/services/pdf/pdfImageConverter.js b/src/services/pdf/pdfImageConverter.js
index 6fe8add..8b08ea9 100644
--- a/src/services/pdf/pdfImageConverter.js
+++ b/src/services/pdf/pdfImageConverter.js
@@ -15,20 +15,49 @@ export const imagesToPDF = async (files, onProgress) => {
return pdf.save();
};
+/**
+ * Converts a PDF into individual JPG images and downloads them as a ZIP file.
+ * Optimized with chunked parallel rendering and Blob usage (Bolt ⚡).
+ */
export const pdfToJpg = async (file, onProgress) => {
const pdf = await pdfjs.getDocument({ data: await file.arrayBuffer() }).promise;
const zip = new JSZip();
- for (let i = 1; i <= pdf.numPages; i++) {
- if (onProgress) onProgress((i / pdf.numPages) * 100);
- const page = await pdf.getPage(i);
- const viewport = page.getViewport({ scale: 2.0 });
- const canvas = document.createElement('canvas');
- canvas.width = viewport.width; canvas.height = viewport.height;
- await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
- const b64 = canvas.toDataURL('image/jpeg', 0.9).split(',')[1];
- zip.file(`page_${i}.jpg`, b64, { base64: true });
+ const numPages = pdf.numPages;
+ const CHUNK_SIZE = 4;
+
+ for (let i = 1; i <= numPages; i += CHUNK_SIZE) {
+ const chunk = [];
+ for (let j = i; j < i + CHUNK_SIZE && j <= numPages; j++) {
+ chunk.push(j);
+ }
+
+ // Render pages in parallel chunks to optimize performance while controlling memory usage
+ await Promise.all(chunk.map(async (pageNum) => {
+ const page = await pdf.getPage(pageNum);
+ const viewport = page.getViewport({ scale: 2.0 });
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ canvas.width = viewport.width;
+ canvas.height = viewport.height;
+
+ await page.render({ canvasContext: context, viewport }).promise;
+
+ // Use toBlob instead of toDataURL to avoid Base64 encoding overhead (Bolt ⚡)
+ return new Promise((resolve) => {
+ canvas.toBlob((blob) => {
+ zip.file(`page_${pageNum}.jpg`, blob);
+ resolve();
+ }, 'image/jpeg', 0.9);
+ });
+ }));
+
+ if (onProgress) {
+ const completed = Math.min(i + CHUNK_SIZE - 1, numPages);
+ onProgress((completed / numPages) * 100);
+ }
}
- if (onProgress) onProgress(95); // Zipping takes a bit
+
+ if (onProgress) onProgress(95); // Zipping phase
const blob = await zip.generateAsync({ type: 'blob' });
if (onProgress) onProgress(100);
downloadBlob(blob, file.name.replace('.pdf', '_images.zip'));