@shopify/react-native-skia 2.13.0 → 2.13.1

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.
@@ -28,7 +28,7 @@ public:
28
28
 
29
29
  gl::Display *getDisplay() { return _glDisplay.get(); }
30
30
  gl::Context *getContext() { return _glContext.get(); }
31
- gl::Surface *getSurface() { return _glSurface.get(); };
31
+ gl::Surface *getSurface() { return _glSurface.get(); }
32
32
  EGLConfig getConfig() { return _glConfig; }
33
33
 
34
34
  private:
@@ -40,4 +40,4 @@ private:
40
40
  Surface &operator=(const Surface &) = delete;
41
41
  };
42
42
 
43
- } // Namespace gl
43
+ } // namespace gl
@@ -66,7 +66,7 @@ static sk_sp<SkData> replaceJpegICCWithAppleP3(sk_sp<SkData> jpegData) {
66
66
  return jpegData;
67
67
 
68
68
  const uint8_t *iccBytes = CFDataGetBytePtr(cfICC);
69
- size_t iccLen = (size_t)CFDataGetLength(cfICC);
69
+ size_t iccLen = static_cast<size_t>(CFDataGetLength(cfICC));
70
70
 
71
71
  // "ICC_PROFILE\0" APP2 marker signature (12 bytes)
72
72
  static const uint8_t iccSig[] = {0x49, 0x43, 0x43, 0x5F, 0x50, 0x52,
@@ -28,7 +28,9 @@ public:
28
28
  : JsiSkWrappingSkPtrNativeObject<JsiSkImageFilter, SkImageFilter>(
29
29
  std::move(context), std::move(imageFilter)) {}
30
30
 
31
- size_t getMemoryPressure() override { return 1024 * 1024; }
31
+ // A image filter node is a few hundred bytes; the images it may reference
32
+ // are reported by their own wrappers.
33
+ size_t getMemoryPressure() override { return kMinMemoryPressure; }
32
34
 
33
35
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
34
36
  installCommon(runtime, prototype);
@@ -277,7 +277,8 @@ public:
277
277
  &JsiSkParagraph::extendedVisit);
278
278
  }
279
279
 
280
- size_t getMemoryPressure() override { return 1024 * 1024; }
280
+ // Shaped glyph runs and line metrics; there is no exact API for this.
281
+ size_t getMemoryPressure() override { return 16 * 1024; }
281
282
 
282
283
  explicit JsiSkParagraph(std::shared_ptr<RNSkPlatformContext> context,
283
284
  para::ParagraphBuilder *paragraphBuilder)
@@ -93,7 +93,7 @@ public:
93
93
  &JsiSkParagraphBuilder::pop);
94
94
  }
95
95
 
96
- size_t getMemoryPressure() override { return 1024 * 1024; }
96
+ size_t getMemoryPressure() override { return kMinMemoryPressure; }
97
97
 
98
98
  explicit JsiSkParagraphBuilder(std::shared_ptr<RNSkPlatformContext> context,
99
99
  para::ParagraphStyle paragraphStyle,
@@ -47,7 +47,7 @@ public:
47
47
  &JsiSkParagraphBuilderFactory::Make);
48
48
  }
49
49
 
50
- size_t getMemoryPressure() override { return 1024 * 1024; }
50
+ size_t getMemoryPressure() override { return kMinMemoryPressure; }
51
51
 
52
52
  explicit JsiSkParagraphBuilderFactory(
53
53
  std::shared_ptr<RNSkPlatformContext> context)
@@ -640,11 +640,18 @@ public:
640
640
  : JsiSkPath(std::move(context), SkPathBuilder(path)) {}
641
641
 
642
642
  size_t getMemoryPressure() override {
643
- auto builder = getObject();
644
- if (!builder)
643
+ if (isDisposed()) {
645
644
  return 0;
646
-
647
- return builder->snapshot().approximateBytesUsed();
645
+ }
646
+ auto builder = getObjectUnchecked();
647
+ if (!builder) {
648
+ return 0;
649
+ }
650
+ // The point, verb and conic weight arrays of the builder. Snapshotting
651
+ // the path to measure it would copy them, and this runs on every round
652
+ // trip of the object to JS.
653
+ return builder->points().size_bytes() + builder->verbs().size_bytes() +
654
+ builder->conicWeights().size_bytes();
648
655
  }
649
656
 
