@shopify/react-native-skia 2.7.0 → 2.9.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 (157) hide show
  1. package/android/build.gradle +10 -4
  2. package/android/cpp/rnskia-android/OpenGLContext.h +29 -3
  3. package/android/cpp/rnskia-android/OpenGLWindowContext.cpp +9 -2
  4. package/android/cpp/rnskia-android/gl/Context.h +3 -0
  5. package/android/cpp/rnskia-android/gl/Surface.h +1 -1
  6. package/cpp/api/JsiSkParagraph.h +114 -0
  7. package/cpp/api/JsiSkiaContext.h +4 -0
  8. package/cpp/rnskia/RNSkView.h +6 -0
  9. package/lib/commonjs/Platform/Platform.js +5 -2
  10. package/lib/commonjs/Platform/Platform.js.map +1 -1
  11. package/lib/commonjs/Platform/Platform.web.js +8 -7
  12. package/lib/commonjs/Platform/Platform.web.js.map +1 -1
  13. package/lib/commonjs/external/reanimated/useVideoLoading.js +11 -2
  14. package/lib/commonjs/external/reanimated/useVideoLoading.js.map +1 -1
  15. package/lib/commonjs/skia/core/Font.d.ts +21 -6
  16. package/lib/commonjs/skia/core/Font.js +24 -4
  17. package/lib/commonjs/skia/core/Font.js.map +1 -1
  18. package/lib/commonjs/skia/core/SVG.web.js +4 -5
  19. package/lib/commonjs/skia/core/SVG.web.js.map +1 -1
  20. package/lib/commonjs/skia/types/Data/Data.d.ts +5 -4
  21. package/lib/commonjs/skia/types/Data/Data.js +7 -1
  22. package/lib/commonjs/skia/types/Data/Data.js.map +1 -1
  23. package/lib/commonjs/skia/types/Paragraph/Paragraph.d.ts +62 -0
  24. package/lib/commonjs/skia/types/Paragraph/Paragraph.js.map +1 -1
  25. package/lib/commonjs/skia/web/Host.js +7 -0
  26. package/lib/commonjs/skia/web/Host.js.map +1 -1
  27. package/lib/commonjs/skia/web/JsiSkFont.d.ts +1 -1
  28. package/lib/commonjs/skia/web/JsiSkFont.js +43 -2
  29. package/lib/commonjs/skia/web/JsiSkFont.js.map +1 -1
  30. package/lib/commonjs/skia/web/JsiSkPaint.js +5 -0
  31. package/lib/commonjs/skia/web/JsiSkPaint.js.map +1 -1
  32. package/lib/commonjs/skia/web/JsiSkParagraph.d.ts +3 -1
  33. package/lib/commonjs/skia/web/JsiSkParagraph.js +6 -0
  34. package/lib/commonjs/skia/web/JsiSkParagraph.js.map +1 -1
  35. package/lib/commonjs/sksg/Container.web.js +34 -3
  36. package/lib/commonjs/sksg/Container.web.js.map +1 -1
  37. package/lib/commonjs/sksg/Recorder/DrawingContext.d.ts +2 -0
  38. package/lib/commonjs/sksg/Recorder/DrawingContext.js +22 -6
  39. package/lib/commonjs/sksg/Recorder/DrawingContext.js.map +1 -1
  40. package/lib/commonjs/sksg/Recorder/FrameScope.d.ts +18 -0
  41. package/lib/commonjs/sksg/Recorder/FrameScope.js +66 -0
  42. package/lib/commonjs/sksg/Recorder/FrameScope.js.map +1 -0
  43. package/lib/commonjs/sksg/Recorder/FrameScope.native.d.ts +10 -0
  44. package/lib/commonjs/sksg/Recorder/FrameScope.native.js +26 -0
  45. package/lib/commonjs/sksg/Recorder/FrameScope.native.js.map +1 -0
  46. package/lib/commonjs/sksg/Recorder/Player.js +1 -2
  47. package/lib/commonjs/sksg/Recorder/Player.js.map +1 -1
  48. package/lib/commonjs/sksg/Recorder/Recorder.d.ts +3 -1
  49. package/lib/commonjs/sksg/Recorder/Recorder.js +12 -1
  50. package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
  51. package/lib/commonjs/sksg/Recorder/commands/Drawing.js +3 -3
  52. package/lib/commonjs/sksg/Recorder/commands/Drawing.js.map +1 -1
  53. package/lib/commonjs/sksg/Recorder/commands/Shaders.js +3 -3
  54. package/lib/commonjs/sksg/Recorder/commands/Shaders.js.map +1 -1
  55. package/lib/commonjs/sksg/StaticContainer.d.ts +1 -0
  56. package/lib/commonjs/sksg/StaticContainer.js +27 -1
  57. package/lib/commonjs/sksg/StaticContainer.js.map +1 -1
  58. package/lib/module/Platform/Platform.js +6 -3
  59. package/lib/module/Platform/Platform.js.map +1 -1
  60. package/lib/module/Platform/Platform.web.js +9 -8
  61. package/lib/module/Platform/Platform.web.js.map +1 -1
  62. package/lib/module/external/reanimated/useVideoLoading.js +12 -2
  63. package/lib/module/external/reanimated/useVideoLoading.js.map +1 -1
  64. package/lib/module/skia/core/Font.d.ts +21 -6
  65. package/lib/module/skia/core/Font.js +26 -5
  66. package/lib/module/skia/core/Font.js.map +1 -1
  67. package/lib/module/skia/core/SVG.web.js +4 -5
  68. package/lib/module/skia/core/SVG.web.js.map +1 -1
  69. package/lib/module/skia/types/Data/Data.d.ts +5 -4
  70. package/lib/module/skia/types/Data/Data.js +5 -0
  71. package/lib/module/skia/types/Data/Data.js.map +1 -1
  72. package/lib/module/skia/types/Paragraph/Paragraph.d.ts +62 -0
  73. package/lib/module/skia/types/Paragraph/Paragraph.js.map +1 -1
  74. package/lib/module/skia/web/Host.js +7 -0
  75. package/lib/module/skia/web/Host.js.map +1 -1
  76. package/lib/module/skia/web/JsiSkFont.d.ts +1 -1
  77. package/lib/module/skia/web/JsiSkFont.js +44 -3
  78. package/lib/module/skia/web/JsiSkFont.js.map +1 -1
  79. package/lib/module/skia/web/JsiSkPaint.js +5 -0
  80. package/lib/module/skia/web/JsiSkPaint.js.map +1 -1
  81. package/lib/module/skia/web/JsiSkParagraph.d.ts +3 -1
  82. package/lib/module/skia/web/JsiSkParagraph.js +7 -1
  83. package/lib/module/skia/web/JsiSkParagraph.js.map +1 -1
  84. package/lib/module/sksg/Container.web.js +35 -4
  85. package/lib/module/sksg/Container.web.js.map +1 -1
  86. package/lib/module/sksg/Recorder/DrawingContext.d.ts +2 -0
  87. package/lib/module/sksg/Recorder/DrawingContext.js +22 -6
  88. package/lib/module/sksg/Recorder/DrawingContext.js.map +1 -1
  89. package/lib/module/sksg/Recorder/FrameScope.d.ts +18 -0
  90. package/lib/module/sksg/Recorder/FrameScope.js +59 -0
  91. package/lib/module/sksg/Recorder/FrameScope.js.map +1 -0
  92. package/lib/module/sksg/Recorder/FrameScope.native.d.ts +10 -0
  93. package/lib/module/sksg/Recorder/FrameScope.native.js +19 -0
  94. package/lib/module/sksg/Recorder/FrameScope.native.js.map +1 -0
  95. package/lib/module/sksg/Recorder/Player.js +1 -2
  96. package/lib/module/sksg/Recorder/Player.js.map +1 -1
  97. package/lib/module/sksg/Recorder/Recorder.d.ts +3 -1
  98. package/lib/module/sksg/Recorder/Recorder.js +10 -0
  99. package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
  100. package/lib/module/sksg/Recorder/commands/Drawing.js +3 -3
  101. package/lib/module/sksg/Recorder/commands/Drawing.js.map +1 -1
  102. package/lib/module/sksg/Recorder/commands/Shaders.js +3 -3
  103. package/lib/module/sksg/Recorder/commands/Shaders.js.map +1 -1
  104. package/lib/module/sksg/StaticContainer.d.ts +1 -0
  105. package/lib/module/sksg/StaticContainer.js +28 -2
  106. package/lib/module/sksg/StaticContainer.js.map +1 -1
  107. package/lib/typescript/lib/commonjs/skia/types/Data/Data.d.ts +1 -0
  108. package/lib/typescript/lib/commonjs/skia/web/JsiSkFont.d.ts +1 -1
  109. package/lib/typescript/lib/commonjs/skia/web/JsiSkParagraph.d.ts +2 -0
  110. package/lib/typescript/lib/commonjs/sksg/Container.web.d.ts +2 -2
  111. package/lib/typescript/lib/commonjs/sksg/Recorder/DrawingContext.d.ts +2 -0
  112. package/lib/typescript/lib/commonjs/sksg/Recorder/FrameScope.d.ts +17 -0
  113. package/lib/typescript/lib/commonjs/sksg/Recorder/FrameScope.native.d.ts +13 -0
  114. package/lib/typescript/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -0
  115. package/lib/typescript/lib/commonjs/sksg/StaticContainer.d.ts +2 -2
  116. package/lib/typescript/lib/module/mock/index.d.ts +1 -0
  117. package/lib/typescript/lib/module/skia/types/Data/Data.d.ts +1 -0
  118. package/lib/typescript/lib/module/skia/web/JsiSkFont.d.ts +1 -1
  119. package/lib/typescript/lib/module/skia/web/JsiSkParagraph.d.ts +2 -0
  120. package/lib/typescript/lib/module/sksg/Container.web.d.ts +2 -2
  121. package/lib/typescript/lib/module/sksg/Recorder/DrawingContext.d.ts +2 -0
  122. package/lib/typescript/lib/module/sksg/Recorder/FrameScope.d.ts +5 -0
  123. package/lib/typescript/lib/module/sksg/Recorder/FrameScope.native.d.ts +5 -0
  124. package/lib/typescript/lib/module/sksg/Recorder/Recorder.d.ts +1 -0
  125. package/lib/typescript/lib/module/sksg/StaticContainer.d.ts +2 -2
  126. package/lib/typescript/src/skia/core/Font.d.ts +21 -6
  127. package/lib/typescript/src/skia/types/Data/Data.d.ts +5 -4
  128. package/lib/typescript/src/skia/types/Paragraph/Paragraph.d.ts +62 -0
  129. package/lib/typescript/src/skia/web/JsiSkFont.d.ts +1 -1
  130. package/lib/typescript/src/skia/web/JsiSkParagraph.d.ts +3 -1
  131. package/lib/typescript/src/sksg/Recorder/DrawingContext.d.ts +2 -0
  132. package/lib/typescript/src/sksg/Recorder/FrameScope.d.ts +18 -0
  133. package/lib/typescript/src/sksg/Recorder/FrameScope.native.d.ts +10 -0
  134. package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +3 -1
  135. package/lib/typescript/src/sksg/StaticContainer.d.ts +1 -0
  136. package/package.json +8 -2
  137. package/react-native-skia.podspec +18 -10
  138. package/src/Platform/Platform.ts +6 -7
  139. package/src/Platform/Platform.web.tsx +9 -8
  140. package/src/external/reanimated/useVideoLoading.ts +12 -2
  141. package/src/skia/core/Font.ts +34 -13
  142. package/src/skia/core/SVG.web.ts +5 -14
  143. package/src/skia/types/Data/Data.ts +14 -4
  144. package/src/skia/types/Paragraph/Paragraph.ts +67 -0
  145. package/src/skia/web/Host.ts +7 -0
  146. package/src/skia/web/JsiSkFont.ts +48 -4
  147. package/src/skia/web/JsiSkPaint.ts +5 -0
  148. package/src/skia/web/JsiSkParagraph.ts +9 -1
  149. package/src/sksg/Container.web.ts +31 -4
  150. package/src/sksg/Recorder/DrawingContext.ts +19 -6
  151. package/src/sksg/Recorder/FrameScope.native.ts +21 -0
  152. package/src/sksg/Recorder/FrameScope.ts +69 -0
  153. package/src/sksg/Recorder/Player.ts +1 -2
  154. package/src/sksg/Recorder/Recorder.ts +15 -1
  155. package/src/sksg/Recorder/commands/Drawing.ts +3 -3
  156. package/src/sksg/Recorder/commands/Shaders.ts +22 -16
  157. package/src/sksg/StaticContainer.ts +29 -2
