• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1describe('Paragraph Behavior', function() {
2    let container;
3
4    let notoSerifFontBuffer = null;
5    // This font is known to support kerning
6    const notoSerifFontLoaded = fetch('/assets/NotoSerif-Regular.ttf').then(
7        (response) => response.arrayBuffer()).then(
8        (buffer) => {
9            notoSerifFontBuffer = buffer;
10        });
11
12    let notoSerifBoldItalicFontBuffer = null;
13    const notoSerifBoldItalicFontLoaded = fetch('/assets/NotoSerif-BoldItalic.ttf').then(
14        (response) => response.arrayBuffer()).then(
15        (buffer) => {
16            notoSerifBoldItalicFontBuffer = buffer;
17        });
18
19    let emojiFontBuffer = null;
20    const emojiFontLoaded = fetch('/assets/NotoColorEmoji.ttf').then(
21        (response) => response.arrayBuffer()).then(
22        (buffer) => {
23            emojiFontBuffer = buffer;
24        });
25
26    let robotoFontBuffer = null;
27    const robotoFontLoaded = fetch('/assets/Roboto-Regular.otf').then(
28        (response) => response.arrayBuffer()).then(
29        (buffer) => {
30            robotoFontBuffer = buffer;
31        });
32
33    beforeEach(async () => {
34        await LoadCanvasKit;
35        await notoSerifFontLoaded;
36        await notoSerifBoldItalicFontLoaded;
37        await emojiFontLoaded;
38        container = document.createElement('div');
39        container.innerHTML = `
40            <canvas width=600 height=600 id=test></canvas>
41            <canvas width=600 height=600 id=report></canvas>`;
42        document.body.appendChild(container);
43    });
44
45    afterEach(() => {
46        document.body.removeChild(container);
47    });
48
49    gm('paragraph_basic', (canvas) => {
50        const paint = new CanvasKit.Paint();
51
52        paint.setColor(CanvasKit.RED);
53        paint.setStyle(CanvasKit.PaintStyle.Stroke);
54
55        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
56        expect(fontMgr.countFamilies()).toEqual(1);
57        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
58
59        const wrapTo = 200;
60
61        const paraStyle = new CanvasKit.ParagraphStyle({
62            textStyle: {
63                color: CanvasKit.BLACK,
64                fontFamilies: ['Noto Serif'],
65                fontSize: 20,
66            },
67            textAlign: CanvasKit.TextAlign.Center,
68            maxLines: 8,
69            ellipsis: '.._.',
70        });
71
72        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
73        builder.addText('VAVAVAVAVAVAVA\nVAVA\n');
74
75        const blueText = new CanvasKit.TextStyle({
76            backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
77            color: CanvasKit.Color(48, 37, 199),
78            fontFamilies: ['Noto Serif'],
79            decoration: CanvasKit.LineThroughDecoration,
80            decorationThickness: 1.5, // multiplier based on font size
81            fontSize: 24,
82        });
83        builder.pushStyle(blueText);
84        builder.addText(`Gosh I hope this wraps at some point, it is such a long line.`)
85        builder.pop();
86        builder.addText(` I'm done with the blue now. `)
87        builder.addText(`Now I hope we should stop before we get 8 lines tall. `);
88        const paragraph = builder.build();
89
90        paragraph.layout(wrapTo);
91
92        expect(paragraph.didExceedMaxLines()).toBeTruthy();
93        expect(paragraph.getAlphabeticBaseline()).toBeCloseTo(21.377, 3);
94        expect(paragraph.getHeight()).toEqual(240);
95        expect(paragraph.getIdeographicBaseline()).toBeCloseTo(27.236, 3);
96        expect(paragraph.getLongestLine()).toBeCloseTo(193.820, 3);
97        expect(paragraph.getMaxIntrinsicWidth()).toBeCloseTo(1444.250, 3);
98        expect(paragraph.getMaxWidth()).toEqual(200);
99        expect(paragraph.getMinIntrinsicWidth()).toBeCloseTo(172.360, 3);
100        expect(paragraph.getWordBoundary(8)).toEqual({
101            start: 0,
102            end: 14,
103        });
104        expect(paragraph.getWordBoundary(25)).toEqual({
105            start: 25,
106            end: 26,
107        });
108
109
110        const lineMetrics = paragraph.getLineMetrics();
111        expect(lineMetrics.length).toEqual(8); // 8 lines worth of metrics
112        const flm = lineMetrics[0]; // First Line Metric
113        expect(flm.startIndex).toEqual(0);
114        expect(flm.endExcludingWhitespaces).toEqual(14)
115        expect(flm.endIndex).toEqual(14); // Including whitespaces but excluding newlines
116        expect(flm.endIncludingNewline).toEqual(15);
117        expect(flm.lineNumber).toEqual(0);
118        expect(flm.isHardBreak).toEqual(true);
119        expect(flm.ascent).toBeCloseTo(21.377, 3);
120        expect(flm.descent).toBeCloseTo(5.859, 3);
121        expect(flm.height).toBeCloseTo(27.000, 3);
122        expect(flm.width).toBeCloseTo(172.360, 3);
123        expect(flm.left).toBeCloseTo(13.818, 3);
124        expect(flm.baseline).toBeCloseTo(21.141, 3);
125
126        canvas.clear(CanvasKit.WHITE);
127        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, 230), paint);
128        canvas.drawParagraph(paragraph, 10, 10);
129
130        paint.delete();
131        fontMgr.delete();
132        paragraph.delete();
133        builder.delete();
134    });
135
136    gm('paragraph_foreground_and_background_color', (canvas) => {
137        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
138        expect(fontMgr.countFamilies()).toEqual(1);
139        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
140
141        const wrapTo = 200;
142
143        const paraStyle = new CanvasKit.ParagraphStyle({
144            textStyle: {
145                foregroundColor: CanvasKit.Color4f(1.0, 0, 0, 0.8),
146                backgroundColor: CanvasKit.Color4f(0, 0, 1.0, 0.8),
147                // color should default to black
148                fontFamilies: ['Noto Serif'],
149                fontSize: 20,
150            },
151
152            textAlign: CanvasKit.TextAlign.Center,
153        });
154        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
155        builder.addText(
156            'This text has a red foregroundColor and a blue backgroundColor.');
157        const paragraph = builder.build();
158        paragraph.layout(300);
159
160        canvas.clear(CanvasKit.WHITE);
161        canvas.drawParagraph(paragraph, 10, 10);
162
163        fontMgr.delete();
164        paragraph.delete();
165        builder.delete();
166    });
167
168    gm('paragraph_foreground_stroke_paint', (canvas) => {
169        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
170        expect(fontMgr.countFamilies()).toEqual(1);
171        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
172
173        const wrapTo = 200;
174
175        const textStyle = {
176            fontFamilies: ['Noto Serif'],
177            fontSize: 40,
178        };
179        const paraStyle = new CanvasKit.ParagraphStyle({
180            textStyle: textStyle,
181            textAlign: CanvasKit.TextAlign.Center,
182        });
183        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
184
185        const fg = new CanvasKit.Paint();
186        fg.setColor(CanvasKit.BLACK);
187        fg.setStyle(CanvasKit.PaintStyle.Stroke);
188
189        const bg = new CanvasKit.Paint();
190        bg.setColor(CanvasKit.TRANSPARENT);
191
192        builder.pushPaintStyle(textStyle, fg, bg);
193        builder.addText(
194            'This text is stroked in black and has no fill');
195        const paragraph = builder.build();
196        paragraph.layout(300);
197
198        canvas.clear(CanvasKit.WHITE);
199        canvas.drawParagraph(paragraph, 10, 10);
200        // Again 5px to the right so you can tell the fill is transparent
201        canvas.drawParagraph(paragraph, 15, 10);
202
203        fg.delete();
204        bg.delete();
205        fontMgr.delete();
206        paragraph.delete();
207        builder.delete();
208    });
209
210    gm('paragraph_letter_word_spacing', (canvas) => {
211        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
212        expect(fontMgr.countFamilies()).toEqual(1);
213        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
214
215        const wrapTo = 200;
216
217        const paraStyle = new CanvasKit.ParagraphStyle({
218            textStyle: {
219                // color should default to black
220                fontFamilies: ['Noto Serif'],
221                fontSize: 20,
222                letterSpacing: 5,
223                wordSpacing: 10,
224            },
225
226            textAlign: CanvasKit.TextAlign.Center,
227        });
228        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
229        builder.addText(
230            'This text should have a lot of space between the letters and words.');
231        const paragraph = builder.build();
232        paragraph.layout(300);
233
234        canvas.clear(CanvasKit.WHITE);
235        canvas.drawParagraph(paragraph, 10, 10);
236
237        fontMgr.delete();
238        paragraph.delete();
239        builder.delete();
240    });
241
242    gm('paragraph_shadows', (canvas) => {
243        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
244        expect(fontMgr.countFamilies()).toEqual(1);
245        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
246
247        const wrapTo = 200;
248
249        const paraStyle = new CanvasKit.ParagraphStyle({
250            textStyle: {
251                color: CanvasKit.WHITE,
252                fontFamilies: ['Noto Serif'],
253                fontSize: 20,
254                shadows: [{color: CanvasKit.BLACK, blurRadius: 15},
255                          {color: CanvasKit.RED, blurRadius: 5, offset: [10, 10]}],
256            },
257
258            textAlign: CanvasKit.TextAlign.Center,
259        });
260        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
261        builder.addText('This text should have a shadow behind it.');
262        const paragraph = builder.build();
263        paragraph.layout(300);
264
265        canvas.clear(CanvasKit.WHITE);
266        canvas.drawParagraph(paragraph, 10, 10);
267
268        fontMgr.delete();
269        paragraph.delete();
270        builder.delete();
271    });
272
273    gm('paragraph_strut_style', (canvas) => {
274        const fontMgr = CanvasKit.FontMgr.FromData(robotoFontBuffer);
275        expect(fontMgr.countFamilies()).toEqual(1);
276        expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
277
278        // The lines in this paragraph should have the same height despite the third
279        // line having a larger font size.
280        const paraStrutStyle = new CanvasKit.ParagraphStyle({
281            textStyle: {
282                fontFamilies: ['Roboto'],
283                color: CanvasKit.BLACK,
284            },
285            strutStyle: {
286                strutEnabled: true,
287                fontFamilies: ['Roboto'],
288                fontSize: 28,
289                heightMultiplier: 1.5,
290                forceStrutHeight: true,
291            },
292        });
293        const paraStyle = new CanvasKit.ParagraphStyle({
294            textStyle: {
295                fontFamilies: ['Roboto'],
296                color: CanvasKit.BLACK,
297            },
298        });
299        const roboto28Style = new CanvasKit.TextStyle({
300            color: CanvasKit.BLACK,
301            fontFamilies: ['Roboto'],
302            fontSize: 28,
303        });
304        const roboto32Style = new CanvasKit.TextStyle({
305            color: CanvasKit.BLACK,
306            fontFamilies: ['Roboto'],
307            fontSize: 32,
308        });
309        const builder = CanvasKit.ParagraphBuilder.Make(paraStrutStyle, fontMgr);
310        builder.pushStyle(roboto28Style);
311        builder.addText('This paragraph\n');
312        builder.pushStyle(roboto32Style);
313        builder.addText('is using\n');
314        builder.pop();
315        builder.pushStyle(roboto28Style);
316        builder.addText('a strut style!\n');
317        builder.pop();
318        builder.pop();
319
320        const builder2 = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
321        builder2.pushStyle(roboto28Style);
322        builder2.addText('This paragraph\n');
323        builder2.pushStyle(roboto32Style);
324        builder2.addText('is not using\n');
325        builder2.pop();
326        builder2.pushStyle(roboto28Style);
327        builder2.addText('a strut style!\n');
328        builder2.pop();
329        builder2.pop();
330
331        const paragraph = builder.build();
332        paragraph.layout(300);
333
334        const paragraph2 = builder2.build();
335        paragraph2.layout(300);
336
337        canvas.clear(CanvasKit.WHITE);
338        canvas.drawParagraph(paragraph, 10, 10);
339        canvas.drawParagraph(paragraph2, 220, 10);
340
341        fontMgr.delete();
342        paragraph.delete();
343        builder.delete();
344    });
345
346    gm('paragraph_font_features', (canvas) => {
347        const fontMgr = CanvasKit.FontMgr.FromData(robotoFontBuffer);
348        expect(fontMgr.countFamilies()).toEqual(1);
349        expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
350
351
352        const paraStyle = new CanvasKit.ParagraphStyle({
353            textStyle: {
354                color: CanvasKit.BLACK,
355                fontFamilies: ['Roboto'],
356                fontSize: 30,
357                fontFeatures: [{name: 'smcp', value: 1}]
358            },
359            textAlign: CanvasKit.TextAlign.Center,
360        });
361        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
362        builder.addText('This Text Should Be In Small Caps');
363        const paragraph = builder.build();
364        paragraph.layout(300);
365
366        canvas.clear(CanvasKit.WHITE);
367        canvas.drawParagraph(paragraph, 10, 10);
368
369        fontMgr.delete();
370        paragraph.delete();
371        builder.delete();
372    });
373
374    gm('paragraph_placeholders', (canvas) => {
375        const fontMgr = CanvasKit.FontMgr.FromData(robotoFontBuffer);
376        expect(fontMgr.countFamilies()).toEqual(1);
377        expect(fontMgr.getFamilyName(0)).toEqual('Roboto');
378
379
380        const paraStyle = new CanvasKit.ParagraphStyle({
381            textStyle: {
382                color: CanvasKit.BLACK,
383                fontFamilies: ['Roboto'],
384                fontSize: 20,
385            },
386            textAlign: CanvasKit.TextAlign.Center,
387        });
388        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
389        builder.addText('There should be ');
390        builder.addPlaceholder(10, 10, CanvasKit.PlaceholderAlignment.AboveBaseline,
391                               CanvasKit.TextBaseline.Ideographic);
392        builder.addText('a space in this sentence.\n');
393
394        builder.addText('There should be ');
395        builder.addPlaceholder(10, 10, CanvasKit.PlaceholderAlignment.BelowBaseline,
396                               CanvasKit.TextBaseline.Ideographic);
397        builder.addText('a dropped space in this sentence.\n');
398
399        builder.addText('There should be ');
400        builder.addPlaceholder(10, 10, null, null, 20);
401        builder.addText('an offset space in this sentence.\n');
402        const paragraph = builder.build();
403        paragraph.layout(300);
404
405        let rects = paragraph.getRectsForPlaceholders();
406
407        canvas.clear(CanvasKit.WHITE);
408        canvas.drawParagraph(paragraph, 10, 10);
409
410        for (const rect of rects) {
411            const p = new CanvasKit.Paint();
412            p.setColor(CanvasKit.Color(0, 0, 255));
413            p.setStyle(CanvasKit.PaintStyle.Stroke);
414            // Account for the (10, 10) offset when we painted the paragraph.
415            const placeholder =
416                CanvasKit.LTRBRect(rect[0]+10,rect[1]+10,rect[2]+10,rect[3]+10);
417            canvas.drawRect(placeholder, p);
418            p.delete();
419        }
420
421        fontMgr.delete();
422        paragraph.delete();
423        builder.delete();
424    });
425
426    // loosely based on SkParagraph_GetRectsForRangeParagraph test in c++ code.
427    gm('paragraph_rects', (canvas) => {
428        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
429
430        const wrapTo = 550;
431        const hStyle = CanvasKit.RectHeightStyle.Max;
432        const wStyle = CanvasKit.RectWidthStyle.Tight;
433
434        const mallocedColor = CanvasKit.Malloc(Float32Array, 4);
435        mallocedColor.toTypedArray().set([0.9, 0.1, 0.1, 1.0]);
436
437        const paraStyle = new CanvasKit.ParagraphStyle({
438            textStyle: {
439                color: mallocedColor,
440                fontFamilies: ['Noto Serif'],
441                fontSize: 50,
442            },
443            textAlign: CanvasKit.TextAlign.Left,
444            maxLines: 10,
445        });
446        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
447        builder.addText('12345,  \"67890\" 12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345');
448        const paragraph = builder.build();
449        CanvasKit.Free(mallocedColor);
450
451        paragraph.layout(wrapTo);
452
453        const ranges = [
454            {
455                start: 0,
456                end: 0,
457                expectedNum: 0,
458            },
459            {
460                start: 0,
461                end: 1,
462                expectedNum: 1,
463                color: CanvasKit.Color(200, 0, 200),
464            },
465            {
466                start: 2,
467                end: 8,
468                expectedNum: 1,
469                color: CanvasKit.Color(255, 0, 0),
470            },
471            {
472                start: 8,
473                end: 21,
474                expectedNum: 1,
475                color: CanvasKit.Color(0, 255, 0),
476            },
477            {
478                start: 30,
479                end: 100,
480                expectedNum: 4,
481                color: CanvasKit.Color(0, 0, 255),
482            },
483            {
484                start: 19,
485                end: 22,
486                expectedNum: 1,
487                color: CanvasKit.Color(0, 200, 200),
488            }
489        ];
490        canvas.clear(CanvasKit.WHITE);
491        // Move it down a bit so we can see the rects that go above 0,0
492        canvas.translate(10, 10);
493        canvas.drawParagraph(paragraph, 0, 0);
494
495        for (const test of ranges) {
496            let rects = paragraph.getRectsForRange(test.start, test.end, hStyle, wStyle);
497            expect(Array.isArray(rects)).toEqual(true);
498            expect(rects.length).toEqual(test.expectedNum);
499
500            for (const rect of rects) {
501                expect(rect.direction.value).toEqual(CanvasKit.TextDirection.LTR.value);
502                const p = new CanvasKit.Paint();
503                p.setColor(test.color);
504                p.setStyle(CanvasKit.PaintStyle.Stroke);
505                canvas.drawRect(rect, p);
506                p.delete();
507            }
508        }
509        expect(CanvasKit.RectHeightStyle.Strut).toBeTruthy();
510
511        fontMgr.delete();
512        paragraph.delete();
513        builder.delete();
514    });
515
516    gm('paragraph_emoji', (canvas) => {
517        const fontMgr = CanvasKit.FontMgr.FromData([notoSerifFontBuffer, emojiFontBuffer]);
518        expect(fontMgr.countFamilies()).toEqual(2);
519        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
520        expect(fontMgr.getFamilyName(1)).toEqual('Noto Color Emoji');
521
522        const wrapTo = 450;
523
524        const paraStyle = new CanvasKit.ParagraphStyle({
525            textStyle: {
526                color: CanvasKit.BLACK,
527                // Put text first, otherwise the "emoji space" is used and that looks bad.
528                fontFamilies: ['Noto Serif', 'Noto Color Emoji'],
529                fontSize: 30,
530            },
531            textAlign: CanvasKit.TextAlign.Left,
532            maxLines: 10,
533        });
534
535        const textStyle = new CanvasKit.TextStyle({
536            color: CanvasKit.BLACK,
537            // The number 4 matches an emoji and looks strange w/o this additional style.
538            fontFamilies: ['Noto Serif'],
539            fontSize: 30,
540        });
541
542        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
543        builder.pushStyle(textStyle);
544        builder.addText('4 flags on following line:\n');
545        builder.pop();
546        builder.addText(`��️‍�� ���� ���� ����\n`);
547        builder.addText('Rainbow Italy Liberia USA\n\n');
548        builder.addText('Emoji below should wrap:\n');
549        builder.addText(`����������������‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍����‍��‍����‍��‍��‍��`);
550        const paragraph = builder.build();
551
552        paragraph.layout(wrapTo);
553
554        canvas.clear(CanvasKit.WHITE);
555        canvas.drawParagraph(paragraph, 10, 10);
556
557        const paint = new CanvasKit.Paint();
558        paint.setColor(CanvasKit.RED);
559        paint.setStyle(CanvasKit.PaintStyle.Stroke);
560        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
561
562        fontMgr.delete();
563        paint.delete();
564        builder.delete();
565        paragraph.delete();
566    });
567
568    gm('paragraph_hits', (canvas) => {
569        const fontMgr = CanvasKit.FontMgr.FromData([notoSerifFontBuffer]);
570
571        const wrapTo = 300;
572
573        const paraStyle = new CanvasKit.ParagraphStyle({
574            textStyle: {
575                color: CanvasKit.BLACK,
576                fontFamilies: ['Noto Serif'],
577                fontSize: 50,
578            },
579            textAlign: CanvasKit.TextAlign.Left,
580            maxLines: 10,
581        });
582        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
583        builder.addText('UNCOPYRIGHTABLE');
584        const paragraph = builder.build();
585
586        paragraph.layout(wrapTo);
587
588        canvas.clear(CanvasKit.WHITE);
589        canvas.translate(10, 10);
590        canvas.drawParagraph(paragraph, 0, 0);
591
592        const paint = new CanvasKit.Paint();
593
594        paint.setColor(CanvasKit.Color(255, 0, 0));
595        paint.setStyle(CanvasKit.PaintStyle.Fill);
596        canvas.drawCircle(20, 30, 3, paint);
597
598        paint.setColor(CanvasKit.Color(0, 0, 255));
599        canvas.drawCircle(80, 90, 3, paint);
600
601        paint.setColor(CanvasKit.Color(0, 255, 0));
602        canvas.drawCircle(280, 2, 3, paint);
603
604        let posU = paragraph.getGlyphPositionAtCoordinate(20, 30);
605        expect(posU).toEqual({
606            pos: 1,
607            affinity: CanvasKit.Affinity.Upstream
608        });
609        let posA = paragraph.getGlyphPositionAtCoordinate(80, 90);
610        expect(posA).toEqual({
611            pos: 11,
612            affinity: CanvasKit.Affinity.Downstream
613        });
614        let posG = paragraph.getGlyphPositionAtCoordinate(280, 2);
615        expect(posG).toEqual({
616            pos: 9,
617            affinity: CanvasKit.Affinity.Upstream
618        });
619
620        builder.delete();
621        paragraph.delete();
622        paint.delete();
623        fontMgr.delete();
624    });
625
626    gm('paragraph_styles', (canvas) => {
627        const paint = new CanvasKit.Paint();
628
629        paint.setColor(CanvasKit.RED);
630        paint.setStyle(CanvasKit.PaintStyle.Stroke);
631
632        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
633
634        const wrapTo = 250;
635
636        const paraStyle = new CanvasKit.ParagraphStyle({
637            textStyle: {
638                fontFamilies: ['Noto Serif'],
639                fontSize: 20,
640                fontStyle: {
641                    weight: CanvasKit.FontWeight.Light,
642                }
643            },
644            textDirection: CanvasKit.TextDirection.RTL,
645            disableHinting: true,
646        });
647
648        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
649        builder.addText('Default text\n');
650
651        const boldItalic = new CanvasKit.TextStyle({
652            color: CanvasKit.RED,
653            fontFamilies: ['Noto Serif'],
654            fontSize: 20,
655            fontStyle: {
656                weight: CanvasKit.FontWeight.Bold,
657                width: CanvasKit.FontWidth.Expanded,
658                slant: CanvasKit.FontSlant.Italic,
659            }
660        });
661        builder.pushStyle(boldItalic);
662        builder.addText(`Bold, Expanded, Italic\n`);
663        builder.pop();
664        builder.addText(`back to normal`);
665        const paragraph = builder.build();
666
667        paragraph.layout(wrapTo);
668
669        canvas.clear(CanvasKit.WHITE);
670
671        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
672        canvas.drawParagraph(paragraph, 10, 10);
673
674        paint.delete();
675        paragraph.delete();
676        builder.delete();
677        fontMgr.delete();
678    });
679
680    gm('paragraph_font_provider', (canvas) => {
681        const paint = new CanvasKit.Paint();
682
683        paint.setColor(CanvasKit.RED);
684        paint.setStyle(CanvasKit.PaintStyle.Stroke);
685
686        // Register Noto Serif as 'sans-serif'.
687        const fontSrc = CanvasKit.TypefaceFontProvider.Make();
688        fontSrc.registerFont(notoSerifFontBuffer, 'sans-serif');
689        fontSrc.registerFont(notoSerifBoldItalicFontBuffer, 'sans-serif');
690
691        const wrapTo = 250;
692
693        const paraStyle = new CanvasKit.ParagraphStyle({
694            textStyle: {
695                fontFamilies: ['sans-serif'],
696                fontSize: 20,
697                fontStyle: {
698                    weight: CanvasKit.FontWeight.Light,
699                }
700            },
701            textDirection: CanvasKit.TextDirection.RTL,
702            disableHinting: true,
703        });
704
705        const builder = CanvasKit.ParagraphBuilder.MakeFromFontProvider(paraStyle, fontSrc);
706        builder.addText('Default text\n');
707
708        const boldItalic = new CanvasKit.TextStyle({
709            color: CanvasKit.RED,
710            fontFamilies: ['sans-serif'],
711            fontSize: 20,
712            fontStyle: {
713                weight: CanvasKit.FontWeight.Bold,
714                width: CanvasKit.FontWidth.Expanded,
715                slant: CanvasKit.FontSlant.Italic,
716            }
717        });
718        builder.pushStyle(boldItalic);
719        builder.addText(`Bold, Expanded, Italic\n`);
720        builder.pop();
721        builder.addText(`back to normal`);
722        const paragraph = builder.build();
723
724        paragraph.layout(wrapTo);
725
726        canvas.clear(CanvasKit.WHITE);
727
728        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
729        canvas.drawParagraph(paragraph, 10, 10);
730
731        paint.delete();
732        paragraph.delete();
733        builder.delete();
734        fontSrc.delete();
735    });
736
737    gm('paragraph_text_styles', (canvas) => {
738        const paint = new CanvasKit.Paint();
739
740        paint.setColor(CanvasKit.GREEN);
741        paint.setStyle(CanvasKit.PaintStyle.Stroke);
742
743        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
744        expect(fontMgr.countFamilies()).toEqual(1);
745        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
746
747        const wrapTo = 200;
748
749        const paraStyle = new CanvasKit.ParagraphStyle({
750            textStyle: {
751                color: CanvasKit.BLACK,
752                fontFamilies: ['Noto Serif'],
753                fontSize: 20,
754                decoration: CanvasKit.UnderlineDecoration,
755                decorationThickness: 1.5, // multiplier based on font size
756                decorationStyle: CanvasKit.DecorationStyle.Wavy,
757            },
758        });
759
760        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
761        builder.addText('VAVAVAVAVAVAVA\nVAVA\n');
762
763        const blueText = new CanvasKit.TextStyle({
764            backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
765            color: CanvasKit.Color(48, 37, 199),
766            fontFamilies: ['Noto Serif'],
767            textBaseline: CanvasKit.TextBaseline.Ideographic,
768            decoration: CanvasKit.LineThroughDecoration,
769            decorationThickness: 1.5, // multiplier based on font size
770        });
771        builder.pushStyle(blueText);
772        builder.addText(`Gosh I hope this wraps at some point, it is such a long line.`);
773        builder.pop();
774        builder.addText(` I'm done with the blue now. `);
775        builder.addText(`Now I hope we should stop before we get 8 lines tall. `);
776        const paragraph = builder.build();
777
778        paragraph.layout(wrapTo);
779
780        expect(paragraph.getAlphabeticBaseline()).toBeCloseTo(21.377, 3);
781        expect(paragraph.getHeight()).toEqual(227);
782        expect(paragraph.getIdeographicBaseline()).toBeCloseTo(27.236, 3);
783        expect(paragraph.getLongestLine()).toBeCloseTo(195.664, 3);
784        expect(paragraph.getMaxIntrinsicWidth()).toBeCloseTo(1167.140, 3);
785        expect(paragraph.getMaxWidth()).toEqual(200);
786        expect(paragraph.getMinIntrinsicWidth()).toBeCloseTo(172.360, 3);
787        // Check "VAVAVAVAVAVAVA"
788        expect(paragraph.getWordBoundary(8)).toEqual({
789            start: 0,
790            end: 14,
791        });
792        // Check "I"
793        expect(paragraph.getWordBoundary(25)).toEqual({
794            start: 25,
795            end: 26,
796        });
797
798        canvas.clear(CanvasKit.WHITE);
799        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, 230), paint);
800        canvas.drawParagraph(paragraph, 10, 10);
801
802        paint.delete();
803        fontMgr.delete();
804        paragraph.delete();
805        builder.delete();
806    });
807
808    gm('paragraph_text_styles_mixed_leading_distribution', (canvas) => {
809        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
810        expect(fontMgr.countFamilies()).toEqual(1);
811        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
812
813        const wrapTo = 200;
814
815        const paraStyle = new CanvasKit.ParagraphStyle({
816            textStyle: {
817                color: CanvasKit.BLACK,
818                backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
819                fontFamilies: ['Noto Serif'],
820                fontSize: 10,
821                heightMultiplier: 10,
822            },
823        });
824
825        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
826        builder.addText('Not half leading');
827
828        const halfLeadingText = new CanvasKit.TextStyle({
829            color: CanvasKit.Color(48, 37, 199),
830            backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
831            fontFamilies: ['Noto Serif'],
832            fontSize: 10,
833            heightMultiplier: 10,
834            halfLeading: true,
835        });
836        builder.pushStyle(halfLeadingText);
837        builder.addText('Half Leading Text');
838        const paragraph = builder.build();
839
840        paragraph.layout(wrapTo);
841        canvas.clear(CanvasKit.WHITE);
842        canvas.drawParagraph(paragraph, 0, 0);
843
844        fontMgr.delete();
845        paragraph.delete();
846        builder.delete();
847    });
848
849    gm('paragraph_mixed_text_height_behavior', (canvas) => {
850        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer);
851        expect(fontMgr.countFamilies()).toEqual(1);
852        expect(fontMgr.getFamilyName(0)).toEqual('Noto Serif');
853        canvas.clear(CanvasKit.WHITE);
854        const paint = new CanvasKit.Paint();
855        paint.setColor(CanvasKit.RED);
856        paint.setStyle(CanvasKit.PaintStyle.Stroke);
857
858        const wrapTo = 220;
859        const behaviors = ["All", "DisableFirstAscent", "DisableLastDescent", "DisableAll"];
860
861        for (let i = 0; i < behaviors.length; i++) {
862            const style = new CanvasKit.ParagraphStyle({
863                textStyle: {
864                    color: CanvasKit.BLACK,
865                    fontFamilies: ['Noto Serif'],
866                    fontSize: 20,
867                    heightMultiplier: 3, // make the difference more obvious
868                },
869                textHeightBehavior: CanvasKit.TextHeightBehavior[behaviors[i]],
870            });
871            const builder = CanvasKit.ParagraphBuilder.Make(style, fontMgr);
872            builder.addText('Text height behavior\nof '+behaviors[i]);
873            const paragraph = builder.build();
874            paragraph.layout(wrapTo);
875            canvas.drawParagraph(paragraph, 0, 150 * i);
876            canvas.drawRect(CanvasKit.LTRBRect(0, 150 * i, wrapTo, 150 * i + 120), paint);
877            paragraph.delete();
878            builder.delete();
879        }
880        paint.delete();
881        fontMgr.delete();
882    });
883
884    it('should not crash if we omit font family on pushed textStyle', () => {
885        const surface = CanvasKit.MakeCanvasSurface('test');
886        expect(surface).toBeTruthy('Could not make surface');
887
888        const canvas = surface.getCanvas();
889        const paint = new CanvasKit.Paint();
890
891        paint.setColor(CanvasKit.RED);
892        paint.setStyle(CanvasKit.PaintStyle.Stroke);
893
894        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
895
896        const wrapTo = 250;
897
898        const paraStyle = new CanvasKit.ParagraphStyle({
899            textStyle: {
900                fontFamilies: ['Noto Serif'],
901                fontSize: 20,
902            },
903            textDirection: CanvasKit.TextDirection.RTL,
904            disableHinting: true,
905        });
906
907        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
908        builder.addText('Default text\n');
909
910        const boldItalic = new CanvasKit.TextStyle({
911            fontStyle: {
912                weight: CanvasKit.FontWeight.Bold,
913                slant: CanvasKit.FontSlant.Italic,
914            }
915        });
916        builder.pushStyle(boldItalic);
917        builder.addText(`Bold, Italic\n`); // doesn't show up, but we don't crash
918        builder.pop();
919        builder.addText(`back to normal`);
920        const paragraph = builder.build();
921
922        paragraph.layout(wrapTo);
923
924        canvas.clear(CanvasKit.WHITE);
925        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
926        canvas.drawParagraph(paragraph, 10, 10);
927
928        surface.flush();
929
930        paragraph.delete();
931        builder.delete();
932        paint.delete();
933        fontMgr.delete();
934    });
935
936    it('should not crash if we omit font family on paragraph style', () => {
937        const surface = CanvasKit.MakeCanvasSurface('test');
938        expect(surface).toBeTruthy('Could not make surface');
939
940        const canvas = surface.getCanvas();
941        const paint = new CanvasKit.Paint();
942
943        paint.setColor(CanvasKit.RED);
944        paint.setStyle(CanvasKit.PaintStyle.Stroke);
945
946        const fontMgr = CanvasKit.FontMgr.FromData(notoSerifFontBuffer, notoSerifBoldItalicFontBuffer);
947
948        const wrapTo = 250;
949
950        const paraStyle = new CanvasKit.ParagraphStyle({
951            textStyle: {
952                fontSize: 20,
953            },
954            textDirection: CanvasKit.TextDirection.RTL,
955            disableHinting: true,
956        });
957
958        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
959        builder.addText('Default text\n');
960
961        const boldItalic = new CanvasKit.TextStyle({
962            fontStyle: {
963                weight: CanvasKit.FontWeight.Bold,
964                slant: CanvasKit.FontSlant.Italic,
965            }
966        });
967        builder.pushStyle(boldItalic);
968        builder.addText(`Bold, Italic\n`);
969        builder.pop();
970        builder.addText(`back to normal`);
971        const paragraph = builder.build();
972
973        paragraph.layout(wrapTo);
974
975        canvas.clear(CanvasKit.WHITE);
976        canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
977        canvas.drawParagraph(paragraph, 10, 10);
978
979        surface.flush();
980
981        paragraph.delete();
982        paint.delete();
983        fontMgr.delete();
984        builder.delete();
985    });
986});
987