@shopify/react-native-skia 2.6.8 → 2.7.0

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.
Files changed (146) hide show
  1. package/android/cpp/jni/include/JniSkiaBaseView.h +6 -4
  2. package/android/cpp/jni/include/JniSkiaPictureView.h +8 -6
  3. package/android/cpp/rnskia-android/OpenGLContext.h +20 -5
  4. package/android/cpp/rnskia-android/RNSkAndroidView.h +8 -8
  5. package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp +9 -5
  6. package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h +4 -2
  7. package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java +48 -12
  8. package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseViewManager.java +5 -0
  9. package/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureView.java +2 -2
  10. package/android/src/main/java/com/shopify/reactnative/skia/ViewScreenshotService.java +11 -7
  11. package/apple/MetalContext.h +7 -6
  12. package/apple/MetalLayerColorSpace.mm +39 -0
  13. package/apple/MetalLayerColorSpaceUtils.h +34 -0
  14. package/apple/MetalWindowContext.h +3 -1
  15. package/apple/MetalWindowContext.mm +9 -8
  16. package/apple/RNSkAppleView.h +6 -0
  17. package/apple/RNSkMetalCanvasProvider.h +2 -0
  18. package/apple/RNSkMetalCanvasProvider.mm +15 -2
  19. package/apple/SkiaCVPixelBufferUtils.mm +10 -0
  20. package/apple/SkiaPictureView.mm +1 -0
  21. package/apple/SkiaPictureViewManager.mm +5 -0
  22. package/apple/SkiaUIView.h +1 -0
  23. package/apple/SkiaUIView.mm +9 -0
  24. package/cpp/api/JsiSkSurface.h +8 -2
  25. package/cpp/rnskia/RNDawnContext.h +4 -3
  26. package/cpp/rnskia/RNDawnUtils.h +25 -6
  27. package/cpp/rnskia/RNDawnWindowContext.h +44 -10
  28. package/cpp/rnskia/RNMetalLayerColorSpace.h +16 -0
  29. package/cpp/rnskia/RNSkJsiViewApi.h +46 -24
  30. package/cpp/rnwgpu/SurfaceRegistry.h +10 -4
  31. package/lib/commonjs/Platform/Platform.web.js +2 -90
  32. package/lib/commonjs/Platform/Platform.web.js.map +1 -1
  33. package/lib/commonjs/mock/index.js +1 -0
  34. package/lib/commonjs/mock/index.js.map +1 -1
  35. package/lib/commonjs/renderer/Canvas.d.ts +10 -1
  36. package/lib/commonjs/renderer/Canvas.js +2 -0
  37. package/lib/commonjs/renderer/Canvas.js.map +1 -1
  38. package/lib/commonjs/skia/types/Surface/Surface.d.ts +5 -1
  39. package/lib/commonjs/skia/types/Surface/Surface.js.map +1 -1
  40. package/lib/commonjs/skia/web/JsiSkParagraphStyle.js +14 -3
  41. package/lib/commonjs/skia/web/JsiSkParagraphStyle.js.map +1 -1
  42. package/lib/commonjs/skia/web/JsiSkSurface.d.ts +1 -1
  43. package/lib/commonjs/skia/web/JsiSkSurface.js +2 -1
  44. package/lib/commonjs/skia/web/JsiSkSurface.js.map +1 -1
  45. package/lib/commonjs/sksg/Container.native.d.ts +1 -0
  46. package/lib/commonjs/sksg/Container.native.js +11 -0
  47. package/lib/commonjs/sksg/Container.native.js.map +1 -1
  48. package/lib/commonjs/sksg/Container.web.d.ts +1 -0
  49. package/lib/commonjs/sksg/Container.web.js +11 -0
  50. package/lib/commonjs/sksg/Container.web.js.map +1 -1
  51. package/lib/commonjs/specs/NativeSkiaModule.web.d.ts +2 -0
  52. package/lib/commonjs/specs/NativeSkiaModule.web.js +32 -7
  53. package/lib/commonjs/specs/NativeSkiaModule.web.js.map +1 -1
  54. package/lib/commonjs/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
  55. package/lib/commonjs/specs/SkiaPictureViewNativeComponent.js.map +1 -1
  56. package/lib/commonjs/views/SkiaPictureView.js +2 -0
  57. package/lib/commonjs/views/SkiaPictureView.js.map +1 -1
  58. package/lib/commonjs/views/SkiaPictureView.web.js +279 -94
  59. package/lib/commonjs/views/SkiaPictureView.web.js.map +1 -1
  60. package/lib/commonjs/views/formats.d.ts +1 -0
  61. package/lib/commonjs/views/formats.js +28 -0
  62. package/lib/commonjs/views/formats.js.map +1 -0
  63. package/lib/commonjs/views/index.d.ts +1 -0
  64. package/lib/commonjs/views/index.js +11 -0
  65. package/lib/commonjs/views/index.js.map +1 -1
  66. package/lib/commonjs/views/types.d.ts +6 -0
  67. package/lib/commonjs/views/types.js.map +1 -1
  68. package/lib/module/Platform/Platform.web.js +3 -91
  69. package/lib/module/Platform/Platform.web.js.map +1 -1
  70. package/lib/module/mock/index.js +1 -0
  71. package/lib/module/mock/index.js.map +1 -1
  72. package/lib/module/renderer/Canvas.d.ts +10 -1
  73. package/lib/module/renderer/Canvas.js +2 -0
  74. package/lib/module/renderer/Canvas.js.map +1 -1
  75. package/lib/module/skia/types/Surface/Surface.d.ts +5 -1
  76. package/lib/module/skia/types/Surface/Surface.js.map +1 -1
  77. package/lib/module/skia/web/JsiSkParagraphStyle.js +14 -3
  78. package/lib/module/skia/web/JsiSkParagraphStyle.js.map +1 -1
  79. package/lib/module/skia/web/JsiSkSurface.d.ts +1 -1
  80. package/lib/module/skia/web/JsiSkSurface.js +2 -1
  81. package/lib/module/skia/web/JsiSkSurface.js.map +1 -1
  82. package/lib/module/sksg/Container.native.d.ts +1 -0
  83. package/lib/module/sksg/Container.native.js +11 -0
  84. package/lib/module/sksg/Container.native.js.map +1 -1
  85. package/lib/module/sksg/Container.web.d.ts +1 -0
  86. package/lib/module/sksg/Container.web.js +11 -0
  87. package/lib/module/sksg/Container.web.js.map +1 -1
  88. package/lib/module/specs/NativeSkiaModule.web.d.ts +2 -0
  89. package/lib/module/specs/NativeSkiaModule.web.js +32 -7
  90. package/lib/module/specs/NativeSkiaModule.web.js.map +1 -1
  91. package/lib/module/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
  92. package/lib/module/specs/SkiaPictureViewNativeComponent.js.map +1 -1
  93. package/lib/module/views/SkiaPictureView.js +2 -0
  94. package/lib/module/views/SkiaPictureView.js.map +1 -1
  95. package/lib/module/views/SkiaPictureView.web.js +280 -95
  96. package/lib/module/views/SkiaPictureView.web.js.map +1 -1
  97. package/lib/module/views/formats.d.ts +1 -0
  98. package/lib/module/views/formats.js +22 -0
  99. package/lib/module/views/formats.js.map +1 -0
  100. package/lib/module/views/index.d.ts +1 -0
  101. package/lib/module/views/index.js +1 -0
  102. package/lib/module/views/index.js.map +1 -1
  103. package/lib/module/views/types.d.ts +6 -0
  104. package/lib/module/views/types.js.map +1 -1
  105. package/lib/typescript/lib/commonjs/Platform/Platform.web.d.ts +1 -2
  106. package/lib/typescript/lib/commonjs/mock/index.d.ts +1 -0
  107. package/lib/typescript/lib/commonjs/renderer/Canvas.d.ts +2 -1
  108. package/lib/typescript/lib/commonjs/skia/web/JsiSkSurface.d.ts +1 -1
  109. package/lib/typescript/lib/commonjs/sksg/Container.native.d.ts +1 -1
  110. package/lib/typescript/lib/commonjs/sksg/Container.web.d.ts +1 -1
  111. package/lib/typescript/lib/commonjs/views/formats.d.ts +2 -0
  112. package/lib/typescript/lib/module/Platform/Platform.web.d.ts +1 -3
  113. package/lib/typescript/lib/module/mock/index.d.ts +2 -2
  114. package/lib/typescript/lib/module/renderer/Canvas.d.ts +2 -1
  115. package/lib/typescript/lib/module/skia/web/JsiSkSurface.d.ts +1 -1
  116. package/lib/typescript/lib/module/sksg/Container.native.d.ts +1 -1
  117. package/lib/typescript/lib/module/sksg/Container.web.d.ts +1 -1
  118. package/lib/typescript/lib/module/views/formats.d.ts +1 -0
  119. package/lib/typescript/lib/module/views/index.d.ts +1 -0
  120. package/lib/typescript/src/renderer/Canvas.d.ts +10 -1
  121. package/lib/typescript/src/skia/types/Surface/Surface.d.ts +5 -1
  122. package/lib/typescript/src/skia/web/JsiSkSurface.d.ts +1 -1
  123. package/lib/typescript/src/sksg/Container.native.d.ts +1 -0
  124. package/lib/typescript/src/sksg/Container.web.d.ts +1 -0
  125. package/lib/typescript/src/specs/NativeSkiaModule.web.d.ts +2 -0
  126. package/lib/typescript/src/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
  127. package/lib/typescript/src/views/formats.d.ts +1 -0
  128. package/lib/typescript/src/views/index.d.ts +1 -0
  129. package/lib/typescript/src/views/types.d.ts +6 -0
  130. package/package.json +4 -1
  131. package/react-native-skia.podspec +1 -0
  132. package/src/Platform/Platform.web.tsx +7 -99
  133. package/src/mock/index.ts +1 -0
  134. package/src/renderer/Canvas.tsx +11 -0
  135. package/src/skia/types/Surface/Surface.ts +5 -1
  136. package/src/skia/web/JsiSkParagraphStyle.ts +16 -2
  137. package/src/skia/web/JsiSkSurface.ts +2 -1
  138. package/src/sksg/Container.native.ts +12 -0
  139. package/src/sksg/Container.web.ts +12 -0
  140. package/src/specs/NativeSkiaModule.web.ts +39 -7
  141. package/src/specs/SkiaPictureViewNativeComponent.ts +1 -0
  142. package/src/views/SkiaPictureView.tsx +2 -0
  143. package/src/views/SkiaPictureView.web.tsx +318 -106
  144. package/src/views/formats.ts +21 -0
  145. package/src/views/index.ts +1 -0
  146. package/src/views/types.ts +7 -0
