tor-browser

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

2d.text.measure.selection-rects.tentative.html (86967B)


      1 <!DOCTYPE html>
      2 <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
      3 <meta charset="UTF-8">
      4 <title>Canvas test: 2d.text.measure.selection-rects.tentative</title>
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="/html/canvas/resources/canvas-tests.js"></script>
      8 <link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
      9 
     10 <h1>2d.text.measure.selection-rects.tentative</h1>
     11 
     12 <script>
     13 
     14 test(t => {
     15  const canvas = document.createElement('canvas');
     16  canvas.width = 100;
     17  canvas.height = 50;
     18  const ctx = canvas.getContext('2d');
     19 
     20  function placeAndSelectTextInDOM(text, from, to) {
     21    const el = document.createElement("div");
     22    el.innerHTML = text;
     23    el.style.font = '50px sans-serif';
     24    el.style.direction = 'ltr';
     25    el.style.textAlign = 'left';
     26    el.style.letterSpacing = '0px';
     27    document.body.appendChild(el);
     28 
     29    let range = document.createRange();
     30 
     31    range.setStart(el.childNodes[0], 0);
     32    range.setEnd(el.childNodes[0], text.length);
     33    const parent = range.getClientRects()[0];
     34    let width = 0;
     35    for (const rect of range.getClientRects()) {
     36      width += rect.width;
     37    }
     38 
     39    range.setStart(el.childNodes[0], from);
     40    range.setEnd(el.childNodes[0], to);
     41 
     42    let sel = window.getSelection();
     43    sel.removeAllRanges();
     44    sel.addRange(range);
     45 
     46    let sel_rects = sel.getRangeAt(0).getClientRects();
     47 
     48    document.body.removeChild(el);
     49 
     50    // Offset to the alignment point determined by textAlign.
     51    let text_align_dx;
     52    switch (el.style.textAlign) {
     53      case 'right':
     54        text_align_dx = width;
     55        break;
     56      case 'center':
     57        text_align_dx = width / 2;
     58        break;
     59      default:
     60        text_align_dx = 0;
     61    }
     62 
     63    for(let i = 0 ; i < sel_rects.length ; ++i) {
     64      sel_rects[i].x -= parent.x + text_align_dx;
     65      sel_rects[i].y -= parent.y;
     66    }
     67 
     68    return sel_rects;
     69  }
     70 
     71  function checkRectListsMatch(list_a, list_b) {
     72    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
     73    for(let i = 0 ; i < list_a.length ; ++i) {
     74      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
     75      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
     76      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
     77      // Y-position not tested here as getting the baseline for text in the
     78      // DOM is not straightforward.
     79    }
     80  }
     81 
     82  ctx.font = '50px sans-serif';
     83  ctx.direction = 'ltr';
     84  ctx.textAlign = 'left';
     85  ctx.letterSpacing = '0px';
     86 
     87  const kTexts = [
     88    'UNAVAILABLE',
     89    '🏁🎶🏁',
     90    ')(あ)(',
     91    '-abcd_',
     92    'איפה הספרייה?',
     93    'bidiמתמטיקה'
     94  ]
     95 
     96  for (text of kTexts) {
     97    const tm = ctx.measureText(text);
     98    // First character.
     99    checkRectListsMatch(
    100      tm.getSelectionRects(0, 1),
    101      placeAndSelectTextInDOM(text, 0, 1)
    102    );
    103    // Last character.
    104    checkRectListsMatch(
    105      tm.getSelectionRects(text.length - 1, text.length),
    106      placeAndSelectTextInDOM(text, text.length - 1, text.length)
    107    );
    108    // Whole string.
    109    checkRectListsMatch(
    110      tm.getSelectionRects(0, text.length),
    111      placeAndSelectTextInDOM(text, 0, text.length)
    112    );
    113    // Intermediate string.
    114    checkRectListsMatch(
    115      tm.getSelectionRects(1, text.length - 1),
    116      placeAndSelectTextInDOM(text, 1, text.length - 1)
    117    );
    118    // Invalid start > end string. Creates 0 width rectangle.
    119    checkRectListsMatch(
    120      tm.getSelectionRects(3, 2),
    121      placeAndSelectTextInDOM(text, 3, 2)
    122    );
    123    checkRectListsMatch(
    124      tm.getSelectionRects(1, 0),
    125      placeAndSelectTextInDOM(text, 1, 0)
    126    );
    127  }
    128 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override.");
    129 
    130 test(t => {
    131  const canvas = document.createElement('canvas');
    132  canvas.width = 100;
    133  canvas.height = 50;
    134  const ctx = canvas.getContext('2d');
    135 
    136  function placeAndSelectTextInDOM(text, from, to) {
    137    const el = document.createElement("div");
    138    el.innerHTML = text;
    139    el.style.font = '50px sans-serif';
    140    el.style.direction = 'rtl';
    141    el.style.textAlign = 'left';
    142    el.style.letterSpacing = '0px';
    143    document.body.appendChild(el);
    144 
    145    let range = document.createRange();
    146 
    147    range.setStart(el.childNodes[0], 0);
    148    range.setEnd(el.childNodes[0], text.length);
    149    const parent = range.getClientRects()[0];
    150    let width = 0;
    151    for (const rect of range.getClientRects()) {
    152      width += rect.width;
    153    }
    154 
    155    range.setStart(el.childNodes[0], from);
    156    range.setEnd(el.childNodes[0], to);
    157 
    158    let sel = window.getSelection();
    159    sel.removeAllRanges();
    160    sel.addRange(range);
    161 
    162    let sel_rects = sel.getRangeAt(0).getClientRects();
    163 
    164    document.body.removeChild(el);
    165 
    166    // Offset to the alignment point determined by textAlign.
    167    let text_align_dx;
    168    switch (el.style.textAlign) {
    169      case 'right':
    170        text_align_dx = width;
    171        break;
    172      case 'center':
    173        text_align_dx = width / 2;
    174        break;
    175      default:
    176        text_align_dx = 0;
    177    }
    178 
    179    for(let i = 0 ; i < sel_rects.length ; ++i) {
    180      sel_rects[i].x -= parent.x + text_align_dx;
    181      sel_rects[i].y -= parent.y;
    182    }
    183 
    184    return sel_rects;
    185  }
    186 
    187  function checkRectListsMatch(list_a, list_b) {
    188    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
    189    for(let i = 0 ; i < list_a.length ; ++i) {
    190      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
    191      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
    192      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
    193      // Y-position not tested here as getting the baseline for text in the
    194      // DOM is not straightforward.
    195    }
    196  }
    197 
    198  ctx.font = '50px sans-serif';
    199  ctx.direction = 'rtl';
    200  ctx.textAlign = 'left';
    201  ctx.letterSpacing = '0px';
    202 
    203  const kTexts = [
    204    'UNAVAILABLE',
    205    '🏁🎶🏁',
    206    ')(あ)(',
    207    '-abcd_',
    208    'איפה הספרייה?',
    209    'bidiמתמטיקה'
    210  ]
    211 
    212  for (text of kTexts) {
    213    const tm = ctx.measureText(text);
    214    // First character.
    215    checkRectListsMatch(
    216      tm.getSelectionRects(0, 1),
    217      placeAndSelectTextInDOM(text, 0, 1)
    218    );
    219    // Last character.
    220    checkRectListsMatch(
    221      tm.getSelectionRects(text.length - 1, text.length),
    222      placeAndSelectTextInDOM(text, text.length - 1, text.length)
    223    );
    224    // Whole string.
    225    checkRectListsMatch(
    226      tm.getSelectionRects(0, text.length),
    227      placeAndSelectTextInDOM(text, 0, text.length)
    228    );
    229    // Intermediate string.
    230    checkRectListsMatch(
    231      tm.getSelectionRects(1, text.length - 1),
    232      placeAndSelectTextInDOM(text, 1, text.length - 1)
    233    );
    234    // Invalid start > end string. Creates 0 width rectangle.
    235    checkRectListsMatch(
    236      tm.getSelectionRects(3, 2),
    237      placeAndSelectTextInDOM(text, 3, 2)
    238    );
    239    checkRectListsMatch(
    240      tm.getSelectionRects(1, 0),
    241      placeAndSelectTextInDOM(text, 1, 0)
    242    );
    243  }
    244 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override.");
    245 
    246 test(t => {
    247  const canvas = document.createElement('canvas');
    248  canvas.width = 100;
    249  canvas.height = 50;
    250  const ctx = canvas.getContext('2d');
    251 
    252  function placeAndSelectTextInDOM(text, from, to) {
    253    const el = document.createElement("div");
    254    el.innerHTML = text;
    255    el.style.font = '50px sans-serif';
    256    el.style.direction = 'ltr';
    257    el.style.textAlign = 'center';
    258    el.style.letterSpacing = '0px';
    259    document.body.appendChild(el);
    260 
    261    let range = document.createRange();
    262 
    263    range.setStart(el.childNodes[0], 0);
    264    range.setEnd(el.childNodes[0], text.length);
    265    const parent = range.getClientRects()[0];
    266    let width = 0;
    267    for (const rect of range.getClientRects()) {
    268      width += rect.width;
    269    }
    270 
    271    range.setStart(el.childNodes[0], from);
    272    range.setEnd(el.childNodes[0], to);
    273 
    274    let sel = window.getSelection();
    275    sel.removeAllRanges();
    276    sel.addRange(range);
    277 
    278    let sel_rects = sel.getRangeAt(0).getClientRects();
    279 
    280    document.body.removeChild(el);
    281 
    282    // Offset to the alignment point determined by textAlign.
    283    let text_align_dx;
    284    switch (el.style.textAlign) {
    285      case 'right':
    286        text_align_dx = width;
    287        break;
    288      case 'center':
    289        text_align_dx = width / 2;
    290        break;
    291      default:
    292        text_align_dx = 0;
    293    }
    294 
    295    for(let i = 0 ; i < sel_rects.length ; ++i) {
    296      sel_rects[i].x -= parent.x + text_align_dx;
    297      sel_rects[i].y -= parent.y;
    298    }
    299 
    300    return sel_rects;
    301  }
    302 
    303  function checkRectListsMatch(list_a, list_b) {
    304    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
    305    for(let i = 0 ; i < list_a.length ; ++i) {
    306      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
    307      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
    308      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
    309      // Y-position not tested here as getting the baseline for text in the
    310      // DOM is not straightforward.
    311    }
    312  }
    313 
    314  ctx.font = '50px sans-serif';
    315  ctx.direction = 'ltr';
    316  ctx.textAlign = 'center';
    317  ctx.letterSpacing = '0px';
    318 
    319  const kTexts = [
    320    'UNAVAILABLE',
    321    '🏁🎶🏁',
    322    ')(あ)(',
    323    '-abcd_',
    324    'איפה הספרייה?',
    325    'bidiמתמטיקה'
    326  ]
    327 
    328  for (text of kTexts) {
    329    const tm = ctx.measureText(text);
    330    // First character.
    331    checkRectListsMatch(
    332      tm.getSelectionRects(0, 1),
    333      placeAndSelectTextInDOM(text, 0, 1)
    334    );
    335    // Last character.
    336    checkRectListsMatch(
    337      tm.getSelectionRects(text.length - 1, text.length),
    338      placeAndSelectTextInDOM(text, text.length - 1, text.length)
    339    );
    340    // Whole string.
    341    checkRectListsMatch(
    342      tm.getSelectionRects(0, text.length),
    343      placeAndSelectTextInDOM(text, 0, text.length)
    344    );
    345    // Intermediate string.
    346    checkRectListsMatch(
    347      tm.getSelectionRects(1, text.length - 1),
    348      placeAndSelectTextInDOM(text, 1, text.length - 1)
    349    );
    350    // Invalid start > end string. Creates 0 width rectangle.
    351    checkRectListsMatch(
    352      tm.getSelectionRects(3, 2),
    353      placeAndSelectTextInDOM(text, 3, 2)
    354    );
    355    checkRectListsMatch(
    356      tm.getSelectionRects(1, 0),
    357      placeAndSelectTextInDOM(text, 1, 0)
    358    );
    359  }
    360 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override.");
    361 
    362 test(t => {
    363  const canvas = document.createElement('canvas');
    364  canvas.width = 100;
    365  canvas.height = 50;
    366  const ctx = canvas.getContext('2d');
    367 
    368  function placeAndSelectTextInDOM(text, from, to) {
    369    const el = document.createElement("div");
    370    el.innerHTML = text;
    371    el.style.font = '50px sans-serif';
    372    el.style.direction = 'rtl';
    373    el.style.textAlign = 'center';
    374    el.style.letterSpacing = '0px';
    375    document.body.appendChild(el);
    376 
    377    let range = document.createRange();
    378 
    379    range.setStart(el.childNodes[0], 0);
    380    range.setEnd(el.childNodes[0], text.length);
    381    const parent = range.getClientRects()[0];
    382    let width = 0;
    383    for (const rect of range.getClientRects()) {
    384      width += rect.width;
    385    }
    386 
    387    range.setStart(el.childNodes[0], from);
    388    range.setEnd(el.childNodes[0], to);
    389 
    390    let sel = window.getSelection();
    391    sel.removeAllRanges();
    392    sel.addRange(range);
    393 
    394    let sel_rects = sel.getRangeAt(0).getClientRects();
    395 
    396    document.body.removeChild(el);
    397 
    398    // Offset to the alignment point determined by textAlign.
    399    let text_align_dx;
    400    switch (el.style.textAlign) {
    401      case 'right':
    402        text_align_dx = width;
    403        break;
    404      case 'center':
    405        text_align_dx = width / 2;
    406        break;
    407      default:
    408        text_align_dx = 0;
    409    }
    410 
    411    for(let i = 0 ; i < sel_rects.length ; ++i) {
    412      sel_rects[i].x -= parent.x + text_align_dx;
    413      sel_rects[i].y -= parent.y;
    414    }
    415 
    416    return sel_rects;
    417  }
    418 
    419  function checkRectListsMatch(list_a, list_b) {
    420    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
    421    for(let i = 0 ; i < list_a.length ; ++i) {
    422      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
    423      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
    424      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
    425      // Y-position not tested here as getting the baseline for text in the
    426      // DOM is not straightforward.
    427    }
    428  }
    429 
    430  ctx.font = '50px sans-serif';
    431  ctx.direction = 'rtl';
    432  ctx.textAlign = 'center';
    433  ctx.letterSpacing = '0px';
    434 
    435  const kTexts = [
    436    'UNAVAILABLE',
    437    '🏁🎶🏁',
    438    ')(あ)(',
    439    '-abcd_',
    440    'איפה הספרייה?',
    441    'bidiמתמטיקה'
    442  ]
    443 
    444  for (text of kTexts) {
    445    const tm = ctx.measureText(text);
    446    // First character.
    447    checkRectListsMatch(
    448      tm.getSelectionRects(0, 1),
    449      placeAndSelectTextInDOM(text, 0, 1)
    450    );
    451    // Last character.
    452    checkRectListsMatch(
    453      tm.getSelectionRects(text.length - 1, text.length),
    454      placeAndSelectTextInDOM(text, text.length - 1, text.length)
    455    );
    456    // Whole string.
    457    checkRectListsMatch(
    458      tm.getSelectionRects(0, text.length),
    459      placeAndSelectTextInDOM(text, 0, text.length)
    460    );
    461    // Intermediate string.
    462    checkRectListsMatch(
    463      tm.getSelectionRects(1, text.length - 1),
    464      placeAndSelectTextInDOM(text, 1, text.length - 1)
    465    );
    466    // Invalid start > end string. Creates 0 width rectangle.
    467    checkRectListsMatch(
    468      tm.getSelectionRects(3, 2),
    469      placeAndSelectTextInDOM(text, 3, 2)
    470    );
    471    checkRectListsMatch(
    472      tm.getSelectionRects(1, 0),
    473      placeAndSelectTextInDOM(text, 1, 0)
    474    );
    475  }
    476 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override.");
    477 
    478 test(t => {
    479  const canvas = document.createElement('canvas');
    480  canvas.width = 100;
    481  canvas.height = 50;
    482  const ctx = canvas.getContext('2d');
    483 
    484  function placeAndSelectTextInDOM(text, from, to) {
    485    const el = document.createElement("div");
    486    el.innerHTML = text;
    487    el.style.font = '50px sans-serif';
    488    el.style.direction = 'ltr';
    489    el.style.textAlign = 'right';
    490    el.style.letterSpacing = '0px';
    491    document.body.appendChild(el);
    492 
    493    let range = document.createRange();
    494 
    495    range.setStart(el.childNodes[0], 0);
    496    range.setEnd(el.childNodes[0], text.length);
    497    const parent = range.getClientRects()[0];
    498    let width = 0;
    499    for (const rect of range.getClientRects()) {
    500      width += rect.width;
    501    }
    502 
    503    range.setStart(el.childNodes[0], from);
    504    range.setEnd(el.childNodes[0], to);
    505 
    506    let sel = window.getSelection();
    507    sel.removeAllRanges();
    508    sel.addRange(range);
    509 
    510    let sel_rects = sel.getRangeAt(0).getClientRects();
    511 
    512    document.body.removeChild(el);
    513 
    514    // Offset to the alignment point determined by textAlign.
    515    let text_align_dx;
    516    switch (el.style.textAlign) {
    517      case 'right':
    518        text_align_dx = width;
    519        break;
    520      case 'center':
    521        text_align_dx = width / 2;
    522        break;
    523      default:
    524        text_align_dx = 0;
    525    }
    526 
    527    for(let i = 0 ; i < sel_rects.length ; ++i) {
    528      sel_rects[i].x -= parent.x + text_align_dx;
    529      sel_rects[i].y -= parent.y;
    530    }
    531 
    532    return sel_rects;
    533  }
    534 
    535  function checkRectListsMatch(list_a, list_b) {
    536    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
    537    for(let i = 0 ; i < list_a.length ; ++i) {
    538      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
    539      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
    540      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
    541      // Y-position not tested here as getting the baseline for text in the
    542      // DOM is not straightforward.
    543    }
    544  }
    545 
    546  ctx.font = '50px sans-serif';
    547  ctx.direction = 'ltr';
    548  ctx.textAlign = 'right';
    549  ctx.letterSpacing = '0px';
    550 
    551  const kTexts = [
    552    'UNAVAILABLE',
    553    '🏁🎶🏁',
    554    ')(あ)(',
    555    '-abcd_',
    556    'איפה הספרייה?',
    557    'bidiמתמטיקה'
    558  ]
    559 
    560  for (text of kTexts) {
    561    const tm = ctx.measureText(text);
    562    // First character.
    563    checkRectListsMatch(
    564      tm.getSelectionRects(0, 1),
    565      placeAndSelectTextInDOM(text, 0, 1)
    566    );
    567    // Last character.
    568    checkRectListsMatch(
    569      tm.getSelectionRects(text.length - 1, text.length),
    570      placeAndSelectTextInDOM(text, text.length - 1, text.length)
    571    );
    572    // Whole string.
    573    checkRectListsMatch(
    574      tm.getSelectionRects(0, text.length),
    575      placeAndSelectTextInDOM(text, 0, text.length)
    576    );
    577    // Intermediate string.
    578    checkRectListsMatch(
    579      tm.getSelectionRects(1, text.length - 1),
    580      placeAndSelectTextInDOM(text, 1, text.length - 1)
    581    );
    582    // Invalid start > end string. Creates 0 width rectangle.
    583    checkRectListsMatch(
    584      tm.getSelectionRects(3, 2),
    585      placeAndSelectTextInDOM(text, 3, 2)
    586    );
    587    checkRectListsMatch(
    588      tm.getSelectionRects(1, 0),
    589      placeAndSelectTextInDOM(text, 1, 0)
    590    );
    591  }
    592 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override.");
    593 
    594 test(t => {
    595  const canvas = document.createElement('canvas');
    596  canvas.width = 100;
    597  canvas.height = 50;
    598  const ctx = canvas.getContext('2d');
    599 
    600  function placeAndSelectTextInDOM(text, from, to) {
    601    const el = document.createElement("div");
    602    el.innerHTML = text;
    603    el.style.font = '50px sans-serif';
    604    el.style.direction = 'rtl';
    605    el.style.textAlign = 'right';
    606    el.style.letterSpacing = '0px';
    607    document.body.appendChild(el);
    608 
    609    let range = document.createRange();
    610 
    611    range.setStart(el.childNodes[0], 0);
    612    range.setEnd(el.childNodes[0], text.length);
    613    const parent = range.getClientRects()[0];
    614    let width = 0;
    615    for (const rect of range.getClientRects()) {
    616      width += rect.width;
    617    }
    618 
    619    range.setStart(el.childNodes[0], from);
    620    range.setEnd(el.childNodes[0], to);
    621 
    622    let sel = window.getSelection();
    623    sel.removeAllRanges();
    624    sel.addRange(range);
    625 
    626    let sel_rects = sel.getRangeAt(0).getClientRects();
    627 
    628    document.body.removeChild(el);
    629 
    630    // Offset to the alignment point determined by textAlign.
    631    let text_align_dx;
    632    switch (el.style.textAlign) {
    633      case 'right':
    634        text_align_dx = width;
    635        break;
    636      case 'center':
    637        text_align_dx = width / 2;
    638        break;
    639      default:
    640        text_align_dx = 0;
    641    }
    642 
    643    for(let i = 0 ; i < sel_rects.length ; ++i) {
    644      sel_rects[i].x -= parent.x + text_align_dx;
    645      sel_rects[i].y -= parent.y;
    646    }
    647 
    648    return sel_rects;
    649  }
    650 
    651  function checkRectListsMatch(list_a, list_b) {
    652    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
    653    for(let i = 0 ; i < list_a.length ; ++i) {
    654      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
    655      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
    656      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
    657      // Y-position not tested here as getting the baseline for text in the
    658      // DOM is not straightforward.
    659    }
    660  }
    661 
    662  ctx.font = '50px sans-serif';
    663  ctx.direction = 'rtl';
    664  ctx.textAlign = 'right';
    665  ctx.letterSpacing = '0px';
    666 
    667  const kTexts = [
    668    'UNAVAILABLE',
    669    '🏁🎶🏁',
    670    ')(あ)(',
    671    '-abcd_',
    672    'איפה הספרייה?',
    673    'bidiמתמטיקה'
    674  ]
    675 
    676  for (text of kTexts) {
    677    const tm = ctx.measureText(text);
    678    // First character.
    679    checkRectListsMatch(
    680      tm.getSelectionRects(0, 1),
    681      placeAndSelectTextInDOM(text, 0, 1)
    682    );
    683    // Last character.
    684    checkRectListsMatch(
    685      tm.getSelectionRects(text.length - 1, text.length),
    686      placeAndSelectTextInDOM(text, text.length - 1, text.length)
    687    );
    688    // Whole string.
    689    checkRectListsMatch(
    690      tm.getSelectionRects(0, text.length),
    691      placeAndSelectTextInDOM(text, 0, text.length)
    692    );
    693    // Intermediate string.
    694    checkRectListsMatch(
    695      tm.getSelectionRects(1, text.length - 1),
    696      placeAndSelectTextInDOM(text, 1, text.length - 1)
    697    );
    698    // Invalid start > end string. Creates 0 width rectangle.
    699    checkRectListsMatch(
    700      tm.getSelectionRects(3, 2),
    701      placeAndSelectTextInDOM(text, 3, 2)
    702    );
    703    checkRectListsMatch(
    704      tm.getSelectionRects(1, 0),
    705      placeAndSelectTextInDOM(text, 1, 0)
    706    );
    707  }
    708 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override.");
    709 
    710 test(t => {
    711  const canvas = document.createElement('canvas');
    712  canvas.width = 100;
    713  canvas.height = 50;
    714  const ctx = canvas.getContext('2d');
    715 
    716  function placeAndSelectTextInDOM(text, from, to) {
    717    const el = document.createElement("div");
    718    el.innerHTML = text;
    719    el.style.font = '50px sans-serif';
    720    el.style.direction = 'ltr';
    721    el.style.textAlign = 'left';
    722    el.style.letterSpacing = '10px';
    723    document.body.appendChild(el);
    724 
    725    let range = document.createRange();
    726 
    727    range.setStart(el.childNodes[0], 0);
    728    range.setEnd(el.childNodes[0], text.length);
    729    const parent = range.getClientRects()[0];
    730    let width = 0;
    731    for (const rect of range.getClientRects()) {
    732      width += rect.width;
    733    }
    734 
    735    range.setStart(el.childNodes[0], from);
    736    range.setEnd(el.childNodes[0], to);
    737 
    738    let sel = window.getSelection();
    739    sel.removeAllRanges();
    740    sel.addRange(range);
    741 
    742    let sel_rects = sel.getRangeAt(0).getClientRects();
    743 
    744    document.body.removeChild(el);
    745 
    746    // Offset to the alignment point determined by textAlign.
    747    let text_align_dx;
    748    switch (el.style.textAlign) {
    749      case 'right':
    750        text_align_dx = width;
    751        break;
    752      case 'center':
    753        text_align_dx = width / 2;
    754        break;
    755      default:
    756        text_align_dx = 0;
    757    }
    758 
    759    for(let i = 0 ; i < sel_rects.length ; ++i) {
    760      sel_rects[i].x -= parent.x + text_align_dx;
    761      sel_rects[i].y -= parent.y;
    762    }
    763 
    764    return sel_rects;
    765  }
    766 
    767  function checkRectListsMatch(list_a, list_b) {
    768    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
    769    for(let i = 0 ; i < list_a.length ; ++i) {
    770      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
    771      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
    772      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
    773      // Y-position not tested here as getting the baseline for text in the
    774      // DOM is not straightforward.
    775    }
    776  }
    777 
    778  ctx.font = '50px sans-serif';
    779  ctx.direction = 'ltr';
    780  ctx.textAlign = 'left';
    781  ctx.letterSpacing = '10px';
    782 
    783  const kTexts = [
    784    'UNAVAILABLE',
    785    '🏁🎶🏁',
    786    ')(あ)(',
    787    '-abcd_',
    788    'איפה הספרייה?',
    789    'bidiמתמטיקה'
    790  ]
    791 
    792  for (text of kTexts) {
    793    const tm = ctx.measureText(text);
    794    // First character.
    795    checkRectListsMatch(
    796      tm.getSelectionRects(0, 1),
    797      placeAndSelectTextInDOM(text, 0, 1)
    798    );
    799    // Last character.
    800    checkRectListsMatch(
    801      tm.getSelectionRects(text.length - 1, text.length),
    802      placeAndSelectTextInDOM(text, text.length - 1, text.length)
    803    );
    804    // Whole string.
    805    checkRectListsMatch(
    806      tm.getSelectionRects(0, text.length),
    807      placeAndSelectTextInDOM(text, 0, text.length)
    808    );
    809    // Intermediate string.
    810    checkRectListsMatch(
    811      tm.getSelectionRects(1, text.length - 1),
    812      placeAndSelectTextInDOM(text, 1, text.length - 1)
    813    );
    814    // Invalid start > end string. Creates 0 width rectangle.
    815    checkRectListsMatch(
    816      tm.getSelectionRects(3, 2),
    817      placeAndSelectTextInDOM(text, 3, 2)
    818    );
    819    checkRectListsMatch(
    820      tm.getSelectionRects(1, 0),
    821      placeAndSelectTextInDOM(text, 1, 0)
    822    );
    823  }
    824 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override.");
    825 
    826 test(t => {
    827  const canvas = document.createElement('canvas');
    828  canvas.width = 100;
    829  canvas.height = 50;
    830  const ctx = canvas.getContext('2d');
    831 
    832  function placeAndSelectTextInDOM(text, from, to) {
    833    const el = document.createElement("div");
    834    el.innerHTML = text;
    835    el.style.font = '50px sans-serif';
    836    el.style.direction = 'rtl';
    837    el.style.textAlign = 'left';
    838    el.style.letterSpacing = '10px';
    839    document.body.appendChild(el);
    840 
    841    let range = document.createRange();
    842 
    843    range.setStart(el.childNodes[0], 0);
    844    range.setEnd(el.childNodes[0], text.length);
    845    const parent = range.getClientRects()[0];
    846    let width = 0;
    847    for (const rect of range.getClientRects()) {
    848      width += rect.width;
    849    }
    850 
    851    range.setStart(el.childNodes[0], from);
    852    range.setEnd(el.childNodes[0], to);
    853 
    854    let sel = window.getSelection();
    855    sel.removeAllRanges();
    856    sel.addRange(range);
    857 
    858    let sel_rects = sel.getRangeAt(0).getClientRects();
    859 
    860    document.body.removeChild(el);
    861 
    862    // Offset to the alignment point determined by textAlign.
    863    let text_align_dx;
    864    switch (el.style.textAlign) {
    865      case 'right':
    866        text_align_dx = width;
    867        break;
    868      case 'center':
    869        text_align_dx = width / 2;
    870        break;
    871      default:
    872        text_align_dx = 0;
    873    }
    874 
    875    for(let i = 0 ; i < sel_rects.length ; ++i) {
    876      sel_rects[i].x -= parent.x + text_align_dx;
    877      sel_rects[i].y -= parent.y;
    878    }
    879 
    880    return sel_rects;
    881  }
    882 
    883  function checkRectListsMatch(list_a, list_b) {
    884    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
    885    for(let i = 0 ; i < list_a.length ; ++i) {
    886      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
    887      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
    888      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
    889      // Y-position not tested here as getting the baseline for text in the
    890      // DOM is not straightforward.
    891    }
    892  }
    893 
    894  ctx.font = '50px sans-serif';
    895  ctx.direction = 'rtl';
    896  ctx.textAlign = 'left';
    897  ctx.letterSpacing = '10px';
    898 
    899  const kTexts = [
    900    'UNAVAILABLE',
    901    '🏁🎶🏁',
    902    ')(あ)(',
    903    '-abcd_',
    904    'איפה הספרייה?',
    905    'bidiמתמטיקה'
    906  ]
    907 
    908  for (text of kTexts) {
    909    const tm = ctx.measureText(text);
    910    // First character.
    911    checkRectListsMatch(
    912      tm.getSelectionRects(0, 1),
    913      placeAndSelectTextInDOM(text, 0, 1)
    914    );
    915    // Last character.
    916    checkRectListsMatch(
    917      tm.getSelectionRects(text.length - 1, text.length),
    918      placeAndSelectTextInDOM(text, text.length - 1, text.length)
    919    );
    920    // Whole string.
    921    checkRectListsMatch(
    922      tm.getSelectionRects(0, text.length),
    923      placeAndSelectTextInDOM(text, 0, text.length)
    924    );
    925    // Intermediate string.
    926    checkRectListsMatch(
    927      tm.getSelectionRects(1, text.length - 1),
    928      placeAndSelectTextInDOM(text, 1, text.length - 1)
    929    );
    930    // Invalid start > end string. Creates 0 width rectangle.
    931    checkRectListsMatch(
    932      tm.getSelectionRects(3, 2),
    933      placeAndSelectTextInDOM(text, 3, 2)
    934    );
    935    checkRectListsMatch(
    936      tm.getSelectionRects(1, 0),
    937      placeAndSelectTextInDOM(text, 1, 0)
    938    );
    939  }
    940 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override.");
    941 
    942 test(t => {
    943  const canvas = document.createElement('canvas');
    944  canvas.width = 100;
    945  canvas.height = 50;
    946  const ctx = canvas.getContext('2d');
    947 
    948  function placeAndSelectTextInDOM(text, from, to) {
    949    const el = document.createElement("div");
    950    el.innerHTML = text;
    951    el.style.font = '50px sans-serif';
    952    el.style.direction = 'ltr';
    953    el.style.textAlign = 'center';
    954    el.style.letterSpacing = '10px';
    955    document.body.appendChild(el);
    956 
    957    let range = document.createRange();
    958 
    959    range.setStart(el.childNodes[0], 0);
    960    range.setEnd(el.childNodes[0], text.length);
    961    const parent = range.getClientRects()[0];
    962    let width = 0;
    963    for (const rect of range.getClientRects()) {
    964      width += rect.width;
    965    }
    966 
    967    range.setStart(el.childNodes[0], from);
    968    range.setEnd(el.childNodes[0], to);
    969 
    970    let sel = window.getSelection();
    971    sel.removeAllRanges();
    972    sel.addRange(range);
    973 
    974    let sel_rects = sel.getRangeAt(0).getClientRects();
    975 
    976    document.body.removeChild(el);
    977 
    978    // Offset to the alignment point determined by textAlign.
    979    let text_align_dx;
    980    switch (el.style.textAlign) {
    981      case 'right':
    982        text_align_dx = width;
    983        break;
    984      case 'center':
    985        text_align_dx = width / 2;
    986        break;
    987      default:
    988        text_align_dx = 0;
    989    }
    990 
    991    for(let i = 0 ; i < sel_rects.length ; ++i) {
    992      sel_rects[i].x -= parent.x + text_align_dx;
    993      sel_rects[i].y -= parent.y;
    994    }
    995 
    996    return sel_rects;
    997  }
    998 
    999  function checkRectListsMatch(list_a, list_b) {
   1000    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1001    for(let i = 0 ; i < list_a.length ; ++i) {
   1002      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1003      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1004      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1005      // Y-position not tested here as getting the baseline for text in the
   1006      // DOM is not straightforward.
   1007    }
   1008  }
   1009 
   1010  ctx.font = '50px sans-serif';
   1011  ctx.direction = 'ltr';
   1012  ctx.textAlign = 'center';
   1013  ctx.letterSpacing = '10px';
   1014 
   1015  const kTexts = [
   1016    'UNAVAILABLE',
   1017    '🏁🎶🏁',
   1018    ')(あ)(',
   1019    '-abcd_',
   1020    'איפה הספרייה?',
   1021    'bidiמתמטיקה'
   1022  ]
   1023 
   1024  for (text of kTexts) {
   1025    const tm = ctx.measureText(text);
   1026    // First character.
   1027    checkRectListsMatch(
   1028      tm.getSelectionRects(0, 1),
   1029      placeAndSelectTextInDOM(text, 0, 1)
   1030    );
   1031    // Last character.
   1032    checkRectListsMatch(
   1033      tm.getSelectionRects(text.length - 1, text.length),
   1034      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   1035    );
   1036    // Whole string.
   1037    checkRectListsMatch(
   1038      tm.getSelectionRects(0, text.length),
   1039      placeAndSelectTextInDOM(text, 0, text.length)
   1040    );
   1041    // Intermediate string.
   1042    checkRectListsMatch(
   1043      tm.getSelectionRects(1, text.length - 1),
   1044      placeAndSelectTextInDOM(text, 1, text.length - 1)
   1045    );
   1046    // Invalid start > end string. Creates 0 width rectangle.
   1047    checkRectListsMatch(
   1048      tm.getSelectionRects(3, 2),
   1049      placeAndSelectTextInDOM(text, 3, 2)
   1050    );
   1051    checkRectListsMatch(
   1052      tm.getSelectionRects(1, 0),
   1053      placeAndSelectTextInDOM(text, 1, 0)
   1054    );
   1055  }
   1056 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override.");
   1057 
   1058 test(t => {
   1059  const canvas = document.createElement('canvas');
   1060  canvas.width = 100;
   1061  canvas.height = 50;
   1062  const ctx = canvas.getContext('2d');
   1063 
   1064  function placeAndSelectTextInDOM(text, from, to) {
   1065    const el = document.createElement("div");
   1066    el.innerHTML = text;
   1067    el.style.font = '50px sans-serif';
   1068    el.style.direction = 'rtl';
   1069    el.style.textAlign = 'center';
   1070    el.style.letterSpacing = '10px';
   1071    document.body.appendChild(el);
   1072 
   1073    let range = document.createRange();
   1074 
   1075    range.setStart(el.childNodes[0], 0);
   1076    range.setEnd(el.childNodes[0], text.length);
   1077    const parent = range.getClientRects()[0];
   1078    let width = 0;
   1079    for (const rect of range.getClientRects()) {
   1080      width += rect.width;
   1081    }
   1082 
   1083    range.setStart(el.childNodes[0], from);
   1084    range.setEnd(el.childNodes[0], to);
   1085 
   1086    let sel = window.getSelection();
   1087    sel.removeAllRanges();
   1088    sel.addRange(range);
   1089 
   1090    let sel_rects = sel.getRangeAt(0).getClientRects();
   1091 
   1092    document.body.removeChild(el);
   1093 
   1094    // Offset to the alignment point determined by textAlign.
   1095    let text_align_dx;
   1096    switch (el.style.textAlign) {
   1097      case 'right':
   1098        text_align_dx = width;
   1099        break;
   1100      case 'center':
   1101        text_align_dx = width / 2;
   1102        break;
   1103      default:
   1104        text_align_dx = 0;
   1105    }
   1106 
   1107    for(let i = 0 ; i < sel_rects.length ; ++i) {
   1108      sel_rects[i].x -= parent.x + text_align_dx;
   1109      sel_rects[i].y -= parent.y;
   1110    }
   1111 
   1112    return sel_rects;
   1113  }
   1114 
   1115  function checkRectListsMatch(list_a, list_b) {
   1116    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1117    for(let i = 0 ; i < list_a.length ; ++i) {
   1118      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1119      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1120      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1121      // Y-position not tested here as getting the baseline for text in the
   1122      // DOM is not straightforward.
   1123    }
   1124  }
   1125 
   1126  ctx.font = '50px sans-serif';
   1127  ctx.direction = 'rtl';
   1128  ctx.textAlign = 'center';
   1129  ctx.letterSpacing = '10px';
   1130 
   1131  const kTexts = [
   1132    'UNAVAILABLE',
   1133    '🏁🎶🏁',
   1134    ')(あ)(',
   1135    '-abcd_',
   1136    'איפה הספרייה?',
   1137    'bidiמתמטיקה'
   1138  ]
   1139 
   1140  for (text of kTexts) {
   1141    const tm = ctx.measureText(text);
   1142    // First character.
   1143    checkRectListsMatch(
   1144      tm.getSelectionRects(0, 1),
   1145      placeAndSelectTextInDOM(text, 0, 1)
   1146    );
   1147    // Last character.
   1148    checkRectListsMatch(
   1149      tm.getSelectionRects(text.length - 1, text.length),
   1150      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   1151    );
   1152    // Whole string.
   1153    checkRectListsMatch(
   1154      tm.getSelectionRects(0, text.length),
   1155      placeAndSelectTextInDOM(text, 0, text.length)
   1156    );
   1157    // Intermediate string.
   1158    checkRectListsMatch(
   1159      tm.getSelectionRects(1, text.length - 1),
   1160      placeAndSelectTextInDOM(text, 1, text.length - 1)
   1161    );
   1162    // Invalid start > end string. Creates 0 width rectangle.
   1163    checkRectListsMatch(
   1164      tm.getSelectionRects(3, 2),
   1165      placeAndSelectTextInDOM(text, 3, 2)
   1166    );
   1167    checkRectListsMatch(
   1168      tm.getSelectionRects(1, 0),
   1169      placeAndSelectTextInDOM(text, 1, 0)
   1170    );
   1171  }
   1172 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override.");
   1173 
   1174 test(t => {
   1175  const canvas = document.createElement('canvas');
   1176  canvas.width = 100;
   1177  canvas.height = 50;
   1178  const ctx = canvas.getContext('2d');
   1179 
   1180  function placeAndSelectTextInDOM(text, from, to) {
   1181    const el = document.createElement("div");
   1182    el.innerHTML = text;
   1183    el.style.font = '50px sans-serif';
   1184    el.style.direction = 'ltr';
   1185    el.style.textAlign = 'right';
   1186    el.style.letterSpacing = '10px';
   1187    document.body.appendChild(el);
   1188 
   1189    let range = document.createRange();
   1190 
   1191    range.setStart(el.childNodes[0], 0);
   1192    range.setEnd(el.childNodes[0], text.length);
   1193    const parent = range.getClientRects()[0];
   1194    let width = 0;
   1195    for (const rect of range.getClientRects()) {
   1196      width += rect.width;
   1197    }
   1198 
   1199    range.setStart(el.childNodes[0], from);
   1200    range.setEnd(el.childNodes[0], to);
   1201 
   1202    let sel = window.getSelection();
   1203    sel.removeAllRanges();
   1204    sel.addRange(range);
   1205 
   1206    let sel_rects = sel.getRangeAt(0).getClientRects();
   1207 
   1208    document.body.removeChild(el);
   1209 
   1210    // Offset to the alignment point determined by textAlign.
   1211    let text_align_dx;
   1212    switch (el.style.textAlign) {
   1213      case 'right':
   1214        text_align_dx = width;
   1215        break;
   1216      case 'center':
   1217        text_align_dx = width / 2;
   1218        break;
   1219      default:
   1220        text_align_dx = 0;
   1221    }
   1222 
   1223    for(let i = 0 ; i < sel_rects.length ; ++i) {
   1224      sel_rects[i].x -= parent.x + text_align_dx;
   1225      sel_rects[i].y -= parent.y;
   1226    }
   1227 
   1228    return sel_rects;
   1229  }
   1230 
   1231  function checkRectListsMatch(list_a, list_b) {
   1232    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1233    for(let i = 0 ; i < list_a.length ; ++i) {
   1234      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1235      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1236      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1237      // Y-position not tested here as getting the baseline for text in the
   1238      // DOM is not straightforward.
   1239    }
   1240  }
   1241 
   1242  ctx.font = '50px sans-serif';
   1243  ctx.direction = 'ltr';
   1244  ctx.textAlign = 'right';
   1245  ctx.letterSpacing = '10px';
   1246 
   1247  const kTexts = [
   1248    'UNAVAILABLE',
   1249    '🏁🎶🏁',
   1250    ')(あ)(',
   1251    '-abcd_',
   1252    'איפה הספרייה?',
   1253    'bidiמתמטיקה'
   1254  ]
   1255 
   1256  for (text of kTexts) {
   1257    const tm = ctx.measureText(text);
   1258    // First character.
   1259    checkRectListsMatch(
   1260      tm.getSelectionRects(0, 1),
   1261      placeAndSelectTextInDOM(text, 0, 1)
   1262    );
   1263    // Last character.
   1264    checkRectListsMatch(
   1265      tm.getSelectionRects(text.length - 1, text.length),
   1266      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   1267    );
   1268    // Whole string.
   1269    checkRectListsMatch(
   1270      tm.getSelectionRects(0, text.length),
   1271      placeAndSelectTextInDOM(text, 0, text.length)
   1272    );
   1273    // Intermediate string.
   1274    checkRectListsMatch(
   1275      tm.getSelectionRects(1, text.length - 1),
   1276      placeAndSelectTextInDOM(text, 1, text.length - 1)
   1277    );
   1278    // Invalid start > end string. Creates 0 width rectangle.
   1279    checkRectListsMatch(
   1280      tm.getSelectionRects(3, 2),
   1281      placeAndSelectTextInDOM(text, 3, 2)
   1282    );
   1283    checkRectListsMatch(
   1284      tm.getSelectionRects(1, 0),
   1285      placeAndSelectTextInDOM(text, 1, 0)
   1286    );
   1287  }
   1288 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override.");
   1289 
   1290 test(t => {
   1291  const canvas = document.createElement('canvas');
   1292  canvas.width = 100;
   1293  canvas.height = 50;
   1294  const ctx = canvas.getContext('2d');
   1295 
   1296  function placeAndSelectTextInDOM(text, from, to) {
   1297    const el = document.createElement("div");
   1298    el.innerHTML = text;
   1299    el.style.font = '50px sans-serif';
   1300    el.style.direction = 'rtl';
   1301    el.style.textAlign = 'right';
   1302    el.style.letterSpacing = '10px';
   1303    document.body.appendChild(el);
   1304 
   1305    let range = document.createRange();
   1306 
   1307    range.setStart(el.childNodes[0], 0);
   1308    range.setEnd(el.childNodes[0], text.length);
   1309    const parent = range.getClientRects()[0];
   1310    let width = 0;
   1311    for (const rect of range.getClientRects()) {
   1312      width += rect.width;
   1313    }
   1314 
   1315    range.setStart(el.childNodes[0], from);
   1316    range.setEnd(el.childNodes[0], to);
   1317 
   1318    let sel = window.getSelection();
   1319    sel.removeAllRanges();
   1320    sel.addRange(range);
   1321 
   1322    let sel_rects = sel.getRangeAt(0).getClientRects();
   1323 
   1324    document.body.removeChild(el);
   1325 
   1326    // Offset to the alignment point determined by textAlign.
   1327    let text_align_dx;
   1328    switch (el.style.textAlign) {
   1329      case 'right':
   1330        text_align_dx = width;
   1331        break;
   1332      case 'center':
   1333        text_align_dx = width / 2;
   1334        break;
   1335      default:
   1336        text_align_dx = 0;
   1337    }
   1338 
   1339    for(let i = 0 ; i < sel_rects.length ; ++i) {
   1340      sel_rects[i].x -= parent.x + text_align_dx;
   1341      sel_rects[i].y -= parent.y;
   1342    }
   1343 
   1344    return sel_rects;
   1345  }
   1346 
   1347  function checkRectListsMatch(list_a, list_b) {
   1348    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1349    for(let i = 0 ; i < list_a.length ; ++i) {
   1350      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1351      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1352      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1353      // Y-position not tested here as getting the baseline for text in the
   1354      // DOM is not straightforward.
   1355    }
   1356  }
   1357 
   1358  ctx.font = '50px sans-serif';
   1359  ctx.direction = 'rtl';
   1360  ctx.textAlign = 'right';
   1361  ctx.letterSpacing = '10px';
   1362 
   1363  const kTexts = [
   1364    'UNAVAILABLE',
   1365    '🏁🎶🏁',
   1366    ')(あ)(',
   1367    '-abcd_',
   1368    'איפה הספרייה?',
   1369    'bidiמתמטיקה'
   1370  ]
   1371 
   1372  for (text of kTexts) {
   1373    const tm = ctx.measureText(text);
   1374    // First character.
   1375    checkRectListsMatch(
   1376      tm.getSelectionRects(0, 1),
   1377      placeAndSelectTextInDOM(text, 0, 1)
   1378    );
   1379    // Last character.
   1380    checkRectListsMatch(
   1381      tm.getSelectionRects(text.length - 1, text.length),
   1382      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   1383    );
   1384    // Whole string.
   1385    checkRectListsMatch(
   1386      tm.getSelectionRects(0, text.length),
   1387      placeAndSelectTextInDOM(text, 0, text.length)
   1388    );
   1389    // Intermediate string.
   1390    checkRectListsMatch(
   1391      tm.getSelectionRects(1, text.length - 1),
   1392      placeAndSelectTextInDOM(text, 1, text.length - 1)
   1393    );
   1394    // Invalid start > end string. Creates 0 width rectangle.
   1395    checkRectListsMatch(
   1396      tm.getSelectionRects(3, 2),
   1397      placeAndSelectTextInDOM(text, 3, 2)
   1398    );
   1399    checkRectListsMatch(
   1400      tm.getSelectionRects(1, 0),
   1401      placeAndSelectTextInDOM(text, 1, 0)
   1402    );
   1403  }
   1404 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override.");
   1405 
   1406 test(t => {
   1407  const canvas = document.createElement('canvas');
   1408  canvas.width = 100;
   1409  canvas.height = 50;
   1410  const ctx = canvas.getContext('2d');
   1411 
   1412  function placeAndSelectTextInDOM(text, from, to) {
   1413    const el = document.createElement("div");
   1414    el.innerHTML = text;
   1415    el.style.font = '50px sans-serif';
   1416    el.style.direction = 'ltr';
   1417    el.style.textAlign = 'left';
   1418    el.style.letterSpacing = '0px';
   1419    document.body.appendChild(el);
   1420 
   1421    let range = document.createRange();
   1422 
   1423    range.setStart(el.childNodes[0], 0);
   1424    range.setEnd(el.childNodes[0], text.length);
   1425    const parent = range.getClientRects()[0];
   1426    let width = 0;
   1427    for (const rect of range.getClientRects()) {
   1428      width += rect.width;
   1429    }
   1430 
   1431    range.setStart(el.childNodes[0], from);
   1432    range.setEnd(el.childNodes[0], to);
   1433 
   1434    let sel = window.getSelection();
   1435    sel.removeAllRanges();
   1436    sel.addRange(range);
   1437 
   1438    let sel_rects = sel.getRangeAt(0).getClientRects();
   1439 
   1440    document.body.removeChild(el);
   1441 
   1442    // Offset to the alignment point determined by textAlign.
   1443    let text_align_dx;
   1444    switch (el.style.textAlign) {
   1445      case 'right':
   1446        text_align_dx = width;
   1447        break;
   1448      case 'center':
   1449        text_align_dx = width / 2;
   1450        break;
   1451      default:
   1452        text_align_dx = 0;
   1453    }
   1454 
   1455    for(let i = 0 ; i < sel_rects.length ; ++i) {
   1456      sel_rects[i].x -= parent.x + text_align_dx;
   1457      sel_rects[i].y -= parent.y;
   1458    }
   1459 
   1460    return sel_rects;
   1461  }
   1462 
   1463  function checkRectListsMatch(list_a, list_b) {
   1464    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1465    for(let i = 0 ; i < list_a.length ; ++i) {
   1466      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1467      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1468      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1469      // Y-position not tested here as getting the baseline for text in the
   1470      // DOM is not straightforward.
   1471    }
   1472  }
   1473 
   1474  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   1475    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   1476    const LTR_OVERRIDE = '\u202D';
   1477    const RTL_OVERRIDE = '\u202E';
   1478    const OVERRIDE_END = '\u202C';
   1479    if (direction_is_ltr) {
   1480      return LTR_OVERRIDE + text + OVERRIDE_END;
   1481    }
   1482    return RTL_OVERRIDE + text + OVERRIDE_END;
   1483  }
   1484 
   1485  ctx.font = '50px sans-serif';
   1486  ctx.direction = 'ltr';
   1487  ctx.textAlign = 'left';
   1488  ctx.letterSpacing = '0px';
   1489 
   1490  const kTexts = [
   1491    'UNAVAILABLE',
   1492    '🏁🎶🏁',
   1493    ')(あ)(',
   1494    '-abcd_',
   1495    'איפה הספרייה?',
   1496    'bidiמתמטיקה'
   1497  ]
   1498 
   1499  for (text of kTexts) {
   1500    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   1501    const tm = ctx.measureText(text);
   1502    // First character.
   1503    checkRectListsMatch(
   1504      tm.getSelectionRects(0, 1),
   1505      placeAndSelectTextInDOM(text, 0, 1)
   1506    );
   1507    // Last character.
   1508    checkRectListsMatch(
   1509      tm.getSelectionRects(text.length - 1, text.length),
   1510      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   1511    );
   1512    // Whole string.
   1513    checkRectListsMatch(
   1514      tm.getSelectionRects(0, text.length),
   1515      placeAndSelectTextInDOM(text, 0, text.length)
   1516    );
   1517    // Intermediate string.
   1518    checkRectListsMatch(
   1519      tm.getSelectionRects(1, text.length - 1),
   1520      placeAndSelectTextInDOM(text, 1, text.length - 1)
   1521    );
   1522    // Invalid start > end string. Creates 0 width rectangle.
   1523    checkRectListsMatch(
   1524      tm.getSelectionRects(3, 2),
   1525      placeAndSelectTextInDOM(text, 3, 2)
   1526    );
   1527    checkRectListsMatch(
   1528      tm.getSelectionRects(1, 0),
   1529      placeAndSelectTextInDOM(text, 1, 0)
   1530    );
   1531  }
   1532 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override.");
   1533 
   1534 test(t => {
   1535  const canvas = document.createElement('canvas');
   1536  canvas.width = 100;
   1537  canvas.height = 50;
   1538  const ctx = canvas.getContext('2d');
   1539 
   1540  function placeAndSelectTextInDOM(text, from, to) {
   1541    const el = document.createElement("div");
   1542    el.innerHTML = text;
   1543    el.style.font = '50px sans-serif';
   1544    el.style.direction = 'rtl';
   1545    el.style.textAlign = 'left';
   1546    el.style.letterSpacing = '0px';
   1547    document.body.appendChild(el);
   1548 
   1549    let range = document.createRange();
   1550 
   1551    range.setStart(el.childNodes[0], 0);
   1552    range.setEnd(el.childNodes[0], text.length);
   1553    const parent = range.getClientRects()[0];
   1554    let width = 0;
   1555    for (const rect of range.getClientRects()) {
   1556      width += rect.width;
   1557    }
   1558 
   1559    range.setStart(el.childNodes[0], from);
   1560    range.setEnd(el.childNodes[0], to);
   1561 
   1562    let sel = window.getSelection();
   1563    sel.removeAllRanges();
   1564    sel.addRange(range);
   1565 
   1566    let sel_rects = sel.getRangeAt(0).getClientRects();
   1567 
   1568    document.body.removeChild(el);
   1569 
   1570    // Offset to the alignment point determined by textAlign.
   1571    let text_align_dx;
   1572    switch (el.style.textAlign) {
   1573      case 'right':
   1574        text_align_dx = width;
   1575        break;
   1576      case 'center':
   1577        text_align_dx = width / 2;
   1578        break;
   1579      default:
   1580        text_align_dx = 0;
   1581    }
   1582 
   1583    for(let i = 0 ; i < sel_rects.length ; ++i) {
   1584      sel_rects[i].x -= parent.x + text_align_dx;
   1585      sel_rects[i].y -= parent.y;
   1586    }
   1587 
   1588    return sel_rects;
   1589  }
   1590 
   1591  function checkRectListsMatch(list_a, list_b) {
   1592    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1593    for(let i = 0 ; i < list_a.length ; ++i) {
   1594      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1595      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1596      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1597      // Y-position not tested here as getting the baseline for text in the
   1598      // DOM is not straightforward.
   1599    }
   1600  }
   1601 
   1602  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   1603    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   1604    const LTR_OVERRIDE = '\u202D';
   1605    const RTL_OVERRIDE = '\u202E';
   1606    const OVERRIDE_END = '\u202C';
   1607    if (direction_is_ltr) {
   1608      return LTR_OVERRIDE + text + OVERRIDE_END;
   1609    }
   1610    return RTL_OVERRIDE + text + OVERRIDE_END;
   1611  }
   1612 
   1613  ctx.font = '50px sans-serif';
   1614  ctx.direction = 'rtl';
   1615  ctx.textAlign = 'left';
   1616  ctx.letterSpacing = '0px';
   1617 
   1618  const kTexts = [
   1619    'UNAVAILABLE',
   1620    '🏁🎶🏁',
   1621    ')(あ)(',
   1622    '-abcd_',
   1623    'איפה הספרייה?',
   1624    'bidiמתמטיקה'
   1625  ]
   1626 
   1627  for (text of kTexts) {
   1628    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   1629    const tm = ctx.measureText(text);
   1630    // First character.
   1631    checkRectListsMatch(
   1632      tm.getSelectionRects(0, 1),
   1633      placeAndSelectTextInDOM(text, 0, 1)
   1634    );
   1635    // Last character.
   1636    checkRectListsMatch(
   1637      tm.getSelectionRects(text.length - 1, text.length),
   1638      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   1639    );
   1640    // Whole string.
   1641    checkRectListsMatch(
   1642      tm.getSelectionRects(0, text.length),
   1643      placeAndSelectTextInDOM(text, 0, text.length)
   1644    );
   1645    // Intermediate string.
   1646    checkRectListsMatch(
   1647      tm.getSelectionRects(1, text.length - 1),
   1648      placeAndSelectTextInDOM(text, 1, text.length - 1)
   1649    );
   1650    // Invalid start > end string. Creates 0 width rectangle.
   1651    checkRectListsMatch(
   1652      tm.getSelectionRects(3, 2),
   1653      placeAndSelectTextInDOM(text, 3, 2)
   1654    );
   1655    checkRectListsMatch(
   1656      tm.getSelectionRects(1, 0),
   1657      placeAndSelectTextInDOM(text, 1, 0)
   1658    );
   1659  }
   1660 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override.");
   1661 
   1662 test(t => {
   1663  const canvas = document.createElement('canvas');
   1664  canvas.width = 100;
   1665  canvas.height = 50;
   1666  const ctx = canvas.getContext('2d');
   1667 
   1668  function placeAndSelectTextInDOM(text, from, to) {
   1669    const el = document.createElement("div");
   1670    el.innerHTML = text;
   1671    el.style.font = '50px sans-serif';
   1672    el.style.direction = 'ltr';
   1673    el.style.textAlign = 'center';
   1674    el.style.letterSpacing = '0px';
   1675    document.body.appendChild(el);
   1676 
   1677    let range = document.createRange();
   1678 
   1679    range.setStart(el.childNodes[0], 0);
   1680    range.setEnd(el.childNodes[0], text.length);
   1681    const parent = range.getClientRects()[0];
   1682    let width = 0;
   1683    for (const rect of range.getClientRects()) {
   1684      width += rect.width;
   1685    }
   1686 
   1687    range.setStart(el.childNodes[0], from);
   1688    range.setEnd(el.childNodes[0], to);
   1689 
   1690    let sel = window.getSelection();
   1691    sel.removeAllRanges();
   1692    sel.addRange(range);
   1693 
   1694    let sel_rects = sel.getRangeAt(0).getClientRects();
   1695 
   1696    document.body.removeChild(el);
   1697 
   1698    // Offset to the alignment point determined by textAlign.
   1699    let text_align_dx;
   1700    switch (el.style.textAlign) {
   1701      case 'right':
   1702        text_align_dx = width;
   1703        break;
   1704      case 'center':
   1705        text_align_dx = width / 2;
   1706        break;
   1707      default:
   1708        text_align_dx = 0;
   1709    }
   1710 
   1711    for(let i = 0 ; i < sel_rects.length ; ++i) {
   1712      sel_rects[i].x -= parent.x + text_align_dx;
   1713      sel_rects[i].y -= parent.y;
   1714    }
   1715 
   1716    return sel_rects;
   1717  }
   1718 
   1719  function checkRectListsMatch(list_a, list_b) {
   1720    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1721    for(let i = 0 ; i < list_a.length ; ++i) {
   1722      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1723      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1724      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1725      // Y-position not tested here as getting the baseline for text in the
   1726      // DOM is not straightforward.
   1727    }
   1728  }
   1729 
   1730  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   1731    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   1732    const LTR_OVERRIDE = '\u202D';
   1733    const RTL_OVERRIDE = '\u202E';
   1734    const OVERRIDE_END = '\u202C';
   1735    if (direction_is_ltr) {
   1736      return LTR_OVERRIDE + text + OVERRIDE_END;
   1737    }
   1738    return RTL_OVERRIDE + text + OVERRIDE_END;
   1739  }
   1740 
   1741  ctx.font = '50px sans-serif';
   1742  ctx.direction = 'ltr';
   1743  ctx.textAlign = 'center';
   1744  ctx.letterSpacing = '0px';
   1745 
   1746  const kTexts = [
   1747    'UNAVAILABLE',
   1748    '🏁🎶🏁',
   1749    ')(あ)(',
   1750    '-abcd_',
   1751    'איפה הספרייה?',
   1752    'bidiמתמטיקה'
   1753  ]
   1754 
   1755  for (text of kTexts) {
   1756    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   1757    const tm = ctx.measureText(text);
   1758    // First character.
   1759    checkRectListsMatch(
   1760      tm.getSelectionRects(0, 1),
   1761      placeAndSelectTextInDOM(text, 0, 1)
   1762    );
   1763    // Last character.
   1764    checkRectListsMatch(
   1765      tm.getSelectionRects(text.length - 1, text.length),
   1766      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   1767    );
   1768    // Whole string.
   1769    checkRectListsMatch(
   1770      tm.getSelectionRects(0, text.length),
   1771      placeAndSelectTextInDOM(text, 0, text.length)
   1772    );
   1773    // Intermediate string.
   1774    checkRectListsMatch(
   1775      tm.getSelectionRects(1, text.length - 1),
   1776      placeAndSelectTextInDOM(text, 1, text.length - 1)
   1777    );
   1778    // Invalid start > end string. Creates 0 width rectangle.
   1779    checkRectListsMatch(
   1780      tm.getSelectionRects(3, 2),
   1781      placeAndSelectTextInDOM(text, 3, 2)
   1782    );
   1783    checkRectListsMatch(
   1784      tm.getSelectionRects(1, 0),
   1785      placeAndSelectTextInDOM(text, 1, 0)
   1786    );
   1787  }
   1788 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override.");
   1789 
   1790 test(t => {
   1791  const canvas = document.createElement('canvas');
   1792  canvas.width = 100;
   1793  canvas.height = 50;
   1794  const ctx = canvas.getContext('2d');
   1795 
   1796  function placeAndSelectTextInDOM(text, from, to) {
   1797    const el = document.createElement("div");
   1798    el.innerHTML = text;
   1799    el.style.font = '50px sans-serif';
   1800    el.style.direction = 'rtl';
   1801    el.style.textAlign = 'center';
   1802    el.style.letterSpacing = '0px';
   1803    document.body.appendChild(el);
   1804 
   1805    let range = document.createRange();
   1806 
   1807    range.setStart(el.childNodes[0], 0);
   1808    range.setEnd(el.childNodes[0], text.length);
   1809    const parent = range.getClientRects()[0];
   1810    let width = 0;
   1811    for (const rect of range.getClientRects()) {
   1812      width += rect.width;
   1813    }
   1814 
   1815    range.setStart(el.childNodes[0], from);
   1816    range.setEnd(el.childNodes[0], to);
   1817 
   1818    let sel = window.getSelection();
   1819    sel.removeAllRanges();
   1820    sel.addRange(range);
   1821 
   1822    let sel_rects = sel.getRangeAt(0).getClientRects();
   1823 
   1824    document.body.removeChild(el);
   1825 
   1826    // Offset to the alignment point determined by textAlign.
   1827    let text_align_dx;
   1828    switch (el.style.textAlign) {
   1829      case 'right':
   1830        text_align_dx = width;
   1831        break;
   1832      case 'center':
   1833        text_align_dx = width / 2;
   1834        break;
   1835      default:
   1836        text_align_dx = 0;
   1837    }
   1838 
   1839    for(let i = 0 ; i < sel_rects.length ; ++i) {
   1840      sel_rects[i].x -= parent.x + text_align_dx;
   1841      sel_rects[i].y -= parent.y;
   1842    }
   1843 
   1844    return sel_rects;
   1845  }
   1846 
   1847  function checkRectListsMatch(list_a, list_b) {
   1848    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1849    for(let i = 0 ; i < list_a.length ; ++i) {
   1850      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1851      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1852      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1853      // Y-position not tested here as getting the baseline for text in the
   1854      // DOM is not straightforward.
   1855    }
   1856  }
   1857 
   1858  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   1859    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   1860    const LTR_OVERRIDE = '\u202D';
   1861    const RTL_OVERRIDE = '\u202E';
   1862    const OVERRIDE_END = '\u202C';
   1863    if (direction_is_ltr) {
   1864      return LTR_OVERRIDE + text + OVERRIDE_END;
   1865    }
   1866    return RTL_OVERRIDE + text + OVERRIDE_END;
   1867  }
   1868 
   1869  ctx.font = '50px sans-serif';
   1870  ctx.direction = 'rtl';
   1871  ctx.textAlign = 'center';
   1872  ctx.letterSpacing = '0px';
   1873 
   1874  const kTexts = [
   1875    'UNAVAILABLE',
   1876    '🏁🎶🏁',
   1877    ')(あ)(',
   1878    '-abcd_',
   1879    'איפה הספרייה?',
   1880    'bidiמתמטיקה'
   1881  ]
   1882 
   1883  for (text of kTexts) {
   1884    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   1885    const tm = ctx.measureText(text);
   1886    // First character.
   1887    checkRectListsMatch(
   1888      tm.getSelectionRects(0, 1),
   1889      placeAndSelectTextInDOM(text, 0, 1)
   1890    );
   1891    // Last character.
   1892    checkRectListsMatch(
   1893      tm.getSelectionRects(text.length - 1, text.length),
   1894      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   1895    );
   1896    // Whole string.
   1897    checkRectListsMatch(
   1898      tm.getSelectionRects(0, text.length),
   1899      placeAndSelectTextInDOM(text, 0, text.length)
   1900    );
   1901    // Intermediate string.
   1902    checkRectListsMatch(
   1903      tm.getSelectionRects(1, text.length - 1),
   1904      placeAndSelectTextInDOM(text, 1, text.length - 1)
   1905    );
   1906    // Invalid start > end string. Creates 0 width rectangle.
   1907    checkRectListsMatch(
   1908      tm.getSelectionRects(3, 2),
   1909      placeAndSelectTextInDOM(text, 3, 2)
   1910    );
   1911    checkRectListsMatch(
   1912      tm.getSelectionRects(1, 0),
   1913      placeAndSelectTextInDOM(text, 1, 0)
   1914    );
   1915  }
   1916 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override.");
   1917 
   1918 test(t => {
   1919  const canvas = document.createElement('canvas');
   1920  canvas.width = 100;
   1921  canvas.height = 50;
   1922  const ctx = canvas.getContext('2d');
   1923 
   1924  function placeAndSelectTextInDOM(text, from, to) {
   1925    const el = document.createElement("div");
   1926    el.innerHTML = text;
   1927    el.style.font = '50px sans-serif';
   1928    el.style.direction = 'ltr';
   1929    el.style.textAlign = 'right';
   1930    el.style.letterSpacing = '0px';
   1931    document.body.appendChild(el);
   1932 
   1933    let range = document.createRange();
   1934 
   1935    range.setStart(el.childNodes[0], 0);
   1936    range.setEnd(el.childNodes[0], text.length);
   1937    const parent = range.getClientRects()[0];
   1938    let width = 0;
   1939    for (const rect of range.getClientRects()) {
   1940      width += rect.width;
   1941    }
   1942 
   1943    range.setStart(el.childNodes[0], from);
   1944    range.setEnd(el.childNodes[0], to);
   1945 
   1946    let sel = window.getSelection();
   1947    sel.removeAllRanges();
   1948    sel.addRange(range);
   1949 
   1950    let sel_rects = sel.getRangeAt(0).getClientRects();
   1951 
   1952    document.body.removeChild(el);
   1953 
   1954    // Offset to the alignment point determined by textAlign.
   1955    let text_align_dx;
   1956    switch (el.style.textAlign) {
   1957      case 'right':
   1958        text_align_dx = width;
   1959        break;
   1960      case 'center':
   1961        text_align_dx = width / 2;
   1962        break;
   1963      default:
   1964        text_align_dx = 0;
   1965    }
   1966 
   1967    for(let i = 0 ; i < sel_rects.length ; ++i) {
   1968      sel_rects[i].x -= parent.x + text_align_dx;
   1969      sel_rects[i].y -= parent.y;
   1970    }
   1971 
   1972    return sel_rects;
   1973  }
   1974 
   1975  function checkRectListsMatch(list_a, list_b) {
   1976    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   1977    for(let i = 0 ; i < list_a.length ; ++i) {
   1978      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   1979      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   1980      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   1981      // Y-position not tested here as getting the baseline for text in the
   1982      // DOM is not straightforward.
   1983    }
   1984  }
   1985 
   1986  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   1987    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   1988    const LTR_OVERRIDE = '\u202D';
   1989    const RTL_OVERRIDE = '\u202E';
   1990    const OVERRIDE_END = '\u202C';
   1991    if (direction_is_ltr) {
   1992      return LTR_OVERRIDE + text + OVERRIDE_END;
   1993    }
   1994    return RTL_OVERRIDE + text + OVERRIDE_END;
   1995  }
   1996 
   1997  ctx.font = '50px sans-serif';
   1998  ctx.direction = 'ltr';
   1999  ctx.textAlign = 'right';
   2000  ctx.letterSpacing = '0px';
   2001 
   2002  const kTexts = [
   2003    'UNAVAILABLE',
   2004    '🏁🎶🏁',
   2005    ')(あ)(',
   2006    '-abcd_',
   2007    'איפה הספרייה?',
   2008    'bidiמתמטיקה'
   2009  ]
   2010 
   2011  for (text of kTexts) {
   2012    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2013    const tm = ctx.measureText(text);
   2014    // First character.
   2015    checkRectListsMatch(
   2016      tm.getSelectionRects(0, 1),
   2017      placeAndSelectTextInDOM(text, 0, 1)
   2018    );
   2019    // Last character.
   2020    checkRectListsMatch(
   2021      tm.getSelectionRects(text.length - 1, text.length),
   2022      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   2023    );
   2024    // Whole string.
   2025    checkRectListsMatch(
   2026      tm.getSelectionRects(0, text.length),
   2027      placeAndSelectTextInDOM(text, 0, text.length)
   2028    );
   2029    // Intermediate string.
   2030    checkRectListsMatch(
   2031      tm.getSelectionRects(1, text.length - 1),
   2032      placeAndSelectTextInDOM(text, 1, text.length - 1)
   2033    );
   2034    // Invalid start > end string. Creates 0 width rectangle.
   2035    checkRectListsMatch(
   2036      tm.getSelectionRects(3, 2),
   2037      placeAndSelectTextInDOM(text, 3, 2)
   2038    );
   2039    checkRectListsMatch(
   2040      tm.getSelectionRects(1, 0),
   2041      placeAndSelectTextInDOM(text, 1, 0)
   2042    );
   2043  }
   2044 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override.");
   2045 
   2046 test(t => {
   2047  const canvas = document.createElement('canvas');
   2048  canvas.width = 100;
   2049  canvas.height = 50;
   2050  const ctx = canvas.getContext('2d');
   2051 
   2052  function placeAndSelectTextInDOM(text, from, to) {
   2053    const el = document.createElement("div");
   2054    el.innerHTML = text;
   2055    el.style.font = '50px sans-serif';
   2056    el.style.direction = 'rtl';
   2057    el.style.textAlign = 'right';
   2058    el.style.letterSpacing = '0px';
   2059    document.body.appendChild(el);
   2060 
   2061    let range = document.createRange();
   2062 
   2063    range.setStart(el.childNodes[0], 0);
   2064    range.setEnd(el.childNodes[0], text.length);
   2065    const parent = range.getClientRects()[0];
   2066    let width = 0;
   2067    for (const rect of range.getClientRects()) {
   2068      width += rect.width;
   2069    }
   2070 
   2071    range.setStart(el.childNodes[0], from);
   2072    range.setEnd(el.childNodes[0], to);
   2073 
   2074    let sel = window.getSelection();
   2075    sel.removeAllRanges();
   2076    sel.addRange(range);
   2077 
   2078    let sel_rects = sel.getRangeAt(0).getClientRects();
   2079 
   2080    document.body.removeChild(el);
   2081 
   2082    // Offset to the alignment point determined by textAlign.
   2083    let text_align_dx;
   2084    switch (el.style.textAlign) {
   2085      case 'right':
   2086        text_align_dx = width;
   2087        break;
   2088      case 'center':
   2089        text_align_dx = width / 2;
   2090        break;
   2091      default:
   2092        text_align_dx = 0;
   2093    }
   2094 
   2095    for(let i = 0 ; i < sel_rects.length ; ++i) {
   2096      sel_rects[i].x -= parent.x + text_align_dx;
   2097      sel_rects[i].y -= parent.y;
   2098    }
   2099 
   2100    return sel_rects;
   2101  }
   2102 
   2103  function checkRectListsMatch(list_a, list_b) {
   2104    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   2105    for(let i = 0 ; i < list_a.length ; ++i) {
   2106      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   2107      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   2108      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   2109      // Y-position not tested here as getting the baseline for text in the
   2110      // DOM is not straightforward.
   2111    }
   2112  }
   2113 
   2114  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2115    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2116    const LTR_OVERRIDE = '\u202D';
   2117    const RTL_OVERRIDE = '\u202E';
   2118    const OVERRIDE_END = '\u202C';
   2119    if (direction_is_ltr) {
   2120      return LTR_OVERRIDE + text + OVERRIDE_END;
   2121    }
   2122    return RTL_OVERRIDE + text + OVERRIDE_END;
   2123  }
   2124 
   2125  ctx.font = '50px sans-serif';
   2126  ctx.direction = 'rtl';
   2127  ctx.textAlign = 'right';
   2128  ctx.letterSpacing = '0px';
   2129 
   2130  const kTexts = [
   2131    'UNAVAILABLE',
   2132    '🏁🎶🏁',
   2133    ')(あ)(',
   2134    '-abcd_',
   2135    'איפה הספרייה?',
   2136    'bidiמתמטיקה'
   2137  ]
   2138 
   2139  for (text of kTexts) {
   2140    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2141    const tm = ctx.measureText(text);
   2142    // First character.
   2143    checkRectListsMatch(
   2144      tm.getSelectionRects(0, 1),
   2145      placeAndSelectTextInDOM(text, 0, 1)
   2146    );
   2147    // Last character.
   2148    checkRectListsMatch(
   2149      tm.getSelectionRects(text.length - 1, text.length),
   2150      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   2151    );
   2152    // Whole string.
   2153    checkRectListsMatch(
   2154      tm.getSelectionRects(0, text.length),
   2155      placeAndSelectTextInDOM(text, 0, text.length)
   2156    );
   2157    // Intermediate string.
   2158    checkRectListsMatch(
   2159      tm.getSelectionRects(1, text.length - 1),
   2160      placeAndSelectTextInDOM(text, 1, text.length - 1)
   2161    );
   2162    // Invalid start > end string. Creates 0 width rectangle.
   2163    checkRectListsMatch(
   2164      tm.getSelectionRects(3, 2),
   2165      placeAndSelectTextInDOM(text, 3, 2)
   2166    );
   2167    checkRectListsMatch(
   2168      tm.getSelectionRects(1, 0),
   2169      placeAndSelectTextInDOM(text, 1, 0)
   2170    );
   2171  }
   2172 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override.");
   2173 
   2174 test(t => {
   2175  const canvas = document.createElement('canvas');
   2176  canvas.width = 100;
   2177  canvas.height = 50;
   2178  const ctx = canvas.getContext('2d');
   2179 
   2180  function placeAndSelectTextInDOM(text, from, to) {
   2181    const el = document.createElement("div");
   2182    el.innerHTML = text;
   2183    el.style.font = '50px sans-serif';
   2184    el.style.direction = 'ltr';
   2185    el.style.textAlign = 'left';
   2186    el.style.letterSpacing = '10px';
   2187    document.body.appendChild(el);
   2188 
   2189    let range = document.createRange();
   2190 
   2191    range.setStart(el.childNodes[0], 0);
   2192    range.setEnd(el.childNodes[0], text.length);
   2193    const parent = range.getClientRects()[0];
   2194    let width = 0;
   2195    for (const rect of range.getClientRects()) {
   2196      width += rect.width;
   2197    }
   2198 
   2199    range.setStart(el.childNodes[0], from);
   2200    range.setEnd(el.childNodes[0], to);
   2201 
   2202    let sel = window.getSelection();
   2203    sel.removeAllRanges();
   2204    sel.addRange(range);
   2205 
   2206    let sel_rects = sel.getRangeAt(0).getClientRects();
   2207 
   2208    document.body.removeChild(el);
   2209 
   2210    // Offset to the alignment point determined by textAlign.
   2211    let text_align_dx;
   2212    switch (el.style.textAlign) {
   2213      case 'right':
   2214        text_align_dx = width;
   2215        break;
   2216      case 'center':
   2217        text_align_dx = width / 2;
   2218        break;
   2219      default:
   2220        text_align_dx = 0;
   2221    }
   2222 
   2223    for(let i = 0 ; i < sel_rects.length ; ++i) {
   2224      sel_rects[i].x -= parent.x + text_align_dx;
   2225      sel_rects[i].y -= parent.y;
   2226    }
   2227 
   2228    return sel_rects;
   2229  }
   2230 
   2231  function checkRectListsMatch(list_a, list_b) {
   2232    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   2233    for(let i = 0 ; i < list_a.length ; ++i) {
   2234      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   2235      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   2236      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   2237      // Y-position not tested here as getting the baseline for text in the
   2238      // DOM is not straightforward.
   2239    }
   2240  }
   2241 
   2242  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2243    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2244    const LTR_OVERRIDE = '\u202D';
   2245    const RTL_OVERRIDE = '\u202E';
   2246    const OVERRIDE_END = '\u202C';
   2247    if (direction_is_ltr) {
   2248      return LTR_OVERRIDE + text + OVERRIDE_END;
   2249    }
   2250    return RTL_OVERRIDE + text + OVERRIDE_END;
   2251  }
   2252 
   2253  ctx.font = '50px sans-serif';
   2254  ctx.direction = 'ltr';
   2255  ctx.textAlign = 'left';
   2256  ctx.letterSpacing = '10px';
   2257 
   2258  const kTexts = [
   2259    'UNAVAILABLE',
   2260    '🏁🎶🏁',
   2261    ')(あ)(',
   2262    '-abcd_',
   2263    'איפה הספרייה?',
   2264    'bidiמתמטיקה'
   2265  ]
   2266 
   2267  for (text of kTexts) {
   2268    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2269    const tm = ctx.measureText(text);
   2270    // First character.
   2271    checkRectListsMatch(
   2272      tm.getSelectionRects(0, 1),
   2273      placeAndSelectTextInDOM(text, 0, 1)
   2274    );
   2275    // Last character.
   2276    checkRectListsMatch(
   2277      tm.getSelectionRects(text.length - 1, text.length),
   2278      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   2279    );
   2280    // Whole string.
   2281    checkRectListsMatch(
   2282      tm.getSelectionRects(0, text.length),
   2283      placeAndSelectTextInDOM(text, 0, text.length)
   2284    );
   2285    // Intermediate string.
   2286    checkRectListsMatch(
   2287      tm.getSelectionRects(1, text.length - 1),
   2288      placeAndSelectTextInDOM(text, 1, text.length - 1)
   2289    );
   2290    // Invalid start > end string. Creates 0 width rectangle.
   2291    checkRectListsMatch(
   2292      tm.getSelectionRects(3, 2),
   2293      placeAndSelectTextInDOM(text, 3, 2)
   2294    );
   2295    checkRectListsMatch(
   2296      tm.getSelectionRects(1, 0),
   2297      placeAndSelectTextInDOM(text, 1, 0)
   2298    );
   2299  }
   2300 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override.");
   2301 
   2302 test(t => {
   2303  const canvas = document.createElement('canvas');
   2304  canvas.width = 100;
   2305  canvas.height = 50;
   2306  const ctx = canvas.getContext('2d');
   2307 
   2308  function placeAndSelectTextInDOM(text, from, to) {
   2309    const el = document.createElement("div");
   2310    el.innerHTML = text;
   2311    el.style.font = '50px sans-serif';
   2312    el.style.direction = 'rtl';
   2313    el.style.textAlign = 'left';
   2314    el.style.letterSpacing = '10px';
   2315    document.body.appendChild(el);
   2316 
   2317    let range = document.createRange();
   2318 
   2319    range.setStart(el.childNodes[0], 0);
   2320    range.setEnd(el.childNodes[0], text.length);
   2321    const parent = range.getClientRects()[0];
   2322    let width = 0;
   2323    for (const rect of range.getClientRects()) {
   2324      width += rect.width;
   2325    }
   2326 
   2327    range.setStart(el.childNodes[0], from);
   2328    range.setEnd(el.childNodes[0], to);
   2329 
   2330    let sel = window.getSelection();
   2331    sel.removeAllRanges();
   2332    sel.addRange(range);
   2333 
   2334    let sel_rects = sel.getRangeAt(0).getClientRects();
   2335 
   2336    document.body.removeChild(el);
   2337 
   2338    // Offset to the alignment point determined by textAlign.
   2339    let text_align_dx;
   2340    switch (el.style.textAlign) {
   2341      case 'right':
   2342        text_align_dx = width;
   2343        break;
   2344      case 'center':
   2345        text_align_dx = width / 2;
   2346        break;
   2347      default:
   2348        text_align_dx = 0;
   2349    }
   2350 
   2351    for(let i = 0 ; i < sel_rects.length ; ++i) {
   2352      sel_rects[i].x -= parent.x + text_align_dx;
   2353      sel_rects[i].y -= parent.y;
   2354    }
   2355 
   2356    return sel_rects;
   2357  }
   2358 
   2359  function checkRectListsMatch(list_a, list_b) {
   2360    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   2361    for(let i = 0 ; i < list_a.length ; ++i) {
   2362      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   2363      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   2364      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   2365      // Y-position not tested here as getting the baseline for text in the
   2366      // DOM is not straightforward.
   2367    }
   2368  }
   2369 
   2370  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2371    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2372    const LTR_OVERRIDE = '\u202D';
   2373    const RTL_OVERRIDE = '\u202E';
   2374    const OVERRIDE_END = '\u202C';
   2375    if (direction_is_ltr) {
   2376      return LTR_OVERRIDE + text + OVERRIDE_END;
   2377    }
   2378    return RTL_OVERRIDE + text + OVERRIDE_END;
   2379  }
   2380 
   2381  ctx.font = '50px sans-serif';
   2382  ctx.direction = 'rtl';
   2383  ctx.textAlign = 'left';
   2384  ctx.letterSpacing = '10px';
   2385 
   2386  const kTexts = [
   2387    'UNAVAILABLE',
   2388    '🏁🎶🏁',
   2389    ')(あ)(',
   2390    '-abcd_',
   2391    'איפה הספרייה?',
   2392    'bidiמתמטיקה'
   2393  ]
   2394 
   2395  for (text of kTexts) {
   2396    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2397    const tm = ctx.measureText(text);
   2398    // First character.
   2399    checkRectListsMatch(
   2400      tm.getSelectionRects(0, 1),
   2401      placeAndSelectTextInDOM(text, 0, 1)
   2402    );
   2403    // Last character.
   2404    checkRectListsMatch(
   2405      tm.getSelectionRects(text.length - 1, text.length),
   2406      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   2407    );
   2408    // Whole string.
   2409    checkRectListsMatch(
   2410      tm.getSelectionRects(0, text.length),
   2411      placeAndSelectTextInDOM(text, 0, text.length)
   2412    );
   2413    // Intermediate string.
   2414    checkRectListsMatch(
   2415      tm.getSelectionRects(1, text.length - 1),
   2416      placeAndSelectTextInDOM(text, 1, text.length - 1)
   2417    );
   2418    // Invalid start > end string. Creates 0 width rectangle.
   2419    checkRectListsMatch(
   2420      tm.getSelectionRects(3, 2),
   2421      placeAndSelectTextInDOM(text, 3, 2)
   2422    );
   2423    checkRectListsMatch(
   2424      tm.getSelectionRects(1, 0),
   2425      placeAndSelectTextInDOM(text, 1, 0)
   2426    );
   2427  }
   2428 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override.");
   2429 
   2430 test(t => {
   2431  const canvas = document.createElement('canvas');
   2432  canvas.width = 100;
   2433  canvas.height = 50;
   2434  const ctx = canvas.getContext('2d');
   2435 
   2436  function placeAndSelectTextInDOM(text, from, to) {
   2437    const el = document.createElement("div");
   2438    el.innerHTML = text;
   2439    el.style.font = '50px sans-serif';
   2440    el.style.direction = 'ltr';
   2441    el.style.textAlign = 'center';
   2442    el.style.letterSpacing = '10px';
   2443    document.body.appendChild(el);
   2444 
   2445    let range = document.createRange();
   2446 
   2447    range.setStart(el.childNodes[0], 0);
   2448    range.setEnd(el.childNodes[0], text.length);
   2449    const parent = range.getClientRects()[0];
   2450    let width = 0;
   2451    for (const rect of range.getClientRects()) {
   2452      width += rect.width;
   2453    }
   2454 
   2455    range.setStart(el.childNodes[0], from);
   2456    range.setEnd(el.childNodes[0], to);
   2457 
   2458    let sel = window.getSelection();
   2459    sel.removeAllRanges();
   2460    sel.addRange(range);
   2461 
   2462    let sel_rects = sel.getRangeAt(0).getClientRects();
   2463 
   2464    document.body.removeChild(el);
   2465 
   2466    // Offset to the alignment point determined by textAlign.
   2467    let text_align_dx;
   2468    switch (el.style.textAlign) {
   2469      case 'right':
   2470        text_align_dx = width;
   2471        break;
   2472      case 'center':
   2473        text_align_dx = width / 2;
   2474        break;
   2475      default:
   2476        text_align_dx = 0;
   2477    }
   2478 
   2479    for(let i = 0 ; i < sel_rects.length ; ++i) {
   2480      sel_rects[i].x -= parent.x + text_align_dx;
   2481      sel_rects[i].y -= parent.y;
   2482    }
   2483 
   2484    return sel_rects;
   2485  }
   2486 
   2487  function checkRectListsMatch(list_a, list_b) {
   2488    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   2489    for(let i = 0 ; i < list_a.length ; ++i) {
   2490      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   2491      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   2492      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   2493      // Y-position not tested here as getting the baseline for text in the
   2494      // DOM is not straightforward.
   2495    }
   2496  }
   2497 
   2498  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2499    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2500    const LTR_OVERRIDE = '\u202D';
   2501    const RTL_OVERRIDE = '\u202E';
   2502    const OVERRIDE_END = '\u202C';
   2503    if (direction_is_ltr) {
   2504      return LTR_OVERRIDE + text + OVERRIDE_END;
   2505    }
   2506    return RTL_OVERRIDE + text + OVERRIDE_END;
   2507  }
   2508 
   2509  ctx.font = '50px sans-serif';
   2510  ctx.direction = 'ltr';
   2511  ctx.textAlign = 'center';
   2512  ctx.letterSpacing = '10px';
   2513 
   2514  const kTexts = [
   2515    'UNAVAILABLE',
   2516    '🏁🎶🏁',
   2517    ')(あ)(',
   2518    '-abcd_',
   2519    'איפה הספרייה?',
   2520    'bidiמתמטיקה'
   2521  ]
   2522 
   2523  for (text of kTexts) {
   2524    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2525    const tm = ctx.measureText(text);
   2526    // First character.
   2527    checkRectListsMatch(
   2528      tm.getSelectionRects(0, 1),
   2529      placeAndSelectTextInDOM(text, 0, 1)
   2530    );
   2531    // Last character.
   2532    checkRectListsMatch(
   2533      tm.getSelectionRects(text.length - 1, text.length),
   2534      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   2535    );
   2536    // Whole string.
   2537    checkRectListsMatch(
   2538      tm.getSelectionRects(0, text.length),
   2539      placeAndSelectTextInDOM(text, 0, text.length)
   2540    );
   2541    // Intermediate string.
   2542    checkRectListsMatch(
   2543      tm.getSelectionRects(1, text.length - 1),
   2544      placeAndSelectTextInDOM(text, 1, text.length - 1)
   2545    );
   2546    // Invalid start > end string. Creates 0 width rectangle.
   2547    checkRectListsMatch(
   2548      tm.getSelectionRects(3, 2),
   2549      placeAndSelectTextInDOM(text, 3, 2)
   2550    );
   2551    checkRectListsMatch(
   2552      tm.getSelectionRects(1, 0),
   2553      placeAndSelectTextInDOM(text, 1, 0)
   2554    );
   2555  }
   2556 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override.");
   2557 
   2558 test(t => {
   2559  const canvas = document.createElement('canvas');
   2560  canvas.width = 100;
   2561  canvas.height = 50;
   2562  const ctx = canvas.getContext('2d');
   2563 
   2564  function placeAndSelectTextInDOM(text, from, to) {
   2565    const el = document.createElement("div");
   2566    el.innerHTML = text;
   2567    el.style.font = '50px sans-serif';
   2568    el.style.direction = 'rtl';
   2569    el.style.textAlign = 'center';
   2570    el.style.letterSpacing = '10px';
   2571    document.body.appendChild(el);
   2572 
   2573    let range = document.createRange();
   2574 
   2575    range.setStart(el.childNodes[0], 0);
   2576    range.setEnd(el.childNodes[0], text.length);
   2577    const parent = range.getClientRects()[0];
   2578    let width = 0;
   2579    for (const rect of range.getClientRects()) {
   2580      width += rect.width;
   2581    }
   2582 
   2583    range.setStart(el.childNodes[0], from);
   2584    range.setEnd(el.childNodes[0], to);
   2585 
   2586    let sel = window.getSelection();
   2587    sel.removeAllRanges();
   2588    sel.addRange(range);
   2589 
   2590    let sel_rects = sel.getRangeAt(0).getClientRects();
   2591 
   2592    document.body.removeChild(el);
   2593 
   2594    // Offset to the alignment point determined by textAlign.
   2595    let text_align_dx;
   2596    switch (el.style.textAlign) {
   2597      case 'right':
   2598        text_align_dx = width;
   2599        break;
   2600      case 'center':
   2601        text_align_dx = width / 2;
   2602        break;
   2603      default:
   2604        text_align_dx = 0;
   2605    }
   2606 
   2607    for(let i = 0 ; i < sel_rects.length ; ++i) {
   2608      sel_rects[i].x -= parent.x + text_align_dx;
   2609      sel_rects[i].y -= parent.y;
   2610    }
   2611 
   2612    return sel_rects;
   2613  }
   2614 
   2615  function checkRectListsMatch(list_a, list_b) {
   2616    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   2617    for(let i = 0 ; i < list_a.length ; ++i) {
   2618      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   2619      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   2620      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   2621      // Y-position not tested here as getting the baseline for text in the
   2622      // DOM is not straightforward.
   2623    }
   2624  }
   2625 
   2626  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2627    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2628    const LTR_OVERRIDE = '\u202D';
   2629    const RTL_OVERRIDE = '\u202E';
   2630    const OVERRIDE_END = '\u202C';
   2631    if (direction_is_ltr) {
   2632      return LTR_OVERRIDE + text + OVERRIDE_END;
   2633    }
   2634    return RTL_OVERRIDE + text + OVERRIDE_END;
   2635  }
   2636 
   2637  ctx.font = '50px sans-serif';
   2638  ctx.direction = 'rtl';
   2639  ctx.textAlign = 'center';
   2640  ctx.letterSpacing = '10px';
   2641 
   2642  const kTexts = [
   2643    'UNAVAILABLE',
   2644    '🏁🎶🏁',
   2645    ')(あ)(',
   2646    '-abcd_',
   2647    'איפה הספרייה?',
   2648    'bidiמתמטיקה'
   2649  ]
   2650 
   2651  for (text of kTexts) {
   2652    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2653    const tm = ctx.measureText(text);
   2654    // First character.
   2655    checkRectListsMatch(
   2656      tm.getSelectionRects(0, 1),
   2657      placeAndSelectTextInDOM(text, 0, 1)
   2658    );
   2659    // Last character.
   2660    checkRectListsMatch(
   2661      tm.getSelectionRects(text.length - 1, text.length),
   2662      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   2663    );
   2664    // Whole string.
   2665    checkRectListsMatch(
   2666      tm.getSelectionRects(0, text.length),
   2667      placeAndSelectTextInDOM(text, 0, text.length)
   2668    );
   2669    // Intermediate string.
   2670    checkRectListsMatch(
   2671      tm.getSelectionRects(1, text.length - 1),
   2672      placeAndSelectTextInDOM(text, 1, text.length - 1)
   2673    );
   2674    // Invalid start > end string. Creates 0 width rectangle.
   2675    checkRectListsMatch(
   2676      tm.getSelectionRects(3, 2),
   2677      placeAndSelectTextInDOM(text, 3, 2)
   2678    );
   2679    checkRectListsMatch(
   2680      tm.getSelectionRects(1, 0),
   2681      placeAndSelectTextInDOM(text, 1, 0)
   2682    );
   2683  }
   2684 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override.");
   2685 
   2686 test(t => {
   2687  const canvas = document.createElement('canvas');
   2688  canvas.width = 100;
   2689  canvas.height = 50;
   2690  const ctx = canvas.getContext('2d');
   2691 
   2692  function placeAndSelectTextInDOM(text, from, to) {
   2693    const el = document.createElement("div");
   2694    el.innerHTML = text;
   2695    el.style.font = '50px sans-serif';
   2696    el.style.direction = 'ltr';
   2697    el.style.textAlign = 'right';
   2698    el.style.letterSpacing = '10px';
   2699    document.body.appendChild(el);
   2700 
   2701    let range = document.createRange();
   2702 
   2703    range.setStart(el.childNodes[0], 0);
   2704    range.setEnd(el.childNodes[0], text.length);
   2705    const parent = range.getClientRects()[0];
   2706    let width = 0;
   2707    for (const rect of range.getClientRects()) {
   2708      width += rect.width;
   2709    }
   2710 
   2711    range.setStart(el.childNodes[0], from);
   2712    range.setEnd(el.childNodes[0], to);
   2713 
   2714    let sel = window.getSelection();
   2715    sel.removeAllRanges();
   2716    sel.addRange(range);
   2717 
   2718    let sel_rects = sel.getRangeAt(0).getClientRects();
   2719 
   2720    document.body.removeChild(el);
   2721 
   2722    // Offset to the alignment point determined by textAlign.
   2723    let text_align_dx;
   2724    switch (el.style.textAlign) {
   2725      case 'right':
   2726        text_align_dx = width;
   2727        break;
   2728      case 'center':
   2729        text_align_dx = width / 2;
   2730        break;
   2731      default:
   2732        text_align_dx = 0;
   2733    }
   2734 
   2735    for(let i = 0 ; i < sel_rects.length ; ++i) {
   2736      sel_rects[i].x -= parent.x + text_align_dx;
   2737      sel_rects[i].y -= parent.y;
   2738    }
   2739 
   2740    return sel_rects;
   2741  }
   2742 
   2743  function checkRectListsMatch(list_a, list_b) {
   2744    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   2745    for(let i = 0 ; i < list_a.length ; ++i) {
   2746      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   2747      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   2748      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   2749      // Y-position not tested here as getting the baseline for text in the
   2750      // DOM is not straightforward.
   2751    }
   2752  }
   2753 
   2754  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2755    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2756    const LTR_OVERRIDE = '\u202D';
   2757    const RTL_OVERRIDE = '\u202E';
   2758    const OVERRIDE_END = '\u202C';
   2759    if (direction_is_ltr) {
   2760      return LTR_OVERRIDE + text + OVERRIDE_END;
   2761    }
   2762    return RTL_OVERRIDE + text + OVERRIDE_END;
   2763  }
   2764 
   2765  ctx.font = '50px sans-serif';
   2766  ctx.direction = 'ltr';
   2767  ctx.textAlign = 'right';
   2768  ctx.letterSpacing = '10px';
   2769 
   2770  const kTexts = [
   2771    'UNAVAILABLE',
   2772    '🏁🎶🏁',
   2773    ')(あ)(',
   2774    '-abcd_',
   2775    'איפה הספרייה?',
   2776    'bidiמתמטיקה'
   2777  ]
   2778 
   2779  for (text of kTexts) {
   2780    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2781    const tm = ctx.measureText(text);
   2782    // First character.
   2783    checkRectListsMatch(
   2784      tm.getSelectionRects(0, 1),
   2785      placeAndSelectTextInDOM(text, 0, 1)
   2786    );
   2787    // Last character.
   2788    checkRectListsMatch(
   2789      tm.getSelectionRects(text.length - 1, text.length),
   2790      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   2791    );
   2792    // Whole string.
   2793    checkRectListsMatch(
   2794      tm.getSelectionRects(0, text.length),
   2795      placeAndSelectTextInDOM(text, 0, text.length)
   2796    );
   2797    // Intermediate string.
   2798    checkRectListsMatch(
   2799      tm.getSelectionRects(1, text.length - 1),
   2800      placeAndSelectTextInDOM(text, 1, text.length - 1)
   2801    );
   2802    // Invalid start > end string. Creates 0 width rectangle.
   2803    checkRectListsMatch(
   2804      tm.getSelectionRects(3, 2),
   2805      placeAndSelectTextInDOM(text, 3, 2)
   2806    );
   2807    checkRectListsMatch(
   2808      tm.getSelectionRects(1, 0),
   2809      placeAndSelectTextInDOM(text, 1, 0)
   2810    );
   2811  }
   2812 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override.");
   2813 
   2814 test(t => {
   2815  const canvas = document.createElement('canvas');
   2816  canvas.width = 100;
   2817  canvas.height = 50;
   2818  const ctx = canvas.getContext('2d');
   2819 
   2820  function placeAndSelectTextInDOM(text, from, to) {
   2821    const el = document.createElement("div");
   2822    el.innerHTML = text;
   2823    el.style.font = '50px sans-serif';
   2824    el.style.direction = 'rtl';
   2825    el.style.textAlign = 'right';
   2826    el.style.letterSpacing = '10px';
   2827    document.body.appendChild(el);
   2828 
   2829    let range = document.createRange();
   2830 
   2831    range.setStart(el.childNodes[0], 0);
   2832    range.setEnd(el.childNodes[0], text.length);
   2833    const parent = range.getClientRects()[0];
   2834    let width = 0;
   2835    for (const rect of range.getClientRects()) {
   2836      width += rect.width;
   2837    }
   2838 
   2839    range.setStart(el.childNodes[0], from);
   2840    range.setEnd(el.childNodes[0], to);
   2841 
   2842    let sel = window.getSelection();
   2843    sel.removeAllRanges();
   2844    sel.addRange(range);
   2845 
   2846    let sel_rects = sel.getRangeAt(0).getClientRects();
   2847 
   2848    document.body.removeChild(el);
   2849 
   2850    // Offset to the alignment point determined by textAlign.
   2851    let text_align_dx;
   2852    switch (el.style.textAlign) {
   2853      case 'right':
   2854        text_align_dx = width;
   2855        break;
   2856      case 'center':
   2857        text_align_dx = width / 2;
   2858        break;
   2859      default:
   2860        text_align_dx = 0;
   2861    }
   2862 
   2863    for(let i = 0 ; i < sel_rects.length ; ++i) {
   2864      sel_rects[i].x -= parent.x + text_align_dx;
   2865      sel_rects[i].y -= parent.y;
   2866    }
   2867 
   2868    return sel_rects;
   2869  }
   2870 
   2871  function checkRectListsMatch(list_a, list_b) {
   2872    _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
   2873    for(let i = 0 ; i < list_a.length ; ++i) {
   2874      assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
   2875      assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
   2876      assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
   2877      // Y-position not tested here as getting the baseline for text in the
   2878      // DOM is not straightforward.
   2879    }
   2880  }
   2881 
   2882  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2883    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2884    const LTR_OVERRIDE = '\u202D';
   2885    const RTL_OVERRIDE = '\u202E';
   2886    const OVERRIDE_END = '\u202C';
   2887    if (direction_is_ltr) {
   2888      return LTR_OVERRIDE + text + OVERRIDE_END;
   2889    }
   2890    return RTL_OVERRIDE + text + OVERRIDE_END;
   2891  }
   2892 
   2893  ctx.font = '50px sans-serif';
   2894  ctx.direction = 'rtl';
   2895  ctx.textAlign = 'right';
   2896  ctx.letterSpacing = '10px';
   2897 
   2898  const kTexts = [
   2899    'UNAVAILABLE',
   2900    '🏁🎶🏁',
   2901    ')(あ)(',
   2902    '-abcd_',
   2903    'איפה הספרייה?',
   2904    'bidiמתמטיקה'
   2905  ]
   2906 
   2907  for (text of kTexts) {
   2908    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2909    const tm = ctx.measureText(text);
   2910    // First character.
   2911    checkRectListsMatch(
   2912      tm.getSelectionRects(0, 1),
   2913      placeAndSelectTextInDOM(text, 0, 1)
   2914    );
   2915    // Last character.
   2916    checkRectListsMatch(
   2917      tm.getSelectionRects(text.length - 1, text.length),
   2918      placeAndSelectTextInDOM(text, text.length - 1, text.length)
   2919    );
   2920    // Whole string.
   2921    checkRectListsMatch(
   2922      tm.getSelectionRects(0, text.length),
   2923      placeAndSelectTextInDOM(text, 0, text.length)
   2924    );
   2925    // Intermediate string.
   2926    checkRectListsMatch(
   2927      tm.getSelectionRects(1, text.length - 1),
   2928      placeAndSelectTextInDOM(text, 1, text.length - 1)
   2929    );
   2930    // Invalid start > end string. Creates 0 width rectangle.
   2931    checkRectListsMatch(
   2932      tm.getSelectionRects(3, 2),
   2933      placeAndSelectTextInDOM(text, 3, 2)
   2934    );
   2935    checkRectListsMatch(
   2936      tm.getSelectionRects(1, 0),
   2937      placeAndSelectTextInDOM(text, 1, 0)
   2938    );
   2939  }
   2940 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override.");
   2941 
   2942 </script>