canvu-react 0.4.73 → 0.4.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -210,6 +210,342 @@ var IndexedDbImageStore = class {
210
210
  }
211
211
  };
212
212
 
213
+ // src/image/pdf-worker-renderer.ts
214
+ var PDF_WORKER_SOURCE = `
215
+ let pdfjsPromise = null;
216
+
217
+ const loadPdfJs = async (pdfjsModuleCandidates) => {
218
+ if (!pdfjsPromise) {
219
+ pdfjsPromise = (async () => {
220
+ let lastError;
221
+ for (const candidate of pdfjsModuleCandidates) {
222
+ try {
223
+ const pdfjs = await import(candidate.moduleUrl);
224
+ if (pdfjs.GlobalWorkerOptions) {
225
+ pdfjs.GlobalWorkerOptions.workerSrc = candidate.workerUrl;
226
+ }
227
+ return pdfjs;
228
+ } catch (error) {
229
+ lastError = error;
230
+ }
231
+ }
232
+ throw lastError ?? new Error("Unable to load pdfjs-dist");
233
+ })();
234
+ }
235
+ return await pdfjsPromise;
236
+ };
237
+
238
+ const createErrorPayload = (error) => ({
239
+ message: error instanceof Error ? error.message : String(error),
240
+ name: error instanceof Error ? error.name : "Error",
241
+ stack: error instanceof Error ? error.stack : undefined,
242
+ });
243
+
244
+ const normalizePdfPageNumbers = (pageNumbers, pageCount) => {
245
+ if (!Array.isArray(pageNumbers) || pageNumbers.length === 0) {
246
+ return Array.from({ length: pageCount }, (_, index) => index + 1);
247
+ }
248
+ return [...new Set(pageNumbers)]
249
+ .filter((pageNumber) => pageNumber >= 1 && pageNumber <= pageCount)
250
+ .sort((left, right) => left - right);
251
+ };
252
+
253
+ const runWithConcurrency = async (items, concurrency, execute) => {
254
+ const results = new Array(items.length);
255
+ let nextIndex = 0;
256
+ const safeConcurrency = Number.isFinite(concurrency) && concurrency > 0
257
+ ? Math.round(concurrency)
258
+ : 1;
259
+ const workerCount = Math.max(1, Math.min(safeConcurrency, items.length));
260
+ await Promise.all(
261
+ Array.from({ length: workerCount }, async () => {
262
+ while (nextIndex < items.length) {
263
+ const currentIndex = nextIndex;
264
+ nextIndex += 1;
265
+ const item = items[currentIndex];
266
+ if (item === undefined) {
267
+ continue;
268
+ }
269
+ results[currentIndex] = await execute(item);
270
+ }
271
+ }),
272
+ );
273
+ return results;
274
+ };
275
+
276
+ const renderPageToBlob = async (page, scale, storeThumbnails) => {
277
+ const raw = page.getViewport({ scale: 1 });
278
+ const adjustedScale = Math.round(raw.width * scale) / raw.width;
279
+ const viewport = page.getViewport({ scale: adjustedScale });
280
+ const width = Math.round(viewport.width);
281
+ const height = Math.round(viewport.height);
282
+ const canvas = new OffscreenCanvas(width, height);
283
+ const canvasContext = canvas.getContext("2d");
284
+ if (!canvasContext) {
285
+ throw new Error("OffscreenCanvas 2D context unavailable");
286
+ }
287
+ canvasContext.imageSmoothingEnabled = true;
288
+ canvasContext.imageSmoothingQuality = "high";
289
+ await page.render({ canvasContext, viewport }).promise;
290
+ const blob = await canvas.convertToBlob({ type: "image/png" });
291
+ let thumbnailBlob;
292
+ if (storeThumbnails) {
293
+ const thumbScale = Math.min(1, 256 / Math.max(width, height));
294
+ const thumbnailWidth = Math.max(1, Math.round(width * thumbScale));
295
+ const thumbnailHeight = Math.max(1, Math.round(height * thumbScale));
296
+ const thumbnailCanvas = new OffscreenCanvas(thumbnailWidth, thumbnailHeight);
297
+ const thumbnailContext = thumbnailCanvas.getContext("2d");
298
+ if (thumbnailContext) {
299
+ thumbnailContext.imageSmoothingEnabled = true;
300
+ thumbnailContext.imageSmoothingQuality = "high";
301
+ thumbnailContext.drawImage(canvas, 0, 0, thumbnailWidth, thumbnailHeight);
302
+ }
303
+ thumbnailBlob = await thumbnailCanvas.convertToBlob({ type: "image/png" });
304
+ }
305
+ return { blob, height, thumbnailBlob, width };
306
+ };
307
+
308
+ self.onmessage = async (event) => {
309
+ const message = event.data;
310
+ if (!message || message.type !== "render") {
311
+ return;
312
+ }
313
+ try {
314
+ const pdfjs = await loadPdfJs(message.pdfjsModuleCandidates);
315
+ const pdf = await pdfjs.getDocument({
316
+ data: new Uint8Array(message.pdfData),
317
+ disableWorker: true,
318
+ }).promise;
319
+ try {
320
+ const pageNumbers = normalizePdfPageNumbers(message.pageNumbers, pdf.numPages);
321
+ const bufferedPages = new Map();
322
+ let nextEmitIndex = 0;
323
+ let emitChain = Promise.resolve();
324
+ const emitPageInOrder = async (pageNumber, renderedPage) => {
325
+ bufferedPages.set(pageNumber, renderedPage);
326
+ const flush = () => {
327
+ while (nextEmitIndex < pageNumbers.length) {
328
+ const nextPageNumber = pageNumbers[nextEmitIndex];
329
+ if (nextPageNumber == null) break;
330
+ const nextPage = bufferedPages.get(nextPageNumber);
331
+ if (!nextPage) break;
332
+ bufferedPages.delete(nextPageNumber);
333
+ nextEmitIndex += 1;
334
+ self.postMessage({
335
+ type: "page",
336
+ page: {
337
+ ...nextPage,
338
+ pageNumber: nextPageNumber,
339
+ },
340
+ });
341
+ }
342
+ };
343
+ const nextChain = emitChain.then(flush, flush);
344
+ emitChain = nextChain.catch(() => {});
345
+ await nextChain;
346
+ };
347
+ await runWithConcurrency(
348
+ pageNumbers,
349
+ message.pageConcurrency,
350
+ async (pageNumber) => {
351
+ const page = await pdf.getPage(pageNumber);
352
+ try {
353
+ const renderedPage = await renderPageToBlob(
354
+ page,
355
+ message.scale,
356
+ message.storeThumbnails,
357
+ );
358
+ await emitPageInOrder(pageNumber, renderedPage);
359
+ } finally {
360
+ page.cleanup();
361
+ }
362
+ },
363
+ );
364
+ await emitChain;
365
+ } finally {
366
+ await pdf.destroy();
367
+ }
368
+ self.postMessage({ type: "done" });
369
+ } catch (error) {
370
+ self.postMessage({ type: "error", error: createErrorPayload(error) });
371
+ }
372
+ };
373
+ `;
374
+ var isRecord = (value) => typeof value === "object" && value !== null;
375
+ var hasObjectMembers = (value) => (typeof value === "object" || typeof value === "function") && value !== null;
376
+ var isOffscreenCanvasCandidate = (value) => typeof value === "function";
377
+ var getEnvironmentRecord = (environment) => isRecord(environment) ? environment : null;
378
+ var canUsePdfWorkerRenderer = (environment = globalThis) => {
379
+ const candidateEnvironment = getEnvironmentRecord(environment);
380
+ if (!candidateEnvironment) return false;
381
+ if (typeof candidateEnvironment.Worker !== "function") return false;
382
+ if (typeof candidateEnvironment.Blob !== "function") return false;
383
+ if (!isOffscreenCanvasCandidate(candidateEnvironment.OffscreenCanvas)) {
384
+ return false;
385
+ }
386
+ const urlCandidate = candidateEnvironment.URL;
387
+ if (!hasObjectMembers(urlCandidate)) return false;
388
+ if (typeof urlCandidate.createObjectURL !== "function") return false;
389
+ if (typeof urlCandidate.revokeObjectURL !== "function") return false;
390
+ try {
391
+ const canvas = new candidateEnvironment.OffscreenCanvas(1, 1);
392
+ return typeof canvas.getContext === "function" && typeof canvas.convertToBlob === "function";
393
+ } catch {
394
+ return false;
395
+ }
396
+ };
397
+ var createAbortError = () => new DOMException("Aborted", "AbortError");
398
+ var throwIfAborted = (signal) => {
399
+ if (signal?.aborted) {
400
+ throw createAbortError();
401
+ }
402
+ };
403
+ var createPdfWorker = () => {
404
+ const workerBlob = new Blob([PDF_WORKER_SOURCE], {
405
+ type: "text/javascript"
406
+ });
407
+ const workerUrl = URL.createObjectURL(workerBlob);
408
+ try {
409
+ const worker = new Worker(workerUrl, {
410
+ name: "canvu-pdf-renderer",
411
+ type: "module"
412
+ });
413
+ return {
414
+ release: () => URL.revokeObjectURL(workerUrl),
415
+ worker
416
+ };
417
+ } catch (error) {
418
+ URL.revokeObjectURL(workerUrl);
419
+ throw error;
420
+ }
421
+ };
422
+ var createWorkerError = (message) => {
423
+ const error = new Error(message.error.message);
424
+ error.name = message.error.name;
425
+ error.stack = message.error.stack;
426
+ return error;
427
+ };
428
+ var PACKAGED_PDFJS_MODULE_FILE = "./pdf.mjs";
429
+ var PACKAGED_PDFJS_WORKER_FILE = "./pdf.worker.mjs";
430
+ var resolveRelativeAssetUrl = (path, baseUrl) => new URL(path, baseUrl).toString();
431
+ var resolvePackagedPdfJsModuleCandidate = (baseUrl = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) => ({
432
+ moduleUrl: resolveRelativeAssetUrl(PACKAGED_PDFJS_MODULE_FILE, baseUrl),
433
+ workerUrl: resolveRelativeAssetUrl(PACKAGED_PDFJS_WORKER_FILE, baseUrl)
434
+ });
435
+ var resolveBundledPdfJsModuleCandidate = () => ({
436
+ moduleUrl: new URL("pdfjs-dist/build/pdf.min.mjs", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).toString(),
437
+ workerUrl: new URL(
438
+ "pdfjs-dist/build/pdf.worker.min.mjs",
439
+ (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))
440
+ ).toString()
441
+ });
442
+ var resolvePdfJsModuleCandidates = () => [
443
+ resolvePackagedPdfJsModuleCandidate(),
444
+ resolveBundledPdfJsModuleCandidate()
445
+ ];
446
+ var loadPdfToStoreWithWorker = async (file, store, options) => {
447
+ throwIfAborted(options.signal);
448
+ const arrayBuffer = await file.arrayBuffer();
449
+ throwIfAborted(options.signal);
450
+ return await new Promise((resolve, reject) => {
451
+ const { release, worker } = createPdfWorker();
452
+ const pageResults = [];
453
+ let storeChain = Promise.resolve();
454
+ let cleanedUp = false;
455
+ let settled = false;
456
+ const cleanup = () => {
457
+ if (cleanedUp) return;
458
+ cleanedUp = true;
459
+ options.signal?.removeEventListener("abort", abortWorker);
460
+ worker.onmessage = null;
461
+ worker.onerror = null;
462
+ worker.terminate();
463
+ release();
464
+ };
465
+ const rejectOnce = (error) => {
466
+ if (settled) return;
467
+ settled = true;
468
+ cleanup();
469
+ reject(error);
470
+ };
471
+ const rejectAfterQueuedStores = (error) => {
472
+ if (settled) return;
473
+ cleanup();
474
+ storeChain.then(() => {
475
+ if (settled) return;
476
+ settled = true;
477
+ reject(error);
478
+ }).catch(rejectOnce);
479
+ };
480
+ function abortWorker() {
481
+ rejectOnce(createAbortError());
482
+ }
483
+ const storePage = (message) => {
484
+ try {
485
+ options.onPageReceived?.({ pageNumber: message.page.pageNumber });
486
+ } catch (error) {
487
+ rejectOnce(error);
488
+ return;
489
+ }
490
+ storeChain = storeChain.then(async () => {
491
+ throwIfAborted(options.signal);
492
+ const blobId = await store.storeOriginal(message.page.blob);
493
+ throwIfAborted(options.signal);
494
+ const thumbnailBlobId = options.storeThumbnails && message.page.thumbnailBlob ? await store.storeThumbnail(message.page.thumbnailBlob) : "";
495
+ throwIfAborted(options.signal);
496
+ const pageResult = {
497
+ blobId,
498
+ height: message.page.height,
499
+ pageNumber: message.page.pageNumber,
500
+ thumbnailBlobId,
501
+ width: message.page.width
502
+ };
503
+ pageResults.push(pageResult);
504
+ await options.onPageStored?.(pageResult);
505
+ throwIfAborted(options.signal);
506
+ });
507
+ void storeChain.catch(rejectOnce);
508
+ };
509
+ worker.onmessage = (event) => {
510
+ const message = event.data;
511
+ if (message.type === "page") {
512
+ storePage(message);
513
+ return;
514
+ }
515
+ if (message.type === "error") {
516
+ rejectAfterQueuedStores(createWorkerError(message));
517
+ return;
518
+ }
519
+ storeChain.then(() => {
520
+ if (settled) return;
521
+ settled = true;
522
+ cleanup();
523
+ resolve(pageResults);
524
+ }).catch(rejectOnce);
525
+ };
526
+ worker.onerror = (event) => {
527
+ rejectAfterQueuedStores(event.error ?? new Error(event.message));
528
+ };
529
+ options.signal?.addEventListener("abort", abortWorker, { once: true });
530
+ try {
531
+ worker.postMessage(
532
+ {
533
+ pdfData: arrayBuffer,
534
+ pdfjsModuleCandidates: resolvePdfJsModuleCandidates(),
535
+ pageNumbers: options.pageNumbers ? [...options.pageNumbers] : void 0,
536
+ pageConcurrency: options.pageConcurrency,
537
+ scale: options.scale,
538
+ storeThumbnails: options.storeThumbnails,
539
+ type: "render"
540
+ },
541
+ [arrayBuffer]
542
+ );
543
+ } catch (error) {
544
+ rejectOnce(error);
545
+ }
546
+ });
547
+ };
548
+
213
549
  // src/image/pdf-loader.ts
