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