@shopify/react-native-skia 1.10.2 → 1.11.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.
Files changed (104) hide show
  1. package/cpp/api/JsiSkApi.h +3 -0
  2. package/cpp/api/recorder/ColorFilters.h +133 -0
  3. package/cpp/api/recorder/Command.h +58 -0
  4. package/cpp/api/recorder/Convertor.h +1213 -0
  5. package/cpp/api/recorder/DataTypes.h +232 -0
  6. package/cpp/api/recorder/DrawingCtx.h +187 -0
  7. package/cpp/api/recorder/Drawings.h +950 -0
  8. package/cpp/api/recorder/ImageFilters.h +292 -0
  9. package/cpp/api/recorder/ImageFit.h +108 -0
  10. package/cpp/api/recorder/JsiRecorder.h +314 -0
  11. package/cpp/api/recorder/Paint.h +191 -0
  12. package/cpp/api/recorder/PathEffects.h +194 -0
  13. package/cpp/api/recorder/RNRecorder.h +637 -0
  14. package/cpp/api/recorder/Shaders.h +406 -0
  15. package/cpp/rnskia/dom/nodes/JsiAtlasNode.h +3 -2
  16. package/cpp/rnskia/dom/nodes/JsiImageNode.h +3 -2
  17. package/jestSetup.js +8 -0
  18. package/jestSetup.mjs +8 -0
  19. package/lib/commonjs/renderer/components/image/Image.d.ts +1 -1
  20. package/lib/commonjs/renderer/components/image/Image.js +8 -2
  21. package/lib/commonjs/renderer/components/image/Image.js.map +1 -1
  22. package/lib/commonjs/skia/types/Recorder.d.ts +52 -0
  23. package/lib/commonjs/skia/types/Recorder.js +6 -0
  24. package/lib/commonjs/skia/types/Recorder.js.map +1 -0
  25. package/lib/commonjs/skia/types/Skia.d.ts +2 -0
  26. package/lib/commonjs/skia/types/Skia.js.map +1 -1
  27. package/lib/commonjs/skia/types/index.d.ts +1 -0
  28. package/lib/commonjs/skia/types/index.js +11 -0
  29. package/lib/commonjs/skia/types/index.js.map +1 -1
  30. package/lib/commonjs/skia/web/JsiSkia.js +3 -0
  31. package/lib/commonjs/skia/web/JsiSkia.js.map +1 -1
  32. package/lib/commonjs/sksg/Container.d.ts +6 -1
  33. package/lib/commonjs/sksg/Container.js +59 -2
  34. package/lib/commonjs/sksg/Container.js.map +1 -1
  35. package/lib/commonjs/sksg/Recorder/DrawingContext.js +1 -0
  36. package/lib/commonjs/sksg/Recorder/DrawingContext.js.map +1 -1
  37. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +53 -0
  38. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js +189 -0
  39. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js.map +1 -0
  40. package/lib/commonjs/sksg/Recorder/Recorder.d.ts +2 -2
  41. package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
  42. package/lib/commonjs/sksg/Recorder/Visitor.d.ts +2 -2
  43. package/lib/commonjs/sksg/Recorder/Visitor.js +2 -2
  44. package/lib/commonjs/sksg/Recorder/Visitor.js.map +1 -1
  45. package/lib/module/renderer/components/image/Image.d.ts +1 -1
  46. package/lib/module/renderer/components/image/Image.js +8 -2
  47. package/lib/module/renderer/components/image/Image.js.map +1 -1
  48. package/lib/module/skia/types/Recorder.d.ts +52 -0
  49. package/lib/module/skia/types/Recorder.js +2 -0
  50. package/lib/module/skia/types/Recorder.js.map +1 -0
  51. package/lib/module/skia/types/Skia.d.ts +2 -0
  52. package/lib/module/skia/types/Skia.js.map +1 -1
  53. package/lib/module/skia/types/index.d.ts +1 -0
  54. package/lib/module/skia/types/index.js +1 -0
  55. package/lib/module/skia/types/index.js.map +1 -1
  56. package/lib/module/skia/web/JsiSkia.js +3 -0
  57. package/lib/module/skia/web/JsiSkia.js.map +1 -1
  58. package/lib/module/sksg/Container.d.ts +6 -1
  59. package/lib/module/sksg/Container.js +59 -2
  60. package/lib/module/sksg/Container.js.map +1 -1
  61. package/lib/module/sksg/Recorder/DrawingContext.js +1 -0
  62. package/lib/module/sksg/Recorder/DrawingContext.js.map +1 -1
  63. package/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +53 -0
  64. package/lib/module/sksg/Recorder/ReanimatedRecorder.js +182 -0
  65. package/lib/module/sksg/Recorder/ReanimatedRecorder.js.map +1 -0
  66. package/lib/module/sksg/Recorder/Recorder.d.ts +2 -2
  67. package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
  68. package/lib/module/sksg/Recorder/Visitor.d.ts +2 -2
  69. package/lib/module/sksg/Recorder/Visitor.js +2 -2
  70. package/lib/module/sksg/Recorder/Visitor.js.map +1 -1
  71. package/lib/typescript/lib/commonjs/renderer/components/image/Image.d.ts +4 -1
  72. package/lib/typescript/lib/commonjs/skia/types/Recorder.d.ts +1 -0
  73. package/lib/typescript/lib/commonjs/skia/web/JsiSkia.d.ts +1 -0
  74. package/lib/typescript/lib/commonjs/sksg/Container.d.ts +5 -1
  75. package/lib/typescript/lib/commonjs/sksg/Reconciler.d.ts +6 -0
  76. package/lib/typescript/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +47 -0
  77. package/lib/typescript/lib/module/mock/index.d.ts +4 -1
  78. package/lib/typescript/lib/module/renderer/components/image/Image.d.ts +4 -1
  79. package/lib/typescript/lib/module/skia/Skia.web.d.ts +1 -0
  80. package/lib/typescript/lib/module/skia/types/Recorder.d.ts +1 -0
  81. package/lib/typescript/lib/module/skia/types/index.d.ts +1 -0
  82. package/lib/typescript/lib/module/skia/web/JsiSkia.d.ts +1 -0
  83. package/lib/typescript/lib/module/sksg/Container.d.ts +5 -1
  84. package/lib/typescript/lib/module/sksg/Reconciler.d.ts +6 -0
  85. package/lib/typescript/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +46 -0
  86. package/lib/typescript/src/renderer/components/image/Image.d.ts +1 -1
  87. package/lib/typescript/src/skia/types/Recorder.d.ts +52 -0
  88. package/lib/typescript/src/skia/types/Skia.d.ts +2 -0
  89. package/lib/typescript/src/skia/types/index.d.ts +1 -0
  90. package/lib/typescript/src/sksg/Container.d.ts +6 -1
  91. package/lib/typescript/src/sksg/Recorder/ReanimatedRecorder.d.ts +53 -0
  92. package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +2 -2
  93. package/lib/typescript/src/sksg/Recorder/Visitor.d.ts +2 -2
  94. package/package.json +3 -2
  95. package/src/renderer/components/image/Image.tsx +2 -2
  96. package/src/skia/types/Recorder.ts +91 -0
  97. package/src/skia/types/Skia.ts +2 -0
  98. package/src/skia/types/index.ts +1 -0
  99. package/src/skia/web/JsiSkia.ts +3 -0
  100. package/src/sksg/Container.ts +63 -4
  101. package/src/sksg/Recorder/DrawingContext.ts +1 -0
  102. package/src/sksg/Recorder/ReanimatedRecorder.ts +271 -0
  103. package/src/sksg/Recorder/Recorder.ts +2 -2
  104. package/src/sksg/Recorder/Visitor.ts +17 -12
