tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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