650
657
  /**
@@ -271,11 +271,18 @@ public:
271
271
  std::make_shared<SkPathBuilder>(std::move(builder))) {}
272
272
 
273
273
  size_t getMemoryPressure() override {
274
- auto builder = getObject();
275
- if (!builder)
274
+ if (isDisposed()) {
276
275
  return 0;
277
- // Estimate memory usage based on snapshot
278
- return builder->snapshot().approximateBytesUsed();
276
+ }
277
+ auto builder = getObjectUnchecked();
278
+ if (!builder) {
279
+ return 0;
280
+ }
281
+ // The point, verb and conic weight arrays of the builder. Snapshotting
282
+ // the path to measure it would copy them, and this runs on every round
283
+ // trip of the object to JS.
284
+ return builder->points().size_bytes() + builder->verbs().size_bytes() +
285
+ builder->conicWeights().size_bytes();
279
286
  }
280
287
 
281
288
  static jsi::Value toValue(jsi::Runtime &runtime,
@@ -58,7 +58,8 @@ public:
58
58
  &JsiSkPictureRecorder::finishRecordingAsPicture);
59
59
  }
60
60
 
61
- size_t getMemoryPressure() override { return 1024 * 1024; }
61
+ // The recorded picture reports its own size once finished.
62
+ size_t getMemoryPressure() override { return kMinMemoryPressure; }
62
63
 
63
64
  static const jsi::HostFunctionType
64
65
  createCtor(std::shared_ptr<RNSkPlatformContext> context) {
@@ -29,7 +29,9 @@ public:
29
29
  : JsiSkWrappingSkPtrNativeObject<JsiSkShader, SkShader>(
30
30
  std::move(context), std::move(shader)) {}
31
31
 
32
- size_t getMemoryPressure() override { return 1024 * 1024; }
32
+ // A shader node is a few hundred bytes; the images it may reference
33
+ // are reported by their own wrappers.
34
+ size_t getMemoryPressure() override { return kMinMemoryPressure; }
33
35
 
34
36
  static sk_sp<SkShader> fromValue(jsi::Runtime &runtime,
35
37
  const jsi::Value &obj) {
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <algorithm>
3
4
  #include <memory>
4
5
  #include <optional>
5
6
  #include <string>
@@ -31,7 +32,7 @@
31
32
  #pragma clang diagnostic pop
32
33
 
33
34
  namespace RNSkia {
34
- using namespace facebook;
35
+ namespace jsi = facebook::jsi;
35
36
 
36
37
  std::unique_ptr<SkCodec> DecodeImageData(sk_sp<SkData> data) {
37
38
  if (data == nullptr) {
@@ -74,6 +75,8 @@ public:
74
75
  if (!codec) {
75
76
  return nullptr;
76
77
  }
78
+ // The asset keeps its decoded frame for the lifetime of the animation.
79
+ _decodedImageBytes += codec->getInfo().computeMinByteSize();
77
80
  return skresources::MultiFrameImageAsset::Make(std::move(codec));
78
81
  }
79
82
 
@@ -102,11 +105,16 @@ public:
102
105
  return this->findAsset(name);
103
106
  }
104
107
 
108
+ // Bytes of the decoded images for the assets loaded so far.
109
+ size_t decodedImageBytes() const { return _decodedImageBytes; }
110
+
105
111
  private:
106
112
  explicit SkottieAssetProvider(AssetMap assets, sk_sp<SkFontMgr> fontMgr)
107
113
  : fAssets(std::move(assets)), fFontMgr(std::move(fontMgr)) {}
108
114
  const AssetMap fAssets;
109
115
  const sk_sp<SkFontMgr> fFontMgr;
116
+ // loadImageAsset() is const in skottie::ResourceProvider.
117
+ mutable size_t _decodedImageBytes = 0;
110
118
 
111
119
  sk_sp<SkData> findAsset(const char name[]) const {
112
120
  auto it = fAssets.find(name);
@@ -121,6 +129,12 @@ class ManagedAnimation {
121
129
  public:
122
130
  ManagedAnimation(std::string json, SkottieAssetProvider::AssetMap assets,
123
131
  sk_sp<SkFontMgr> fontMgr) {
132
+ _approximateBytesUsed = json.size();
133
+ for (const auto &asset : assets) {
134
+ if (asset.second) {
135
+ _approximateBytesUsed += asset.second->size();
136
+ }
137
+ }
124
138
  _propManager = std::make_unique<CustomPropertyManager>(
125
139
  CustomPropertyManager::Mode::kCollapseProperties, "");
126
140
  _resourceProvider =
@@ -132,6 +146,8 @@ public:
132
146
  builder->setPropertyObserver(_propManager->getPropertyObserver());
133
147
  _animation = builder->make(json.c_str(), json.size());
134
148
  _slotManager = builder->getSlotManager();
149
+ // Image assets are loaded while the animation is built.
150
+ _approximateBytesUsed += _resourceProvider->decodedImageBytes();
135
151
  }
136
152
 
137
153
  ~ManagedAnimation() {
@@ -151,6 +167,10 @@ public:
151
167
  sk_sp<skottie::SlotManager> _slotManager = nullptr;
152
168
  sk_sp<SkottieAssetProvider> _resourceProvider = nullptr;
153
169
  std::unique_ptr<CustomPropertyManager> _propManager = nullptr;
170
+ // Size of the animation JSON, its encoded assets and its decoded images:
171
+ // the memory the animation actually owns (the scene graph is in the same
172
+ // order as the JSON). Used as the GC hint for the wrapper.
173
+ size_t _approximateBytesUsed = 0;
154
174
  };
155
175
 
156
176
  class JsiSkSkottie
@@ -579,31 +599,13 @@ public:
579
599
 
580
600
  size_t getMemoryPressure() override {
581
601
  auto animation = getObject();
582
- if (!animation || !animation->_animation) {
583
- return 1024; // Base size if no animation
602
+ if (!animation) {
603
+ return kMinMemoryPressure;
584
604
  }
585
-
586
- auto size = animation->_animation->size();
587
- auto duration = animation->_animation->duration();
588
- auto fps = animation->_animation->fps();
589
-
590
- // Estimate memory usage based on animation properties
591
- // Base calculation: width * height * 4 bytes per pixel * estimated frame
592
- // count
593
- size_t frameCount = static_cast<size_t>(duration * fps);
594
- size_t estimatedFrameSize =
595
- static_cast<size_t>(size.width() * size.height() * 4);
596
-
597
- // Conservative estimate: assume some frames are cached
598
- size_t cachedFrames =
599
- std::min(frameCount, static_cast<size_t>(60)); // Max 60 cached frames
600
- size_t animationMemory = estimatedFrameSize * cachedFrames;
601
-
602
- // Add base overhead for animation data structures
603
- size_t baseOverhead =
604
- 64 * 1024; // 64KB for metadata, property managers, etc.
605
-
606
- return animationMemory + baseOverhead;
605
+ // Skottie keeps no frame cache: report the bytes the animation owns.
606
+ // Estimating a pixel cache here (width * height * 4 * 60 frames) charged
607
+ // hundreds of MB per wrapper, and each capture in a worklet repeated it.
608
+ return std::max(animation->_approximateBytesUsed, kMinMemoryPressure);
607
609
  }
608
610
  };
609
611
  } // namespace RNSkia
@@ -55,7 +55,7 @@ public:
55
55
  CommandType type;
56
56
  std::string nodeType;
57
57
 
58
- Command(CommandType t) : type(t) {}
58
+ explicit Command(CommandType t) : type(t) {}
59
59
  Command(CommandType t, std::string nodeT) : type(t), nodeType(nodeT) {}
60
60
  virtual ~Command() = default;
61
61
  };
@@ -692,7 +692,7 @@ SkPath1DPathEffect::Style getPropertyValue(jsi::Runtime &runtime,
692
692
  // specialization
693
693
  struct BlendModeValue {
694
694
  int value;
695
- BlendModeValue(int v = 0) : value(v) {}
695
+ explicit BlendModeValue(int v = 0) : value(v) {}
696
696
  operator int() const { return value; }
697
697
  };
698
698
 
@@ -78,7 +78,7 @@ SkPoint processPoint(jsi::Runtime &runtime, const jsi::Value &value) {
78
78
  }
79
79
  }
80
80
  throw std::runtime_error("Couldn't read point value");
81
- };
81
+ }
82
82
 
83
83
  // TODO: return the SkRRect directly
84
84
  std::shared_ptr<SkRRect> processRRect(jsi::Runtime &runtime,
@@ -52,7 +52,7 @@ struct Composers {
52
52
 
53
53
  class DrawingCtx {
54
54
  public:
55
- DrawingCtx(SkCanvas *canvas) : canvas(canvas) {
55
+ explicit DrawingCtx(SkCanvas *canvas) : canvas(canvas) {
56
56
  SkPaint paint;
57
57
  paint.setAntiAlias(true);
58
58
  paints.push_back(paint);
@@ -24,7 +24,7 @@ class JsiRecorder
24
24
  public:
25
25
  static constexpr const char *CLASS_NAME = "Recorder";
26
26
 
27
- JsiRecorder(std::shared_ptr<RNSkPlatformContext> context)
27
+ explicit JsiRecorder(std::shared_ptr<RNSkPlatformContext> context)
28
28
  : JsiSkWrappingSharedPtrNativeObject<JsiRecorder, Recorder>(
29
29
  std::move(context), std::make_shared<Recorder>()) {
30
30
  getObject()->_context = getContext();
@@ -379,9 +379,12 @@ public:
379
379
  installHostMethod(runtime, prototype, "reset", &JsiRecorder::reset);
380
380
  }
381
381
 
382
- // This has no basis in reality but since since these are private long-lived
383
- // objects, we think it is more than fine.
384
- size_t getMemoryPressure() override { return 5 * 1024 * 1024; }
382
+ // The recorder itself is a small command list. The resources it references
383
+ // (images, pictures, paths) report their own size through their wrappers
384
+ // and the pictures produced by play() report theirs. Do not put a made-up
385
+ // number here: a new recorder is created on every React commit and the
386
+ // charge is repeated each time it is unboxed on the UI runtime.
387
+ size_t getMemoryPressure() override { return kMinMemoryPressure; }
385
388
 
386
389
  static const jsi::HostFunctionType
387
390
  createCtor(std::shared_ptr<RNSkPlatformContext> context) {
@@ -4,7 +4,7 @@
4
4
  * Use of this source code is governed by a BSD-style license that can be
5
5
  * found in the LICENSE file.
6
6
  */
