@shopify/react-native-skia 2.8.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 (48) 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/JsiSkiaContext.h +4 -0
  7. package/cpp/rnskia/RNSkView.h +6 -0
  8. package/lib/commonjs/Platform/Platform.js +5 -2
  9. package/lib/commonjs/Platform/Platform.js.map +1 -1
  10. package/lib/commonjs/Platform/Platform.web.js +8 -7
  11. package/lib/commonjs/Platform/Platform.web.js.map +1 -1
  12. package/lib/commonjs/skia/core/Font.d.ts +21 -6
  13. package/lib/commonjs/skia/core/Font.js +24 -4
  14. package/lib/commonjs/skia/core/Font.js.map +1 -1
  15. package/lib/commonjs/skia/types/Data/Data.d.ts +5 -4
  16. package/lib/commonjs/skia/types/Data/Data.js +7 -1
  17. package/lib/commonjs/skia/types/Data/Data.js.map +1 -1
  18. package/lib/commonjs/skia/web/JsiSkFont.d.ts +1 -1
  19. package/lib/commonjs/skia/web/JsiSkFont.js +43 -2
  20. package/lib/commonjs/skia/web/JsiSkFont.js.map +1 -1
  21. package/lib/module/Platform/Platform.js +6 -3
  22. package/lib/module/Platform/Platform.js.map +1 -1
  23. package/lib/module/Platform/Platform.web.js +9 -8
  24. package/lib/module/Platform/Platform.web.js.map +1 -1
  25. package/lib/module/skia/core/Font.d.ts +21 -6
  26. package/lib/module/skia/core/Font.js +26 -5
  27. package/lib/module/skia/core/Font.js.map +1 -1
  28. package/lib/module/skia/types/Data/Data.d.ts +5 -4
  29. package/lib/module/skia/types/Data/Data.js +5 -0
  30. package/lib/module/skia/types/Data/Data.js.map +1 -1
  31. package/lib/module/skia/web/JsiSkFont.d.ts +1 -1
  32. package/lib/module/skia/web/JsiSkFont.js +44 -3
  33. package/lib/module/skia/web/JsiSkFont.js.map +1 -1
  34. package/lib/typescript/lib/commonjs/skia/types/Data/Data.d.ts +1 -0
  35. package/lib/typescript/lib/commonjs/skia/web/JsiSkFont.d.ts +1 -1
  36. package/lib/typescript/lib/module/mock/index.d.ts +1 -0
  37. package/lib/typescript/lib/module/skia/types/Data/Data.d.ts +1 -0
  38. package/lib/typescript/lib/module/skia/web/JsiSkFont.d.ts +1 -1
  39. package/lib/typescript/src/skia/core/Font.d.ts +21 -6
  40. package/lib/typescript/src/skia/types/Data/Data.d.ts +5 -4
  41. package/lib/typescript/src/skia/web/JsiSkFont.d.ts +1 -1
  42. package/package.json +8 -2
  43. package/react-native-skia.podspec +18 -10
  44. package/src/Platform/Platform.ts +6 -7
  45. package/src/Platform/Platform.web.tsx +9 -8
  46. package/src/skia/core/Font.ts +34 -13
  47. package/src/skia/types/Data/Data.ts +14 -4
  48. package/src/skia/web/JsiSkFont.ts +48 -4
@@ -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");
@@ -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
 
@@ -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,4 +1,4 @@
1
- import type { CanvasKit, Font } from "canvaskit-wasm";
1
+ import type { CanvasKit, Font, Paint } from "canvaskit-wasm";
2
2
 
3
3
  import type {
4
4
  FontEdging,
@@ -10,7 +10,7 @@ import type {
10
10
  SkTypeface,
11
11
  } from "../types";
12
12
 
13
- import { HostObject, getEnum, throwNotImplementedOnRNWeb } from "./Host";
13
+ import { HostObject, getEnum } from "./Host";
14
14
  import { JsiSkPaint } from "./JsiSkPaint";
15
15
  import { JsiSkPoint } from "./JsiSkPoint";
16
16
  import { JsiSkRect } from "./JsiSkRect";
@@ -21,8 +21,52 @@ export class JsiSkFont extends HostObject<Font, "Font"> implements SkFont {
21
21
  super(CanvasKit, ref, "Font");
22
22
  }
23
23
 
24
- measureText(_text: string, _paint?: SkPaint | undefined) {
25
- return throwNotImplementedOnRNWeb<SkRect>();
24
+ measureText(text: string, paint?: SkPaint | undefined): SkRect {
25
+ // CanvasKit doesn't expose SkFont::measureText directly, so we polyfill
26
+ // it: for each glyph we take its ink bounds (relative to its own origin),
27
+ // offset it by the accumulated advance, and union the results. This
28
+ // matches the bounds computed natively by SkFont::measureText.
29
+ const glyphs = this.ref.getGlyphIDs(text);
30
+ if (glyphs.length === 0) {
31
+ return new JsiSkRect(this.CanvasKit, this.CanvasKit.XYWHRect(0, 0, 0, 0));
32
+ }
33
+ const skPaint = paint ? JsiSkPaint.fromValue<Paint>(paint) : null;
34
+ // Flattened rectangles: 4 floats (left, top, right, bottom) per glyph.
35
+ const bounds = this.ref.getGlyphBounds(glyphs, skPaint);
36
+ const advances = this.ref.getGlyphWidths(glyphs, skPaint);
37
+ let left = 0;
38
+ let top = 0;
39
+ let right = 0;
40
+ let bottom = 0;
41
+ let isEmpty = true;
42
+ let xPos = 0;
43
+ for (let i = 0; i < glyphs.length; i++) {
44
+ const l = bounds[i * 4] + xPos;
45
+ const t = bounds[i * 4 + 1];
46
+ const r = bounds[i * 4 + 2] + xPos;
47
+ const b = bounds[i * 4 + 3];
48
+ xPos += advances[i];
49
+ // Skip empty glyph bounds (e.g. whitespace), like SkRect::join does.
50
+ if (l >= r || t >= b) {
51
+ continue;
52
+ }
53
+ if (isEmpty) {
54
+ left = l;
55
+ top = t;
56
+ right = r;
57
+ bottom = b;
58
+ isEmpty = false;
59
+ } else {
60
+ left = Math.min(left, l);
61
+ top = Math.min(top, t);
62
+ right = Math.max(right, r);
63
+ bottom = Math.max(bottom, b);
64
+ }
65
+ }
66
+ return new JsiSkRect(
67
+ this.CanvasKit,
68
+ this.CanvasKit.LTRBRect(left, top, right, bottom)
69
+ );
26
70
  }
27
71
 
28
72
  getTextWidth(text: string, paint?: SkPaint | undefined) {