text.yaml (103930B)
1 - name: 2d.text.font.parse.basic 2 code: | 3 ctx.font = '20px serif'; 4 @assert ctx.font === '20px serif'; 5 6 ctx.font = '20PX SERIF'; 7 @assert ctx.font === '20px serif'; @moz-todo 8 9 - name: 2d.text.font.parse.tiny 10 code: | 11 ctx.font = '1px sans-serif'; 12 @assert ctx.font === '1px sans-serif'; 13 14 - name: 2d.text.font.parse.complex 15 code: | 16 ctx.font = 'small-caps italic 400 12px/2 Unknown Font, sans-serif'; 17 @assert ['italic small-caps 12px "Unknown Font", sans-serif', 'italic small-caps 12px Unknown Font, sans-serif'].includes(ctx.font); 18 19 - name: 2d.text.font.parse.complex2 20 code: | 21 ctx.font = 'small-caps italic 400 12px/2 "Unknown Font #2", sans-serif'; 22 @assert ctx.font === 'italic small-caps 12px "Unknown Font #2", sans-serif'; 23 24 - name: 2d.text.font.parse.family 25 code: | 26 ctx.font = '20px cursive,fantasy,monospace,sans-serif,serif,UnquotedFont,"QuotedFont\\\\\\","'; 27 @assert ctx.font === '20px cursive, fantasy, monospace, sans-serif, serif, UnquotedFont, "QuotedFont\\\\\\","'; 28 29 # TODO: 30 # 2d.text.font.parse.size.absolute 31 # xx-small x-small small medium large x-large xx-large 32 # 2d.text.font.parse.size.relative 33 # smaller larger 34 # 2d.text.font.parse.size.length.relative 35 # em ex px 36 # 2d.text.font.parse.size.length.absolute 37 # in cm mm pt pc 38 39 - name: 2d.text.font.parse.size.percentage 40 canvas: 'style="font-size: 144px"' 41 canvas_types: ['HtmlCanvas'] 42 code: | 43 ctx.font = '50% serif'; 44 @assert ctx.font === '72px serif'; @moz-todo 45 canvas.setAttribute('style', 'font-size: 100px'); 46 @assert ctx.font === '72px serif'; @moz-todo 47 48 - name: 2d.text.font.parse.size.percentage.default 49 canvas_types: ['HtmlCanvas'] 50 code: | 51 var canvas2 = document.createElement('canvas'); 52 var ctx2 = canvas2.getContext('2d'); 53 ctx2.font = '1000% serif'; 54 @assert ctx2.font === '100px serif'; @moz-todo 55 56 - name: 2d.text.font.parse.system 57 desc: System fonts must be computed to explicit values 58 code: | 59 ctx.font = 'message-box'; 60 @assert ctx.font !== 'message-box'; 61 62 - name: 2d.text.font.parse.invalid 63 code: | 64 ctx.font = '20px serif'; 65 @assert ctx.font === '20px serif'; 66 67 ctx.font = '20px serif'; 68 ctx.font = ''; 69 @assert ctx.font === '20px serif'; 70 71 ctx.font = '20px serif'; 72 ctx.font = 'bogus'; 73 @assert ctx.font === '20px serif'; 74 75 ctx.font = '20px serif'; 76 ctx.font = 'inherit'; 77 @assert ctx.font === '20px serif'; 78 79 ctx.font = '20px serif'; 80 ctx.font = '10px {bogus}'; 81 @assert ctx.font === '20px serif'; 82 83 ctx.font = '20px serif'; 84 ctx.font = '10px initial'; 85 @assert ctx.font === '20px serif'; @moz-todo 86 87 ctx.font = '20px serif'; 88 ctx.font = '10px default'; 89 @assert ctx.font === '20px serif'; @moz-todo 90 91 ctx.font = '20px serif'; 92 ctx.font = '10px inherit'; 93 @assert ctx.font === '20px serif'; 94 95 ctx.font = '20px serif'; 96 ctx.font = '10px revert'; 97 @assert ctx.font === '20px serif'; 98 99 ctx.font = '20px serif'; 100 ctx.font = 'var(--x)'; 101 @assert ctx.font === '20px serif'; 102 103 ctx.font = '20px serif'; 104 ctx.font = 'var(--x, 10px serif)'; 105 @assert ctx.font === '20px serif'; 106 107 ctx.font = '20px serif'; 108 ctx.font = '1em serif; background: green; margin: 10px'; 109 @assert ctx.font === '20px serif'; 110 111 - name: 2d.text.font.default 112 code: | 113 @assert ctx.font === '10px sans-serif'; 114 115 - name: 2d.text.font.relative_size 116 canvas_types: ['HtmlCanvas'] 117 code: | 118 var canvas2 = document.createElement('canvas'); 119 var ctx2 = canvas2.getContext('2d'); 120 ctx2.font = '1em sans-serif'; 121 @assert ctx2.font === '10px sans-serif'; 122 123 - name: 2d.text.font.relative_size 124 canvas_types: ['OffscreenCanvas', 'Worker'] 125 code: | 126 ctx.font = '1em sans-serif'; 127 @assert ctx.font === '10px sans-serif'; 128 129 - name: 2d.text.font.weight 130 code: | 131 ctx.font = 'italic 400 12px serif'; 132 @assert ctx.font === 'italic 12px serif'; 133 134 ctx.font = 'italic 300 12px serif'; 135 @assert ctx.font === 'italic 300 12px serif'; 136 137 - name: 2d.text.lang.default 138 code: | 139 @assert ctx.lang === 'inherit'; 140 141 - name: 2d.text.lang.valid 142 code: | 143 ctx.lang = ''; 144 @assert ctx.lang === ''; 145 146 ctx.lang = 'inherit'; 147 @assert ctx.lang === 'inherit'; 148 149 ctx.lang = 'en-US'; 150 @assert ctx.lang === 'en-US'; 151 152 ctx.lang = 'not-a-real-lang'; 153 @assert ctx.lang === 'not-a-real-lang'; 154 155 - name: 2d.text.align.valid 156 code: | 157 ctx.textAlign = 'start'; 158 @assert ctx.textAlign === 'start'; 159 160 ctx.textAlign = 'end'; 161 @assert ctx.textAlign === 'end'; 162 163 ctx.textAlign = 'left'; 164 @assert ctx.textAlign === 'left'; 165 166 ctx.textAlign = 'right'; 167 @assert ctx.textAlign === 'right'; 168 169 ctx.textAlign = 'center'; 170 @assert ctx.textAlign === 'center'; 171 172 - name: 2d.text.align.invalid 173 code: | 174 ctx.textAlign = 'start'; 175 ctx.textAlign = 'bogus'; 176 @assert ctx.textAlign === 'start'; 177 178 ctx.textAlign = 'start'; 179 ctx.textAlign = 'END'; 180 @assert ctx.textAlign === 'start'; 181 182 ctx.textAlign = 'start'; 183 ctx.textAlign = 'end '; 184 @assert ctx.textAlign === 'start'; 185 186 ctx.textAlign = 'start'; 187 ctx.textAlign = 'end\0'; 188 @assert ctx.textAlign === 'start'; 189 190 - name: 2d.text.align.default 191 code: | 192 @assert ctx.textAlign === 'start'; 193 194 - name: 2d.text.baseline.valid 195 code: | 196 ctx.textBaseline = 'top'; 197 @assert ctx.textBaseline === 'top'; 198 199 ctx.textBaseline = 'hanging'; 200 @assert ctx.textBaseline === 'hanging'; 201 202 ctx.textBaseline = 'middle'; 203 @assert ctx.textBaseline === 'middle'; 204 205 ctx.textBaseline = 'alphabetic'; 206 @assert ctx.textBaseline === 'alphabetic'; 207 208 ctx.textBaseline = 'ideographic'; 209 @assert ctx.textBaseline === 'ideographic'; 210 211 ctx.textBaseline = 'bottom'; 212 @assert ctx.textBaseline === 'bottom'; 213 214 - name: 2d.text.baseline.invalid 215 code: | 216 ctx.textBaseline = 'top'; 217 ctx.textBaseline = 'bogus'; 218 @assert ctx.textBaseline === 'top'; 219 220 ctx.textBaseline = 'top'; 221 ctx.textBaseline = 'MIDDLE'; 222 @assert ctx.textBaseline === 'top'; 223 224 ctx.textBaseline = 'top'; 225 ctx.textBaseline = 'middle '; 226 @assert ctx.textBaseline === 'top'; 227 228 ctx.textBaseline = 'top'; 229 ctx.textBaseline = 'middle\0'; 230 @assert ctx.textBaseline === 'top'; 231 232 - name: 2d.text.baseline.default 233 code: | 234 @assert ctx.textBaseline === 'alphabetic'; 235 236 - name: 2d.text.direction.default 237 code: | 238 @assert ctx.direction === 'inherit'; 239 240 - name: 2d.text.direction.valid 241 code: | 242 ctx.direction = 'ltr'; 243 @assert ctx.direction === 'ltr'; 244 245 ctx.direction = 'rtl'; 246 @assert ctx.direction === 'rtl'; 247 248 ctx.direction = 'inherit'; 249 @assert ctx.direction === 'inherit'; 250 251 - name: 2d.text.direction.invalid 252 code: | 253 ctx.direction = 'ltr'; 254 ctx.direction = 'rtl '; 255 @assert ctx.direction === 'ltr'; 256 257 ctx.direction = 'rtl'; 258 ctx.direction = 'LTR'; 259 @assert ctx.direction === 'rtl'; 260 261 ctx.direction = 'ltr'; 262 ctx.direction = 'rtl\0'; 263 @assert ctx.direction === 'ltr'; 264 265 ctx.direction = 'ltr'; 266 ctx.direction = 'bogus'; 267 @assert ctx.direction === 'ltr'; 268 269 ctx.direction = 'ltr'; 270 ctx.direction = 'inheri'; 271 @assert ctx.direction === 'ltr'; 272 273 - name: 2d.text.draw.baseline.top 274 desc: textBaseline top is the top of the em square (not the bounding box) 275 test_type: promise 276 fonts: 277 - CanvasTest 278 code: | 279 {{ load_font }} 280 ctx.font = '50px CanvasTest'; 281 ctx.fillStyle = '#f00'; 282 ctx.fillRect(0, 0, 100, 50); 283 ctx.fillStyle = '#0f0'; 284 ctx.textBaseline = 'top'; 285 ctx.fillText('CC', 0, 0); 286 @assert pixel 5,5 ==~ 0,255,0,255; 287 @assert pixel 95,5 ==~ 0,255,0,255; 288 @assert pixel 25,25 ==~ 0,255,0,255; 289 @assert pixel 75,25 ==~ 0,255,0,255; 290 @assert pixel 5,45 ==~ 0,255,0,255; 291 @assert pixel 95,45 ==~ 0,255,0,255; 292 expected: green 293 variants: 294 - &load-font-variant-definition 295 HtmlCanvas: 296 append_variants_to_name: false 297 canvas_types: ['HtmlCanvas'] 298 load_font: |- 299 await document.fonts.ready; 300 OffscreenCanvas: 301 append_variants_to_name: false 302 canvas_types: ['OffscreenCanvas', 'Worker'] 303 load_font: |- 304 var f = new FontFace("{{ fonts[0] }}", "url('/fonts/{{ fonts[0] }}.ttf')"); 305 f.load(); 306 {% set root = 'self' if canvas_type == 'Worker' else 'document' %} 307 {{ root }}.fonts.add(f); 308 await {{ root }}.fonts.ready; 309 310 - name: 2d.text.draw.baseline.bottom 311 desc: textBaseline bottom is the bottom of the em square (not the bounding box) 312 test_type: promise 313 fonts: 314 - CanvasTest 315 code: | 316 {{ load_font }} 317 ctx.font = '50px CanvasTest'; 318 ctx.fillStyle = '#f00'; 319 ctx.fillRect(0, 0, 100, 50); 320 ctx.fillStyle = '#0f0'; 321 ctx.textBaseline = 'bottom'; 322 ctx.fillText('CC', 0, 50); 323 @assert pixel 5,5 ==~ 0,255,0,255; 324 @assert pixel 95,5 ==~ 0,255,0,255; 325 @assert pixel 25,25 ==~ 0,255,0,255; 326 @assert pixel 75,25 ==~ 0,255,0,255; 327 @assert pixel 5,45 ==~ 0,255,0,255; 328 @assert pixel 95,45 ==~ 0,255,0,255; 329 expected: green 330 variants: 331 - *load-font-variant-definition 332 333 - name: 2d.text.draw.baseline.middle 334 desc: textBaseline middle is the middle of the em square (not the bounding box) 335 test_type: promise 336 fonts: 337 - CanvasTest 338 code: | 339 {{ load_font }} 340 ctx.font = '50px CanvasTest'; 341 ctx.fillStyle = '#f00'; 342 ctx.fillRect(0, 0, 100, 50); 343 ctx.fillStyle = '#0f0'; 344 ctx.textBaseline = 'middle'; 345 ctx.fillText('CC', 0, 25); 346 @assert pixel 5,5 ==~ 0,255,0,255; 347 @assert pixel 95,5 ==~ 0,255,0,255; 348 @assert pixel 25,25 ==~ 0,255,0,255; 349 @assert pixel 75,25 ==~ 0,255,0,255; 350 @assert pixel 5,45 ==~ 0,255,0,255; 351 @assert pixel 95,45 ==~ 0,255,0,255; 352 expected: green 353 variants: 354 - *load-font-variant-definition 355 356 - name: 2d.text.draw.baseline.alphabetic 357 test_type: promise 358 fonts: 359 - CanvasTest 360 code: | 361 {{ load_font }} 362 ctx.font = '50px CanvasTest'; 363 ctx.fillStyle = '#f00'; 364 ctx.fillRect(0, 0, 100, 50); 365 ctx.fillStyle = '#0f0'; 366 ctx.textBaseline = 'alphabetic'; 367 ctx.fillText('CC', 0, 37.5); 368 @assert pixel 5,5 ==~ 0,255,0,255; 369 @assert pixel 95,5 ==~ 0,255,0,255; 370 @assert pixel 25,25 ==~ 0,255,0,255; 371 @assert pixel 75,25 ==~ 0,255,0,255; 372 @assert pixel 5,45 ==~ 0,255,0,255; 373 @assert pixel 95,45 ==~ 0,255,0,255; 374 expected: green 375 variants: 376 - *load-font-variant-definition 377 378 - name: 2d.text.draw.baseline.ideographic 379 test_type: promise 380 fonts: 381 - CanvasTest 382 code: | 383 {{ load_font }} 384 ctx.font = '50px CanvasTest'; 385 ctx.fillStyle = '#f00'; 386 ctx.fillRect(0, 0, 100, 50); 387 ctx.fillStyle = '#0f0'; 388 ctx.textBaseline = 'ideographic'; 389 ctx.fillText('CC', 0, 31.25); 390 @assert pixel 5,5 ==~ 0,255,0,255; 391 @assert pixel 95,5 ==~ 0,255,0,255; 392 @assert pixel 25,25 ==~ 0,255,0,255; 393 @assert pixel 75,25 ==~ 0,255,0,255; 394 @assert pixel 5,45 ==~ 0,255,0,255; @moz-todo 395 @assert pixel 95,45 ==~ 0,255,0,255; @moz-todo 396 expected: green 397 variants: 398 - *load-font-variant-definition 399 400 - name: 2d.text.draw.baseline.hanging 401 test_type: promise 402 fonts: 403 - CanvasTest 404 code: | 405 {{ load_font }} 406 ctx.font = '50px CanvasTest'; 407 ctx.fillStyle = '#f00'; 408 ctx.fillRect(0, 0, 100, 50); 409 ctx.fillStyle = '#0f0'; 410 ctx.textBaseline = 'hanging'; 411 ctx.fillText('CC', 0, 12.5); 412 @assert pixel 5,5 ==~ 0,255,0,255; @moz-todo 413 @assert pixel 95,5 ==~ 0,255,0,255; @moz-todo 414 @assert pixel 25,25 ==~ 0,255,0,255; 415 @assert pixel 75,25 ==~ 0,255,0,255; 416 @assert pixel 5,45 ==~ 0,255,0,255; 417 @assert pixel 95,45 ==~ 0,255,0,255; 418 expected: green 419 variants: 420 - *load-font-variant-definition 421 422 - name: 2d.text.draw.space.collapse.space 423 desc: Space characters are converted to U+0020, and are NOT collapsed 424 test_type: promise 425 fonts: 426 - CanvasTest 427 code: | 428 {{ load_font }} 429 ctx.font = '50px CanvasTest'; 430 ctx.fillStyle = '#f00'; 431 ctx.fillRect(0, 0, 100, 50); 432 ctx.fillStyle = '#0f0'; 433 ctx.fillText('E EE', 0, 37.5); 434 @assert pixel 25,25 ==~ 0,255,0,255; 435 @assert pixel 75,25 ==~ 255,0,0,255; 436 expected: green 437 variants: 438 - *load-font-variant-definition 439 440 - name: 2d.text.draw.space.collapse.other 441 desc: Space characters are converted to U+0020, and are NOT collapsed 442 test_type: promise 443 fonts: 444 - CanvasTest 445 code: | 446 {{ load_font }} 447 ctx.font = '50px CanvasTest'; 448 ctx.fillStyle = '#f00'; 449 ctx.fillRect(0, 0, 100, 50); 450 ctx.fillStyle = '#0f0'; 451 ctx.fillText('E \x09\x0a\x0c\x0d \x09\x0a\x0c\x0dEE', 0, 37.5); 452 @assert pixel 25,25 ==~ 0,255,0,255; 453 @assert pixel 75,25 ==~ 255,0,0,255; 454 expected: green 455 variants: 456 - *load-font-variant-definition 457 458 - name: 2d.text.draw.space.collapse.start 459 desc: Space characters at the start of a line are NOT collapsed 460 test_type: promise 461 fonts: 462 - CanvasTest 463 code: | 464 {{ load_font }} 465 ctx.font = '50px CanvasTest'; 466 ctx.fillStyle = '#f00'; 467 ctx.fillRect(0, 0, 100, 50); 468 ctx.fillStyle = '#0f0'; 469 ctx.fillText(' EE', 0, 37.5); 470 @assert pixel 25,25 ==~ 255,0,0,255; @moz-todo 471 @assert pixel 75,25 ==~ 0,255,0,255; 472 expected: green 473 variants: 474 - *load-font-variant-definition 475 476 - name: 2d.text.draw.space.collapse.end 477 desc: Space characters at the end of a line are NOT collapsed 478 test_type: promise 479 fonts: 480 - CanvasTest 481 code: | 482 {{ load_font }} 483 ctx.font = '50px CanvasTest'; 484 ctx.fillStyle = '#f00'; 485 ctx.fillRect(0, 0, 100, 50); 486 ctx.fillStyle = '#0f0'; 487 ctx.textAlign = 'right'; 488 ctx.fillText('EE ', 100, 37.5); 489 @assert pixel 25,25 ==~ 0,255,0,255; 490 @assert pixel 75,25 ==~ 255,0,0,255; 491 expected: green 492 variants: 493 - *load-font-variant-definition 494 495 - name: 2d.text.measure.width.space 496 desc: Space characters are converted to U+0020 and NOT collapsed 497 test_type: promise 498 fonts: 499 - CanvasTest 500 code: | 501 {{ load_font }} 502 ctx.font = '50px CanvasTest'; 503 @assert ctx.measureText('A B').width === 150; 504 @assert ctx.measureText('A B').width === 200; 505 @assert ctx.measureText('A \x09\x0a\x0c\x0d \x09\x0a\x0c\x0dB').width === 650; 506 @assert ctx.measureText('A \x0b B').width >= 200; 507 508 @assert ctx.measureText(' AB').width === 150; 509 @assert ctx.measureText('AB ').width === 150; 510 variants: 511 - *load-font-variant-definition 512 513 - name: 2d.text.measure.width.nullCharacter 514 desc: Null character does not take up space 515 test_type: promise 516 fonts: 517 - CanvasTest 518 code: | 519 {{ load_font }} 520 ctx.font = '50px CanvasTest'; 521 @assert ctx.measureText('\u0000').width === 0; 522 variants: 523 - *load-font-variant-definition 524 525 - name: 2d.text.drawing.style.measure.rtl.text 526 desc: Measurement should follow canvas direction instead text direction 527 code: | 528 metrics = ctx.measureText('اَلْعَرَبِيَّةُ'); 529 @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight; 530 531 metrics = ctx.measureText('hello'); 532 @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight; 533 534 - name: 2d.text.drawing.style.measure.textAlign 535 desc: Measurement should be related to textAlignment 536 code: | 537 ctx.textAlign = "right"; 538 metrics = ctx.measureText('hello'); 539 @assert metrics.actualBoundingBoxLeft > metrics.actualBoundingBoxRight; 540 541 ctx.textAlign = "left" 542 metrics = ctx.measureText('hello'); 543 @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight; 544 545 - name: 2d.text.drawing.style.measure.direction 546 desc: Measurement should follow text direction 547 code: | 548 ctx.direction = "ltr"; 549 metrics = ctx.measureText('hello'); 550 @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight; 551 552 ctx.direction = "rtl"; 553 metrics = ctx.measureText('hello'); 554 @assert metrics.actualBoundingBoxLeft > metrics.actualBoundingBoxRight; 555 556 - name: 2d.text.draw.fill.basic 557 desc: fillText draws filled text 558 manual: 559 code: | 560 ctx.fillStyle = '#000'; 561 ctx.fillRect(0, 0, 100, 50); 562 ctx.fillStyle = '#0f0'; 563 ctx.strokeStyle = '#f00'; 564 ctx.font = '35px Arial, sans-serif'; 565 ctx.fillText('PASS', 5, 35); 566 expected: &passfill | 567 size 100 50 568 cr.set_source_rgb(0, 0, 0) 569 cr.rectangle(0, 0, 100, 50) 570 cr.fill() 571 cr.set_source_rgb(0, 1, 0) 572 cr.select_font_face("Arial") 573 cr.set_font_size(35) 574 cr.translate(5, 35) 575 cr.text_path("PASS") 576 cr.fill() 577 578 - name: 2d.text.draw.fill.unaffected 579 desc: fillText does not start a new path or subpath 580 code: | 581 ctx.fillStyle = '#f00'; 582 ctx.fillRect(0, 0, 100, 50); 583 584 ctx.moveTo(0, 0); 585 ctx.lineTo(100, 0); 586 587 ctx.font = '35px Arial, sans-serif'; 588 ctx.fillText('FAIL', 5, 35); 589 590 ctx.lineTo(100, 50); 591 ctx.lineTo(0, 50); 592 ctx.fillStyle = '#0f0'; 593 ctx.fill(); 594 595 @assert pixel 50,25 == 0,255,0,255; 596 @assert pixel 5,45 == 0,255,0,255; 597 expected: green 598 599 - name: 2d.text.draw.fill.rtl 600 desc: fillText respects Right-To-Left Override characters 601 manual: 602 code: | 603 ctx.fillStyle = '#000'; 604 ctx.fillRect(0, 0, 100, 50); 605 ctx.fillStyle = '#0f0'; 606 ctx.strokeStyle = '#f00'; 607 ctx.font = '35px Arial, sans-serif'; 608 ctx.fillText('\u202eFAIL \xa0 \xa0 SSAP', 5, 35); 609 expected: *passfill 610 611 - name: 2d.text.draw.fill.maxWidth.large 612 desc: fillText handles maxWidth correctly 613 manual: 614 code: | 615 ctx.fillStyle = '#000'; 616 ctx.fillRect(0, 0, 100, 50); 617 ctx.fillStyle = '#0f0'; 618 ctx.font = '35px Arial, sans-serif'; 619 ctx.fillText('PASS', 5, 35, 200); 620 expected: *passfill 621 622 - name: 2d.text.draw.fill.maxWidth.small 623 desc: fillText handles maxWidth correctly 624 code: | 625 ctx.fillStyle = '#0f0'; 626 ctx.fillRect(0, 0, 100, 50); 627 ctx.fillStyle = '#f00'; 628 ctx.font = '35px Arial, sans-serif'; 629 ctx.fillText('fail fail fail fail fail', -100, 35, 90); 630 _assertGreen(ctx, 100, 50); 631 expected: green 632 633 - name: 2d.text.draw.fill.maxWidth.zero 634 desc: fillText handles maxWidth correctly 635 code: | 636 ctx.fillStyle = '#0f0'; 637 ctx.fillRect(0, 0, 100, 50); 638 ctx.fillStyle = '#f00'; 639 ctx.font = '35px Arial, sans-serif'; 640 ctx.fillText('fail fail fail fail fail', 5, 35, 0); 641 _assertGreen(ctx, 100, 50); 642 expected: green 643 644 - name: 2d.text.draw.fill.maxWidth.negative 645 desc: fillText handles maxWidth correctly 646 code: | 647 ctx.fillStyle = '#0f0'; 648 ctx.fillRect(0, 0, 100, 50); 649 ctx.fillStyle = '#f00'; 650 ctx.font = '35px Arial, sans-serif'; 651 ctx.fillText('fail fail fail fail fail', 5, 35, -1); 652 _assertGreen(ctx, 100, 50); 653 expected: green 654 655 - name: 2d.text.draw.fill.maxWidth.NaN 656 desc: fillText handles maxWidth correctly 657 code: | 658 ctx.fillStyle = '#0f0'; 659 ctx.fillRect(0, 0, 100, 50); 660 ctx.fillStyle = '#f00'; 661 ctx.font = '35px Arial, sans-serif'; 662 ctx.fillText('fail fail fail fail fail', 5, 35, NaN); 663 _assertGreen(ctx, 100, 50); 664 expected: green 665 666 - name: 2d.text.draw.stroke.basic 667 desc: strokeText draws stroked text 668 manual: 669 code: | 670 ctx.fillStyle = '#000'; 671 ctx.fillRect(0, 0, 100, 50); 672 ctx.strokeStyle = '#0f0'; 673 ctx.fillStyle = '#f00'; 674 ctx.lineWidth = 1; 675 ctx.font = '35px Arial, sans-serif'; 676 ctx.strokeText('PASS', 5, 35); 677 expected: | 678 size 100 50 679 cr.set_source_rgb(0, 0, 0) 680 cr.rectangle(0, 0, 100, 50) 681 cr.fill() 682 cr.set_source_rgb(0, 1, 0) 683 cr.select_font_face("Arial") 684 cr.set_font_size(35) 685 cr.set_line_width(1) 686 cr.translate(5, 35) 687 cr.text_path("PASS") 688 cr.stroke() 689 690 - name: 2d.text.draw.stroke.unaffected 691 desc: strokeText does not start a new path or subpath 692 code: | 693 ctx.fillStyle = '#f00'; 694 ctx.fillRect(0, 0, 100, 50); 695 696 ctx.moveTo(0, 0); 697 ctx.lineTo(100, 0); 698 699 ctx.font = '35px Arial, sans-serif'; 700 ctx.strokeStyle = '#f00'; 701 ctx.strokeText('FAIL', 5, 35); 702 703 ctx.lineTo(100, 50); 704 ctx.lineTo(0, 50); 705 ctx.fillStyle = '#0f0'; 706 ctx.fill(); 707 708 @assert pixel 50,25 == 0,255,0,255; 709 @assert pixel 5,45 == 0,255,0,255; 710 expected: green 711 712 - name: 2d.text.draw.kern.consistent 713 desc: Stroked and filled text should have exactly the same kerning so it overlaps 714 manual: 715 code: | 716 ctx.fillStyle = '#0f0'; 717 ctx.fillRect(0, 0, 100, 50); 718 ctx.fillStyle = '#f00'; 719 ctx.strokeStyle = '#0f0'; 720 ctx.lineWidth = 3; 721 ctx.font = '20px Arial, sans-serif'; 722 ctx.fillText('VAVAVAVAVAVAVA', -50, 25); 723 ctx.fillText('ToToToToToToTo', -50, 45); 724 ctx.strokeText('VAVAVAVAVAVAVA', -50, 25); 725 ctx.strokeText('ToToToToToToTo', -50, 45); 726 expected: green 727 728 - name: 2d.text.drawing.style.reset.fontKerning.none 729 desc: >- 730 crbug/338965374, fontKerning still works after setting font for a second 731 time. 732 code: | 733 ctx.font = '100px serif'; 734 ctx.fontKerning = "none"; 735 const width1 = ctx.measureText("AW").width; 736 ctx.font = '100px serif'; 737 @assert ctx.fontKerning === "none"; 738 const width2 = ctx.measureText("AW").width; 739 @assert width1 === width2; 740 741 - name: 2d.text.drawing.style.reset.fontKerning.none2 742 desc: FontKerning value still applies after font changes. 743 code: | 744 ctx.font = '10px serif'; 745 ctx.fontKerning = "none"; 746 ctx.font = '20px serif'; 747 ctx.fillText("TATATA", 20, 30); 748 reference: | 749 ctx.font = '20px serif'; 750 ctx.fontKerning = "none"; 751 ctx.fillText("TATATA", 20, 30); 752 753 # CanvasTest is: 754 # A = (0, 0) to (1em, 0.75em) (above baseline) 755 # B = (0, 0) to (1em, -0.25em) (below baseline) 756 # C = (0, -0.25em) to (1em, 0.75em) (the em square) plus some Xs above and below 757 # D = (0, -0.25em) to (1em, 0.75em) (the em square) plus some Xs left and right 758 # E = (0, -0.25em) to (1em, 0.75em) (the em square) 759 # space = empty, 1em wide 760 # 761 # At 50px, "E" will fill the canvas vertically 762 # At 67px, "A" will fill the canvas vertically 763 # 764 # Ideographic baseline is 0.125em above alphabetic 765 # Mathematical baseline is 0.375em above alphabetic 766 # Hanging baseline is 0.500em above alphabetic 767 768 - name: 2d.text.draw.fill.maxWidth.fontface 769 desc: fillText works on @font-face fonts 770 test_type: promise 771 fonts: 772 - CanvasTest 773 code: | 774 {{ load_font }} 775 ctx.font = '50px CanvasTest'; 776 ctx.fillStyle = '#0f0'; 777 ctx.fillRect(0, 0, 100, 50); 778 ctx.fillStyle = '#f00'; 779 ctx.fillText('EEEE', -50, 37.5, 40); 780 @assert pixel 5,5 ==~ 0,255,0,255; 781 @assert pixel 95,5 ==~ 0,255,0,255; 782 @assert pixel 25,25 ==~ 0,255,0,255; 783 @assert pixel 75,25 ==~ 0,255,0,255; 784 expected: green 785 variants: 786 - *load-font-variant-definition 787 788 - name: 2d.text.draw.fill.maxWidth.bound 789 desc: fillText handles maxWidth based on line size, not bounding box size 790 test_type: promise 791 fonts: 792 - CanvasTest 793 code: | 794 {{ load_font }} 795 ctx.font = '50px CanvasTest'; 796 ctx.fillStyle = '#f00'; 797 ctx.fillRect(0, 0, 100, 50); 798 ctx.fillStyle = '#0f0'; 799 ctx.fillText('DD', 0, 37.5, 100); 800 @assert pixel 5,5 ==~ 0,255,0,255; 801 @assert pixel 95,5 ==~ 0,255,0,255; 802 @assert pixel 25,25 ==~ 0,255,0,255; 803 @assert pixel 75,25 ==~ 0,255,0,255; 804 expected: green 805 variants: 806 - *load-font-variant-definition 807 808 - name: 2d.text.draw.fontface 809 fonts: 810 - CanvasTest 811 test_type: promise 812 code: | 813 {{ load_font }} 814 ctx.font = '67px CanvasTest'; 815 ctx.fillStyle = '#f00'; 816 ctx.fillRect(0, 0, 100, 50); 817 ctx.fillStyle = '#0f0'; 818 ctx.fillText('AA', 0, 50); 819 @assert pixel 5,5 ==~ 0,255,0,255; 820 @assert pixel 95,5 ==~ 0,255,0,255; 821 @assert pixel 25,25 ==~ 0,255,0,255; 822 @assert pixel 75,25 ==~ 0,255,0,255; 823 expected: green 824 variants: 825 - *load-font-variant-definition 826 827 - name: 2d.text.draw.fontface.repeat 828 desc: Draw with the font immediately, then wait a bit until and draw again. (This 829 crashes some version of WebKit.) 830 test_type: promise 831 fonts: 832 - CanvasTest 833 font_unused_in_dom: true 834 code: | 835 {{ load_font }} 836 ctx.fillStyle = '#f00'; 837 ctx.fillRect(0, 0, 100, 50); 838 ctx.font = '67px CanvasTest'; 839 ctx.fillStyle = '#0f0'; 840 ctx.fillText('AA', 0, 50); 841 842 await new Promise(resolve => t.step_timeout(resolve, 500)); 843 ctx.fillText('AA', 0, 50); 844 _assertPixelApprox(canvas, 5,5, 0,255,0,255, 2); 845 _assertPixelApprox(canvas, 95,5, 0,255,0,255, 2); 846 _assertPixelApprox(canvas, 25,25, 0,255,0,255, 2); 847 _assertPixelApprox(canvas, 75,25, 0,255,0,255, 2); 848 expected: green 849 variants: 850 - *load-font-variant-definition 851 852 - name: 2d.text.draw.fontface.notinpage 853 desc: '@font-face fonts should work even if they are not used in the page' 854 test_type: promise 855 fonts: 856 - CanvasTest 857 font_unused_in_dom: true 858 code: | 859 {{ load_font }} 860 ctx.font = '67px CanvasTest'; 861 ctx.fillStyle = '#f00'; 862 ctx.fillRect(0, 0, 100, 50); 863 ctx.fillStyle = '#0f0'; 864 ctx.fillText('AA', 0, 50); 865 @assert pixel 5,5 ==~ 0,255,0,255; @moz-todo 866 @assert pixel 95,5 ==~ 0,255,0,255; @moz-todo 867 @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo 868 @assert pixel 75,25 ==~ 0,255,0,255; @moz-todo 869 expected: green 870 variants: 871 - *load-font-variant-definition 872 873 - name: 2d.text.draw.align.left 874 desc: textAlign left is the left of the first em square (not the bounding box) 875 test_type: promise 876 fonts: 877 - CanvasTest 878 code: | 879 {{ load_font }} 880 ctx.font = '50px CanvasTest'; 881 ctx.fillStyle = '#f00'; 882 ctx.fillRect(0, 0, 100, 50); 883 ctx.fillStyle = '#0f0'; 884 ctx.textAlign = 'left'; 885 ctx.fillText('DD', 0, 37.5); 886 @assert pixel 5,5 ==~ 0,255,0,255; 887 @assert pixel 95,5 ==~ 0,255,0,255; 888 @assert pixel 25,25 ==~ 0,255,0,255; 889 @assert pixel 75,25 ==~ 0,255,0,255; 890 @assert pixel 5,45 ==~ 0,255,0,255; 891 @assert pixel 95,45 ==~ 0,255,0,255; 892 expected: green 893 variants: 894 - *load-font-variant-definition 895 896 - name: 2d.text.draw.align.right 897 desc: textAlign right is the right of the last em square (not the bounding box) 898 test_type: promise 899 fonts: 900 - CanvasTest 901 code: | 902 {{ load_font }} 903 ctx.font = '50px CanvasTest'; 904 ctx.fillStyle = '#f00'; 905 ctx.fillRect(0, 0, 100, 50); 906 ctx.fillStyle = '#0f0'; 907 ctx.textAlign = 'right'; 908 ctx.fillText('DD', 100, 37.5); 909 @assert pixel 5,5 ==~ 0,255,0,255; 910 @assert pixel 95,5 ==~ 0,255,0,255; 911 @assert pixel 25,25 ==~ 0,255,0,255; 912 @assert pixel 75,25 ==~ 0,255,0,255; 913 @assert pixel 5,45 ==~ 0,255,0,255; 914 @assert pixel 95,45 ==~ 0,255,0,255; 915 expected: green 916 variants: 917 - *load-font-variant-definition 918 919 - name: 2d.text.draw.align.start.ltr 920 desc: textAlign start with ltr is the left edge 921 canvas: dir="ltr" 922 test_type: promise 923 fonts: 924 - CanvasTest 925 code: | 926 {{ load_font }} 927 ctx.font = '50px CanvasTest'; 928 {% if canvas_type != 'HtmlCanvas' %} 929 ctx.direction = 'ltr'; 930 {% endif %} 931 ctx.fillStyle = '#f00'; 932 ctx.fillRect(0, 0, 100, 50); 933 ctx.fillStyle = '#0f0'; 934 ctx.textAlign = 'start'; 935 ctx.fillText('DD', 0, 37.5); 936 @assert pixel 5,5 ==~ 0,255,0,255; 937 @assert pixel 95,5 ==~ 0,255,0,255; 938 @assert pixel 25,25 ==~ 0,255,0,255; 939 @assert pixel 75,25 ==~ 0,255,0,255; 940 @assert pixel 5,45 ==~ 0,255,0,255; 941 @assert pixel 95,45 ==~ 0,255,0,255; 942 expected: green 943 variants: 944 - *load-font-variant-definition 945 946 - name: 2d.text.draw.align.start.rtl 947 desc: textAlign start with rtl is the right edge 948 canvas: dir="rtl" 949 test_type: promise 950 fonts: 951 - CanvasTest 952 code: | 953 {{ load_font }} 954 ctx.font = '50px CanvasTest'; 955 {% if canvas_type != 'HtmlCanvas' %} 956 ctx.direction = 'rtl'; 957 {% endif %} 958 ctx.fillStyle = '#f00'; 959 ctx.fillRect(0, 0, 100, 50); 960 ctx.fillStyle = '#0f0'; 961 ctx.textAlign = 'start'; 962 ctx.fillText('DD', 100, 37.5); 963 @assert pixel 5,5 ==~ 0,255,0,255; 964 @assert pixel 95,5 ==~ 0,255,0,255; 965 @assert pixel 25,25 ==~ 0,255,0,255; 966 @assert pixel 75,25 ==~ 0,255,0,255; 967 @assert pixel 5,45 ==~ 0,255,0,255; 968 @assert pixel 95,45 ==~ 0,255,0,255; 969 expected: green 970 variants: 971 - *load-font-variant-definition 972 973 - name: 2d.text.draw.align.end.ltr 974 desc: textAlign end with ltr is the right edge 975 test_type: promise 976 canvas: dir="ltr" 977 fonts: 978 - CanvasTest 979 code: | 980 {{ load_font }} 981 ctx.font = '50px CanvasTest'; 982 {% if canvas_type != 'HtmlCanvas' %} 983 ctx.direction = 'ltr'; 984 {% endif %} 985 ctx.fillStyle = '#f00'; 986 ctx.fillRect(0, 0, 100, 50); 987 ctx.fillStyle = '#0f0'; 988 ctx.textAlign = 'end'; 989 ctx.fillText('DD', 100, 37.5); 990 @assert pixel 5,5 ==~ 0,255,0,255; 991 @assert pixel 95,5 ==~ 0,255,0,255; 992 @assert pixel 25,25 ==~ 0,255,0,255; 993 @assert pixel 75,25 ==~ 0,255,0,255; 994 @assert pixel 5,45 ==~ 0,255,0,255; 995 @assert pixel 95,45 ==~ 0,255,0,255; 996 expected: green 997 variants: 998 - *load-font-variant-definition 999 1000 - name: 2d.text.draw.align.end.rtl 1001 desc: textAlign end with rtl is the left edge 1002 canvas: dir="rtl" 1003 test_type: promise 1004 fonts: 1005 - CanvasTest 1006 code: | 1007 {{ load_font }} 1008 ctx.font = '50px CanvasTest'; 1009 {% if canvas_type != 'HtmlCanvas' %} 1010 ctx.direction = 'rtl'; 1011 {% endif %} 1012 ctx.fillStyle = '#f00'; 1013 ctx.fillRect(0, 0, 100, 50); 1014 ctx.fillStyle = '#0f0'; 1015 ctx.textAlign = 'end'; 1016 ctx.fillText('DD', 0, 37.5); 1017 @assert pixel 5,5 ==~ 0,255,0,255; 1018 @assert pixel 95,5 ==~ 0,255,0,255; 1019 @assert pixel 25,25 ==~ 0,255,0,255; 1020 @assert pixel 75,25 ==~ 0,255,0,255; 1021 @assert pixel 5,45 ==~ 0,255,0,255; 1022 @assert pixel 95,45 ==~ 0,255,0,255; 1023 expected: green 1024 variants: 1025 - *load-font-variant-definition 1026 1027 - name: 2d.text.draw.align.center 1028 desc: textAlign center is the center of the em squares (not the bounding box) 1029 test_type: promise 1030 fonts: 1031 - CanvasTest 1032 code: | 1033 {{ load_font }} 1034 ctx.font = '50px CanvasTest'; 1035 ctx.fillStyle = '#f00'; 1036 ctx.fillRect(0, 0, 100, 50); 1037 ctx.fillStyle = '#0f0'; 1038 ctx.textAlign = 'center'; 1039 ctx.fillText('DD', 50, 37.5); 1040 @assert pixel 5,5 ==~ 0,255,0,255; 1041 @assert pixel 95,5 ==~ 0,255,0,255; 1042 @assert pixel 25,25 ==~ 0,255,0,255; 1043 @assert pixel 75,25 ==~ 0,255,0,255; 1044 @assert pixel 5,45 ==~ 0,255,0,255; 1045 @assert pixel 95,45 ==~ 0,255,0,255; 1046 expected: green 1047 variants: 1048 - *load-font-variant-definition 1049 1050 1051 - name: 2d.text.draw.space.basic 1052 desc: U+0020 is rendered the correct size (1em wide) 1053 test_type: promise 1054 fonts: 1055 - CanvasTest 1056 code: | 1057 {{ load_font }} 1058 ctx.font = '50px CanvasTest'; 1059 ctx.fillStyle = '#f00'; 1060 ctx.fillRect(0, 0, 100, 50); 1061 ctx.fillStyle = '#0f0'; 1062 ctx.fillText('E EE', -100, 37.5); 1063 @assert pixel 25,25 ==~ 0,255,0,255; 1064 @assert pixel 75,25 ==~ 0,255,0,255; 1065 expected: green 1066 variants: 1067 - *load-font-variant-definition 1068 1069 - name: 2d.text.draw.space.collapse.nonspace 1070 desc: Non-space characters are not converted to U+0020 and collapsed 1071 test_type: promise 1072 fonts: 1073 - CanvasTest 1074 code: | 1075 {{ load_font }} 1076 ctx.font = '50px CanvasTest'; 1077 ctx.fillStyle = '#f00'; 1078 ctx.fillRect(0, 0, 100, 50); 1079 ctx.fillStyle = '#0f0'; 1080 ctx.fillText('E\x0b EE', -150, 37.5); 1081 @assert pixel 25,25 ==~ 0,255,0,255; 1082 @assert pixel 75,25 ==~ 0,255,0,255; 1083 expected: green 1084 variants: 1085 - *load-font-variant-definition 1086 1087 - name: 2d.text.measure.width.basic 1088 desc: The width of character is same as font used 1089 test_type: promise 1090 fonts: 1091 - CanvasTest 1092 code: | 1093 {{ load_font }} 1094 ctx.font = '50px CanvasTest'; 1095 @assert ctx.measureText('A').width === 50; 1096 @assert ctx.measureText('AA').width === 100; 1097 @assert ctx.measureText('ABCD').width === 200; 1098 1099 ctx.font = '100px CanvasTest'; 1100 @assert ctx.measureText('A').width === 100; 1101 variants: 1102 - *load-font-variant-definition 1103 1104 - name: 2d.text.measure.width.empty 1105 desc: The empty string has zero width 1106 test_type: promise 1107 fonts: 1108 - CanvasTest 1109 code: | 1110 {{ load_font }} 1111 ctx.font = '50px CanvasTest'; 1112 @assert ctx.measureText("").width === 0; 1113 variants: 1114 - *load-font-variant-definition 1115 1116 - name: 2d.text.measure.actualBoundingBox 1117 desc: Testing actualBoundingBox 1118 test_type: promise 1119 fonts: 1120 - CanvasTest 1121 code: | 1122 {{ load_font }} 1123 ctx.font = '50px CanvasTest'; 1124 ctx.direction = 'ltr'; 1125 ctx.align = 'left' 1126 ctx.baseline = 'alphabetic' 1127 // Different platforms may render text slightly different. 1128 // Values that are nominally expected to be zero might actually vary by a 1129 // pixel or so if the UA accounts for antialiasing at glyph edges, so we 1130 // allow a slight deviation. 1131 @assert Math.abs(ctx.measureText('A').actualBoundingBoxLeft) <= 1; 1132 @assert ctx.measureText('A').actualBoundingBoxRight >= 50; 1133 @assert ctx.measureText('A').actualBoundingBoxAscent >= 35; 1134 @assert Math.abs(ctx.measureText('A').actualBoundingBoxDescent) <= 1; 1135 1136 @assert ctx.measureText('D').actualBoundingBoxLeft >= 48; 1137 @assert ctx.measureText('D').actualBoundingBoxLeft <= 52; 1138 @assert ctx.measureText('D').actualBoundingBoxRight >= 75; 1139 @assert ctx.measureText('D').actualBoundingBoxRight <= 80; 1140 @assert ctx.measureText('D').actualBoundingBoxAscent >= 35; 1141 @assert ctx.measureText('D').actualBoundingBoxAscent <= 40; 1142 @assert ctx.measureText('D').actualBoundingBoxDescent >= 12; 1143 @assert ctx.measureText('D').actualBoundingBoxDescent <= 15; 1144 1145 @assert Math.abs(ctx.measureText('ABCD').actualBoundingBoxLeft) <= 1; 1146 @assert ctx.measureText('ABCD').actualBoundingBoxRight >= 200; 1147 @assert ctx.measureText('ABCD').actualBoundingBoxAscent >= 85; 1148 @assert ctx.measureText('ABCD').actualBoundingBoxDescent >= 37; 1149 variants: 1150 - *load-font-variant-definition 1151 1152 - name: 2d.text.measure.actualBoundingBox.whitespace 1153 desc: Testing actualBoundingBox with leading/trailing whitespace 1154 test_type: promise 1155 fonts: 1156 - CanvasTest 1157 code: | 1158 {{ load_font }} 1159 ctx.font = '50px CanvasTest'; 1160 ctx.direction = 'ltr'; 1161 ctx.align = 'left' 1162 ctx.baseline = 'alphabetic' 1163 // Different platforms may render text slightly different. 1164 // Values that are nominally expected to be zero might actually vary by a 1165 // pixel or so if the UA accounts for antialiasing at glyph edges, so we 1166 // allow a slight deviation. 1167 var whitespaces = [0x9, 0xa, 0xc, 0xd, 0x20, 0x3000]; 1168 for (var codepoint of whitespaces) { 1169 let whitespace = String.fromCharCode(codepoint); 1170 1171 @assert Math.abs(ctx.measureText('A' + whitespace).actualBoundingBoxLeft) <= 1; 1172 @assert ctx.measureText('A' + whitespace).actualBoundingBoxRight >= 50; 1173 1174 @assert Math.abs(ctx.measureText(whitespace + 'A').actualBoundingBoxLeft) >= 49; 1175 @assert ctx.measureText(whitespace + 'A').actualBoundingBoxRight <= 101; 1176 } 1177 variants: 1178 - *load-font-variant-definition 1179 1180 - name: 2d.text.measure.fontBoundingBox 1181 desc: Testing fontBoundingBox measurements 1182 test_type: promise 1183 fonts: 1184 - CanvasTest 1185 code: | 1186 {{ load_font }} 1187 ctx.font = '40px CanvasTest'; 1188 ctx.direction = 'ltr'; 1189 ctx.align = 'left' 1190 @assert ctx.measureText('A').fontBoundingBoxAscent === 30; 1191 @assert ctx.measureText('A').fontBoundingBoxDescent === 10; 1192 1193 @assert ctx.measureText('ABCD').fontBoundingBoxAscent === 30; 1194 @assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10; 1195 variants: 1196 - *load-font-variant-definition 1197 1198 - name: 2d.text.measure.fontBoundingBox.ahem 1199 desc: Testing fontBoundingBox for font ahem 1200 test_type: promise 1201 fonts: 1202 - Ahem 1203 code: | 1204 {{ load_font }} 1205 ctx.font = '50px Ahem'; 1206 ctx.direction = 'ltr'; 1207 ctx.align = 'left' 1208 @assert ctx.measureText('A').fontBoundingBoxAscent === 40; 1209 @assert ctx.measureText('A').fontBoundingBoxDescent === 10; 1210 @assert ctx.measureText('ABCD').fontBoundingBoxAscent === 40; 1211 @assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10; 1212 variants: 1213 - *load-font-variant-definition 1214 1215 - name: 2d.text.measure.fontBoundingBox-reduced-ascent 1216 desc: Testing fontBoundingBox for OffscreenCanvas with reduced ascent metric 1217 test_type: promise 1218 fonts: 1219 - CanvasTest-ascent256 1220 code: | 1221 {{ load_font }} 1222 ctx.font = '40px CanvasTest-ascent256'; 1223 ctx.direction = 'ltr'; 1224 ctx.align = 'left' 1225 @assert ctx.measureText('A').fontBoundingBoxAscent === 10; 1226 @assert ctx.measureText('A').fontBoundingBoxDescent === 10; 1227 1228 @assert ctx.measureText('ABCD').fontBoundingBoxAscent === 10; 1229 @assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10; 1230 variants: 1231 - *load-font-variant-definition 1232 1233 - name: 2d.text.measure.fontBoundingBox-zero-descent 1234 desc: Testing fontBoundingBox for OffscreenCanvas with zero descent metric 1235 test_type: promise 1236 fonts: 1237 - CanvasTest-descent0 1238 code: | 1239 {{ load_font }} 1240 ctx.font = '40px CanvasTest-descent0'; 1241 ctx.direction = 'ltr'; 1242 ctx.align = 'left' 1243 @assert ctx.measureText('A').fontBoundingBoxAscent === 30; 1244 @assert ctx.measureText('A').fontBoundingBoxDescent === 0; 1245 1246 @assert ctx.measureText('ABCD').fontBoundingBoxAscent === 30; 1247 @assert ctx.measureText('ABCD').fontBoundingBoxDescent === 0; 1248 variants: 1249 - *load-font-variant-definition 1250 1251 - name: 2d.text.measure.emHeights 1252 desc: Testing emHeights 1253 test_type: promise 1254 fonts: 1255 - CanvasTest 1256 code: | 1257 {{ load_font }} 1258 ctx.font = '40px CanvasTest'; 1259 ctx.direction = 'ltr'; 1260 ctx.align = 'left' 1261 @assert ctx.measureText('A').emHeightAscent === 30; 1262 @assert ctx.measureText('A').emHeightDescent === 10; 1263 @assert ctx.measureText('A').emHeightDescent + ctx.measureText('A').emHeightAscent === 40; 1264 1265 @assert ctx.measureText('ABCD').emHeightAscent === 30; 1266 @assert ctx.measureText('ABCD').emHeightDescent === 10; 1267 @assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40; 1268 variants: 1269 - *load-font-variant-definition 1270 1271 - name: 2d.text.measure.emHeights-low-ascent 1272 desc: Testing emHeights with reduced ascent metric 1273 test_type: promise 1274 fonts: 1275 - CanvasTest-ascent256 1276 code: | 1277 {{ load_font }} 1278 ctx.font = '40px CanvasTest-ascent256'; 1279 ctx.direction = 'ltr'; 1280 ctx.align = 'left' 1281 @assert ctx.measureText('A').emHeightAscent === 20; 1282 @assert ctx.measureText('A').emHeightDescent === 20; 1283 @assert ctx.measureText('A').emHeightDescent + ctx.measureText('A').emHeightAscent === 40; 1284 1285 @assert ctx.measureText('ABCD').emHeightAscent === 20; 1286 @assert ctx.measureText('ABCD').emHeightDescent === 20; 1287 @assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40; 1288 variants: 1289 - *load-font-variant-definition 1290 1291 - name: 2d.text.measure.emHeights-zero-descent 1292 desc: Testing emHeights with zero descent metric 1293 test_type: promise 1294 fonts: 1295 - CanvasTest-descent0 1296 code: | 1297 {{ load_font }} 1298 ctx.font = '40px CanvasTest-descent0'; 1299 ctx.direction = 'ltr'; 1300 ctx.align = 'left' 1301 @assert ctx.measureText('A').emHeightAscent === 40; 1302 @assert ctx.measureText('A').emHeightDescent === 0; 1303 @assert ctx.measureText('A').emHeightDescent + ctx.measureText('A').emHeightAscent === 40; 1304 1305 @assert ctx.measureText('ABCD').emHeightAscent === 40; 1306 @assert ctx.measureText('ABCD').emHeightDescent === 0; 1307 @assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40; 1308 variants: 1309 - *load-font-variant-definition 1310 1311 - name: 2d.text.measure.baselines 1312 desc: Testing baselines 1313 test_type: promise 1314 fonts: 1315 - CanvasTest 1316 code: | 1317 {{ load_font }} 1318 ctx.font = '50px CanvasTest'; 1319 ctx.direction = 'ltr'; 1320 ctx.align = 'left' 1321 @assert Math.abs(ctx.measureText('A').alphabeticBaseline) === 0; 1322 @assert ctx.measureText('A').ideographicBaseline === 6.25; 1323 @assert ctx.measureText('A').hangingBaseline === 25; 1324 1325 @assert Math.abs(ctx.measureText('ABCD').alphabeticBaseline) === 0; 1326 @assert ctx.measureText('ABCD').ideographicBaseline === 6.25; 1327 @assert ctx.measureText('ABCD').hangingBaseline === 25; 1328 variants: 1329 - *load-font-variant-definition 1330 1331 - name: 2d.text.measure.selection-rects.tentative 1332 desc: >- 1333 Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with 1334 direction {{ text_direction }}, text align {{ text_align }}, 1335 {{ letter_spacing }} letter spacing, and {{ variant_names[3] }}. 1336 canvas_types: ['HtmlCanvas', 'OffscreenCanvas'] 1337 code: | 1338 function placeAndSelectTextInDOM(text, from, to) { 1339 const el = document.createElement("div"); 1340 el.innerHTML = text; 1341 el.style.font = '50px sans-serif'; 1342 el.style.direction = '{{ text_direction }}'; 1343 el.style.textAlign = '{{ text_align }}'; 1344 el.style.letterSpacing = '{{ letter_spacing }}'; 1345 document.body.appendChild(el); 1346 1347 let range = document.createRange(); 1348 1349 range.setStart(el.childNodes[0], 0); 1350 range.setEnd(el.childNodes[0], text.length); 1351 const parent = range.getClientRects()[0]; 1352 let width = 0; 1353 for (const rect of range.getClientRects()) { 1354 width += rect.width; 1355 } 1356 1357 range.setStart(el.childNodes[0], from); 1358 range.setEnd(el.childNodes[0], to); 1359 1360 let sel = window.getSelection(); 1361 sel.removeAllRanges(); 1362 sel.addRange(range); 1363 1364 let sel_rects = sel.getRangeAt(0).getClientRects(); 1365 1366 document.body.removeChild(el); 1367 1368 // Offset to the alignment point determined by textAlign. 1369 let text_align_dx; 1370 switch (el.style.textAlign) { 1371 case 'right': 1372 text_align_dx = width; 1373 break; 1374 case 'center': 1375 text_align_dx = width / 2; 1376 break; 1377 default: 1378 text_align_dx = 0; 1379 } 1380 1381 for(let i = 0 ; i < sel_rects.length ; ++i) { 1382 sel_rects[i].x -= parent.x + text_align_dx; 1383 sel_rects[i].y -= parent.y; 1384 } 1385 1386 return sel_rects; 1387 } 1388 1389 function checkRectListsMatch(list_a, list_b) { 1390 @assert list_a.length === list_b.length; 1391 for(let i = 0 ; i < list_a.length ; ++i) { 1392 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1393 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1394 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1395 // Y-position not tested here as getting the baseline for text in the 1396 // DOM is not straightforward. 1397 } 1398 } 1399 {% if use_directional_override %} 1400 1401 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 1402 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 1403 const LTR_OVERRIDE = '\u202D'; 1404 const RTL_OVERRIDE = '\u202E'; 1405 const OVERRIDE_END = '\u202C'; 1406 if (direction_is_ltr) { 1407 return LTR_OVERRIDE + text + OVERRIDE_END; 1408 } 1409 return RTL_OVERRIDE + text + OVERRIDE_END; 1410 } 1411 {% endif %} 1412 1413 ctx.font = '50px sans-serif'; 1414 ctx.direction = '{{ text_direction }}'; 1415 ctx.textAlign = '{{ text_align }}'; 1416 ctx.letterSpacing = '{{ letter_spacing }}'; 1417 1418 const kTexts = [ 1419 'UNAVAILABLE', 1420 '🏁🎶🏁', 1421 ')(あ)(', 1422 '-abcd_', 1423 'איפה הספרייה?', 1424 'bidiמתמטיקה' 1425 ] 1426 1427 for (text of kTexts) { 1428 {% if use_directional_override %} 1429 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 1430 {% endif %} 1431 const tm = ctx.measureText(text); 1432 // First character. 1433 checkRectListsMatch( 1434 tm.getSelectionRects(0, 1), 1435 placeAndSelectTextInDOM(text, 0, 1) 1436 ); 1437 // Last character. 1438 checkRectListsMatch( 1439 tm.getSelectionRects(text.length - 1, text.length), 1440 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1441 ); 1442 // Whole string. 1443 checkRectListsMatch( 1444 tm.getSelectionRects(0, text.length), 1445 placeAndSelectTextInDOM(text, 0, text.length) 1446 ); 1447 // Intermediate string. 1448 checkRectListsMatch( 1449 tm.getSelectionRects(1, text.length - 1), 1450 placeAndSelectTextInDOM(text, 1, text.length - 1) 1451 ); 1452 // Invalid start > end string. Creates 0 width rectangle. 1453 checkRectListsMatch( 1454 tm.getSelectionRects(3, 2), 1455 placeAndSelectTextInDOM(text, 3, 2) 1456 ); 1457 checkRectListsMatch( 1458 tm.getSelectionRects(1, 0), 1459 placeAndSelectTextInDOM(text, 1, 0) 1460 ); 1461 } 1462 variants_layout: [single_file, single_file, single_file, single_file] 1463 variants: 1464 - direction-ltr: 1465 text_direction: ltr 1466 direction-rtl: 1467 text_direction: rtl 1468 - align-left: 1469 text_align: left 1470 align-center: 1471 text_align: center 1472 align-right: 1473 text_align: right 1474 - no-spacing: 1475 letter_spacing: 0px 1476 spacing: 1477 letter_spacing: 10px 1478 - no-directional-override: 1479 use_directional_override: false 1480 directional-override: 1481 use_directional_override: true 1482 1483 - name: 2d.text.measure.selection-rects-baselines.tentative 1484 desc: >- 1485 Check that TextMetrics::getSelectionRects() works correctly with 1486 textBaseline. 1487 code: | 1488 ctx.font = '50px sans-serif'; 1489 1490 const kBaselines = [ 1491 "top", 1492 "hanging", 1493 "middle", 1494 "alphabetic", 1495 "ideographic", 1496 "bottom", 1497 ]; 1498 1499 const kTexts = [ 1500 'UNAVAILABLE', 1501 '🏁🎶🏁', 1502 ')(あ)(', 1503 '-abcd_', 1504 'איפה הספרייה?', 1505 'bidiמתמטיקה' 1506 ] 1507 1508 for (const text of kTexts) { 1509 for (const baseline of kBaselines) { 1510 const tm = ctx.measureText(text); 1511 // First character. 1512 for (const r of tm.getSelectionRects(0, 1)) { 1513 assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0); 1514 assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0); 1515 } 1516 // Last character. 1517 for (const r of tm.getSelectionRects(text.length - 1, text.length)) { 1518 assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0); 1519 assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0); 1520 } 1521 // Whole string. 1522 for (const r of tm.getSelectionRects(0, text.length)) { 1523 assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0); 1524 assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0); 1525 } 1526 // Intermediate string. 1527 for (const r of tm.getSelectionRects(1, text.length - 1)) { 1528 assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0); 1529 assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0); 1530 } 1531 // Invalid start > end string. Creates 0 width rectangle. 1532 for (const r of tm.getSelectionRects(3, 2)) { 1533 assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0); 1534 assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0); 1535 } 1536 for (const r of tm.getSelectionRects(1, 0)) { 1537 assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0); 1538 assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0); 1539 } 1540 } 1541 } 1542 1543 - name: 2d.text.measure.selection-rects-exceptions.tentative 1544 desc: >- 1545 Check that TextMetrics::getSelectionRects() throws when using invalid 1546 indexes. 1547 code: | 1548 const kTexts = [ 1549 'UNAVAILABLE', 1550 '🏁🎶🏁', 1551 ')(あ)(', 1552 '-abcd_', 1553 'اين المكتبة؟', 1554 'bidiالرياضيات' 1555 ] 1556 1557 for (const text of kTexts) { 1558 const tm = ctx.measureText(text); 1559 // Handled by the IDL binding. 1560 assert_throws_js(TypeError, () => tm.getSelectionRects(-1, 0) ); 1561 assert_throws_js(TypeError, () => tm.getSelectionRects(0, -1) ); 1562 assert_throws_js(TypeError, () => tm.getSelectionRects(-1, -1) ); 1563 // Thrown in TextMetrics. 1564 assert_throws_dom("IndexSizeError", 1565 () => tm.getSelectionRects(text.length + 1, 0) ); 1566 assert_throws_dom("IndexSizeError", 1567 () => tm.getSelectionRects(0, text.length + 1) ); 1568 assert_throws_dom("IndexSizeError", 1569 () => tm.getSelectionRects(text.length + 1, text.length + 1) ); 1570 } 1571 1572 - name: 2d.text.measure.getActualBoundingBox.tentative 1573 desc: >- 1574 Test TextMetrics::getActualBoundingBox(), with text align {{ text_align }} 1575 , and {{ letter_spacing }} letter spacing. 1576 test_type: promise 1577 fonts: 1578 - CanvasTest 1579 size: [800, 200] 1580 code: | 1581 // Use measureText to create a rect for the whole text 1582 function getFullTextBoundingBox(text) { 1583 const tm = ctx.measureText(text); 1584 return { 1585 x: -tm.actualBoundingBoxLeft, 1586 y: -tm.actualBoundingBoxAscent, 1587 width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight, 1588 height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent 1589 }; 1590 } 1591 1592 // Returns a string a replaces the characters in text that are not in the 1593 // range [start, end) with spaces. 1594 function buildTestString(text, start, end) { 1595 let ret = ''; 1596 for (let i = 0; i < text.length; ++i) { 1597 if (start <= i && i < end) 1598 ret += text[i]; 1599 else 1600 ret += ' '; 1601 } 1602 return ret; 1603 } 1604 1605 function checkRectsMatch(rect_a, rect_b, text, start, end) { 1606 assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`); 1607 assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`); 1608 assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`); 1609 assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`); 1610 } 1611 1612 function testForSubstring(text, start, end) { 1613 const textMetrics = ctx.measureText(text); 1614 const rect_from_api = textMetrics.getActualBoundingBox(start, end); 1615 const rect_from_full_bounds = getFullTextBoundingBox( 1616 buildTestString(text, start, end)); 1617 checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end); 1618 } 1619 1620 {{ load_font }} 1621 ctx.textAlign = '{{ text_align }}'; 1622 ctx.letterSpacing = '{{ letter_spacing }}'; 1623 1624 const kAligns = [ 1625 'left', 1626 'center', 1627 'right', 1628 ]; 1629 1630 const kBaselines = [ 1631 'top', 1632 'hanging', 1633 'middle', 1634 'alphabetic', 1635 'ideographic', 1636 'bottom', 1637 ]; 1638 1639 ctx.font = '50px CanvasTest'; 1640 const text = 'ABCDE'; 1641 for (const align of kAligns) { 1642 for (const baseline of kBaselines) { 1643 ctx.textAlign = align; 1644 ctx.textBaseline = baseline; 1645 // Full string. 1646 testForSubstring(text, 0, text.length); 1647 // Intermediate string. 1648 testForSubstring(text, 1, text.length - 1); 1649 // First character. 1650 testForSubstring(text, 0, 1); 1651 // Intermediate character. 1652 testForSubstring(text, 2, 3); 1653 } 1654 } 1655 variants_layout: 1656 [multi_files, single_file, single_file] 1657 variants: 1658 - *load-font-variant-definition 1659 - align-left: 1660 text_align: left 1661 align-center: 1662 text_align: center 1663 align-right: 1664 text_align: right 1665 - no-spacing: 1666 letter_spacing: 0px 1667 spacing: 1668 letter_spacing: 10px 1669 1670 - name: 2d.text.measure.getActualBoundingBox-full-text.tentative 1671 desc: >- 1672 Test TextMetrics::getActualBoundingBox() for the full length of the string 1673 for some edge cases, with direction {{ text_direction }} and 1674 {{ variant_names[1] }} 1675 size: [800, 200] 1676 code: | 1677 // Use measureText to create a rect for the whole text 1678 function getFullTextBoundingBox(text) { 1679 const tm = ctx.measureText(text); 1680 return { 1681 x: -tm.actualBoundingBoxLeft, 1682 y: -tm.actualBoundingBoxAscent, 1683 width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight, 1684 height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent 1685 }; 1686 } 1687 1688 function checkRectsMatch(rect_a, rect_b) { 1689 assert_approx_equals(rect_a.x, rect_b.x, 1.0); 1690 assert_approx_equals(rect_a.y, rect_b.y, 1.0); 1691 assert_approx_equals(rect_a.width, rect_b.width, 1.0); 1692 assert_approx_equals(rect_a.height, rect_b.height, 1.0); 1693 } 1694 {% if use_directional_override %} 1695 1696 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 1697 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 1698 const LTR_OVERRIDE = '\u202D'; 1699 const RTL_OVERRIDE = '\u202E'; 1700 const OVERRIDE_END = '\u202C'; 1701 if (direction_is_ltr) { 1702 return LTR_OVERRIDE + text + OVERRIDE_END; 1703 } 1704 return RTL_OVERRIDE + text + OVERRIDE_END; 1705 } 1706 {% endif %} 1707 1708 const kAligns = [ 1709 'left', 1710 'center', 1711 'right', 1712 ]; 1713 1714 const kBaselines = [ 1715 'top', 1716 'hanging', 1717 'middle', 1718 'alphabetic', 1719 'ideographic', 1720 'bottom', 1721 ]; 1722 1723 const kTexts = [ 1724 'UNAVAILABLE', 1725 '🏁🎶🏁', 1726 ')(あ)(', 1727 '-abcd ', 1728 'اين المكتبة؟', 1729 'bidiالرياضيات' 1730 ] 1731 1732 ctx.font = '50px sans-serif'; 1733 ctx.direction = '{{ text_direction }}'; 1734 for (const align of kAligns) { 1735 for (const baseline of kBaselines) { 1736 ctx.textAlign = align; 1737 ctx.textBaseline = baseline; 1738 for (text of kTexts) { 1739 {% if use_directional_override %} 1740 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 1741 {% endif %} 1742 const tm = ctx.measureText(text); 1743 const rect_from_api = tm.getActualBoundingBox(0, text.length); 1744 const rect_from_full_bounds = getFullTextBoundingBox(text); 1745 checkRectsMatch(rect_from_api, rect_from_full_bounds) 1746 } 1747 } 1748 } 1749 variants_layout: [single_file, single_file] 1750 variants: 1751 - direction-ltr: 1752 text_direction: ltr 1753 direction-rtl: 1754 text_direction: rtl 1755 - no-directional-override: 1756 use_directional_override: false 1757 directional-override: 1758 use_directional_override: true 1759 1760 - name: 2d.text.measure.getActualBoundingBox-exceptions.tentative 1761 desc: >- 1762 Check that TextMetrics::getActualBoundingBox() throws when using invalid 1763 indexes. 1764 code: | 1765 const kTexts = [ 1766 'UNAVAILABLE', 1767 '🏁🎶🏁', 1768 ')(あ)(', 1769 '-abcd_', 1770 'اين المكتبة؟', 1771 'bidiالرياضيات' 1772 ] 1773 1774 for (const text of kTexts) { 1775 const tm = ctx.measureText(text); 1776 // Handled by the IDL binding. 1777 assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) ); 1778 assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) ); 1779 assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) ); 1780 // Thrown in TextMetrics. 1781 assert_throws_dom("IndexSizeError", 1782 () => tm.getActualBoundingBox(text.length, 0) ); 1783 assert_throws_dom("IndexSizeError", 1784 () => tm.getActualBoundingBox(0, text.length + 1) ); 1785 assert_throws_dom("IndexSizeError", 1786 () => tm.getActualBoundingBox(text.length, text.length + 1) ); 1787 } 1788 1789 - name: 2d.text.measure.index-from-offset.tentative 1790 desc: >- 1791 Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, 1792 where possible, with direction {{ text_direction }}, text align 1793 {{ text_align }}, {{ letter_spacing }} letter spacing and 1794 {{ variant_names[3] }}. 1795 canvas_types: ['HtmlCanvas', 'OffscreenCanvas'] 1796 code: | 1797 function alignOffset(offset, width) { 1798 if ('{{ text_align }}' == 'center') { 1799 offset -= width / 2; 1800 } else if ('{{ text_align }}' == 'right' || 1801 ('{{ text_direction }}' == 'ltr' && '{{ text_align }}' == 'end') || 1802 ('{{ text_direction }}' == 'rtl' && '{{ text_align }}' == 'start')) { 1803 offset -= width; 1804 } 1805 return offset; 1806 } 1807 1808 {% if use_directional_override %} 1809 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 1810 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 1811 const LTR_OVERRIDE = '\u202D'; 1812 const RTL_OVERRIDE = '\u202E'; 1813 const OVERRIDE_END = '\u202C'; 1814 if (direction_is_ltr) { 1815 return LTR_OVERRIDE + text + OVERRIDE_END; 1816 } 1817 return RTL_OVERRIDE + text + OVERRIDE_END; 1818 } 1819 {% endif %} 1820 1821 function placeAndMeasureTextInDOM(text, text_width, point) { 1822 const el = document.createElement("p"); 1823 el.innerHTML = text; 1824 el.style.font = '50px sans-serif'; 1825 el.style.direction = '{{ text_direction }}'; 1826 el.style.letterSpacing = '{{ letter_spacing }}'; 1827 // Put the text top left to make offsets simpler. 1828 el.style.padding = '0px'; 1829 el.style.margin = '0px'; 1830 el.style.position = 'absolute'; 1831 el.style.x = '0px'; 1832 el.style.y = '0px'; 1833 document.body.appendChild(el); 1834 text_bound = el.getBoundingClientRect(); 1835 text_x = text_bound.x; 1836 text_y = text_bound.y + text_bound.height / 2; 1837 1838 // Offset to the requested point determined by textAlign and direction. 1839 let text_align_dx = 0; 1840 if ('{{ text_align }}' == 'center') { 1841 text_align_dx = text_width / 2; 1842 } else if ('{{ text_align }}' == 'right' || 1843 ('{{ text_direction }}' == 'ltr' && '{{ text_align }}' == 'end') || 1844 ('{{ text_direction }}' == 'rtl' && '{{ text_align }}' == 'start')) { 1845 text_align_dx = text_width; 1846 } 1847 position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y); 1848 @assert position.offsetNode.parentNode === el; 1849 1850 document.body.removeChild(el); 1851 1852 return position.offset; 1853 } 1854 1855 ctx.font = '50px sans-serif'; 1856 ctx.direction = '{{ text_direction }}'; 1857 ctx.textAlign = '{{ text_align }}'; 1858 ctx.letterSpacing = '{{ letter_spacing }}'; 1859 1860 const kTexts = [ 1861 'UNAVAILABLE', 1862 '🏁🎶🏁', 1863 ')(あ)(', 1864 ')(ああ)(', 1865 'ああ)(ああ', 1866 '--abcd__' 1867 ] 1868 1869 for (text of kTexts) { 1870 {% if use_directional_override %} 1871 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 1872 {% endif %} 1873 const tm = ctx.measureText(text); 1874 text_width = tm.width; 1875 step = 30; 1876 if ('{{letter_spacing}}' == '10px') { 1877 step = 40; 1878 } 1879 1880 offset = step; 1881 adjusted_offset = alignOffset(offset, text_width); 1882 tm_position = tm.getIndexFromOffset(adjusted_offset); 1883 doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset); 1884 assert_equals(tm_position, 1885 doc_position, 1886 "for " + text + " offset " + offset); 1887 1888 offset = text_width / 2 - 10; 1889 adjusted_offset = alignOffset(offset, text_width); 1890 tm_position = tm.getIndexFromOffset(adjusted_offset); 1891 doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset); 1892 assert_equals(tm_position, 1893 doc_position, 1894 "for " + text + " offset " + offset); 1895 1896 offset = text_width / 2 + 10; 1897 adjusted_offset = alignOffset(offset, text_width); 1898 tm_position = tm.getIndexFromOffset(adjusted_offset); 1899 doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset); 1900 assert_equals(tm_position, 1901 doc_position, 1902 "for " + text + " offset " + offset); 1903 1904 offset = text_width - step; 1905 adjusted_offset = alignOffset(offset, text_width); 1906 tm_position = tm.getIndexFromOffset(adjusted_offset); 1907 doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset); 1908 assert_equals(tm_position, 1909 doc_position, 1910 "for " + text + " offset " + offset); 1911 } 1912 variants_layout: [single_file, single_file, single_file, single_file] 1913 variants: 1914 - direction-ltr: 1915 text_direction: ltr 1916 direction-rtl: 1917 text_direction: rtl 1918 - align-left: 1919 text_align: left 1920 align-center: 1921 text_align: center 1922 align-right: 1923 text_align: right 1924 align-start: 1925 text_align: start 1926 align-end: 1927 text_align: end 1928 - no-spacing: 1929 letter_spacing: 0px 1930 spacing: 1931 letter_spacing: 10px 1932 - no-directional-override: 1933 use_directional_override: false 1934 directional-override: 1935 use_directional_override: true 1936 1937 - name: 2d.text.measure.index-from-offset-edges.tentative 1938 desc: >- 1939 Check that TextMetrics::getIndexFromOffset() gives correct edges when 1940 the requested point is outside the range, with direction {{ text_direction }} 1941 and text align {{ text_align }}. 1942 code: | 1943 function computeExpected(text, text_width, offset) { 1944 expected_position = 0; 1945 if ('{{ text_align }}' == 'center' && offset == 0) { 1946 return text.length / 2; 1947 } 1948 if ('{{ text_direction }}' == 'ltr') { 1949 if (offset >= text_width) { 1950 return text.length; 1951 } 1952 if (offset <= -text_width) { 1953 return 0; 1954 } 1955 // offset must be 0. 1956 if ('{{ text_align }}' == 'start' || '{{ text_align }}' == 'left') { 1957 return 0; 1958 } else { 1959 return text.length; 1960 } 1961 } else { 1962 if (offset >= text_width) { 1963 return 0; 1964 } 1965 if (offset <= -text_width) { 1966 return text.length; 1967 } 1968 // offset must be 0. 1969 if ('{{ text_align }}' == 'start' || '{{ text_align }}' == 'right') { 1970 return 0; 1971 } else { 1972 return text.length; 1973 } 1974 } 1975 return expected_position; 1976 } 1977 1978 ctx.font = '50px sans-serif'; 1979 ctx.direction = '{{ text_direction }}'; 1980 ctx.textAlign = '{{ text_align }}'; 1981 ctx.letterSpacing = '{{ letter_spacing }}'; 1982 1983 // The leading and trailing '-' cause the string to always follow 1984 // the specified direction, even though the interior will always be ltr. 1985 const text = '-0123456789-'; 1986 1987 // Points are multiples of the string width as reported by 1988 // textMetrics.width. 1989 const kPoints = [ 1990 -2, 1991 -1, 1992 0, 1993 1, 1994 2 1995 ] 1996 1997 const tm = ctx.measureText(text); 1998 text_width = tm.width; 1999 for (const multiple of kPoints) { 2000 offset = multiple * text_width; 2001 tm_position = tm.getIndexFromOffset(offset); 2002 expected_position = computeExpected(text, text_width, offset); 2003 assert_equals(tm_position, 2004 expected_position, 2005 "for " + text + " multiple " + multiple); 2006 } 2007 variants_layout: [single_file, single_file] 2008 variants: 2009 - direction-ltr: 2010 text_direction: ltr 2011 direction-rtl: 2012 text_direction: rtl 2013 - align-left: 2014 text_align: left 2015 align-center: 2016 text_align: center 2017 align-right: 2018 text_align: right 2019 align-start: 2020 text_align: start 2021 align-end: 2022 text_align: end 2023 2024 - name: 2d.text.measure.index-from-offset-edge-cases.tentative 2025 desc: >- 2026 Test the edge cases for getIndexFromOffset, where the point is at the 2027 edge of glyph and at the midpoint. 2028 test_type: promise 2029 fonts: 2030 - CanvasTest 2031 code: | 2032 {{ load_font }} 2033 ctx.font = '50px CanvasTest'; 2034 ctx.direction = 'ltr'; 2035 ctx.align = 'left' 2036 ctx.baseline = 'alphabetic' 2037 tm = ctx.measureText('A'); 2038 const a_width = tm.width; 2039 tm = ctx.measureText('B'); 2040 const b_width = tm.width; 2041 tm = ctx.measureText('C'); 2042 const c_width = tm.width; 2043 const epsilon = 1.0e-4; 2044 2045 tm = ctx.measureText('ABC'); 2046 @assert tm.getIndexFromOffset(0) == 0; 2047 @assert tm.getIndexFromOffset(a_width / 2) == 0; 2048 @assert tm.getIndexFromOffset(a_width / 2 + 1) == 1; 2049 @assert tm.getIndexFromOffset(a_width) == 1; 2050 @assert tm.getIndexFromOffset(a_width + b_width / 2) == 1; 2051 @assert tm.getIndexFromOffset(a_width + b_width / 2 + 1) == 2; 2052 @assert tm.getIndexFromOffset(a_width + b_width) == 2; 2053 @assert tm.getIndexFromOffset(a_width + b_width + c_width / 2) == 2; 2054 @assert tm.getIndexFromOffset(a_width + b_width + c_width / 2 + 1) == 3; 2055 @assert tm.getIndexFromOffset(a_width + b_width + c_width) == 3; 2056 variants: 2057 - *load-font-variant-definition 2058 2059 - name: 2d.text.measure.text-clusters-split.tentative 2060 desc: >- 2061 Test that getTextClusters() splits the input correctly into the 2062 minimal clusters, keeping emojis together. 2063 size: [400, 200] 2064 code: | 2065 function getClusterIndexes(text) { 2066 const clusters = ctx.measureText(text).getTextClusters(); 2067 const result = []; 2068 for (let i = 0; i < clusters.length; i++) { 2069 const end = clusters[i].end; 2070 if (end === (i + 1 < clusters.length ? clusters[i + 1].start : text.length)) { 2071 result.push(clusters[i].start); 2072 } else { 2073 result.push([clusters[i].start, clusters[i].end]); 2074 } 2075 } 2076 return result; 2077 } 2078 2079 function assertClusterIndexes(text, expected) { 2080 const actual = getClusterIndexes(text); 2081 assert_array_equals(actual, expected); 2082 } 2083 2084 ctx.font = '50px serif'; 2085 const text = 'ABC ☺️❤️'; 2086 ctx.fillText(text, 20, 100); 2087 assertClusterIndexes(text, [0, 1, 2, 3, 4, 6]); 2088 2089 // [UAX#29]: https://unicode.org/reports/tr29/ 2090 // [UAX#29] GB9: × (Extend | ZWJ) 2091 assertClusterIndexes('X\u200DY', [0, 2]); 2092 // [UAX#29] GB11: \p{Extended_Pictographic} Extend* ZWJ × \p{Extended_Pictographic} 2093 assertClusterIndexes('\u{1FFFD}\u200D\u{1FFFD}', [0]); 2094 2095 - name: 2d.text.measure.text-clusters-exceptions.tentative 2096 desc: >- 2097 Check that TextMetrics::getTextClusters() throws when using invalid 2098 indexes. 2099 code: | 2100 const kTexts = [ 2101 'UNAVAILABLE', 2102 '🏁🎶🏁', 2103 ')(あ)(', 2104 '-abcd_' 2105 ] 2106 2107 for (const text of kTexts) { 2108 const tm = ctx.measureText(text); 2109 // Handled by the IDL binding. 2110 assert_throws_js(TypeError, () => tm.getTextClusters(-1, 0) ); 2111 assert_throws_js(TypeError, () => tm.getTextClusters(0, -1) ); 2112 assert_throws_js(TypeError, () => tm.getTextClusters(-1, -1) ); 2113 // Thrown in TextMetrics. 2114 assert_throws_dom("IndexSizeError", 2115 () => tm.getTextClusters(text.length, 0) ); 2116 assert_throws_dom("IndexSizeError", 2117 () => tm.getTextClusters(0, text.length + 1) ); 2118 assert_throws_dom("IndexSizeError", 2119 () => tm.getTextClusters(text.length, text.length + 1) ); 2120 } 2121 2122 - name: 2d.text.measure.text-clusters-position.tentative 2123 desc: >- 2124 Test that TextMetrics::getTextClusters() returns clusters that are 2125 positioned according to the target align and baseline passed as options. 2126 size: [500, 500] 2127 test_type: promise 2128 fonts: 2129 - CanvasTest 2130 code: | 2131 {{ load_font }} 2132 ctx.font = '40px CanvasTest'; 2133 const text = 'E'; 2134 2135 // Origin for all the measurements is placed at the top left corner. 2136 ctx.textAlign = 'left'; 2137 ctx.textBaseline = 'top'; 2138 let tm = ctx.measureText(text); 2139 2140 // X position. 2141 @assert Math.abs(tm.getTextClusters({align: 'left'})[0].x) === 0; 2142 @assert tm.getTextClusters({align: 'center'})[0].x === 20; 2143 @assert tm.getTextClusters({align: 'right'})[0].x === 40; 2144 2145 // Y position. 2146 @assert Math.abs(tm.getTextClusters({baseline: 'top'})[0].y) === 0; 2147 @assert tm.getTextClusters({baseline: 'middle'})[0].y === 20; 2148 @assert tm.getTextClusters({baseline: 'bottom'})[0].y === 40; 2149 @assert tm.getTextClusters({baseline: 'alphabetic'})[0].y === 30; 2150 variants: 2151 - *load-font-variant-definition 2152 2153 - name: 2d.text.measure.fillTextCluster-align.tentative 2154 desc: >- 2155 Test that fillTextCluster() correctly positions the text, taking into 2156 account the textAlign from the context at the time the text was measured. 2157 size: [250, 43] 2158 code: | 2159 ctx.font = '20px serif'; 2160 const text = 'Test ☺️ א'; 2161 const x = canvas.width / 2; 2162 const y = canvas.height / 2; 2163 2164 ctx.textAlign = '{{ ctx_align }}'; 2165 let tm = ctx.measureText(text); 2166 const clusters = tm.getTextClusters(); 2167 2168 // Rendering all clusters with the same (x, y) parameters must be 2169 // equivalent to a fillText() call at (x, y). 2170 for (const cluster of clusters) { 2171 ctx.fillTextCluster(cluster, x, y); 2172 } 2173 reference: | 2174 ctx.font = '20px serif'; 2175 const text = 'Test ☺️ א'; 2176 const x = canvas.width / 2; 2177 const y = canvas.height / 2; 2178 2179 ctx.textAlign = '{{ ctx_align }}'; 2180 2181 // Rendering all clusters with the same (x, y) parameters must be 2182 // equivalent to a fillText() call at (x, y). 2183 ctx.fillText(text, x, y); 2184 variants_layout: 2185 [single_file] 2186 variants: 2187 - ctx_align_left: 2188 ctx_align: left 2189 ctx_align_center: 2190 ctx_align: center 2191 ctx_align_right: 2192 ctx_align: right 2193 2194 - name: 2d.text.measure.strokeTextCluster-align.tentative 2195 desc: >- 2196 Test that strokeTextCluster() correctly positions the text, taking into 2197 account the textAlign from the context at the time the text was measured. 2198 size: [250, 43] 2199 code: | 2200 ctx.font = '20px serif'; 2201 const text = 'Test ☺️ א'; 2202 const x = canvas.width / 2; 2203 const y = canvas.height / 2; 2204 2205 ctx.textAlign = '{{ ctx_align }}'; 2206 let tm = ctx.measureText(text); 2207 const clusters = tm.getTextClusters(); 2208 2209 // Rendering all clusters with the same (x, y) parameters must be 2210 // equivalent to a strokeText() call at (x, y). 2211 for (const cluster of clusters) { 2212 ctx.strokeTextCluster(cluster, x, y); 2213 } 2214 reference: | 2215 ctx.font = '20px serif'; 2216 const text = 'Test ☺️ א'; 2217 const x = canvas.width / 2; 2218 const y = canvas.height / 2; 2219 2220 ctx.textAlign = '{{ ctx_align }}'; 2221 2222 // Rendering all clusters with the same (x, y) parameters must be 2223 // equivalent to a strokeText() call at (x, y). 2224 ctx.strokeText(text, x, y); 2225 variants_layout: 2226 [single_file] 2227 variants: 2228 - ctx_align_left: 2229 ctx_align: left 2230 ctx_align_center: 2231 ctx_align: center 2232 ctx_align_right: 2233 ctx_align: right 2234 2235 - name: 2d.text.measure.fillTextCluster-baseline.tentative 2236 desc: >- 2237 Test that fillTextCluster() correctly positions the text, taking into 2238 account the textBaseline from the context at the time the text was measured. 2239 size: [180, 43] 2240 code: | 2241 ctx.font = '20px serif'; 2242 const text = 'Test ☺️ א'; 2243 const x = 20; 2244 const y = canvas.height / 2; 2245 2246 ctx.textBaseline = '{{ ctx_baseline }}'; 2247 let tm = ctx.measureText(text); 2248 const clusters = tm.getTextClusters(); 2249 2250 // Rendering all clusters with the same (x, y) parameters must be 2251 // equivalent to a fillText() call at (x, y). 2252 for (const cluster of clusters) { 2253 ctx.fillTextCluster(cluster, x, y); 2254 } 2255 reference: | 2256 ctx.font = '20px serif'; 2257 const text = 'Test ☺️ א'; 2258 const x = 20; 2259 const y = canvas.height / 2; 2260 2261 ctx.textBaseline = '{{ ctx_baseline }}'; 2262 ctx.fillText(text, x, y); 2263 variants_layout: 2264 [single_file] 2265 variants: 2266 - ctx_baseline_top: 2267 ctx_baseline: top 2268 ctx_baseline_middle: 2269 ctx_baseline: middle 2270 ctx_baseline_bottom: 2271 ctx_baseline: bottom 2272 ctx_baseline_alphabetic: 2273 ctx_baseline: alphabetic 2274 2275 - name: 2d.text.measure.strokeTextCluster-baseline.tentative 2276 desc: >- 2277 Test that strokeTextCluster() correctly positions the text, taking into 2278 account the textBaseline from the context at the time the text was measured. 2279 size: [180, 43] 2280 code: | 2281 ctx.font = '20px serif'; 2282 const text = 'Test ☺️ א'; 2283 const x = 20; 2284 const y = canvas.height / 2; 2285 2286 ctx.textBaseline = '{{ ctx_baseline }}'; 2287 let tm = ctx.measureText(text); 2288 const clusters = tm.getTextClusters(); 2289 2290 // Rendering all clusters with the same (x, y) parameters must be 2291 // equivalent to a strokeText() call at (x, y). 2292 for (const cluster of clusters) { 2293 ctx.strokeTextCluster(cluster, x, y); 2294 } 2295 reference: | 2296 ctx.font = '20px serif'; 2297 const text = 'Test ☺️ א'; 2298 const x = 20; 2299 const y = canvas.height / 2; 2300 2301 ctx.textBaseline = '{{ ctx_baseline }}'; 2302 ctx.strokeText(text, x, y); 2303 variants_layout: 2304 [single_file] 2305 variants: 2306 - ctx_baseline_top: 2307 ctx_baseline: top 2308 ctx_baseline_middle: 2309 ctx_baseline: middle 2310 ctx_baseline_bottom: 2311 ctx_baseline: bottom 2312 ctx_baseline_alphabetic: 2313 ctx_baseline: alphabetic 2314 2315 - name: 2d.text.measure.fillTextCluster-font-change.tentative 2316 desc: >- 2317 Test that fillTextCluster() renders in the font used originally when the 2318 text was measured, even if the font set on the context has changed since. 2319 size: [500, 200] 2320 code: | 2321 ctx.font = '50px sans-serif'; 2322 const text = 'Hello ♦️ World!'; 2323 let tm = ctx.measureText(text); 2324 const clusters = tm.getTextClusters(); 2325 2326 ctx.font = '80px serif'; 2327 2328 const x = 100; 2329 const y = 100; 2330 for (const cluster of clusters) { 2331 ctx.fillTextCluster(cluster, x, y); 2332 } 2333 reference: | 2334 ctx.font = '50px sans-serif'; 2335 const text = 'Hello ♦️ World!'; 2336 let tm = ctx.measureText(text); 2337 const clusters = tm.getTextClusters(); 2338 2339 const x = 100; 2340 const y = 100; 2341 ctx.fillText(text, x, y); 2342 2343 - name: 2d.text.measure.strokeTextCluster-font-change.tentative 2344 desc: >- 2345 Test that strokeTextCluster() renders in the font used originally when the 2346 text was measured, even if the font set on the context has changed since. 2347 size: [500, 200] 2348 code: | 2349 ctx.font = '50px sans-serif'; 2350 const text = 'Hello ♦️ World!'; 2351 let tm = ctx.measureText(text); 2352 const clusters = tm.getTextClusters(); 2353 2354 ctx.font = '80px serif'; 2355 2356 const x = 100; 2357 const y = 100; 2358 for (const cluster of clusters) { 2359 ctx.strokeTextCluster(cluster, x, y); 2360 } 2361 reference: | 2362 ctx.font = '50px sans-serif'; 2363 const text = 'Hello ♦️ World!'; 2364 let tm = ctx.measureText(text); 2365 const clusters = tm.getTextClusters(); 2366 2367 const x = 100; 2368 const y = 100; 2369 ctx.strokeText(text, x, y); 2370 2371 - name: 2d.text.measure.fillTextCluster-drawing-styles-change.tentative 2372 desc: >- 2373 Test that fillTextCluster() renders using the drawing styles as they were 2374 when `ctx.measureText()` was called, regardless of any changes in the 2375 context since. 2376 size: [250, 80] 2377 code: | 2378 ctx.font = '20px serif'; 2379 const text = 'Test ♦️ find'; 2380 2381 {{ original_value }} 2382 2383 let tm = ctx.measureText(text); 2384 const clusters = tm.getTextClusters(); 2385 2386 {{ modified_value }} 2387 2388 for (const cluster of clusters) { 2389 ctx.fillTextCluster(cluster, 10, 25); 2390 } 2391 2392 ctx.fillText(text, 10, 50); 2393 reference: | 2394 ctx.font = '20px serif'; 2395 const text = 'Test ♦️ find'; 2396 2397 {{ original_value }} 2398 2399 ctx.fillText(text, 10, 25); 2400 2401 {{ modified_value }} 2402 2403 ctx.fillText(text, 10, 50); 2404 variants_layout: 2405 [single_file] 2406 grid_width: 2 2407 variants: 2408 - letter_spacing: 2409 original_value: |- 2410 ctx.letterSpacing = '2px'; 2411 modified_value: |- 2412 ctx.letterSpacing = '6px'; 2413 word_spacing: 2414 original_value: |- 2415 ctx.wordSpacing = '2px'; 2416 modified_value: |- 2417 ctx.wordSpacing = '10px'; 2418 font_kerning: 2419 original_value: |- 2420 ctx.fontKerning = 'none'; 2421 modified_value: |- 2422 ctx.fontKerning = 'normal'; 2423 font_variant_caps: 2424 original_value: |- 2425 ctx.fontVariantCaps = 'small-caps'; 2426 modified_value: |- 2427 ctx.fontVariantCaps = 'all-small-caps'; 2428 2429 - name: 2d.text.measure.strokeTextCluster-drawing-styles-change.tentative 2430 desc: >- 2431 Test that strokeTextCluster() renders using the drawing styles as they were 2432 when `ctx.measureText()` was called, regardless of any changes in the 2433 context since. 2434 size: [250, 80] 2435 code: | 2436 ctx.font = '20px serif'; 2437 const text = 'Test ♦️ find'; 2438 2439 {{ original_value }} 2440 2441 let tm = ctx.measureText(text); 2442 const clusters = tm.getTextClusters(); 2443 2444 {{ modified_value }} 2445 2446 for (const cluster of clusters) { 2447 ctx.strokeTextCluster(cluster, 10, 25); 2448 } 2449 2450 ctx.strokeText(text, 10, 50); 2451 reference: | 2452 ctx.font = '20px serif'; 2453 const text = 'Test ♦️ find'; 2454 2455 {{ original_value }} 2456 2457 ctx.strokeText(text, 10, 25); 2458 2459 {{ modified_value }} 2460 2461 ctx.strokeText(text, 10, 50); 2462 variants_layout: 2463 [single_file] 2464 grid_width: 2 2465 variants: 2466 - letter_spacing: 2467 original_value: |- 2468 ctx.letterSpacing = '2px'; 2469 modified_value: |- 2470 ctx.letterSpacing = '6px'; 2471 word_spacing: 2472 original_value: |- 2473 ctx.wordSpacing = '2px'; 2474 modified_value: |- 2475 ctx.wordSpacing = '10px'; 2476 font_kerning: 2477 original_value: |- 2478 ctx.fontKerning = 'none'; 2479 modified_value: |- 2480 ctx.fontKerning = 'normal'; 2481 font_variant_caps: 2482 original_value: |- 2483 ctx.fontVariantCaps = 'small-caps'; 2484 modified_value: |- 2485 ctx.fontVariantCaps = 'all-small-caps'; 2486 2487 - name: 2d.text.measure.fillTextCluster-range.tentative 2488 desc: >- 2489 Test that getTextClusters() and fillTextCluster() correctly render 2490 different ranges of the input text. 2491 test_type: promise 2492 fonts: 2493 - CanvasTest 2494 size: [400, 300] 2495 code: | 2496 // Renders all the clusters in the list from position (x, y). 2497 function renderClusters(clusters, x, y) { 2498 for (const cluster of clusters) { 2499 ctx.fillTextCluster(cluster, x, y); 2500 } 2501 } 2502 2503 {{ load_font }} 2504 2505 ctx.font = '50px CanvasTest'; 2506 ctx.textAlign = 'left'; 2507 ctx.textBaseline = 'top'; 2508 const text = 'EEEEE'; 2509 let tm = ctx.measureText(text); 2510 2511 // Background color. 2512 ctx.fillStyle = '#f00'; 2513 ctx.fillRect(0, 0, canvas.width, canvas.height); 2514 2515 ctx.fillStyle = '#0f0'; 2516 2517 // Without the first character. 2518 renderClusters(tm.getTextClusters(1, 5), 0, 0); 2519 @assert pixel 5,5 ==~ 255,0,0,255; 2520 @assert pixel 55,5 ==~ 0,255,0,255; 2521 @assert pixel 105,5 ==~ 0,255,0,255; 2522 @assert pixel 155,5 ==~ 0,255,0,255; 2523 @assert pixel 205,5 ==~ 0,255,0,255; 2524 // Without the last character. 2525 renderClusters(tm.getTextClusters(0, 4), 0, 100); 2526 @assert pixel 5,105 ==~ 0,255,0,255; 2527 @assert pixel 55,105 ==~ 0,255,0,255; 2528 @assert pixel 105,105 ==~ 0,255,0,255; 2529 @assert pixel 155,105 ==~ 0,255,0,255; 2530 @assert pixel 205,105 ==~ 255,0,0,255; 2531 // Only the middle character. 2532 renderClusters(tm.getTextClusters(2, 3), 0, 150); 2533 @assert pixel 5,155 ==~ 255,0,0,255; 2534 @assert pixel 55,155 ==~ 255,0,0,255; 2535 @assert pixel 105,155 ==~ 0,255,0,255; 2536 @assert pixel 155,155 ==~ 255,0,0,255; 2537 @assert pixel 205,155 ==~ 255,0,0,255; 2538 variants: 2539 - *load-font-variant-definition 2540 2541 - name: 2d.text.measure.strokeTextCluster-range.tentative 2542 desc: >- 2543 Test that getTextClusters() and strokeTextCluster() correctly render 2544 different ranges of the input text. 2545 test_type: promise 2546 fonts: 2547 - CanvasTest 2548 size: [400, 300] 2549 code: | 2550 // Renders all the clusters in the list from position (x, y). 2551 function renderClusters(clusters, x, y) { 2552 for (const cluster of clusters) { 2553 ctx.strokeTextCluster(cluster, x, y); 2554 } 2555 } 2556 2557 {{ load_font }} 2558 2559 ctx.font = '50px CanvasTest'; 2560 ctx.textAlign = 'left'; 2561 ctx.textBaseline = 'top'; 2562 const text = 'EEEEE'; 2563 let tm = ctx.measureText(text); 2564 2565 // Background color. 2566 ctx.fillStyle = '#f00'; 2567 ctx.fillRect(0, 0, canvas.width, canvas.height); 2568 2569 ctx.strokeStyle = '#0f0'; 2570 ctx.lineWidth = 12; 2571 2572 // Without the first character. 2573 renderClusters(tm.getTextClusters(1, 5), 0, 0); 2574 @assert pixel 5,5 ==~ 255,0,0,255; 2575 @assert pixel 55,5 ==~ 0,255,0,255; 2576 @assert pixel 105,5 ==~ 0,255,0,255; 2577 @assert pixel 155,5 ==~ 0,255,0,255; 2578 @assert pixel 205,5 ==~ 0,255,0,255; 2579 // Without the last character. 2580 renderClusters(tm.getTextClusters(0, 4), 0, 100); 2581 @assert pixel 5,105 ==~ 0,255,0,255; 2582 @assert pixel 55,105 ==~ 0,255,0,255; 2583 @assert pixel 105,105 ==~ 0,255,0,255; 2584 @assert pixel 155,105 ==~ 0,255,0,255; 2585 @assert pixel 245,105 ==~ 255,0,0,255; 2586 // Only the middle character. 2587 renderClusters(tm.getTextClusters(2, 3), 0, 200); 2588 @assert pixel 5,205 ==~ 255,0,0,255; 2589 @assert pixel 55,205 ==~ 255,0,0,255; 2590 @assert pixel 105,205 ==~ 0,255,0,255; 2591 @assert pixel 195,205 ==~ 255,0,0,255; 2592 @assert pixel 245,205 ==~ 255,0,0,255; 2593 variants: 2594 - *load-font-variant-definition 2595 2596 - name: 2d.text.measure.fillTextCluster-options.tentative 2597 desc: >- 2598 Test that fillTextCluster() correctly applies the options passed as a 2599 dictionary. 2600 test_type: promise 2601 fonts: 2602 - CanvasTest 2603 size: [100, 300] 2604 code: | 2605 {{ load_font }} 2606 2607 ctx.font = '50px CanvasTest'; 2608 ctx.textAlign = 'left'; 2609 ctx.textBaseline = 'top'; 2610 const text = 'E'; 2611 const tm = ctx.measureText(text); 2612 const cluster = tm.getTextClusters()[0]; 2613 2614 // Background color. 2615 ctx.fillStyle = '#f00'; 2616 ctx.fillRect(0, 0, canvas.width, canvas.height); 2617 2618 ctx.fillStyle = '#0f0'; 2619 2620 // Override the align and baseline of the cluster. 2621 ctx.fillTextCluster(cluster, 50, 50, {align: 'right', baseline: 'bottom'}); 2622 @assert pixel 5,5 ==~ 0,255,0,255; 2623 @assert pixel 45,5 ==~ 0,255,0,255; 2624 @assert pixel 5,45 ==~ 0,255,0,255; 2625 @assert pixel 45,45 ==~ 0,255,0,255; 2626 @assert pixel 55,5 ==~ 255,0,0,255; 2627 @assert pixel 5,55 ==~ 255,0,0,255; 2628 @assert pixel 55,55 ==~ 255,0,0,255; 2629 2630 // Override the x and y values of the cluster. 2631 ctx.fillTextCluster(cluster, 0, 100, {x: 10, y: 10}); 2632 @assert pixel 15,115 ==~ 0,255,0,255; 2633 @assert pixel 55,115 ==~ 0,255,0,255; 2634 @assert pixel 15,155 ==~ 0,255,0,255; 2635 @assert pixel 55,155 ==~ 0,255,0,255; 2636 @assert pixel 65,115 ==~ 255,0,0,255; 2637 @assert pixel 15,165 ==~ 255,0,0,255; 2638 @assert pixel 65,165 ==~ 255,0,0,255; 2639 2640 // Override the align, baseline, x, and y values of the cluster. 2641 ctx.fillTextCluster(cluster, 50, 250, 2642 {align: 'right', baseline: 'bottom', x: 10, y: 10}); 2643 @assert pixel 15,215 ==~ 0,255,0,255; 2644 @assert pixel 55,215 ==~ 0,255,0,255; 2645 @assert pixel 15,255 ==~ 0,255,0,255; 2646 @assert pixel 55,255 ==~ 0,255,0,255; 2647 @assert pixel 65,215 ==~ 255,0,0,255; 2648 @assert pixel 15,265 ==~ 255,0,0,255; 2649 @assert pixel 65,265 ==~ 255,0,0,255; 2650 variants: 2651 - *load-font-variant-definition 2652 2653 - name: 2d.text.measure.strokeTextCluster-options.tentative 2654 desc: >- 2655 Test that strokeTextCluster() correctly applies the options passed as a 2656 dictionary. 2657 test_type: promise 2658 fonts: 2659 - CanvasTest 2660 size: [100, 300] 2661 code: | 2662 {{ load_font }} 2663 2664 ctx.font = '50px CanvasTest'; 2665 ctx.textAlign = 'left'; 2666 ctx.textBaseline = 'top'; 2667 const text = 'E'; 2668 const tm = ctx.measureText(text); 2669 const cluster = tm.getTextClusters()[0]; 2670 2671 // Background color. 2672 ctx.fillStyle = '#f00'; 2673 ctx.fillRect(0, 0, canvas.width, canvas.height); 2674 2675 ctx.strokeStyle = '#0f0'; 2676 ctx.lineWidth = 12; 2677 2678 // Override the align and baseline of the cluster. 2679 ctx.strokeTextCluster(cluster, 50, 50, {align: 'right', baseline: 'bottom'}); 2680 @assert pixel 5,5 ==~ 0,255,0,255; 2681 @assert pixel 45,5 ==~ 0,255,0,255; 2682 @assert pixel 5,45 ==~ 0,255,0,255; 2683 @assert pixel 45,45 ==~ 0,255,0,255; 2684 @assert pixel 5,95 ==~ 255,0,0,255; 2685 @assert pixel 50,95 ==~ 255,0,0,255; 2686 @assert pixel 95,50 ==~ 255,0,0,255; 2687 @assert pixel 95,5 ==~ 255,0,0,255; 2688 2689 // Override the x and y values of the cluster. 2690 ctx.strokeTextCluster(cluster, 0, 100, {x: 10, y: 10}); 2691 @assert pixel 15,115 ==~ 0,255,0,255; 2692 @assert pixel 55,115 ==~ 0,255,0,255; 2693 @assert pixel 15,155 ==~ 0,255,0,255; 2694 @assert pixel 55,155 ==~ 0,255,0,255; 2695 @assert pixel 1,101 ==~ 255,0,0,255; 2696 @assert pixel 1,151 ==~ 255,0,0,255; 2697 @assert pixel 51,101 ==~ 255,0,0,255; 2698 2699 // Override the align, baseline, x, and y values of the cluster. 2700 ctx.strokeTextCluster(cluster, 50, 250, 2701 {align: 'right', baseline: 'bottom', x: 10, y: 10}); 2702 @assert pixel 15,215 ==~ 0,255,0,255; 2703 @assert pixel 55,215 ==~ 0,255,0,255; 2704 @assert pixel 15,255 ==~ 0,255,0,255; 2705 @assert pixel 55,255 ==~ 0,255,0,255; 2706 @assert pixel 5,295 ==~ 255,0,0,255; 2707 @assert pixel 50,295 ==~ 255,0,0,255; 2708 @assert pixel 95,250 ==~ 255,0,0,255; 2709 @assert pixel 95,25 ==~ 255,0,0,255; 2710 variants: 2711 - *load-font-variant-definition 2712 2713 - name: 2d.text.drawing.style.absolute.spacing 2714 desc: Testing letter spacing and word spacing with absolute length 2715 code: | 2716 @assert ctx.letterSpacing === '0px'; 2717 @assert ctx.wordSpacing === '0px'; 2718 2719 ctx.letterSpacing = '3px'; 2720 @assert ctx.letterSpacing === '3px'; 2721 @assert ctx.wordSpacing === '0px'; 2722 2723 ctx.wordSpacing = '5px'; 2724 @assert ctx.letterSpacing === '3px'; 2725 @assert ctx.wordSpacing === '5px'; 2726 2727 ctx.letterSpacing = '-1px'; 2728 ctx.wordSpacing = '-1px'; 2729 @assert ctx.letterSpacing === '-1px'; 2730 @assert ctx.wordSpacing === '-1px'; 2731 2732 ctx.letterSpacing = '1PX'; 2733 ctx.wordSpacing = '10PX'; 2734 @assert ctx.letterSpacing === '1px'; 2735 @assert ctx.wordSpacing === '10px'; 2736 2737 - name: 2d.text.measure.lang 2738 desc: Testing the lang attribute 2739 test_type: promise 2740 fonts: 2741 - Lato-Medium 2742 code: | 2743 {{ load_font }} 2744 2745 ctx.font = '50px Lato-Medium'; 2746 ctx.lang = 'tr'; 2747 const text = 'fi'; 2748 const tm_tr = ctx.measureText(text); 2749 const tr_width = tm_tr.width; 2750 2751 ctx.lang = 'en'; 2752 const tm_en = ctx.measureText(text); 2753 const en_width = tm_en.width; 2754 2755 @assert tr_width > en_width; 2756 variants: 2757 - *load-font-variant-definition 2758 2759 - name: 2d.text.measure.lang.inherit 2760 desc: Testing the lang attribute 2761 test_type: promise 2762 canvas_types: ['HtmlCanvas', 'OffscreenCanvas'] 2763 fonts: 2764 - Lato-Medium 2765 canvas: 'lang="tr"' 2766 code: | 2767 {{ load_font }} 2768 2769 ctx.font = '50px Lato-Medium'; 2770 ctx.lang = 'inherit'; 2771 const text = 'fi'; 2772 const tm_tr = ctx.measureText(text); 2773 const tr_width = tm_tr.width; 2774 2775 ctx.lang = 'en'; 2776 const tm_en = ctx.measureText(text); 2777 const en_width = tm_en.width; 2778 2779 @assert tr_width > en_width; 2780 variants: 2781 - &load-font-variant-definitio-and-set-lang 2782 HtmlCanvas: 2783 append_variants_to_name: false 2784 canvas_types: ['HtmlCanvas'] 2785 load_font: |- 2786 await document.fonts.ready; 2787 OffscreenCanvas: 2788 append_variants_to_name: false 2789 canvas_types: ['OffscreenCanvas'] 2790 load_font: |- 2791 document.documentElement.setAttribute('lang','tr'); 2792 var f = new FontFace("{{ fonts[0] }}", "url('/fonts/{{ fonts[0] }}.ttf')"); 2793 f.load(); 2794 {% set root = 'self' if canvas_type == 'Worker' else 'document' %} 2795 {{ root }}.fonts.add(f); 2796 await {{ root }}.fonts.ready; 2797 2798 - name: 2d.text.drawing.style.font-relative.spacing 2799 desc: Testing letter spacing and word spacing with font-relative length 2800 code: | 2801 @assert ctx.letterSpacing === '0px'; 2802 @assert ctx.wordSpacing === '0px'; 2803 2804 ctx.letterSpacing = '1EX'; 2805 ctx.wordSpacing = '1EM'; 2806 @assert ctx.letterSpacing === '1ex'; 2807 @assert ctx.wordSpacing === '1em'; 2808 2809 ctx.letterSpacing = '1ch'; 2810 ctx.wordSpacing = '1ic'; 2811 @assert ctx.letterSpacing === '1ch'; 2812 @assert ctx.wordSpacing === '1ic'; 2813 2814 - name: 2d.text.drawing.style.nonfinite.spacing 2815 desc: Testing letter spacing and word spacing with nonfinite inputs 2816 code: | 2817 @assert ctx.letterSpacing === '0px'; 2818 @assert ctx.wordSpacing === '0px'; 2819 2820 function test_word_spacing(value) { 2821 ctx.wordSpacing = value; 2822 ctx.letterSpacing = value; 2823 @assert ctx.wordSpacing === '0px'; 2824 @assert ctx.letterSpacing === '0px'; 2825 } 2826 @nonfinite test_word_spacing(<0 NaN Infinity -Infinity>); 2827 2828 - name: 2d.text.drawing.style.invalid.spacing 2829 desc: Testing letter spacing and word spacing with invalid units 2830 code: | 2831 @assert ctx.letterSpacing === '0px'; 2832 @assert ctx.wordSpacing === '0px'; 2833 2834 function test_word_spacing(value) { 2835 ctx.wordSpacing = value; 2836 ctx.letterSpacing = value; 2837 @assert ctx.wordSpacing === '0px'; 2838 @assert ctx.letterSpacing === '0px'; 2839 } 2840 @nonfinite test_word_spacing(< '0s' '1min' '1deg' '1pp' 'initial' 'inherit' 'normal' 'none'>); 2841 2842 - name: 2d.text.drawing.style.letterSpacing.measure 2843 desc: Testing letter spacing with different length units 2844 code: | 2845 @assert ctx.letterSpacing === '0px'; 2846 @assert ctx.wordSpacing === '0px'; 2847 var width_normal = ctx.measureText('Hello World').width; 2848 2849 function test_letter_spacing(value, difference_spacing, epsilon) { 2850 ctx.letterSpacing = value; 2851 @assert ctx.letterSpacing === value; 2852 @assert ctx.wordSpacing === '0px'; 2853 width_with_letter_spacing = ctx.measureText('Hello World').width; 2854 assert_approx_equals(width_with_letter_spacing, width_normal + difference_spacing, epsilon, "letter spacing doesn't work."); 2855 } 2856 2857 // The first value is the letter Spacing to be set, the second value the 2858 // change in length of string 'Hello World', note that there are 11 letters 2859 // in 'hello world', so the length difference is always letterSpacing * 11. 2860 // and the third value is the acceptable differencee for the length change, 2861 // note that unit such as 1cm/1mm doesn't map to an exact pixel value. 2862 test_cases = [['3px', 33, 0.1], 2863 ['5px', 55, 0.1], 2864 ['-2px', -22, 0.1], 2865 ['1em', 110, 0.1], 2866 ['-0.1em', -11, 0.1], 2867 ['1in', 1056, 0.1], 2868 ['-0.1cm', -41.65, 0.2], 2869 ['-0.6mm', -24.95, 0.2]] 2870 2871 for (const test_case of test_cases) { 2872 test_letter_spacing(test_case[0], test_case[1], test_case[2]); 2873 } 2874 2875 - name: 2d.text.drawing.style.wordSpacing.measure 2876 desc: Testing word spacing with different length units 2877 code: | 2878 @assert ctx.letterSpacing === '0px'; 2879 @assert ctx.wordSpacing === '0px'; 2880 var width_normal = ctx.measureText('Hello World, again').width; 2881 2882 function test_word_spacing(value, difference_spacing, epsilon) { 2883 ctx.wordSpacing = value; 2884 @assert ctx.letterSpacing === '0px'; 2885 @assert ctx.wordSpacing === value; 2886 width_with_word_spacing = ctx.measureText('Hello World, again').width; 2887 assert_approx_equals(width_with_word_spacing, width_normal + difference_spacing, epsilon, "word spacing doesn't work."); 2888 } 2889 2890 // The first value is the word Spacing to be set, the second value the 2891 // change in length of string 'Hello World', note that there are 2 words 2892 // in 'Hello World, again', so the length difference is always wordSpacing * 2. 2893 // and the third value is the acceptable differencee for the length change, 2894 // note that unit such as 1cm/1mm doesn't map to an exact pixel value. 2895 test_cases = [['3px', 6, 0.1], 2896 ['5px', 10, 0.1], 2897 ['-2px', -4, 0.1], 2898 ['1em', 20, 0.1], 2899 ['-0.5em', -10, 0.1], 2900 ['1in', 192, 0.1], 2901 ['-0.1cm', -7.57, 0.2], 2902 ['-0.6mm', -4.54, 0.2]] 2903 2904 for (const test_case of test_cases) { 2905 test_word_spacing(test_case[0], test_case[1], test_case[2]); 2906 } 2907 2908 - name: 2d.text.drawing.style.letterSpacing.change.font 2909 desc: Set letter spacing and word spacing to font dependent value and verify it works after font change. 2910 code: | 2911 @assert ctx.letterSpacing === '0px'; 2912 @assert ctx.wordSpacing === '0px'; 2913 // Get the width for 'Hello World' at default size, 10px. 2914 var width_normal = ctx.measureText('Hello World').width; 2915 2916 ctx.letterSpacing = '1em'; 2917 @assert ctx.letterSpacing === '1em'; 2918 // 1em = 10px. Add 10px after each letter in "Hello World", 2919 // makes it 110px longer. 2920 var width_with_spacing = ctx.measureText('Hello World').width; 2921 assert_approx_equals(width_with_spacing, width_normal + 110, 0.1, "letter-spacing error"); 2922 2923 // Changing font to 20px. Without resetting the spacing, 1em letterSpacing 2924 // is now 20px, so it's suppose to be 220px longer without any letterSpacing set. 2925 ctx.font = '20px serif'; 2926 width_with_spacing = ctx.measureText('Hello World').width; 2927 // Now calculate the reference spacing for "Hello World" with no spacing. 2928 ctx.letterSpacing = '0em'; 2929 width_normal = ctx.measureText('Hello World').width; 2930 assert_approx_equals(width_with_spacing, width_normal + 220, 0.1, "letter-spacing error after font change"); 2931 2932 - name: 2d.text.drawing.style.wordSpacing.change.font 2933 desc: Set word spacing and word spacing to font dependent value and verify it works after font change. 2934 code: | 2935 @assert ctx.letterSpacing === '0px'; 2936 @assert ctx.wordSpacing === '0px'; 2937 // Get the width for 'Hello World, again' at default size, 10px. 2938 var width_normal = ctx.measureText('Hello World, again').width; 2939 2940 ctx.wordSpacing = '1em'; 2941 @assert ctx.wordSpacing === '1em'; 2942 // 1em = 10px. Add 10px after each word in "Hello World, again", 2943 // makes it 20px longer. 2944 var width_with_spacing = ctx.measureText('Hello World, again').width; 2945 @assert width_with_spacing === width_normal + 20; 2946 2947 // Changing font to 20px. Without resetting the spacing, 1em wordSpacing 2948 // is now 20px, so it's suppose to be 40px longer without any wordSpacing set. 2949 ctx.font = '20px serif'; 2950 width_with_spacing = ctx.measureText('Hello World, again').width; 2951 // Now calculate the reference spacing for "Hello World, again" with no spacing. 2952 ctx.wordSpacing = '0em'; 2953 width_normal = ctx.measureText('Hello World, again').width; 2954 @assert width_with_spacing === width_normal + 40; 2955 2956 - name: 2d.text.drawing.style.fontKerning 2957 desc: Testing basic functionalities of fontKerning for canvas 2958 code: | 2959 @assert ctx.fontKerning === "auto"; 2960 ctx.fontKerning = "normal"; 2961 @assert ctx.fontKerning === "normal"; 2962 width_normal = ctx.measureText("TAWATAVA").width; 2963 ctx.fontKerning = "none"; 2964 @assert ctx.fontKerning === "none"; 2965 width_none = ctx.measureText("TAWATAVA").width; 2966 @assert width_normal < width_none; 2967 2968 - name: 2d.text.drawing.style.fontKerning.with.uppercase 2969 desc: Testing basic functionalities of fontKerning for canvas 2970 code: | 2971 @assert ctx.fontKerning === "auto"; 2972 ctx.fontKerning = "Normal"; 2973 @assert ctx.fontKerning === "auto"; 2974 ctx.fontKerning = "normal"; 2975 @assert ctx.fontKerning === "normal"; 2976 ctx.fontKerning = "Auto"; 2977 @assert ctx.fontKerning === "normal"; 2978 ctx.fontKerning = "auto"; 2979 ctx.fontKerning = "noRmal"; 2980 @assert ctx.fontKerning === "auto"; 2981 ctx.fontKerning = "auto"; 2982 ctx.fontKerning = "NoRMal"; 2983 @assert ctx.fontKerning === "auto"; 2984 ctx.fontKerning = "auto"; 2985 ctx.fontKerning = "NORMAL"; 2986 @assert ctx.fontKerning === "auto"; 2987 2988 ctx.fontKerning = "None"; 2989 @assert ctx.fontKerning === "auto"; 2990 ctx.fontKerning = "none"; 2991 @assert ctx.fontKerning === "none"; 2992 ctx.fontKerning = "Auto"; 2993 @assert ctx.fontKerning === "none"; 2994 ctx.fontKerning = "auto"; 2995 ctx.fontKerning = "nOne"; 2996 @assert ctx.fontKerning === "auto"; 2997 ctx.fontKerning = "auto"; 2998 ctx.fontKerning = "nonE"; 2999 @assert ctx.fontKerning === "auto"; 3000 ctx.fontKerning = "auto"; 3001 ctx.fontKerning = "NONE"; 3002 @assert ctx.fontKerning === "auto"; 3003 3004 - name: 2d.text.drawing.style.fontVariant.settings 3005 desc: Testing basic functionalities of fontVariant for canvas 3006 code: | 3007 // Setting fontVariantCaps with lower cases 3008 @assert ctx.fontVariantCaps === "normal"; 3009 3010 ctx.fontVariantCaps = "normal"; 3011 @assert ctx.fontVariantCaps === "normal"; 3012 3013 ctx.fontVariantCaps = "small-caps"; 3014 @assert ctx.fontVariantCaps === "small-caps"; 3015 3016 ctx.fontVariantCaps = "all-small-caps"; 3017 @assert ctx.fontVariantCaps === "all-small-caps"; 3018 3019 ctx.fontVariantCaps = "petite-caps"; 3020 @assert ctx.fontVariantCaps === "petite-caps"; 3021 3022 ctx.fontVariantCaps = "all-petite-caps"; 3023 @assert ctx.fontVariantCaps === "all-petite-caps"; 3024 3025 ctx.fontVariantCaps = "unicase"; 3026 @assert ctx.fontVariantCaps === "unicase"; 3027 3028 ctx.fontVariantCaps = "titling-caps"; 3029 @assert ctx.fontVariantCaps === "titling-caps"; 3030 3031 // Setting fontVariantCaps with mixed-case values is not valid 3032 ctx.fontVariantCaps = "nORmal"; 3033 @assert ctx.fontVariantCaps === "titling-caps"; 3034 3035 ctx.fontVariantCaps = "normal"; 3036 @assert ctx.fontVariantCaps === "normal"; 3037 3038 ctx.fontVariantCaps = "smaLL-caps"; 3039 @assert ctx.fontVariantCaps === "normal"; 3040 3041 ctx.fontVariantCaps = "all-small-CAPS"; 3042 @assert ctx.fontVariantCaps === "normal"; 3043 3044 ctx.fontVariantCaps = "pEtitE-caps"; 3045 @assert ctx.fontVariantCaps === "normal"; 3046 3047 ctx.fontVariantCaps = "All-Petite-Caps"; 3048 @assert ctx.fontVariantCaps === "normal"; 3049 3050 ctx.fontVariantCaps = "uNIcase"; 3051 @assert ctx.fontVariantCaps === "normal"; 3052 3053 ctx.fontVariantCaps = "titling-CAPS"; 3054 @assert ctx.fontVariantCaps === "normal"; 3055 3056 // Setting fontVariantCaps with non-existing font variant. 3057 ctx.fontVariantCaps = "titling-caps"; 3058 ctx.fontVariantCaps = "abcd"; 3059 @assert ctx.fontVariantCaps === "titling-caps"; 3060 3061 - name: 2d.text.drawing.style.textRendering.settings 3062 desc: Testing basic functionalities of textRendering in Canvas 3063 code: | 3064 // Setting textRendering with correct case. 3065 @assert ctx.textRendering === "auto"; 3066 3067 ctx.textRendering = "optimizeSpeed"; 3068 @assert ctx.textRendering === "optimizeSpeed"; 3069 3070 ctx.textRendering = "optimizeLegibility"; 3071 @assert ctx.textRendering === "optimizeLegibility"; 3072 3073 ctx.textRendering = "geometricPrecision"; 3074 @assert ctx.textRendering === "geometricPrecision"; 3075 3076 ctx.textRendering = "auto"; 3077 @assert ctx.textRendering === "auto"; 3078 3079 // Setting textRendering with incorrect case is ignored. 3080 ctx.textRendering = "OPtimizeSpeed"; 3081 @assert ctx.textRendering === "auto"; 3082 3083 ctx.textRendering = "OPtimizELEgibility"; 3084 @assert ctx.textRendering === "auto"; 3085 3086 ctx.textRendering = "GeometricPrecision"; 3087 @assert ctx.textRendering === "auto"; 3088 3089 ctx.textRendering = "optimizespeed"; 3090 @assert ctx.textRendering === "auto"; 3091 3092 ctx.textRendering = "optimizelegibility"; 3093 @assert ctx.textRendering === "auto"; 3094 3095 ctx.textRendering = "geometricprecision"; 3096 @assert ctx.textRendering === "auto"; 3097 3098 ctx.textRendering = "optimizeLegibility"; 3099 @assert ctx.textRendering === "optimizeLegibility"; 3100 3101 ctx.textRendering = "AUTO"; 3102 @assert ctx.textRendering === "optimizeLegibility"; 3103 3104 ctx.textRendering = "Auto"; 3105 @assert ctx.textRendering === "optimizeLegibility"; 3106 3107 // Setting textRendering with non-existing font variant. 3108 ctx.textRendering = "abcd"; 3109 @assert ctx.textRendering === "optimizeLegibility"; 3110 3111 ctx.textRendering = "normal"; 3112 @assert ctx.textRendering === "optimizeLegibility"; 3113 3114 ctx.textRendering = ""; 3115 @assert ctx.textRendering === "optimizeLegibility"; 3116 3117 ctx.textRendering = "auto"; 3118 @assert ctx.textRendering === "auto"; 3119 3120 - name: 2d.text.drawing.style.reset.TextRendering 3121 desc: TextRendering stays the same after font change. 3122 code: | 3123 ctx.font = '20px serif'; 3124 ctx.textRendering = "optimizeSpeed"; 3125 @assert ctx.textRendering === "optimizeSpeed"; 3126 ctx.font = '10px serif'; 3127 @assert ctx.textRendering === "optimizeSpeed"; 3128 3129 - name: 2d.text.drawing.style.fontStretch.settings 3130 desc: Testing value setting of fontStretch in Canvas 3131 code: | 3132 // Setting fontStretch with lower cases 3133 ctx.fontStretch = "ultra-condensed"; 3134 @assert ctx.fontStretch === "ultra-condensed"; 3135 3136 ctx.fontStretch = "extra-condensed"; 3137 @assert ctx.fontStretch === "extra-condensed"; 3138 3139 ctx.fontStretch = "condensed"; 3140 @assert ctx.fontStretch === "condensed"; 3141 3142 ctx.fontStretch = "semi-condensed"; 3143 @assert ctx.fontStretch === "semi-condensed"; 3144 3145 ctx.fontStretch = "normal"; 3146 @assert ctx.fontStretch === "normal"; 3147 3148 ctx.fontStretch = "semi-expanded"; 3149 @assert ctx.fontStretch === "semi-expanded"; 3150 3151 ctx.fontStretch = "expanded"; 3152 @assert ctx.fontStretch === "expanded"; 3153 3154 ctx.fontStretch = "extra-expanded"; 3155 @assert ctx.fontStretch === "extra-expanded"; 3156 3157 ctx.fontStretch = "ultra-expanded"; 3158 @assert ctx.fontStretch === "ultra-expanded"; 3159 3160 // Setting fontStretch with lower cases and upper cases word, 3161 // these values should be ignored. 3162 ctx.fontStretch = "ulTra-condensed"; 3163 @assert ctx.fontStretch === "ultra-expanded"; 3164 3165 ctx.fontStretch = "Extra-condensed"; 3166 @assert ctx.fontStretch === "ultra-expanded"; 3167 3168 ctx.fontStretch = "cOndensed"; 3169 @assert ctx.fontStretch === "ultra-expanded"; 3170 3171 ctx.fontStretch = "Semi-Condensed"; 3172 @assert ctx.fontStretch === "ultra-expanded"; 3173 3174 ctx.fontStretch = "normaL"; 3175 @assert ctx.fontStretch === "ultra-expanded"; 3176 3177 ctx.fontStretch = "semi-Expanded"; 3178 @assert ctx.fontStretch === "ultra-expanded"; 3179 3180 ctx.fontStretch = "Expanded"; 3181 @assert ctx.fontStretch === "ultra-expanded"; 3182 3183 ctx.fontStretch = "eXtra-expanded"; 3184 @assert ctx.fontStretch === "ultra-expanded"; 3185 3186 ctx.fontStretch = "abcd"; 3187 @assert ctx.fontStretch === "ultra-expanded"; 3188 3189 - name: 2d.text.fontVariantCaps1 3190 desc: Testing small caps setting in fontVariant 3191 code: | 3192 ctx.font = "32px serif"; 3193 ctx.fontVariantCaps = "small-caps"; 3194 // This should render the same as font = "small-caps 32px serif". 3195 ctx.fillText("Hello World", 20, 100); 3196 reference: | 3197 ctx.font = "small-caps 32px serif"; 3198 ctx.fillText("Hello World", 20, 100); 3199 3200 - name: 2d.text.fontVariantCaps2 3201 desc: Testing small caps setting in fontVariant 3202 code: | 3203 ctx.font = "small-caps 32px serif"; 3204 // "mismatch" test, to verify that small-caps does change the rendering. 3205 smallCaps_len = ctx.measureText("Hello World").width; 3206 3207 ctx.font = "32px serif"; 3208 normalCaps_len = ctx.measureText("Hello World").width; 3209 @assert smallCaps_len != normalCaps_len; 3210 3211 - name: 2d.text.fontVariantCaps3 3212 desc: Testing small caps setting in fontVariant 3213 code: | 3214 ctx.font = "32px serif"; 3215 ctx.fontVariantCaps = "all-small-caps"; 3216 // This should render the same as using font = "small-caps 32px serif" 3217 // with all the underlying text in lowercase. 3218 ctx.fillText("Hello World", 20, 100); 3219 reference: | 3220 ctx.font = "small-caps 32px serif"; 3221 ctx.fillText("hello world", 20, 100); 3222 3223 - name: 2d.text.fontVariantCaps4 3224 desc: Testing small caps setting in fontVariant 3225 code: | 3226 ctx.font = "small-caps 32px serif"; 3227 // fontVariantCaps overrides the small-caps setting from the font attribute 3228 // (spec unclear, cf. https://github.com/whatwg/html/issues/8103) 3229 ctx.fontVariantCaps = "all-small-caps"; 3230 ctx.fillText("Hello World", 20, 100); 3231 reference: | 3232 ctx.font = "small-caps 32px serif"; 3233 ctx.fillText("hello world", 20, 100); 3234 3235 - name: 2d.text.fontVariantCaps5 3236 desc: Testing small caps setting in fontVariant 3237 code: | 3238 ctx.font = "small-caps 32px serif"; 3239 // fontVariantCaps 'normal' does not override the setting from the font attribute. 3240 // (spec unclear, cf. https://github.com/whatwg/html/issues/8103) 3241 ctx.fontVariantCaps = "normal"; 3242 ctx.fillText("Hello World", 20, 100); 3243 reference: | 3244 ctx.font = "small-caps 32px serif"; 3245 ctx.fillText("Hello World", 20, 100); 3246 3247 - name: 2d.text.fontVariantCaps6 3248 desc: Testing small caps setting in fontVariant 3249 code: | 3250 // fontVariantCaps is reset when the font attribute is set. 3251 // (spec unclear, cf. https://github.com/whatwg/html/issues/8103) 3252 ctx.fontVariantCaps = "all-small-caps"; 3253 ctx.font = "32px serif"; 3254 ctx.fillText("Hello World", 20, 100); 3255 reference: | 3256 ctx.font = "32px serif"; 3257 ctx.fillText("Hello World", 20, 100); 3258 3259 - name: 2d.text.fontVariantCaps.after.reset.font 3260 desc: Testing if the fontVariantCaps is reset after font change 3261 size: [300, 300] 3262 code: | 3263 ctx.font = "32px serif"; 3264 ctx.fontVariantCaps = "small-caps"; 3265 3266 ctx.font = "31px serif"; 3267 ctx.fillText("Hello World", 20, 40); 3268 ctx.fontVariantCaps = "small-caps"; 3269 ctx.fillText("Hello World", 20, 80); 3270 reference: | 3271 ctx.font = "31px serif"; 3272 ctx.fillText("Hello World", 20, 40); 3273 ctx.fontVariantCaps = "small-caps"; 3274 ctx.fillText("Hello World", 20, 80); 3275 3276 - name: 2d.text.setFont.mathFont 3277 desc: crbug.com/1212190, make sure offscreencanvas doesn't crash with Math Font 3278 code: | 3279 ctx.font = "math serif"; 3280 3281 - name: 2d.text.writingmode 3282 desc: writing-mode in css should not change how text is rendered 3283 canvas_types: ['HtmlCanvas'] 3284 size: [300, 300] 3285 code: | 3286 canvas.style.textOrientation = "upright"; 3287 canvas.style.writingMode = "vertical-rl"; 3288 canvas.style.fontFamily = "Arial"; 3289 3290 ctx.font = "bold 64px Arial"; 3291 ctx.textBaseline = "top"; 3292 3293 ctx.fillText("Happy", 100, 0); 3294 reference: | 3295 ctx.font = "bold 64px Arial"; 3296 ctx.textBaseline = "top"; 3297 3298 ctx.fillText("Happy", 100, 0); 3299 3300 # TODO: shadows, alpha, composite, clip