xref: /aosp_15_r20/external/skia/modules/canvaskit/tests/paragraph_test.js (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1describe('Paragraph Behavior', function() {
2    let container;
3
4    const assetLoadingPromises = [];
5    let notoSerifFontBuffer = null;
6    // This font is known to support kerning
7    assetLoadingPromises.push(fetch('/assets/NotoSerif-Regular.ttf').then(
8        (response) => response.arrayBuffer()).then(
9        (buffer) => {
10            notoSerifFontBuffer = buffer;
11        }));
12
13    let notoSerifBoldItalicFontBuffer = null;
14    assetLoadingPromises.push(fetch('/assets/NotoSerif-BoldItalic.ttf').then(
15        (response) => response.arrayBuffer()).then(
16        (buffer) => {
17            notoSerifBoldItalicFontBuffer = buffer;
18        }));
19
20    let emojiFontBuffer = null;
21    assetLoadingPromises.push(fetch('/assets/NotoColorEmoji.ttf').then(
22        (response) => response.arrayBuffer()).then(
23        (buffer) => {
24            emojiFontBuffer = buffer;
25        }));
26
27    let robotoFontBuffer = null;
28    assetLoadingPromises.push(fetch('/assets/Roboto-Regular.otf').then(
29        (response) => response.arrayBuffer()).then(
30        (buffer) => {
31            robotoFontBuffer = buffer;
32        }));
33
34    let robotoVariableFontBuffer = null;
35    assetLoadingPromises.push(fetch('/assets/RobotoSlab-VariableFont_wght.ttf').then(
36        (response) => response.arrayBuffer()).then(
37        (buffer) => {
38            robotoVariableFontBuffer = buffer;
39        }));
40
41    beforeEach(async () => {
42        await EverythingLoaded;
43        await Promise.all(assetLoadingPromises);
44        container = document.createElement('div');
45        container.innerHTML = `
46            <canvas width=600 height=600 id=test></canvas>
47            <canvas width=600 height=600 id=report></canvas>`;
48        document.body.appendChild(container);
49    });
50
51    afterEach(() => {
52        document.body.removeChild(container);
53    });
54
55    gm('paragraph_basic', (canvas) => {
56        const paint = new CanvasKit.Paint();
57
58        paint.setColor(CanvasKit.RED);
59        paint.setStyle(CanvasKit.PaintStyle.Stroke);
60
61        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
62        expect(fontMgr.countFamilies()).toEqual(1);
63        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
64
65        const wrapTo = 200;
66
67        const paraStyle = new CanvasKit.ParagraphStyle({
68            textStyle: {
69                color: CanvasKit.BLACK,
70                fontFamilies: ['Noto Serif'],
71                fontSize: 20,
72            },
73            textAlign: CanvasKit.TextAlign.Center,
74            maxLines: 8,
75            ellipsis: '.._.',
76        });
77
78        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
79        builder.addText('VAVAVAVAVAVAVA\nVAVA\n');
80
81        const blueText = new CanvasKit.TextStyle({
82            backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
83            color: CanvasKit.Color(48, 37, 199),
84            fontFamilies: ['Noto Serif'],
85            decoration: CanvasKit.LineThroughDecoration,
86            decorationThickness: 1.5, // multiplier based on font size
87            fontSize: 24,
88        });
89        builder.pushStyle(blueText);
90        builder.addText(`Gosh I hope this wraps at some point, it is such a long line.`)
91        builder.pop();
92        builder.addText(` I'm done with the blue now. `)
93        builder.addText(`Now I hope we should stop before we get 8 lines tall. `);
94        const paragraph = builder.build();
95
96        paragraph.layout(wrapTo);
97
98        expect(paragraph.didExceedMaxLines()).toBeTruthy();
99        expect(paragraph.getAlphabeticBaseline()).toBeCloseTo(21.377, 3);
100        expect(paragraph.getHeight()).toEqual(240);
101        expect(paragraph.getIdeographicBaseline()).toBeCloseTo(27.236, 3);
102        expect(paragraph.getLongestLine()).toBeCloseTo(193.820, 3);
103        expect(paragraph.getMaxIntrinsicWidth()).toBeCloseTo(1444.250, 3);
104        expect(paragraph.getMaxWidth()).toEqual(200);
105        expect(paragraph.getMinIntrinsicWidth()).toBeCloseTo(172.360, 3);
106        expect(paragraph.getWordBoundary(8)).toEqual({
107            start: 0,
108            end: 14,
109        });
110        expect(paragraph.getWordBoundary(25)).toEqual({
111            start: 25,
112            end: 26,
113        });
114
115
116        const lineMetrics = paragraph.getLineMetrics();
117        expect(lineMetrics.length).toEqual(8); // 8 lines worth of metrics
118        const flm = lineMetrics[0]; // First Line Metric
119        expect(flm.startIndex).toEqual(0);
120        expect(flm.endExcludingWhitespaces).toEqual(14)
121        expect(flm.endIndex).toEqual(14); // Including whitespaces but excluding newlines
122        expect(flm.endIncludingNewline).toEqual(15);
123        expect(flm.lineNumber).toEqual(0);
124        expect(flm.isHardBreak).toEqual(true);
125        expect(flm.ascent).toBeCloseTo(21.377, 3);
126        expect(flm.descent).toBeCloseTo(5.859, 3);
127        expect(flm.height).toBeCloseTo(27.000, 3);
128        expect(flm.width).toBeCloseTo(172.360, 3);
129        expect(flm.left).toBeCloseTo(13.818, 3);
130        expect(flm.baseline).toBeCloseTo(21.141, 3);
131
132        const singleLineMetrics = paragraph.getLineMetricsAt(0);
133        expect(singleLineMetrics.startIndex).toEqual(flm.startIndex);
134        expect(singleLineMetrics.lineNumber).toEqual(flm.lineNumber);
135        expect(paragraph.getLineMetricsAt(9)).toBeFalsy();
136        expect(paragraph.getNumberOfLines()).toEqual(8);
137        expect(paragraph.getLineNumberAt(9999)).toEqual(-1);
138        expect(paragraph.getLineNumberAt(0)).toEqual(0);
139
140        const glyphInfo = paragraph.getGlyphInfoAt(13);
141        expect(glyphInfo.graphemeClusterTextRange.start).toEqual(13);
142        expect(glyphInfo.graphemeClusterTextRange.end).toEqual(14);
143        expect(glyphInfo.dir).toEqual(CanvasKit.TextDirection.LTR);
144        expect(glyphInfo.isEllipsis).toEqual(false);
145        expect(glyphInfo.graphemeLayoutBounds[0]).toBeCloseTo(172.08, 3);
146        expect(glyphInfo.graphemeLayoutBounds[1]).toBeCloseTo(-0.24, 3);
147        expect(glyphInfo.graphemeLayoutBounds[2]).toBeCloseTo(186.18, 3);
148        expect(glyphInfo.graphemeLayoutBounds[3]).toBeCloseTo(27, 3);
149
150        expect(paragraph.getGlyphInfoAt(9999)).toBeFalsy();
151
152        // This should hit the last character on the first line.
153        const lastGlyphOnFirstLine = paragraph.getClosestGlyphInfoAtCoordinate(180, 21);
154        expect(lastGlyphOnFirstLine.graphemeClusterTextRange.start).toEqual(13);
155        expect(lastGlyphOnFirstLine.graphemeClusterTextRange.end).toEqual(14);
156        expect(lastGlyphOnFirstLine.dir).toEqual(CanvasKit.TextDirection.LTR);
157        expect(lastGlyphOnFirstLine.isEllipsis).toEqual(false);
158        expect(lastGlyphOnFirstLine.graphemeLayoutBounds[0]).toBeCloseTo(172.08, 3);
159        expect(lastGlyphOnFirstLine.graphemeLayoutBounds[1]).toBeCloseTo(-0.24, 3);
160        expect(lastGlyphOnFirstLine.graphemeLayoutBounds[2]).toBeCloseTo(186.18, 3);
161        expect(lastGlyphOnFirstLine.graphemeLayoutBounds[3]).toBeCloseTo(27, 3);
162
163        const unresolvedGlyphs = paragraph.unresolvedCodepoints();
164        expect(unresolvedGlyphs.length).toEqual(0, unresolvedGlyphs);
165
166        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, 230), paint);
167        canvas.drawParagraph(paragraph, 10, 10);
168
169        paint.delete();
170        fontMgr.delete();
171        paragraph.delete();
172        builder.delete();
173    });
174
175    gm('paragraph_foreground_and_background_color', (canvas) => {
176        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
177        expect(fontMgr.countFamilies()).toEqual(1);
178        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
179
180        const wrapTo = 200;
181
182        const paraStyle = new CanvasKit.ParagraphStyle({
183            textStyle: {
184                foregroundColor: CanvasKit.Color4f(1.0, 0, 0, 0.8),
185                backgroundColor: CanvasKit.Color4f(0, 0, 1.0, 0.8),
186                // color should default to black
187                fontFamilies: ['Noto Serif'],
188                fontSize: 20,
189            },
190
191            textAlign: CanvasKit.TextAlign.Center,
192        });
193        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
194        builder.addText(
195            'This text has a red foregroundColor and a blue backgroundColor.');
196        const paragraph = builder.build();
197        paragraph.layout(300);
198        canvas.drawParagraph(paragraph, 10, 10);
199
200        fontMgr.delete();
201        paragraph.delete();
202        builder.delete();
203    });
204
205    gm('paragraph_foreground_stroke_paint', (canvas) => {
206        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
207        expect(fontMgr.countFamilies()).toEqual(1);
208        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
209
210        const wrapTo = 200;
211
212        const textStyle = {
213            fontFamilies: ['Noto Serif'],
214            fontSize: 40,
215        };
216        const paraStyle = new CanvasKit.ParagraphStyle({
217            textStyle: textStyle,
218            textAlign: CanvasKit.TextAlign.Center,
219        });
220        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
221
222        const fg = new CanvasKit.Paint();
223        fg.setColor(CanvasKit.BLACK);
224        fg.setStyle(CanvasKit.PaintStyle.Stroke);
225
226        const bg = new CanvasKit.Paint();
227        bg.setColor(CanvasKit.TRANSPARENT);
228
229        builder.pushPaintStyle(textStyle, fg, bg);
230        builder.addText(
231            'This text is stroked in black and has no fill');
232        const paragraph = builder.build();
233        paragraph.layout(300);
234        canvas.drawParagraph(paragraph, 10, 10);
235        // Again 5px to the right so you can tell the fill is transparent
236        canvas.drawParagraph(paragraph, 15, 10);
237
238        fg.delete();
239        bg.delete();
240        fontMgr.delete();
241        paragraph.delete();
242        builder.delete();
243    });
244
245    gm('paragraph_letter_word_spacing', (canvas) => {
246        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
247        expect(fontMgr.countFamilies()).toEqual(1);
248        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
249
250        const wrapTo = 200;
251
252        const paraStyle = new CanvasKit.ParagraphStyle({
253            textStyle: {
254                // color should default to black
255                fontFamilies: ['Noto Serif'],
256                fontSize: 20,
257                letterSpacing: 5,
258                wordSpacing: 10,
259            },
260
261            textAlign: CanvasKit.TextAlign.Center,
262        });
263        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
264        builder.addText(
265            'This text should have a lot of space between the letters and words.');
266        const paragraph = builder.build();
267        paragraph.layout(300);
268        canvas.drawParagraph(paragraph, 10, 10);
269
270        fontMgr.delete();
271        paragraph.delete();
272        builder.delete();
273    });
274
275    gm('paragraph_shadows', (canvas) => {
276        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
277        expect(fontMgr.countFamilies()).toEqual(1);
278        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
279
280        const wrapTo = 200;
281
282        const paraStyle = new CanvasKit.ParagraphStyle({
283            textStyle: {
284                color: CanvasKit.WHITE,
285                fontFamilies: ['Noto Serif'],
286                fontSize: 20,
287                shadows: [{color: CanvasKit.BLACK, blurRadius: 15},
288                          {color: CanvasKit.RED, blurRadius: 5, offset: [10, 10]}],
289            },
290
291            textAlign: CanvasKit.TextAlign.Center,
292        });
293        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
294        builder.addText('This text should have a shadow behind it.');
295        const paragraph = builder.build();
296        paragraph.layout(300);
297        canvas.drawParagraph(paragraph, 10, 10);
298
299        fontMgr.delete();
300        paragraph.delete();
301        builder.delete();
302    });
303
304    gm('paragraph_strut_style', (canvas) => {
305        const fontMgr = CanvasKit.FontMgr.FromData(robotoFontBuffer);
306        expect(fontMgr.countFamilies()).toEqual(1);
307        expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
308
309        // The lines in this paragraph should have the same height despite the third
310        // line having a larger font size.
311        const paraStrutStyle = new CanvasKit.ParagraphStyle({
312            textStyle: {
313                fontFamilies: ['Roboto'],
314                color: CanvasKit.BLACK,
315            },
316            strutStyle: {
317                strutEnabled: true,
318                fontFamilies: ['Roboto'],
319                fontSize: 28,
320                heightMultiplier: 1.5,
321                forceStrutHeight: true,
322            },
323        });
324        const paraStyle = new CanvasKit.ParagraphStyle({
325            textStyle: {
326                fontFamilies: ['Roboto'],
327                color: CanvasKit.BLACK,
328            },
329        });
330        const roboto28Style = new CanvasKit.TextStyle({
331            color: CanvasKit.BLACK,
332            fontFamilies: ['Roboto'],
333            fontSize: 28,
334        });
335        const roboto32Style = new CanvasKit.TextStyle({
336            color: CanvasKit.BLACK,
337            fontFamilies: ['Roboto'],
338            fontSize: 32,
339        });
340        const builder = CanvasKit.ParagraphBuilder.Make(paraStrutStyle, fontMgr);
341        builder.pushStyle(roboto28Style);
342        builder.addText('This paragraph\n');
343        builder.pushStyle(roboto32Style);
344        builder.addText('is using\n');
345        builder.pop();
346        builder.pushStyle(roboto28Style);
347        builder.addText('a strut style!\n');
348        builder.pop();
349        builder.pop();
350
351        const builder2 = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
352        builder2.pushStyle(roboto28Style);
353        builder2.addText('This paragraph\n');
354        builder2.pushStyle(roboto32Style);
355        builder2.addText('is not using\n');
356        builder2.pop();
357        builder2.pushStyle(roboto28Style);
358        builder2.addText('a strut style!\n');
359        builder2.pop();
360        builder2.pop();
361
362        const paragraph = builder.build();
363        paragraph.layout(300);
364
365        const paragraph2 = builder2.build();
366        paragraph2.layout(300);
367        canvas.drawParagraph(paragraph, 10, 10);
368        canvas.drawParagraph(paragraph2, 220, 10);
369
370        fontMgr.delete();
371        paragraph.delete();
372        builder.delete();
373    });
374
375    gm('paragraph_font_features', (canvas) => {
376        const fontMgr = CanvasKit.FontMgr.FromData(robotoFontBuffer);
377        expect(fontMgr.countFamilies()).toEqual(1);
378        expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
379
380
381        const paraStyle = new CanvasKit.ParagraphStyle({
382            textStyle: {
383                color: CanvasKit.BLACK,
384                fontFamilies: ['Roboto'],
385                fontSize: 30,
386                fontFeatures: [{name: 'smcp', value: 1}]
387            },
388            textAlign: CanvasKit.TextAlign.Center,
389        });
390        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
391        builder.addText('This Text Should Be In Small Caps');
392        const paragraph = builder.build();
393        paragraph.layout(300);
394        canvas.drawParagraph(paragraph, 10, 10);
395
396        fontMgr.delete();
397        paragraph.delete();
398        builder.delete();
399    });
400
401    gm('paragraph_font_variations', (canvas) => {
402        const fontMgr = CanvasKit.FontMgr.FromData(robotoVariableFontBuffer);
403        expect(fontMgr.countFamilies()).toEqual(1);
404        expect(fontMgr.getFamilyName(0)).toEqual('Roboto Slab');
405
406        const paraStyle = new CanvasKit.ParagraphStyle({
407            textStyle: {
408                color: CanvasKit.BLACK,
409                fontFamilies: ['Roboto Slab'],
410                fontSize: 30,
411            },
412            textAlign: CanvasKit.TextAlign.Center,
413        });
414        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
415        builder.addText('Normal\n');
416        builder.pushStyle(new CanvasKit.TextStyle({
417            fontFamilies: ['Roboto Slab'],
418            fontSize: 30,
419            fontVariations: [{axis: 'wght', value: 900}]
420        }));
421        builder.addText('Heavy Weight\n');
422        builder.pushStyle(new CanvasKit.TextStyle({
423            fontFamilies: ['Roboto Slab'],
424            fontSize: 30,
425            fontVariations: [{axis: 'wght', value: 100}]
426        }));
427        builder.addText('Light Weight\n');
428        builder.pop();
429        builder.pop();
430
431        const paragraph = builder.build();
432        paragraph.layout(300);
433
434        canvas.clear(CanvasKit.WHITE);
435        canvas.drawParagraph(paragraph, 10, 10);
436
437        fontMgr.delete();
438        paragraph.delete();
439        builder.delete();
440    });
441
442    gm('paragraph_placeholders', (canvas) => {
443        const fontMgr = CanvasKit.FontMgr.FromData(robotoFontBuffer);
444        expect(fontMgr.countFamilies()).toEqual(1);
445        expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
446
447
448        const paraStyle = new CanvasKit.ParagraphStyle({
449            textStyle: {
450                color: CanvasKit.BLACK,
451                fontFamilies: ['Roboto'],
452                fontSize: 20,
453            },
454            textAlign: CanvasKit.TextAlign.Center,
455        });
456        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
457        builder.addText('There should be ');
458        builder.addPlaceholder(10, 10, CanvasKit.PlaceholderAlignment.AboveBaseline,
459                               CanvasKit.TextBaseline.Ideographic);
460        builder.addText('a space in this sentence.\n');
461
462        builder.addText('There should be ');
463        builder.addPlaceholder(10, 10, CanvasKit.PlaceholderAlignment.BelowBaseline,
464                               CanvasKit.TextBaseline.Ideographic);
465        builder.addText('a dropped space in this sentence.\n');
466
467        builder.addText('There should be ');
468        builder.addPlaceholder(10, 10, null, null, 20);
469        builder.addText('an offset space in this sentence.\n');
470        const paragraph = builder.build();
471        paragraph.layout(300);
472
473        let rects = paragraph.getRectsForPlaceholders();
474        canvas.drawParagraph(paragraph, 10, 10);
475
476        for (const r of rects) {
477            const p = new CanvasKit.Paint();
478            p.setColor(CanvasKit.Color(0, 0, 255));
479            p.setStyle(CanvasKit.PaintStyle.Stroke);
480            // Account for the (10, 10) offset when we painted the paragraph.
481            const rect = r.rect;
482            expect(r.dir).toEqual(CanvasKit.TextDirection.LTR);
483            const placeholder =
484                CanvasKit.LTRBRect(rect[0]+10,rect[1]+10,rect[2]+10,rect[3]+10);
485            canvas.drawRect(placeholder, p);
486            p.delete();
487        }
488
489        fontMgr.delete();
490        paragraph.delete();
491        builder.delete();
492    });
493
494    // loosely based on SkParagraph_GetRectsForRangeParagraph test in c++ code.
495    gm('paragraph_rects', (canvas) => {
496        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
497
498        const wrapTo = 550;
499        const hStyle = CanvasKit.RectHeightStyle.Max;
500        const wStyle = CanvasKit.RectWidthStyle.Tight;
501
502        const mallocedColor = CanvasKit.Malloc(Float32Array, 4);
503        mallocedColor.toTypedArray().set([0.9, 0.1, 0.1, 1.0]);
504
505        const paraStyle = new CanvasKit.ParagraphStyle({
506            textStyle: {
507                color: mallocedColor,
508                fontFamilies: ['Noto Serif'],
509                fontSize: 50,
510            },
511            textAlign: CanvasKit.TextAlign.Left,
512            maxLines: 10,
513        });
514        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
515        builder.addText('12345,  \"67890\" 12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345');
516        const paragraph = builder.build();
517        CanvasKit.Free(mallocedColor);
518
519        paragraph.layout(wrapTo);
520
521        const ranges = [
522            {
523                start: 0,
524                end: 0,
525                expectedNum: 0,
526            },
527            {
528                start: 0,
529                end: 1,
530                expectedNum: 1,
531                color: CanvasKit.Color(200, 0, 200),
532            },
533            {
534                start: 2,
535                end: 8,
536                expectedNum: 1,
537                color: CanvasKit.Color(255, 0, 0),
538            },
539            {
540                start: 8,
541                end: 21,
542                expectedNum: 1,
543                color: CanvasKit.Color(0, 255, 0),
544            },
545            {
546                start: 30,
547                end: 100,
548                expectedNum: 4,
549                color: CanvasKit.Color(0, 0, 255),
550            },
551            {
552                start: 19,
553                end: 22,
554                expectedNum: 1,
555                color: CanvasKit.Color(0, 200, 200),
556            }
557        ];
558        // Move it down a bit so we can see the rects that go above 0,0
559        canvas.translate(10, 10);
560        canvas.drawParagraph(paragraph, 0, 0);
561
562        for (const test of ranges) {
563            let rects = paragraph.getRectsForRange(test.start, test.end, hStyle, wStyle);
564            expect(Array.isArray(rects)).toEqual(true);
565            expect(rects.length).toEqual(test.expectedNum);
566
567            for (const r of rects) {
568                expect(r.dir).toEqual(CanvasKit.TextDirection.LTR);
569                const p = new CanvasKit.Paint();
570                p.setColor(test.color);
571                p.setStyle(CanvasKit.PaintStyle.Stroke);
572                canvas.drawRect(r.rect, p);
573                p.delete();
574            }
575        }
576        expect(CanvasKit.RectHeightStyle.Strut).toBeTruthy();
577
578        fontMgr.delete();
579        paragraph.delete();
580        builder.delete();
581    });
582
583    gm('paragraph_emoji', (canvas) => {
584        const fontMgr = CanvasKit.FontMgr.FromData([notoSerifFontBuffer, emojiFontBuffer]);
585        expect(fontMgr.countFamilies()).toEqual(2);
586        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
587        expect(fontMgr.getFamilyName(1)).toEqual('Noto Color Emoji');
588
589        const wrapTo = 450;
590
591        const paraStyle = new CanvasKit.ParagraphStyle({
592            textStyle: {
593                color: CanvasKit.BLACK,
594                // Put text first, otherwise the "emoji space" is used and that looks bad.
595                fontFamilies: ['Noto Serif', 'Noto Color Emoji'],
596                fontSize: 30,
597            },
598            textAlign: CanvasKit.TextAlign.Left,
599            maxLines: 10,
600        });
601
602        const textStyle = new CanvasKit.TextStyle({
603            color: CanvasKit.BLACK,
604            // The number 4 matches an emoji and looks strange w/o this additional style.
605            fontFamilies: ['Noto Serif'],
606            fontSize: 30,
607        });
608
609        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
610        builder.pushStyle(textStyle);
611        builder.addText('4 flags on following line:\n');
612        builder.pop();
613        builder.addText(`��️‍�� ���� ���� ����\n`);
614        builder.addText('Rainbow Italy Liberia USA\n\n');
615        builder.addText('Emoji below should wrap:\n');
616        builder.addText(`����������������‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍��`);
617        const paragraph = builder.build();
618
619        paragraph.layout(wrapTo);
620        canvas.drawParagraph(paragraph, 10, 10);
621
622        const paint = new CanvasKit.Paint();
623        paint.setColor(CanvasKit.RED);
624        paint.setStyle(CanvasKit.PaintStyle.Stroke);
625        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
626
627        fontMgr.delete();
628        paint.delete();
629        builder.delete();
630        paragraph.delete();
631    });
632
633    gm('paragraph_hits', (canvas) => {
634        const fontMgr = CanvasKit.FontMgr.FromData([notoSerifFontBuffer]);
635
636        const wrapTo = 300;
637
638        const paraStyle = new CanvasKit.ParagraphStyle({
639            textStyle: {
640                color: CanvasKit.BLACK,
641                fontFamilies: ['Noto Serif'],
642                fontSize: 50,
643            },
644            textAlign: CanvasKit.TextAlign.Left,
645            maxLines: 10,
646        });
647        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
648        builder.addText('UNCOPYRIGHTABLE');
649        const paragraph = builder.build();
650
651        paragraph.layout(wrapTo);
652        canvas.translate(10, 10);
653        canvas.drawParagraph(paragraph, 0, 0);
654
655        const paint = new CanvasKit.Paint();
656
657        paint.setColor(CanvasKit.Color(255, 0, 0));
658        paint.setStyle(CanvasKit.PaintStyle.Fill);
659        canvas.drawCircle(20, 30, 3, paint);
660
661        paint.setColor(CanvasKit.Color(0, 0, 255));
662        canvas.drawCircle(80, 90, 3, paint);
663
664        paint.setColor(CanvasKit.Color(0, 255, 0));
665        canvas.drawCircle(280, 2, 3, paint);
666
667        let posU = paragraph.getGlyphPositionAtCoordinate(20, 30);
668        expect(posU).toEqual({
669            pos: 1,
670            affinity: CanvasKit.Affinity.Upstream
671        });
672        let posA = paragraph.getGlyphPositionAtCoordinate(80, 90);
673        expect(posA).toEqual({
674            pos: 11,
675            affinity: CanvasKit.Affinity.Downstream
676        });
677        let posG = paragraph.getGlyphPositionAtCoordinate(280, 2);
678        expect(posG).toEqual({
679            pos: 9,
680            affinity: CanvasKit.Affinity.Upstream
681        });
682
683        builder.delete();
684        paragraph.delete();
685        paint.delete();
686        fontMgr.delete();
687    });
688
689    gm('paragraph_styles', (canvas) => {
690        const paint = new CanvasKit.Paint();
691
692        paint.setColor(CanvasKit.RED);
693        paint.setStyle(CanvasKit.PaintStyle.Stroke);
694
695        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
696
697        const wrapTo = 250;
698
699        const paraStyle = new CanvasKit.ParagraphStyle({
700            textStyle: {
701                fontFamilies: ['Noto Serif'],
702                fontSize: 20,
703                fontStyle: {
704                    weight: CanvasKit.FontWeight.Light,
705                }
706            },
707            textDirection: CanvasKit.TextDirection.RTL,
708            disableHinting: true,
709        });
710
711        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
712        builder.addText('Default text\n');
713
714        const boldItalic = new CanvasKit.TextStyle({
715            color: CanvasKit.RED,
716            fontFamilies: ['Noto Serif'],
717            fontSize: 20,
718            fontStyle: {
719                weight: CanvasKit.FontWeight.Bold,
720                width: CanvasKit.FontWidth.Expanded,
721                slant: CanvasKit.FontSlant.Italic,
722            }
723        });
724        builder.pushStyle(boldItalic);
725        builder.addText(`Bold, Expanded, Italic\n`);
726        builder.pop();
727        builder.addText(`back to normal`);
728        const paragraph = builder.build();
729
730        paragraph.layout(wrapTo);
731
732        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
733        canvas.drawParagraph(paragraph, 10, 10);
734
735        paint.delete();
736        paragraph.delete();
737        builder.delete();
738        fontMgr.delete();
739    });
740
741    it('paragraph_rounding_hack', () => {
742        const paraStyleDefault = new CanvasKit.ParagraphStyle({
743            textStyle: {
744                fontFamilies: ['Noto Serif'],
745                fontSize: 20,
746                fontStyle: {
747                    weight: CanvasKit.FontWeight.Light,
748                }
749            },
750            textDirection: CanvasKit.TextDirection.RTL,
751            disableHinting: true,
752        });
753        expect(paraStyleDefault.applyRoundingHack).toEqual(true);
754
755        const paraStyleOverride = new CanvasKit.ParagraphStyle({
756            textStyle: {
757                fontFamilies: ['Noto Serif'],
758                fontSize: 20,
759                fontStyle: {
760                    weight: CanvasKit.FontWeight.Light,
761                }
762            },
763            textDirection: CanvasKit.TextDirection.RTL,
764            disableHinting: true,
765            applyRoundingHack: false,
766        });
767        expect(paraStyleOverride.applyRoundingHack).toEqual(false);
768    });
769
770    gm('paragraph_font_provider', (canvas) => {
771        const paint = new CanvasKit.Paint();
772
773        paint.setColor(CanvasKit.RED);
774        paint.setStyle(CanvasKit.PaintStyle.Stroke);
775
776        // Register Noto Serif as 'sans-serif'.
777        const fontSrc = CanvasKit.TypefaceFontProvider.Make();
778        fontSrc.registerFont(notoSerifFontBuffer, 'sans-serif');
779        fontSrc.registerFont(notoSerifBoldItalicFontBuffer, 'sans-serif');
780
781        const wrapTo = 250;
782
783        const paraStyle = new CanvasKit.ParagraphStyle({
784            textStyle: {
785                fontFamilies: ['sans-serif'],
786                fontSize: 20,
787                fontStyle: {
788                    weight: CanvasKit.FontWeight.Light,
789                }
790            },
791            textDirection: CanvasKit.TextDirection.RTL,
792            disableHinting: true,
793        });
794
795        const builder = CanvasKit.ParagraphBuilder.MakeFromFontProvider(paraStyle, fontSrc);
796        builder.addText('Default text\n');
797
798        const boldItalic = new CanvasKit.TextStyle({
799            color: CanvasKit.RED,
800            fontFamilies: ['sans-serif'],
801            fontSize: 20,
802            fontStyle: {
803                weight: CanvasKit.FontWeight.Bold,
804                width: CanvasKit.FontWidth.Expanded,
805                slant: CanvasKit.FontSlant.Italic,
806            }
807        });
808        builder.pushStyle(boldItalic);
809        builder.addText(`Bold, Expanded, Italic\n`);
810        builder.pop();
811        builder.addText(`back to normal`);
812        const paragraph = builder.build();
813
814        paragraph.layout(wrapTo);
815
816        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
817        canvas.drawParagraph(paragraph, 10, 10);
818
819        paint.delete();
820        paragraph.delete();
821        builder.delete();
822        fontSrc.delete();
823    });
824
825    gm('paragraph_font_collection', (canvas) => {
826        const paint = new CanvasKit.Paint();
827
828        paint.setColor(CanvasKit.RED);
829        paint.setStyle(CanvasKit.PaintStyle.Stroke);
830
831        // Register Noto Serif as 'sans-serif'.
832        const fontSrc = CanvasKit.TypefaceFontProvider.Make();
833        fontSrc.registerFont(notoSerifFontBuffer, 'sans-serif');
834        const fontCollection = CanvasKit.FontCollection.Make();
835        fontCollection.setDefaultFontManager(fontSrc);
836
837        const wrapTo = 250;
838
839        const paraStyle = new CanvasKit.ParagraphStyle({
840            textStyle: {
841                fontFamilies: ['sans-serif'],
842                fontSize: 20,
843            },
844            disableHinting: true,
845        });
846
847        const builder = CanvasKit.ParagraphBuilder.MakeFromFontCollection(
848	    paraStyle, fontCollection);
849        builder.addText('ABC DEF GHI');
850
851        const paragraph = builder.build();
852        paragraph.layout(wrapTo);
853        canvas.drawParagraph(paragraph, 10, 10);
854
855        paint.delete();
856        paragraph.delete();
857        builder.delete();
858        fontSrc.delete();
859    });
860
861    gm('paragraph_text_styles', (canvas) => {
862        const paint = new CanvasKit.Paint();
863
864        paint.setColor(CanvasKit.GREEN);
865        paint.setStyle(CanvasKit.PaintStyle.Stroke);
866
867        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
868        expect(fontMgr.countFamilies()).toEqual(1);
869        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
870
871        const wrapTo = 200;
872
873        const paraStyle = new CanvasKit.ParagraphStyle({
874            textStyle: {
875                color: CanvasKit.BLACK,
876                fontFamilies: ['Noto Serif'],
877                fontSize: 20,
878                decoration: CanvasKit.UnderlineDecoration,
879                decorationThickness: 1.5, // multiplier based on font size
880                decorationStyle: CanvasKit.DecorationStyle.Wavy,
881            },
882        });
883
884        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
885        builder.addText('VAVAVAVAVAVAVA\nVAVA\n');
886
887        const blueText = new CanvasKit.TextStyle({
888            backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
889            color: CanvasKit.Color(48, 37, 199),
890            fontFamilies: ['Noto Serif'],
891            textBaseline: CanvasKit.TextBaseline.Ideographic,
892            decoration: CanvasKit.LineThroughDecoration,
893            decorationThickness: 1.5, // multiplier based on font size
894        });
895        builder.pushStyle(blueText);
896        builder.addText(`Gosh I hope this wraps at some point, it is such a long line.`);
897        builder.pop();
898        builder.addText(` I'm done with the blue now. `);
899        builder.addText(`Now I hope we should stop before we get 8 lines tall. `);
900        const paragraph = builder.build();
901
902        paragraph.layout(wrapTo);
903
904        expect(paragraph.getAlphabeticBaseline()).toBeCloseTo(21.377, 3);
905        expect(paragraph.getHeight()).toEqual(227);
906        expect(paragraph.getIdeographicBaseline()).toBeCloseTo(27.236, 3);
907        expect(paragraph.getLongestLine()).toBeCloseTo(195.664, 3);
908        expect(paragraph.getMaxIntrinsicWidth()).toBeCloseTo(1167.140, 3);
909        expect(paragraph.getMaxWidth()).toEqual(200);
910        expect(paragraph.getMinIntrinsicWidth()).toBeCloseTo(172.360, 3);
911        // Check "VAVAVAVAVAVAVA"
912        expect(paragraph.getWordBoundary(8)).toEqual({
913            start: 0,
914            end: 14,
915        });
916        // Check "I"
917        expect(paragraph.getWordBoundary(25)).toEqual({
918            start: 25,
919            end: 26,
920        });
921        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, 230), paint);
922        canvas.drawParagraph(paragraph, 10, 10);
923
924        paint.delete();
925        fontMgr.delete();
926        paragraph.delete();
927        builder.delete();
928    });
929
930    gm('paragraph_text_styles_mixed_leading_distribution', (canvas) => {
931        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
932        expect(fontMgr.countFamilies()).toEqual(1);
933        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
934
935        const wrapTo = 200;
936
937        const paraStyle = new CanvasKit.ParagraphStyle({
938            textStyle: {
939                color: CanvasKit.BLACK,
940                backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
941                fontFamilies: ['Noto Serif'],
942                fontSize: 10,
943                heightMultiplier: 10,
944            },
945        });
946
947        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
948        builder.addText('Not half leading');
949
950        const halfLeadingText = new CanvasKit.TextStyle({
951            color: CanvasKit.Color(48, 37, 199),
952            backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
953            fontFamilies: ['Noto Serif'],
954            fontSize: 10,
955            heightMultiplier: 10,
956            halfLeading: true,
957        });
958        builder.pushStyle(halfLeadingText);
959        builder.addText('Half Leading Text');
960        const paragraph = builder.build();
961
962        paragraph.layout(wrapTo);
963        canvas.drawParagraph(paragraph, 0, 0);
964
965        fontMgr.delete();
966        paragraph.delete();
967        builder.delete();
968    });
969
970    gm('paragraph_mixed_text_height_behavior', (canvas) => {
971        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
972        expect(fontMgr.countFamilies()).toEqual(1);
973        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
974        const paint = new CanvasKit.Paint();
975        paint.setColor(CanvasKit.RED);
976        paint.setStyle(CanvasKit.PaintStyle.Stroke);
977
978        const wrapTo = 220;
979        const behaviors = ["All", "DisableFirstAscent", "DisableLastDescent", "DisableAll"];
980
981        for (let i = 0; i < behaviors.length; i++) {
982            const style = new CanvasKit.ParagraphStyle({
983                textStyle: {
984                    color: CanvasKit.BLACK,
985                    fontFamilies: ['Noto Serif'],
986                    fontSize: 20,
987                    heightMultiplier: 3, // make the difference more obvious
988                },
989                textHeightBehavior: CanvasKit.TextHeightBehavior[behaviors[i]],
990            });
991            const builder = CanvasKit.ParagraphBuilder.Make(style, fontMgr);
992            builder.addText('Text height behavior\nof '+behaviors[i]);
993            const paragraph = builder.build();
994            paragraph.layout(wrapTo);
995            canvas.drawParagraph(paragraph, 0, 150 * i);
996            canvas.drawRect(CanvasKit.LTRBRect(0, 150 * i, wrapTo, 150 * i + 120), paint);
997            paragraph.delete();
998            builder.delete();
999        }
1000        paint.delete();
1001        fontMgr.delete();
1002    });
1003
1004    it('should not crash if we omit font family on pushed textStyle', () => {
1005        const surface = CanvasKit.MakeCanvasSurface('test');
1006        expect(surface).toBeTruthy('Could not make surface');
1007
1008        const canvas = surface.getCanvas();
1009        const paint = new CanvasKit.Paint();
1010
1011        paint.setColor(CanvasKit.RED);
1012        paint.setStyle(CanvasKit.PaintStyle.Stroke);
1013
1014        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
1015
1016        const wrapTo = 250;
1017
1018        const paraStyle = new CanvasKit.ParagraphStyle({
1019            textStyle: {
1020                fontFamilies: ['Noto Serif'],
1021                fontSize: 20,
1022            },
1023            textDirection: CanvasKit.TextDirection.RTL,
1024            disableHinting: true,
1025        });
1026
1027        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
1028        builder.addText('Default text\n');
1029
1030        const boldItalic = new CanvasKit.TextStyle({
1031            fontStyle: {
1032                weight: CanvasKit.FontWeight.Bold,
1033                slant: CanvasKit.FontSlant.Italic,
1034            }
1035        });
1036        builder.pushStyle(boldItalic);
1037        builder.addText(`Bold, Italic\n`); // doesn't show up, but we don't crash
1038        builder.pop();
1039        builder.addText(`back to normal`);
1040        const paragraph = builder.build();
1041
1042        paragraph.layout(wrapTo);
1043        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
1044        canvas.drawParagraph(paragraph, 10, 10);
1045
1046        surface.flush();
1047
1048        paragraph.delete();
1049        builder.delete();
1050        paint.delete();
1051        fontMgr.delete();
1052    });
1053
1054    it('should not crash if we omit font family on paragraph style', () => {
1055        const surface = CanvasKit.MakeCanvasSurface('test');
1056        expect(surface).toBeTruthy('Could not make surface');
1057
1058        const canvas = surface.getCanvas();
1059        const paint = new CanvasKit.Paint();
1060
1061        paint.setColor(CanvasKit.RED);
1062        paint.setStyle(CanvasKit.PaintStyle.Stroke);
1063
1064        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
1065
1066        const wrapTo = 250;
1067
1068        const paraStyle = new CanvasKit.ParagraphStyle({
1069            textStyle: {
1070                fontSize: 20,
1071            },
1072            textDirection: CanvasKit.TextDirection.RTL,
1073            disableHinting: true,
1074        });
1075
1076        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
1077        builder.addText('Default text\n');
1078
1079        const boldItalic = new CanvasKit.TextStyle({
1080            fontStyle: {
1081                weight: CanvasKit.FontWeight.Bold,
1082                slant: CanvasKit.FontSlant.Italic,
1083            }
1084        });
1085        builder.pushStyle(boldItalic);
1086        builder.addText(`Bold, Italic\n`);
1087        builder.pop();
1088        builder.addText(`back to normal`);
1089        const paragraph = builder.build();
1090
1091        paragraph.layout(wrapTo);
1092        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
1093        canvas.drawParagraph(paragraph, 10, 10);
1094
1095        surface.flush();
1096
1097        paragraph.delete();
1098        paint.delete();
1099        fontMgr.delete();
1100        builder.delete();
1101    });
1102
1103    gm('paragraph_builder_with_reset', (canvas) => {
1104        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
1105
1106        const wrapTo = 250;
1107
1108        const paraStyle = new CanvasKit.ParagraphStyle({
1109            textStyle: {
1110                fontSize: 20,
1111            },
1112        });
1113
1114        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
1115        builder.addText('Default text\n');
1116
1117        const boldItalic = new CanvasKit.TextStyle({
1118            fontStyle: {
1119                weight: CanvasKit.FontWeight.Bold,
1120                slant: CanvasKit.FontSlant.Italic,
1121            }
1122        });
1123        builder.pushStyle(boldItalic);
1124        builder.addText(`Bold, Italic\n`);
1125        builder.pop();
1126        const paragraph = builder.build();
1127        paragraph.layout(wrapTo);
1128
1129        builder.reset();
1130        builder.addText('This builder has been reused\n');
1131
1132        builder.pushStyle(boldItalic);
1133        builder.addText(`2 Bold, Italic\n`);
1134        builder.pop();
1135        builder.addText(`2 back to normal`);
1136        const paragraph2 = builder.build();
1137        paragraph2.layout(wrapTo);
1138
1139        canvas.drawParagraph(paragraph, 10, 10);
1140        canvas.drawParagraph(paragraph2, 10, 100);
1141
1142        paragraph.delete();
1143        paragraph2.delete();
1144        fontMgr.delete();
1145        builder.delete();
1146    });
1147
1148    // This helped find and resolve skbug.com/13247
1149    gm('paragraph_saved_to_skpicture', (canvas) => {
1150        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
1151
1152        const wrapTo = 250;
1153
1154        const paraStyle = new CanvasKit.ParagraphStyle({
1155            textStyle: {
1156                fontSize: 20,
1157            },
1158        });
1159
1160        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
1161        builder.addText('This was saved to an SkPicture\n');
1162
1163        const boldItalic = new CanvasKit.TextStyle({
1164            fontStyle: {
1165                weight: CanvasKit.FontWeight.Bold,
1166                slant: CanvasKit.FontSlant.Italic,
1167            }
1168        });
1169        builder.pushStyle(boldItalic);
1170        builder.addText(`Bold, Italic\n`);
1171        builder.pop();
1172        const paragraph = builder.build();
1173        paragraph.layout(wrapTo);
1174
1175        const recorder = new CanvasKit.PictureRecorder();
1176        const skpCanvas = recorder.beginRecording(CanvasKit.LTRBRect(0, 0, 200, 200));
1177        skpCanvas.drawParagraph(paragraph, 10, 10);
1178        const picture = recorder.finishRecordingAsPicture();
1179
1180        canvas.drawPicture(CanvasKit.MakePicture(picture.serialize()));
1181
1182        picture.delete();
1183        recorder.delete();
1184        paragraph.delete();
1185        fontMgr.delete();
1186        builder.delete();
1187    });
1188
1189    it('should replace tab characters', () => {
1190        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
1191        const wrapTo = 250;
1192
1193        const paraStyle = new CanvasKit.ParagraphStyle({
1194            textStyle: {
1195                color: CanvasKit.BLACK,
1196                fontFamilies: ['Noto Serif'],
1197                fontSize: 20,
1198            },
1199            textAlign: CanvasKit.TextAlign.Left,
1200            replaceTabCharacters: true,
1201        });
1202
1203        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
1204        builder.addText('1\t2');
1205
1206        const paragraph = builder.build();
1207        paragraph.layout(wrapTo);
1208
1209        const lines = paragraph.getShapedLines();
1210
1211        expect(lines.length).toEqual(1);
1212        expect(lines[0].runs.length).toEqual(1);
1213        expect(lines[0].runs[0].glyphs.length).toEqual(3);
1214
1215        // The tab should not be a missing glyph.
1216        expect(lines[0].runs[0].glyphs[1]).not.toEqual(0);
1217
1218        paragraph.delete();
1219        fontMgr.delete();
1220        builder.delete();
1221    });
1222
1223    gm('paragraph_fontSize_and_heightMultiplier_0', (canvas) => {
1224        const fontMgr = CanvasKit.FontMgr.FromData(robotoFontBuffer);
1225        const wrapTo = 250;
1226        const paraStyle = new CanvasKit.ParagraphStyle({
1227            textStyle: {
1228                fontSize: 0,
1229                heightMultiplier: 0,
1230            },
1231        });
1232
1233        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
1234        builder.addText('This should not be visible');
1235        const paragraph = builder.build();
1236        paragraph.layout(wrapTo);
1237        canvas.drawParagraph(paragraph, 10, 10);
1238
1239        let rects = paragraph.getRectsForRange(0, 1, CanvasKit.RectHeightStyle.Tight, CanvasKit.RectWidthStyle.Tight);
1240        const paint = new CanvasKit.Paint();
1241        paint.setColor(CanvasKit.Color(255, 0, 0));
1242        canvas.drawRect(rects[0].rect, paint);
1243        expect(rects[0].dir).toEqual(CanvasKit.TextDirection.LTR);
1244        paint.delete();
1245
1246        paragraph.delete();
1247        fontMgr.delete();
1248        builder.delete();
1249    });
1250
1251});
1252