canvu-react 0.4.75 → 0.4.77

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/react.cjs CHANGED
@@ -1193,342 +1193,6 @@ async function createThumbnailBlobUrlFromStore(store, thumbnailBlobId) {
1193
1193
  return URL.createObjectURL(blob);
1194
1194
  }
1195
1195
 
1196
- // src/image/pdf-worker-renderer.ts
1197
- var PDF_WORKER_SOURCE = `
1198
- let pdfjsPromise = null;
1199
-
1200
- const loadPdfJs = async (pdfjsModuleCandidates) => {
1201
- if (!pdfjsPromise) {
1202
- pdfjsPromise = (async () => {
1203
- let lastError;
1204
- for (const candidate of pdfjsModuleCandidates) {
1205
- try {
1206
- const pdfjs = await import(candidate.moduleUrl);
1207
- if (pdfjs.GlobalWorkerOptions) {
1208
- pdfjs.GlobalWorkerOptions.workerSrc = candidate.workerUrl;
1209
- }
1210
- return pdfjs;
1211
- } catch (error) {
1212
- lastError = error;
1213
- }
1214
- }
1215
- throw lastError ?? new Error("Unable to load pdfjs-dist");
1216
- })();
1217
- }
1218
- return await pdfjsPromise;
1219
- };
1220
-
1221
- const createErrorPayload = (error) => ({
1222
- message: error instanceof Error ? error.message : String(error),
1223
- name: error instanceof Error ? error.name : "Error",
1224
- stack: error instanceof Error ? error.stack : undefined,
1225
- });
1226
-
1227
- const normalizePdfPageNumbers = (pageNumbers, pageCount) => {
1228
- if (!Array.isArray(pageNumbers) || pageNumbers.length === 0) {
1229
- return Array.from({ length: pageCount }, (_, index) => index + 1);
1230
- }
1231
- return [...new Set(pageNumbers)]
1232
- .filter((pageNumber) => pageNumber >= 1 && pageNumber <= pageCount)
1233
- .sort((left, right) => left - right);
1234
- };
1235
-
1236
- const runWithConcurrency = async (items, concurrency, execute) => {
1237
- const results = new Array(items.length);
1238
- let nextIndex = 0;
1239
- const safeConcurrency = Number.isFinite(concurrency) && concurrency > 0
1240
- ? Math.round(concurrency)
1241
- : 1;
1242
- const workerCount = Math.max(1, Math.min(safeConcurrency, items.length));
1243
- await Promise.all(
1244
- Array.from({ length: workerCount }, async () => {
1245
- while (nextIndex < items.length) {
1246
- const currentIndex = nextIndex;
1247
- nextIndex += 1;
1248
- const item = items[currentIndex];
1249
- if (item === undefined) {
1250
- continue;
1251
- }
1252
- results[currentIndex] = await execute(item);
1253
- }
1254
- }),
1255
- );
1256
- return results;
1257
- };
1258
-
1259
- const renderPageToBlob = async (page, scale, storeThumbnails) => {
1260
- const raw = page.getViewport({ scale: 1 });
1261
- const adjustedScale = Math.round(raw.width * scale) / raw.width;
1262
- const viewport = page.getViewport({ scale: adjustedScale });
1263
- const width = Math.round(viewport.width);
1264
- const height = Math.round(viewport.height);
1265
- const canvas = new OffscreenCanvas(width, height);
1266
- const canvasContext = canvas.getContext("2d");
1267
- if (!canvasContext) {
1268
- throw new Error("OffscreenCanvas 2D context unavailable");
1269
- }
1270
- canvasContext.imageSmoothingEnabled = true;
1271
- canvasContext.imageSmoothingQuality = "high";
1272
- await page.render({ canvasContext, viewport }).promise;
1273
- const blob = await canvas.convertToBlob({ type: "image/png" });
1274
- let thumbnailBlob;
1275
- if (storeThumbnails) {
1276
- const thumbScale = Math.min(1, 256 / Math.max(width, height));
1277
- const thumbnailWidth = Math.max(1, Math.round(width * thumbScale));
1278
- const thumbnailHeight = Math.max(1, Math.round(height * thumbScale));
1279
- const thumbnailCanvas = new OffscreenCanvas(thumbnailWidth, thumbnailHeight);
1280
- const thumbnailContext = thumbnailCanvas.getContext("2d");
1281
- if (thumbnailContext) {
1282
- thumbnailContext.imageSmoothingEnabled = true;
1283
- thumbnailContext.imageSmoothingQuality = "high";
1284
- thumbnailContext.drawImage(canvas, 0, 0, thumbnailWidth, thumbnailHeight);
1285
- }
1286
- thumbnailBlob = await thumbnailCanvas.convertToBlob({ type: "image/png" });
1287
- }
1288
- return { blob, height, thumbnailBlob, width };
1289
- };
1290
-
1291
- self.onmessage = async (event) => {
1292
- const message = event.data;
1293
- if (!message || message.type !== "render") {
1294
- return;
1295
- }
1296
- try {
1297
- const pdfjs = await loadPdfJs(message.pdfjsModuleCandidates);
1298
- const pdf = await pdfjs.getDocument({
1299
- data: new Uint8Array(message.pdfData),
1300
- disableWorker: true,
1301
- }).promise;
1302
- try {
1303
- const pageNumbers = normalizePdfPageNumbers(message.pageNumbers, pdf.numPages);
1304
- const bufferedPages = new Map();
1305
- let nextEmitIndex = 0;
1306
- let emitChain = Promise.resolve();
1307
- const emitPageInOrder = async (pageNumber, renderedPage) => {
1308
- bufferedPages.set(pageNumber, renderedPage);
1309
- const flush = () => {
1310
- while (nextEmitIndex < pageNumbers.length) {
1311
- const nextPageNumber = pageNumbers[nextEmitIndex];
1312
- if (nextPageNumber == null) break;
1313
- const nextPage = bufferedPages.get(nextPageNumber);
1314
- if (!nextPage) break;
1315
- bufferedPages.delete(nextPageNumber);
1316
- nextEmitIndex += 1;
1317
- self.postMessage({
1318
- type: "page",
1319
- page: {
1320
- ...nextPage,
1321
- pageNumber: nextPageNumber,
1322
- },
1323
- });
1324
- }
1325
- };
1326
- const nextChain = emitChain.then(flush, flush);
1327
- emitChain = nextChain.catch(() => {});
1328
- await nextChain;
1329
- };
1330
- await runWithConcurrency(
1331
- pageNumbers,
1332
- message.pageConcurrency,
1333
- async (pageNumber) => {
1334
- const page = await pdf.getPage(pageNumber);
1335
- try {
1336
- const renderedPage = await renderPageToBlob(
1337
- page,
1338
- message.scale,
1339
- message.storeThumbnails,
1340
- );
1341
- await emitPageInOrder(pageNumber, renderedPage);
1342
- } finally {
1343
- page.cleanup();
1344
- }
1345
- },
1346
- );
1347
- await emitChain;
1348
- } finally {
1349
- await pdf.destroy();
1350
- }
1351
- self.postMessage({ type: "done" });
1352
- } catch (error) {
1353
- self.postMessage({ type: "error", error: createErrorPayload(error) });
1354
- }
1355
- };
1356
- `;
1357
- var isRecord = (value) => typeof value === "object" && value !== null;
1358
- var hasObjectMembers = (value) => (typeof value === "object" || typeof value === "function") && value !== null;
1359
- var isOffscreenCanvasCandidate = (value) => typeof value === "function";
1360
- var getEnvironmentRecord = (environment) => isRecord(environment) ? environment : null;
1361
- var canUsePdfWorkerRenderer = (environment = globalThis) => {
1362
- const candidateEnvironment = getEnvironmentRecord(environment);
1363
- if (!candidateEnvironment) return false;
1364
- if (typeof candidateEnvironment.Worker !== "function") return false;
1365
- if (typeof candidateEnvironment.Blob !== "function") return false;
1366
- if (!isOffscreenCanvasCandidate(candidateEnvironment.OffscreenCanvas)) {
1367
- return false;
1368
- }
1369
- const urlCandidate = candidateEnvironment.URL;
1370
- if (!hasObjectMembers(urlCandidate)) return false;
1371
- if (typeof urlCandidate.createObjectURL !== "function") return false;
1372
- if (typeof urlCandidate.revokeObjectURL !== "function") return false;
1373
- try {
1374
- const canvas = new candidateEnvironment.OffscreenCanvas(1, 1);
1375
- return typeof canvas.getContext === "function" && typeof canvas.convertToBlob === "function";
1376
- } catch {
1377
- return false;
1378
- }
1379
- };
1380
- var createAbortError = () => new DOMException("Aborted", "AbortError");
1381
- var throwIfAborted = (signal) => {
1382
- if (signal?.aborted) {
1383
- throw createAbortError();
1384
- }
1385
- };
1386
- var createPdfWorker = () => {
1387
- const workerBlob = new Blob([PDF_WORKER_SOURCE], {
1388
- type: "text/javascript"
1389
- });
1390
- const workerUrl = URL.createObjectURL(workerBlob);
1391
- try {
1392
- const worker = new Worker(workerUrl, {
1393
- name: "canvu-pdf-renderer",
1394
- type: "module"
1395
- });
1396
- return {
1397
- release: () => URL.revokeObjectURL(workerUrl),
1398
- worker
1399
- };
1400
- } catch (error) {
1401
- URL.revokeObjectURL(workerUrl);
1402
- throw error;
1403
- }
1404
- };
1405
- var createWorkerError = (message) => {
1406
- const error = new Error(message.error.message);
1407
- error.name = message.error.name;
1408
- error.stack = message.error.stack;
1409
- return error;
1410
- };
1411
- var PACKAGED_PDFJS_MODULE_FILE = "./pdf.mjs";
1412
- var PACKAGED_PDFJS_WORKER_FILE = "./pdf.worker.mjs";
1413
- var resolveRelativeAssetUrl = (path, baseUrl) => new URL(path, baseUrl).toString();
1414
- var resolvePackagedPdfJsModuleCandidate = (baseUrl = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('react.cjs', document.baseURI).href))) => ({
1415
- moduleUrl: resolveRelativeAssetUrl(PACKAGED_PDFJS_MODULE_FILE, baseUrl),
1416
- workerUrl: resolveRelativeAssetUrl(PACKAGED_PDFJS_WORKER_FILE, baseUrl)
1417
- });
1418
- var resolveBundledPdfJsModuleCandidate = () => ({
1419
- 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('react.cjs', document.baseURI).href))).toString(),
1420
- workerUrl: new URL(
1421
- "pdfjs-dist/build/pdf.worker.min.mjs",
1422
- (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('react.cjs', document.baseURI).href))
1423
- ).toString()
1424
- });
1425
- var resolvePdfJsModuleCandidates = () => [
1426
- resolvePackagedPdfJsModuleCandidate(),
1427
- resolveBundledPdfJsModuleCandidate()
1428
- ];
1429
- var loadPdfToStoreWithWorker = async (file, store, options) => {
1430
- throwIfAborted(options.signal);
1431
- const arrayBuffer = await file.arrayBuffer();
1432
- throwIfAborted(options.signal);
1433
- return await new Promise((resolve, reject) => {
1434
- const { release, worker } = createPdfWorker();
1435
- const pageResults = [];
1436
- let storeChain = Promise.resolve();
1437
- let cleanedUp = false;
1438
- let settled = false;
1439
- const cleanup = () => {
1440
- if (cleanedUp) return;
1441
- cleanedUp = true;
1442
- options.signal?.removeEventListener("abort", abortWorker);
1443
- worker.onmessage = null;
1444
- worker.onerror = null;
1445
- worker.terminate();
1446
- release();
1447
- };
1448
- const rejectOnce = (error) => {
1449
- if (settled) return;
1450
- settled = true;
1451
- cleanup();
1452
- reject(error);
1453
- };
1454
- const rejectAfterQueuedStores = (error) => {
1455
- if (settled) return;
1456
- cleanup();
1457
- storeChain.then(() => {
1458
- if (settled) return;
1459
- settled = true;
1460
- reject(error);
1461
- }).catch(rejectOnce);
1462
- };
1463
- function abortWorker() {
1464
- rejectOnce(createAbortError());
1465
- }
1466
- const storePage = (message) => {
1467
- try {
1468
- options.onPageReceived?.({ pageNumber: message.page.pageNumber });
1469
- } catch (error) {
1470
- rejectOnce(error);
1471
- return;
1472
- }
1473
- storeChain = storeChain.then(async () => {
1474
- throwIfAborted(options.signal);
1475
- const blobId = await store.storeOriginal(message.page.blob);
1476
- throwIfAborted(options.signal);
1477
- const thumbnailBlobId = options.storeThumbnails && message.page.thumbnailBlob ? await store.storeThumbnail(message.page.thumbnailBlob) : "";
1478
- throwIfAborted(options.signal);
1479
- const pageResult = {
1480
- blobId,
1481
- height: message.page.height,
1482
- pageNumber: message.page.pageNumber,
1483
- thumbnailBlobId,
1484
- width: message.page.width
1485
- };
1486
- pageResults.push(pageResult);
1487
- await options.onPageStored?.(pageResult);
1488
- throwIfAborted(options.signal);
1489
- });
1490
- void storeChain.catch(rejectOnce);
1491
- };
1492
- worker.onmessage = (event) => {
1493
- const message = event.data;
1494
- if (message.type === "page") {
1495
- storePage(message);
1496
- return;
1497
- }
1498
- if (message.type === "error") {
1499
- rejectAfterQueuedStores(createWorkerError(message));
1500
- return;
1501
- }
1502
- storeChain.then(() => {
1503
- if (settled) return;
1504
- settled = true;
1505
- cleanup();
1506
- resolve(pageResults);
1507
- }).catch(rejectOnce);
1508
- };
1509
- worker.onerror = (event) => {
1510
- rejectAfterQueuedStores(event.error ?? new Error(event.message));
1511
- };
1512
- options.signal?.addEventListener("abort", abortWorker, { once: true });
1513
- try {
1514
- worker.postMessage(
1515
- {
1516
- pdfData: arrayBuffer,
1517
- pdfjsModuleCandidates: resolvePdfJsModuleCandidates(),
1518
- pageNumbers: options.pageNumbers ? [...options.pageNumbers] : void 0,
1519
- pageConcurrency: options.pageConcurrency,
1520
- scale: options.scale,
1521
- storeThumbnails: options.storeThumbnails,
1522
- type: "render"
1523
- },
1524
- [arrayBuffer]
1525
- );
1526
- } catch (error) {
1527
- rejectOnce(error);
1528
- }
1529
- });
1530
- };
1531
-
1532
1196
  // src/image/pdf-loader.ts