7
- #include "base64.h"
7
+ #include "api/third_party/base64.h"
8
8
 
9
9
  #include <cstdint>
10
10
 
@@ -1,5 +1,6 @@
1
1
  #include "JSICache.h"
2
2
 
3
+ #include <algorithm>
3
4
  #include <iterator>
4
5
  #include <memory>
5
6
  #include <stdexcept>
@@ -96,4 +97,26 @@ jsi::Object &JSICache::setPrototype(PrototypeKey key, jsi::Object prototype) {
96
97
  return it->second;
97
98
  }
98
99
 
100
+ jsi::Value JSICache::lockWrapper(jsi::Runtime &runtime,
101
+ const void *nativeObject) {
102
+ auto it = _wrappers.find(nativeObject);
103
+ if (it == _wrappers.end()) {
104
+ return jsi::Value::undefined();
105
+ }
106
+ return it->second.lock(runtime);
107
+ }
108
+
109
+ void JSICache::setWrapper(jsi::Runtime &runtime, const void *nativeObject,
110
+ const jsi::Object &wrapper) {
111
+ _wrappers.insert_or_assign(nativeObject, jsi::WeakObject(runtime, wrapper));
112
+ if (_wrappers.size() < _pruneWrappersAt) {
113
+ return;
114
+ }
115
+ for (auto it = _wrappers.begin(); it != _wrappers.end();) {
116
+ it = it->second.lock(runtime).isUndefined() ? _wrappers.erase(it)
117
+ : std::next(it);
118
+ }
119
+ _pruneWrappersAt = std::max(kMinPruneWrappersAt, _wrappers.size() * 2);
120
+ }
121
+
99
122
  } // namespace RNJsi
@@ -13,7 +13,8 @@ namespace jsi = facebook::jsi;
13
13
 
14
14
  /**
15
15
  * Per-runtime store for JSI values that native code wants to keep across
16
- * calls (currently: the prototype object of every NativeObject class).
16
+ * calls: the prototype object of every NativeObject class, and the JS
17
+ * wrapper of every native object that has one on the runtime.
17
18
  *
18
19
  * Ownership model (borrowed from react-native-nitro-modules' JSICache):
19
20
  *
@@ -72,6 +73,26 @@ public:
72
73
  */
73
74
  jsi::Object &setPrototype(PrototypeKey key, jsi::Object prototype);
74
75
 
76
+ /**
77
+ * Returns the JS wrapper stored for `nativeObject` on this runtime, or
78
+ * undefined if none was stored or the wrapper has been collected.
79
+ *
80
+ * The key is the native object's address. A live wrapper keeps its native
81
+ * object alive (the object is the wrapper's NativeState), so as long as
82
+ * the entry locks it still belongs to that object. Once the wrapper has
83
+ * been collected the entry is stale and is skipped or replaced, so an
84
+ * address reused by a new object can never resolve to the old wrapper.
85
+ */
86
+ jsi::Value lockWrapper(jsi::Runtime &runtime, const void *nativeObject);
87
+
88
+ /**
89
+ * Stores `wrapper` as the JS wrapper of `nativeObject` on this runtime.
90
+ * Only a weak reference is kept: the wrapper stays collectable, and the
91
+ * native object is released when it goes away.
92
+ */
93
+ void setWrapper(jsi::Runtime &runtime, const void *nativeObject,
94
+ const jsi::Object &wrapper);
95
+
75
96
  JSICache() = default;
76
97
  ~JSICache() override = default;
77
98
  JSICache(const JSICache &) = delete;
@@ -81,6 +102,12 @@ private:
81
102
  static std::shared_ptr<JSICache> getOrCreateOnGlobal(jsi::Runtime &runtime);
82
103
 
83
104
  std::unordered_map<PrototypeKey, jsi::Object> _prototypes;
105
+ // Native object -> its wrapper on this runtime. Entries of collected
106
+ // wrappers are pruned in setWrapper once the map has doubled since the
107
+ // last pruning, so it stays proportional to the number of live wrappers.
108
+ std::unordered_map<const void *, jsi::WeakObject> _wrappers;
109
+ size_t _pruneWrappersAt = kMinPruneWrappersAt;
110
+ static constexpr size_t kMinPruneWrappersAt = 64;
84
111
 
85
112
  // Runtime -> cache. Weak on purpose: the runtime's global owns the cache.
86
113
  static std::mutex _registryMutex;
@@ -210,12 +210,12 @@ public:
210
210
  if (instance == nullptr) {
211
211
  throw jsi::JSError(rt, "Invalid boxed native object state");
212
212
  }
213
- // Unboxing creates a *view* of the object on the target runtime.
214
- // Keep the runtime the object was originally created on: async
215
- // native code (e.g. GPUDevice error/lost events) delivers into
216
- // the creation runtime, and rebinding it to a worklet runtime
217
- // would invoke main-runtime jsi::Functions on the wrong runtime
218
- // and thread.
213
+ // Unboxing hands back the wrapper cached for the target runtime,
214
+ // creating it on first use. Keep the runtime the object was
215
+ // originally created on: async native code (e.g. GPUDevice
216
+ // error/lost events) delivers into the creation runtime, and
217
+ // rebinding it to a worklet runtime would invoke main-runtime
218
+ // jsi::Functions on the wrong runtime and thread.
219
219
  auto *originalRuntime = instance->getCreationRuntime();
220
220
  auto value = Derived::create(rt, instance);
221
221
  if (originalRuntime != nullptr) {
@@ -262,10 +262,37 @@ public:
262
262
  }
263
263
 