@@ -1,6 +1,10 @@
1
1
  import type { SkCanvas } from "../Canvas";
2
+ import type { SkFont } from "../Font";
2
3
  import type { SkJSIInstance } from "../JsiInstance";
4
+ import type { SkPath } from "../Path";
5
+ import type { SkPoint } from "../Point";
3
6
  import type { SkRect } from "../Rect";
7
+ import type { SkSize } from "../Size";
4
8
  import type { TextDirection } from "./ParagraphStyle";
5
9
  export interface LineMetrics {
6
10
  /** The index in the text buffer the line begins. */
@@ -36,6 +40,42 @@ export interface SkRectWithDirection {
36
40
  rect: SkRect;
37
41
  direction: TextDirection;
38
42
  }
43
+ /**
44
+ * Information about a run of glyphs passed to the visitor of
45
+ * `SkParagraph.extendedVisit`. All glyphs in a run share the same font: for
46
+ * text shaped through font fallback, each fallback font produces its own
47
+ * run(s).
48
+ */
49
+ export interface ParagraphVisitorInfo {
50
+ /** The font used to shape this run (the resolved fallback font, if any). */
51
+ font: SkFont;
52
+ /** Origin of the run within the paragraph (baseline corrected). */
53
+ origin: SkPoint;
54
+ /** Advance of the run. */
55
+ advance: SkSize;
56
+ /** The glyph ids of the run. */
57
+ glyphs: number[];
58
+ /**
59
+ * The position of each glyph, relative to the run `origin`. Add `origin`
60
+ * to obtain paragraph coordinates: for a run shaped through font fallback,
61
+ * the run placement is carried by `origin`, not by the positions.
62
+ */
63
+ positions: SkPoint[];
64
+ /**
65
+ * The tight ink bounds of each glyph, relative to the glyph's origin.
66
+ * Combine with `positions` to compute the exact painted bounds of the run.
67
+ */
68
+ bounds: SkRect[];
69
+ /** The UTF-8 text index of the cluster each glyph belongs to. */
70
+ utf8Starts: number[];
71
+ /** Reserved flags (currently always 0). */
72
+ flags: number;
73
+ }
74
+ /**
75
+ * Visitor for `SkParagraph.extendedVisit`. Called once per run of glyphs;
76
+ * `info` is null to signal the end of a line.
77
+ */
78
+ export type ParagraphExtendedVisitor = (lineNumber: number, info: ParagraphVisitorInfo | null) => void;
39
79
  export interface SkParagraph extends SkJSIInstance<"Paragraph"> {
40
80
  /**
41
81
  * Calculates the position of the the glyphs in the paragraph
@@ -106,4 +146,26 @@ export interface SkParagraph extends SkJSIInstance<"Paragraph"> {
106
146
  * to the paragraph.
107
147
  */
108
148
  getRectsForPlaceholders(): SkRectWithDirection[];
149
+ /**
150
+ * Converts the glyphs of the given line into an SkPath, with all font
151
+ * fallbacks already applied. Use `computeTightBounds()` on the resulting
152
+ * path to measure the exact ink bounds of the rendered text (as opposed to
153
+ * the font-metrics based bounds returned by `getRectsForRange`).
154
+ * Note that color glyphs (e.g. emojis) cannot be converted to a path and
155
+ * are skipped. Returns null if the line number is out of bounds.
156
+ * This method requires the layout method to have been called first.
157
+ * Not implemented on React Native Web.
158
+ * @param lineNumber The line number (zero indexed, see `getLineMetrics`)
159
+ */
160
+ getPath(lineNumber: number): SkPath | null;
161
+ /**
162
+ * Visits the laid out paragraph, calling the visitor once for every run of
163
+ * glyphs with the resolved font, glyph ids, positions and per-glyph tight
164
+ * ink bounds. The visitor is called with a null info to signal the end of
165
+ * each line. This exposes the exact layout that would be painted.
166
+ * This method requires the layout method to have been called first.
167
+ * Not implemented on React Native Web.
168
+ * @param visitor Called once per glyph run, and once per line end
169
+ */
170
+ extendedVisit(visitor: ParagraphExtendedVisitor): void;
109
171
  }
@@ -5,7 +5,7 @@ import { JsiSkRect } from "./JsiSkRect";
5
5
  import { JsiSkTypeface } from "./JsiSkTypeface";
6
6
  export declare class JsiSkFont extends HostObject<Font, "Font"> implements SkFont {
7
7
  constructor(CanvasKit: CanvasKit, ref: Font);
8
- measureText(_text: string, _paint?: SkPaint | undefined): SkRect;
8
+ measureText(text: string, paint?: SkPaint | undefined): SkRect;
9
9
  getTextWidth(text: string, paint?: SkPaint | undefined): number;
10
10
  getMetrics(): {
11
11
  ascent: number;
@@ -1,5 +1,5 @@
1
1
  import type { CanvasKit, Paragraph } from "canvaskit-wasm";
2
- import type { SkRect, SkRectWithDirection, SkParagraph, LineMetrics } from "../types";
2
+ import type { SkRect, SkRectWithDirection, SkParagraph, SkPath, LineMetrics, ParagraphExtendedVisitor } from "../types";
3
3
  import { HostObject } from "./Host";
4
4
  import type { JsiSkCanvas } from "./JsiSkCanvas";
5
5
  export declare class JsiSkParagraph extends HostObject<Paragraph, "Paragraph"> implements SkParagraph {
@@ -15,4 +15,6 @@ export declare class JsiSkParagraph extends HostObject<Paragraph, "Paragraph"> i
15
15
  getRectsForPlaceholders(): SkRectWithDirection[];
16
16
  getRectsForRange(start: number, end: number): SkRect[];
17
17
  getLineMetrics(): LineMetrics[];
18
+ getPath(_lineNumber: number): SkPath | null;
19
+ extendedVisit(_visitor: ParagraphExtendedVisitor): void;
18
20
  }
@@ -2,6 +2,8 @@ import type { Skia, SkCanvas, SkColorFilter, SkPaint, SkShader, SkImageFilter, S
2
2
  export declare const createDrawingContext: (Skia: Skia, paintPool: SkPaint[], canvas: SkCanvas) => {
3
3
  Skia: Skia;
4
4
  canvas: SkCanvas;
5
+ track: <T>(value: T) => T;
6
+ dispose: () => void;
5
7
  paints: SkPaint[];
6
8
  colorFilters: SkColorFilter[];
7
9
  shaders: SkShader[];
@@ -0,0 +1,18 @@
1
+ import type { Skia } from "../../skia/types";
2
+ export interface FrameScope {
3
+ Skia: Skia;
4
+ track: <T>(value: T) => T;
5
+ dispose: () => void;
6
+ }
7
+ /**
8
+ * Wraps a Skia instance so that every object created through its factories is
9
+ * collected and deleted when the scope is disposed. The renderer only keeps
10
+ * the objects it creates alive for the duration of a frame: once the frame
11
+ * has been recorded they can be deleted. This is required on Web where
12
+ * CanvasKit objects live in WASM memory that the JS garbage collector does
13
+ * not perceive, so it (almost) never reclaims them on its own.
14
+ *
15
+ * Objects passed in via props are user-owned: they are not created through
16
+ * this facade and are therefore never tracked nor disposed.
17
+ */
18
+ export declare const createFrameScope: (Skia: Skia) => FrameScope;
@@ -0,0 +1,10 @@
1
+ import type { Skia } from "../../skia/types";
2
+ import type { FrameScope } from "./FrameScope";
3
+ /**
4
+ * No-op on native. The JSI wrappers report the size of the native objects
5
+ * they hold to the garbage collector (setExternalMemoryPressure), so the GC
6
+ * is aware of the real cost of the objects the renderer creates and reclaims
7
+ * them on its own. The tracking scope is only needed on Web, where CanvasKit
8
+ * objects live in WASM memory the GC cannot see (see FrameScope.ts).
9
+ */
10
+ export declare const createFrameScope: (Skia: Skia) => FrameScope;
@@ -2,12 +2,14 @@ import type { SharedValue } from "react-native-reanimated";
2
2
  import { NodeType } from "../../dom/types";
3
3
  import type { BlurMaskFilterProps, CircleProps, CTMProps, ImageProps, PaintProps, PointsProps, PathProps, RectProps, RoundedRectProps, OvalProps, LineProps, PatchProps, VerticesProps, DiffRectProps, TextProps, TextPathProps, TextBlobProps, GlyphsProps, PictureProps, ImageSVGProps, ParagraphProps, AtlasProps, BoxProps, BoxShadowProps, SkottieProps, DrawingNodeProps } from "../../dom/types";
4
4
  import type { AnimatedProps } from "../../renderer";
5
- import type { SkPaint, BaseRecorder } from "../../skia/types";
5
+ import type { SkPaint, SkPicture, BaseRecorder } from "../../skia/types";
6
6
  import type { Command } from "./Core";
7
7
  export interface Recording {
8
8
  commands: Command[];
9
9
  paintPool: SkPaint[];
10
+ lastPicture?: SkPicture | null;
10
11
  }
12
+ export declare const disposeRecording: (recording: Recording) => void;
11
13
  interface AnimationValues {
12
14
  animationValues: Set<SharedValue<unknown>>;
13
15
  }
@@ -18,5 +18,6 @@ export declare abstract class Container {
18
18
  export declare class StaticContainer extends Container {
19
19
  private nativeId;
20
20
  constructor(Skia: Skia, nativeId: number);
21
+ unmount(): void;
21
22
  redraw(): void;
22
23
  }
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.7.0",
11
+ "version": "2.9.0",
12
12
  "description": "High-performance React Native Graphics using Skia",
13
13
  "main": "lib/module/index.js",
14
14
  "react-native": "src/index.ts",
@@ -32,7 +32,8 @@
32
32
  "cpp/**/*.{h,cpp}",
33
33
  "apple/**",
34
34
  "react-native-skia.podspec",
35
- "dist/**"
35
+ "dist/**",
36
+ "libs/.graphite"
36
37
  ],
37
38
  "scripts": {
38
39
  "lint": "eslint . --ext .ts,.tsx --max-warnings 0 --cache --fix",
@@ -140,6 +141,11 @@
140
141
  "react-native-skia-apple-tvos": "150.0.0",
141
142
  "react-reconciler": "0.31.0"
142
143
  },
144
+ "graphiteDependencies": {
145
+ "react-native-skia-graphite-android": "150.0.0",
146
+ "react-native-skia-graphite-apple-ios": "150.0.0",
147
+ "react-native-skia-graphite-apple-macos": "150.0.0"
148
+ },
143
149
  "eslintIgnore": [
144
150
  "node_modules/",
145
151
  "lib/"
@@ -29,10 +29,8 @@ end
29
29
  # re-copied and CocoaPods picks up the change. This is best-effort: if `pod install`
30
30
  # does not detect the change, a clean reinstall fixes it (acceptable until the upcoming
31
31
  # Swift Package Manager migration).
32
- install_apple_skia_libs = lambda do |base_dir|
33
- { 'ios' => 'react-native-skia-apple-ios',
34
- 'macos' => 'react-native-skia-apple-macos',
35
- 'tvos' => 'react-native-skia-apple-tvos' }.each do |platform, pkg_name|
32
+ install_apple_skia_libs = lambda do |base_dir, packages|
33
+ packages.each do |platform, pkg_name|
36
34
  pkg_dir = resolve_node_package.call(pkg_name, base_dir)
37
35
  next if pkg_dir.nil?
38
36
 
@@ -54,9 +52,18 @@ install_apple_skia_libs = lambda do |base_dir|
54
52
  end
55
53
  end
56
54
 
57
- # Graphite downloads its binaries directly into libs/; only the default build needs
58
- # the npm packages copied in.
59
- install_apple_skia_libs.call(__dir__) unless use_graphite
55
+ # The default (Ganesh) build ships its binaries in the react-native-skia-apple-*
56
+ # npm packages, the Graphite build in react-native-skia-graphite-apple-* (no tvOS).
57
+ # During in-repo development install-skia-graphite downloads the binaries directly
58
+ # into libs/ and the graphite packages are absent from node_modules, in which case
59
+ # the copy below is a no-op and the downloaded binaries are used as-is.
60
+ apple_skia_packages = use_graphite ?
61
+ { 'ios' => 'react-native-skia-graphite-apple-ios',
62
+ 'macos' => 'react-native-skia-graphite-apple-macos' } :
63
+ { 'ios' => 'react-native-skia-apple-ios',
64
+ 'macos' => 'react-native-skia-apple-macos',
65
+ 'tvos' => 'react-native-skia-apple-tvos' }
66
+ install_apple_skia_libs.call(__dir__, apple_skia_packages)
60
67
 
61
68
  # Set preprocessor definitions based on GRAPHITE flag
62
69
  preprocessor_defs = use_graphite ?
@@ -71,14 +78,15 @@ framework_names = ['libskia', 'libsvg', 'libskshaper', 'libskparagraph',
71
78
  # Add Dawn library for Graphite builds (contains dawn::native symbols)
72
79
  framework_names += ['libdawn_combined'] if use_graphite
73
80
 
74
- # Verify that the prebuilt binaries are available (copied in above, or downloaded by
75
- # install-skia-graphite for Graphite builds).
81
+ # Verify that the prebuilt binaries are available (copied in above from the npm
82
+ # packages, or downloaded by install-skia-graphite for in-repo Graphite builds).
76
83
  unless Dir.exist?(File.join(__dir__, 'libs', 'ios')) && Dir.exist?(File.join(__dir__, 'libs', 'macos'))
84
+ expected_packages = apple_skia_packages.values.join(', ')
77
85
  Pod::UI.warn "#{'-' * 72}"
78
86
  Pod::UI.warn "react-native-skia: Skia prebuilt binaries not found in libs/!"
79
87
  Pod::UI.warn ""
80
88
  Pod::UI.warn "Make sure dependencies are installed (yarn install / npm install) so that"
81
- Pod::UI.warn "the react-native-skia-apple-* packages are present, then run `pod install` again."
89
+ Pod::UI.warn "the #{expected_packages} packages are present, then run `pod install` again."
82
90
  Pod::UI.warn "#{'-' * 72}"
83
91
  raise "react-native-skia: Skia prebuilt binaries not found. Run `yarn install` then `pod install` to fix this."
84
92
  end
@@ -7,7 +7,7 @@ import {
7
7
  } from "react-native";
8
8
 
9
9
  import type { DataModule } from "../skia/types";
10
- import { isRNModule } from "../skia/types";
10
+ import { isRNModule, unwrapModule } from "../skia/types";
11
11
 
12
12
  import type { IPlatform } from "./IPlatform";
13
13
 
@@ -15,12 +15,11 @@ export const Platform: IPlatform = {
15
15
  OS: RNPlatform.OS,
16
16
  PixelRatio: PixelRatio.get(),
17
17
  resolveAsset: (source: DataModule) => {
18
- // eslint-disable-next-line no-nested-ternary
19
- return isRNModule(source)
20
- ? Image.resolveAssetSource(source).uri
21
- : "uri" in source
22
- ? source.uri
23
- : source.default;
18
+ const asset = unwrapModule(source);
19
+ if (typeof asset === "string") {
20
+ return asset;
21
+ }
22
+ return isRNModule(asset) ? Image.resolveAssetSource(asset).uri : asset.uri;
24
23
  },
25
24
  findNodeHandle,
26
25
  View,
@@ -3,7 +3,7 @@ import React, { useMemo } from "react";
3
3
  import type { ViewComponent, ViewProps } from "react-native";
4
4
 
5
5
  import type { DataModule } from "../skia/types";
6
- import { isRNModule } from "../skia/types";
6
+ import { isRNModule, unwrapModule } from "../skia/types";
7
7
 
8
8
  import type { IPlatform } from "./IPlatform";
9
9
 
@@ -40,12 +40,16 @@ export const Platform: IPlatform = {
40
40
  OS: "web",
41
41
  PixelRatio: typeof window !== "undefined" ? window.devicePixelRatio : 1, // window is not defined on node
42
42
  resolveAsset: (source: DataModule) => {
43
- if (isRNModule(source)) {
44
- if (typeof source === "number" && typeof require === "function") {
43
+ const asset = unwrapModule(source);
44
+ if (typeof asset === "string") {
45
+ return asset;
46
+ }
47
+ if (isRNModule(asset)) {
48
+ if (typeof require === "function") {
45
49
  const {
46
50
  getAssetByID,
47
51
  } = require("react-native/Libraries/Image/AssetRegistry");
48
- const { httpServerLocation, name, type } = getAssetByID(source);
52
+ const { httpServerLocation, name, type } = getAssetByID(asset);
49
53
  const uri = `${httpServerLocation}/${name}.${type}`;
50
54
  return uri;
51
55
  }
@@ -53,10 +57,7 @@ export const Platform: IPlatform = {
53
57
  "Asset source is a number - this is not supported on the web"
54
58
  );
55
59
  }
56
- if ("uri" in source) {
57
- return source.uri;
58
- }
59
- return source.default;
60
+ return asset.uri;
60
61
  },
61
62
  findNodeHandle: () => {
62
63
  throw new Error("findNodeHandle is not supported on the web");
@@ -1,11 +1,21 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
+ import type { WorkletRuntime } from "react-native-reanimated";
2
3
 
3
4
  import type { Video } from "../../skia/types";
4
5
  import { Skia } from "../../skia";
5
6
 
6
7
  import Rea from "./ReanimatedProxy";
7
8
 
8
- const runtime = Rea.createWorkletRuntime("video-metadata-runtime");
9
+ // Created lazily: accessing the proxy at module scope would require
10
+ // react-native-reanimated as soon as the package is imported, and would spawn
11
+ // the runtime even for apps that never load a video.
12
+ let runtime: WorkletRuntime | undefined;
13
+ const getRuntime = () => {
14
+ if (runtime === undefined) {
15
+ runtime = Rea.createWorkletRuntime("video-metadata-runtime");
16
+ }
17
+ return runtime;
18
+ };
9
19
 
10
20
  type VideoSource = string | null;
11
21
 
@@ -22,7 +32,7 @@ export const useVideoLoading = (source: VideoSource) => {
22
32
  );
23
33
  useEffect(() => {
24
34
  if (source) {
25
- Rea.runOnRuntime(runtime, cb)(source);
35
+ Rea.runOnRuntime(getRuntime(), cb)(source);
26
36
  }
27
37
  }, [cb, source]);
28
38
  return video;
@@ -2,7 +2,7 @@
2
2
  import { useEffect, useMemo, useState } from "react";
3
3
 
4
4
  import { Skia } from "../Skia";
5
- import { FontSlant } from "../types";
5
+ import { FontSlant, FontWeight } from "../types";
6
6
  import type { DataModule, DataSourceParam, SkFontMgr } from "../types";
7
7
  import { Platform } from "../../Platform";
8
8
  import type { SkTypefaceFontProvider } from "../types/Paragraph/TypefaceFontProvider";
@@ -29,8 +29,18 @@ export const useFont = (
29
29
  }, [size, typeface]);
30
30
  };
31
31
 
32
- type Slant = "normal" | "italic" | "oblique";
33
- type Weight =
32
+ /**
33
+ * React Native style font slant, as found in the `fontStyle` property of
34
+ * `TextStyle`. The Skia {@link FontSlant} enum is accepted as well.
35
+ */
36
+ export type RNFontSlant = "normal" | "italic" | "oblique" | FontSlant;
37
+
38
+ /**
39
+ * React Native style font weight, as found in the `fontWeight` property of
40
+ * `TextStyle`. Numeric weights such as the {@link FontWeight} enum values
41
+ * used by the Paragraph API are accepted as well.
42
+ */
43
+ export type RNFontWeight =
34
44
  | "normal"
35
45
  | "bold"
36
46
  | "100"
@@ -41,13 +51,20 @@ type Weight =
41
51
  | "600"
42
52
  | "700"
43
53
  | "800"
44
- | "900";
54
+ | "900"
55
+ | FontWeight;
45
56
 
46
- interface RNFontStyle {
57
+ /**
58
+ * Font style accepted by {@link matchFont}.
59
+ * `fontStyle` and `fontWeight` accept both the React Native string values
60
+ * and the Skia enums ({@link FontSlant} and {@link FontWeight}), so the same
61
+ * values can be shared with the Paragraph API.
62
+ */
63
+ export interface RNFontStyle {
47
64
  fontFamily: string;
48
65
  fontSize: number;
49
- fontStyle: Slant;
50
- fontWeight: Weight;
66
+ fontStyle: RNFontSlant;
67
+ fontWeight: RNFontWeight;
51
68
  }
52
69
 
53
70
  const defaultFontStyle: RNFontStyle = {
@@ -57,8 +74,10 @@ const defaultFontStyle: RNFontStyle = {
57
74
  fontWeight: "normal",
58
75
  };
59
76
 
60
- const slant = (s: Slant) => {
61
- if (s === "italic") {
77
+ const slant = (s: RNFontSlant): FontSlant => {
78
+ if (typeof s === "number") {
79
+ return s;
80
+ } else if (s === "italic") {
62
81
  return FontSlant.Italic;
63
82
  } else if (s === "oblique") {
64
83
  return FontSlant.Oblique;
@@ -67,14 +86,16 @@ const slant = (s: Slant) => {
67
86
  }
68
87
  };
69
88
 
70
- const weight = (fontWeight: Weight) => {
89
+ const weight = (fontWeight: RNFontWeight): FontWeight => {
71
90
  switch (fontWeight) {
72
91
  case "normal":
73
- return 400;
92
+ return FontWeight.Normal;
74
93
  case "bold":
75
- return 700;
94
+ return FontWeight.Bold;
76
95
  default:
77
- return parseInt(fontWeight, 10);
96
+ return typeof fontWeight === "number"
97
+ ? fontWeight
98
+ : (parseInt(fontWeight, 10) as FontWeight);
78
99
  }
79
100
  };
80
101
 
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
2
2
 
3
3
  import { Skia } from "../Skia";
4
4
  import type { DataSourceParam, SkSVG } from "../types";
5
+ import { Platform } from "../../Platform";
5
6
 
6
7
  export const useSVG = (
7
8
  source: DataSourceParam,
@@ -16,22 +17,12 @@ export const useSVG = (
16
17
  let src: string;
17
18
  if (typeof source === "string") {
18
19
  src = source;
19
- } else if (
20
- typeof source === "object" &&
21
- "default" in source &&
22
- typeof source.default === "string"
23
- ) {
24
- src = source.default;
25
- } else if (typeof source === "object" && "uri" in source) {
26
- src = source.uri;
27
- } else {
20
+ } else if (source instanceof Uint8Array) {
28
21
  throw new Error(
29
- `Invalid svg data source. Make sure that the source resolves to a string. Got: ${JSON.stringify(
30
- source,
31
- null,
32
- 2
33
- )}`
22
+ `Invalid svg data source. Make sure that the source resolves to a string. Got: ${source}`
34
23
  );
24
+ } else {
25
+ src = Platform.resolveAsset(source);
35
26
  }
36
27
  const result = await fetch(src);
37
28
  const svgStr = await result.text();
@@ -3,15 +3,15 @@ import type { SkJSIInstance } from "../JsiInstance";
3
3
  export type SkData = SkJSIInstance<"Data">;
4
4
 
5
5
  type RNModule = number;
6
- type ESModule = {
7
- __esModule: true;
8
- default: string;
9
- };
10
6
  type MetroAsset = {
11
7
  uri: string;
12
8
  width: number;
13
9
  height: number;
14
10
  };
11
+ type ESModule = {
12
+ __esModule: true;
13
+ default: RNModule | MetroAsset | string;
14
+ };
15
15
 
16
16
  export type DataModule = RNModule | ESModule | MetroAsset;
17
17
  export type DataSource = DataModule | string | Uint8Array;
@@ -19,3 +19,13 @@ export type DataSourceParam = DataSource | null | undefined;
19
19
 
20
20
  export const isRNModule = (mod: DataModule): mod is RNModule =>
21
21
  typeof mod === "number";
22
+
23
+ // Since Expo SDK 52, on web, require() may return an ES module interop
24
+ // object ({ default: <asset> }) instead of the asset itself.
25
+ // See https://github.com/Shopify/react-native-skia/issues/2784
26
+ export const unwrapModule = (
27
+ mod: DataModule
28
+ ): RNModule | MetroAsset | string =>
29
+ typeof mod === "object" && mod !== null && "default" in mod
30
+ ? mod.default
31
+ : mod;
@@ -1,6 +1,10 @@
1
1
  import type { SkCanvas } from "../Canvas";
2
+ import type { SkFont } from "../Font";
2
3
  import type { SkJSIInstance } from "../JsiInstance";
4
+ import type { SkPath } from "../Path";
5
+ import type { SkPoint } from "../Point";
3
6
  import type { SkRect } from "../Rect";
7
+ import type { SkSize } from "../Size";
4
8
 
5
9
  import type { TextDirection } from "./ParagraphStyle";
6
10
 
@@ -40,6 +44,47 @@ export interface SkRectWithDirection {
40
44
  direction: TextDirection;
41
45
  }
42
46
 
47
+ /**
48
+ * Information about a run of glyphs passed to the visitor of
49
+ * `SkParagraph.extendedVisit`. All glyphs in a run share the same font: for
50
+ * text shaped through font fallback, each fallback font produces its own
51
+ * run(s).
52
+ */
53
+ export interface ParagraphVisitorInfo {
54
+ /** The font used to shape this run (the resolved fallback font, if any). */
55
+ font: SkFont;
56
+ /** Origin of the run within the paragraph (baseline corrected). */
57
+ origin: SkPoint;
58
+ /** Advance of the run. */
59
+ advance: SkSize;
60
+ /** The glyph ids of the run. */
61
+ glyphs: number[];
62
+ /**
63
+ * The position of each glyph, relative to the run `origin`. Add `origin`
64
+ * to obtain paragraph coordinates: for a run shaped through font fallback,
65
+ * the run placement is carried by `origin`, not by the positions.
66
+ */
67
+ positions: SkPoint[];
68
+ /**
69
+ * The tight ink bounds of each glyph, relative to the glyph's origin.
70
+ * Combine with `positions` to compute the exact painted bounds of the run.
71
+ */
72
+ bounds: SkRect[];
73
+ /** The UTF-8 text index of the cluster each glyph belongs to. */
74
+ utf8Starts: number[];
75
+ /** Reserved flags (currently always 0). */
76
+ flags: number;
77
+ }
78
+
79
+ /**
80
+ * Visitor for `SkParagraph.extendedVisit`. Called once per run of glyphs;
81
+ * `info` is null to signal the end of a line.
82
+ */
83
+ export type ParagraphExtendedVisitor = (
84
+ lineNumber: number,
85
+ info: ParagraphVisitorInfo | null
86
+ ) => void;
87
+
43
88
  export interface SkParagraph extends SkJSIInstance<"Paragraph"> {
44
89
  /**
45
90
  * Calculates the position of the the glyphs in the paragraph
@@ -112,4 +157,26 @@ export interface SkParagraph extends SkJSIInstance<"Paragraph"> {
112
157
  * to the paragraph.
113
158
  */
114
159
  getRectsForPlaceholders(): SkRectWithDirection[];
160
+ /**
161
+ * Converts the glyphs of the given line into an SkPath, with all font
162
+ * fallbacks already applied. Use `computeTightBounds()` on the resulting
163
+ * path to measure the exact ink bounds of the rendered text (as opposed to
164
+ * the font-metrics based bounds returned by `getRectsForRange`).
165
+ * Note that color glyphs (e.g. emojis) cannot be converted to a path and
166
+ * are skipped. Returns null if the line number is out of bounds.
167
+ * This method requires the layout method to have been called first.
168
+ * Not implemented on React Native Web.
169
+ * @param lineNumber The line number (zero indexed, see `getLineMetrics`)
170
+ */
171
+ getPath(lineNumber: number): SkPath | null;
172
+ /**
173
+ * Visits the laid out paragraph, calling the visitor once for every run of
174
+ * glyphs with the resolved font, glyph ids, positions and per-glyph tight
175
+ * ink bounds. The visitor is called with a null info to signal the end of
176
+ * each line. This exposes the exact layout that would be painted.
177
+ * This method requires the layout method to have been called first.
178
+ * Not implemented on React Native Web.
179
+ * @param visitor Called once per glyph run, and once per line end
180
+ */
181
+ extendedVisit(visitor: ParagraphExtendedVisitor): void;
115
182
  }
@@ -41,6 +41,13 @@ export abstract class BaseHostObject<T, N extends string>
41
41
  "delete" in this.ref &&
42
42
  typeof this.ref.delete === "function"
43
43
  ) {
44
+ // Renderer-owned and user-owned disposal can overlap (and emscripten
45
+ // throws on double delete) — make disposing an already-deleted object
46
+ // a no-op.
47
+ const ref = this.ref as unknown as { isDeleted?: () => boolean };
48
+ if (typeof ref.isDeleted === "function" && ref.isDeleted()) {
49
+ return;
50
+ }
44
51
  this.ref.delete();
45
52
  }
46
53
  }