@shopify/react-native-skia 1.10.1 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java +0 -13
  2. package/android/src/main/java/com/shopify/reactnative/skia/SkiaTextureView.java +1 -24
  3. package/cpp/api/JsiSkApi.h +3 -0
  4. package/cpp/api/recorder/ColorFilters.h +133 -0
  5. package/cpp/api/recorder/Command.h +58 -0
  6. package/cpp/api/recorder/Convertor.h +1212 -0
  7. package/cpp/api/recorder/DataTypes.h +234 -0
  8. package/cpp/api/recorder/DrawingCtx.h +187 -0
  9. package/cpp/api/recorder/Drawings.h +949 -0
  10. package/cpp/api/recorder/Image.h +108 -0
  11. package/cpp/api/recorder/ImageFilters.h +292 -0
  12. package/cpp/api/recorder/JsiRecorder.h +314 -0
  13. package/cpp/api/recorder/Paint.h +191 -0
  14. package/cpp/api/recorder/PathEffects.h +194 -0
  15. package/cpp/api/recorder/RNRecorder.h +635 -0
  16. package/cpp/api/recorder/Shaders.h +406 -0
  17. package/cpp/rnskia/dom/nodes/JsiAtlasNode.h +3 -2
  18. package/cpp/rnskia/dom/nodes/JsiImageNode.h +3 -2
  19. package/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm +4 -8
  20. package/jestSetup.js +8 -0
  21. package/jestSetup.mjs +8 -0
  22. package/lib/commonjs/renderer/components/image/Image.d.ts +1 -1
  23. package/lib/commonjs/renderer/components/image/Image.js +8 -2
  24. package/lib/commonjs/renderer/components/image/Image.js.map +1 -1
  25. package/lib/commonjs/skia/types/Recorder.d.ts +52 -0
  26. package/lib/commonjs/skia/types/Recorder.js +6 -0
  27. package/lib/commonjs/skia/types/Recorder.js.map +1 -0
  28. package/lib/commonjs/skia/types/Skia.d.ts +2 -0
  29. package/lib/commonjs/skia/types/Skia.js.map +1 -1
  30. package/lib/commonjs/skia/types/index.d.ts +1 -0
  31. package/lib/commonjs/skia/types/index.js +11 -0
  32. package/lib/commonjs/skia/types/index.js.map +1 -1
  33. package/lib/commonjs/skia/web/JsiSkia.js +3 -0
  34. package/lib/commonjs/skia/web/JsiSkia.js.map +1 -1
  35. package/lib/commonjs/sksg/Container.d.ts +6 -1
  36. package/lib/commonjs/sksg/Container.js +59 -2
  37. package/lib/commonjs/sksg/Container.js.map +1 -1
  38. package/lib/commonjs/sksg/Recorder/DrawingContext.js +1 -0
  39. package/lib/commonjs/sksg/Recorder/DrawingContext.js.map +1 -1
  40. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +53 -0
  41. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js +189 -0
  42. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js.map +1 -0
  43. package/lib/commonjs/sksg/Recorder/Recorder.d.ts +2 -2
  44. package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
  45. package/lib/commonjs/sksg/Recorder/Visitor.d.ts +2 -2
  46. package/lib/commonjs/sksg/Recorder/Visitor.js +2 -2
  47. package/lib/commonjs/sksg/Recorder/Visitor.js.map +1 -1
  48. package/lib/module/renderer/components/image/Image.d.ts +1 -1
  49. package/lib/module/renderer/components/image/Image.js +8 -2
  50. package/lib/module/renderer/components/image/Image.js.map +1 -1
  51. package/lib/module/skia/types/Recorder.d.ts +52 -0
  52. package/lib/module/skia/types/Recorder.js +2 -0
  53. package/lib/module/skia/types/Recorder.js.map +1 -0
  54. package/lib/module/skia/types/Skia.d.ts +2 -0
  55. package/lib/module/skia/types/Skia.js.map +1 -1
  56. package/lib/module/skia/types/index.d.ts +1 -0
  57. package/lib/module/skia/types/index.js +1 -0
  58. package/lib/module/skia/types/index.js.map +1 -1
  59. package/lib/module/skia/web/JsiSkia.js +3 -0
  60. package/lib/module/skia/web/JsiSkia.js.map +1 -1
  61. package/lib/module/sksg/Container.d.ts +6 -1
  62. package/lib/module/sksg/Container.js +59 -2
  63. package/lib/module/sksg/Container.js.map +1 -1
  64. package/lib/module/sksg/Recorder/DrawingContext.js +1 -0
  65. package/lib/module/sksg/Recorder/DrawingContext.js.map +1 -1
  66. package/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +53 -0
  67. package/lib/module/sksg/Recorder/ReanimatedRecorder.js +182 -0
  68. package/lib/module/sksg/Recorder/ReanimatedRecorder.js.map +1 -0
  69. package/lib/module/sksg/Recorder/Recorder.d.ts +2 -2
  70. package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
  71. package/lib/module/sksg/Recorder/Visitor.d.ts +2 -2
  72. package/lib/module/sksg/Recorder/Visitor.js +2 -2
  73. package/lib/module/sksg/Recorder/Visitor.js.map +1 -1
  74. package/lib/typescript/lib/commonjs/renderer/components/image/Image.d.ts +4 -1
  75. package/lib/typescript/lib/commonjs/skia/types/Recorder.d.ts +1 -0
  76. package/lib/typescript/lib/commonjs/skia/web/JsiSkia.d.ts +1 -0
  77. package/lib/typescript/lib/commonjs/sksg/Container.d.ts +5 -1
  78. package/lib/typescript/lib/commonjs/sksg/Reconciler.d.ts +6 -0
  79. package/lib/typescript/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +47 -0
  80. package/lib/typescript/lib/module/mock/index.d.ts +4 -1
  81. package/lib/typescript/lib/module/renderer/components/image/Image.d.ts +4 -1
  82. package/lib/typescript/lib/module/skia/Skia.web.d.ts +1 -0
  83. package/lib/typescript/lib/module/skia/types/Recorder.d.ts +1 -0
  84. package/lib/typescript/lib/module/skia/types/index.d.ts +1 -0
  85. package/lib/typescript/lib/module/skia/web/JsiSkia.d.ts +1 -0
  86. package/lib/typescript/lib/module/sksg/Container.d.ts +5 -1
  87. package/lib/typescript/lib/module/sksg/Reconciler.d.ts +6 -0
  88. package/lib/typescript/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +46 -0
  89. package/lib/typescript/src/renderer/components/image/Image.d.ts +1 -1
  90. package/lib/typescript/src/skia/types/Recorder.d.ts +52 -0
  91. package/lib/typescript/src/skia/types/Skia.d.ts +2 -0
  92. package/lib/typescript/src/skia/types/index.d.ts +1 -0
  93. package/lib/typescript/src/sksg/Container.d.ts +6 -1
  94. package/lib/typescript/src/sksg/Recorder/ReanimatedRecorder.d.ts +53 -0
  95. package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +2 -2
  96. package/lib/typescript/src/sksg/Recorder/Visitor.d.ts +2 -2
  97. package/package.json +3 -2
  98. package/src/renderer/components/image/Image.tsx +2 -2
  99. package/src/skia/types/Recorder.ts +91 -0
  100. package/src/skia/types/Skia.ts +2 -0
  101. package/src/skia/types/index.ts +1 -0
  102. package/src/skia/web/JsiSkia.ts +3 -0
  103. package/src/sksg/Container.ts +63 -4
  104. package/src/sksg/Recorder/DrawingContext.ts +1 -0
  105. package/src/sksg/Recorder/ReanimatedRecorder.ts +271 -0
  106. package/src/sksg/Recorder/Recorder.ts +2 -2
  107. package/src/sksg/Recorder/Visitor.ts +17 -12
