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.js CHANGED
@@ -1186,342 +1186,6 @@ async function createThumbnailBlobUrlFromStore(store, thumbnailBlobId) {
1186
1186
  return URL.createObjectURL(blob);
1187
1187
  }
1188
1188
 
1189
- // src/image/pdf-worker-renderer.ts
1190
- var PDF_WORKER_SOURCE = `
1191
- let pdfjsPromise = null;
1192
-
1193
- const loadPdfJs = async (pdfjsModuleCandidates) => {
1194
- if (!pdfjsPromise) {
1195
- pdfjsPromise = (async () => {
1196
- let lastError;
1197
- for (const candidate of pdfjsModuleCandidates) {
1198
- try {
1199
- const pdfjs = await import(candidate.moduleUrl);
1200
- if (pdfjs.GlobalWorkerOptions) {
1201
- pdfjs.GlobalWorkerOptions.workerSrc = candidate.workerUrl;
1202
- }
1203
- return pdfjs;
1204
- } catch (error) {
1205
- lastError = error;
1206
- }
1207
- }
1208
- throw lastError ?? new Error("Unable to load pdfjs-dist");
1209
- })();
1210
- }
1211
- return await pdfjsPromise;
1212
- };
1213
-
1214
- const createErrorPayload = (error) => ({
1215
- message: error instanceof Error ? error.message : String(error),
1216
- name: error instanceof Error ? error.name : "Error",
1217
- stack: error instanceof Error ? error.stack : undefined,
1218
- });
1219
-
1220
- const normalizePdfPageNumbers = (pageNumbers, pageCount) => {
1221
- if (!Array.isArray(pageNumbers) || pageNumbers.length === 0) {
1222
- return Array.from({ length: pageCount }, (_, index) => index + 1);
1223
- }
1224
- return [...new Set(pageNumbers)]
1225
- .filter((pageNumber) => pageNumber >= 1 && pageNumber <= pageCount)
1226
- .sort((left, right) => left - right);
1227
- };
1228
-
1229
- const runWithConcurrency = async (items, concurrency, execute) => {
1230
- const results = new Array(items.length);
1231
- let nextIndex = 0;
1232
- const safeConcurrency = Number.isFinite(concurrency) && concurrency > 0
1233
- ? Math.round(concurrency)
1234
- : 1;
1235
- const workerCount = Math.max(1, Math.min(safeConcurrency, items.length));
1236
- await Promise.all(
1237
- Array.from({ length: workerCount }, async () => {
1238
- while (nextIndex < items.length) {
1239
- const currentIndex = nextIndex;
1240
- nextIndex += 1;
1241
- const item = items[currentIndex];
1242
- if (item === undefined) {
1243
- continue;
1244
- }
1245
- results[currentIndex] = await execute(item);
1246
- }
1247
- }),
1248
- );
1249
- return results;
1250
- };
1251
-
1252
- const renderPageToBlob = async (page, scale, storeThumbnails) => {
1253
- const raw = page.getViewport({ scale: 1 });
1254
- const adjustedScale = Math.round(raw.width * scale) / raw.width;
1255
- const viewport = page.getViewport({ scale: adjustedScale });
1256
- const width = Math.round(viewport.width);
1257
- const height = Math.round(viewport.height);
1258
- const canvas = new OffscreenCanvas(width, height);
1259
- const canvasContext = canvas.getContext("2d");
1260
- if (!canvasContext) {
1261
- throw new Error("OffscreenCanvas 2D context unavailable");
1262
- }
1263
- canvasContext.imageSmoothingEnabled = true;
1264
- canvasContext.imageSmoothingQuality = "high";
1265
- await page.render({ canvasContext, viewport }).promise;
1266
- const blob = await canvas.convertToBlob({ type: "image/png" });
1267
- let thumbnailBlob;
1268
- if (storeThumbnails) {
1269
- const thumbScale = Math.min(1, 256 / Math.max(width, height));
1270
- const thumbnailWidth = Math.max(1, Math.round(width * thumbScale));
1271
- const thumbnailHeight = Math.max(1, Math.round(height * thumbScale));
1272
- const thumbnailCanvas = new OffscreenCanvas(thumbnailWidth, thumbnailHeight);
1273
- const thumbnailContext = thumbnailCanvas.getContext("2d");
1274
- if (thumbnailContext) {
1275
- thumbnailContext.imageSmoothingEnabled = true;
1276
- thumbnailContext.imageSmoothingQuality = "high";
1277
- thumbnailContext.drawImage(canvas, 0, 0, thumbnailWidth, thumbnailHeight);
1278
- }
1279
- thumbnailBlob = await thumbnailCanvas.convertToBlob({ type: "image/png" });
1280
- }
1281
- return { blob, height, thumbnailBlob, width };
1282
- };
1283
-
1284
- self.onmessage = async (event) => {
1285
- const message = event.data;
1286
- if (!message || message.type !== "render") {
1287
- return;
1288
- }
1289
- try {
1290
- const pdfjs = await loadPdfJs(message.pdfjsModuleCandidates);
1291
- const pdf = await pdfjs.getDocument({
1292
- data: new Uint8Array(message.pdfData),
1293
- disableWorker: true,
1294
- }).promise;
1295
- try {
1296
- const pageNumbers = normalizePdfPageNumbers(message.pageNumbers, pdf.numPages);
1297
- const bufferedPages = new Map();
1298
- let nextEmitIndex = 0;
1299
- let emitChain = Promise.resolve();
1300
- const emitPageInOrder = async (pageNumber, renderedPage) => {
1301
- bufferedPages.set(pageNumber, renderedPage);
1302
- const flush = () => {
1303
- while (nextEmitIndex < pageNumbers.length) {
1304
- const nextPageNumber = pageNumbers[nextEmitIndex];
1305
- if (nextPageNumber == null) break;
1306
- const nextPage = bufferedPages.get(nextPageNumber);
1307
- if (!nextPage) break;
1308
- bufferedPages.delete(nextPageNumber);
1309
- nextEmitIndex += 1;
1310
- self.postMessage({
1311
- type: "page",
1312
- page: {
1313
- ...nextPage,
1314
- pageNumber: nextPageNumber,
1315
- },
1316
- });
1317
- }
1318
- };
1319
- const nextChain = emitChain.then(flush, flush);
1320
- emitChain = nextChain.catch(() => {});
1321
- await nextChain;
1322
- };
1323
- await runWithConcurrency(
1324
- pageNumbers,
1325
- message.pageConcurrency,
1326
- async (pageNumber) => {
1327
- const page = await pdf.getPage(pageNumber);
1328
- try {
1329
- const renderedPage = await renderPageToBlob(
1330
- page,
1331
- message.scale,
1332
- message.storeThumbnails,
1333
- );
1334
- await emitPageInOrder(pageNumber, renderedPage);
1335
- } finally {
1336
- page.cleanup();
1337
- }
1338
- },
1339
- );
1340
- await emitChain;
1341
- } finally {
1342
- await pdf.destroy();
1343
- }
1344
- self.postMessage({ type: "done" });
1345
- } catch (error) {
1346
- self.postMessage({ type: "error", error: createErrorPayload(error) });
1347
- }
1348
- };
1349
- `;
1350
- var isRecord = (value) => typeof value === "object" && value !== null;
1351
- var hasObjectMembers = (value) => (typeof value === "object" || typeof value === "function") && value !== null;
1352
- var isOffscreenCanvasCandidate = (value) => typeof value === "function";
1353
- var getEnvironmentRecord = (environment) => isRecord(environment) ? environment : null;
1354
- var canUsePdfWorkerRenderer = (environment = globalThis) => {
1355
- const candidateEnvironment = getEnvironmentRecord(environment);
1356
- if (!candidateEnvironment) return false;
1357
- if (typeof candidateEnvironment.Worker !== "function") return false;
1358
- if (typeof candidateEnvironment.Blob !== "function") return false;
1359
- if (!isOffscreenCanvasCandidate(candidateEnvironment.OffscreenCanvas)) {
1360
- return false;
1361
- }
1362
- const urlCandidate = candidateEnvironment.URL;
1363
- if (!hasObjectMembers(urlCandidate)) return false;
1364
- if (typeof urlCandidate.createObjectURL !== "function") return false;
1365
- if (typeof urlCandidate.revokeObjectURL !== "function") return false;
1366
- try {
1367
- const canvas = new candidateEnvironment.OffscreenCanvas(1, 1);
1368
- return typeof canvas.getContext === "function" && typeof canvas.convertToBlob === "function";
1369
- } catch {
1370
- return false;
1371
- }
1372
- };
1373
- var createAbortError = () => new DOMException("Aborted", "AbortError");
1374
- var throwIfAborted = (signal) => {
1375
- if (signal?.aborted) {
1376
- throw createAbortError();
1377
- }
1378
- };
1379
- var createPdfWorker = () => {
1380
- const workerBlob = new Blob([PDF_WORKER_SOURCE], {
1381
- type: "text/javascript"
1382
- });
1383
- const workerUrl = URL.createObjectURL(workerBlob);
1384
- try {
1385
- const worker = new Worker(workerUrl, {
1386
- name: "canvu-pdf-renderer",
1387
- type: "module"
1388
- });
1389
- return {
1390
- release: () => URL.revokeObjectURL(workerUrl),
1391
- worker
1392
- };
1393
- } catch (error) {
1394
- URL.revokeObjectURL(workerUrl);
1395
- throw error;
1396
- }
1397
- };
1398
- var createWorkerError = (message) => {
1399
- const error = new Error(message.error.message);
1400
- error.name = message.error.name;
1401
- error.stack = message.error.stack;
1402
- return error;
1403
- };
1404
- var PACKAGED_PDFJS_MODULE_FILE = "./pdf.mjs";
1405
- var PACKAGED_PDFJS_WORKER_FILE = "./pdf.worker.mjs";
1406
- var resolveRelativeAssetUrl = (path, baseUrl) => new URL(path, baseUrl).toString();
1407
- var resolvePackagedPdfJsModuleCandidate = (baseUrl = import.meta.url) => ({
1408
- moduleUrl: resolveRelativeAssetUrl(PACKAGED_PDFJS_MODULE_FILE, baseUrl),
1409
- workerUrl: resolveRelativeAssetUrl(PACKAGED_PDFJS_WORKER_FILE, baseUrl)
1410
- });
1411
- var resolveBundledPdfJsModuleCandidate = () => ({
1412
- moduleUrl: new URL("pdfjs-dist/build/pdf.min.mjs", import.meta.url).toString(),
1413
- workerUrl: new URL(
1414
- "pdfjs-dist/build/pdf.worker.min.mjs",
1415
- import.meta.url
1416
- ).toString()
1417
- });
1418
- var resolvePdfJsModuleCandidates = () => [
1419
- resolvePackagedPdfJsModuleCandidate(),
1420
- resolveBundledPdfJsModuleCandidate()
1421
- ];
1422
- var loadPdfToStoreWithWorker = async (file, store, options) => {
1423
- throwIfAborted(options.signal);
1424
- const arrayBuffer = await file.arrayBuffer();
1425
- throwIfAborted(options.signal);
1426
- return await new Promise((resolve, reject) => {
1427
- const { release, worker } = createPdfWorker();
1428
- const pageResults = [];
1429
- let storeChain = Promise.resolve();
1430
- let cleanedUp = false;
1431
- let settled = false;
1432
- const cleanup = () => {
1433
- if (cleanedUp) return;
1434
- cleanedUp = true;
1435
- options.signal?.removeEventListener("abort", abortWorker);
1436
- worker.onmessage = null;
1437
- worker.onerror = null;
1438
- worker.terminate();
1439
- release();
1440
- };
1441
- const rejectOnce = (error) => {
1442
- if (settled) return;
1443
- settled = true;
1444
- cleanup();
1445
- reject(error);
1446
- };
1447
- const rejectAfterQueuedStores = (error) => {
1448
- if (settled) return;
1449
- cleanup();
1450
- storeChain.then(() => {
1451
- if (settled) return;
1452
- settled = true;
1453
- reject(error);
1454
- }).catch(rejectOnce);
1455
- };
1456
- function abortWorker() {
1457
- rejectOnce(createAbortError());
1458
- }
1459
- const storePage = (message) => {
1460
- try {
1461
- options.onPageReceived?.({ pageNumber: message.page.pageNumber });
1462
- } catch (error) {
1463
- rejectOnce(error);
1464
- return;
1465
- }
1466
- storeChain = storeChain.then(async () => {
1467
- throwIfAborted(options.signal);
1468
- const blobId = await store.storeOriginal(message.page.blob);
1469
- throwIfAborted(options.signal);
1470
- const thumbnailBlobId = options.storeThumbnails && message.page.thumbnailBlob ? await store.storeThumbnail(message.page.thumbnailBlob) : "";
1471
- throwIfAborted(options.signal);
1472
- const pageResult = {
1473
- blobId,
1474
- height: message.page.height,
1475
- pageNumber: message.page.pageNumber,
1476
- thumbnailBlobId,
1477
- width: message.page.width
1478
- };
1479
- pageResults.push(pageResult);
1480
- await options.onPageStored?.(pageResult);
1481
- throwIfAborted(options.signal);
1482
- });
1483
- void storeChain.catch(rejectOnce);
1484
- };
1485
- worker.onmessage = (event) => {
1486
- const message = event.data;
1487
- if (message.type === "page") {
1488
- storePage(message);
1489
- return;
1490
- }
1491
- if (message.type === "error") {
1492
- rejectAfterQueuedStores(createWorkerError(message));
1493
- return;
1494
- }
1495
- storeChain.then(() => {
1496
- if (settled) return;
1497
- settled = true;
1498
- cleanup();
1499
- resolve(pageResults);
1500
- }).catch(rejectOnce);
1501
- };
1502
- worker.onerror = (event) => {
1503
- rejectAfterQueuedStores(event.error ?? new Error(event.message));
1504
- };
1505
- options.signal?.addEventListener("abort", abortWorker, { once: true });
1506
- try {
1507
- worker.postMessage(
1508
- {
1509
- pdfData: arrayBuffer,
1510
- pdfjsModuleCandidates: resolvePdfJsModuleCandidates(),
1511
- pageNumbers: options.pageNumbers ? [...options.pageNumbers] : void 0,
1512
- pageConcurrency: options.pageConcurrency,
1513
- scale: options.scale,
1514
- storeThumbnails: options.storeThumbnails,
1515
- type: "render"
1516
- },
1517
- [arrayBuffer]
1518
- );
1519
- } catch (error) {
1520
- rejectOnce(error);
1521
- }
1522
- });
1523
- };
1524
-
1525
1189
  // src/image/pdf-loader.ts