264
264
  /**
265
- * Create a JS object with native state attached.
265
+ * Returns the JS wrapper of `instance` on `runtime`, creating it on first
266
+ * use.
267
+ *
268
+ * A native object has at most one live wrapper per runtime. The wrapper is
269
+ * cached (weakly, so it stays collectable) in the runtime's JSICache, and
270
+ * converting the same object again (returning it from another native
271
+ * call, unboxing it in a worklet on every frame, ...) hands back that
272
+ * wrapper instead of a new one. Each wrapper reports the native memory the
273
+ * object owns to the GC of its runtime through setExternalMemoryPressure,
274
+ * so the memory is charged exactly once per runtime that can reach the
275
+ * object, however often it is converted; charging every conversion used
276
+ * to multiply the amount by the number of captures in a worklet until
277
+ * Hermes hit its max heap size. The hint is refreshed on every round trip
278
+ * for objects whose size changes after creation (path and paragraph
279
+ * builders).
266
280
  */
267
281
  static jsi::Value create(jsi::Runtime &runtime,
268
282
  std::shared_ptr<Derived> instance) {
283
+ auto &cache = JSICache::get(runtime);
284
+ const void *key = instance.get();
285
+
286
+ auto existing = cache.lockWrapper(runtime, key);
287
+ if (existing.isObject()) {
288
+ auto pressure = instance->getMemoryPressure();
289
+ if (pressure > 0) {
290
+ existing.getObject(runtime).setExternalMemoryPressure(runtime,
291
+ pressure);
292
+ }
293
+ return existing;
294
+ }
295
+
269
296
  // Store creation runtime for logging etc.
270
297
  instance->setCreationRuntime(&runtime);
271
298
 
@@ -290,9 +317,11 @@ public:
290
317
  obj.setExternalMemoryPressure(runtime, pressure);
291
318
  }
292
319
 
320
+ cache.setWrapper(runtime, key, obj);
293
321
  return std::move(obj);
294
322
  }
295
323
 
