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