diff --git a/bun.lock b/bun.lock index 2b44f9d2..7323f8d0 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "react-native-evals", @@ -42,6 +43,7 @@ "@react-navigation/native": "^7.1.0", "@react-navigation/native-stack": "^7.3.0", "@shopify/flash-list": "2.0.2", + "@shopify/react-native-skia": "^2.11.2", "@tanstack/react-query": "^5.90.21", "expo": "~56.0.12", "expo-audio": "~56.0.12", @@ -554,6 +556,8 @@ "@shopify/flash-list": ["@shopify/flash-list@2.0.2", "", { "dependencies": { "tslib": "2.8.1" }, "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w=="], + "@shopify/react-native-skia": ["@shopify/react-native-skia@2.11.2", "", { "dependencies": { "canvaskit-wasm": "0.41.0", "react-native-skia-android": "152.0.0", "react-native-skia-apple-ios": "152.0.0", "react-native-skia-apple-macos": "152.0.0", "react-native-skia-apple-tvos": "152.0.0", "react-reconciler": "0.31.0" }, "peerDependencies": { "react": ">=19.0", "react-native": ">=0.78", "react-native-reanimated": ">=4.0.0", "react-native-worklets": ">=0.7.0" }, "optionalPeers": ["react-native", "react-native-reanimated", "react-native-worklets"], "bin": { "setup-skia-web": "scripts/setup-canvaskit.js" } }, "sha512-BGTSMlsl3gM/gw9FyfCDgEATBQvNkhyDX9iEDu/iu3ZLcZT7+QzeFwpC0Sij2pLjuY33x3UDPS2DhSis7Mo7Xw=="], + "@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="], "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], @@ -690,6 +694,8 @@ "@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="], + "@webgpu/types": ["@webgpu/types@0.1.21", "", {}, "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow=="], + "@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], @@ -822,6 +828,8 @@ "caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="], + "canvaskit-wasm": ["canvaskit-wasm@0.41.0", "", { "dependencies": { "@webgpu/types": "0.1.21" } }, "sha512-cnbL02NFB3yOYMF/MtxViZHgD1vh55Pvy+zR8q4JuFvyCPejZP3eClkt2GuZ0S7jOmGMCJXaHBasbMChbR9JZg=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -1712,8 +1720,18 @@ "react-native-screens": ["react-native-screens@4.25.2", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": ">=0.82.0" } }, "sha512-1Nj1fusFd+rIMKU/qC9yGKVG+3ofh11d3OdBQKL1iVvQfKvcB8vhvTGQf2TkfxW3bamxN+hCZIXmNuU0mRkyDg=="], + "react-native-skia-android": ["react-native-skia-android@152.0.0", "", {}, "sha512-X++QG8bpPLHpavUyNfQvAxvtjH3r8fnivxS1PTFZsKL4ZiAJ1V9IVsJi4qltafWJbzhq7IutpZQNYd06uwNkjg=="], + + "react-native-skia-apple-ios": ["react-native-skia-apple-ios@152.0.0", "", {}, "sha512-rmnHYWkwvxm7B9fXZCkTRdZVoQwVnNydjytfphxatweQmEqwYE+/g1LOL/emhM/ulmwAFKFT0VNAEzAlvHId2A=="], + + "react-native-skia-apple-macos": ["react-native-skia-apple-macos@152.0.0", "", {}, "sha512-boWCDyPPJUuKwcE+DU8X7dop6EwLdrOH+v7pxYAj+43NbOwgfV1wzq4g+kWIQqag5ZOQInq2mVwCc8S1eJjlXw=="], + + "react-native-skia-apple-tvos": ["react-native-skia-apple-tvos@152.0.0", "", {}, "sha512-+UnXke4Yz9Jmy3uTcDbYFrVG/+CZMqYbaTV3f52wovaMzf8wWy+iZampZr6dW6rOWLWrsUFiq7yMImg6UGbiFg=="], + "react-native-worklets": ["react-native-worklets@0.7.4", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", "@babel/plugin-transform-classes": "7.28.4", "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", "@babel/plugin-transform-optional-chaining": "7.27.1", "@babel/plugin-transform-shorthand-properties": "7.27.1", "@babel/plugin-transform-template-literals": "7.27.1", "@babel/plugin-transform-unicode-regex": "7.27.1", "@babel/preset-typescript": "7.27.1", "convert-source-map": "2.0.0", "semver": "7.7.3" }, "peerDependencies": { "@babel/core": "*", "react": "*", "react-native": "*" } }, "sha512-NYOdM1MwBb3n+AtMqy1tFy3Mn8DliQtd8sbzAVRf9Gc+uvQ0zRfxN7dS8ZzoyX7t6cyQL5THuGhlnX+iFlQTag=="], + "react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="], + "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], @@ -2276,6 +2294,8 @@ "react-native-worklets/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "react-reconciler/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="], diff --git a/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx b/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx index 77c16c5a..7a60ae2c 100644 --- a/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx +++ b/evals/skia/01-rn-skia-canvas-fill-background/reference/App.tsx @@ -18,7 +18,9 @@ const fontStyle = { const font = matchFont(fontStyle) function CanvasContent() { - const { width, height } = useCanvasSize() + const { + size: { width, height }, + } = useCanvasSize() const label = `${Math.round(width)} × ${Math.round(height)}` const textX = width / 2 - (font?.getTextWidth(label) ?? 0) / 2 diff --git a/evals/skia/08-rn-skia-text-rendering/reference/App.tsx b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx index 1c42e5e6..761da1e5 100644 --- a/evals/skia/08-rn-skia-text-rendering/reference/App.tsx +++ b/evals/skia/08-rn-skia-text-rendering/reference/App.tsx @@ -20,7 +20,9 @@ const font = matchFont(fontStyle) const LABEL = 'Hello, Skia!' function CenteredText() { - const { width, height } = useCanvasSize() + const { + size: { width, height }, + } = useCanvasSize() const textWidth = font?.getTextWidth(LABEL) ?? 0 const x = (width - textWidth) / 2 const y = height / 2 + FONT_SIZE / 2 diff --git a/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md b/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md index b87d2b22..38597ef5 100644 --- a/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md +++ b/evals/skia/16-rn-skia-clip-rect-and-path/prompt.md @@ -1 +1 @@ -Demonstrate two clipping techniques on a Skia canvas: use ClipRect to restrict a gradient fill to a rectangular region, and use ClipPath to clip a solid-color rectangle to a non-rectangular polygon or star shape defined by an SVG path string. +Demonstrate two clipping techniques on a Skia canvas using the `clip` prop on `Group`: clip a gradient fill to a rectangular region, and clip a solid-color rectangle to a non-rectangular polygon or star shape defined by an SVG path string. diff --git a/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx b/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx index 5e4d6999..d1b38aa1 100644 --- a/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx +++ b/evals/skia/16-rn-skia-clip-rect-and-path/reference/App.tsx @@ -1,12 +1,11 @@ import { Canvas, - ClipPath, - ClipRect, Fill, Group, LinearGradient, Rect, Skia, + rect, vec, } from '@shopify/react-native-skia' @@ -19,9 +18,8 @@ export default function App() { - {/* ClipRect: restrict a gradient to a rectangle */} - - + {/* Clip a gradient to a rectangular region via Group's clip prop */} + - {/* ClipPath: clip a solid block to a star shape */} - - + {/* Clip a solid block to a star shape via Group's clip prop */} + diff --git a/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml b/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml index 5e437f64..71a16c31 100644 --- a/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml +++ b/evals/skia/16-rn-skia-clip-rect-and-path/requirements.yaml @@ -7,10 +7,10 @@ requirements: description: Must render all drawing content inside a Canvas component from @shopify/react-native-skia. weight: 0.1 - id: uses-clip-rect - description: Must use the ClipRect component from @shopify/react-native-skia to clip a drawing to a rectangular region. + description: Must use the `clip` prop on a Group component with a rectangle (e.g. from `rect()` or an `{ x, y, width, height }` object) to clip a drawing to a rectangular region. - id: uses-clip-path - description: Must use the ClipPath component from @shopify/react-native-skia with a path or SVG path string. + description: Must use the `clip` prop on a Group component with a path (an SkPath or SVG path string) to clip a drawing to a non-rectangular shape. - id: clip-path-is-non-rectangular - description: The ClipPath shape must be a non-rectangular polygon, star, or curve — not just a simple rectangle. + description: The path-based clip shape must be a non-rectangular polygon, star, or curve — not just a simple rectangle. - id: both-clips-applied-to-distinct-drawings - description: ClipRect and ClipPath must each be applied to a different visual element so both clipping techniques are demonstrated independently. + description: The rectangular clip and the path clip must each be applied to a different visual element (separate Group components) so both clipping techniques are demonstrated independently. diff --git a/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx b/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx index 0b4930ec..cb46cec8 100644 --- a/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx +++ b/evals/skia/24-rn-skia-picture-save-restore/reference/App.tsx @@ -23,7 +23,7 @@ export default function App() { paint.setColor(Skia.Color(FIRST_RECT_COLOR)) canvas.save() canvas.translate(CX, 90) - canvas.rotate(FIRST_RECT_ROTATION_DEG) + canvas.rotate(FIRST_RECT_ROTATION_DEG, 0, 0) canvas.drawRect( { x: -RECT_WIDTH / 2, @@ -41,7 +41,7 @@ export default function App() { paint.setColor(Skia.Color(THIRD_RECT_COLOR)) canvas.save() canvas.translate(CX, 230) - canvas.rotate(THIRD_RECT_ROTATION_DEG) + canvas.rotate(THIRD_RECT_ROTATION_DEG, 0, 0) canvas.drawRect( { x: -RECT_WIDTH / 2, diff --git a/evals/skia/README.md b/evals/skia/README.md index 6cefe82b..c741036d 100644 --- a/evals/skia/README.md +++ b/evals/skia/README.md @@ -37,7 +37,7 @@ React Native Skia evals — testing how well LLMs implement high-performance 2D | 10 | Capture canvas output with `makeImageSnapshot()` via `useCanvasRef`; call `encodeToBytes()` for raw pixel data | canvas/overview | | 11 | Use `matchFont` with a `fontStyle` object for system font resolution; the Text `y` origin is the text baseline, not the top | text/text | | 12 | Apply blend modes at the `Group` level with the `blendMode` prop to composite child elements | paint/overview | -| 13 | Use `ClipRect` / `ClipPath` as children of a `Group` or drawing element to mask content | canvas/overview | +| 13 | Use the `clip` prop on `Group` (rect, rrect, or path) to mask content; `invertClip` reverses it | group | | 14 | Use `SweepGradient` / `TwoPointConicalGradient` for angular and conical fills beyond linear/radial | shaders/gradients | | 15 | Apply effects to a group composite with the `layer` prop (`` + image filters), not per-child filters alone | group | | 16 | Use `Skia.ParagraphBuilder` + `` for multi-style text layouts; call `layout(width)` before rendering | text/paragraph | @@ -86,7 +86,7 @@ The pack now covers three built-in gradient shaders (linear, radial, sweep). Maj | Canvas sizing (UI thread) | `onSize` shared value prop | Partially covered by `useCanvasSize` in eval 01 | | Per-element gestures | `Animated.View` overlay tracking | Documented in rule 4; eval 14 only tests canvas-level pan | | Nested image shaders | `ImageShader` inside custom `Shader` | Advanced; needs bundled image asset | -| Fitbox / Group clip props | `FitBox`, `Group clip` / `invertClip` | Overlap path/SVG evals; `FitBox` is a strong future add | +| FitBox | `FitBox` | Overlaps path/SVG evals; a strong future add | | Path / mask filters | `DashPathEffect`, `BlurMask`, morphology | Lower-level paint modifiers; narrow use cases | ## Common issue clusters diff --git a/testbench/package.json b/testbench/package.json index aacc0c4e..26526649 100644 --- a/testbench/package.json +++ b/testbench/package.json @@ -24,6 +24,7 @@ "@react-navigation/native": "^7.1.0", "@react-navigation/native-stack": "^7.3.0", "@shopify/flash-list": "2.0.2", + "@shopify/react-native-skia": "^2.11.2", "@tanstack/react-query": "^5.90.21", "expo": "~56.0.12", "expo-audio": "~56.0.12",