1526
1190
  var pdfjsPromise = null;
1527
1191
  function getPdfJs() {
@@ -1538,7 +1202,7 @@ function getPdfJs() {
1538
1202
  return pdfjsPromise;
1539
1203
  }
1540
1204
  async function renderPageToCanvas(page, scale, signal) {
1541
- throwIfAborted2(signal);
1205
+ throwIfAborted(signal);
1542
1206
  const raw = page.getViewport({ scale: 1 });
1543
1207
  const adjustedScale = Math.round(raw.width * scale) / raw.width;
1544
1208
  const viewport = page.getViewport({ scale: adjustedScale });
@@ -1560,7 +1224,7 @@ async function renderPageToCanvas(page, scale, signal) {
1560
1224
  try {
1561
1225
  await renderTask.promise;
1562
1226
  } catch (error) {
1563
- if (signal.aborted) throw createAbortError2();
1227
+ if (signal.aborted) throw createAbortError();
1564
1228
  throw error;
1565
1229
  } finally {
1566
1230
  signal.removeEventListener("abort", abortRender);
@@ -1568,15 +1232,15 @@ async function renderPageToCanvas(page, scale, signal) {
1568
1232
  } else {
1569
1233
  await renderTask.promise;
1570
1234
  }
1571
- throwIfAborted2(signal);
1235
+ throwIfAborted(signal);
1572
1236
  return { canvas, width: w, height: h };
1573
1237
  }
1574
- function createAbortError2() {
1238
+ function createAbortError() {
1575
1239
  return new DOMException("Aborted", "AbortError");
1576
1240
  }
1577
- function throwIfAborted2(signal) {
1241
+ function throwIfAborted(signal) {
1578
1242
  if (signal?.aborted) {
1579
- throw createAbortError2();
1243
+ throw createAbortError();
1580
1244
  }
1581
1245
  }
1582
1246
  function normalizePdfPageNumbers(pageNumbers, pageCount) {
@@ -1592,7 +1256,7 @@ async function runWithConcurrency(items, concurrency, execute, signal) {
1592
1256
  await Promise.all(
1593
1257
  Array.from({ length: workerCount }, async () => {
1594
1258
  while (nextIndex < items.length) {
1595
- throwIfAborted2(signal);
1259
+ throwIfAborted(signal);
1596
1260
  const currentIndex = nextIndex;
1597
1261
  nextIndex += 1;
1598
1262
  const item = items[currentIndex];
@@ -1600,7 +1264,7 @@ async function runWithConcurrency(items, concurrency, execute, signal) {
1600
1264
  continue;
1601
1265
  }
1602
1266
  results[currentIndex] = await execute(item);
1603
- throwIfAborted2(signal);
1267
+ throwIfAborted(signal);
1604
1268
  }
1605
1269
  })
1606
1270
  );
@@ -1611,52 +1275,11 @@ async function loadPdfToStore(file, store, options) {
1611
1275
  const pageConcurrency = options?.pageConcurrency ?? 2;
1612
1276
  const storeThumbnails = options?.storeThumbnails ?? false;
1613
1277
  const signal = options?.signal;
1614
- throwIfAborted2(signal);
1615
- if (canUsePdfWorkerRenderer()) {
1616
- let workerPageCount = 0;
1617
- try {
1618
- return await loadPdfToStoreWithWorker(file, store, {
1619
- scale,
1620
- pageNumbers: options?.pageNumbers,
1621
- pageConcurrency,
1622
- storeThumbnails,
1623
- signal,
1624
- onPageReceived: () => {
1625
- workerPageCount += 1;
1626
- },
1627
- onPageStored: async (page) => {
1628
- await options?.onPageStored?.(page);
1629
- }
1630
- });
1631
- } catch (error) {
1632
- if (signal?.aborted || workerPageCount > 0) {
1633
- throw error;
1634
- }
1635
- }
1636
- }
1637
- return await loadPdfToStoreOnMainThread(file, store, {
1638
- scale,
1639
- pageNumbers: options?.pageNumbers,
1640
- pageConcurrency,
1641
- storeThumbnails,
1642
- signal,
1643
- onPageStored: options?.onPageStored
1644
- });
1645
- }
1646
- async function loadPdfToStoreOnMainThread(file, store, options) {
1647
- const {
1648
- pageConcurrency,
1649
- scale,
1650
- signal,
1651
- storeThumbnails,
1652
- onPageStored,
1653
- pageNumbers
1654
- } = options;
1655
- throwIfAborted2(signal);
1278
+ throwIfAborted(signal);
1656
1279
  const pdfjs = await getPdfJs();
1657
- throwIfAborted2(signal);
1280
+ throwIfAborted(signal);
1658
1281
  const arrayBuffer = await file.arrayBuffer();
1659
- throwIfAborted2(signal);
1282
+ throwIfAborted(signal);
1660
1283
  const loadingTask = pdfjs.getDocument({ data: arrayBuffer });
1661
1284
  if (signal) {
1662
1285
  const abortLoading = () => {
@@ -1668,14 +1291,14 @@ async function loadPdfToStoreOnMainThread(file, store, options) {
1668
1291
  signal.removeEventListener("abort", abortLoading);
1669
1292
  return await loadPdfDocumentToStore(pdf2, store, {
1670
1293
  scale,
1671
- pageNumbers,
1294
+ pageNumbers: options?.pageNumbers,
1672
1295
  pageConcurrency,
1673
1296
  storeThumbnails,
1674
1297
  signal,
1675
- onPageStored
1298
+ onPageStored: options?.onPageStored
1676
1299
  });
1677
1300
  } catch (error) {
1678
- if (signal.aborted) throw createAbortError2();
1301
+ if (signal.aborted) throw createAbortError();
1679
1302
  throw error;
1680
1303
  } finally {
1681
1304
  signal.removeEventListener("abort", abortLoading);
@@ -1684,16 +1307,16 @@ async function loadPdfToStoreOnMainThread(file, store, options) {
1684
1307
  const pdf = await loadingTask.promise;
1685
1308
  return await loadPdfDocumentToStore(pdf, store, {
1686
1309
  scale,
1687
- pageNumbers,
1310
+ pageNumbers: options?.pageNumbers,
1688
1311
  pageConcurrency,
1689
1312
  storeThumbnails,
1690
- onPageStored
1313
+ onPageStored: options?.onPageStored
1691
1314
  });
1692
1315
  }
1693
1316
  async function loadPdfDocumentToStore(pdf, store, options) {
1694
1317
  const { pageConcurrency, scale, signal } = options;
1695
1318
  const storeThumbnails = options.storeThumbnails ?? false;
1696
- throwIfAborted2(signal);
1319
+ throwIfAborted(signal);
1697
1320
  const pageNumbers = normalizePdfPageNumbers(options?.pageNumbers, pdf.numPages);
1698
1321
  const bufferedResults = /* @__PURE__ */ new Map();
1699
1322
  let nextEmitIndex = 0;
@@ -1708,9 +1331,9 @@ async function loadPdfDocumentToStore(pdf, store, options) {
1708
1331
  if (!bufferedResult) break;
1709
1332
  bufferedResults.delete(nextPageNumber);
1710
1333
  nextEmitIndex += 1;
1711
- throwIfAborted2(signal);
1334
+ throwIfAborted(signal);
1712
1335
  await options?.onPageStored?.(bufferedResult);
1713
- throwIfAborted2(signal);
1336
+ throwIfAborted(signal);
1714
1337
  }
1715
1338
  };
1716
1339
  const nextChain = emitChain.then(run, run);
@@ -1722,20 +1345,20 @@ async function loadPdfDocumentToStore(pdf, store, options) {
1722
1345
  pageNumbers,
1723
1346
  pageConcurrency,
1724
1347
  async (pageNumber) => {
1725
- throwIfAborted2(signal);
1348
+ throwIfAborted(signal);
1726
1349
  const page = await pdf.getPage(pageNumber);
1727
- throwIfAborted2(signal);
1350
+ throwIfAborted(signal);
1728
1351
  const { canvas, width, height } = await renderPageToCanvas(
1729
1352
  page,
1730
1353
  scale,
1731
1354
  signal
1732
1355
  );
1733
- throwIfAborted2(signal);
1356
+ throwIfAborted(signal);
1734
1357
  const mime = "image/png";
1735
1358
  const pageBlob = await encodeCanvasToBlob(canvas, { mimeType: mime });
1736
- throwIfAborted2(signal);
1359
+ throwIfAborted(signal);
1737
1360
  const blobId = await store.storeOriginal(pageBlob);
1738
- throwIfAborted2(signal);
1361
+ throwIfAborted(signal);
1739
1362
  const thumbnailBlobId = storeThumbnails ? await (async () => {
1740
1363
  const thumbScale = Math.min(1, 256 / Math.max(width, height));
1741
1364
  const tw = Math.max(1, Math.round(width * thumbScale));
@@ -1752,7 +1375,7 @@ async function loadPdfDocumentToStore(pdf, store, options) {
1752
1375
  const thumbBlob = await encodeCanvasToBlob(thumbCanvas, {
1753
1376
  mimeType: mime
1754
1377
  });
1755
- throwIfAborted2(signal);
1378
+ throwIfAborted(signal);
1756
1379
  return await store.storeThumbnail(thumbBlob);
1757
1380
  })() : "";
1758
1381
  const pageResult = {
@@ -2106,22 +1729,22 @@ function finalizeIngestedItem(item, context, uploadResult, decorateItem) {
2106
1729
  const itemWithAssetData = applyAssetUploadResultToItem(item, uploadResult);
2107
1730
  return decorateItem ? decorateItem(itemWithAssetData, context) : itemWithAssetData;
2108
1731
  }
2109
- function createAbortError3() {
1732
+ function createAbortError2() {
2110
1733
  return new DOMException("Aborted", "AbortError");
2111
1734
  }
2112
- function throwIfAborted3(signal) {
1735
+ function throwIfAborted2(signal) {
2113
1736
  if (signal?.aborted) {
2114
- throw createAbortError3();
1737
+ throw createAbortError2();
2115
1738
  }
2116
1739
  }
2117
1740
  function isAbortError(error) {
2118
1741
  return (error instanceof DOMException || error instanceof Error) && error.name === "AbortError";
2119
1742
  }
2120
1743
  async function uploadAssetIfNeeded(assetStore, file, kind, signal) {
2121
- throwIfAborted3(signal);
1744
+ throwIfAborted2(signal);
2122
1745
  if (!assetStore) return null;
2123
1746
  const result = await assetStore.upload({ file, kind, signal });
2124
- throwIfAborted3(signal);
1747
+ throwIfAborted2(signal);
2125
1748
  return result ?? null;
2126
1749
  }
2127
1750
  async function ingestAssetFilesToSceneItems(options) {
@@ -2144,7 +1767,7 @@ async function ingestAssetFilesToSceneItems(options) {
2144
1767
  const errors = [];
2145
1768
  let occupiedBottomY = existingItems.length > 0 ? Math.max(...existingItems.map((item) => item.bounds.y + item.bounds.height)) : null;
2146
1769
  for (const file of files) {
2147
- throwIfAborted3(signal);
1770
+ throwIfAborted2(signal);
2148
1771
  const kind = getAssetKindForFile(file);
2149
1772
  if (!kind) {
2150
1773
  const error = {
@@ -2170,10 +1793,10 @@ async function ingestAssetFilesToSceneItems(options) {
2170
1793
  storeThumbnails: false,
2171
1794
  signal,
2172
1795
  onPageStored: async (page) => {
2173
- throwIfAborted3(signal);
1796
+ throwIfAborted2(signal);
2174
1797
  const uploadResult2 = await uploadResultPromise;
2175
1798
  const fullUrl2 = await createBlobUrlFromStore(imageStore, page.blobId);
2176
- throwIfAborted3(signal);
1799
+ throwIfAborted2(signal);
2177
1800
  const naturalTopY2 = worldCenter.y - page.height / 2;
2178
1801
  const stackedTopY2 = occupiedBottomY == null ? naturalTopY2 : occupiedBottomY + gapWorld;
2179
1802
  const bounds2 = {
@@ -2213,18 +1836,18 @@ async function ingestAssetFilesToSceneItems(options) {
2213
1836
  );
2214
1837
  items.push(item2);
2215
1838
  occupiedBottomY = bounds2.y + page.height;
2216
- throwIfAborted3(signal);
1839
+ throwIfAborted2(signal);
2217
1840
  onItemsReady?.([item2], { file, kind });
2218
1841
  }
2219
1842
  });
2220
1843
  continue;
2221
1844
  }
2222
- throwIfAborted3(signal);
1845
+ throwIfAborted2(signal);
2223
1846
  const [uploadResult, storedImage] = await Promise.all([
2224
1847
  uploadAssetIfNeeded(assetStore, file, kind, signal),
2225
1848
  loadImageToStore(file, imageStore)
2226
1849
  ]);
2227
- throwIfAborted3(signal);
1850
+ throwIfAborted2(signal);
2228
1851
  const { blobId, thumbnailBlobId, width, height } = storedImage;
2229
1852
  const fullUrl = await createBlobUrlFromStore(imageStore, blobId);
2230
1853
  const thumbBlob = await imageStore.getThumbnail(thumbnailBlobId);
@@ -2262,7 +1885,7 @@ async function ingestAssetFilesToSceneItems(options) {
2262
1885
  decorateItem
2263
1886
  );
2264
1887
  items.push(item);
2265
- throwIfAborted3(signal);
1888
+ throwIfAborted2(signal);
2266
1889
  onItemsReady?.([item], { file, kind });
2267
1890
  occupiedBottomY = occupiedBottomY == null ? bounds.y + height : Math.max(occupiedBottomY, bounds.y + height);
2268
1891
  } catch (error) {
@@ -7539,6 +7162,9 @@ function shouldRedrawRasterImageCanvas({
7539
7162
  if (currentSourceKey !== nextSourceKey && safeCurrentWidth === safeNextWidth && safeCurrentHeight === safeNextHeight) {
7540
7163
  return true;
7541
7164
  }
7165
+ if (currentSourceKey !== nextSourceKey && (safeNextWidth < safeCurrentWidth || safeNextHeight < safeCurrentHeight)) {
7166
+ return true;
7167
+ }
7542
7168
  return safeNextWidth > safeCurrentWidth * upscaleRedrawRatio || safeNextHeight > safeCurrentHeight * upscaleRedrawRatio;
7543
7169
  }
7544
7170
  function toPositiveFiniteNumber(value, fallback) {
@@ -8918,6 +8544,42 @@ function didReadOnlyActivationMovePastTap(session, pointer, tapPx) {
8918
8544
  return Math.hypot(dx, dy) > tapPx;
8919
8545
  }
8920
8546
 
8547
+ // src/react/render-scheduler.ts
8548
+ var getDefaultAnimationFrameRuntime = () => typeof window === "undefined" ? {} : window;
8549
+ function createAnimationFrameRenderScheduler({
8550
+ render,
8551
+ runtime = getDefaultAnimationFrameRuntime()
8552
+ }) {
8553
+ let frameId = null;
8554
+ const canceledFrameIds = /* @__PURE__ */ new Set();
8555
+ const request = () => {
8556
+ if (frameId != null) return;
8557
+ if (typeof runtime.requestAnimationFrame !== "function") {
8558
+ render();
8559
+ return;
8560
+ }
8561
+ const scheduledFrameId = runtime.requestAnimationFrame(() => {
8562
+ const currentFrameId = scheduledFrameId;
8563
+ if (canceledFrameIds.has(currentFrameId)) {
8564
+ canceledFrameIds.delete(currentFrameId);
8565
+ return;
8566
+ }
8567
+ if (frameId === currentFrameId) {
8568
+ frameId = null;
8569
+ }
8570
+ render();
8571
+ });
8572
+ frameId = scheduledFrameId;
8573
+ };
8574
+ const cancel = () => {
8575
+ if (frameId == null) return;
8576
+ canceledFrameIds.add(frameId);
8577
+ runtime.cancelAnimationFrame?.(frameId);
8578
+ frameId = null;
8579
+ };
8580
+ return { request, cancel };
8581
+ }
8582
+
8921
8583
  // src/react/stable-selection.ts
8922
8584
  function shallowEqualStringArray(a, b) {
8923
8585
  if (a === b) return true;
@@ -9930,6 +9592,9 @@ var VectorViewport = forwardRef(
9930
9592
  reducedMotionRef.current = reducedMotion;
9931
9593
  const [zoomPercent, setZoomPercent] = useState(100);
9932
9594
  const [cameraTick, setCameraTick] = useState(0);
9595
+ const renderFrameSchedulerRef = useRef(
9596
+ null
9597
+ );
9933
9598
  const [placementPreview, setPlacementPreview] = useState(null);
9934
9599
  const [eraserTrail, setEraserTrail] = useState([]);
9935
9600
  const [laserTrail, setLaserTrail] = useState([]);
@@ -10310,7 +9975,7 @@ var VectorViewport = forwardRef(
10310
9975
  renderSceneWithLivePenStroke
10311
9976
  ]
10312
9977
  );
10313
- const renderFrame = useCallback(() => {
9978
+ const renderFrameNow = useCallback(() => {
10314
9979
  const renderer = rendererRef.current;
10315
9980
  const cam = cameraRef.current;
10316
9981
  if (!renderer || !cam) return;
@@ -10323,6 +9988,14 @@ var VectorViewport = forwardRef(
10323
9988
  }
10324
9989
  onCameraChangeRef.current?.();
10325
9990
  }, []);
9991
+ const renderFrame = useCallback(() => {
9992
+ const scheduler = renderFrameSchedulerRef.current;
9993
+ if (!scheduler) {
9994
+ renderFrameNow();
9995
+ return;
9996
+ }
9997
+ scheduler.request();
9998
+ }, [renderFrameNow]);
10326
9999
  useEffect(() => {
10327
10000
  const el = sceneContainerRef.current;
10328
10001
  const interactionEl = interactionRootRef.current;
@@ -10341,6 +10014,9 @@ var VectorViewport = forwardRef(
10341
10014
  rasterImageCanvas: imageCanvasRenderingRef.current
10342
10015
  });
10343
10016
  rendererRef.current = renderer;
10017
+ renderFrameSchedulerRef.current = createAnimationFrameRenderScheduler({
10018
+ render: renderFrameNow
10019
+ });
10344
10020
  renderer.setInteractionState({
10345
10021
  selectedIds: effectiveSelectedIdsRef.current,
10346
10022
  hoveredItemId: hoveredItemIdRef.current
@@ -10375,6 +10051,8 @@ var VectorViewport = forwardRef(
10375
10051
  });
10376
10052
  }
10377
10053
  return () => {
10054
+ renderFrameSchedulerRef.current?.cancel();
10055
+ renderFrameSchedulerRef.current = null;
10378
10056
  detachInput();
10379
10057
  detachPencil?.();
10380
10058
  renderer.destroy();
@@ -10383,7 +10061,7 @@ var VectorViewport = forwardRef(
10383
10061
  cameraRef.current = null;
10384
10062
  setCameraForOverlay(null);
10385
10063
  };
10386
- }, [applePencilNav, renderFrame, resolveReadOnlyActivation]);
10064
+ }, [applePencilNav, renderFrame, renderFrameNow, resolveReadOnlyActivation]);
10387
10065
  useEffect(() => {
10388
10066
  rendererRef.current?.setInteractionState({
10389
10067
  selectedIds: effectiveSelectedIds,
@@ -10511,10 +10189,10 @@ var VectorViewport = forwardRef(
10511
10189
  livePenStrokeItemRef.current = null;
10512
10190
  }
10513
10191
  scene.setItems(resolvedSceneItems);
10514
- renderFrame();
10192
+ renderFrameNow();
10515
10193
  renderer?.renderLiveItem(livePenStrokeItemRef.current);
10516
10194
  }
10517
- }, [resolvedSceneItems, renderFrame]);
10195
+ }, [resolvedSceneItems, renderFrameNow]);
10518
10196
  useEffect(() => {
10519
10197
  const ids = effectiveSelectedIds;
10520
10198
  const valid = ids.filter((id) => items.some((i) => i.id === id));