324
+ public:
296
325
  /**
297
326
  * Get the native state from a JS value.
298
327
  * Throws if the value doesn't have the expected native state.
@@ -112,7 +112,7 @@ public:
112
112
  int height = static_cast<int>(IOSurfaceGetHeight(ioSurface));
113
113
  #else
114
114
  wgpu::SharedTextureMemoryAHardwareBufferDescriptor platformDesc;
115
- auto ahb = (AHardwareBuffer *)buffer;
115
+ auto ahb = reinterpret_cast<AHardwareBuffer *>(buffer);
116
116
  platformDesc.handle = ahb;
117
117
  AHardwareBuffer_Desc adesc;
118
118
  AHardwareBuffer_describe(ahb, &adesc);
@@ -9,6 +9,12 @@ var _interpolate = require("./interpolate");
9
9
  const lerp = (value, from, to, p1, p2) => {
10
10
  "worklet";
11
11
 
12
+ // Zero-width segment (duplicate input stops): t would be NaN or Infinity.
13
+ // Return the segment's end path (p2), i.e. the value jumps to p2 at the stop.
14
+ // reanimated's interpolate has no explicit guard here, so this is our choice.
15
+ if (to === from) {
16
+ return p2;
17
+ }
12
18
  const t = (value - from) / (to - from);
13
19
  // interpolate returns a new path (immutable operation)
14
20
  return p2.interpolate(p1, t);
@@ -33,12 +39,21 @@ const lerp = (value, from, to, p1, p2) => {
33
39
  const interpolatePaths = (value, input, outputRange, options, _output) => {
34
40
  "worklet";
35
41
 
42
+ if (input.length < 2 || input.length !== outputRange.length) {
43
+ throw new Error(`interpolatePaths() requires input and outputRange to have the same length and at least 2 entries, received ${input.length} and ${outputRange.length}`);
44
+ }
45
+ if (Number.isNaN(value)) {
46
+ throw new Error("interpolatePaths() received NaN as value");
47
+ }
36
48
  const extrapolation = (0, _interpolate.validateInterpolationOptions)(options);
37
49
  if (value < input[0]) {
38
50
  switch (extrapolation.extrapolateLeft) {
39
51
  case _interpolate.Extrapolate.CLAMP:
40
52
  return outputRange[0];
41
53
  case _interpolate.Extrapolate.EXTEND:
54
+ if (!Number.isFinite(value)) {
55
+ throw new Error("interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead");
56
+ }
42
57
  return lerp(value, input[0], input[1], outputRange[0], outputRange[1]);
43
58
  case _interpolate.Extrapolate.IDENTITY:
44
59
  throw new Error("Identity is not a supported extrapolation type for interpolatePaths()");
@@ -50,6 +65,9 @@ const interpolatePaths = (value, input, outputRange, options, _output) => {
50
65
  case _interpolate.Extrapolate.CLAMP:
51
66
  return outputRange[outputRange.length - 1];
52
67
  case _interpolate.Extrapolate.EXTEND:
68
+ if (!Number.isFinite(value)) {
69
+ throw new Error("interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead");
70
+ }
53
71
  return lerp(value, input[input.length - 2], input[input.length - 1], outputRange[input.length - 2], outputRange[input.length - 1]);
54
72
  case _interpolate.Extrapolate.IDENTITY:
55
73
  throw new Error("Identity is not a supported extrapolation type for interpolatePaths()");
@@ -1 +1 @@
1
- {"version":3,"names":["_typeddash","require","_interpolate","lerp","value","from","to","p1","p2","t","interpolate","interpolatePaths","input","outputRange","options","_output","extrapolation","validateInterpolationOptions","extrapolateLeft","Extrapolate","CLAMP","EXTEND","IDENTITY","Error","exhaustiveCheck","length","extrapolateRight","i","exports"],"sources":["interpolatePaths.ts"],"sourcesContent":["import type { SkPath } from \"../../skia/types\";\nimport { exhaustiveCheck } from \"../../renderer/typeddash\";\n\nimport type { ExtrapolationType } from \"./interpolate\";\nimport { validateInterpolationOptions, Extrapolate } from \"./interpolate\";\n\nconst lerp = (\n value: number,\n from: number,\n to: number,\n p1: SkPath,\n p2: SkPath\n) => {\n \"worklet\";\n const t = (value - from) / (to - from);\n // interpolate returns a new path (immutable operation)\n return p2.interpolate(p1, t)!;\n};\n\n/**\n * Maps an input value within a range to an output path within a path range.\n * @param value - The input value.\n * @param inputRange - The range of the input value.\n * @param outputRange - The range of the output path.\n * @param options - Extrapolation options\n * @returns The output path.\n * @example <caption>Map a value between 0 and 1 to a path between two paths.</caption>\n * const path1 = new Path();\n * path1.moveTo(0, 0);\n * path1.lineTo(100, 0);\n * const path2 = new Path();\n * path2.moveTo(0, 0);\n * path2.lineTo(0, 100);\n * const path = interpolatePath(0.5, [0, 1], [path1, path2]);\n */\nexport const interpolatePaths = (\n value: number,\n input: number[],\n outputRange: SkPath[],\n options?: ExtrapolationType,\n _output?: SkPath\n) => {\n \"worklet\";\n const extrapolation = validateInterpolationOptions(options);\n if (value < input[0]) {\n switch (extrapolation.extrapolateLeft) {\n case Extrapolate.CLAMP:\n return outputRange[0];\n case Extrapolate.EXTEND:\n return lerp(value, input[0], input[1], outputRange[0], outputRange[1]);\n case Extrapolate.IDENTITY:\n throw new Error(\n \"Identity is not a supported extrapolation type for interpolatePaths()\"\n );\n default:\n exhaustiveCheck(extrapolation.extrapolateLeft);\n }\n } else if (value > input[input.length - 1]) {\n switch (extrapolation.extrapolateRight) {\n case Extrapolate.CLAMP:\n return outputRange[outputRange.length - 1];\n case Extrapolate.EXTEND:\n return lerp(\n value,\n input[input.length - 2],\n input[input.length - 1],\n outputRange[input.length - 2],\n outputRange[input.length - 1]\n );\n case Extrapolate.IDENTITY:\n throw new Error(\n \"Identity is not a supported extrapolation type for interpolatePaths()\"\n );\n default:\n exhaustiveCheck(extrapolation.extrapolateRight);\n }\n }\n let i = 0;\n for (; i <= input.length - 1; i++) {\n if (value >= input[i] && value <= input[i + 1]) {\n break;\n }\n }\n return lerp(\n value,\n input[i],\n input[i + 1],\n outputRange[i],\n outputRange[i + 1]\n );\n};\n"],"mappings":";;;;;;AACA,IAAAA,UAAA,GAAAC,OAAA;AAGA,IAAAC,YAAA,GAAAD,OAAA;AAEA,MAAME,IAAI,GAAGA,CACXC,KAAa,EACbC,IAAY,EACZC,EAAU,EACVC,EAAU,EACVC,EAAU,KACP;EACH,SAAS;;EACT,MAAMC,CAAC,GAAG,CAACL,KAAK,GAAGC,IAAI,KAAKC,EAAE,GAAGD,IAAI,CAAC;EACtC;EACA,OAAOG,EAAE,CAACE,WAAW,CAACH,EAAE,EAAEE,CAAC,CAAC;AAC9B,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAME,gBAAgB,GAAGA,CAC9BP,KAAa,EACbQ,KAAe,EACfC,WAAqB,EACrBC,OAA2B,EAC3BC,OAAgB,KACb;EACH,SAAS;;EACT,MAAMC,aAAa,GAAG,IAAAC,yCAA4B,EAACH,OAAO,CAAC;EAC3D,IAAIV,KAAK,GAAGQ,KAAK,CAAC,CAAC,CAAC,EAAE;IACpB,QAAQI,aAAa,CAACE,eAAe;MACnC,KAAKC,wBAAW,CAACC,KAAK;QACpB,OAAOP,WAAW,CAAC,CAAC,CAAC;MACvB,KAAKM,wBAAW,CAACE,MAAM;QACrB,OAAOlB,IAAI,CAACC,KAAK,EAAEQ,KAAK,CAAC,CAAC,CAAC,EAAEA,KAAK,CAAC,CAAC,CAAC,EAAEC,WAAW,CAAC,CAAC,CAAC,EAAEA,WAAW,CAAC,CAAC,CAAC,CAAC;MACxE,KAAKM,wBAAW,CAACG,QAAQ;QACvB,MAAM,IAAIC,KAAK,CACb,uEACF,CAAC;MACH;QACE,IAAAC,0BAAe,EAACR,aAAa,CAACE,eAAe,CAAC;IAClD;EACF,CAAC,MAAM,IAAId,KAAK,GAAGQ,KAAK,CAACA,KAAK,CAACa,MAAM,GAAG,CAAC,CAAC,EAAE;IAC1C,QAAQT,aAAa,CAACU,gBAAgB;MACpC,KAAKP,wBAAW,CAACC,KAAK;QACpB,OAAOP,WAAW,CAACA,WAAW,CAACY,MAAM,GAAG,CAAC,CAAC;MAC5C,KAAKN,wBAAW,CAACE,MAAM;QACrB,OAAOlB,IAAI,CACTC,KAAK,EACLQ,KAAK,CAACA,KAAK,CAACa,MAAM,GAAG,CAAC,CAAC,EACvBb,KAAK,CAACA,KAAK,CAACa,MAAM,GAAG,CAAC,CAAC,EACvBZ,WAAW,CAACD,KAAK,CAACa,MAAM,GAAG,CAAC,CAAC,EAC7BZ,WAAW,CAACD,KAAK,CAACa,MAAM,GAAG,CAAC,CAC9B,CAAC;MACH,KAAKN,wBAAW,CAACG,QAAQ;QACvB,MAAM,IAAIC,KAAK,CACb,uEACF,CAAC;MACH;QACE,IAAAC,0BAAe,EAACR,aAAa,CAACU,gBAAgB,CAAC;IACnD;EACF;EACA,IAAIC,CAAC,GAAG,CAAC;EACT,OAAOA,CAAC,IAAIf,KAAK,CAACa,MAAM,GAAG,CAAC,EAAEE,CAAC,EAAE,EAAE;IACjC,IAAIvB,KAAK,IAAIQ,KAAK,CAACe,CAAC,CAAC,IAAIvB,KAAK,IAAIQ,KAAK,CAACe,CAAC,GAAG,CAAC,CAAC,EAAE;MAC9C;IACF;EACF;EACA,OAAOxB,IAAI,CACTC,KAAK,EACLQ,KAAK,CAACe,CAAC,CAAC,EACRf,KAAK,CAACe,CAAC,GAAG,CAAC,CAAC,EACZd,WAAW,CAACc,CAAC,CAAC,EACdd,WAAW,CAACc,CAAC,GAAG,CAAC,CACnB,CAAC;AACH,CAAC;AAACC,OAAA,CAAAjB,gBAAA,GAAAA,gBAAA","ignoreList":[]}
1
+ {"version":3,"names":["_typeddash","require","_interpolate","lerp","value","from","to","p1","p2","t","interpolate","interpolatePaths","input","outputRange","options","_output","length","Error","Number","isNaN","extrapolation","validateInterpolationOptions","extrapolateLeft","Extrapolate","CLAMP","EXTEND","isFinite","IDENTITY","exhaustiveCheck","extrapolateRight","i","exports"],"sources":["interpolatePaths.ts"],"sourcesContent":["import type { SkPath } from \"../../skia/types\";\nimport { exhaustiveCheck } from \"../../renderer/typeddash\";\n\nimport type { ExtrapolationType } from \"./interpolate\";\nimport { validateInterpolationOptions, Extrapolate } from \"./interpolate\";\n\nconst lerp = (\n value: number,\n from: number,\n to: number,\n p1: SkPath,\n p2: SkPath\n) => {\n \"worklet\";\n // Zero-width segment (duplicate input stops): t would be NaN or Infinity.\n // Return the segment's end path (p2), i.e. the value jumps to p2 at the stop.\n // reanimated's interpolate has no explicit guard here, so this is our choice.\n if (to === from) {\n return p2;\n }\n const t = (value - from) / (to - from);\n // interpolate returns a new path (immutable operation)\n return p2.interpolate(p1, t)!;\n};\n\n/**\n * Maps an input value within a range to an output path within a path range.\n * @param value - The input value.\n * @param inputRange - The range of the input value.\n * @param outputRange - The range of the output path.\n * @param options - Extrapolation options\n * @returns The output path.\n * @example <caption>Map a value between 0 and 1 to a path between two paths.</caption>\n * const path1 = new Path();\n * path1.moveTo(0, 0);\n * path1.lineTo(100, 0);\n * const path2 = new Path();\n * path2.moveTo(0, 0);\n * path2.lineTo(0, 100);\n * const path = interpolatePath(0.5, [0, 1], [path1, path2]);\n */\nexport const interpolatePaths = (\n value: number,\n input: number[],\n outputRange: SkPath[],\n options?: ExtrapolationType,\n _output?: SkPath\n) => {\n \"worklet\";\n if (input.length < 2 || input.length !== outputRange.length) {\n throw new Error(\n `interpolatePaths() requires input and outputRange to have the same length and at least 2 entries, received ${input.length} and ${outputRange.length}`\n );\n }\n if (Number.isNaN(value)) {\n throw new Error(\"interpolatePaths() received NaN as value\");\n }\n const extrapolation = validateInterpolationOptions(options);\n if (value < input[0]) {\n switch (extrapolation.extrapolateLeft) {\n case Extrapolate.CLAMP:\n return outputRange[0];\n case Extrapolate.EXTEND:\n if (!Number.isFinite(value)) {\n throw new Error(\n \"interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead\"\n );\n }\n return lerp(value, input[0], input[1], outputRange[0], outputRange[1]);\n case Extrapolate.IDENTITY:\n throw new Error(\n \"Identity is not a supported extrapolation type for interpolatePaths()\"\n );\n default:\n exhaustiveCheck(extrapolation.extrapolateLeft);\n }\n } else if (value > input[input.length - 1]) {\n switch (extrapolation.extrapolateRight) {\n case Extrapolate.CLAMP:\n return outputRange[outputRange.length - 1];\n case Extrapolate.EXTEND:\n if (!Number.isFinite(value)) {\n throw new Error(\n \"interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead\"\n );\n }\n return lerp(\n value,\n input[input.length - 2],\n input[input.length - 1],\n outputRange[input.length - 2],\n outputRange[input.length - 1]\n );\n case Extrapolate.IDENTITY:\n throw new Error(\n \"Identity is not a supported extrapolation type for interpolatePaths()\"\n );\n default:\n exhaustiveCheck(extrapolation.extrapolateRight);\n }\n }\n let i = 0;\n for (; i <= input.length - 1; i++) {\n if (value >= input[i] && value <= input[i + 1]) {\n break;\n }\n }\n return lerp(\n value,\n input[i],\n input[i + 1],\n outputRange[i],\n outputRange[i + 1]\n );\n};\n"],"mappings":"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","ignoreList":[]}
@@ -3,6 +3,12 @@ import { validateInterpolationOptions, Extrapolate } from "./interpolate";
3
3
  const lerp = (value, from, to, p1, p2) => {
4
4
  "worklet";
5
5
 
6
+ // Zero-width segment (duplicate input stops): t would be NaN or Infinity.
7
+ // Return the segment's end path (p2), i.e. the value jumps to p2 at the stop.
8
+ // reanimated's interpolate has no explicit guard here, so this is our choice.
9
+ if (to === from) {
10
+ return p2;
11
+ }
6
12
  const t = (value - from) / (to - from);
7
13
  // interpolate returns a new path (immutable operation)
8
14
  return p2.interpolate(p1, t);
@@ -27,12 +33,21 @@ const lerp = (value, from, to, p1, p2) => {
27
33
  export const interpolatePaths = (value, input, outputRange, options, _output) => {
28
34
  "worklet";
29
35
 
36
+ if (input.length < 2 || input.length !== outputRange.length) {
37
+ throw new Error(`interpolatePaths() requires input and outputRange to have the same length and at least 2 entries, received ${input.length} and ${outputRange.length}`);
38
+ }
39
+ if (Number.isNaN(value)) {
40
+ throw new Error("interpolatePaths() received NaN as value");
41
+ }
30
42
  const extrapolation = validateInterpolationOptions(options);
31
43
  if (value < input[0]) {
32
44
  switch (extrapolation.extrapolateLeft) {
33
45
  case Extrapolate.CLAMP:
34
46
  return outputRange[0];
35
47
  case Extrapolate.EXTEND:
48
+ if (!Number.isFinite(value)) {
49
+ throw new Error("interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead");
50
+ }
36
51
  return lerp(value, input[0], input[1], outputRange[0], outputRange[1]);
37
52
  case Extrapolate.IDENTITY:
38
53
  throw new Error("Identity is not a supported extrapolation type for interpolatePaths()");
@@ -44,6 +59,9 @@ export const interpolatePaths = (value, input, outputRange, options, _output) =>
44
59
  case Extrapolate.CLAMP:
45
60
  return outputRange[outputRange.length - 1];
46
61
  case Extrapolate.EXTEND:
62
+ if (!Number.isFinite(value)) {
63
+ throw new Error("interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead");
64
+ }
47
65
  return lerp(value, input[input.length - 2], input[input.length - 1], outputRange[input.length - 2], outputRange[input.length - 1]);
48
66
  case Extrapolate.IDENTITY:
49
67
  throw new Error("Identity is not a supported extrapolation type for interpolatePaths()");
@@ -1 +1 @@
1
- {"version":3,"names":["exhaustiveCheck","validateInterpolationOptions","Extrapolate","lerp","value","from","to","p1","p2","t","interpolate","interpolatePaths","input","outputRange","options","_output","extrapolation","extrapolateLeft","CLAMP","EXTEND","IDENTITY","Error","length","extrapolateRight","i"],"sources":["interpolatePaths.ts"],"sourcesContent":["import type { SkPath } from \"../../skia/types\";\nimport { exhaustiveCheck } from \"../../renderer/typeddash\";\n\nimport type { ExtrapolationType } from \"./interpolate\";\nimport { validateInterpolationOptions, Extrapolate } from \"./interpolate\";\n\nconst lerp = (\n value: number,\n from: number,\n to: number,\n p1: SkPath,\n p2: SkPath\n) => {\n \"worklet\";\n const t = (value - from) / (to - from);\n // interpolate returns a new path (immutable operation)\n return p2.interpolate(p1, t)!;\n};\n\n/**\n * Maps an input value within a range to an output path within a path range.\n * @param value - The input value.\n * @param inputRange - The range of the input value.\n * @param outputRange - The range of the output path.\n * @param options - Extrapolation options\n * @returns The output path.\n * @example <caption>Map a value between 0 and 1 to a path between two paths.</caption>\n * const path1 = new Path();\n * path1.moveTo(0, 0);\n * path1.lineTo(100, 0);\n * const path2 = new Path();\n * path2.moveTo(0, 0);\n * path2.lineTo(0, 100);\n * const path = interpolatePath(0.5, [0, 1], [path1, path2]);\n */\nexport const interpolatePaths = (\n value: number,\n input: number[],\n outputRange: SkPath[],\n options?: ExtrapolationType,\n _output?: SkPath\n) => {\n \"worklet\";\n const extrapolation = validateInterpolationOptions(options);\n if (value < input[0]) {\n switch (extrapolation.extrapolateLeft) {\n case Extrapolate.CLAMP:\n return outputRange[0];\n case Extrapolate.EXTEND:\n return lerp(value, input[0], input[1], outputRange[0], outputRange[1]);\n case Extrapolate.IDENTITY:\n throw new Error(\n \"Identity is not a supported extrapolation type for interpolatePaths()\"\n );\n default:\n exhaustiveCheck(extrapolation.extrapolateLeft);\n }\n } else if (value > input[input.length - 1]) {\n switch (extrapolation.extrapolateRight) {\n case Extrapolate.CLAMP:\n return outputRange[outputRange.length - 1];\n case Extrapolate.EXTEND:\n return lerp(\n value,\n input[input.length - 2],\n input[input.length - 1],\n outputRange[input.length - 2],\n outputRange[input.length - 1]\n );\n case Extrapolate.IDENTITY:\n throw new Error(\n \"Identity is not a supported extrapolation type for interpolatePaths()\"\n );\n default:\n exhaustiveCheck(extrapolation.extrapolateRight);\n }\n }\n let i = 0;\n for (; i <= input.length - 1; i++) {\n if (value >= input[i] && value <= input[i + 1]) {\n break;\n }\n }\n return lerp(\n value,\n input[i],\n input[i + 1],\n outputRange[i],\n outputRange[i + 1]\n );\n};\n"],"mappings":"AACA,SAASA,eAAe,QAAQ,0BAA0B;AAG1D,SAASC,4BAA4B,EAAEC,WAAW,QAAQ,eAAe;AAEzE,MAAMC,IAAI,GAAGA,CACXC,KAAa,EACbC,IAAY,EACZC,EAAU,EACVC,EAAU,EACVC,EAAU,KACP;EACH,SAAS;;EACT,MAAMC,CAAC,GAAG,CAACL,KAAK,GAAGC,IAAI,KAAKC,EAAE,GAAGD,IAAI,CAAC;EACtC;EACA,OAAOG,EAAE,CAACE,WAAW,CAACH,EAAE,EAAEE,CAAC,CAAC;AAC9B,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAME,gBAAgB,GAAGA,CAC9BP,KAAa,EACbQ,KAAe,EACfC,WAAqB,EACrBC,OAA2B,EAC3BC,OAAgB,KACb;EACH,SAAS;;EACT,MAAMC,aAAa,GAAGf,4BAA4B,CAACa,OAAO,CAAC;EAC3D,IAAIV,KAAK,GAAGQ,KAAK,CAAC,CAAC,CAAC,EAAE;IACpB,QAAQI,aAAa,CAACC,eAAe;MACnC,KAAKf,WAAW,CAACgB,KAAK;QACpB,OAAOL,WAAW,CAAC,CAAC,CAAC;MACvB,KAAKX,WAAW,CAACiB,MAAM;QACrB,OAAOhB,IAAI,CAACC,KAAK,EAAEQ,KAAK,CAAC,CAAC,CAAC,EAAEA,KAAK,CAAC,CAAC,CAAC,EAAEC,WAAW,CAAC,CAAC,CAAC,EAAEA,WAAW,CAAC,CAAC,CAAC,CAAC;MACxE,KAAKX,WAAW,CAACkB,QAAQ;QACvB,MAAM,IAAIC,KAAK,CACb,uEACF,CAAC;MACH;QACErB,eAAe,CAACgB,aAAa,CAACC,eAAe,CAAC;IAClD;EACF,CAAC,MAAM,IAAIb,KAAK,GAAGQ,KAAK,CAACA,KAAK,CAACU,MAAM,GAAG,CAAC,CAAC,EAAE;IAC1C,QAAQN,aAAa,CAACO,gBAAgB;MACpC,KAAKrB,WAAW,CAACgB,KAAK;QACpB,OAAOL,WAAW,CAACA,WAAW,CAACS,MAAM,GAAG,CAAC,CAAC;MAC5C,KAAKpB,WAAW,CAACiB,MAAM;QACrB,OAAOhB,IAAI,CACTC,KAAK,EACLQ,KAAK,CAACA,KAAK,CAACU,MAAM,GAAG,CAAC,CAAC,EACvBV,KAAK,CAACA,KAAK,CAACU,MAAM,GAAG,CAAC,CAAC,EACvBT,WAAW,CAACD,KAAK,CAACU,MAAM,GAAG,CAAC,CAAC,EAC7BT,WAAW,CAACD,KAAK,CAACU,MAAM,GAAG,CAAC,CAC9B,CAAC;MACH,KAAKpB,WAAW,CAACkB,QAAQ;QACvB,MAAM,IAAIC,KAAK,CACb,uEACF,CAAC;MACH;QACErB,eAAe,CAACgB,aAAa,CAACO,gBAAgB,CAAC;IACnD;EACF;EACA,IAAIC,CAAC,GAAG,CAAC;EACT,OAAOA,CAAC,IAAIZ,KAAK,CAACU,MAAM,GAAG,CAAC,EAAEE,CAAC,EAAE,EAAE;IACjC,IAAIpB,KAAK,IAAIQ,KAAK,CAACY,CAAC,CAAC,IAAIpB,KAAK,IAAIQ,KAAK,CAACY,CAAC,GAAG,CAAC,CAAC,EAAE;MAC9C;IACF;EACF;EACA,OAAOrB,IAAI,CACTC,KAAK,EACLQ,KAAK,CAACY,CAAC,CAAC,EACRZ,KAAK,CAACY,CAAC,GAAG,CAAC,CAAC,EACZX,WAAW,CAACW,CAAC,CAAC,EACdX,WAAW,CAACW,CAAC,GAAG,CAAC,CACnB,CAAC;AACH,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["exhaustiveCheck","validateInterpolationOptions","Extrapolate","lerp","value","from","to","p1","p2","t","interpolate","interpolatePaths","input","outputRange","options","_output","length","Error","Number","isNaN","extrapolation","extrapolateLeft","CLAMP","EXTEND","isFinite","IDENTITY","extrapolateRight","i"],"sources":["interpolatePaths.ts"],"sourcesContent":["import type { SkPath } from \"../../skia/types\";\nimport { exhaustiveCheck } from \"../../renderer/typeddash\";\n\nimport type { ExtrapolationType } from \"./interpolate\";\nimport { validateInterpolationOptions, Extrapolate } from \"./interpolate\";\n\nconst lerp = (\n value: number,\n from: number,\n to: number,\n p1: SkPath,\n p2: SkPath\n) => {\n \"worklet\";\n // Zero-width segment (duplicate input stops): t would be NaN or Infinity.\n // Return the segment's end path (p2), i.e. the value jumps to p2 at the stop.\n // reanimated's interpolate has no explicit guard here, so this is our choice.\n if (to === from) {\n return p2;\n }\n const t = (value - from) / (to - from);\n // interpolate returns a new path (immutable operation)\n return p2.interpolate(p1, t)!;\n};\n\n/**\n * Maps an input value within a range to an output path within a path range.\n * @param value - The input value.\n * @param inputRange - The range of the input value.\n * @param outputRange - The range of the output path.\n * @param options - Extrapolation options\n * @returns The output path.\n * @example <caption>Map a value between 0 and 1 to a path between two paths.</caption>\n * const path1 = new Path();\n * path1.moveTo(0, 0);\n * path1.lineTo(100, 0);\n * const path2 = new Path();\n * path2.moveTo(0, 0);\n * path2.lineTo(0, 100);\n * const path = interpolatePath(0.5, [0, 1], [path1, path2]);\n */\nexport const interpolatePaths = (\n value: number,\n input: number[],\n outputRange: SkPath[],\n options?: ExtrapolationType,\n _output?: SkPath\n) => {\n \"worklet\";\n if (input.length < 2 || input.length !== outputRange.length) {\n throw new Error(\n `interpolatePaths() requires input and outputRange to have the same length and at least 2 entries, received ${input.length} and ${outputRange.length}`\n );\n }\n if (Number.isNaN(value)) {\n throw new Error(\"interpolatePaths() received NaN as value\");\n }\n const extrapolation = validateInterpolationOptions(options);\n if (value < input[0]) {\n switch (extrapolation.extrapolateLeft) {\n case Extrapolate.CLAMP:\n return outputRange[0];\n case Extrapolate.EXTEND:\n if (!Number.isFinite(value)) {\n throw new Error(\n \"interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead\"\n );\n }\n return lerp(value, input[0], input[1], outputRange[0], outputRange[1]);\n case Extrapolate.IDENTITY:\n throw new Error(\n \"Identity is not a supported extrapolation type for interpolatePaths()\"\n );\n default:\n exhaustiveCheck(extrapolation.extrapolateLeft);\n }\n } else if (value > input[input.length - 1]) {\n switch (extrapolation.extrapolateRight) {\n case Extrapolate.CLAMP:\n return outputRange[outputRange.length - 1];\n case Extrapolate.EXTEND:\n if (!Number.isFinite(value)) {\n throw new Error(\n \"interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead\"\n );\n }\n return lerp(\n value,\n input[input.length - 2],\n input[input.length - 1],\n outputRange[input.length - 2],\n outputRange[input.length - 1]\n );\n case Extrapolate.IDENTITY:\n throw new Error(\n \"Identity is not a supported extrapolation type for interpolatePaths()\"\n );\n default:\n exhaustiveCheck(extrapolation.extrapolateRight);\n }\n }\n let i = 0;\n for (; i <= input.length - 1; i++) {\n if (value >= input[i] && value <= input[i + 1]) {\n break;\n }\n }\n return lerp(\n value,\n input[i],\n input[i + 1],\n outputRange[i],\n outputRange[i + 1]\n );\n};\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "setup-skia-web": "scripts/setup-canvaskit.js"
9
9
  },
10
10
  "title": "React Native Skia",
11
- "version": "2.13.0",
11
+ "version": "2.13.1",
12
12
  "description": "High-performance React Native Graphics using Skia",
13
13
  "main": "lib/module/index.js",
14
14
  "react-native": "src/index.ts",
@@ -12,6 +12,12 @@ const lerp = (
12
12
  p2: SkPath
13
13
  ) => {
14
14
  "worklet";
15
+ // Zero-width segment (duplicate input stops): t would be NaN or Infinity.
16
+ // Return the segment's end path (p2), i.e. the value jumps to p2 at the stop.
17
+ // reanimated's interpolate has no explicit guard here, so this is our choice.
18
+ if (to === from) {
19
+ return p2;
20
+ }
15
21
  const t = (value - from) / (to - from);
16
22
  // interpolate returns a new path (immutable operation)
17
23
  return p2.interpolate(p1, t)!;
@@ -41,12 +47,25 @@ export const interpolatePaths = (
41
47
  _output?: SkPath
42
48
  ) => {
43
49
  "worklet";
50
+ if (input.length < 2 || input.length !== outputRange.length) {
51
+ throw new Error(
52
+ `interpolatePaths() requires input and outputRange to have the same length and at least 2 entries, received ${input.length} and ${outputRange.length}`
53
+ );
54
+ }
55
+ if (Number.isNaN(value)) {
56
+ throw new Error("interpolatePaths() received NaN as value");
57
+ }
44
58
  const extrapolation = validateInterpolationOptions(options);
45
59
  if (value < input[0]) {
46
60
  switch (extrapolation.extrapolateLeft) {
47
61
  case Extrapolate.CLAMP:
48
62
  return outputRange[0];
49
63
  case Extrapolate.EXTEND:
64
+ if (!Number.isFinite(value)) {
65
+ throw new Error(
66
+ "interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead"
67
+ );
68
+ }
50
69
  return lerp(value, input[0], input[1], outputRange[0], outputRange[1]);
51
70
  case Extrapolate.IDENTITY:
52
71
  throw new Error(
@@ -60,6 +79,11 @@ export const interpolatePaths = (
60
79
  case Extrapolate.CLAMP:
61
80
  return outputRange[outputRange.length - 1];
62
81
  case Extrapolate.EXTEND:
82
+ if (!Number.isFinite(value)) {
83
+ throw new Error(
84
+ "interpolatePaths() cannot extend with an infinite value, use clamp extrapolation instead"
85
+ );
86
+ }
63
87
  return lerp(
64
88
  value,
65
89
  input[input.length - 2],