@shopify/react-native-skia 2.2.7 → 2.2.9
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.
- package/android/build.gradle +0 -11
- package/cpp/api/JsiSkParagraph.h +40 -8
- package/cpp/api/recorder/DrawingCtx.h +15 -0
- package/cpp/api/recorder/JsiRecorder.h +2 -1
- package/cpp/api/recorder/RNRecorder.h +2 -2
- package/cpp/api/recorder/Shaders.h +6 -5
- package/lib/commonjs/renderer/__tests__/e2e/ParagraphMethods.spec.d.ts +1 -0
- package/lib/commonjs/renderer/__tests__/e2e/setup/Paragraph.d.ts +1 -1
- package/lib/commonjs/skia/types/Paragraph/Paragraph.d.ts +31 -1
- package/lib/commonjs/skia/types/Paragraph/Paragraph.js.map +1 -1
- package/lib/commonjs/skia/types/Recorder.d.ts +1 -1
- package/lib/commonjs/skia/types/Recorder.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkParagraph.d.ts +2 -2
- package/lib/commonjs/skia/web/JsiSkParagraph.js +4 -18
- package/lib/commonjs/skia/web/JsiSkParagraph.js.map +1 -1
- package/lib/commonjs/sksg/Container.d.ts +2 -10
- package/lib/commonjs/sksg/Container.js +1 -78
- package/lib/commonjs/sksg/Container.js.map +1 -1
- package/lib/commonjs/sksg/Container.web.d.ts +14 -0
- package/lib/commonjs/sksg/Container.web.js +91 -0
- package/lib/commonjs/sksg/Container.web.js.map +1 -0
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js +2 -2
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -1
- package/lib/commonjs/sksg/Recorder/Recorder.js +3 -2
- package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/Visitor.js +2 -2
- package/lib/commonjs/sksg/Recorder/Visitor.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/commands/Shaders.js +3 -3
- package/lib/commonjs/sksg/Recorder/commands/Shaders.js.map +1 -1
- package/lib/module/renderer/__tests__/e2e/ParagraphMethods.spec.d.ts +1 -0
- package/lib/module/renderer/__tests__/e2e/setup/Paragraph.d.ts +1 -1
- package/lib/module/skia/types/Paragraph/Paragraph.d.ts +31 -1
- package/lib/module/skia/types/Paragraph/Paragraph.js.map +1 -1
- package/lib/module/skia/types/Recorder.d.ts +1 -1
- package/lib/module/skia/types/Recorder.js.map +1 -1
- package/lib/module/skia/web/JsiSkParagraph.d.ts +2 -2
- package/lib/module/skia/web/JsiSkParagraph.js +4 -18
- package/lib/module/skia/web/JsiSkParagraph.js.map +1 -1
- package/lib/module/sksg/Container.d.ts +2 -10
- package/lib/module/sksg/Container.js +2 -78
- package/lib/module/sksg/Container.js.map +1 -1
- package/lib/module/sksg/Container.web.d.ts +14 -0
- package/lib/module/sksg/Container.web.js +83 -0
- package/lib/module/sksg/Container.web.js.map +1 -0
- package/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/module/sksg/Recorder/ReanimatedRecorder.js +2 -2
- package/lib/module/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
- package/lib/module/sksg/Recorder/Recorder.d.ts +1 -1
- package/lib/module/sksg/Recorder/Recorder.js +3 -2
- package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
- package/lib/module/sksg/Recorder/Visitor.js +2 -2
- package/lib/module/sksg/Recorder/Visitor.js.map +1 -1
- package/lib/module/sksg/Recorder/commands/Shaders.js +3 -3
- package/lib/module/sksg/Recorder/commands/Shaders.js.map +1 -1
- package/lib/typescript/lib/commonjs/sksg/Container.d.ts +1 -13
- package/lib/typescript/lib/commonjs/sksg/Container.web.d.ts +15 -0
- package/lib/typescript/lib/commonjs/sksg/Reconciler.d.ts +1 -17
- package/lib/typescript/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/typescript/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -1
- package/lib/typescript/lib/module/sksg/Container.d.ts +1 -14
- package/lib/typescript/lib/module/sksg/Container.web.d.ts +15 -0
- package/lib/typescript/lib/module/sksg/Reconciler.d.ts +1 -17
- package/lib/typescript/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/typescript/lib/module/sksg/Recorder/Recorder.d.ts +1 -1
- package/lib/typescript/src/renderer/__tests__/e2e/ParagraphMethods.spec.d.ts +1 -0
- package/lib/typescript/src/renderer/__tests__/e2e/setup/Paragraph.d.ts +1 -1
- package/lib/typescript/src/skia/types/Paragraph/Paragraph.d.ts +31 -1
- package/lib/typescript/src/skia/types/Recorder.d.ts +1 -1
- package/lib/typescript/src/skia/web/JsiSkParagraph.d.ts +2 -2
- package/lib/typescript/src/sksg/Container.d.ts +2 -10
- package/lib/typescript/src/sksg/Container.web.d.ts +14 -0
- package/lib/typescript/src/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +1 -1
- package/package.json +1 -1
- package/src/renderer/__tests__/e2e/ParagraphMethods.spec.tsx +383 -0
- package/src/renderer/__tests__/e2e/Shader.spec.tsx +93 -0
- package/src/skia/types/Paragraph/Paragraph.ts +32 -1
- package/src/skia/types/Recorder.ts +5 -1
- package/src/skia/web/JsiSkParagraph.ts +19 -28
- package/src/sksg/Container.ts +2 -81
- package/src/sksg/Container.web.ts +95 -0
- package/src/sksg/Recorder/ReanimatedRecorder.ts +6 -2
- package/src/sksg/Recorder/Recorder.ts +6 -2
- package/src/sksg/Recorder/Visitor.ts +2 -2
- package/src/sksg/Recorder/commands/Shaders.ts +8 -3
@@ -0,0 +1,383 @@
|
|
1
|
+
import { resolveFile, surface } from "../setup";
|
2
|
+
import {
|
3
|
+
PlaceholderAlignment,
|
4
|
+
TextBaseline,
|
5
|
+
TextDirection,
|
6
|
+
} from "../../../skia/types";
|
7
|
+
|
8
|
+
const RobotoRegular = Array.from(
|
9
|
+
resolveFile("skia/__tests__/assets/Roboto-Regular.ttf")
|
10
|
+
);
|
11
|
+
|
12
|
+
describe("Paragraph Methods", () => {
|
13
|
+
describe("getRectsForPlaceholders", () => {
|
14
|
+
it("should handle multiple placeholders with different alignments", async () => {
|
15
|
+
const placeholderRects = await surface.eval(
|
16
|
+
(Skia, ctx) => {
|
17
|
+
const robotoRegular = Skia.Typeface.MakeFreeTypeFaceFromData(
|
18
|
+
Skia.Data.fromBytes(new Uint8Array(ctx.RobotoRegular))
|
19
|
+
)!;
|
20
|
+
const provider = Skia.TypefaceFontProvider.Make();
|
21
|
+
provider.registerFont(robotoRegular, "Roboto");
|
22
|
+
|
23
|
+
const builder = Skia.ParagraphBuilder.Make(
|
24
|
+
{
|
25
|
+
textStyle: {
|
26
|
+
color: Skia.Color("black"),
|
27
|
+
fontFamilies: ["Roboto"],
|
28
|
+
fontSize: 16,
|
29
|
+
},
|
30
|
+
},
|
31
|
+
provider
|
32
|
+
);
|
33
|
+
|
34
|
+
builder.addText("Start ");
|
35
|
+
builder.addPlaceholder(
|
36
|
+
20,
|
37
|
+
20,
|
38
|
+
ctx.PlaceholderAlignment.Baseline,
|
39
|
+
ctx.TextBaseline.Alphabetic
|
40
|
+
);
|
41
|
+
builder.addText(" middle ");
|
42
|
+
builder.addPlaceholder(
|
43
|
+
15,
|
44
|
+
15,
|
45
|
+
ctx.PlaceholderAlignment.Top,
|
46
|
+
ctx.TextBaseline.Alphabetic
|
47
|
+
);
|
48
|
+
builder.addText(" end");
|
49
|
+
|
50
|
+
const paragraph = builder.build();
|
51
|
+
paragraph.layout(200);
|
52
|
+
|
53
|
+
const rects = paragraph.getRectsForPlaceholders();
|
54
|
+
return rects.map((r) => ({
|
55
|
+
x: r.rect.x,
|
56
|
+
y: r.rect.y,
|
57
|
+
width: r.rect.width,
|
58
|
+
height: r.rect.height,
|
59
|
+
direction: r.direction,
|
60
|
+
}));
|
61
|
+
},
|
62
|
+
{
|
63
|
+
RobotoRegular,
|
64
|
+
PlaceholderAlignment,
|
65
|
+
TextBaseline,
|
66
|
+
}
|
67
|
+
);
|
68
|
+
|
69
|
+
expect(placeholderRects).toHaveLength(2);
|
70
|
+
expect(placeholderRects[0].width).toBe(20);
|
71
|
+
expect(placeholderRects[0].height).toBe(20);
|
72
|
+
expect(placeholderRects[1].width).toBeCloseTo(15);
|
73
|
+
expect(placeholderRects[1].height).toBeCloseTo(15);
|
74
|
+
});
|
75
|
+
|
76
|
+
it("should return correct direction for placeholders", async () => {
|
77
|
+
const placeholderInfo = await surface.eval(
|
78
|
+
(Skia, ctx) => {
|
79
|
+
const robotoRegular = Skia.Typeface.MakeFreeTypeFaceFromData(
|
80
|
+
Skia.Data.fromBytes(new Uint8Array(ctx.RobotoRegular))
|
81
|
+
)!;
|
82
|
+
const provider = Skia.TypefaceFontProvider.Make();
|
83
|
+
provider.registerFont(robotoRegular, "Roboto");
|
84
|
+
|
85
|
+
const builder = Skia.ParagraphBuilder.Make(
|
86
|
+
{
|
87
|
+
textStyle: {
|
88
|
+
color: Skia.Color("black"),
|
89
|
+
fontFamilies: ["Roboto"],
|
90
|
+
fontSize: 16,
|
91
|
+
},
|
92
|
+
},
|
93
|
+
provider
|
94
|
+
);
|
95
|
+
|
96
|
+
builder.addText("Text with ");
|
97
|
+
builder.addPlaceholder(30, 30);
|
98
|
+
builder.addText(" placeholder");
|
99
|
+
|
100
|
+
const paragraph = builder.build();
|
101
|
+
paragraph.layout(300);
|
102
|
+
|
103
|
+
const rects = paragraph.getRectsForPlaceholders();
|
104
|
+
return rects.map((r) => ({
|
105
|
+
direction: r.direction === ctx.TextDirection.LTR ? "LTR" : "RTL",
|
106
|
+
}));
|
107
|
+
},
|
108
|
+
{
|
109
|
+
RobotoRegular,
|
110
|
+
TextDirection,
|
111
|
+
}
|
112
|
+
);
|
113
|
+
|
114
|
+
expect(placeholderInfo).toHaveLength(1);
|
115
|
+
expect(placeholderInfo[0].direction).toBe("LTR");
|
116
|
+
});
|
117
|
+
|
118
|
+
it("should return empty array when no placeholders", async () => {
|
119
|
+
const placeholderCount = await surface.eval(
|
120
|
+
(Skia, ctx) => {
|
121
|
+
const robotoRegular = Skia.Typeface.MakeFreeTypeFaceFromData(
|
122
|
+
Skia.Data.fromBytes(new Uint8Array(ctx.RobotoRegular))
|
123
|
+
)!;
|
124
|
+
const provider = Skia.TypefaceFontProvider.Make();
|
125
|
+
provider.registerFont(robotoRegular, "Roboto");
|
126
|
+
|
127
|
+
const builder = Skia.ParagraphBuilder.Make(
|
128
|
+
{
|
129
|
+
textStyle: {
|
130
|
+
color: Skia.Color("black"),
|
131
|
+
fontFamilies: ["Roboto"],
|
132
|
+
fontSize: 16,
|
133
|
+
},
|
134
|
+
},
|
135
|
+
provider
|
136
|
+
);
|
137
|
+
|
138
|
+
builder.addText("Text without any placeholders");
|
139
|
+
|
140
|
+
const paragraph = builder.build();
|
141
|
+
paragraph.layout(300);
|
142
|
+
|
143
|
+
const rects = paragraph.getRectsForPlaceholders();
|
144
|
+
return rects.length;
|
145
|
+
},
|
146
|
+
{
|
147
|
+
RobotoRegular,
|
148
|
+
}
|
149
|
+
);
|
150
|
+
|
151
|
+
expect(placeholderCount).toBe(0);
|
152
|
+
});
|
153
|
+
});
|
154
|
+
|
155
|
+
describe("getLineMetrics", () => {
|
156
|
+
it("should return line metrics for single line text", async () => {
|
157
|
+
const lineMetrics = await surface.eval(
|
158
|
+
(Skia, ctx) => {
|
159
|
+
const robotoRegular = Skia.Typeface.MakeFreeTypeFaceFromData(
|
160
|
+
Skia.Data.fromBytes(new Uint8Array(ctx.RobotoRegular))
|
161
|
+
)!;
|
162
|
+
const provider = Skia.TypefaceFontProvider.Make();
|
163
|
+
provider.registerFont(robotoRegular, "Roboto");
|
164
|
+
|
165
|
+
const builder = Skia.ParagraphBuilder.Make(
|
166
|
+
{
|
167
|
+
textStyle: {
|
168
|
+
color: Skia.Color("black"),
|
169
|
+
fontFamilies: ["Roboto"],
|
170
|
+
fontSize: 16,
|
171
|
+
},
|
172
|
+
},
|
173
|
+
provider
|
174
|
+
);
|
175
|
+
|
176
|
+
builder.addText("Single line text");
|
177
|
+
|
178
|
+
const paragraph = builder.build();
|
179
|
+
paragraph.layout(300);
|
180
|
+
|
181
|
+
return paragraph.getLineMetrics();
|
182
|
+
},
|
183
|
+
{
|
184
|
+
RobotoRegular,
|
185
|
+
}
|
186
|
+
);
|
187
|
+
|
188
|
+
expect(lineMetrics).toHaveLength(1);
|
189
|
+
expect(lineMetrics[0].lineNumber).toBe(0);
|
190
|
+
expect(lineMetrics[0].startIndex).toBe(0);
|
191
|
+
expect(lineMetrics[0].endIndex).toBe(16);
|
192
|
+
expect(lineMetrics[0].width).toBeGreaterThan(0);
|
193
|
+
expect(lineMetrics[0].height).toBeGreaterThan(0);
|
194
|
+
expect(lineMetrics[0].ascent).toBeGreaterThan(0);
|
195
|
+
expect(lineMetrics[0].descent).toBeGreaterThan(0);
|
196
|
+
// Note: Even single lines without explicit breaks may report isHardBreak as true
|
197
|
+
expect(typeof lineMetrics[0].isHardBreak).toBe('boolean');
|
198
|
+
});
|
199
|
+
|
200
|
+
it("should return line metrics for multi-line text with wrapping", async () => {
|
201
|
+
const lineMetrics = await surface.eval(
|
202
|
+
(Skia, ctx) => {
|
203
|
+
const robotoRegular = Skia.Typeface.MakeFreeTypeFaceFromData(
|
204
|
+
Skia.Data.fromBytes(new Uint8Array(ctx.RobotoRegular))
|
205
|
+
)!;
|
206
|
+
const provider = Skia.TypefaceFontProvider.Make();
|
207
|
+
provider.registerFont(robotoRegular, "Roboto");
|
208
|
+
|
209
|
+
const builder = Skia.ParagraphBuilder.Make(
|
210
|
+
{
|
211
|
+
textStyle: {
|
212
|
+
color: Skia.Color("black"),
|
213
|
+
fontFamilies: ["Roboto"],
|
214
|
+
fontSize: 16,
|
215
|
+
},
|
216
|
+
},
|
217
|
+
provider
|
218
|
+
);
|
219
|
+
|
220
|
+
builder.addText(
|
221
|
+
"This is a very long text that should wrap to multiple lines when laid out with a narrow width constraint"
|
222
|
+
);
|
223
|
+
|
224
|
+
const paragraph = builder.build();
|
225
|
+
paragraph.layout(100); // Narrow width to force wrapping
|
226
|
+
|
227
|
+
return paragraph.getLineMetrics();
|
228
|
+
},
|
229
|
+
{
|
230
|
+
RobotoRegular,
|
231
|
+
}
|
232
|
+
);
|
233
|
+
|
234
|
+
expect(lineMetrics.length).toBeGreaterThan(1);
|
235
|
+
|
236
|
+
// Check first line
|
237
|
+
expect(lineMetrics[0].lineNumber).toBe(0);
|
238
|
+
expect(lineMetrics[0].startIndex).toBe(0);
|
239
|
+
expect(lineMetrics[0].width).toBeLessThanOrEqual(100);
|
240
|
+
|
241
|
+
// Check second line
|
242
|
+
expect(lineMetrics[1].lineNumber).toBe(1);
|
243
|
+
expect(lineMetrics[1].startIndex).toBeGreaterThan(0);
|
244
|
+
expect(lineMetrics[1].baseline).toBeGreaterThan(lineMetrics[0].baseline);
|
245
|
+
});
|
246
|
+
|
247
|
+
it("should handle hard breaks (newlines) correctly", async () => {
|
248
|
+
const lineMetrics = await surface.eval(
|
249
|
+
(Skia, ctx) => {
|
250
|
+
const robotoRegular = Skia.Typeface.MakeFreeTypeFaceFromData(
|
251
|
+
Skia.Data.fromBytes(new Uint8Array(ctx.RobotoRegular))
|
252
|
+
)!;
|
253
|
+
const provider = Skia.TypefaceFontProvider.Make();
|
254
|
+
provider.registerFont(robotoRegular, "Roboto");
|
255
|
+
|
256
|
+
const builder = Skia.ParagraphBuilder.Make(
|
257
|
+
{
|
258
|
+
textStyle: {
|
259
|
+
color: Skia.Color("black"),
|
260
|
+
fontFamilies: ["Roboto"],
|
261
|
+
fontSize: 16,
|
262
|
+
},
|
263
|
+
},
|
264
|
+
provider
|
265
|
+
);
|
266
|
+
|
267
|
+
builder.addText("First line\nSecond line\nThird line");
|
268
|
+
|
269
|
+
const paragraph = builder.build();
|
270
|
+
paragraph.layout(300);
|
271
|
+
|
272
|
+
return paragraph.getLineMetrics();
|
273
|
+
},
|
274
|
+
{
|
275
|
+
RobotoRegular,
|
276
|
+
}
|
277
|
+
);
|
278
|
+
|
279
|
+
expect(lineMetrics).toHaveLength(3);
|
280
|
+
|
281
|
+
// All lines report isHardBreak as true in this implementation
|
282
|
+
expect(lineMetrics[0].isHardBreak).toBe(true);
|
283
|
+
expect(lineMetrics[1].isHardBreak).toBe(true);
|
284
|
+
expect(lineMetrics[2].isHardBreak).toBe(true);
|
285
|
+
|
286
|
+
// Check line numbers
|
287
|
+
expect(lineMetrics[0].lineNumber).toBe(0);
|
288
|
+
expect(lineMetrics[1].lineNumber).toBe(1);
|
289
|
+
expect(lineMetrics[2].lineNumber).toBe(2);
|
290
|
+
});
|
291
|
+
|
292
|
+
it("should correctly report line dimensions and positions", async () => {
|
293
|
+
const lineMetrics = await surface.eval(
|
294
|
+
(Skia, ctx) => {
|
295
|
+
const robotoRegular = Skia.Typeface.MakeFreeTypeFaceFromData(
|
296
|
+
Skia.Data.fromBytes(new Uint8Array(ctx.RobotoRegular))
|
297
|
+
)!;
|
298
|
+
const provider = Skia.TypefaceFontProvider.Make();
|
299
|
+
provider.registerFont(robotoRegular, "Roboto");
|
300
|
+
|
301
|
+
const builder = Skia.ParagraphBuilder.Make(
|
302
|
+
{
|
303
|
+
textStyle: {
|
304
|
+
color: Skia.Color("black"),
|
305
|
+
fontFamilies: ["Roboto"],
|
306
|
+
fontSize: 20,
|
307
|
+
},
|
308
|
+
},
|
309
|
+
provider
|
310
|
+
);
|
311
|
+
|
312
|
+
builder.addText("Test\nLine");
|
313
|
+
|
314
|
+
const paragraph = builder.build();
|
315
|
+
paragraph.layout(300);
|
316
|
+
|
317
|
+
return paragraph.getLineMetrics();
|
318
|
+
},
|
319
|
+
{
|
320
|
+
RobotoRegular,
|
321
|
+
}
|
322
|
+
);
|
323
|
+
|
324
|
+
expect(lineMetrics).toHaveLength(2);
|
325
|
+
|
326
|
+
// First line
|
327
|
+
const firstLine = lineMetrics[0];
|
328
|
+
// Height should be close to ascent + descent
|
329
|
+
expect(firstLine.height).toBeGreaterThan(0);
|
330
|
+
expect(Math.abs(firstLine.height - (firstLine.ascent + firstLine.descent))).toBeLessThan(1);
|
331
|
+
expect(firstLine.left).toBe(0);
|
332
|
+
expect(firstLine.baseline).toBeGreaterThan(0);
|
333
|
+
|
334
|
+
// Second line should be below the first
|
335
|
+
const secondLine = lineMetrics[1];
|
336
|
+
expect(secondLine.baseline).toBeGreaterThan(firstLine.baseline);
|
337
|
+
expect(secondLine.baseline - firstLine.baseline).toBeCloseTo(firstLine.height, 1);
|
338
|
+
});
|
339
|
+
|
340
|
+
it("should handle empty lines correctly", async () => {
|
341
|
+
const lineMetrics = await surface.eval(
|
342
|
+
(Skia, ctx) => {
|
343
|
+
const robotoRegular = Skia.Typeface.MakeFreeTypeFaceFromData(
|
344
|
+
Skia.Data.fromBytes(new Uint8Array(ctx.RobotoRegular))
|
345
|
+
)!;
|
346
|
+
const provider = Skia.TypefaceFontProvider.Make();
|
347
|
+
provider.registerFont(robotoRegular, "Roboto");
|
348
|
+
|
349
|
+
const builder = Skia.ParagraphBuilder.Make(
|
350
|
+
{
|
351
|
+
textStyle: {
|
352
|
+
color: Skia.Color("black"),
|
353
|
+
fontFamilies: ["Roboto"],
|
354
|
+
fontSize: 16,
|
355
|
+
},
|
356
|
+
},
|
357
|
+
provider
|
358
|
+
);
|
359
|
+
|
360
|
+
builder.addText("Line 1\n\nLine 3");
|
361
|
+
|
362
|
+
const paragraph = builder.build();
|
363
|
+
paragraph.layout(300);
|
364
|
+
|
365
|
+
return paragraph.getLineMetrics();
|
366
|
+
},
|
367
|
+
{
|
368
|
+
RobotoRegular,
|
369
|
+
}
|
370
|
+
);
|
371
|
+
|
372
|
+
expect(lineMetrics).toHaveLength(3);
|
373
|
+
|
374
|
+
// Middle line should be empty but still have metrics
|
375
|
+
const emptyLine = lineMetrics[1];
|
376
|
+
// Empty line might have startIndex != endIndex depending on implementation
|
377
|
+
expect(emptyLine.endIndex).toBeGreaterThanOrEqual(emptyLine.startIndex);
|
378
|
+
expect(emptyLine.width).toBeGreaterThanOrEqual(0);
|
379
|
+
expect(emptyLine.height).toBeGreaterThan(0); // Still has height
|
380
|
+
expect(emptyLine.isHardBreak).toBe(true);
|
381
|
+
});
|
382
|
+
});
|
383
|
+
});
|
@@ -257,4 +257,97 @@ vec4 main(vec2 pos) {
|
|
257
257
|
);
|
258
258
|
checkImage(img, docPath("shaders/color.png"));
|
259
259
|
});
|
260
|
+
it("should display a mix of red and lightblue color", async () => {
|
261
|
+
const { Skia } = importSkia();
|
262
|
+
const colorSelection = Skia.RuntimeEffect.Make(`uniform shader child1;
|
263
|
+
uniform shader child2;
|
264
|
+
|
265
|
+
vec4 main(vec2 pos) {
|
266
|
+
vec4 c1 = child1.eval(pos);
|
267
|
+
vec4 c2 = child2.eval(pos);
|
268
|
+
return mix(c1, c2, 0.5);
|
269
|
+
}`)!;
|
270
|
+
const img = await surface.draw(
|
271
|
+
<Fill>
|
272
|
+
<Shader source={colorSelection}>
|
273
|
+
<ColorShader color="lightblue" />
|
274
|
+
<ColorShader color="red" />
|
275
|
+
</Shader>
|
276
|
+
</Fill>
|
277
|
+
);
|
278
|
+
checkImage(img, docPath("shaders/mixed-colors.png"));
|
279
|
+
});
|
280
|
+
it("should display a mix of red and lightblue from custom shaders", async () => {
|
281
|
+
const { Skia } = importSkia();
|
282
|
+
const colorSelection = Skia.RuntimeEffect.Make(`uniform shader child1;
|
283
|
+
uniform shader child2;
|
284
|
+
|
285
|
+
vec4 main(vec2 pos) {
|
286
|
+
vec4 c1 = child1.eval(pos);
|
287
|
+
vec4 c2 = child2.eval(pos);
|
288
|
+
return mix(c1, c2, 0.5);
|
289
|
+
}`)!;
|
290
|
+
expect(colorSelection).toBeDefined();
|
291
|
+
const colorShader = Skia.RuntimeEffect.Make(`
|
292
|
+
uniform vec4 color;
|
293
|
+
|
294
|
+
vec4 main(vec2 pos) {
|
295
|
+
return color;
|
296
|
+
}
|
297
|
+
`)!;
|
298
|
+
expect(colorShader).toBeDefined();
|
299
|
+
const img = await surface.draw(
|
300
|
+
<Fill>
|
301
|
+
<Shader source={colorSelection}>
|
302
|
+
<Shader
|
303
|
+
source={colorShader}
|
304
|
+
uniforms={{ color: Skia.Color("lightblue") }}
|
305
|
+
/>
|
306
|
+
<Shader
|
307
|
+
source={colorShader}
|
308
|
+
uniforms={{ color: Skia.Color("red") }}
|
309
|
+
/>
|
310
|
+
</Shader>
|
311
|
+
</Fill>
|
312
|
+
);
|
313
|
+
checkImage(img, docPath("shaders/mixed-colors.png"));
|
314
|
+
});
|
315
|
+
|
316
|
+
it("should display different results based on children order", async () => {
|
317
|
+
const { Skia } = importSkia();
|
318
|
+
const orderSensitiveShader = Skia.RuntimeEffect.Make(`uniform shader child1;
|
319
|
+
uniform shader child2;
|
320
|
+
|
321
|
+
vec4 main(vec2 pos) {
|
322
|
+
vec4 c1 = child1.eval(pos);
|
323
|
+
vec4 c2 = child2.eval(pos);
|
324
|
+
// Order-dependent: blend child2 over child1 with position-based alpha
|
325
|
+
float alpha = pos.x / 256.0;
|
326
|
+
return mix(c1, c2, alpha);
|
327
|
+
}`)!;
|
328
|
+
expect(orderSensitiveShader).toBeDefined();
|
329
|
+
const colorShader = Skia.RuntimeEffect.Make(`
|
330
|
+
uniform vec4 color;
|
331
|
+
|
332
|
+
vec4 main(vec2 pos) {
|
333
|
+
return color;
|
334
|
+
}
|
335
|
+
`)!;
|
336
|
+
expect(colorShader).toBeDefined();
|
337
|
+
const img = await surface.draw(
|
338
|
+
<Fill>
|
339
|
+
<Shader source={orderSensitiveShader}>
|
340
|
+
<Shader
|
341
|
+
source={colorShader}
|
342
|
+
uniforms={{ color: Skia.Color("blue") }}
|
343
|
+
/>
|
344
|
+
<Shader
|
345
|
+
source={colorShader}
|
346
|
+
uniforms={{ color: Skia.Color("yellow") }}
|
347
|
+
/>
|
348
|
+
</Shader>
|
349
|
+
</Fill>
|
350
|
+
);
|
351
|
+
checkImage(img, docPath("shaders/order-dependent.png"));
|
352
|
+
});
|
260
353
|
});
|
@@ -4,6 +4,37 @@ import type { SkRect } from "../Rect";
|
|
4
4
|
|
5
5
|
import type { TextDirection } from "./ParagraphStyle";
|
6
6
|
|
7
|
+
export interface LineMetrics {
|
8
|
+
/** The index in the text buffer the line begins. */
|
9
|
+
startIndex: number;
|
10
|
+
/** The index in the text buffer the line ends. */
|
11
|
+
endIndex: number;
|
12
|
+
endExcludingWhitespaces: number;
|
13
|
+
endIncludingNewline: number;
|
14
|
+
/** True if the line ends in a hard break (e.g. newline) */
|
15
|
+
isHardBreak: boolean;
|
16
|
+
/**
|
17
|
+
* The final computed ascent for the line. This can be impacted by
|
18
|
+
* the strut, height, scaling, as well as outlying runs that are very tall.
|
19
|
+
*/
|
20
|
+
ascent: number;
|
21
|
+
/**
|
22
|
+
* The final computed descent for the line. This can be impacted by
|
23
|
+
* the strut, height, scaling, as well as outlying runs that are very tall.
|
24
|
+
*/
|
25
|
+
descent: number;
|
26
|
+
/** round(ascent + descent) */
|
27
|
+
height: number;
|
28
|
+
/** width of the line */
|
29
|
+
width: number;
|
30
|
+
/** The left edge of the line. The right edge can be obtained with `left + width` */
|
31
|
+
left: number;
|
32
|
+
/** The y position of the baseline for this line from the top of the paragraph. */
|
33
|
+
baseline: number;
|
34
|
+
/** Zero indexed line number. */
|
35
|
+
lineNumber: number;
|
36
|
+
}
|
37
|
+
|
7
38
|
export interface SkRectWithDirection {
|
8
39
|
rect: SkRect;
|
9
40
|
direction: TextDirection;
|
@@ -75,7 +106,7 @@ export interface SkParagraph extends SkJSIInstance<"Paragraph"> {
|
|
75
106
|
* Returns the bounding boxes for all lines in the paragraph. This method
|
76
107
|
* requires the layout method to have been called first.
|
77
108
|
*/
|
78
|
-
getLineMetrics():
|
109
|
+
getLineMetrics(): LineMetrics[];
|
79
110
|
/**
|
80
111
|
* Returns a list of rects with direction info for the placeholders added
|
81
112
|
* to the paragraph.
|
@@ -48,7 +48,11 @@ export interface BaseRecorder {
|
|
48
48
|
colorFilterType: NodeType,
|
49
49
|
props: AnimatedProps<unknown>
|
50
50
|
): void;
|
51
|
-
pushShader(
|
51
|
+
pushShader(
|
52
|
+
shaderType: NodeType,
|
53
|
+
props: AnimatedProps<unknown>,
|
54
|
+
children: number
|
55
|
+
): void;
|
52
56
|
pushBlurMaskFilter(props: AnimatedProps<BlurMaskFilterProps>): void;
|
53
57
|
composePathEffect(): void;
|
54
58
|
composeColorFilter(): void;
|
@@ -1,9 +1,15 @@
|
|
1
1
|
import type { CanvasKit, Paragraph } from "canvaskit-wasm";
|
2
2
|
|
3
|
-
import type {
|
3
|
+
import type {
|
4
|
+
SkRect,
|
5
|
+
SkRectWithDirection,
|
6
|
+
SkParagraph,
|
7
|
+
LineMetrics,
|
8
|
+
} from "../types";
|
4
9
|
|
5
10
|
import { HostObject } from "./Host";
|
6
11
|
import type { JsiSkCanvas } from "./JsiSkCanvas";
|
12
|
+
import { JsiSkRect } from "./JsiSkRect";
|
7
13
|
|
8
14
|
export class JsiSkParagraph
|
9
15
|
extends HostObject<Paragraph, "Paragraph">
|
@@ -12,41 +18,36 @@ export class JsiSkParagraph
|
|
12
18
|
constructor(CanvasKit: CanvasKit, ref: Paragraph) {
|
13
19
|
super(CanvasKit, ref, "Paragraph");
|
14
20
|
}
|
15
|
-
getMinIntrinsicWidth()
|
21
|
+
getMinIntrinsicWidth() {
|
16
22
|
return this.ref.getMinIntrinsicWidth();
|
17
23
|
}
|
18
24
|
|
19
|
-
getMaxIntrinsicWidth()
|
25
|
+
getMaxIntrinsicWidth() {
|
20
26
|
return this.ref.getMaxIntrinsicWidth();
|
21
27
|
}
|
22
28
|
|
23
|
-
getLongestLine()
|
29
|
+
getLongestLine() {
|
24
30
|
return this.ref.getLongestLine();
|
25
31
|
}
|
26
32
|
|
27
|
-
layout(width: number)
|
33
|
+
layout(width: number) {
|
28
34
|
this.ref.layout(width);
|
29
35
|
}
|
30
|
-
paint(canvas: JsiSkCanvas, x: number, y: number)
|
36
|
+
paint(canvas: JsiSkCanvas, x: number, y: number) {
|
31
37
|
canvas.ref.drawParagraph(this.ref, x, y);
|
32
38
|
}
|
33
|
-
getHeight()
|
39
|
+
getHeight() {
|
34
40
|
return this.ref.getHeight();
|
35
41
|
}
|
36
|
-
getMaxWidth()
|
42
|
+
getMaxWidth() {
|
37
43
|
return this.ref.getMaxWidth();
|
38
44
|
}
|
39
|
-
getGlyphPositionAtCoordinate(x: number, y: number)
|
45
|
+
getGlyphPositionAtCoordinate(x: number, y: number) {
|
40
46
|
return this.ref.getGlyphPositionAtCoordinate(x, y).pos;
|
41
47
|
}
|
42
48
|
getRectsForPlaceholders(): SkRectWithDirection[] {
|
43
49
|
return this.ref.getRectsForPlaceholders().map(({ rect, dir }) => ({
|
44
|
-
rect:
|
45
|
-
x: rect.at(0)!,
|
46
|
-
y: rect.at(1)!,
|
47
|
-
width: rect.at(2)!,
|
48
|
-
height: rect.at(3)!,
|
49
|
-
},
|
50
|
+
rect: new JsiSkRect(this.CanvasKit, rect),
|
50
51
|
direction: dir.value,
|
51
52
|
}));
|
52
53
|
}
|
@@ -58,20 +59,10 @@ export class JsiSkParagraph
|
|
58
59
|
{ value: 0 } /** kTight */,
|
59
60
|
{ value: 0 } /** kTight */
|
60
61
|
)
|
61
|
-
.map(({ rect }) => (
|
62
|
-
x: rect[0],
|
63
|
-
y: rect[1],
|
64
|
-
width: rect[2],
|
65
|
-
height: rect[3],
|
66
|
-
}));
|
62
|
+
.map(({ rect }) => new JsiSkRect(this.CanvasKit, rect));
|
67
63
|
}
|
68
|
-
getLineMetrics():
|
69
|
-
return this.ref.getLineMetrics()
|
70
|
-
x: r.left,
|
71
|
-
y: index * r.height,
|
72
|
-
width: r.width,
|
73
|
-
height: r.height,
|
74
|
-
}));
|
64
|
+
getLineMetrics(): LineMetrics[] {
|
65
|
+
return this.ref.getLineMetrics();
|
75
66
|
}
|
76
67
|
|
77
68
|
dispose() {
|