214
550
  var pdfjsPromise = null;
215
551
  function getPdfJs() {
@@ -226,7 +562,7 @@ function getPdfJs() {
226
562
  return pdfjsPromise;
227
563
  }
228
564
  async function renderPageToCanvas(page, scale, signal) {
229
- throwIfAborted(signal);
565
+ throwIfAborted2(signal);
230
566
  const raw = page.getViewport({ scale: 1 });
231
567
  const adjustedScale = Math.round(raw.width * scale) / raw.width;
232
568
  const viewport = page.getViewport({ scale: adjustedScale });
@@ -248,7 +584,7 @@ async function renderPageToCanvas(page, scale, signal) {
248
584
  try {
249
585
  await renderTask.promise;
250
586
  } catch (error) {
251
- if (signal.aborted) throw createAbortError();
587
+ if (signal.aborted) throw createAbortError2();
252
588
  throw error;
253
589
  } finally {
254
590
  signal.removeEventListener("abort", abortRender);
@@ -256,15 +592,15 @@ async function renderPageToCanvas(page, scale, signal) {
256
592
  } else {
257
593
  await renderTask.promise;
258
594
  }
259
- throwIfAborted(signal);
595
+ throwIfAborted2(signal);
260
596
  return { canvas, width: w, height: h };
261
597
  }
262
- function createAbortError() {
598
+ function createAbortError2() {
263
599
  return new DOMException("Aborted", "AbortError");
264
600
  }
265
- function throwIfAborted(signal) {
601
+ function throwIfAborted2(signal) {
266
602
  if (signal?.aborted) {
267
- throw createAbortError();
603
+ throw createAbortError2();
268
604
  }
269
605
  }
270
606
  function normalizePdfPageNumbers(pageNumbers, pageCount) {
@@ -280,7 +616,7 @@ async function runWithConcurrency(items, concurrency, execute, signal) {
280
616
  await Promise.all(
281
617
  Array.from({ length: workerCount }, async () => {
282
618
  while (nextIndex < items.length) {
283
- throwIfAborted(signal);
619
+ throwIfAborted2(signal);
284
620
  const currentIndex = nextIndex;
285
621
  nextIndex += 1;
286
622
  const item = items[currentIndex];
@@ -288,7 +624,7 @@ async function runWithConcurrency(items, concurrency, execute, signal) {
288
624
  continue;
289
625
  }
290
626
  results[currentIndex] = await execute(item);
291
- throwIfAborted(signal);
627
+ throwIfAborted2(signal);
292
628
  }
293
629
  })
294
630
  );
@@ -299,11 +635,52 @@ async function loadPdfToStore(file, store, options) {
299
635
  const pageConcurrency = options?.pageConcurrency ?? 2;
300
636
  const storeThumbnails = options?.storeThumbnails ?? false;
301
637
  const signal = options?.signal;
302
- throwIfAborted(signal);
638
+ throwIfAborted2(signal);
639
+ if (canUsePdfWorkerRenderer()) {
640
+ let workerPageCount = 0;
641
+ try {
642
+ return await loadPdfToStoreWithWorker(file, store, {
643
+ scale,
644
+ pageNumbers: options?.pageNumbers,
645
+ pageConcurrency,
646
+ storeThumbnails,
647
+ signal,
648
+ onPageReceived: () => {
649
+ workerPageCount += 1;
650
+ },
651
+ onPageStored: async (page) => {
652
+ await options?.onPageStored?.(page);
653
+ }
654
+ });
655
+ } catch (error) {
656
+ if (signal?.aborted || workerPageCount > 0) {
657
+ throw error;
658
+ }
659
+ }
660
+ }
661
+ return await loadPdfToStoreOnMainThread(file, store, {
662
+ scale,
663
+ pageNumbers: options?.pageNumbers,
664
+ pageConcurrency,
665
+ storeThumbnails,
666
+ signal,
667
+ onPageStored: options?.onPageStored
668
+ });
669
+ }
670
+ async function loadPdfToStoreOnMainThread(file, store, options) {
671
+ const {
672
+ pageConcurrency,
673
+ scale,
674
+ signal,
675
+ storeThumbnails,
676
+ onPageStored,
677
+ pageNumbers
678
+ } = options;
679
+ throwIfAborted2(signal);
303
680
  const pdfjs = await getPdfJs();
304
- throwIfAborted(signal);
681
+ throwIfAborted2(signal);
305
682
  const arrayBuffer = await file.arrayBuffer();
306
- throwIfAborted(signal);
683
+ throwIfAborted2(signal);
307
684
  const loadingTask = pdfjs.getDocument({ data: arrayBuffer });
308
685
  if (signal) {
309
686
  const abortLoading = () => {
@@ -315,14 +692,14 @@ async function loadPdfToStore(file, store, options) {
315
692
  signal.removeEventListener("abort", abortLoading);
316
693
  return await loadPdfDocumentToStore(pdf2, store, {
317
694
  scale,
318
- pageNumbers: options?.pageNumbers,
695
+ pageNumbers,
319
696
  pageConcurrency,
320
697
  storeThumbnails,
321
698
  signal,
322
- onPageStored: options?.onPageStored
699
+ onPageStored
323
700
  });
324
701
  } catch (error) {
325
- if (signal.aborted) throw createAbortError();
702
+ if (signal.aborted) throw createAbortError2();
326
703
  throw error;
327
704
  } finally {
328
705
  signal.removeEventListener("abort", abortLoading);
@@ -331,16 +708,16 @@ async function loadPdfToStore(file, store, options) {
331
708
  const pdf = await loadingTask.promise;
332
709
  return await loadPdfDocumentToStore(pdf, store, {
333
710
  scale,
334
- pageNumbers: options?.pageNumbers,
711
+ pageNumbers,
335
712
  pageConcurrency,
336
713
  storeThumbnails,
337
- onPageStored: options?.onPageStored
714
+ onPageStored
338
715
  });
339
716
  }
340
717
  async function loadPdfDocumentToStore(pdf, store, options) {
341
718
  const { pageConcurrency, scale, signal } = options;
342
719
  const storeThumbnails = options.storeThumbnails ?? false;
343
- throwIfAborted(signal);
720
+ throwIfAborted2(signal);
344
721
  const pageNumbers = normalizePdfPageNumbers(options?.pageNumbers, pdf.numPages);
345
722
  const bufferedResults = /* @__PURE__ */ new Map();
346
723
  let nextEmitIndex = 0;
@@ -355,9 +732,9 @@ async function loadPdfDocumentToStore(pdf, store, options) {
355
732
  if (!bufferedResult) break;
356
733
  bufferedResults.delete(nextPageNumber);
357
734
  nextEmitIndex += 1;
358
- throwIfAborted(signal);
735
+ throwIfAborted2(signal);
359
736
  await options?.onPageStored?.(bufferedResult);
360
- throwIfAborted(signal);
737
+ throwIfAborted2(signal);
361
738
  }
362
739
  };
363
740
  const nextChain = emitChain.then(run, run);
@@ -369,20 +746,20 @@ async function loadPdfDocumentToStore(pdf, store, options) {
369
746
  pageNumbers,
370
747
  pageConcurrency,
371
748
  async (pageNumber) => {
372
- throwIfAborted(signal);
749
+ throwIfAborted2(signal);
373
750
  const page = await pdf.getPage(pageNumber);
374
- throwIfAborted(signal);
751
+ throwIfAborted2(signal);
375
752
  const { canvas, width, height } = await renderPageToCanvas(
376
753
  page,
377
754
  scale,
378
755
  signal
379
756
  );
380
- throwIfAborted(signal);
757
+ throwIfAborted2(signal);
381
758
  const mime = "image/png";
382
759
  const pageBlob = await encodeCanvasToBlob(canvas, { mimeType: mime });
383
- throwIfAborted(signal);
760
+ throwIfAborted2(signal);
384
761
  const blobId = await store.storeOriginal(pageBlob);
385
- throwIfAborted(signal);
762
+ throwIfAborted2(signal);
386
763
  const thumbnailBlobId = storeThumbnails ? await (async () => {
387
764
  const thumbScale = Math.min(1, 256 / Math.max(width, height));
388
765
  const tw = Math.max(1, Math.round(width * thumbScale));
@@ -399,7 +776,7 @@ async function loadPdfDocumentToStore(pdf, store, options) {
399
776
  const thumbBlob = await encodeCanvasToBlob(thumbCanvas, {
400
777
  mimeType: mime
401
778
  });
402
- throwIfAborted(signal);
779
+ throwIfAborted2(signal);
403
780
  return await store.storeThumbnail(thumbBlob);
404
781
  })() : "";
405
782
  const pageResult = {