@shopify/react-native-skia 1.10.2 → 1.11.1

Sign up to get free protection for your applications and to get access to all the features.
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