canvu-react 0.4.34 → 0.4.35

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/native.cjs CHANGED
@@ -1404,6 +1404,141 @@ function smoothFreehandPointsToPathD(points) {
1404
1404
  d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
1405
1405
  return d;
1406
1406
  }
1407
+ var DEFAULT_NATIVE_IMAGE_CACHE_MAX_ENTRIES = 96;
1408
+ function disposeCachedImage(image) {
1409
+ try {
1410
+ image.dispose?.();
1411
+ } catch {
1412
+ }
1413
+ }
1414
+ function createNativeImageCache({
1415
+ loadImage,
1416
+ maxEntries = DEFAULT_NATIVE_IMAGE_CACHE_MAX_ENTRIES
1417
+ }) {
1418
+ const safeMaxEntries = Math.max(1, Math.round(maxEntries));
1419
+ const entries = /* @__PURE__ */ new Map();
1420
+ let clock = 0;
1421
+ const touchEntry = (entry) => {
1422
+ clock += 1;
1423
+ entry.lastUsed = clock;
1424
+ };
1425
+ const createEntry = () => {
1426
+ clock += 1;
1427
+ return {
1428
+ lastUsed: clock,
1429
+ retainCount: 0
1430
+ };
1431
+ };
1432
+ const prune = () => {
1433
+ const cachedEntries = [...entries.entries()].filter(
1434
+ (entry) => entry[1].image != null && entry[1].retainCount === 0
1435
+ );
1436
+ if (cachedEntries.length <= safeMaxEntries) return;
1437
+ const evictedEntries = cachedEntries.sort(
1438
+ (leftEntry, rightEntry) => leftEntry[1].lastUsed - rightEntry[1].lastUsed
1439
+ ).slice(0, cachedEntries.length - safeMaxEntries);
1440
+ for (const [href, entry] of evictedEntries) {
1441
+ entries.delete(href);
1442
+ disposeCachedImage(entry.image);
1443
+ }
1444
+ };
1445
+ const getCached = (href) => {
1446
+ const entry = entries.get(href);
1447
+ if (!entry?.image) return null;
1448
+ touchEntry(entry);
1449
+ return entry.image;
1450
+ };
1451
+ const load = async (href) => {
1452
+ const cachedImage = getCached(href);
1453
+ if (cachedImage) return cachedImage;
1454
+ const existingEntry = entries.get(href);
1455
+ if (existingEntry?.promise) return await existingEntry.promise;
1456
+ const entry = existingEntry ?? createEntry();
1457
+ const promise = loadImage(href).then((image) => {
1458
+ if (!image) {
1459
+ if (entry.retainCount === 0) entries.delete(href);
1460
+ return null;
1461
+ }
1462
+ entry.image = image;
1463
+ entry.promise = void 0;
1464
+ touchEntry(entry);
1465
+ prune();
1466
+ return image;
1467
+ }).catch((error) => {
1468
+ entries.delete(href);
1469
+ throw error;
1470
+ });
1471
+ entry.promise = promise;
1472
+ entries.set(href, entry);
1473
+ return await promise;
1474
+ };
1475
+ const retain = (href) => {
1476
+ const entry = entries.get(href) ?? createEntry();
1477
+ entry.retainCount += 1;
1478
+ touchEntry(entry);
1479
+ entries.set(href, entry);
1480
+ let released = false;
1481
+ return () => {
1482
+ if (released) return;
1483
+ released = true;
1484
+ entry.retainCount = Math.max(0, entry.retainCount - 1);
1485
+ if (!entry.image && !entry.promise && entry.retainCount === 0) {
1486
+ entries.delete(href);
1487
+ return;
1488
+ }
1489
+ prune();
1490
+ };
1491
+ };
1492
+ const clear = () => {
1493
+ for (const entry of entries.values()) {
1494
+ if (entry.image) disposeCachedImage(entry.image);
1495
+ }
1496
+ entries.clear();
1497
+ };
1498
+ const size = () => [...entries.values()].filter((entry) => entry.image != null).length;
1499
+ return { getCached, load, retain, clear, size };
1500
+ }
1501
+ async function loadSkiaImageFromHref(href) {
1502
+ const { Skia } = await import('@shopify/react-native-skia');
1503
+ const data = await Skia.Data.fromURI(href);
1504
+ return Skia.Image.MakeImageFromEncoded(data);
1505
+ }
1506
+ var nativeSkiaImageCache = createNativeImageCache({
1507
+ loadImage: loadSkiaImageFromHref
1508
+ });
1509
+ function useCachedSkiaImage(href) {
1510
+ const [loadedImage, setLoadedImage] = react.useState(
1511
+ () => href ? { href, image: nativeSkiaImageCache.getCached(href) } : null
1512
+ );
1513
+ react.useEffect(() => {
1514
+ if (!href) {
1515
+ setLoadedImage(null);
1516
+ return;
1517
+ }
1518
+ const releaseImage = nativeSkiaImageCache.retain(href);
1519
+ const cachedImage = nativeSkiaImageCache.getCached(href);
1520
+ if (cachedImage) {
1521
+ setLoadedImage({ href, image: cachedImage });
1522
+ return releaseImage;
1523
+ }
1524
+ let active = true;
1525
+ setLoadedImage({ href, image: null });
1526
+ void nativeSkiaImageCache.load(href).then(
1527
+ (loadedImage2) => {
1528
+ if (active) setLoadedImage({ href, image: loadedImage2 });
1529
+ },
1530
+ () => {
1531
+ if (active) setLoadedImage({ href, image: null });
1532
+ }
1533
+ );
1534
+ return () => {
1535
+ active = false;
1536
+ releaseImage();
1537
+ };
1538
+ }, [href]);
1539
+ if (!href || loadedImage?.href !== href) return null;
1540
+ return loadedImage.image;
1541
+ }
1407
1542
 
1408
1543
  // src/native/skia-transform.ts
1409
1544
  function parseNum(s) {
@@ -1668,7 +1803,7 @@ function SvgNodeItem({ node }) {
1668
1803
  function SvgImageNodeItem({
1669
1804
  node
1670
1805
  }) {
1671
- const image = reactNativeSkia.useImage(node.href);
1806
+ const image = useCachedSkiaImage(node.href);
1672
1807
  if (!image) return null;
1673
1808
  return /* @__PURE__ */ jsxRuntime.jsx(
1674
1809
  reactNativeSkia.Image,