@@ -0,0 +1,949 @@
1
+ #pragma once
2
+
3
+ #include <optional>
4
+
5
+ #include "Command.h"
6
+ #include "Convertor.h"
7
+ #include "DrawingCtx.h"
8
+ #include "Image.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, float ty = 0) {
359
+ const auto& rect = box.rect();
360
+ SkRect newRect = SkRect::MakeXYWH(
361
+ rect.x() - dx + tx,
362
+ rect.y() - dy + ty,
363
+ rect.width() + 2 * dx,
364
+ rect.height() + 2 * dy
365
+ );
366
+
367
+ SkRRect result;
368
+ result.setRectXY(newRect, box.radii()[0].fX + dx, box.radii()[0].fY + dy);
369
+ return result;
370
+ }
371
+
372
+ SkRRect deflate(const SkRRect& box, float dx, float dy, float tx = 0, float ty = 0) {
373
+ return inflate(box, -dx, -dy, tx, ty);
374
+ }
375
+
376
+ public:
377
+ BoxCmd(jsi::Runtime &runtime, const jsi::Object &object, const jsi::Array &shadowsArray,
378
+ Variables &variables)
379
+ : Command(CommandType::DrawBox) {
380
+
381
+ convertProperty(runtime, object, "box", props.box, variables);
382
+ size_t shadowCount = shadowsArray.size(runtime);
383
+ shadows.reserve(shadowCount);
384
+
385
+ for (size_t i = 0; i < shadowCount; i++) {
386
+ auto shadowObj = 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
+
403
+ // Get current paint properties
404
+ auto paint = ctx->getPaint();
405
+ float opacity = paint.getAlphaf();
406
+
407
+ // Convert box to RRect if needed
408
+ SkRRect box;
409
+ if (std::holds_alternative<SkRect>(props.box)) {
410
+ auto rect = std::get<SkRect>(props.box);
411
+ box.setRectXY(rect, 0, 0);
412
+ } else {
413
+ box = std::get<SkRRect>(props.box);
414
+ }
415
+
416
+ // Draw outer shadows first
417
+ for (const auto& shadow : shadows) {
418
+ if (!shadow.inner.value_or(false)) {
419
+ SkPaint shadowPaint;
420
+ shadowPaint.setAntiAlias(true);
421
+ shadowPaint.setColor(shadow.color.value_or(SK_ColorBLACK));
422
+ shadowPaint.setAlphaf(opacity);
423
+ shadowPaint.setMaskFilter(SkMaskFilter::MakeBlur(
424
+ SkBlurStyle::kNormal_SkBlurStyle, shadow.blur, true));
425
+
426
+ auto shadowBox = 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 = deflate(box, shadow.spread, shadow.spread, shadow.dx, shadow.dy);
452
+ auto outer = inflate(box, delta, delta);
453
+
454
+ ctx->canvas->drawDRRect(outer, inner, shadowPaint);
455
+ ctx->canvas->restore();
456
+ }
457
+ }
458
+ }
459
+ };
460
+
461
+ struct ImageCmdProps {
462
+ float x = 0;
463
+ float y = 0;
464
+ std::optional<float> width;
465
+ std::optional<float> height;
466
+ std::optional<SkRect> rect;
467
+ std::string fit;
468
+ std::optional<sk_sp<SkImage>> image;
469
+ std::optional<SkSamplingOptions> sampling;
470
+ };
471
+
472
+ class ImageCmd : public Command {
473
+ private:
474
+ ImageCmdProps props;
475
+
476
+ public:
477
+ ImageCmd(jsi::Runtime &runtime, const jsi::Object &object,
478
+ Variables &variables)
479
+ : Command(CommandType::DrawImage) {
480
+ convertProperty(runtime, object, "rect", props.rect, variables);
481
+ convertProperty(runtime, object, "image", props.image, variables);
482
+ convertProperty(runtime, object, "sampling", props.sampling, variables);
483
+
484
+ convertProperty(runtime, object, "fit", props.fit, variables);
485
+ convertProperty(runtime, object, "x", props.x, variables);
486
+ convertProperty(runtime, object, "y", props.y, variables);
487
+ convertProperty(runtime, object, "width", props.width, variables);
488
+ convertProperty(runtime, object, "height", props.height, variables);
489
+ convertProperty(runtime, object, "rect", props.rect, variables);
490
+ }
491
+
492
+ void draw(DrawingCtx *ctx) {
493
+ auto [x, y, width, height, rect, fit, image, sampling] = props;
494
+ if (image.has_value()) {
495
+ auto img = image.value();
496
+ auto hasRect =
497
+ rect.has_value() || (width.has_value() && height.has_value());
498
+ if (hasRect) {
499
+ auto src = SkRect::MakeXYWH(0, 0, img->width(), img->height());
500
+ auto dst = rect.has_value()
501
+ ? rect.value()
502
+ : SkRect::MakeXYWH(x, y, width.value(), height.value());
503
+ auto rects = RNSkiaImage::fitRects(fit, src, dst);
504
+ ctx->canvas->drawImageRect(
505
+ img, rects.src, rects.dst,
506
+ sampling.value_or(SkSamplingOptions(SkFilterMode::kLinear)),
507
+ &(ctx->getPaint()), SkCanvas::kStrict_SrcRectConstraint);
508
+ } else {
509
+ throw std::runtime_error(
510
+ "Image node could not resolve image dimension props.");
511
+ }
512
+ }
513
+ }
514
+ };
515
+
516
+ struct PointsCmdProps {
517
+ std::vector<SkPoint> points;
518
+ SkCanvas::PointMode mode;
519
+ };
520
+
521
+ class PointsCmd : public Command {
522
+ private:
523
+ PointsCmdProps props;
524
+
525
+ public:
526
+ PointsCmd(jsi::Runtime &runtime, const jsi::Object &object,
527
+ Variables &variables)
528
+ : Command(CommandType::DrawPoints) {
529
+ convertProperty(runtime, object, "points", props.points, variables);
530
+ convertProperty(runtime, object, "mode", props.mode, variables);
531
+ }
532
+
533
+ void draw(DrawingCtx *ctx) {
534
+ ctx->canvas->drawPoints(props.mode, props.points.size(),
535
+ props.points.data(), ctx->getPaint());
536
+ }
537
+ };
538
+
539
+ struct RRectCmdProps {
540
+ std::optional<SkRRect> rect;
541
+ float x = 0;
542
+ float y = 0;
543
+ std::optional<float> width;
544
+ std::optional<float> height;
545
+ std::optional<Radius> r;
546
+ };
547
+
548
+ class RRectCmd : public Command {
549
+ private:
550
+ RRectCmdProps props;
551
+
552
+ public:
553
+ RRectCmd(jsi::Runtime &runtime, const jsi::Object &object,
554
+ Variables &variables)
555
+ : Command(CommandType::DrawRRect) {
556
+ convertProperty(runtime, object, "rect", props.rect, variables);
557
+ convertProperty(runtime, object, "x", props.x, variables);
558
+ convertProperty(runtime, object, "y", props.y, variables);
559
+ convertProperty(runtime, object, "width", props.width, variables);
560
+ convertProperty(runtime, object, "height", props.height, variables);
561
+ convertProperty(runtime, object, "r", props.r, variables);
562
+ }
563
+
564
+ void draw(DrawingCtx *ctx) {
565
+ auto [rect, x, y, width, height, r] = props;
566
+ if (rect.has_value()) {
567
+ ctx->canvas->drawRRect(rect.value(), ctx->getPaint());
568
+ } else {
569
+ if (!width.has_value() || !height.has_value() || !r.has_value()) {
570
+ throw std::runtime_error("Invalid properties for rounded rect");
571
+ }
572
+ auto rct = SkRRect::MakeRectXY(
573
+ SkRect::MakeXYWH(x, y, width.value(), height.value()), r.value().rX,
574
+ r.value().rY);
575
+ ctx->canvas->drawRRect(rct, ctx->getPaint());
576
+ }
577
+ }
578
+ };
579
+
580
+ struct OvalCmdProps {
581
+ std::optional<SkRect> rect;
582
+ float x = 0;
583
+ float y = 0;
584
+ std::optional<float> width;
585
+ std::optional<float> height;
586
+ };
587
+
588
+ class OvalCmd : public Command {
589
+ private:
590
+ OvalCmdProps props;
591
+
592
+ public:
593
+ OvalCmd(jsi::Runtime &runtime, const jsi::Object &object,
594
+ Variables &variables)
595
+ : Command(CommandType::DrawOval) {
596
+ convertProperty(runtime, object, "x", props.x, variables);
597
+ convertProperty(runtime, object, "y", props.y, variables);
598
+ convertProperty(runtime, object, "width", props.width, variables);
599
+ convertProperty(runtime, object, "height", props.height, variables);
600
+ convertProperty(runtime, object, "rect", props.rect, variables);
601
+ }
602
+
603
+ void draw(DrawingCtx *ctx) {
604
+ auto [rect, x, y, width, height] = props;
605
+ if (rect.has_value()) {
606
+ ctx->canvas->drawOval(rect.value(), ctx->getPaint());
607
+ } else {
608
+ if (!width.has_value() || !height.has_value()) {
609
+ throw std::runtime_error("Invalid properties received for Oval");
610
+ }
611
+ auto rct = SkRect::MakeXYWH(x, y, width.value(), height.value());
612
+ ctx->canvas->drawOval(rct, ctx->getPaint());
613
+ }
614
+ }
615
+ };
616
+
617
+ struct PatchCmdProps {
618
+ Patch patch;
619
+ std::optional<std::vector<SkColor>> colors;
620
+ std::optional<std::vector<SkPoint>> texture;
621
+ std::optional<SkBlendMode> blendMode;
622
+ };
623
+
624
+ class PatchCmd : public Command {
625
+ private:
626
+ PatchCmdProps props;
627
+
628
+ public:
629
+ PatchCmd(jsi::Runtime &runtime, const jsi::Object &object,
630
+ Variables &variables)
631
+ : Command(CommandType::DrawPatch) {
632
+ convertProperty(runtime, object, "patch", props.patch, variables);
633
+ convertProperty(runtime, object, "colors", props.colors, variables);
634
+ convertProperty(runtime, object, "texture", props.texture, variables);
635
+ convertProperty(runtime, object, "blendMode", props.blendMode, variables);
636
+ }
637
+
638
+ void draw(DrawingCtx *ctx) {
639
+ // Determine default blend mode based on presence of colors
640
+ SkBlendMode defaultBlendMode = props.colors.has_value()
641
+ ? SkBlendMode::kDstOver
642
+ : SkBlendMode::kSrcOver;
643
+
644
+ ctx->canvas->drawPatch(
645
+ props.patch.data(),
646
+ props.colors.has_value() ? props.colors.value().data() : nullptr,
647
+ props.texture.has_value() ? props.texture.value().data() : nullptr,
648
+ props.blendMode.value_or(defaultBlendMode), ctx->getPaint());
649
+ }
650
+ };
651
+
652
+ struct VerticesCmdProps {
653
+ std::vector<SkPoint> vertices;
654
+ std::optional<std::vector<SkColor>> colors;
655
+ std::optional<std::vector<SkPoint>> textures;
656
+ SkVertices::VertexMode mode;
657
+ std::optional<SkBlendMode> blendMode;
658
+ std::optional<std::vector<uint16_t>> indices;
659
+ };
660
+
661
+ class VerticesCmd : public Command {
662
+ private:
663
+ VerticesCmdProps props;
664
+
665
+ public:
666
+ VerticesCmd(jsi::Runtime &runtime, const jsi::Object &object,
667
+ Variables &variables)
668
+ : Command(CommandType::DrawVertices) {
669
+ convertProperty(runtime, object, "vertices", props.vertices, variables);
670
+ convertProperty(runtime, object, "colors", props.colors, variables);
671
+ convertProperty(runtime, object, "textures", props.textures, variables);
672
+ convertProperty(runtime, object, "mode", props.mode, variables);
673
+ convertProperty(runtime, object, "blendMode", props.blendMode, variables);
674
+ convertProperty(runtime, object, "indices", props.indices, variables);
675
+ }
676
+
677
+ void draw(DrawingCtx *ctx) {
678
+ // Create vertices using MakeCopy
679
+ auto vertices = SkVertices::MakeCopy(
680
+ props.mode, static_cast<int>(props.vertices.size()),
681
+ props.vertices.data(),
682
+ props.textures.has_value() ? props.textures.value().data() : nullptr,
683
+ props.colors.has_value() ? props.colors.value().data() : nullptr,
684
+ props.indices.has_value()
685
+ ? static_cast<int>(props.indices.value().size())
686
+ : 0,
687
+ props.indices.has_value() ? props.indices.value().data() : nullptr);
688
+
689
+ // Determine blend mode - use DstOver if colors are provided, SrcOver
690
+ // otherwise
691
+ const auto defaultBlendMode = props.colors.has_value()
692
+ ? SkBlendMode::kDstOver
693
+ : SkBlendMode::kSrcOver;
694
+
695
+ // Draw the vertices with the determined blend mode
696
+ ctx->canvas->drawVertices(
697
+ vertices, props.blendMode.value_or(defaultBlendMode), ctx->getPaint());
698
+ }
699
+ };
700
+
701
+ struct DiffRectCmdProps {
702
+ SkRRect outer;
703
+ SkRRect inner;
704
+ };
705
+
706
+ class DiffRectCmd : public Command {
707
+ private:
708
+ DiffRectCmdProps props;
709
+
710
+ public:
711
+ DiffRectCmd(jsi::Runtime &runtime, const jsi::Object &object,
712
+ Variables &variables)
713
+ : Command(CommandType::DrawDiffRect) {
714
+ convertProperty(runtime, object, "outer", props.outer, variables);
715
+ convertProperty(runtime, object, "inner", props.inner, variables);
716
+ }
717
+
718
+ void draw(DrawingCtx *ctx) {
719
+ ctx->canvas->drawDRRect(props.outer, props.inner, ctx->getPaint());
720
+ }
721
+ };
722
+
723
+ struct TextBlobCmdProps {
724
+ sk_sp<SkTextBlob> blob;
725
+ float x;
726
+ float y;
727
+ };
728
+
729
+ class TextBlobCmd : public Command {
730
+ private:
731
+ TextBlobCmdProps props;
732
+
733
+ public:
734
+ TextBlobCmd(jsi::Runtime &runtime, const jsi::Object &object,
735
+ Variables &variables)
736
+ : Command(CommandType::DrawTextBlob) {
737
+ convertProperty(runtime, object, "blob", props.blob, variables);
738
+ convertProperty(runtime, object, "x", props.x, variables);
739
+ convertProperty(runtime, object, "y", props.y, variables);
740
+ }
741
+
742
+ void draw(DrawingCtx *ctx) {
743
+ ctx->canvas->drawTextBlob(props.blob, props.x, props.y, ctx->getPaint());
744
+ }
745
+ };
746
+
747
+ struct GlyphData {
748
+ std::vector<SkGlyphID> glyphIds;
749
+ std::vector<SkPoint> positions;
750
+ };
751
+
752
+ struct GlyphsCmdProps {
753
+ std::optional<SkFont> font;
754
+ float x;
755
+ float y;
756
+ // GlyphData glyphs;
757
+ };
758
+
759
+ class GlyphsCmd : public Command {
760
+ private:
761
+ GlyphsCmdProps props;
762
+
763
+ public:
764
+ GlyphsCmd(jsi::Runtime &runtime, const jsi::Object &object,
765
+ Variables &variables)
766
+ : Command(CommandType::DrawGlyphs) {
767
+ convertProperty(runtime, object, "font", props.font, variables);
768
+ convertProperty(runtime, object, "x", props.x, variables);
769
+ convertProperty(runtime, object, "y", props.y, variables);
770
+ // convertProperty(runtime, object, "glyphs", props.glyphs, variables);
771
+ }
772
+
773
+ void draw(DrawingCtx *ctx) {
774
+ if (props.font.has_value()) {
775
+ // std::vector<uint16_t> glyphIds;
776
+ // std::vector<SkPoint> positions;
777
+ // for (const auto &[id, pos] : props.glyphs) {
778
+ // glyphIds.push_back(id);
779
+ // positions.push_back(pos);
780
+ // }
781
+ // ctx->canvas->drawGlyphs(
782
+ // static_cast<int>(props.glyphs.glyphIds.size()),
783
+ // props.glyphs.glyphIds.data(), props.glyphs.positions.data(),
784
+ // SkPoint::Make(props.x, props.y), props.font.value(),
785
+ // ctx->getPaint());
786
+ }
787
+ }
788
+ };
789
+
790
+ struct PictureCmdProps {
791
+ sk_sp<SkPicture> picture;
792
+ };
793
+
794
+ class PictureCmd : public Command {
795
+ private:
796
+ PictureCmdProps props;
797
+
798
+ public:
799
+ PictureCmd(jsi::Runtime &runtime, const jsi::Object &object,
800
+ Variables &variables)
801
+ : Command(CommandType::DrawPicture) {
802
+ convertProperty(runtime, object, "picture", props.picture, variables);
803
+ }
804
+
805
+ void draw(DrawingCtx *ctx) { ctx->canvas->drawPicture(props.picture); }
806
+ };
807
+
808
+ struct ImageSVGCmdProps {
809
+ sk_sp<SkSVGDOM> svg;
810
+ std::optional<float> x;
811
+ std::optional<float> y;
812
+ std::optional<float> width;
813
+ std::optional<float> height;
814
+ std::optional<SkRect> rect;
815
+ };
816
+
817
+ class ImageSVGCmd : public Command {
818
+ private:
819
+ ImageSVGCmdProps props;
820
+
821
+ public:
822
+ ImageSVGCmd(jsi::Runtime &runtime, const jsi::Object &object,
823
+ Variables &variables)
824
+ : Command(CommandType::DrawImageSVG) {
825
+ // Convert SVG property - expect a host object of JsiSkSVG type
826
+ auto svgValue = object.getProperty(runtime, "svg");
827
+ if (svgValue.isObject() &&
828
+ svgValue.asObject(runtime).isHostObject(runtime)) {
829
+ auto ptr = std::dynamic_pointer_cast<JsiSkSVG>(
830
+ svgValue.asObject(runtime).asHostObject(runtime));
831
+ if (ptr != nullptr) {
832
+ props.svg = ptr->getObject();
833
+ } else {
834
+ throw std::runtime_error(
835
+ "Expected SkSvgDom object for the svg property.");
836
+ }
837
+ }
838
+
839
+ // Convert other properties
840
+ convertProperty(runtime, object, "x", props.x, variables);
841
+ convertProperty(runtime, object, "y", props.y, variables);
842
+ convertProperty(runtime, object, "width", props.width, variables);
843
+ convertProperty(runtime, object, "height", props.height, variables);
844
+ convertProperty(runtime, object, "rect", props.rect, variables);
845
+ }
846
+
847
+ void draw(DrawingCtx *ctx) {
848
+ if (props.svg != nullptr) {
849
+ ctx->canvas->save();
850
+
851
+ if (props.rect.has_value()) {
852
+ // If rect is provided, use it for translation and container size
853
+ auto rect = props.rect.value();
854
+ ctx->canvas->translate(rect.x(), rect.y());
855
+ props.svg->setContainerSize(SkSize::Make(rect.width(), rect.height()));
856
+ } else {
857
+ // Otherwise use individual x, y, width, height properties
858
+ float x = props.x.value_or(-1);
859
+ float y = props.y.value_or(-1);
860
+ float width = props.width.value_or(-1);
861
+ float height = props.height.value_or(-1);
862
+
863
+ if (x != -1 && y != -1) {
864
+ ctx->canvas->translate(x, y);
865
+ }
866
+
867
+ if (width != -1 && height != -1) {
868
+ props.svg->setContainerSize(SkSize::Make(width, height));
869
+ }
870
+ }
871
+
872
+ // Render the SVG
873
+ props.svg->render(ctx->canvas);
874
+ ctx->canvas->restore();
875
+ }
876
+ }
877
+ };
878
+
879
+ struct ParagraphCmdProps {
880
+ para::Paragraph *paragraph;
881
+ float x;
882
+ float y;
883
+ float width;
884
+ };
885
+
886
+ class ParagraphCmd : public Command {
887
+ private:
888
+ ParagraphCmdProps props;
889
+
890
+ public:
891
+ ParagraphCmd(jsi::Runtime &runtime, const jsi::Object &object,
892
+ Variables &variables)
893
+ : Command(CommandType::DrawParagraph) {
894
+ convertProperty(runtime, object, "paragraph", props.paragraph, variables);
895
+ convertProperty(runtime, object, "x", props.x, variables);
896
+ convertProperty(runtime, object, "y", props.y, variables);
897
+ convertProperty(runtime, object, "width", props.width, variables);
898
+ }
899
+
900
+ void draw(DrawingCtx *ctx) {
901
+ if (props.paragraph) {
902
+ props.paragraph->layout(props.width);
903
+ props.paragraph->paint(ctx->canvas, props.x, props.y);
904
+ }
905
+ }
906
+ };
907
+
908
+ struct AtlasCmdProps {
909
+ sk_sp<SkImage> image;
910
+ std::vector<SkRect> sprites;
911
+ std::vector<SkRSXform> transforms;
912
+ std::optional<std::vector<SkColor>> colors;
913
+ std::optional<SkBlendMode> blendMode;
914
+ std::optional<SkSamplingOptions> sampling;
915
+ };
916
+
917
+ class AtlasCmd : public Command {
918
+ private:
919
+ AtlasCmdProps props;
920
+
921
+ public:
922
+ AtlasCmd(jsi::Runtime &runtime, const jsi::Object &object,
923
+ Variables &variables)
924
+ : Command(CommandType::DrawAtlas) {
925
+ convertProperty(runtime, object, "image", props.image, variables);
926
+ convertProperty(runtime, object, "sprites", props.sprites, variables);
927
+ convertProperty(runtime, object, "transforms", props.transforms, variables);
928
+ convertProperty(runtime, object, "colors", props.colors, variables);
929
+ convertProperty(runtime, object, "blendMode", props.blendMode, variables);
930
+ convertProperty(runtime, object, "sampling", props.sampling, variables);
931
+ }
932
+
933
+ void draw(DrawingCtx *ctx) {
934
+ if (props.image) {
935
+ auto colors =
936
+ props.colors.has_value() ? props.colors.value().data() : nullptr;
937
+ auto blendMode = props.blendMode.value_or(SkBlendMode::kDstOver);
938
+ auto sampling =
939
+ props.sampling.value_or(SkSamplingOptions(SkFilterMode::kLinear));
940
+
941
+ ctx->canvas->drawAtlas(props.image.get(), props.transforms.data(),
942
+ props.sprites.data(), colors,
943
+ props.transforms.size(), blendMode, sampling,
944
+ nullptr, &(ctx->getPaint()));
945
+ }
946
+ }
947
+ };
948
+
949
+ } // namespace RNSkia