1533
1197
  var pdfjsPromise = null;
1534
1198
  function getPdfJs() {
@@ -1545,7 +1209,7 @@ function getPdfJs() {
1545
1209
  return pdfjsPromise;
1546
1210
  }
1547
1211
  async function renderPageToCanvas(page, scale, signal) {
1548
- throwIfAborted2(signal);
1212
+ throwIfAborted(signal);
1549
1213
  const raw = page.getViewport({ scale: 1 });
1550
1214
  const adjustedScale = Math.round(raw.width * scale) / raw.width;
1551
1215
  const viewport = page.getViewport({ scale: adjustedScale });
@@ -1567,7 +1231,7 @@ async function renderPageToCanvas(page, scale, signal) {
1567
1231
  try {
1568
1232
  await renderTask.promise;
1569
1233
  } catch (error) {
1570
- if (signal.aborted) throw createAbortError2();
1234
+ if (signal.aborted) throw createAbortError();
1571
1235
  throw error;
1572
1236
  } finally {
1573
1237
  signal.removeEventListener("abort", abortRender);
@@ -1575,15 +1239,15 @@ async function renderPageToCanvas(page, scale, signal) {
1575
1239
  } else {
1576
1240
  await renderTask.promise;
1577
1241
  }
1578
- throwIfAborted2(signal);
1242
+ throwIfAborted(signal);
1579
1243
  return { canvas, width: w, height: h };
1580
1244
  }
1581
- function createAbortError2() {
1245
+ function createAbortError() {
1582
1246
  return new DOMException("Aborted", "AbortError");
1583
1247
  }
1584
- function throwIfAborted2(signal) {
1248
+ function throwIfAborted(signal) {
1585
1249
  if (signal?.aborted) {
1586
- throw createAbortError2();
1250
+ throw createAbortError();
1587
1251
  }
1588
1252
  }
1589
1253
  function normalizePdfPageNumbers(pageNumbers, pageCount) {
@@ -1599,7 +1263,7 @@ async function runWithConcurrency(items, concurrency, execute, signal) {
1599
1263
  await Promise.all(
1600
1264
  Array.from({ length: workerCount }, async () => {
1601
1265
  while (nextIndex < items.length) {
1602
- throwIfAborted2(signal);
1266
+ throwIfAborted(signal);
1603
1267
  const currentIndex = nextIndex;
1604
1268
  nextIndex += 1;
1605
1269
  const item = items[currentIndex];
@@ -1607,7 +1271,7 @@ async function runWithConcurrency(items, concurrency, execute, signal) {
1607
1271
  continue;
1608
1272
  }
1609
1273
  results[currentIndex] = await execute(item);
1610
- throwIfAborted2(signal);
1274
+ throwIfAborted(signal);
1611
1275
  }
1612
1276
  })
1613
1277
  );
@@ -1618,52 +1282,11 @@ async function loadPdfToStore(file, store, options) {
1618
1282
  const pageConcurrency = options?.pageConcurrency ?? 2;
1619
1283
  const storeThumbnails = options?.storeThumbnails ?? false;
1620
1284
  const signal = options?.signal;
1621
- throwIfAborted2(signal);
1622
- if (canUsePdfWorkerRenderer()) {
1623
- let workerPageCount = 0;
1624
- try {
1625
- return await loadPdfToStoreWithWorker(file, store, {
1626
- scale,
1627
- pageNumbers: options?.pageNumbers,
1628
- pageConcurrency,
1629
- storeThumbnails,
1630
- signal,
1631
- onPageReceived: () => {
1632
- workerPageCount += 1;
1633
- },
1634
- onPageStored: async (page) => {
1635
- await options?.onPageStored?.(page);
1636
- }
1637
- });
1638
- } catch (error) {
1639
- if (signal?.aborted || workerPageCount > 0) {
1640
- throw error;
1641
- }
1642
- }
1643
- }
1644
- return await loadPdfToStoreOnMainThread(file, store, {
1645
- scale,
1646
- pageNumbers: options?.pageNumbers,
1647
- pageConcurrency,
1648
- storeThumbnails,
1649
- signal,
1650
- onPageStored: options?.onPageStored
1651
- });
1652
- }
1653
- async function loadPdfToStoreOnMainThread(file, store, options) {
1654
- const {
1655
- pageConcurrency,
1656
- scale,
1657
- signal,
1658
- storeThumbnails,
1659
- onPageStored,
1660
- pageNumbers
1661
- } = options;
1662
- throwIfAborted2(signal);
1285
+ throwIfAborted(signal);
1663
1286
  const pdfjs = await getPdfJs();
1664
- throwIfAborted2(signal);
1287
+ throwIfAborted(signal);
1665
1288
  const arrayBuffer = await file.arrayBuffer();
1666
- throwIfAborted2(signal);
1289
+ throwIfAborted(signal);
1667
1290
  const loadingTask = pdfjs.getDocument({ data: arrayBuffer });
1668
1291
  if (signal) {
1669
1292
  const abortLoading = () => {
@@ -1675,14 +1298,14 @@ async function loadPdfToStoreOnMainThread(file, store, options) {
1675
1298
  signal.removeEventListener("abort", abortLoading);
1676
1299
  return await loadPdfDocumentToStore(pdf2, store, {
1677
1300
  scale,
1678
- pageNumbers,
1301
+ pageNumbers: options?.pageNumbers,
1679
1302
  pageConcurrency,
1680
1303
  storeThumbnails,
1681
1304
  signal,
1682
- onPageStored
1305
+ onPageStored: options?.onPageStored
1683
1306
  });
1684
1307
  } catch (error) {
1685
- if (signal.aborted) throw createAbortError2();
1308
+ if (signal.aborted) throw createAbortError();
1686
1309
  throw error;
1687
1310
  } finally {
1688
1311
  signal.removeEventListener("abort", abortLoading);
@@ -1691,16 +1314,16 @@ async function loadPdfToStoreOnMainThread(file, store, options) {
1691
1314
  const pdf = await loadingTask.promise;
1692
1315
  return await loadPdfDocumentToStore(pdf, store, {
1693
1316
  scale,
1694
- pageNumbers,
1317
+ pageNumbers: options?.pageNumbers,
1695
1318
  pageConcurrency,
1696
1319
  storeThumbnails,
1697
- onPageStored
1320
+ onPageStored: options?.onPageStored
1698
1321
  });
1699
1322
  }
1700
1323
  async function loadPdfDocumentToStore(pdf, store, options) {
1701
1324
  const { pageConcurrency, scale, signal } = options;
1702
1325
  const storeThumbnails = options.storeThumbnails ?? false;
1703
- throwIfAborted2(signal);
1326
+ throwIfAborted(signal);
1704
1327
  const pageNumbers = normalizePdfPageNumbers(options?.pageNumbers, pdf.numPages);
1705
1328
  const bufferedResults = /* @__PURE__ */ new Map();
1706
1329
  let nextEmitIndex = 0;
@@ -1715,9 +1338,9 @@ async function loadPdfDocumentToStore(pdf, store, options) {
1715
1338
  if (!bufferedResult) break;
1716
1339
  bufferedResults.delete(nextPageNumber);
1717
1340
  nextEmitIndex += 1;
1718
- throwIfAborted2(signal);
1341
+ throwIfAborted(signal);
1719
1342
  await options?.onPageStored?.(bufferedResult);
1720
- throwIfAborted2(signal);
1343
+ throwIfAborted(signal);
1721
1344
  }
1722
1345
  };
1723
1346
  const nextChain = emitChain.then(run, run);
@@ -1729,20 +1352,20 @@ async function loadPdfDocumentToStore(pdf, store, options) {
1729
1352
  pageNumbers,
1730
1353
  pageConcurrency,
1731
1354
  async (pageNumber) => {
1732
- throwIfAborted2(signal);
1355
+ throwIfAborted(signal);
1733
1356
  const page = await pdf.getPage(pageNumber);
1734
- throwIfAborted2(signal);
1357
+ throwIfAborted(signal);
1735
1358
  const { canvas, width, height } = await renderPageToCanvas(
1736
1359
  page,
1737
1360
  scale,
1738
1361
  signal
1739
1362
  );
1740
- throwIfAborted2(signal);
1363
+ throwIfAborted(signal);
1741
1364
  const mime = "image/png";
1742
1365
  const pageBlob = await encodeCanvasToBlob(canvas, { mimeType: mime });
1743
- throwIfAborted2(signal);
1366
+ throwIfAborted(signal);
1744
1367
  const blobId = await store.storeOriginal(pageBlob);
1745
- throwIfAborted2(signal);
1368
+ throwIfAborted(signal);
1746
1369
  const thumbnailBlobId = storeThumbnails ? await (async () => {
1747
1370
  const thumbScale = Math.min(1, 256 / Math.max(width, height));
1748
1371
  const tw = Math.max(1, Math.round(width * thumbScale));
@@ -1759,7 +1382,7 @@ async function loadPdfDocumentToStore(pdf, store, options) {
1759
1382
  const thumbBlob = await encodeCanvasToBlob(thumbCanvas, {
1760
1383
  mimeType: mime
1761
1384
  });
1762
- throwIfAborted2(signal);
1385
+ throwIfAborted(signal);
1763
1386
  return await store.storeThumbnail(thumbBlob);
1764
1387
  })() : "";
1765
1388
  const pageResult = {
@@ -2113,22 +1736,22 @@ function finalizeIngestedItem(item, context, uploadResult, decorateItem) {
2113
1736
  const itemWithAssetData = applyAssetUploadResultToItem(item, uploadResult);
2114
1737
  return decorateItem ? decorateItem(itemWithAssetData, context) : itemWithAssetData;
2115
1738
  }
2116
- function createAbortError3() {
1739
+ function createAbortError2() {
2117
1740
  return new DOMException("Aborted", "AbortError");
2118
1741
  }
2119
- function throwIfAborted3(signal) {
1742
+ function throwIfAborted2(signal) {
2120
1743
  if (signal?.aborted) {
2121
- throw createAbortError3();
1744
+ throw createAbortError2();
2122
1745
  }
2123
1746
  }
2124
1747
  function isAbortError(error) {
2125
1748
  return (error instanceof DOMException || error instanceof Error) && error.name === "AbortError";
2126
1749
  }
2127
1750
  async function uploadAssetIfNeeded(assetStore, file, kind, signal) {
2128
- throwIfAborted3(signal);
1751
+ throwIfAborted2(signal);
2129
1752
  if (!assetStore) return null;
2130
1753
  const result = await assetStore.upload({ file, kind, signal });
2131
- throwIfAborted3(signal);
1754
+ throwIfAborted2(signal);
2132
1755
  return result ?? null;
2133
1756
  }
2134
1757
  async function ingestAssetFilesToSceneItems(options) {
@@ -2151,7 +1774,7 @@ async function ingestAssetFilesToSceneItems(options) {
2151
1774
  const errors = [];
2152
1775
  let occupiedBottomY = existingItems.length > 0 ? Math.max(...existingItems.map((item) => item.bounds.y + item.bounds.height)) : null;
2153
1776
  for (const file of files) {
2154
- throwIfAborted3(signal);
1777
+ throwIfAborted2(signal);
2155
1778
  const kind = getAssetKindForFile(file);
2156
1779
  if (!kind) {
2157
1780
  const error = {
@@ -2177,10 +1800,10 @@ async function ingestAssetFilesToSceneItems(options) {
2177
1800
  storeThumbnails: false,
2178
1801
  signal,
2179
1802
  onPageStored: async (page) => {
2180
- throwIfAborted3(signal);
1803
+ throwIfAborted2(signal);
2181
1804
  const uploadResult2 = await uploadResultPromise;
2182
1805
  const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
2183
- throwIfAborted3(signal);
1806
+ throwIfAborted2(signal);
2184
1807
  const naturalTopY2 = worldCenter.y - page.height / 2;
2185
1808
  const stackedTopY2 = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
2186
1809
  const bounds2 = {
@@ -2220,18 +1843,18 @@ async function ingestAssetFilesToSceneItems(options) {
2220
1843
  );
2221
1844
  items.push(item2);
2222
1845
  occupiedBottomY = bounds2.y + page.height;
2223
- throwIfAborted3(signal);
1846
+ throwIfAborted2(signal);
2224
1847
  onItemsReady?.([item2], { file, kind });
2225
1848
  }
2226
1849
  });
2227
1850
  continue;
2228
1851
  }
2229
- throwIfAborted3(signal);
1852
+ throwIfAborted2(signal);
2230
1853
  const [uploadResult, storedImage] = await Promise.all([
2231
1854
  uploadAssetIfNeeded(assetStore, file, kind, signal),
2232
1855
  loadImageToStore(file, imageStore)
2233
1856
  ]);
2234
- throwIfAborted3(signal);
1857
+ throwIfAborted2(signal);
2235
1858
  const { blobId, thumbnailBlobId, width, height } = storedImage;
2236
1859
  const fullUrl = await createBlobUrlFromStore(imageStore, blobId);
2237
1860
  const thumbBlob = await imageStore.getThumbnail(thumbnailBlobId);
@@ -2269,7 +1892,7 @@ async function ingestAssetFilesToSceneItems(options) {
2269
1892
  decorateItem
2270
1893
  );
2271
1894
  items.push(item);
2272
- throwIfAborted3(signal);
1895
+ throwIfAborted2(signal);
2273
1896
  onItemsReady?.([item], { file, kind });
2274
1897
  occupiedBottomY = occupiedBottomY == null ? bounds.y + height : Math.max(occupiedBottomY, bounds.y + height);
2275
1898
  } catch (error) {
@@ -7546,6 +7169,9 @@ function shouldRedrawRasterImageCanvas({
7546
7169
  if (currentSourceKey !== nextSourceKey && safeCurrentWidth === safeNextWidth && safeCurrentHeight === safeNextHeight) {
7547
7170
  return true;
7548
7171
  }
7172
+ if (currentSourceKey !== nextSourceKey && (safeNextWidth < safeCurrentWidth || safeNextHeight < safeCurrentHeight)) {
7173
+ return true;
7174
+ }
7549
7175
  return safeNextWidth > safeCurrentWidth * upscaleRedrawRatio || safeNextHeight > safeCurrentHeight * upscaleRedrawRatio;
7550
7176
  }
7551
7177
  function toPositiveFiniteNumber(value, fallback) {
@@ -8925,6 +8551,42 @@ function didReadOnlyActivationMovePastTap(session, pointer, tapPx) {
8925
8551
  return Math.hypot(dx, dy) > tapPx;
8926
8552
  }
8927
8553
 
8554
+ // src/react/render-scheduler.ts
8555
+ var getDefaultAnimationFrameRuntime = () => typeof window === "undefined" ? {} : window;
8556
+ function createAnimationFrameRenderScheduler({
8557
+ render,
8558
+ runtime = getDefaultAnimationFrameRuntime()
8559
+ }) {
8560
+ let frameId = null;
8561
+ const canceledFrameIds = /* @__PURE__ */ new Set();
8562
+ const request = () => {
8563
+ if (frameId != null) return;
8564
+ if (typeof runtime.requestAnimationFrame !== "function") {
8565
+ render();
8566
+ return;
8567
+ }
8568
+ const scheduledFrameId = runtime.requestAnimationFrame(() => {
8569
+ const currentFrameId = scheduledFrameId;
8570
+ if (canceledFrameIds.has(currentFrameId)) {
8571
+ canceledFrameIds.delete(currentFrameId);
8572
+ return;
8573
+ }
8574
+ if (frameId === currentFrameId) {
8575
+ frameId = null;
8576
+ }
8577
+ render();
8578
+ });
8579
+ frameId = scheduledFrameId;
8580
+ };
8581
+ const cancel = () => {
8582
+ if (frameId == null) return;
8583
+ canceledFrameIds.add(frameId);
8584
+ runtime.cancelAnimationFrame?.(frameId);
8585
+ frameId = null;
8586
+ };
8587
+ return { request, cancel };
8588
+ }
8589
+
8928
8590
  // src/react/stable-selection.ts
8929
8591
  function shallowEqualStringArray(a, b) {
8930
8592
  if (a === b) return true;
@@ -9937,6 +9599,9 @@ var VectorViewport = react.forwardRef(
9937
9599
  reducedMotionRef.current = reducedMotion;
9938
9600
  const [zoomPercent, setZoomPercent] = react.useState(100);
9939
9601
  const [cameraTick, setCameraTick] = react.useState(0);
9602
+ const renderFrameSchedulerRef = react.useRef(
9603
+ null
9604
+ );
9940
9605
  const [placementPreview, setPlacementPreview] = react.useState(null);
9941
9606
  const [eraserTrail, setEraserTrail] = react.useState([]);
9942
9607
  const [laserTrail, setLaserTrail] = react.useState([]);
@@ -10317,7 +9982,7 @@ var VectorViewport = react.forwardRef(
10317
9982
  renderSceneWithLivePenStroke
10318
9983
  ]
10319
9984
  );
10320
- const renderFrame = react.useCallback(() => {
9985
+ const renderFrameNow = react.useCallback(() => {
10321
9986
  const renderer = rendererRef.current;
10322
9987
  const cam = cameraRef.current;
10323
9988
  if (!renderer || !cam) return;
@@ -10330,6 +9995,14 @@ var VectorViewport = react.forwardRef(
10330
9995
  }
10331
9996
  onCameraChangeRef.current?.();
10332
9997
  }, []);
9998
+ const renderFrame = react.useCallback(() => {
9999
+ const scheduler = renderFrameSchedulerRef.current;
10000
+ if (!scheduler) {
10001
+ renderFrameNow();
10002
+ return;
10003
+ }
10004
+ scheduler.request();
10005
+ }, [renderFrameNow]);
10333
10006
  react.useEffect(() => {
10334
10007
  const el = sceneContainerRef.current;
10335
10008
  const interactionEl = interactionRootRef.current;
@@ -10348,6 +10021,9 @@ var VectorViewport = react.forwardRef(
10348
10021
  rasterImageCanvas: imageCanvasRenderingRef.current
10349
10022
  });
10350
10023
  rendererRef.current = renderer;
10024
+ renderFrameSchedulerRef.current = createAnimationFrameRenderScheduler({
10025
+ render: renderFrameNow
10026
+ });
10351
10027
  renderer.setInteractionState({
10352
10028
  selectedIds: effectiveSelectedIdsRef.current,
10353
10029
  hoveredItemId: hoveredItemIdRef.current
@@ -10382,6 +10058,8 @@ var VectorViewport = react.forwardRef(
10382
10058
  });
10383
10059
  }
10384
10060
  return () => {
10061
+ renderFrameSchedulerRef.current?.cancel();
10062
+ renderFrameSchedulerRef.current = null;
10385
10063
  detachInput();
10386
10064
  detachPencil?.();
10387
10065
  renderer.destroy();
@@ -10390,7 +10068,7 @@ var VectorViewport = react.forwardRef(
10390
10068
  cameraRef.current = null;
10391
10069
  setCameraForOverlay(null);
10392
10070
  };
10393
- }, [applePencilNav, renderFrame, resolveReadOnlyActivation]);
10071
+ }, [applePencilNav, renderFrame, renderFrameNow, resolveReadOnlyActivation]);
10394
10072
  react.useEffect(() => {
10395
10073
  rendererRef.current?.setInteractionState({
10396
10074
  selectedIds: effectiveSelectedIds,
@@ -10518,10 +10196,10 @@ var VectorViewport = react.forwardRef(
10518
10196
  livePenStrokeItemRef.current = null;
10519
10197
  }
10520
10198
  scene.setItems(resolvedSceneItems);
10521
- renderFrame();
10199
+ renderFrameNow();
10522
10200
  renderer?.renderLiveItem(livePenStrokeItemRef.current);
10523
10201
  }
10524
- }, [resolvedSceneItems, renderFrame]);
10202
+ }, [resolvedSceneItems, renderFrameNow]);
10525
10203
  react.useEffect(() => {
10526
10204
  const ids = effectiveSelectedIds;
10527
10205
  const valid = ids.filter((id) => items.some((i) => i.id === id));