@@ -0,0 +1,950 @@
1
+ #pragma once
2
+
3
+ #include <optional>
4
+
5
+ #include "Command.h"
6
+ #include "Convertor.h"
7
+ #include "DrawingCtx.h"
8
+ #include "ImageFit.h"
9
+
10
+ namespace RNSkia {
11
+
12
+ struct CircleCmdProps {
13
+ std::optional<float> cx;
14
+ std::optional<float> cy;
15
+ std::optional<SkPoint> c;
16
+ float r;
17
+ };
18
+
19
+ class CircleCmd : public Command {
20
+ private:
21
+ CircleCmdProps props;
22
+
23
+ public:
24
+ CircleCmd(jsi::Runtime &runtime, const jsi::Object &object,
25
+ Variables &variables)
26
+ : Command(CommandType::DrawCircle) {
27
+ convertProperty(runtime, object, "cx", props.cx, variables);
28
+ convertProperty(runtime, object, "cy", props.cy, variables);
29
+ convertProperty(runtime, object, "c", props.c, variables);
30
+ convertProperty(runtime, object, "r", props.r, variables);
31
+ }
32
+
33
+ void draw(DrawingCtx *ctx) {
34
+ auto paint = ctx->getPaint();
35
+ if (props.c.has_value()) {
36
+ ctx->canvas->drawCircle(props.c.value(), props.r, paint);
37
+ } else {
38
+ auto cx = props.cx.value_or(0);
39
+ auto cy = props.cy.value_or(0);
40
+ auto r = props.r;
41
+ ctx->canvas->drawCircle(cx, cy, r, paint);
42
+ }
43
+ }
44
+ };
45
+
46
+ struct RectCmdProps {
47
+ float x = 0;
48
+ float y = 0;
49
+ std::optional<float> width;
50
+ std::optional<float> height;
51
+ std::optional<SkRect> rect;
52
+ };
53
+
54
+ class RectCmd : public Command {
55
+ private:
56
+ RectCmdProps props;
57
+
58
+ public:
59
+ RectCmd(jsi::Runtime &runtime, const jsi::Object &object,
60
+ Variables &variables)
61
+ : Command(CommandType::DrawRect) {
62
+ convertProperty(runtime, object, "x", props.x, variables);
63
+ convertProperty(runtime, object, "y", props.y, variables);
64
+ convertProperty(runtime, object, "width", props.width, variables);
65
+ convertProperty(runtime, object, "height", props.height, variables);
66
+ convertProperty(runtime, object, "rect", props.rect, variables);
67
+ }
68
+
69
+ void draw(DrawingCtx *ctx) {
70
+ auto [x, y, width, height, rect] = props;
71
+ if (rect.has_value()) {
72
+ ctx->canvas->drawRect(rect.value(), ctx->getPaint());
73
+ } else {
74
+ auto rct = SkRect::MakeXYWH(x, y, width.value(), height.value());
75
+ ctx->canvas->drawRect(rct, ctx->getPaint());
76
+ }
77
+ }
78
+ };
79
+
80
+ struct PathCmdProps {
81
+ SkPath path;
82
+ float start;
83
+ float end;
84
+ std::optional<StrokeOpts> stroke;
85
+ std::optional<SkPathFillType> fillType;
86
+ };
87
+
88
+ class PathCmd : public Command {
89
+ private:
90
+ PathCmdProps props;
91
+
92
+ public:
93
+ PathCmd(jsi::Runtime &runtime, const jsi::Object &object,
94
+ Variables &variables)
95
+ : Command(CommandType::DrawPath) {
96
+ convertProperty(runtime, object, "path", props.path, variables);
97
+ convertProperty(runtime, object, "start", props.start, variables);
98
+ convertProperty(runtime, object, "end", props.end, variables);
99
+ convertProperty(runtime, object, "stroke", props.stroke, variables);
100
+ convertProperty(runtime, object, "fillType", props.fillType, variables);
101
+ }
102
+
103
+ void draw(DrawingCtx *ctx) {
104
+ // Saturate start and end values (clamp between 0 and 1)
105
+ float start = std::clamp(props.start, 0.0f, 1.0f);
106
+ float end = std::clamp(props.end, 0.0f, 1.0f);
107
+
108
+ // Check conditions that require path mutation
109
+ bool hasStartOffset = start != 0.0f;
110
+ bool hasEndOffset = end != 1.0f;
111
+ bool hasStrokeOptions = props.stroke.has_value();
112
+ bool hasFillType = props.fillType.has_value();
113
+ bool willMutatePath =
114
+ hasStartOffset || hasEndOffset || hasStrokeOptions || hasFillType;
115
+
116
+ std::shared_ptr<const SkPath> pathToUse;
117
+
118
+ if (willMutatePath) {
119
+ // Create a filtered path for modifications
120
+ SkPath filteredPath(props.path);
121
+
122
+ // Handle path trimming
123
+ if (hasStartOffset || hasEndOffset) {
124
+ auto pe =
125
+ SkTrimPathEffect::Make(start, end, SkTrimPathEffect::Mode::kNormal);
126
+ if (pe != nullptr) {
127
+ SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
128
+ if (!pe->filterPath(&filteredPath, filteredPath, &rec, nullptr)) {
129
+ throw std::runtime_error(
130
+ "Failed trimming path with parameters start: " +
131
+ std::to_string(start) + ", end: " + std::to_string(end));
132
+ }
133
+ filteredPath.swap(filteredPath);
134
+ } else {
135
+ throw std::runtime_error(
136
+ "Failed trimming path with parameters start: " +
137
+ std::to_string(start) + ", end: " + std::to_string(end));
138
+ }
139
+ }
140
+
141
+ // Set fill type
142
+ auto p = std::make_shared<SkPath>(filteredPath);
143
+ if (props.fillType.has_value()) {
144
+ p->setFillType(props.fillType.value());
145
+ }
146
+ // Handle stroke options
147
+ if (hasStrokeOptions) {
148
+ const auto &stroke = props.stroke.value();
149
+ SkPaint strokePaint;
150
+
151
+ if (stroke.cap.has_value()) {
152
+ strokePaint.setStrokeCap(stroke.cap.value());
153
+ }
154
+
155
+ if (stroke.join.has_value()) {
156
+ strokePaint.setStrokeJoin(stroke.join.value());
157
+ }
158
+
159
+ if (stroke.width.has_value()) {
160
+ strokePaint.setStrokeWidth(stroke.width.value());
161
+ }
162
+
163
+ if (stroke.miter_limit.has_value()) {
164
+ strokePaint.setStrokeMiter(stroke.miter_limit.value());
165
+ }
166
+
167
+ float precision = stroke.precision.value_or(1.0f);
168
+
169
+ auto strokedPath = std::make_shared<SkPath>();
170
+ if (!skpathutils::FillPathWithPaint(*p, strokePaint, strokedPath.get(),
171
+ nullptr, precision)) {
172
+ throw std::runtime_error("Failed to apply stroke to path");
173
+ }
174
+ pathToUse = std::const_pointer_cast<const SkPath>(strokedPath);
175
+ } else {
176
+ pathToUse = std::const_pointer_cast<const SkPath>(p);
177
+ }
178
+ } else {
179
+ // Use the original path directly
180
+ pathToUse = std::make_shared<const SkPath>(props.path);
181
+ }
182
+
183
+ if (!pathToUse) {
184
+ throw std::runtime_error(
185
+ "Path node could not resolve path props correctly.");
186
+ }
187
+
188
+ // Draw the final path
189
+ ctx->canvas->drawPath(*pathToUse, ctx->getPaint());
190
+ }
191
+ };
192
+
193
+ struct LineCmdProps {
194
+ SkPoint p1;
195
+ SkPoint p2;
196
+ };
197
+
198
+ class LineCmd : public Command {
199
+ private:
200
+ LineCmdProps props;
201
+
202
+ public:
203
+ LineCmd(jsi::Runtime &runtime, const jsi::Object &object,
204
+ Variables &variables)
205
+ : Command(CommandType::DrawLine) {
206
+ convertProperty(runtime, object, "p1", props.p1, variables);
207
+ convertProperty(runtime, object, "p2", props.p2, variables);
208
+ }
209
+
210
+ void draw(DrawingCtx *ctx) {
211
+ ctx->canvas->drawLine(props.p1.x(), props.p1.y(), props.p2.x(),
212
+ props.p2.y(), ctx->getPaint());
213
+ }
214
+ };
215
+
216
+ struct TextPathProps {
217
+ std::optional<SkFont> font;
218
+ std::string text;
219
+ SkPath path;
220
+ float initialOffset;
221
+ };
222
+
223
+ class TextPathCmd : public Command {
224
+ private:
225
+ TextPathProps props;
226
+
227
+ public:
228
+ TextPathCmd(jsi::Runtime &runtime, const jsi::Object &object,
229
+ Variables &variables)
230
+ : Command(CommandType::DrawTextPath) {
231
+ convertProperty(runtime, object, "font", props.font, variables);
232
+ convertProperty(runtime, object, "text", props.text, variables);
233
+ convertProperty(runtime, object, "path", props.path, variables);
234
+ convertProperty(runtime, object, "initialOffset", props.initialOffset,
235
+ variables);
236
+ }
237
+
238
+ void draw(DrawingCtx *ctx) {
239
+ auto [font, text, path, initialOffset] = props;
240
+ if (font.has_value()) {
241
+ // Get glyphs
242
+ auto numGlyphIds =
243
+ font->countText(text.c_str(), text.length(), SkTextEncoding::kUTF8);
244
+
245
+ std::vector<SkGlyphID> glyphIds;
246
+ glyphIds.reserve(numGlyphIds);
247
+ auto ids = font->textToGlyphs(
248
+ text.c_str(), text.length(), SkTextEncoding::kUTF8,
249
+ static_cast<SkGlyphID *>(glyphIds.data()), numGlyphIds);
250
+
251
+ // Get glyph widths
252
+ int glyphsSize = static_cast<int>(ids);
253
+ std::vector<SkScalar> widthPtrs;
254
+ widthPtrs.resize(glyphsSize);
255
+ font->getWidthsBounds(glyphIds.data(), numGlyphIds,
256
+ static_cast<SkScalar *>(widthPtrs.data()), nullptr,
257
+ nullptr); // TODO: Should we use paint somehow here?
258
+
259
+ std::vector<SkRSXform> rsx;
260
+ SkContourMeasureIter meas(path, false, 1);
261
+
262
+ auto cont = meas.next();
263
+ auto dist = initialOffset;
264
+
265
+ for (size_t i = 0; i < text.length() && cont != nullptr; ++i) {
266
+ auto width = widthPtrs[i];
267
+ dist += width / 2;
268
+ if (dist > cont->length()) {
269
+ // jump to next contour
270
+ cont = meas.next();
271
+ if (cont == nullptr) {
272
+ // We have come to the end of the path - terminate the string
273
+ // right here.
274
+ text = text.substr(0, i);
275
+ break;
276
+ }
277
+ dist = width / 2;
278
+ }
279
+ // Gives us the (x, y) coordinates as well as the cos/sin of the
280
+ // tangent line at that position.
281
+ SkPoint pos;
282
+ SkVector tan;
283
+ if (!cont->getPosTan(dist, &pos, &tan)) {
284
+ throw std::runtime_error(
285
+ "Could not calculate distance when resolving text path");
286
+ }
287
+ auto px = pos.x();
288
+ auto py = pos.y();
289
+ auto tx = tan.x();
290
+ auto ty = tan.y();
291
+
292
+ auto adjustedX = px - (width / 2) * tx;
293
+ auto adjustedY = py - (width / 2) * ty;
294
+
295
+ rsx.push_back(SkRSXform::Make(tx, ty, adjustedX, adjustedY));
296
+ dist += width / 2;
297
+ }
298
+
299
+ auto blob = SkTextBlob::MakeFromRSXform(text.c_str(), text.length(),
300
+ rsx.data(), *font);
301
+ ctx->canvas->drawTextBlob(blob, 0, 0, ctx->getPaint());
302
+ }
303
+ }
304
+ };
305
+
306
+ struct TextCmdProps {
307
+ std::optional<SkFont> font;
308
+ std::string text;
309
+ float x;
310
+ float y;
311
+ };
312
+
313
+ class TextCmd : public Command {
314
+ private:
315
+ TextCmdProps props;
316
+
317
+ public:
318
+ TextCmd(jsi::Runtime &runtime, const jsi::Object &object,
319
+ Variables &variables)
320
+ : Command(CommandType::DrawText) {
321
+ convertProperty(runtime, object, "font", props.font, variables);
322
+ convertProperty(runtime, object, "text", props.text, variables);
323
+ convertProperty(runtime, object, "x", props.x, variables);
324
+ convertProperty(runtime, object, "y", props.y, variables);
325
+ }
326
+
327
+ void draw(DrawingCtx *ctx) {
328
+ auto [font, text, x, y] = props;
329
+ auto paint = ctx->getPaint();
330
+ if (font.has_value()) {
331
+ ctx->canvas->drawSimpleText(text.c_str(), text.length(),
332
+ SkTextEncoding::kUTF8, x, y, font.value(),
333
+ paint);
334
+ }
335
+ }
336
+ };
337
+
338
+ // Add to Drawings.h after existing command structures
339
+ struct BoxShadowCmdProps {
340
+ float dx = 0;
341
+ float dy = 0;
342
+ float spread = 0;
343
+ float blur = 0;
344
+ std::optional<SkColor> color;
345
+ std::optional<bool> inner;
346
+ };
347
+
348
+ struct BoxCmdProps {
349
+ std::variant<SkRect, SkRRect> box;
350
+ };
351
+
352
+ class BoxCmd : public Command {
353
+ private:
354
+ BoxCmdProps props;
355
+ std::vector<BoxShadowCmdProps> shadows;
356
+
357
+ // Helper function to inflate RRect (deflate is just negative inflation)
358
+ SkRRect inflate(const SkRRect &box, float dx, float dy, float tx = 0,
359
+ float ty = 0) {
360
+ const auto &rect = box.rect();
361
+ SkRect newRect =
362
+ SkRect::MakeXYWH(rect.x() - dx + tx, rect.y() - dy + ty,
363
+ rect.width() + 2 * dx, rect.height() + 2 * dy);
364
+
365
+ SkRRect result;
366
+ result.setRectXY(newRect, box.radii()[0].fX + dx, box.radii()[0].fY + dy);
367
+ return result;
368
+ }
369
+
370
+ SkRRect deflate(const SkRRect &box, float dx, float dy, float tx = 0,
371
+ float ty = 0) {
372
+ return inflate(box, -dx, -dy, tx, ty);
373
+ }
374
+
375
+ public:
376
+ BoxCmd(jsi::Runtime &runtime, const jsi::Object &object,
377
+ const jsi::Array &shadowsArray, Variables &variables)
378
+ : Command(CommandType::DrawBox) {
379
+
380
+ convertProperty(runtime, object, "box", props.box, variables);
381
+ size_t shadowCount = shadowsArray.size(runtime);
382
+ shadows.reserve(shadowCount);
383
+
384
+ for (size_t i = 0; i < shadowCount; i++) {
385
+ auto shadowObj =
386
+ shadowsArray.getValueAtIndex(runtime, i).asObject(runtime);
387
+ BoxShadowCmdProps shadow;
388
+
389
+ convertProperty(runtime, shadowObj, "dx", shadow.dx, variables);
390
+ convertProperty(runtime, shadowObj, "dy", shadow.dy, variables);
391
+ convertProperty(runtime, shadowObj, "spread", shadow.spread, variables);
392
+ convertProperty(runtime, shadowObj, "blur", shadow.blur, variables);
393
+ convertProperty(runtime, shadowObj, "color", shadow.color, variables);
394
+ convertProperty(runtime, shadowObj, "inner", shadow.inner, variables);
395
+
396
+ shadows.push_back(shadow);
397
+ }
398
+ }
399
+
400
+ void draw(DrawingCtx *ctx) {
401
+
402
+ // Get current paint properties
403
+ auto paint = ctx->getPaint();
404
+ float opacity = paint.getAlphaf();
405
+
406
+ // Convert box to RRect if needed
407
+ SkRRect box;
408
+ if (std::holds_alternative<SkRect>(props.box)) {
409
+ auto rect = std::get<SkRect>(props.box);
410
+ box.setRectXY(rect, 0, 0);
411
+ } else {
412
+ box = std::get<SkRRect>(props.box);
413
+ }
414
+
415
+ // Draw outer shadows first
416
+ for (const auto &shadow : shadows) {
417
+ if (!shadow.inner.value_or(false)) {
418
+ SkPaint shadowPaint;
419
+ shadowPaint.setAntiAlias(true);
420
+ shadowPaint.setColor(shadow.color.value_or(SK_ColorBLACK));
421
+ shadowPaint.setAlphaf(opacity);
422
+ shadowPaint.setMaskFilter(SkMaskFilter::MakeBlur(
423
+ SkBlurStyle::kNormal_SkBlurStyle, shadow.blur, true));
424
+
425
+ auto shadowBox =
426
+ inflate(box, shadow.spread, shadow.spread, shadow.dx, shadow.dy);
427
+ ctx->canvas->drawRRect(shadowBox, shadowPaint);
428
+ }
429
+ }
430
+
431
+ // Draw main box
432
+ ctx->canvas->drawRRect(box, paint);
433
+
434
+ // Draw inner shadows
435
+ for (const auto &shadow : shadows) {
436
+ if (shadow.inner.value_or(false)) {
437
+ ctx->canvas->save();
438
+
439
+ // Clip to box bounds
440
+ ctx->canvas->clipRRect(box, SkClipOp::kIntersect, true);
441
+
442
+ SkPaint shadowPaint;
443
+ shadowPaint.setAntiAlias(true);
444
+ shadowPaint.setColor(shadow.color.value_or(SK_ColorBLACK));
445
+ shadowPaint.setAlphaf(opacity);
446
+ shadowPaint.setMaskFilter(SkMaskFilter::MakeBlur(
447
+ SkBlurStyle::kNormal_SkBlurStyle, shadow.blur, true));
448
+
449
+ // Calculate shadow bounds
450
+ float delta = 10 + std::max(std::abs(shadow.dx), std::abs(shadow.dy));
451
+ auto inner =
452
+ deflate(box, shadow.spread, shadow.spread, shadow.dx, shadow.dy);
453
+ auto outer = inflate(box, delta, delta);
454
+
455
+ ctx->canvas->drawDRRect(outer, inner, shadowPaint);
456
+ ctx->canvas->restore();
457
+ }
458
+ }
459
+ }
460
+ };
461
+
462
+ struct ImageCmdProps {
463
+ float x = 0;
464
+ float y = 0;
465
+ std::optional<float> width;
466
+ std::optional<float> height;
467
+ std::optional<SkRect> rect;
468
+ std::string fit;
469
+ std::optional<sk_sp<SkImage>> image;
470
+ std::optional<SkSamplingOptions> sampling;
471
+ };
472
+
473
+ class ImageCmd : public Command {
474
+ private:
475
+ ImageCmdProps props;
476
+
477
+ public:
478
+ ImageCmd(jsi::Runtime &runtime, const jsi::Object &object,
479
+ Variables &variables)
480
+ : Command(CommandType::DrawImage) {
481
+ convertProperty(runtime, object, "rect", props.rect, variables);
482
+ convertProperty(runtime, object, "image", props.image, variables);
483
+ convertProperty(runtime, object, "sampling", props.sampling, variables);
484
+
485
+ convertProperty(runtime, object, "fit", props.fit, variables);
486
+ convertProperty(runtime, object, "x", props.x, variables);
487
+ convertProperty(runtime, object, "y", props.y, variables);
488
+ convertProperty(runtime, object, "width", props.width, variables);
489
+ convertProperty(runtime, object, "height", props.height, variables);
490
+ convertProperty(runtime, object, "rect", props.rect, variables);
491
+ }
492
+
493
+ void draw(DrawingCtx *ctx) {
494
+ auto [x, y, width, height, rect, fit, image, sampling] = props;
495
+ if (image.has_value()) {
496
+ auto img = image.value();
497
+ auto hasRect =
498
+ rect.has_value() || (width.has_value() && height.has_value());
499
+ if (hasRect) {
500
+ auto src = SkRect::MakeXYWH(0, 0, img->width(), img->height());
501
+ auto dst = rect.has_value()
502
+ ? rect.value()
503
+ : SkRect::MakeXYWH(x, y, width.value(), height.value());
504
+ auto rects = RNSkiaImage::fitRects(fit, src, dst);
505
+ ctx->canvas->drawImageRect(
506
+ img, rects.src, rects.dst,
507
+ sampling.value_or(SkSamplingOptions(SkFilterMode::kLinear)),
508
+ &(ctx->getPaint()), SkCanvas::kStrict_SrcRectConstraint);
509
+ } else {
510
+ throw std::runtime_error(
511
+ "Image node could not resolve image dimension props.");
512
+ }
513
+ }
514
+ }
515
+ };
516
+
517
+ struct PointsCmdProps {
518
+ std::vector<SkPoint> points;
519
+ SkCanvas::PointMode mode;
520
+ };
521
+
522
+ class PointsCmd : public Command {
523
+ private:
524
+ PointsCmdProps props;
525
+
526
+ public:
527
+ PointsCmd(jsi::Runtime &runtime, const jsi::Object &object,
528
+ Variables &variables)
529
+ : Command(CommandType::DrawPoints) {
530
+ convertProperty(runtime, object, "points", props.points, variables);
531
+ convertProperty(runtime, object, "mode", props.mode, variables);
532
+ }
533
+
534
+ void draw(DrawingCtx *ctx) {
535
+ ctx->canvas->drawPoints(props.mode, props.points.size(),
536
+ props.points.data(), ctx->getPaint());
537
+ }
538
+ };
539
+
540
+ struct RRectCmdProps {
541
+ std::optional<SkRRect> rect;
542
+ float x = 0;
543
+ float y = 0;
544
+ std::optional<float> width;
545
+ std::optional<float> height;
546
+ std::optional<Radius> r;
547
+ };
548
+
549
+ class RRectCmd : public Command {
550
+ private:
551
+ RRectCmdProps props;
552
+
553
+ public:
554
+ RRectCmd(jsi::Runtime &runtime, const jsi::Object &object,
555
+ Variables &variables)
556
+ : Command(CommandType::DrawRRect) {
557
+ convertProperty(runtime, object, "rect", props.rect, variables);
558
+ convertProperty(runtime, object, "x", props.x, variables);
559
+ convertProperty(runtime, object, "y", props.y, variables);
560
+ convertProperty(runtime, object, "width", props.width, variables);
561
+ convertProperty(runtime, object, "height", props.height, variables);
562
+ convertProperty(runtime, object, "r", props.r, variables);
563
+ }
564
+
565
+ void draw(DrawingCtx *ctx) {
566
+ auto [rect, x, y, width, height, r] = props;
567
+ if (rect.has_value()) {
568
+ ctx->canvas->drawRRect(rect.value(), ctx->getPaint());
569
+ } else {
570
+ if (!width.has_value() || !height.has_value() || !r.has_value()) {
571
+ throw std::runtime_error("Invalid properties for rounded rect");
572
+ }
573
+ auto rct = SkRRect::MakeRectXY(
574
+ SkRect::MakeXYWH(x, y, width.value(), height.value()), r.value().rX,
575
+ r.value().rY);
576
+ ctx->canvas->drawRRect(rct, ctx->getPaint());
577
+ }
578
+ }
579
+ };
580
+
581
+ struct OvalCmdProps {
582
+ std::optional<SkRect> rect;
583
+ float x = 0;
584
+ float y = 0;
585
+ std::optional<float> width;
586
+ std::optional<float> height;
587
+ };
588
+
589
+ class OvalCmd : public Command {
590
+ private:
591
+ OvalCmdProps props;
592
+
593
+ public:
594
+ OvalCmd(jsi::Runtime &runtime, const jsi::Object &object,
595
+ Variables &variables)
596
+ : Command(CommandType::DrawOval) {
597
+ convertProperty(runtime, object, "x", props.x, variables);
598
+ convertProperty(runtime, object, "y", props.y, variables);
599
+ convertProperty(runtime, object, "width", props.width, variables);
600
+ convertProperty(runtime, object, "height", props.height, variables);
601
+ convertProperty(runtime, object, "rect", props.rect, variables);
602
+ }
603
+
604
+ void draw(DrawingCtx *ctx) {
605
+ auto [rect, x, y, width, height] = props;
606
+ if (rect.has_value()) {
607
+ ctx->canvas->drawOval(rect.value(), ctx->getPaint());
608
+ } else {
609
+ if (!width.has_value() || !height.has_value()) {
610
+ throw std::runtime_error("Invalid properties received for Oval");
611
+ }
612
+ auto rct = SkRect::MakeXYWH(x, y, width.value(), height.value());
613
+ ctx->canvas->drawOval(rct, ctx->getPaint());
614
+ }
615
+ }
616
+ };
617
+
618
+ struct PatchCmdProps {
619
+ Patch patch;
620
+ std::optional<std::vector<SkColor>> colors;
621
+ std::optional<std::vector<SkPoint>> texture;
622
+ std::optional<SkBlendMode> blendMode;
623
+ };
624
+
625
+ class PatchCmd : public Command {
626
+ private:
627
+ PatchCmdProps props;
628
+
629
+ public:
630
+ PatchCmd(jsi::Runtime &runtime, const jsi::Object &object,
631
+ Variables &variables)
632
+ : Command(CommandType::DrawPatch) {
633
+ convertProperty(runtime, object, "patch", props.patch, variables);
634
+ convertProperty(runtime, object, "colors", props.colors, variables);
635
+ convertProperty(runtime, object, "texture", props.texture, variables);
636
+ convertProperty(runtime, object, "blendMode", props.blendMode, variables);
637
+ }
638
+
639
+ void draw(DrawingCtx *ctx) {
640
+ // Determine default blend mode based on presence of colors
641
+ SkBlendMode defaultBlendMode = props.colors.has_value()
642
+ ? SkBlendMode::kDstOver
643
+ : SkBlendMode::kSrcOver;
644
+
645
+ ctx->canvas->drawPatch(
646
+ props.patch.data(),
647
+ props.colors.has_value() ? props.colors.value().data() : nullptr,
648
+ props.texture.has_value() ? props.texture.value().data() : nullptr,
649
+ props.blendMode.value_or(defaultBlendMode), ctx->getPaint());
650
+ }
651
+ };
652
+
653
+ struct VerticesCmdProps {
654
+ std::vector<SkPoint> vertices;
655
+ std::optional<std::vector<SkColor>> colors;
656
+ std::optional<std::vector<SkPoint>> textures;
657
+ SkVertices::VertexMode mode;
658
+ std::optional<SkBlendMode> blendMode;
659
+ std::optional<std::vector<uint16_t>> indices;
660
+ };
661
+
662
+ class VerticesCmd : public Command {
663
+ private:
664
+ VerticesCmdProps props;
665
+
666
+ public:
667
+ VerticesCmd(jsi::Runtime &runtime, const jsi::Object &object,
668
+ Variables &variables)
669
+ : Command(CommandType::DrawVertices) {
670
+ convertProperty(runtime, object, "vertices", props.vertices, variables);
671
+ convertProperty(runtime, object, "colors", props.colors, variables);
672
+ convertProperty(runtime, object, "textures", props.textures, variables);
673
+ convertProperty(runtime, object, "mode", props.mode, variables);
674
+ convertProperty(runtime, object, "blendMode", props.blendMode, variables);
675
+ convertProperty(runtime, object, "indices", props.indices, variables);
676
+ }
677
+
678
+ void draw(DrawingCtx *ctx) {
679
+ // Create vertices using MakeCopy
680
+ auto vertices = SkVertices::MakeCopy(
681
+ props.mode, static_cast<int>(props.vertices.size()),
682
+ props.vertices.data(),
683
+ props.textures.has_value() ? props.textures.value().data() : nullptr,
684
+ props.colors.has_value() ? props.colors.value().data() : nullptr,
685
+ props.indices.has_value()
686
+ ? static_cast<int>(props.indices.value().size())
687
+ : 0,
688
+ props.indices.has_value() ? props.indices.value().data() : nullptr);
689
+
690
+ // Determine blend mode - use DstOver if colors are provided, SrcOver
691
+ // otherwise
692
+ const auto defaultBlendMode = props.colors.has_value()
693
+ ? SkBlendMode::kDstOver
694
+ : SkBlendMode::kSrcOver;
695
+
696
+ // Draw the vertices with the determined blend mode
697
+ ctx->canvas->drawVertices(
698
+ vertices, props.blendMode.value_or(defaultBlendMode), ctx->getPaint());
699
+ }
700
+ };
701
+
702
+ struct DiffRectCmdProps {
703
+ SkRRect outer;
704
+ SkRRect inner;
705
+ };
706
+
707
+ class DiffRectCmd : public Command {
708
+ private:
709
+ DiffRectCmdProps props;
710
+
711
+ public:
712
+ DiffRectCmd(jsi::Runtime &runtime, const jsi::Object &object,
713
+ Variables &variables)
714
+ : Command(CommandType::DrawDiffRect) {
715
+ convertProperty(runtime, object, "outer", props.outer, variables);
716
+ convertProperty(runtime, object, "inner", props.inner, variables);
717
+ }
718
+
719
+ void draw(DrawingCtx *ctx) {
720
+ ctx->canvas->drawDRRect(props.outer, props.inner, ctx->getPaint());
721
+ }
722
+ };
723
+
724
+ struct TextBlobCmdProps {
725
+ sk_sp<SkTextBlob> blob;
726
+ float x;
727
+ float y;
728
+ };
729
+
730
+ class TextBlobCmd : public Command {
731
+ private:
732
+ TextBlobCmdProps props;
733
+
734
+ public:
735
+ TextBlobCmd(jsi::Runtime &runtime, const jsi::Object &object,
736
+ Variables &variables)
737
+ : Command(CommandType::DrawTextBlob) {
738
+ convertProperty(runtime, object, "blob", props.blob, variables);
739
+ convertProperty(runtime, object, "x", props.x, variables);
740
+ convertProperty(runtime, object, "y", props.y, variables);
741
+ }
742
+
743
+ void draw(DrawingCtx *ctx) {
744
+ ctx->canvas->drawTextBlob(props.blob, props.x, props.y, ctx->getPaint());
745
+ }
746
+ };
747
+
748
+ struct GlyphData {
749
+ std::vector<SkGlyphID> glyphIds;
750
+ std::vector<SkPoint> positions;
751
+ };
752
+
753
+ struct GlyphsCmdProps {
754
+ std::optional<SkFont> font;
755
+ float x;
756
+ float y;
757
+ // GlyphData glyphs;
758
+ };
759
+
760
+ class GlyphsCmd : public Command {
761
+ private:
762
+ GlyphsCmdProps props;
763
+
764
+ public:
765
+ GlyphsCmd(jsi::Runtime &runtime, const jsi::Object &object,
766
+ Variables &variables)
767
+ : Command(CommandType::DrawGlyphs) {
768
+ convertProperty(runtime, object, "font", props.font, variables);
769
+ convertProperty(runtime, object, "x", props.x, variables);
770
+ convertProperty(runtime, object, "y", props.y, variables);
771
+ // convertProperty(runtime, object, "glyphs", props.glyphs, variables);
772
+ }
773
+
774
+ void draw(DrawingCtx *ctx) {
775
+ if (props.font.has_value()) {
776
+ // std::vector<uint16_t> glyphIds;
777
+ // std::vector<SkPoint> positions;
778
+ // for (const auto &[id, pos] : props.glyphs) {
779
+ // glyphIds.push_back(id);
780
+ // positions.push_back(pos);
781
+ // }
782
+ // ctx->canvas->drawGlyphs(
783
+ // static_cast<int>(props.glyphs.glyphIds.size()),
784
+ // props.glyphs.glyphIds.data(), props.glyphs.positions.data(),
785
+ // SkPoint::Make(props.x, props.y), props.font.value(),
786
+ // ctx->getPaint());
787
+ }
788
+ }
789
+ };
790
+
791
+ struct PictureCmdProps {
792
+ sk_sp<SkPicture> picture;
793
+ };
794
+
795
+ class PictureCmd : public Command {
796
+ private:
797
+ PictureCmdProps props;
798
+
799
+ public:
800
+ PictureCmd(jsi::Runtime &runtime, const jsi::Object &object,
801
+ Variables &variables)
802
+ : Command(CommandType::DrawPicture) {
803
+ convertProperty(runtime, object, "picture", props.picture, variables);
804
+ }
805
+
806
+ void draw(DrawingCtx *ctx) { ctx->canvas->drawPicture(props.picture); }
807
+ };
808
+
809
+ struct ImageSVGCmdProps {
810
+ sk_sp<SkSVGDOM> svg;
811
+ std::optional<float> x;
812
+ std::optional<float> y;
813
+ std::optional<float> width;
814
+ std::optional<float> height;
815
+ std::optional<SkRect> rect;
816
+ };
817
+
818
+ class ImageSVGCmd : public Command {
819
+ private:
820
+ ImageSVGCmdProps props;
821
+
822
+ public:
823
+ ImageSVGCmd(jsi::Runtime &runtime, const jsi::Object &object,
824
+ Variables &variables)
825
+ : Command(CommandType::DrawImageSVG) {
826
+ // Convert SVG property - expect a host object of JsiSkSVG type
827
+ auto svgValue = object.getProperty(runtime, "svg");
828
+ if (svgValue.isObject() &&
829
+ svgValue.asObject(runtime).isHostObject(runtime)) {
830
+ auto ptr = std::dynamic_pointer_cast<JsiSkSVG>(
831
+ svgValue.asObject(runtime).asHostObject(runtime));
832
+ if (ptr != nullptr) {
833
+ props.svg = ptr->getObject();
834
+ } else {
835
+ throw std::runtime_error(
836
+ "Expected SkSvgDom object for the svg property.");
837
+ }
838
+ }
839
+
840
+ // Convert other properties
841
+ convertProperty(runtime, object, "x", props.x, variables);
842
+ convertProperty(runtime, object, "y", props.y, variables);
843
+ convertProperty(runtime, object, "width", props.width, variables);
844
+ convertProperty(runtime, object, "height", props.height, variables);
845
+ convertProperty(runtime, object, "rect", props.rect, variables);
846
+ }
847
+
848
+ void draw(DrawingCtx *ctx) {
849
+ if (props.svg != nullptr) {
850
+ ctx->canvas->save();
851
+
852
+ if (props.rect.has_value()) {
853
+ // If rect is provided, use it for translation and container size
854
+ auto rect = props.rect.value();
855
+ ctx->canvas->translate(rect.x(), rect.y());
856
+ props.svg->setContainerSize(SkSize::Make(rect.width(), rect.height()));
857
+ } else {
858
+ // Otherwise use individual x, y, width, height properties
859
+ float x = props.x.value_or(-1);
860
+ float y = props.y.value_or(-1);
861
+ float width = props.width.value_or(-1);
862
+ float height = props.height.value_or(-1);
863
+
864
+ if (x != -1 && y != -1) {
865
+ ctx->canvas->translate(x, y);
866
+ }
867
+
868
+ if (width != -1 && height != -1) {
869
+ props.svg->setContainerSize(SkSize::Make(width, height));
870
+ }
871
+ }
872
+
873
+ // Render the SVG
874
+ props.svg->render(ctx->canvas);
875
+ ctx->canvas->restore();
876
+ }
877
+ }
878
+ };
879
+
880
+ struct ParagraphCmdProps {
881
+ para::Paragraph *paragraph;
882
+ float x;
883
+ float y;
884
+ float width;
885
+ };
886
+
887
+ class ParagraphCmd : public Command {
888
+ private:
889
+ ParagraphCmdProps props;
890
+
891
+ public:
892
+ ParagraphCmd(jsi::Runtime &runtime, const jsi::Object &object,
893
+ Variables &variables)
894
+ : Command(CommandType::DrawParagraph) {
895
+ convertProperty(runtime, object, "paragraph", props.paragraph, variables);
896
+ convertProperty(runtime, object, "x", props.x, variables);
897
+ convertProperty(runtime, object, "y", props.y, variables);
898
+ convertProperty(runtime, object, "width", props.width, variables);
899
+ }
900
+
901
+ void draw(DrawingCtx *ctx) {
902
+ if (props.paragraph) {
903
+ props.paragraph->layout(props.width);
904
+ props.paragraph->paint(ctx->canvas, props.x, props.y);
905
+ }
906
+ }
907
+ };
908
+
909
+ struct AtlasCmdProps {
910
+ sk_sp<SkImage> image;
911
+ std::vector<SkRect> sprites;
912
+ std::vector<SkRSXform> transforms;
913
+ std::optional<std::vector<SkColor>> colors;
914
+ std::optional<SkBlendMode> blendMode;
915
+ std::optional<SkSamplingOptions> sampling;
916
+ };
917
+
918
+ class AtlasCmd : public Command {
919
+ private:
920
+ AtlasCmdProps props;
921
+
922
+ public:
923
+ AtlasCmd(jsi::Runtime &runtime, const jsi::Object &object,
924
+ Variables &variables)
925
+ : Command(CommandType::DrawAtlas) {
926
+ convertProperty(runtime, object, "image", props.image, variables);
927
+ convertProperty(runtime, object, "sprites", props.sprites, variables);
928
+ convertProperty(runtime, object, "transforms", props.transforms, variables);
929
+ convertProperty(runtime, object, "colors", props.colors, variables);
930
+ convertProperty(runtime, object, "blendMode", props.blendMode, variables);
931
+ convertProperty(runtime, object, "sampling", props.sampling, variables);
932
+ }
933
+
934
+ void draw(DrawingCtx *ctx) {
935
+ if (props.image) {
936
+ auto colors =
937
+ props.colors.has_value() ? props.colors.value().data() : nullptr;
938
+ auto blendMode = props.blendMode.value_or(SkBlendMode::kDstOver);
939
+ auto sampling =
940
+ props.sampling.value_or(SkSamplingOptions(SkFilterMode::kLinear));
941
+
942
+ ctx->canvas->drawAtlas(props.image.get(), props.transforms.data(),
943
+ props.sprites.data(), colors,
944
+ props.transforms.size(), blendMode, sampling,
945
+ nullptr, &(ctx->getPaint()));
946
+ }
947
+ }
948
+ };
949
+
950
+ } // namespace RNSkia