@@ -1,9 +1,8 @@
1
- function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
1
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
3
2
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
3
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
- /* global HTMLCanvasElement */
6
- import React, { useRef, useEffect, useCallback, useImperativeHandle } from "react";
4
+ /* global HTMLCanvasElement, ResizeObserver, MediaQueryList */
5
+ import React, { useRef, useEffect, useCallback, useImperativeHandle, useLayoutEffect } from "react";
7
6
  import { JsiSkSurface } from "../skia/web/JsiSkSurface";
8
7
  import { Platform } from "../Platform";
9
8
  import { SkiaViewNativeId } from "./SkiaViewNativeId";
@@ -19,10 +18,26 @@ const dp2Pixel = (pd, rect) => {
19
18
  };
20
19
  };
21
20
  class WebGLRenderer {
22
- constructor(canvas, pd) {
21
+ constructor(canvas) {
23
22
  this.canvas = canvas;
24
- this.pd = pd;
25
23
  _defineProperty(this, "surface", null);
24
+ _defineProperty(this, "grContext", null);
25
+ _defineProperty(this, "contextHandle", 0);
26
+ _defineProperty(this, "pd", 1);
27
+ this.contextHandle = CanvasKit.GetWebGLContext(canvas);
28
+ if (!this.contextHandle) {
29
+ throw new Error("Could not create a WebGL context");
30
+ }
31
+ this.grContext = CanvasKit.MakeWebGLContext(this.contextHandle);
32
+ if (!this.grContext) {
33
+ CanvasKit.deleteContext(this.contextHandle);
34
+ this.contextHandle = 0;
35
+ throw new Error("Could not create a graphics context");
36
+ }
37
+ const ctx = canvas.getContext("webgl2");
38
+ if (ctx) {
39
+ ctx.drawingBufferColorSpace = "display-p3";
40
+ }
26
41
  this.onResize();
27
42
  }
28
43
  makeImageSnapshot(picture, rect) {
@@ -36,17 +51,26 @@ class WebGLRenderer {
36
51
  return this.surface.makeImageSnapshot(dp2Pixel(this.pd, rect));
37
52
  }
38
53
  onResize() {
54
+ var _this$surface;
39
55
  const {
40
- canvas,
41
- pd
56
+ canvas
42
57
  } = this;
43
- canvas.width = canvas.clientWidth * pd;
44
- canvas.height = canvas.clientHeight * pd;
45
- const surface = CanvasKit.MakeWebGLCanvasSurface(canvas);
46
- const ctx = canvas.getContext("webgl2");
47
- if (ctx) {
48
- ctx.drawingBufferColorSpace = "display-p3";
58
+ if (!this.grContext) {
59
+ return;
60
+ }
61
+ this.pd = window.devicePixelRatio;
62
+ canvas.width = canvas.clientWidth * this.pd;
63
+ canvas.height = canvas.clientHeight * this.pd;
64
+ (_this$surface = this.surface) === null || _this$surface === void 0 || _this$surface.ref.delete();
65
+ this.surface = null;
66
+ if (canvas.width === 0 || canvas.height === 0) {
67
+ // The canvas hasn't been laid out yet (or is hidden). The view's
68
+ // ResizeObserver calls us again as soon as it has a size.
69
+ return;
49
70
  }
71
+ // Reuse the existing WebGL context and GrDirectContext: only the surface
72
+ // needs to be recreated when the canvas is resized.
73
+ const surface = CanvasKit.MakeOnScreenGLSurface(this.grContext, canvas.width, canvas.height, CanvasKit.ColorSpace.SRGB);
50
74
  if (!surface) {
51
75
  throw new Error("Could not create surface");
52
76
  }
@@ -57,39 +81,68 @@ class WebGLRenderer {
57
81
  const canvas = this.surface.getCanvas();
58
82
  canvas.clear(Float32Array.of(0, 0, 0, 0));
59
83
  canvas.save();
60
- canvas.scale(pd, pd);
84
+ canvas.scale(this.pd, this.pd);
61
85
  canvas.drawPicture(picture);
62
86
  canvas.restore();
63
87
  this.surface.ref.flush();
64
88
  }
65
89
  }
66
90
  dispose() {
67
- if (this.surface) {
68
- var _this$canvas;
69
- (_this$canvas = this.canvas) === null || _this$canvas === void 0 || (_this$canvas = _this$canvas.getContext("webgl2")) === null || _this$canvas === void 0 || (_this$canvas = _this$canvas.getExtension("WEBGL_lose_context")) === null || _this$canvas === void 0 || _this$canvas.loseContext();
70
- this.surface.ref.delete();
71
- this.surface = null;
91
+ var _this$surface2, _this$canvas;
92
+ (_this$surface2 = this.surface) === null || _this$surface2 === void 0 || _this$surface2.ref.delete();
93
+ this.surface = null;
94
+ if (this.grContext) {
95
+ this.grContext.releaseResourcesAndAbandonContext();
96
+ this.grContext.delete();
97
+ this.grContext = null;
98
+ }
99
+ (_this$canvas = this.canvas) === null || _this$canvas === void 0 || (_this$canvas = _this$canvas.getContext("webgl2")) === null || _this$canvas === void 0 || (_this$canvas = _this$canvas.getExtension("WEBGL_lose_context")) === null || _this$canvas === void 0 || _this$canvas.loseContext();
100
+ if (this.contextHandle) {
101
+ // Unregister the context from CanvasKit's internal registry, otherwise
102
+ // it retains the canvas element (and its detached DOM tree) forever.
103
+ CanvasKit.deleteContext(this.contextHandle);
104
+ // Making the now-deleted handle current clears CanvasKit's
105
+ // current-context globals (GLctx/Module.ctx), which would otherwise
106
+ // keep referencing the context (and the canvas) until another surface
107
+ // becomes current. With a deleted handle this is a no-op that returns
108
+ // null without creating anything.
109
+ CanvasKit.MakeWebGLContext(this.contextHandle);
110
+ this.contextHandle = 0;
72
111
  }
73
112
  }
74
113
  }
75
114
  class StaticWebGLRenderer {
76
- constructor(canvas, pd) {
115
+ constructor(canvas) {
77
116
  this.canvas = canvas;
78
- this.pd = pd;
79
117
  _defineProperty(this, "cachedImage", null);
118
+ _defineProperty(this, "pd", 1);
80
119
  }
81
120
  onResize() {
82
121
  this.cachedImage = null;
83
122
  }
84
123
  renderPictureToSurface(picture) {
124
+ this.pd = window.devicePixelRatio;
125
+ if (this.canvas.clientWidth === 0 || this.canvas.clientHeight === 0) {
126
+ return null;
127
+ }
85
128
  const tempCanvas = new OffscreenCanvas(this.canvas.clientWidth * this.pd, this.canvas.clientHeight * this.pd);
86
129
  let surface = null;
130
+ let grContext = null;
131
+ let contextHandle = 0;
87
132
  try {
88
- const webglSurface = CanvasKit.MakeWebGLCanvasSurface(tempCanvas);
133
+ contextHandle = CanvasKit.GetWebGLContext(tempCanvas);
134
+ if (!contextHandle) {
135
+ throw new Error("Could not create a WebGL context");
136
+ }
137
+ grContext = CanvasKit.MakeWebGLContext(contextHandle);
138
+ if (!grContext) {
139
+ throw new Error("Could not create a graphics context");
140
+ }
89
141
  const ctx = tempCanvas.getContext("webgl2");
90
142
  if (ctx) {
91
143
  ctx.drawingBufferColorSpace = "display-p3";
92
144
  }
145
+ const webglSurface = CanvasKit.MakeOnScreenGLSurface(grContext, tempCanvas.width, tempCanvas.height, CanvasKit.ColorSpace.SRGB);
93
146
  if (!webglSurface) {
94
147
  throw new Error("Could not create WebGL surface");
95
148
  }
@@ -103,23 +156,34 @@ class StaticWebGLRenderer {
103
156
  surface.ref.flush();
104
157
  return {
105
158
  surface,
106
- tempCanvas
159
+ tempCanvas,
160
+ grContext,
161
+ contextHandle
107
162
  };
108
163
  } catch (error) {
109
- if (surface) {
110
- surface.ref.delete();
111
- }
112
- this.cleanupWebGLContext(tempCanvas);
164
+ this.cleanupRenderResult({
165
+ surface,
166
+ tempCanvas,
167
+ grContext,
168
+ contextHandle
169
+ });
113
170
  return null;
114
171
  }
115
172
  }
116
- cleanupWebGLContext(tempCanvas) {
117
- const ctx = tempCanvas.getContext("webgl2");
118
- if (ctx) {
119
- const loseContext = ctx.getExtension("WEBGL_lose_context");
120
- if (loseContext) {
121
- loseContext.loseContext();
122
- }
173
+ cleanupRenderResult(result) {
174
+ var _result$surface, _result$tempCanvas$ge;
175
+ (_result$surface = result.surface) === null || _result$surface === void 0 || _result$surface.ref.delete();
176
+ if (result.grContext) {
177
+ result.grContext.releaseResourcesAndAbandonContext();
178
+ result.grContext.delete();
179
+ }
180
+ (_result$tempCanvas$ge = result.tempCanvas.getContext("webgl2")) === null || _result$tempCanvas$ge === void 0 || (_result$tempCanvas$ge = _result$tempCanvas$ge.getExtension("WEBGL_lose_context")) === null || _result$tempCanvas$ge === void 0 || _result$tempCanvas$ge.loseContext();
181
+ if (result.contextHandle) {
182
+ // Unregister the context from CanvasKit's internal registry, otherwise
183
+ // it retains the OffscreenCanvas forever.
184
+ CanvasKit.deleteContext(result.contextHandle);
185
+ // Clear CanvasKit's current-context globals (see WebGLRenderer.dispose).
186
+ CanvasKit.MakeWebGLContext(result.contextHandle);
123
187
  }
124
188
  }
125
189
  draw(picture) {
@@ -132,6 +196,7 @@ class StaticWebGLRenderer {
132
196
  } = renderResult;
133
197
  const ctx2d = this.canvas.getContext("2d");
134
198
  if (!ctx2d) {
199
+ this.cleanupRenderResult(renderResult);
135
200
  throw new Error("Could not get 2D context");
136
201
  }
137
202
 
@@ -141,7 +206,7 @@ class StaticWebGLRenderer {
141
206
 
142
207
  // Draw the tempCanvas scaled down to the display size
143
208
  ctx2d.drawImage(tempCanvas, 0, 0, tempCanvas.width, tempCanvas.height, 0, 0, this.canvas.clientWidth * this.pd, this.canvas.clientHeight * this.pd);
144
- this.cleanupWebGLContext(tempCanvas);
209
+ this.cleanupRenderResult(renderResult);
145
210
  }
146
211
  makeImageSnapshot(picture, rect) {
147
212
  if (!this.cachedImage) {
@@ -149,17 +214,12 @@ class StaticWebGLRenderer {
149
214
  if (!renderResult) {
150
215
  return null;
151
216
  }
152
- const {
153
- surface,
154
- tempCanvas
155
- } = renderResult;
156
217
  try {
157
- this.cachedImage = surface.makeImageSnapshot(dp2Pixel(this.pd, rect));
218
+ this.cachedImage = renderResult.surface.makeImageSnapshot(dp2Pixel(this.pd, rect));
158
219
  } catch (error) {
159
220
  console.error("Error creating image snapshot:", error);
160
221
  } finally {
161
- surface.ref.delete();
162
- this.cleanupWebGLContext(tempCanvas);
222
+ this.cleanupRenderResult(renderResult);
163
223
  }
164
224
  }
165
225
  return this.cachedImage;
@@ -170,23 +230,87 @@ class StaticWebGLRenderer {
170
230
  this.cachedImage = null;
171
231
  }
172
232
  }
173
- const pd = Platform.PixelRatio;
233
+
234
+ // Mirrors the event react-native-web synthesizes for onLayout.
235
+ const makeLayoutEvent = (x, y, width, height) => ({
236
+ timeStamp: Date.now(),
237
+ nativeEvent: {
238
+ layout: {
239
+ x,
240
+ y,
241
+ width,
242
+ height
243
+ }
244
+ },
245
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
246
+ // @ts-expect-error
247
+ currentTarget: 0,
248
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
249
+ // @ts-expect-error
250
+ target: 0,
251
+ bubbles: false,
252
+ cancelable: false,
253
+ defaultPrevented: false,
254
+ eventPhase: 0,
255
+ isDefaultPrevented() {
256
+ throw new Error("Method not supported on web.");
257
+ },
258
+ isPropagationStopped() {
259
+ throw new Error("Method not supported on web.");
260
+ },
261
+ persist() {
262
+ throw new Error("Method not supported on web.");
263
+ },
264
+ preventDefault() {
265
+ throw new Error("Method not supported on web.");
266
+ },
267
+ stopPropagation() {
268
+ throw new Error("Method not supported on web.");
269
+ },
270
+ isTrusted: true,
271
+ type: ""
272
+ });
174
273
  export const SkiaPictureView = props => {
175
274
  const {
176
- ref
177
- } = props;
178
- const canvasRef = useRef(null);
179
- const renderer = useRef(null);
180
- const redrawRequestsRef = useRef(0);
181
- const requestIdRef = useRef(0);
182
- const pictureRef = useRef(null);
183
- const {
275
+ ref,
184
276
  picture,
185
277
  onLayout
186
278
  } = props;
187
- const redraw = useCallback(() => {
188
- redrawRequestsRef.current++;
279
+ const canvasRef = useRef(null);
280
+ const rendererRef = useRef(null);
281
+ const pictureRef = useRef(null);
282
+ // The rendering lifecycle deliberately does not depend on layout events:
283
+ // - The renderer is created synchronously on mount (the canvas element is
284
+ // guaranteed to exist in useLayoutEffect; a zero size at that point is
285
+ // fine, the surface is created once the canvas is measurable).
286
+ // - Redraw requests coalesce into a single animation frame. A picture
287
+ // dispatched before the canvas has a size stays in pictureRef (drawing
288
+ // while unmeasured is a no-op) and is painted by the resize path below
289
+ // once the canvas becomes measurable, so it is never lost.
290
+ // - A ResizeObserver on the canvas itself recreates the surface and repaints
291
+ // synchronously (its callbacks run after layout, before paint), and is
292
+ // also the source of the user-facing onLayout event.
293
+ const redrawPendingRef = useRef(false);
294
+ const frameRef = useRef(0);
295
+ const onLayoutRef = useRef(onLayout);
296
+ useLayoutEffect(() => {
297
+ onLayoutRef.current = onLayout;
298
+ }, [onLayout]);
299
+ const flushRedraw = useCallback(() => {
300
+ frameRef.current = 0;
301
+ if (redrawPendingRef.current && rendererRef.current && pictureRef.current) {
302
+ redrawPendingRef.current = false;
303
+ rendererRef.current.draw(pictureRef.current);
304
+ }
305
+ // If the renderer or picture isn't available yet, the request stays
306
+ // pending and is flushed by whichever arrives last.
189
307
  }, []);
308
+ const redraw = useCallback(() => {
309
+ redrawPendingRef.current = true;
310
+ if (frameRef.current === 0) {
311
+ frameRef.current = requestAnimationFrame(flushRedraw);
312
+ }
313
+ }, [flushRedraw]);
190
314
  const getSize = useCallback(() => {
191
315
  var _canvasRef$current, _canvasRef$current2;
192
316
  return {
@@ -199,8 +323,8 @@ export const SkiaPictureView = props => {
199
323
  redraw();
200
324
  }, [redraw]);
201
325
  const makeImageSnapshot = useCallback(rect => {
202
- if (renderer.current && pictureRef.current) {
203
- return renderer.current.makeImageSnapshot(pictureRef.current, rect);
326
+ if (rendererRef.current && pictureRef.current) {
327
+ return rendererRef.current.makeImageSnapshot(pictureRef.current, rect);
204
328
  }
205
329
  return null;
206
330
  }, []);
@@ -233,27 +357,98 @@ export const SkiaPictureView = props => {
233
357
  callback(x, y, rect.width, rect.height);
234
358
  }
235
359
  }, []);
236
- const tick = useCallback(() => {
237
- if (redrawRequestsRef.current > 0) {
238
- redrawRequestsRef.current = 0;
239
- if (renderer.current && pictureRef.current) {
240
- renderer.current.draw(pictureRef.current);
241
- }
242
- }
243
- requestIdRef.current = requestAnimationFrame(tick);
244
- }, []);
245
- const onLayoutEvent = useCallback(evt => {
360
+ const isStatic = props.__destroyWebGLContextAfterRender === true;
361
+ useLayoutEffect(() => {
246
362
  const canvas = canvasRef.current;
247
- if (canvas) {
248
- renderer.current = props.__destroyWebGLContextAfterRender === true ? new StaticWebGLRenderer(canvas, pd) : new WebGLRenderer(canvas, pd);
363
+ if (!canvas) {
364
+ return undefined;
365
+ }
366
+ const renderer = isStatic ? new StaticWebGLRenderer(canvas) : new WebGLRenderer(canvas);
367
+ rendererRef.current = renderer;
368
+ const drawPicture = () => {
249
369
  if (pictureRef.current) {
250
- renderer.current.draw(pictureRef.current);
370
+ redrawPendingRef.current = false;
371
+ renderer.draw(pictureRef.current);
251
372
  }
252
- }
253
- if (onLayout) {
254
- onLayout(evt);
255
- }
256
- }, [onLayout, props.__destroyWebGLContextAfterRender]);
373
+ };
374
+
375
+ // The renderer constructor already sized the surface from the current
376
+ // layout, so the observer's initial delivery only repaints if the size
377
+ // changed in between.
378
+ let lastWidth = canvas.clientWidth;
379
+ let lastHeight = canvas.clientHeight;
380
+ let lastPixelDensity = window.devicePixelRatio;
381
+ const resizeIfNeeded = () => {
382
+ const width = canvas.clientWidth;
383
+ const height = canvas.clientHeight;
384
+ const pixelDensity = window.devicePixelRatio;
385
+ if (width === lastWidth && height === lastHeight && pixelDensity === lastPixelDensity) {
386
+ return;
387
+ }
388
+ lastWidth = width;
389
+ lastHeight = height;
390
+ lastPixelDensity = pixelDensity;
391
+ renderer.onResize();
392
+ drawPicture();
393
+ };
394
+ const observer = new ResizeObserver(entries => {
395
+ resizeIfNeeded();
396
+ const layoutHandler = onLayoutRef.current;
397
+ if (layoutHandler) {
398
+ const {
399
+ left,
400
+ top,
401
+ width,
402
+ height
403
+ } = entries[0].contentRect;
404
+ // setTimeout 0 is taken from react-native-web (UIManager)
405
+ setTimeout(() => layoutHandler(makeLayoutEvent(left, top, width, height)), 0);
406
+ }
407
+ });
408
+ observer.observe(canvas);
409
+
410
+ // A pixel-density change with no CSS size change (browser zoom, moving
411
+ // the window to another display) doesn't trigger the ResizeObserver:
412
+ // watch it via matchMedia, re-arming the query for each new density.
413
+ // This degrades gracefully where unsupported: without matchMedia or
414
+ // MediaQueryList.addEventListener (older Safari), or where the query
415
+ // never matches because the resolution feature is unknown (Safari < 16),
416
+ // density-only changes simply don't repaint — everything else still does.
417
+ let media = null;
418
+ const onPixelDensityChange = () => {
419
+ resizeIfNeeded();
420
+ watchPixelDensity();
421
+ };
422
+ const watchPixelDensity = () => {
423
+ var _media;
424
+ (_media = media) === null || _media === void 0 || _media.removeEventListener("change", onPixelDensityChange);
425
+ media = null;
426
+ if (typeof window.matchMedia === "function") {
427
+ const query = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
428
+ if (typeof query.addEventListener === "function") {
429
+ media = query;
430
+ media.addEventListener("change", onPixelDensityChange);
431
+ }
432
+ }
433
+ };
434
+ watchPixelDensity();
435
+
436
+ // Paint any picture that was dispatched before the renderer existed.
437
+ drawPicture();
438
+ return () => {
439
+ var _media2;
440
+ observer.disconnect();
441
+ (_media2 = media) === null || _media2 === void 0 || _media2.removeEventListener("change", onPixelDensityChange);
442
+ rendererRef.current = null;
443
+ renderer.dispose();
444
+ };
445
+ }, [isStatic]);
446
+ useEffect(() => {
447
+ return () => {
448
+ cancelAnimationFrame(frameRef.current);
449
+ frameRef.current = 0;
450
+ };
451
+ }, []);
257
452
  useImperativeHandle(ref, () => ({
258
453
  setPicture,
259
454
  getSize,
@@ -268,7 +463,8 @@ export const SkiaPictureView = props => {
268
463
  useEffect(() => {
269
464
  var _props$nativeID;
270
465
  const nativeID = (_props$nativeID = props.nativeID) !== null && _props$nativeID !== void 0 ? _props$nativeID : `${SkiaViewNativeId.current++}`;
271
- global.SkiaViewApi.registerView(nativeID, {
466
+ const api = global.SkiaViewApi;
467
+ api.registerView(nativeID, {
272
468
  setPicture,
273
469
  getSize,
274
470
  redraw,
@@ -276,35 +472,24 @@ export const SkiaPictureView = props => {
276
472
  measure,
277
473
  measureInWindow
278
474
  });
279
- }, [setPicture, getSize, redraw, makeImageSnapshot, measure, measureInWindow, props.nativeID]);
280
- useEffect(() => {
281
- if (props.picture) {
282
- setPicture(props.picture);
283
- }
284
- }, [setPicture, props.picture]);
285
- useEffect(() => {
286
- tick();
287
475
  return () => {
288
- cancelAnimationFrame(requestIdRef.current);
289
- if (renderer.current) {
290
- renderer.current.dispose();
291
- renderer.current = null;
292
- }
476
+ api.unregisterView(nativeID);
293
477
  };
294
- }, [tick]);
478
+ }, [setPicture, getSize, redraw, makeImageSnapshot, measure, measureInWindow, props.nativeID]);
295
479
  useEffect(() => {
296
- if (renderer.current && pictureRef.current) {
297
- renderer.current.draw(pictureRef.current);
480
+ if (picture) {
481
+ setPicture(picture);
298
482
  }
299
- }, [picture, redraw]);
483
+ }, [setPicture, picture]);
300
484
  const {
301
- debug = false,
485
+ debug: _debug,
302
486
  ref: _ref,
487
+ onLayout: _onLayout,
488
+ picture: _picture,
489
+ __destroyWebGLContextAfterRender: _isStatic,
303
490
  ...viewProps
304
491
  } = props;
305
- return /*#__PURE__*/React.createElement(Platform.View, _extends({}, viewProps, {
306
- onLayout: onLayoutEvent
307
- }), /*#__PURE__*/React.createElement("canvas", {
492
+ return /*#__PURE__*/React.createElement(Platform.View, viewProps, /*#__PURE__*/React.createElement("canvas", {
308
493
  ref: canvasRef,
309
494
  style: {
310
495
  display: "block",