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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import getStroke from 'perfect-freehand';
2
- import { Group, Canvas, Rect, Circle, Path, RoundedRect, Oval, DashPathEffect, Line, vec, matchFont, Text, useImage, Image } from '@shopify/react-native-skia';
2
+ import { Group, Canvas, Rect, Circle, Path, RoundedRect, Oval, DashPathEffect, Line, vec, matchFont, Text, Image } from '@shopify/react-native-skia';
3
3
  import { memo, forwardRef, useState, useRef, useCallback, useEffect, useMemo, useImperativeHandle } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { StyleSheet, PanResponder, View, Pressable, Text as Text$1, ScrollView } from 'react-native';
@@ -1398,6 +1398,141 @@ function smoothFreehandPointsToPathD(points) {
1398
1398
  d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
1399
1399
  return d;
1400
1400
  }
1401
+ var DEFAULT_NATIVE_IMAGE_CACHE_MAX_ENTRIES = 96;
1402
+ function disposeCachedImage(image) {
1403
+ try {
1404
+ image.dispose?.();
1405
+ } catch {
1406
+ }
1407
+ }
1408
+ function createNativeImageCache({
1409
+ loadImage,
1410
+ maxEntries = DEFAULT_NATIVE_IMAGE_CACHE_MAX_ENTRIES
1411
+ }) {
1412
+ const safeMaxEntries = Math.max(1, Math.round(maxEntries));
1413
+ const entries = /* @__PURE__ */ new Map();
1414
+ let clock = 0;
1415
+ const touchEntry = (entry) => {
1416
+ clock += 1;
1417
+ entry.lastUsed = clock;
1418
+ };
1419
+ const createEntry = () => {
1420
+ clock += 1;
1421
+ return {
1422
+ lastUsed: clock,
1423
+ retainCount: 0
1424
+ };
1425
+ };
1426
+ const prune = () => {
1427
+ const cachedEntries = [...entries.entries()].filter(
1428
+ (entry) => entry[1].image != null && entry[1].retainCount === 0
1429
+ );
1430
+ if (cachedEntries.length <= safeMaxEntries) return;
1431
+ const evictedEntries = cachedEntries.sort(
1432
+ (leftEntry, rightEntry) => leftEntry[1].lastUsed - rightEntry[1].lastUsed
1433
+ ).slice(0, cachedEntries.length - safeMaxEntries);
1434
+ for (const [href, entry] of evictedEntries) {
1435
+ entries.delete(href);
1436
+ disposeCachedImage(entry.image);
1437
+ }
1438
+ };
1439
+ const getCached = (href) => {
1440
+ const entry = entries.get(href);
1441
+ if (!entry?.image) return null;
1442
+ touchEntry(entry);
1443
+ return entry.image;
1444
+ };
1445
+ const load = async (href) => {
1446
+ const cachedImage = getCached(href);
1447
+ if (cachedImage) return cachedImage;
1448
+ const existingEntry = entries.get(href);
1449
+ if (existingEntry?.promise) return await existingEntry.promise;
1450
+ const entry = existingEntry ?? createEntry();
1451
+ const promise = loadImage(href).then((image) => {
1452
+ if (!image) {
1453
+ if (entry.retainCount === 0) entries.delete(href);
1454
+ return null;
1455
+ }
1456
+ entry.image = image;
1457
+ entry.promise = void 0;
1458
+ touchEntry(entry);
1459
+ prune();
1460
+ return image;
1461
+ }).catch((error) => {
1462
+ entries.delete(href);
1463
+ throw error;
1464
+ });
1465
+ entry.promise = promise;
1466
+ entries.set(href, entry);
1467
+ return await promise;
1468
+ };
1469
+ const retain = (href) => {
1470
+ const entry = entries.get(href) ?? createEntry();
1471
+ entry.retainCount += 1;
1472
+ touchEntry(entry);
1473
+ entries.set(href, entry);
1474
+ let released = false;
1475
+ return () => {
1476
+ if (released) return;
1477
+ released = true;
1478
+ entry.retainCount = Math.max(0, entry.retainCount - 1);
1479
+ if (!entry.image && !entry.promise && entry.retainCount === 0) {
1480
+ entries.delete(href);
1481
+ return;
1482
+ }
1483
+ prune();
1484
+ };
1485
+ };
1486
+ const clear = () => {
1487
+ for (const entry of entries.values()) {
1488
+ if (entry.image) disposeCachedImage(entry.image);
1489
+ }
1490
+ entries.clear();
1491
+ };
1492
+ const size = () => [...entries.values()].filter((entry) => entry.image != null).length;
1493
+ return { getCached, load, retain, clear, size };
1494
+ }
1495
+ async function loadSkiaImageFromHref(href) {
1496
+ const { Skia } = await import('@shopify/react-native-skia');
1497
+ const data = await Skia.Data.fromURI(href);
1498
+ return Skia.Image.MakeImageFromEncoded(data);
1499
+ }
1500
+ var nativeSkiaImageCache = createNativeImageCache({
1501
+ loadImage: loadSkiaImageFromHref
1502
+ });
1503
+ function useCachedSkiaImage(href) {
1504
+ const [loadedImage, setLoadedImage] = useState(
1505
+ () => href ? { href, image: nativeSkiaImageCache.getCached(href) } : null
1506
+ );
1507
+ useEffect(() => {
1508
+ if (!href) {
1509
+ setLoadedImage(null);
1510
+ return;
1511
+ }
1512
+ const releaseImage = nativeSkiaImageCache.retain(href);
1513
+ const cachedImage = nativeSkiaImageCache.getCached(href);
1514
+ if (cachedImage) {
1515
+ setLoadedImage({ href, image: cachedImage });
1516
+ return releaseImage;
1517
+ }
1518
+ let active = true;
1519
+ setLoadedImage({ href, image: null });
1520
+ void nativeSkiaImageCache.load(href).then(
1521
+ (loadedImage2) => {
1522
+ if (active) setLoadedImage({ href, image: loadedImage2 });
1523
+ },
1524
+ () => {
1525
+ if (active) setLoadedImage({ href, image: null });
1526
+ }
1527
+ );
1528
+ return () => {
1529
+ active = false;
1530
+ releaseImage();
1531
+ };
1532
+ }, [href]);
1533
+ if (!href || loadedImage?.href !== href) return null;
1534
+ return loadedImage.image;
1535
+ }
1401
1536
 
1402
1537
  // src/native/skia-transform.ts
1403
1538
  function parseNum(s) {
@@ -1662,7 +1797,7 @@ function SvgNodeItem({ node }) {
1662
1797
  function SvgImageNodeItem({
1663
1798
  node
1664
1799
  }) {
1665
- const image = useImage(node.href);
1800
+ const image = useCachedSkiaImage(node.href);
1666
1801
  if (!image) return null;
1667
1802
  return /* @__PURE__ */ jsx(
1668
1803
  Image,