tor-browser

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

2d.text.measure.index-from-offset.tentative.html (152709B)


      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>OffscreenCanvas test: 2d.text.measure.index-from-offset.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 
      9 <h1>2d.text.measure.index-from-offset.tentative</h1>
     10 
     11 <script>
     12 
     13 test(t => {
     14  const canvas = new OffscreenCanvas(100, 50);
     15  const ctx = canvas.getContext('2d');
     16 
     17  function alignOffset(offset, width) {
     18    if ('left' == 'center') {
     19      offset -= width / 2;
     20    } else if ('left' == 'right' ||
     21             ('ltr' == 'ltr' && 'left' == 'end') ||
     22             ('ltr' == 'rtl' && 'left' == 'start')) {
     23      offset -= width;
     24    }
     25    return offset;
     26  }
     27 
     28 
     29  function placeAndMeasureTextInDOM(text, text_width, point) {
     30    const el = document.createElement("p");
     31    el.innerHTML = text;
     32    el.style.font = '50px sans-serif';
     33    el.style.direction = 'ltr';
     34    el.style.letterSpacing = '0px';
     35    // Put the text top left to make offsets simpler.
     36    el.style.padding = '0px';
     37    el.style.margin = '0px';
     38    el.style.position = 'absolute';
     39    el.style.x = '0px';
     40    el.style.y = '0px';
     41    document.body.appendChild(el);
     42    text_bound = el.getBoundingClientRect();
     43    text_x = text_bound.x;
     44    text_y = text_bound.y + text_bound.height / 2;
     45 
     46    // Offset to the requested point determined by textAlign and direction.
     47    let text_align_dx = 0;
     48    if ('left' == 'center') {
     49      text_align_dx = text_width / 2;
     50    } else if ('left' == 'right' ||
     51             ('ltr' == 'ltr' && 'left' == 'end') ||
     52             ('ltr' == 'rtl' && 'left' == 'start')) {
     53      text_align_dx = text_width;
     54    }
     55    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
     56    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
     57 
     58    document.body.removeChild(el);
     59 
     60    return position.offset;
     61  }
     62 
     63  ctx.font = '50px sans-serif';
     64  ctx.direction = 'ltr';
     65  ctx.textAlign = 'left';
     66  ctx.letterSpacing = '0px';
     67 
     68  const kTexts = [
     69    'UNAVAILABLE',
     70    '🏁🎢🏁',
     71    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
     72    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
     73    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
     74    '--abcd__'
     75  ]
     76 
     77  for (text of kTexts) {
     78    const tm = ctx.measureText(text);
     79    text_width = tm.width;
     80    step = 30;
     81    if ('0px' == '10px') {
     82      step = 40;
     83    }
     84 
     85    offset = step;
     86    adjusted_offset = alignOffset(offset, text_width);
     87    tm_position = tm.getIndexFromOffset(adjusted_offset);
     88    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
     89    assert_equals(tm_position,
     90                  doc_position,
     91                  "for " + text + " offset " + offset);
     92 
     93    offset = text_width / 2 - 10;
     94    adjusted_offset = alignOffset(offset, text_width);
     95    tm_position = tm.getIndexFromOffset(adjusted_offset);
     96    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
     97    assert_equals(tm_position,
     98                  doc_position,
     99                  "for " + text + " offset " + offset);
    100 
    101    offset = text_width / 2 + 10;
    102    adjusted_offset = alignOffset(offset, text_width);
    103    tm_position = tm.getIndexFromOffset(adjusted_offset);
    104    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    105    assert_equals(tm_position,
    106                  doc_position,
    107                  "for " + text + " offset " + offset);
    108 
    109    offset = text_width - step;
    110    adjusted_offset = alignOffset(offset, text_width);
    111    tm_position = tm.getIndexFromOffset(adjusted_offset);
    112    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    113    assert_equals(tm_position,
    114                  doc_position,
    115                  "for " + text + " offset " + offset);
    116  }
    117 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and no-directional-override.");
    118 
    119 test(t => {
    120  const canvas = new OffscreenCanvas(100, 50);
    121  const ctx = canvas.getContext('2d');
    122 
    123  function alignOffset(offset, width) {
    124    if ('left' == 'center') {
    125      offset -= width / 2;
    126    } else if ('left' == 'right' ||
    127             ('rtl' == 'ltr' && 'left' == 'end') ||
    128             ('rtl' == 'rtl' && 'left' == 'start')) {
    129      offset -= width;
    130    }
    131    return offset;
    132  }
    133 
    134 
    135  function placeAndMeasureTextInDOM(text, text_width, point) {
    136    const el = document.createElement("p");
    137    el.innerHTML = text;
    138    el.style.font = '50px sans-serif';
    139    el.style.direction = 'rtl';
    140    el.style.letterSpacing = '0px';
    141    // Put the text top left to make offsets simpler.
    142    el.style.padding = '0px';
    143    el.style.margin = '0px';
    144    el.style.position = 'absolute';
    145    el.style.x = '0px';
    146    el.style.y = '0px';
    147    document.body.appendChild(el);
    148    text_bound = el.getBoundingClientRect();
    149    text_x = text_bound.x;
    150    text_y = text_bound.y + text_bound.height / 2;
    151 
    152    // Offset to the requested point determined by textAlign and direction.
    153    let text_align_dx = 0;
    154    if ('left' == 'center') {
    155      text_align_dx = text_width / 2;
    156    } else if ('left' == 'right' ||
    157             ('rtl' == 'ltr' && 'left' == 'end') ||
    158             ('rtl' == 'rtl' && 'left' == 'start')) {
    159      text_align_dx = text_width;
    160    }
    161    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
    162    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
    163 
    164    document.body.removeChild(el);
    165 
    166    return position.offset;
    167  }
    168 
    169  ctx.font = '50px sans-serif';
    170  ctx.direction = 'rtl';
    171  ctx.textAlign = 'left';
    172  ctx.letterSpacing = '0px';
    173 
    174  const kTexts = [
    175    'UNAVAILABLE',
    176    '🏁🎢🏁',
    177    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
    178    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
    179    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
    180    '--abcd__'
    181  ]
    182 
    183  for (text of kTexts) {
    184    const tm = ctx.measureText(text);
    185    text_width = tm.width;
    186    step = 30;
    187    if ('0px' == '10px') {
    188      step = 40;
    189    }
    190 
    191    offset = step;
    192    adjusted_offset = alignOffset(offset, text_width);
    193    tm_position = tm.getIndexFromOffset(adjusted_offset);
    194    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    195    assert_equals(tm_position,
    196                  doc_position,
    197                  "for " + text + " offset " + offset);
    198 
    199    offset = text_width / 2 - 10;
    200    adjusted_offset = alignOffset(offset, text_width);
    201    tm_position = tm.getIndexFromOffset(adjusted_offset);
    202    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    203    assert_equals(tm_position,
    204                  doc_position,
    205                  "for " + text + " offset " + offset);
    206 
    207    offset = text_width / 2 + 10;
    208    adjusted_offset = alignOffset(offset, text_width);
    209    tm_position = tm.getIndexFromOffset(adjusted_offset);
    210    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    211    assert_equals(tm_position,
    212                  doc_position,
    213                  "for " + text + " offset " + offset);
    214 
    215    offset = text_width - step;
    216    adjusted_offset = alignOffset(offset, text_width);
    217    tm_position = tm.getIndexFromOffset(adjusted_offset);
    218    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    219    assert_equals(tm_position,
    220                  doc_position,
    221                  "for " + text + " offset " + offset);
    222  }
    223 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and no-directional-override.");
    224 
    225 test(t => {
    226  const canvas = new OffscreenCanvas(100, 50);
    227  const ctx = canvas.getContext('2d');
    228 
    229  function alignOffset(offset, width) {
    230    if ('center' == 'center') {
    231      offset -= width / 2;
    232    } else if ('center' == 'right' ||
    233             ('ltr' == 'ltr' && 'center' == 'end') ||
    234             ('ltr' == 'rtl' && 'center' == 'start')) {
    235      offset -= width;
    236    }
    237    return offset;
    238  }
    239 
    240 
    241  function placeAndMeasureTextInDOM(text, text_width, point) {
    242    const el = document.createElement("p");
    243    el.innerHTML = text;
    244    el.style.font = '50px sans-serif';
    245    el.style.direction = 'ltr';
    246    el.style.letterSpacing = '0px';
    247    // Put the text top left to make offsets simpler.
    248    el.style.padding = '0px';
    249    el.style.margin = '0px';
    250    el.style.position = 'absolute';
    251    el.style.x = '0px';
    252    el.style.y = '0px';
    253    document.body.appendChild(el);
    254    text_bound = el.getBoundingClientRect();
    255    text_x = text_bound.x;
    256    text_y = text_bound.y + text_bound.height / 2;
    257 
    258    // Offset to the requested point determined by textAlign and direction.
    259    let text_align_dx = 0;
    260    if ('center' == 'center') {
    261      text_align_dx = text_width / 2;
    262    } else if ('center' == 'right' ||
    263             ('ltr' == 'ltr' && 'center' == 'end') ||
    264             ('ltr' == 'rtl' && 'center' == 'start')) {
    265      text_align_dx = text_width;
    266    }
    267    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
    268    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
    269 
    270    document.body.removeChild(el);
    271 
    272    return position.offset;
    273  }
    274 
    275  ctx.font = '50px sans-serif';
    276  ctx.direction = 'ltr';
    277  ctx.textAlign = 'center';
    278  ctx.letterSpacing = '0px';
    279 
    280  const kTexts = [
    281    'UNAVAILABLE',
    282    '🏁🎢🏁',
    283    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
    284    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
    285    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
    286    '--abcd__'
    287  ]
    288 
    289  for (text of kTexts) {
    290    const tm = ctx.measureText(text);
    291    text_width = tm.width;
    292    step = 30;
    293    if ('0px' == '10px') {
    294      step = 40;
    295    }
    296 
    297    offset = step;
    298    adjusted_offset = alignOffset(offset, text_width);
    299    tm_position = tm.getIndexFromOffset(adjusted_offset);
    300    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    301    assert_equals(tm_position,
    302                  doc_position,
    303                  "for " + text + " offset " + offset);
    304 
    305    offset = text_width / 2 - 10;
    306    adjusted_offset = alignOffset(offset, text_width);
    307    tm_position = tm.getIndexFromOffset(adjusted_offset);
    308    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    309    assert_equals(tm_position,
    310                  doc_position,
    311                  "for " + text + " offset " + offset);
    312 
    313    offset = text_width / 2 + 10;
    314    adjusted_offset = alignOffset(offset, text_width);
    315    tm_position = tm.getIndexFromOffset(adjusted_offset);
    316    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    317    assert_equals(tm_position,
    318                  doc_position,
    319                  "for " + text + " offset " + offset);
    320 
    321    offset = text_width - step;
    322    adjusted_offset = alignOffset(offset, text_width);
    323    tm_position = tm.getIndexFromOffset(adjusted_offset);
    324    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    325    assert_equals(tm_position,
    326                  doc_position,
    327                  "for " + text + " offset " + offset);
    328  }
    329 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and no-directional-override.");
    330 
    331 test(t => {
    332  const canvas = new OffscreenCanvas(100, 50);
    333  const ctx = canvas.getContext('2d');
    334 
    335  function alignOffset(offset, width) {
    336    if ('center' == 'center') {
    337      offset -= width / 2;
    338    } else if ('center' == 'right' ||
    339             ('rtl' == 'ltr' && 'center' == 'end') ||
    340             ('rtl' == 'rtl' && 'center' == 'start')) {
    341      offset -= width;
    342    }
    343    return offset;
    344  }
    345 
    346 
    347  function placeAndMeasureTextInDOM(text, text_width, point) {
    348    const el = document.createElement("p");
    349    el.innerHTML = text;
    350    el.style.font = '50px sans-serif';
    351    el.style.direction = 'rtl';
    352    el.style.letterSpacing = '0px';
    353    // Put the text top left to make offsets simpler.
    354    el.style.padding = '0px';
    355    el.style.margin = '0px';
    356    el.style.position = 'absolute';
    357    el.style.x = '0px';
    358    el.style.y = '0px';
    359    document.body.appendChild(el);
    360    text_bound = el.getBoundingClientRect();
    361    text_x = text_bound.x;
    362    text_y = text_bound.y + text_bound.height / 2;
    363 
    364    // Offset to the requested point determined by textAlign and direction.
    365    let text_align_dx = 0;
    366    if ('center' == 'center') {
    367      text_align_dx = text_width / 2;
    368    } else if ('center' == 'right' ||
    369             ('rtl' == 'ltr' && 'center' == 'end') ||
    370             ('rtl' == 'rtl' && 'center' == 'start')) {
    371      text_align_dx = text_width;
    372    }
    373    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
    374    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
    375 
    376    document.body.removeChild(el);
    377 
    378    return position.offset;
    379  }
    380 
    381  ctx.font = '50px sans-serif';
    382  ctx.direction = 'rtl';
    383  ctx.textAlign = 'center';
    384  ctx.letterSpacing = '0px';
    385 
    386  const kTexts = [
    387    'UNAVAILABLE',
    388    '🏁🎢🏁',
    389    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
    390    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
    391    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
    392    '--abcd__'
    393  ]
    394 
    395  for (text of kTexts) {
    396    const tm = ctx.measureText(text);
    397    text_width = tm.width;
    398    step = 30;
    399    if ('0px' == '10px') {
    400      step = 40;
    401    }
    402 
    403    offset = step;
    404    adjusted_offset = alignOffset(offset, text_width);
    405    tm_position = tm.getIndexFromOffset(adjusted_offset);
    406    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    407    assert_equals(tm_position,
    408                  doc_position,
    409                  "for " + text + " offset " + offset);
    410 
    411    offset = text_width / 2 - 10;
    412    adjusted_offset = alignOffset(offset, text_width);
    413    tm_position = tm.getIndexFromOffset(adjusted_offset);
    414    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    415    assert_equals(tm_position,
    416                  doc_position,
    417                  "for " + text + " offset " + offset);
    418 
    419    offset = text_width / 2 + 10;
    420    adjusted_offset = alignOffset(offset, text_width);
    421    tm_position = tm.getIndexFromOffset(adjusted_offset);
    422    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    423    assert_equals(tm_position,
    424                  doc_position,
    425                  "for " + text + " offset " + offset);
    426 
    427    offset = text_width - step;
    428    adjusted_offset = alignOffset(offset, text_width);
    429    tm_position = tm.getIndexFromOffset(adjusted_offset);
    430    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    431    assert_equals(tm_position,
    432                  doc_position,
    433                  "for " + text + " offset " + offset);
    434  }
    435 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and no-directional-override.");
    436 
    437 test(t => {
    438  const canvas = new OffscreenCanvas(100, 50);
    439  const ctx = canvas.getContext('2d');
    440 
    441  function alignOffset(offset, width) {
    442    if ('right' == 'center') {
    443      offset -= width / 2;
    444    } else if ('right' == 'right' ||
    445             ('ltr' == 'ltr' && 'right' == 'end') ||
    446             ('ltr' == 'rtl' && 'right' == 'start')) {
    447      offset -= width;
    448    }
    449    return offset;
    450  }
    451 
    452 
    453  function placeAndMeasureTextInDOM(text, text_width, point) {
    454    const el = document.createElement("p");
    455    el.innerHTML = text;
    456    el.style.font = '50px sans-serif';
    457    el.style.direction = 'ltr';
    458    el.style.letterSpacing = '0px';
    459    // Put the text top left to make offsets simpler.
    460    el.style.padding = '0px';
    461    el.style.margin = '0px';
    462    el.style.position = 'absolute';
    463    el.style.x = '0px';
    464    el.style.y = '0px';
    465    document.body.appendChild(el);
    466    text_bound = el.getBoundingClientRect();
    467    text_x = text_bound.x;
    468    text_y = text_bound.y + text_bound.height / 2;
    469 
    470    // Offset to the requested point determined by textAlign and direction.
    471    let text_align_dx = 0;
    472    if ('right' == 'center') {
    473      text_align_dx = text_width / 2;
    474    } else if ('right' == 'right' ||
    475             ('ltr' == 'ltr' && 'right' == 'end') ||
    476             ('ltr' == 'rtl' && 'right' == 'start')) {
    477      text_align_dx = text_width;
    478    }
    479    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
    480    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
    481 
    482    document.body.removeChild(el);
    483 
    484    return position.offset;
    485  }
    486 
    487  ctx.font = '50px sans-serif';
    488  ctx.direction = 'ltr';
    489  ctx.textAlign = 'right';
    490  ctx.letterSpacing = '0px';
    491 
    492  const kTexts = [
    493    'UNAVAILABLE',
    494    '🏁🎢🏁',
    495    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
    496    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
    497    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
    498    '--abcd__'
    499  ]
    500 
    501  for (text of kTexts) {
    502    const tm = ctx.measureText(text);
    503    text_width = tm.width;
    504    step = 30;
    505    if ('0px' == '10px') {
    506      step = 40;
    507    }
    508 
    509    offset = step;
    510    adjusted_offset = alignOffset(offset, text_width);
    511    tm_position = tm.getIndexFromOffset(adjusted_offset);
    512    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    513    assert_equals(tm_position,
    514                  doc_position,
    515                  "for " + text + " offset " + offset);
    516 
    517    offset = text_width / 2 - 10;
    518    adjusted_offset = alignOffset(offset, text_width);
    519    tm_position = tm.getIndexFromOffset(adjusted_offset);
    520    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    521    assert_equals(tm_position,
    522                  doc_position,
    523                  "for " + text + " offset " + offset);
    524 
    525    offset = text_width / 2 + 10;
    526    adjusted_offset = alignOffset(offset, text_width);
    527    tm_position = tm.getIndexFromOffset(adjusted_offset);
    528    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    529    assert_equals(tm_position,
    530                  doc_position,
    531                  "for " + text + " offset " + offset);
    532 
    533    offset = text_width - step;
    534    adjusted_offset = alignOffset(offset, text_width);
    535    tm_position = tm.getIndexFromOffset(adjusted_offset);
    536    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    537    assert_equals(tm_position,
    538                  doc_position,
    539                  "for " + text + " offset " + offset);
    540  }
    541 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and no-directional-override.");
    542 
    543 test(t => {
    544  const canvas = new OffscreenCanvas(100, 50);
    545  const ctx = canvas.getContext('2d');
    546 
    547  function alignOffset(offset, width) {
    548    if ('right' == 'center') {
    549      offset -= width / 2;
    550    } else if ('right' == 'right' ||
    551             ('rtl' == 'ltr' && 'right' == 'end') ||
    552             ('rtl' == 'rtl' && 'right' == 'start')) {
    553      offset -= width;
    554    }
    555    return offset;
    556  }
    557 
    558 
    559  function placeAndMeasureTextInDOM(text, text_width, point) {
    560    const el = document.createElement("p");
    561    el.innerHTML = text;
    562    el.style.font = '50px sans-serif';
    563    el.style.direction = 'rtl';
    564    el.style.letterSpacing = '0px';
    565    // Put the text top left to make offsets simpler.
    566    el.style.padding = '0px';
    567    el.style.margin = '0px';
    568    el.style.position = 'absolute';
    569    el.style.x = '0px';
    570    el.style.y = '0px';
    571    document.body.appendChild(el);
    572    text_bound = el.getBoundingClientRect();
    573    text_x = text_bound.x;
    574    text_y = text_bound.y + text_bound.height / 2;
    575 
    576    // Offset to the requested point determined by textAlign and direction.
    577    let text_align_dx = 0;
    578    if ('right' == 'center') {
    579      text_align_dx = text_width / 2;
    580    } else if ('right' == 'right' ||
    581             ('rtl' == 'ltr' && 'right' == 'end') ||
    582             ('rtl' == 'rtl' && 'right' == 'start')) {
    583      text_align_dx = text_width;
    584    }
    585    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
    586    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
    587 
    588    document.body.removeChild(el);
    589 
    590    return position.offset;
    591  }
    592 
    593  ctx.font = '50px sans-serif';
    594  ctx.direction = 'rtl';
    595  ctx.textAlign = 'right';
    596  ctx.letterSpacing = '0px';
    597 
    598  const kTexts = [
    599    'UNAVAILABLE',
    600    '🏁🎢🏁',
    601    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
    602    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
    603    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
    604    '--abcd__'
    605  ]
    606 
    607  for (text of kTexts) {
    608    const tm = ctx.measureText(text);
    609    text_width = tm.width;
    610    step = 30;
    611    if ('0px' == '10px') {
    612      step = 40;
    613    }
    614 
    615    offset = step;
    616    adjusted_offset = alignOffset(offset, text_width);
    617    tm_position = tm.getIndexFromOffset(adjusted_offset);
    618    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    619    assert_equals(tm_position,
    620                  doc_position,
    621                  "for " + text + " offset " + offset);
    622 
    623    offset = text_width / 2 - 10;
    624    adjusted_offset = alignOffset(offset, text_width);
    625    tm_position = tm.getIndexFromOffset(adjusted_offset);
    626    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    627    assert_equals(tm_position,
    628                  doc_position,
    629                  "for " + text + " offset " + offset);
    630 
    631    offset = text_width / 2 + 10;
    632    adjusted_offset = alignOffset(offset, text_width);
    633    tm_position = tm.getIndexFromOffset(adjusted_offset);
    634    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    635    assert_equals(tm_position,
    636                  doc_position,
    637                  "for " + text + " offset " + offset);
    638 
    639    offset = text_width - step;
    640    adjusted_offset = alignOffset(offset, text_width);
    641    tm_position = tm.getIndexFromOffset(adjusted_offset);
    642    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    643    assert_equals(tm_position,
    644                  doc_position,
    645                  "for " + text + " offset " + offset);
    646  }
    647 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and no-directional-override.");
    648 
    649 test(t => {
    650  const canvas = new OffscreenCanvas(100, 50);
    651  const ctx = canvas.getContext('2d');
    652 
    653  function alignOffset(offset, width) {
    654    if ('start' == 'center') {
    655      offset -= width / 2;
    656    } else if ('start' == 'right' ||
    657             ('ltr' == 'ltr' && 'start' == 'end') ||
    658             ('ltr' == 'rtl' && 'start' == 'start')) {
    659      offset -= width;
    660    }
    661    return offset;
    662  }
    663 
    664 
    665  function placeAndMeasureTextInDOM(text, text_width, point) {
    666    const el = document.createElement("p");
    667    el.innerHTML = text;
    668    el.style.font = '50px sans-serif';
    669    el.style.direction = 'ltr';
    670    el.style.letterSpacing = '0px';
    671    // Put the text top left to make offsets simpler.
    672    el.style.padding = '0px';
    673    el.style.margin = '0px';
    674    el.style.position = 'absolute';
    675    el.style.x = '0px';
    676    el.style.y = '0px';
    677    document.body.appendChild(el);
    678    text_bound = el.getBoundingClientRect();
    679    text_x = text_bound.x;
    680    text_y = text_bound.y + text_bound.height / 2;
    681 
    682    // Offset to the requested point determined by textAlign and direction.
    683    let text_align_dx = 0;
    684    if ('start' == 'center') {
    685      text_align_dx = text_width / 2;
    686    } else if ('start' == 'right' ||
    687             ('ltr' == 'ltr' && 'start' == 'end') ||
    688             ('ltr' == 'rtl' && 'start' == 'start')) {
    689      text_align_dx = text_width;
    690    }
    691    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
    692    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
    693 
    694    document.body.removeChild(el);
    695 
    696    return position.offset;
    697  }
    698 
    699  ctx.font = '50px sans-serif';
    700  ctx.direction = 'ltr';
    701  ctx.textAlign = 'start';
    702  ctx.letterSpacing = '0px';
    703 
    704  const kTexts = [
    705    'UNAVAILABLE',
    706    '🏁🎢🏁',
    707    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
    708    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
    709    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
    710    '--abcd__'
    711  ]
    712 
    713  for (text of kTexts) {
    714    const tm = ctx.measureText(text);
    715    text_width = tm.width;
    716    step = 30;
    717    if ('0px' == '10px') {
    718      step = 40;
    719    }
    720 
    721    offset = step;
    722    adjusted_offset = alignOffset(offset, text_width);
    723    tm_position = tm.getIndexFromOffset(adjusted_offset);
    724    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    725    assert_equals(tm_position,
    726                  doc_position,
    727                  "for " + text + " offset " + offset);
    728 
    729    offset = text_width / 2 - 10;
    730    adjusted_offset = alignOffset(offset, text_width);
    731    tm_position = tm.getIndexFromOffset(adjusted_offset);
    732    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    733    assert_equals(tm_position,
    734                  doc_position,
    735                  "for " + text + " offset " + offset);
    736 
    737    offset = text_width / 2 + 10;
    738    adjusted_offset = alignOffset(offset, text_width);
    739    tm_position = tm.getIndexFromOffset(adjusted_offset);
    740    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    741    assert_equals(tm_position,
    742                  doc_position,
    743                  "for " + text + " offset " + offset);
    744 
    745    offset = text_width - step;
    746    adjusted_offset = alignOffset(offset, text_width);
    747    tm_position = tm.getIndexFromOffset(adjusted_offset);
    748    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    749    assert_equals(tm_position,
    750                  doc_position,
    751                  "for " + text + " offset " + offset);
    752  }
    753 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and no-directional-override.");
    754 
    755 test(t => {
    756  const canvas = new OffscreenCanvas(100, 50);
    757  const ctx = canvas.getContext('2d');
    758 
    759  function alignOffset(offset, width) {
    760    if ('start' == 'center') {
    761      offset -= width / 2;
    762    } else if ('start' == 'right' ||
    763             ('rtl' == 'ltr' && 'start' == 'end') ||
    764             ('rtl' == 'rtl' && 'start' == 'start')) {
    765      offset -= width;
    766    }
    767    return offset;
    768  }
    769 
    770 
    771  function placeAndMeasureTextInDOM(text, text_width, point) {
    772    const el = document.createElement("p");
    773    el.innerHTML = text;
    774    el.style.font = '50px sans-serif';
    775    el.style.direction = 'rtl';
    776    el.style.letterSpacing = '0px';
    777    // Put the text top left to make offsets simpler.
    778    el.style.padding = '0px';
    779    el.style.margin = '0px';
    780    el.style.position = 'absolute';
    781    el.style.x = '0px';
    782    el.style.y = '0px';
    783    document.body.appendChild(el);
    784    text_bound = el.getBoundingClientRect();
    785    text_x = text_bound.x;
    786    text_y = text_bound.y + text_bound.height / 2;
    787 
    788    // Offset to the requested point determined by textAlign and direction.
    789    let text_align_dx = 0;
    790    if ('start' == 'center') {
    791      text_align_dx = text_width / 2;
    792    } else if ('start' == 'right' ||
    793             ('rtl' == 'ltr' && 'start' == 'end') ||
    794             ('rtl' == 'rtl' && 'start' == 'start')) {
    795      text_align_dx = text_width;
    796    }
    797    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
    798    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
    799 
    800    document.body.removeChild(el);
    801 
    802    return position.offset;
    803  }
    804 
    805  ctx.font = '50px sans-serif';
    806  ctx.direction = 'rtl';
    807  ctx.textAlign = 'start';
    808  ctx.letterSpacing = '0px';
    809 
    810  const kTexts = [
    811    'UNAVAILABLE',
    812    '🏁🎢🏁',
    813    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
    814    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
    815    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
    816    '--abcd__'
    817  ]
    818 
    819  for (text of kTexts) {
    820    const tm = ctx.measureText(text);
    821    text_width = tm.width;
    822    step = 30;
    823    if ('0px' == '10px') {
    824      step = 40;
    825    }
    826 
    827    offset = step;
    828    adjusted_offset = alignOffset(offset, text_width);
    829    tm_position = tm.getIndexFromOffset(adjusted_offset);
    830    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    831    assert_equals(tm_position,
    832                  doc_position,
    833                  "for " + text + " offset " + offset);
    834 
    835    offset = text_width / 2 - 10;
    836    adjusted_offset = alignOffset(offset, text_width);
    837    tm_position = tm.getIndexFromOffset(adjusted_offset);
    838    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    839    assert_equals(tm_position,
    840                  doc_position,
    841                  "for " + text + " offset " + offset);
    842 
    843    offset = text_width / 2 + 10;
    844    adjusted_offset = alignOffset(offset, text_width);
    845    tm_position = tm.getIndexFromOffset(adjusted_offset);
    846    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    847    assert_equals(tm_position,
    848                  doc_position,
    849                  "for " + text + " offset " + offset);
    850 
    851    offset = text_width - step;
    852    adjusted_offset = alignOffset(offset, text_width);
    853    tm_position = tm.getIndexFromOffset(adjusted_offset);
    854    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    855    assert_equals(tm_position,
    856                  doc_position,
    857                  "for " + text + " offset " + offset);
    858  }
    859 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and no-directional-override.");
    860 
    861 test(t => {
    862  const canvas = new OffscreenCanvas(100, 50);
    863  const ctx = canvas.getContext('2d');
    864 
    865  function alignOffset(offset, width) {
    866    if ('end' == 'center') {
    867      offset -= width / 2;
    868    } else if ('end' == 'right' ||
    869             ('ltr' == 'ltr' && 'end' == 'end') ||
    870             ('ltr' == 'rtl' && 'end' == 'start')) {
    871      offset -= width;
    872    }
    873    return offset;
    874  }
    875 
    876 
    877  function placeAndMeasureTextInDOM(text, text_width, point) {
    878    const el = document.createElement("p");
    879    el.innerHTML = text;
    880    el.style.font = '50px sans-serif';
    881    el.style.direction = 'ltr';
    882    el.style.letterSpacing = '0px';
    883    // Put the text top left to make offsets simpler.
    884    el.style.padding = '0px';
    885    el.style.margin = '0px';
    886    el.style.position = 'absolute';
    887    el.style.x = '0px';
    888    el.style.y = '0px';
    889    document.body.appendChild(el);
    890    text_bound = el.getBoundingClientRect();
    891    text_x = text_bound.x;
    892    text_y = text_bound.y + text_bound.height / 2;
    893 
    894    // Offset to the requested point determined by textAlign and direction.
    895    let text_align_dx = 0;
    896    if ('end' == 'center') {
    897      text_align_dx = text_width / 2;
    898    } else if ('end' == 'right' ||
    899             ('ltr' == 'ltr' && 'end' == 'end') ||
    900             ('ltr' == 'rtl' && 'end' == 'start')) {
    901      text_align_dx = text_width;
    902    }
    903    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
    904    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
    905 
    906    document.body.removeChild(el);
    907 
    908    return position.offset;
    909  }
    910 
    911  ctx.font = '50px sans-serif';
    912  ctx.direction = 'ltr';
    913  ctx.textAlign = 'end';
    914  ctx.letterSpacing = '0px';
    915 
    916  const kTexts = [
    917    'UNAVAILABLE',
    918    '🏁🎢🏁',
    919    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
    920    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
    921    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
    922    '--abcd__'
    923  ]
    924 
    925  for (text of kTexts) {
    926    const tm = ctx.measureText(text);
    927    text_width = tm.width;
    928    step = 30;
    929    if ('0px' == '10px') {
    930      step = 40;
    931    }
    932 
    933    offset = step;
    934    adjusted_offset = alignOffset(offset, text_width);
    935    tm_position = tm.getIndexFromOffset(adjusted_offset);
    936    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    937    assert_equals(tm_position,
    938                  doc_position,
    939                  "for " + text + " offset " + offset);
    940 
    941    offset = text_width / 2 - 10;
    942    adjusted_offset = alignOffset(offset, text_width);
    943    tm_position = tm.getIndexFromOffset(adjusted_offset);
    944    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    945    assert_equals(tm_position,
    946                  doc_position,
    947                  "for " + text + " offset " + offset);
    948 
    949    offset = text_width / 2 + 10;
    950    adjusted_offset = alignOffset(offset, text_width);
    951    tm_position = tm.getIndexFromOffset(adjusted_offset);
    952    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    953    assert_equals(tm_position,
    954                  doc_position,
    955                  "for " + text + " offset " + offset);
    956 
    957    offset = text_width - step;
    958    adjusted_offset = alignOffset(offset, text_width);
    959    tm_position = tm.getIndexFromOffset(adjusted_offset);
    960    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
    961    assert_equals(tm_position,
    962                  doc_position,
    963                  "for " + text + " offset " + offset);
    964  }
    965 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and no-directional-override.");
    966 
    967 test(t => {
    968  const canvas = new OffscreenCanvas(100, 50);
    969  const ctx = canvas.getContext('2d');
    970 
    971  function alignOffset(offset, width) {
    972    if ('end' == 'center') {
    973      offset -= width / 2;
    974    } else if ('end' == 'right' ||
    975             ('rtl' == 'ltr' && 'end' == 'end') ||
    976             ('rtl' == 'rtl' && 'end' == 'start')) {
    977      offset -= width;
    978    }
    979    return offset;
    980  }
    981 
    982 
    983  function placeAndMeasureTextInDOM(text, text_width, point) {
    984    const el = document.createElement("p");
    985    el.innerHTML = text;
    986    el.style.font = '50px sans-serif';
    987    el.style.direction = 'rtl';
    988    el.style.letterSpacing = '0px';
    989    // Put the text top left to make offsets simpler.
    990    el.style.padding = '0px';
    991    el.style.margin = '0px';
    992    el.style.position = 'absolute';
    993    el.style.x = '0px';
    994    el.style.y = '0px';
    995    document.body.appendChild(el);
    996    text_bound = el.getBoundingClientRect();
    997    text_x = text_bound.x;
    998    text_y = text_bound.y + text_bound.height / 2;
    999 
   1000    // Offset to the requested point determined by textAlign and direction.
   1001    let text_align_dx = 0;
   1002    if ('end' == 'center') {
   1003      text_align_dx = text_width / 2;
   1004    } else if ('end' == 'right' ||
   1005             ('rtl' == 'ltr' && 'end' == 'end') ||
   1006             ('rtl' == 'rtl' && 'end' == 'start')) {
   1007      text_align_dx = text_width;
   1008    }
   1009    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1010    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1011 
   1012    document.body.removeChild(el);
   1013 
   1014    return position.offset;
   1015  }
   1016 
   1017  ctx.font = '50px sans-serif';
   1018  ctx.direction = 'rtl';
   1019  ctx.textAlign = 'end';
   1020  ctx.letterSpacing = '0px';
   1021 
   1022  const kTexts = [
   1023    'UNAVAILABLE',
   1024    '🏁🎢🏁',
   1025    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1026    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1027    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1028    '--abcd__'
   1029  ]
   1030 
   1031  for (text of kTexts) {
   1032    const tm = ctx.measureText(text);
   1033    text_width = tm.width;
   1034    step = 30;
   1035    if ('0px' == '10px') {
   1036      step = 40;
   1037    }
   1038 
   1039    offset = step;
   1040    adjusted_offset = alignOffset(offset, text_width);
   1041    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1042    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1043    assert_equals(tm_position,
   1044                  doc_position,
   1045                  "for " + text + " offset " + offset);
   1046 
   1047    offset = text_width / 2 - 10;
   1048    adjusted_offset = alignOffset(offset, text_width);
   1049    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1050    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1051    assert_equals(tm_position,
   1052                  doc_position,
   1053                  "for " + text + " offset " + offset);
   1054 
   1055    offset = text_width / 2 + 10;
   1056    adjusted_offset = alignOffset(offset, text_width);
   1057    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1058    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1059    assert_equals(tm_position,
   1060                  doc_position,
   1061                  "for " + text + " offset " + offset);
   1062 
   1063    offset = text_width - step;
   1064    adjusted_offset = alignOffset(offset, text_width);
   1065    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1066    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1067    assert_equals(tm_position,
   1068                  doc_position,
   1069                  "for " + text + " offset " + offset);
   1070  }
   1071 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and no-directional-override.");
   1072 
   1073 test(t => {
   1074  const canvas = new OffscreenCanvas(100, 50);
   1075  const ctx = canvas.getContext('2d');
   1076 
   1077  function alignOffset(offset, width) {
   1078    if ('left' == 'center') {
   1079      offset -= width / 2;
   1080    } else if ('left' == 'right' ||
   1081             ('ltr' == 'ltr' && 'left' == 'end') ||
   1082             ('ltr' == 'rtl' && 'left' == 'start')) {
   1083      offset -= width;
   1084    }
   1085    return offset;
   1086  }
   1087 
   1088 
   1089  function placeAndMeasureTextInDOM(text, text_width, point) {
   1090    const el = document.createElement("p");
   1091    el.innerHTML = text;
   1092    el.style.font = '50px sans-serif';
   1093    el.style.direction = 'ltr';
   1094    el.style.letterSpacing = '10px';
   1095    // Put the text top left to make offsets simpler.
   1096    el.style.padding = '0px';
   1097    el.style.margin = '0px';
   1098    el.style.position = 'absolute';
   1099    el.style.x = '0px';
   1100    el.style.y = '0px';
   1101    document.body.appendChild(el);
   1102    text_bound = el.getBoundingClientRect();
   1103    text_x = text_bound.x;
   1104    text_y = text_bound.y + text_bound.height / 2;
   1105 
   1106    // Offset to the requested point determined by textAlign and direction.
   1107    let text_align_dx = 0;
   1108    if ('left' == 'center') {
   1109      text_align_dx = text_width / 2;
   1110    } else if ('left' == 'right' ||
   1111             ('ltr' == 'ltr' && 'left' == 'end') ||
   1112             ('ltr' == 'rtl' && 'left' == 'start')) {
   1113      text_align_dx = text_width;
   1114    }
   1115    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1116    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1117 
   1118    document.body.removeChild(el);
   1119 
   1120    return position.offset;
   1121  }
   1122 
   1123  ctx.font = '50px sans-serif';
   1124  ctx.direction = 'ltr';
   1125  ctx.textAlign = 'left';
   1126  ctx.letterSpacing = '10px';
   1127 
   1128  const kTexts = [
   1129    'UNAVAILABLE',
   1130    '🏁🎢🏁',
   1131    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1132    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1133    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1134    '--abcd__'
   1135  ]
   1136 
   1137  for (text of kTexts) {
   1138    const tm = ctx.measureText(text);
   1139    text_width = tm.width;
   1140    step = 30;
   1141    if ('10px' == '10px') {
   1142      step = 40;
   1143    }
   1144 
   1145    offset = step;
   1146    adjusted_offset = alignOffset(offset, text_width);
   1147    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1148    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1149    assert_equals(tm_position,
   1150                  doc_position,
   1151                  "for " + text + " offset " + offset);
   1152 
   1153    offset = text_width / 2 - 10;
   1154    adjusted_offset = alignOffset(offset, text_width);
   1155    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1156    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1157    assert_equals(tm_position,
   1158                  doc_position,
   1159                  "for " + text + " offset " + offset);
   1160 
   1161    offset = text_width / 2 + 10;
   1162    adjusted_offset = alignOffset(offset, text_width);
   1163    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1164    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1165    assert_equals(tm_position,
   1166                  doc_position,
   1167                  "for " + text + " offset " + offset);
   1168 
   1169    offset = text_width - step;
   1170    adjusted_offset = alignOffset(offset, text_width);
   1171    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1172    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1173    assert_equals(tm_position,
   1174                  doc_position,
   1175                  "for " + text + " offset " + offset);
   1176  }
   1177 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and no-directional-override.");
   1178 
   1179 test(t => {
   1180  const canvas = new OffscreenCanvas(100, 50);
   1181  const ctx = canvas.getContext('2d');
   1182 
   1183  function alignOffset(offset, width) {
   1184    if ('left' == 'center') {
   1185      offset -= width / 2;
   1186    } else if ('left' == 'right' ||
   1187             ('rtl' == 'ltr' && 'left' == 'end') ||
   1188             ('rtl' == 'rtl' && 'left' == 'start')) {
   1189      offset -= width;
   1190    }
   1191    return offset;
   1192  }
   1193 
   1194 
   1195  function placeAndMeasureTextInDOM(text, text_width, point) {
   1196    const el = document.createElement("p");
   1197    el.innerHTML = text;
   1198    el.style.font = '50px sans-serif';
   1199    el.style.direction = 'rtl';
   1200    el.style.letterSpacing = '10px';
   1201    // Put the text top left to make offsets simpler.
   1202    el.style.padding = '0px';
   1203    el.style.margin = '0px';
   1204    el.style.position = 'absolute';
   1205    el.style.x = '0px';
   1206    el.style.y = '0px';
   1207    document.body.appendChild(el);
   1208    text_bound = el.getBoundingClientRect();
   1209    text_x = text_bound.x;
   1210    text_y = text_bound.y + text_bound.height / 2;
   1211 
   1212    // Offset to the requested point determined by textAlign and direction.
   1213    let text_align_dx = 0;
   1214    if ('left' == 'center') {
   1215      text_align_dx = text_width / 2;
   1216    } else if ('left' == 'right' ||
   1217             ('rtl' == 'ltr' && 'left' == 'end') ||
   1218             ('rtl' == 'rtl' && 'left' == 'start')) {
   1219      text_align_dx = text_width;
   1220    }
   1221    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1222    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1223 
   1224    document.body.removeChild(el);
   1225 
   1226    return position.offset;
   1227  }
   1228 
   1229  ctx.font = '50px sans-serif';
   1230  ctx.direction = 'rtl';
   1231  ctx.textAlign = 'left';
   1232  ctx.letterSpacing = '10px';
   1233 
   1234  const kTexts = [
   1235    'UNAVAILABLE',
   1236    '🏁🎢🏁',
   1237    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1238    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1239    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1240    '--abcd__'
   1241  ]
   1242 
   1243  for (text of kTexts) {
   1244    const tm = ctx.measureText(text);
   1245    text_width = tm.width;
   1246    step = 30;
   1247    if ('10px' == '10px') {
   1248      step = 40;
   1249    }
   1250 
   1251    offset = step;
   1252    adjusted_offset = alignOffset(offset, text_width);
   1253    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1254    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1255    assert_equals(tm_position,
   1256                  doc_position,
   1257                  "for " + text + " offset " + offset);
   1258 
   1259    offset = text_width / 2 - 10;
   1260    adjusted_offset = alignOffset(offset, text_width);
   1261    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1262    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1263    assert_equals(tm_position,
   1264                  doc_position,
   1265                  "for " + text + " offset " + offset);
   1266 
   1267    offset = text_width / 2 + 10;
   1268    adjusted_offset = alignOffset(offset, text_width);
   1269    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1270    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1271    assert_equals(tm_position,
   1272                  doc_position,
   1273                  "for " + text + " offset " + offset);
   1274 
   1275    offset = text_width - step;
   1276    adjusted_offset = alignOffset(offset, text_width);
   1277    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1278    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1279    assert_equals(tm_position,
   1280                  doc_position,
   1281                  "for " + text + " offset " + offset);
   1282  }
   1283 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and no-directional-override.");
   1284 
   1285 test(t => {
   1286  const canvas = new OffscreenCanvas(100, 50);
   1287  const ctx = canvas.getContext('2d');
   1288 
   1289  function alignOffset(offset, width) {
   1290    if ('center' == 'center') {
   1291      offset -= width / 2;
   1292    } else if ('center' == 'right' ||
   1293             ('ltr' == 'ltr' && 'center' == 'end') ||
   1294             ('ltr' == 'rtl' && 'center' == 'start')) {
   1295      offset -= width;
   1296    }
   1297    return offset;
   1298  }
   1299 
   1300 
   1301  function placeAndMeasureTextInDOM(text, text_width, point) {
   1302    const el = document.createElement("p");
   1303    el.innerHTML = text;
   1304    el.style.font = '50px sans-serif';
   1305    el.style.direction = 'ltr';
   1306    el.style.letterSpacing = '10px';
   1307    // Put the text top left to make offsets simpler.
   1308    el.style.padding = '0px';
   1309    el.style.margin = '0px';
   1310    el.style.position = 'absolute';
   1311    el.style.x = '0px';
   1312    el.style.y = '0px';
   1313    document.body.appendChild(el);
   1314    text_bound = el.getBoundingClientRect();
   1315    text_x = text_bound.x;
   1316    text_y = text_bound.y + text_bound.height / 2;
   1317 
   1318    // Offset to the requested point determined by textAlign and direction.
   1319    let text_align_dx = 0;
   1320    if ('center' == 'center') {
   1321      text_align_dx = text_width / 2;
   1322    } else if ('center' == 'right' ||
   1323             ('ltr' == 'ltr' && 'center' == 'end') ||
   1324             ('ltr' == 'rtl' && 'center' == 'start')) {
   1325      text_align_dx = text_width;
   1326    }
   1327    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1328    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1329 
   1330    document.body.removeChild(el);
   1331 
   1332    return position.offset;
   1333  }
   1334 
   1335  ctx.font = '50px sans-serif';
   1336  ctx.direction = 'ltr';
   1337  ctx.textAlign = 'center';
   1338  ctx.letterSpacing = '10px';
   1339 
   1340  const kTexts = [
   1341    'UNAVAILABLE',
   1342    '🏁🎢🏁',
   1343    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1344    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1345    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1346    '--abcd__'
   1347  ]
   1348 
   1349  for (text of kTexts) {
   1350    const tm = ctx.measureText(text);
   1351    text_width = tm.width;
   1352    step = 30;
   1353    if ('10px' == '10px') {
   1354      step = 40;
   1355    }
   1356 
   1357    offset = step;
   1358    adjusted_offset = alignOffset(offset, text_width);
   1359    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1360    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1361    assert_equals(tm_position,
   1362                  doc_position,
   1363                  "for " + text + " offset " + offset);
   1364 
   1365    offset = text_width / 2 - 10;
   1366    adjusted_offset = alignOffset(offset, text_width);
   1367    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1368    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1369    assert_equals(tm_position,
   1370                  doc_position,
   1371                  "for " + text + " offset " + offset);
   1372 
   1373    offset = text_width / 2 + 10;
   1374    adjusted_offset = alignOffset(offset, text_width);
   1375    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1376    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1377    assert_equals(tm_position,
   1378                  doc_position,
   1379                  "for " + text + " offset " + offset);
   1380 
   1381    offset = text_width - step;
   1382    adjusted_offset = alignOffset(offset, text_width);
   1383    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1384    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1385    assert_equals(tm_position,
   1386                  doc_position,
   1387                  "for " + text + " offset " + offset);
   1388  }
   1389 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and no-directional-override.");
   1390 
   1391 test(t => {
   1392  const canvas = new OffscreenCanvas(100, 50);
   1393  const ctx = canvas.getContext('2d');
   1394 
   1395  function alignOffset(offset, width) {
   1396    if ('center' == 'center') {
   1397      offset -= width / 2;
   1398    } else if ('center' == 'right' ||
   1399             ('rtl' == 'ltr' && 'center' == 'end') ||
   1400             ('rtl' == 'rtl' && 'center' == 'start')) {
   1401      offset -= width;
   1402    }
   1403    return offset;
   1404  }
   1405 
   1406 
   1407  function placeAndMeasureTextInDOM(text, text_width, point) {
   1408    const el = document.createElement("p");
   1409    el.innerHTML = text;
   1410    el.style.font = '50px sans-serif';
   1411    el.style.direction = 'rtl';
   1412    el.style.letterSpacing = '10px';
   1413    // Put the text top left to make offsets simpler.
   1414    el.style.padding = '0px';
   1415    el.style.margin = '0px';
   1416    el.style.position = 'absolute';
   1417    el.style.x = '0px';
   1418    el.style.y = '0px';
   1419    document.body.appendChild(el);
   1420    text_bound = el.getBoundingClientRect();
   1421    text_x = text_bound.x;
   1422    text_y = text_bound.y + text_bound.height / 2;
   1423 
   1424    // Offset to the requested point determined by textAlign and direction.
   1425    let text_align_dx = 0;
   1426    if ('center' == 'center') {
   1427      text_align_dx = text_width / 2;
   1428    } else if ('center' == 'right' ||
   1429             ('rtl' == 'ltr' && 'center' == 'end') ||
   1430             ('rtl' == 'rtl' && 'center' == 'start')) {
   1431      text_align_dx = text_width;
   1432    }
   1433    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1434    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1435 
   1436    document.body.removeChild(el);
   1437 
   1438    return position.offset;
   1439  }
   1440 
   1441  ctx.font = '50px sans-serif';
   1442  ctx.direction = 'rtl';
   1443  ctx.textAlign = 'center';
   1444  ctx.letterSpacing = '10px';
   1445 
   1446  const kTexts = [
   1447    'UNAVAILABLE',
   1448    '🏁🎢🏁',
   1449    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1450    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1451    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1452    '--abcd__'
   1453  ]
   1454 
   1455  for (text of kTexts) {
   1456    const tm = ctx.measureText(text);
   1457    text_width = tm.width;
   1458    step = 30;
   1459    if ('10px' == '10px') {
   1460      step = 40;
   1461    }
   1462 
   1463    offset = step;
   1464    adjusted_offset = alignOffset(offset, text_width);
   1465    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1466    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1467    assert_equals(tm_position,
   1468                  doc_position,
   1469                  "for " + text + " offset " + offset);
   1470 
   1471    offset = text_width / 2 - 10;
   1472    adjusted_offset = alignOffset(offset, text_width);
   1473    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1474    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1475    assert_equals(tm_position,
   1476                  doc_position,
   1477                  "for " + text + " offset " + offset);
   1478 
   1479    offset = text_width / 2 + 10;
   1480    adjusted_offset = alignOffset(offset, text_width);
   1481    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1482    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1483    assert_equals(tm_position,
   1484                  doc_position,
   1485                  "for " + text + " offset " + offset);
   1486 
   1487    offset = text_width - step;
   1488    adjusted_offset = alignOffset(offset, text_width);
   1489    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1490    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1491    assert_equals(tm_position,
   1492                  doc_position,
   1493                  "for " + text + " offset " + offset);
   1494  }
   1495 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and no-directional-override.");
   1496 
   1497 test(t => {
   1498  const canvas = new OffscreenCanvas(100, 50);
   1499  const ctx = canvas.getContext('2d');
   1500 
   1501  function alignOffset(offset, width) {
   1502    if ('right' == 'center') {
   1503      offset -= width / 2;
   1504    } else if ('right' == 'right' ||
   1505             ('ltr' == 'ltr' && 'right' == 'end') ||
   1506             ('ltr' == 'rtl' && 'right' == 'start')) {
   1507      offset -= width;
   1508    }
   1509    return offset;
   1510  }
   1511 
   1512 
   1513  function placeAndMeasureTextInDOM(text, text_width, point) {
   1514    const el = document.createElement("p");
   1515    el.innerHTML = text;
   1516    el.style.font = '50px sans-serif';
   1517    el.style.direction = 'ltr';
   1518    el.style.letterSpacing = '10px';
   1519    // Put the text top left to make offsets simpler.
   1520    el.style.padding = '0px';
   1521    el.style.margin = '0px';
   1522    el.style.position = 'absolute';
   1523    el.style.x = '0px';
   1524    el.style.y = '0px';
   1525    document.body.appendChild(el);
   1526    text_bound = el.getBoundingClientRect();
   1527    text_x = text_bound.x;
   1528    text_y = text_bound.y + text_bound.height / 2;
   1529 
   1530    // Offset to the requested point determined by textAlign and direction.
   1531    let text_align_dx = 0;
   1532    if ('right' == 'center') {
   1533      text_align_dx = text_width / 2;
   1534    } else if ('right' == 'right' ||
   1535             ('ltr' == 'ltr' && 'right' == 'end') ||
   1536             ('ltr' == 'rtl' && 'right' == 'start')) {
   1537      text_align_dx = text_width;
   1538    }
   1539    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1540    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1541 
   1542    document.body.removeChild(el);
   1543 
   1544    return position.offset;
   1545  }
   1546 
   1547  ctx.font = '50px sans-serif';
   1548  ctx.direction = 'ltr';
   1549  ctx.textAlign = 'right';
   1550  ctx.letterSpacing = '10px';
   1551 
   1552  const kTexts = [
   1553    'UNAVAILABLE',
   1554    '🏁🎢🏁',
   1555    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1556    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1557    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1558    '--abcd__'
   1559  ]
   1560 
   1561  for (text of kTexts) {
   1562    const tm = ctx.measureText(text);
   1563    text_width = tm.width;
   1564    step = 30;
   1565    if ('10px' == '10px') {
   1566      step = 40;
   1567    }
   1568 
   1569    offset = step;
   1570    adjusted_offset = alignOffset(offset, text_width);
   1571    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1572    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1573    assert_equals(tm_position,
   1574                  doc_position,
   1575                  "for " + text + " offset " + offset);
   1576 
   1577    offset = text_width / 2 - 10;
   1578    adjusted_offset = alignOffset(offset, text_width);
   1579    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1580    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1581    assert_equals(tm_position,
   1582                  doc_position,
   1583                  "for " + text + " offset " + offset);
   1584 
   1585    offset = text_width / 2 + 10;
   1586    adjusted_offset = alignOffset(offset, text_width);
   1587    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1588    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1589    assert_equals(tm_position,
   1590                  doc_position,
   1591                  "for " + text + " offset " + offset);
   1592 
   1593    offset = text_width - step;
   1594    adjusted_offset = alignOffset(offset, text_width);
   1595    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1596    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1597    assert_equals(tm_position,
   1598                  doc_position,
   1599                  "for " + text + " offset " + offset);
   1600  }
   1601 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and no-directional-override.");
   1602 
   1603 test(t => {
   1604  const canvas = new OffscreenCanvas(100, 50);
   1605  const ctx = canvas.getContext('2d');
   1606 
   1607  function alignOffset(offset, width) {
   1608    if ('right' == 'center') {
   1609      offset -= width / 2;
   1610    } else if ('right' == 'right' ||
   1611             ('rtl' == 'ltr' && 'right' == 'end') ||
   1612             ('rtl' == 'rtl' && 'right' == 'start')) {
   1613      offset -= width;
   1614    }
   1615    return offset;
   1616  }
   1617 
   1618 
   1619  function placeAndMeasureTextInDOM(text, text_width, point) {
   1620    const el = document.createElement("p");
   1621    el.innerHTML = text;
   1622    el.style.font = '50px sans-serif';
   1623    el.style.direction = 'rtl';
   1624    el.style.letterSpacing = '10px';
   1625    // Put the text top left to make offsets simpler.
   1626    el.style.padding = '0px';
   1627    el.style.margin = '0px';
   1628    el.style.position = 'absolute';
   1629    el.style.x = '0px';
   1630    el.style.y = '0px';
   1631    document.body.appendChild(el);
   1632    text_bound = el.getBoundingClientRect();
   1633    text_x = text_bound.x;
   1634    text_y = text_bound.y + text_bound.height / 2;
   1635 
   1636    // Offset to the requested point determined by textAlign and direction.
   1637    let text_align_dx = 0;
   1638    if ('right' == 'center') {
   1639      text_align_dx = text_width / 2;
   1640    } else if ('right' == 'right' ||
   1641             ('rtl' == 'ltr' && 'right' == 'end') ||
   1642             ('rtl' == 'rtl' && 'right' == 'start')) {
   1643      text_align_dx = text_width;
   1644    }
   1645    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1646    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1647 
   1648    document.body.removeChild(el);
   1649 
   1650    return position.offset;
   1651  }
   1652 
   1653  ctx.font = '50px sans-serif';
   1654  ctx.direction = 'rtl';
   1655  ctx.textAlign = 'right';
   1656  ctx.letterSpacing = '10px';
   1657 
   1658  const kTexts = [
   1659    'UNAVAILABLE',
   1660    '🏁🎢🏁',
   1661    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1662    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1663    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1664    '--abcd__'
   1665  ]
   1666 
   1667  for (text of kTexts) {
   1668    const tm = ctx.measureText(text);
   1669    text_width = tm.width;
   1670    step = 30;
   1671    if ('10px' == '10px') {
   1672      step = 40;
   1673    }
   1674 
   1675    offset = step;
   1676    adjusted_offset = alignOffset(offset, text_width);
   1677    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1678    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1679    assert_equals(tm_position,
   1680                  doc_position,
   1681                  "for " + text + " offset " + offset);
   1682 
   1683    offset = text_width / 2 - 10;
   1684    adjusted_offset = alignOffset(offset, text_width);
   1685    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1686    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1687    assert_equals(tm_position,
   1688                  doc_position,
   1689                  "for " + text + " offset " + offset);
   1690 
   1691    offset = text_width / 2 + 10;
   1692    adjusted_offset = alignOffset(offset, text_width);
   1693    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1694    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1695    assert_equals(tm_position,
   1696                  doc_position,
   1697                  "for " + text + " offset " + offset);
   1698 
   1699    offset = text_width - step;
   1700    adjusted_offset = alignOffset(offset, text_width);
   1701    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1702    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1703    assert_equals(tm_position,
   1704                  doc_position,
   1705                  "for " + text + " offset " + offset);
   1706  }
   1707 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and no-directional-override.");
   1708 
   1709 test(t => {
   1710  const canvas = new OffscreenCanvas(100, 50);
   1711  const ctx = canvas.getContext('2d');
   1712 
   1713  function alignOffset(offset, width) {
   1714    if ('start' == 'center') {
   1715      offset -= width / 2;
   1716    } else if ('start' == 'right' ||
   1717             ('ltr' == 'ltr' && 'start' == 'end') ||
   1718             ('ltr' == 'rtl' && 'start' == 'start')) {
   1719      offset -= width;
   1720    }
   1721    return offset;
   1722  }
   1723 
   1724 
   1725  function placeAndMeasureTextInDOM(text, text_width, point) {
   1726    const el = document.createElement("p");
   1727    el.innerHTML = text;
   1728    el.style.font = '50px sans-serif';
   1729    el.style.direction = 'ltr';
   1730    el.style.letterSpacing = '10px';
   1731    // Put the text top left to make offsets simpler.
   1732    el.style.padding = '0px';
   1733    el.style.margin = '0px';
   1734    el.style.position = 'absolute';
   1735    el.style.x = '0px';
   1736    el.style.y = '0px';
   1737    document.body.appendChild(el);
   1738    text_bound = el.getBoundingClientRect();
   1739    text_x = text_bound.x;
   1740    text_y = text_bound.y + text_bound.height / 2;
   1741 
   1742    // Offset to the requested point determined by textAlign and direction.
   1743    let text_align_dx = 0;
   1744    if ('start' == 'center') {
   1745      text_align_dx = text_width / 2;
   1746    } else if ('start' == 'right' ||
   1747             ('ltr' == 'ltr' && 'start' == 'end') ||
   1748             ('ltr' == 'rtl' && 'start' == 'start')) {
   1749      text_align_dx = text_width;
   1750    }
   1751    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1752    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1753 
   1754    document.body.removeChild(el);
   1755 
   1756    return position.offset;
   1757  }
   1758 
   1759  ctx.font = '50px sans-serif';
   1760  ctx.direction = 'ltr';
   1761  ctx.textAlign = 'start';
   1762  ctx.letterSpacing = '10px';
   1763 
   1764  const kTexts = [
   1765    'UNAVAILABLE',
   1766    '🏁🎢🏁',
   1767    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1768    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1769    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1770    '--abcd__'
   1771  ]
   1772 
   1773  for (text of kTexts) {
   1774    const tm = ctx.measureText(text);
   1775    text_width = tm.width;
   1776    step = 30;
   1777    if ('10px' == '10px') {
   1778      step = 40;
   1779    }
   1780 
   1781    offset = step;
   1782    adjusted_offset = alignOffset(offset, text_width);
   1783    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1784    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1785    assert_equals(tm_position,
   1786                  doc_position,
   1787                  "for " + text + " offset " + offset);
   1788 
   1789    offset = text_width / 2 - 10;
   1790    adjusted_offset = alignOffset(offset, text_width);
   1791    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1792    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1793    assert_equals(tm_position,
   1794                  doc_position,
   1795                  "for " + text + " offset " + offset);
   1796 
   1797    offset = text_width / 2 + 10;
   1798    adjusted_offset = alignOffset(offset, text_width);
   1799    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1800    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1801    assert_equals(tm_position,
   1802                  doc_position,
   1803                  "for " + text + " offset " + offset);
   1804 
   1805    offset = text_width - step;
   1806    adjusted_offset = alignOffset(offset, text_width);
   1807    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1808    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1809    assert_equals(tm_position,
   1810                  doc_position,
   1811                  "for " + text + " offset " + offset);
   1812  }
   1813 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and no-directional-override.");
   1814 
   1815 test(t => {
   1816  const canvas = new OffscreenCanvas(100, 50);
   1817  const ctx = canvas.getContext('2d');
   1818 
   1819  function alignOffset(offset, width) {
   1820    if ('start' == 'center') {
   1821      offset -= width / 2;
   1822    } else if ('start' == 'right' ||
   1823             ('rtl' == 'ltr' && 'start' == 'end') ||
   1824             ('rtl' == 'rtl' && 'start' == 'start')) {
   1825      offset -= width;
   1826    }
   1827    return offset;
   1828  }
   1829 
   1830 
   1831  function placeAndMeasureTextInDOM(text, text_width, point) {
   1832    const el = document.createElement("p");
   1833    el.innerHTML = text;
   1834    el.style.font = '50px sans-serif';
   1835    el.style.direction = 'rtl';
   1836    el.style.letterSpacing = '10px';
   1837    // Put the text top left to make offsets simpler.
   1838    el.style.padding = '0px';
   1839    el.style.margin = '0px';
   1840    el.style.position = 'absolute';
   1841    el.style.x = '0px';
   1842    el.style.y = '0px';
   1843    document.body.appendChild(el);
   1844    text_bound = el.getBoundingClientRect();
   1845    text_x = text_bound.x;
   1846    text_y = text_bound.y + text_bound.height / 2;
   1847 
   1848    // Offset to the requested point determined by textAlign and direction.
   1849    let text_align_dx = 0;
   1850    if ('start' == 'center') {
   1851      text_align_dx = text_width / 2;
   1852    } else if ('start' == 'right' ||
   1853             ('rtl' == 'ltr' && 'start' == 'end') ||
   1854             ('rtl' == 'rtl' && 'start' == 'start')) {
   1855      text_align_dx = text_width;
   1856    }
   1857    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1858    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1859 
   1860    document.body.removeChild(el);
   1861 
   1862    return position.offset;
   1863  }
   1864 
   1865  ctx.font = '50px sans-serif';
   1866  ctx.direction = 'rtl';
   1867  ctx.textAlign = 'start';
   1868  ctx.letterSpacing = '10px';
   1869 
   1870  const kTexts = [
   1871    'UNAVAILABLE',
   1872    '🏁🎢🏁',
   1873    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1874    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1875    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1876    '--abcd__'
   1877  ]
   1878 
   1879  for (text of kTexts) {
   1880    const tm = ctx.measureText(text);
   1881    text_width = tm.width;
   1882    step = 30;
   1883    if ('10px' == '10px') {
   1884      step = 40;
   1885    }
   1886 
   1887    offset = step;
   1888    adjusted_offset = alignOffset(offset, text_width);
   1889    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1890    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1891    assert_equals(tm_position,
   1892                  doc_position,
   1893                  "for " + text + " offset " + offset);
   1894 
   1895    offset = text_width / 2 - 10;
   1896    adjusted_offset = alignOffset(offset, text_width);
   1897    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1898    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1899    assert_equals(tm_position,
   1900                  doc_position,
   1901                  "for " + text + " offset " + offset);
   1902 
   1903    offset = text_width / 2 + 10;
   1904    adjusted_offset = alignOffset(offset, text_width);
   1905    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1906    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1907    assert_equals(tm_position,
   1908                  doc_position,
   1909                  "for " + text + " offset " + offset);
   1910 
   1911    offset = text_width - step;
   1912    adjusted_offset = alignOffset(offset, text_width);
   1913    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1914    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1915    assert_equals(tm_position,
   1916                  doc_position,
   1917                  "for " + text + " offset " + offset);
   1918  }
   1919 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and no-directional-override.");
   1920 
   1921 test(t => {
   1922  const canvas = new OffscreenCanvas(100, 50);
   1923  const ctx = canvas.getContext('2d');
   1924 
   1925  function alignOffset(offset, width) {
   1926    if ('end' == 'center') {
   1927      offset -= width / 2;
   1928    } else if ('end' == 'right' ||
   1929             ('ltr' == 'ltr' && 'end' == 'end') ||
   1930             ('ltr' == 'rtl' && 'end' == 'start')) {
   1931      offset -= width;
   1932    }
   1933    return offset;
   1934  }
   1935 
   1936 
   1937  function placeAndMeasureTextInDOM(text, text_width, point) {
   1938    const el = document.createElement("p");
   1939    el.innerHTML = text;
   1940    el.style.font = '50px sans-serif';
   1941    el.style.direction = 'ltr';
   1942    el.style.letterSpacing = '10px';
   1943    // Put the text top left to make offsets simpler.
   1944    el.style.padding = '0px';
   1945    el.style.margin = '0px';
   1946    el.style.position = 'absolute';
   1947    el.style.x = '0px';
   1948    el.style.y = '0px';
   1949    document.body.appendChild(el);
   1950    text_bound = el.getBoundingClientRect();
   1951    text_x = text_bound.x;
   1952    text_y = text_bound.y + text_bound.height / 2;
   1953 
   1954    // Offset to the requested point determined by textAlign and direction.
   1955    let text_align_dx = 0;
   1956    if ('end' == 'center') {
   1957      text_align_dx = text_width / 2;
   1958    } else if ('end' == 'right' ||
   1959             ('ltr' == 'ltr' && 'end' == 'end') ||
   1960             ('ltr' == 'rtl' && 'end' == 'start')) {
   1961      text_align_dx = text_width;
   1962    }
   1963    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   1964    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   1965 
   1966    document.body.removeChild(el);
   1967 
   1968    return position.offset;
   1969  }
   1970 
   1971  ctx.font = '50px sans-serif';
   1972  ctx.direction = 'ltr';
   1973  ctx.textAlign = 'end';
   1974  ctx.letterSpacing = '10px';
   1975 
   1976  const kTexts = [
   1977    'UNAVAILABLE',
   1978    '🏁🎢🏁',
   1979    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   1980    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   1981    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   1982    '--abcd__'
   1983  ]
   1984 
   1985  for (text of kTexts) {
   1986    const tm = ctx.measureText(text);
   1987    text_width = tm.width;
   1988    step = 30;
   1989    if ('10px' == '10px') {
   1990      step = 40;
   1991    }
   1992 
   1993    offset = step;
   1994    adjusted_offset = alignOffset(offset, text_width);
   1995    tm_position = tm.getIndexFromOffset(adjusted_offset);
   1996    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   1997    assert_equals(tm_position,
   1998                  doc_position,
   1999                  "for " + text + " offset " + offset);
   2000 
   2001    offset = text_width / 2 - 10;
   2002    adjusted_offset = alignOffset(offset, text_width);
   2003    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2004    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2005    assert_equals(tm_position,
   2006                  doc_position,
   2007                  "for " + text + " offset " + offset);
   2008 
   2009    offset = text_width / 2 + 10;
   2010    adjusted_offset = alignOffset(offset, text_width);
   2011    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2012    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2013    assert_equals(tm_position,
   2014                  doc_position,
   2015                  "for " + text + " offset " + offset);
   2016 
   2017    offset = text_width - step;
   2018    adjusted_offset = alignOffset(offset, text_width);
   2019    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2020    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2021    assert_equals(tm_position,
   2022                  doc_position,
   2023                  "for " + text + " offset " + offset);
   2024  }
   2025 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and no-directional-override.");
   2026 
   2027 test(t => {
   2028  const canvas = new OffscreenCanvas(100, 50);
   2029  const ctx = canvas.getContext('2d');
   2030 
   2031  function alignOffset(offset, width) {
   2032    if ('end' == 'center') {
   2033      offset -= width / 2;
   2034    } else if ('end' == 'right' ||
   2035             ('rtl' == 'ltr' && 'end' == 'end') ||
   2036             ('rtl' == 'rtl' && 'end' == 'start')) {
   2037      offset -= width;
   2038    }
   2039    return offset;
   2040  }
   2041 
   2042 
   2043  function placeAndMeasureTextInDOM(text, text_width, point) {
   2044    const el = document.createElement("p");
   2045    el.innerHTML = text;
   2046    el.style.font = '50px sans-serif';
   2047    el.style.direction = 'rtl';
   2048    el.style.letterSpacing = '10px';
   2049    // Put the text top left to make offsets simpler.
   2050    el.style.padding = '0px';
   2051    el.style.margin = '0px';
   2052    el.style.position = 'absolute';
   2053    el.style.x = '0px';
   2054    el.style.y = '0px';
   2055    document.body.appendChild(el);
   2056    text_bound = el.getBoundingClientRect();
   2057    text_x = text_bound.x;
   2058    text_y = text_bound.y + text_bound.height / 2;
   2059 
   2060    // Offset to the requested point determined by textAlign and direction.
   2061    let text_align_dx = 0;
   2062    if ('end' == 'center') {
   2063      text_align_dx = text_width / 2;
   2064    } else if ('end' == 'right' ||
   2065             ('rtl' == 'ltr' && 'end' == 'end') ||
   2066             ('rtl' == 'rtl' && 'end' == 'start')) {
   2067      text_align_dx = text_width;
   2068    }
   2069    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   2070    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   2071 
   2072    document.body.removeChild(el);
   2073 
   2074    return position.offset;
   2075  }
   2076 
   2077  ctx.font = '50px sans-serif';
   2078  ctx.direction = 'rtl';
   2079  ctx.textAlign = 'end';
   2080  ctx.letterSpacing = '10px';
   2081 
   2082  const kTexts = [
   2083    'UNAVAILABLE',
   2084    '🏁🎢🏁',
   2085    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   2086    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   2087    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   2088    '--abcd__'
   2089  ]
   2090 
   2091  for (text of kTexts) {
   2092    const tm = ctx.measureText(text);
   2093    text_width = tm.width;
   2094    step = 30;
   2095    if ('10px' == '10px') {
   2096      step = 40;
   2097    }
   2098 
   2099    offset = step;
   2100    adjusted_offset = alignOffset(offset, text_width);
   2101    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2102    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2103    assert_equals(tm_position,
   2104                  doc_position,
   2105                  "for " + text + " offset " + offset);
   2106 
   2107    offset = text_width / 2 - 10;
   2108    adjusted_offset = alignOffset(offset, text_width);
   2109    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2110    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2111    assert_equals(tm_position,
   2112                  doc_position,
   2113                  "for " + text + " offset " + offset);
   2114 
   2115    offset = text_width / 2 + 10;
   2116    adjusted_offset = alignOffset(offset, text_width);
   2117    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2118    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2119    assert_equals(tm_position,
   2120                  doc_position,
   2121                  "for " + text + " offset " + offset);
   2122 
   2123    offset = text_width - step;
   2124    adjusted_offset = alignOffset(offset, text_width);
   2125    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2126    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2127    assert_equals(tm_position,
   2128                  doc_position,
   2129                  "for " + text + " offset " + offset);
   2130  }
   2131 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and no-directional-override.");
   2132 
   2133 test(t => {
   2134  const canvas = new OffscreenCanvas(100, 50);
   2135  const ctx = canvas.getContext('2d');
   2136 
   2137  function alignOffset(offset, width) {
   2138    if ('left' == 'center') {
   2139      offset -= width / 2;
   2140    } else if ('left' == 'right' ||
   2141             ('ltr' == 'ltr' && 'left' == 'end') ||
   2142             ('ltr' == 'rtl' && 'left' == 'start')) {
   2143      offset -= width;
   2144    }
   2145    return offset;
   2146  }
   2147 
   2148  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2149    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2150    const LTR_OVERRIDE = '\u202D';
   2151    const RTL_OVERRIDE = '\u202E';
   2152    const OVERRIDE_END = '\u202C';
   2153    if (direction_is_ltr) {
   2154      return LTR_OVERRIDE + text + OVERRIDE_END;
   2155    }
   2156    return RTL_OVERRIDE + text + OVERRIDE_END;
   2157  }
   2158 
   2159  function placeAndMeasureTextInDOM(text, text_width, point) {
   2160    const el = document.createElement("p");
   2161    el.innerHTML = text;
   2162    el.style.font = '50px sans-serif';
   2163    el.style.direction = 'ltr';
   2164    el.style.letterSpacing = '0px';
   2165    // Put the text top left to make offsets simpler.
   2166    el.style.padding = '0px';
   2167    el.style.margin = '0px';
   2168    el.style.position = 'absolute';
   2169    el.style.x = '0px';
   2170    el.style.y = '0px';
   2171    document.body.appendChild(el);
   2172    text_bound = el.getBoundingClientRect();
   2173    text_x = text_bound.x;
   2174    text_y = text_bound.y + text_bound.height / 2;
   2175 
   2176    // Offset to the requested point determined by textAlign and direction.
   2177    let text_align_dx = 0;
   2178    if ('left' == 'center') {
   2179      text_align_dx = text_width / 2;
   2180    } else if ('left' == 'right' ||
   2181             ('ltr' == 'ltr' && 'left' == 'end') ||
   2182             ('ltr' == 'rtl' && 'left' == 'start')) {
   2183      text_align_dx = text_width;
   2184    }
   2185    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   2186    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   2187 
   2188    document.body.removeChild(el);
   2189 
   2190    return position.offset;
   2191  }
   2192 
   2193  ctx.font = '50px sans-serif';
   2194  ctx.direction = 'ltr';
   2195  ctx.textAlign = 'left';
   2196  ctx.letterSpacing = '0px';
   2197 
   2198  const kTexts = [
   2199    'UNAVAILABLE',
   2200    '🏁🎢🏁',
   2201    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   2202    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   2203    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   2204    '--abcd__'
   2205  ]
   2206 
   2207  for (text of kTexts) {
   2208    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2209    const tm = ctx.measureText(text);
   2210    text_width = tm.width;
   2211    step = 30;
   2212    if ('0px' == '10px') {
   2213      step = 40;
   2214    }
   2215 
   2216    offset = step;
   2217    adjusted_offset = alignOffset(offset, text_width);
   2218    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2219    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2220    assert_equals(tm_position,
   2221                  doc_position,
   2222                  "for " + text + " offset " + offset);
   2223 
   2224    offset = text_width / 2 - 10;
   2225    adjusted_offset = alignOffset(offset, text_width);
   2226    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2227    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2228    assert_equals(tm_position,
   2229                  doc_position,
   2230                  "for " + text + " offset " + offset);
   2231 
   2232    offset = text_width / 2 + 10;
   2233    adjusted_offset = alignOffset(offset, text_width);
   2234    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2235    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2236    assert_equals(tm_position,
   2237                  doc_position,
   2238                  "for " + text + " offset " + offset);
   2239 
   2240    offset = text_width - step;
   2241    adjusted_offset = alignOffset(offset, text_width);
   2242    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2243    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2244    assert_equals(tm_position,
   2245                  doc_position,
   2246                  "for " + text + " offset " + offset);
   2247  }
   2248 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and directional-override.");
   2249 
   2250 test(t => {
   2251  const canvas = new OffscreenCanvas(100, 50);
   2252  const ctx = canvas.getContext('2d');
   2253 
   2254  function alignOffset(offset, width) {
   2255    if ('left' == 'center') {
   2256      offset -= width / 2;
   2257    } else if ('left' == 'right' ||
   2258             ('rtl' == 'ltr' && 'left' == 'end') ||
   2259             ('rtl' == 'rtl' && 'left' == 'start')) {
   2260      offset -= width;
   2261    }
   2262    return offset;
   2263  }
   2264 
   2265  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2266    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2267    const LTR_OVERRIDE = '\u202D';
   2268    const RTL_OVERRIDE = '\u202E';
   2269    const OVERRIDE_END = '\u202C';
   2270    if (direction_is_ltr) {
   2271      return LTR_OVERRIDE + text + OVERRIDE_END;
   2272    }
   2273    return RTL_OVERRIDE + text + OVERRIDE_END;
   2274  }
   2275 
   2276  function placeAndMeasureTextInDOM(text, text_width, point) {
   2277    const el = document.createElement("p");
   2278    el.innerHTML = text;
   2279    el.style.font = '50px sans-serif';
   2280    el.style.direction = 'rtl';
   2281    el.style.letterSpacing = '0px';
   2282    // Put the text top left to make offsets simpler.
   2283    el.style.padding = '0px';
   2284    el.style.margin = '0px';
   2285    el.style.position = 'absolute';
   2286    el.style.x = '0px';
   2287    el.style.y = '0px';
   2288    document.body.appendChild(el);
   2289    text_bound = el.getBoundingClientRect();
   2290    text_x = text_bound.x;
   2291    text_y = text_bound.y + text_bound.height / 2;
   2292 
   2293    // Offset to the requested point determined by textAlign and direction.
   2294    let text_align_dx = 0;
   2295    if ('left' == 'center') {
   2296      text_align_dx = text_width / 2;
   2297    } else if ('left' == 'right' ||
   2298             ('rtl' == 'ltr' && 'left' == 'end') ||
   2299             ('rtl' == 'rtl' && 'left' == 'start')) {
   2300      text_align_dx = text_width;
   2301    }
   2302    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   2303    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   2304 
   2305    document.body.removeChild(el);
   2306 
   2307    return position.offset;
   2308  }
   2309 
   2310  ctx.font = '50px sans-serif';
   2311  ctx.direction = 'rtl';
   2312  ctx.textAlign = 'left';
   2313  ctx.letterSpacing = '0px';
   2314 
   2315  const kTexts = [
   2316    'UNAVAILABLE',
   2317    '🏁🎢🏁',
   2318    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   2319    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   2320    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   2321    '--abcd__'
   2322  ]
   2323 
   2324  for (text of kTexts) {
   2325    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2326    const tm = ctx.measureText(text);
   2327    text_width = tm.width;
   2328    step = 30;
   2329    if ('0px' == '10px') {
   2330      step = 40;
   2331    }
   2332 
   2333    offset = step;
   2334    adjusted_offset = alignOffset(offset, text_width);
   2335    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2336    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2337    assert_equals(tm_position,
   2338                  doc_position,
   2339                  "for " + text + " offset " + offset);
   2340 
   2341    offset = text_width / 2 - 10;
   2342    adjusted_offset = alignOffset(offset, text_width);
   2343    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2344    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2345    assert_equals(tm_position,
   2346                  doc_position,
   2347                  "for " + text + " offset " + offset);
   2348 
   2349    offset = text_width / 2 + 10;
   2350    adjusted_offset = alignOffset(offset, text_width);
   2351    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2352    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2353    assert_equals(tm_position,
   2354                  doc_position,
   2355                  "for " + text + " offset " + offset);
   2356 
   2357    offset = text_width - step;
   2358    adjusted_offset = alignOffset(offset, text_width);
   2359    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2360    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2361    assert_equals(tm_position,
   2362                  doc_position,
   2363                  "for " + text + " offset " + offset);
   2364  }
   2365 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and directional-override.");
   2366 
   2367 test(t => {
   2368  const canvas = new OffscreenCanvas(100, 50);
   2369  const ctx = canvas.getContext('2d');
   2370 
   2371  function alignOffset(offset, width) {
   2372    if ('center' == 'center') {
   2373      offset -= width / 2;
   2374    } else if ('center' == 'right' ||
   2375             ('ltr' == 'ltr' && 'center' == 'end') ||
   2376             ('ltr' == 'rtl' && 'center' == 'start')) {
   2377      offset -= width;
   2378    }
   2379    return offset;
   2380  }
   2381 
   2382  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2383    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2384    const LTR_OVERRIDE = '\u202D';
   2385    const RTL_OVERRIDE = '\u202E';
   2386    const OVERRIDE_END = '\u202C';
   2387    if (direction_is_ltr) {
   2388      return LTR_OVERRIDE + text + OVERRIDE_END;
   2389    }
   2390    return RTL_OVERRIDE + text + OVERRIDE_END;
   2391  }
   2392 
   2393  function placeAndMeasureTextInDOM(text, text_width, point) {
   2394    const el = document.createElement("p");
   2395    el.innerHTML = text;
   2396    el.style.font = '50px sans-serif';
   2397    el.style.direction = 'ltr';
   2398    el.style.letterSpacing = '0px';
   2399    // Put the text top left to make offsets simpler.
   2400    el.style.padding = '0px';
   2401    el.style.margin = '0px';
   2402    el.style.position = 'absolute';
   2403    el.style.x = '0px';
   2404    el.style.y = '0px';
   2405    document.body.appendChild(el);
   2406    text_bound = el.getBoundingClientRect();
   2407    text_x = text_bound.x;
   2408    text_y = text_bound.y + text_bound.height / 2;
   2409 
   2410    // Offset to the requested point determined by textAlign and direction.
   2411    let text_align_dx = 0;
   2412    if ('center' == 'center') {
   2413      text_align_dx = text_width / 2;
   2414    } else if ('center' == 'right' ||
   2415             ('ltr' == 'ltr' && 'center' == 'end') ||
   2416             ('ltr' == 'rtl' && 'center' == 'start')) {
   2417      text_align_dx = text_width;
   2418    }
   2419    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   2420    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   2421 
   2422    document.body.removeChild(el);
   2423 
   2424    return position.offset;
   2425  }
   2426 
   2427  ctx.font = '50px sans-serif';
   2428  ctx.direction = 'ltr';
   2429  ctx.textAlign = 'center';
   2430  ctx.letterSpacing = '0px';
   2431 
   2432  const kTexts = [
   2433    'UNAVAILABLE',
   2434    '🏁🎢🏁',
   2435    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   2436    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   2437    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   2438    '--abcd__'
   2439  ]
   2440 
   2441  for (text of kTexts) {
   2442    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2443    const tm = ctx.measureText(text);
   2444    text_width = tm.width;
   2445    step = 30;
   2446    if ('0px' == '10px') {
   2447      step = 40;
   2448    }
   2449 
   2450    offset = step;
   2451    adjusted_offset = alignOffset(offset, text_width);
   2452    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2453    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2454    assert_equals(tm_position,
   2455                  doc_position,
   2456                  "for " + text + " offset " + offset);
   2457 
   2458    offset = text_width / 2 - 10;
   2459    adjusted_offset = alignOffset(offset, text_width);
   2460    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2461    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2462    assert_equals(tm_position,
   2463                  doc_position,
   2464                  "for " + text + " offset " + offset);
   2465 
   2466    offset = text_width / 2 + 10;
   2467    adjusted_offset = alignOffset(offset, text_width);
   2468    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2469    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2470    assert_equals(tm_position,
   2471                  doc_position,
   2472                  "for " + text + " offset " + offset);
   2473 
   2474    offset = text_width - step;
   2475    adjusted_offset = alignOffset(offset, text_width);
   2476    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2477    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2478    assert_equals(tm_position,
   2479                  doc_position,
   2480                  "for " + text + " offset " + offset);
   2481  }
   2482 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and directional-override.");
   2483 
   2484 test(t => {
   2485  const canvas = new OffscreenCanvas(100, 50);
   2486  const ctx = canvas.getContext('2d');
   2487 
   2488  function alignOffset(offset, width) {
   2489    if ('center' == 'center') {
   2490      offset -= width / 2;
   2491    } else if ('center' == 'right' ||
   2492             ('rtl' == 'ltr' && 'center' == 'end') ||
   2493             ('rtl' == 'rtl' && 'center' == 'start')) {
   2494      offset -= width;
   2495    }
   2496    return offset;
   2497  }
   2498 
   2499  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2500    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2501    const LTR_OVERRIDE = '\u202D';
   2502    const RTL_OVERRIDE = '\u202E';
   2503    const OVERRIDE_END = '\u202C';
   2504    if (direction_is_ltr) {
   2505      return LTR_OVERRIDE + text + OVERRIDE_END;
   2506    }
   2507    return RTL_OVERRIDE + text + OVERRIDE_END;
   2508  }
   2509 
   2510  function placeAndMeasureTextInDOM(text, text_width, point) {
   2511    const el = document.createElement("p");
   2512    el.innerHTML = text;
   2513    el.style.font = '50px sans-serif';
   2514    el.style.direction = 'rtl';
   2515    el.style.letterSpacing = '0px';
   2516    // Put the text top left to make offsets simpler.
   2517    el.style.padding = '0px';
   2518    el.style.margin = '0px';
   2519    el.style.position = 'absolute';
   2520    el.style.x = '0px';
   2521    el.style.y = '0px';
   2522    document.body.appendChild(el);
   2523    text_bound = el.getBoundingClientRect();
   2524    text_x = text_bound.x;
   2525    text_y = text_bound.y + text_bound.height / 2;
   2526 
   2527    // Offset to the requested point determined by textAlign and direction.
   2528    let text_align_dx = 0;
   2529    if ('center' == 'center') {
   2530      text_align_dx = text_width / 2;
   2531    } else if ('center' == 'right' ||
   2532             ('rtl' == 'ltr' && 'center' == 'end') ||
   2533             ('rtl' == 'rtl' && 'center' == 'start')) {
   2534      text_align_dx = text_width;
   2535    }
   2536    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   2537    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   2538 
   2539    document.body.removeChild(el);
   2540 
   2541    return position.offset;
   2542  }
   2543 
   2544  ctx.font = '50px sans-serif';
   2545  ctx.direction = 'rtl';
   2546  ctx.textAlign = 'center';
   2547  ctx.letterSpacing = '0px';
   2548 
   2549  const kTexts = [
   2550    'UNAVAILABLE',
   2551    '🏁🎢🏁',
   2552    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   2553    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   2554    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   2555    '--abcd__'
   2556  ]
   2557 
   2558  for (text of kTexts) {
   2559    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2560    const tm = ctx.measureText(text);
   2561    text_width = tm.width;
   2562    step = 30;
   2563    if ('0px' == '10px') {
   2564      step = 40;
   2565    }
   2566 
   2567    offset = step;
   2568    adjusted_offset = alignOffset(offset, text_width);
   2569    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2570    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2571    assert_equals(tm_position,
   2572                  doc_position,
   2573                  "for " + text + " offset " + offset);
   2574 
   2575    offset = text_width / 2 - 10;
   2576    adjusted_offset = alignOffset(offset, text_width);
   2577    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2578    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2579    assert_equals(tm_position,
   2580                  doc_position,
   2581                  "for " + text + " offset " + offset);
   2582 
   2583    offset = text_width / 2 + 10;
   2584    adjusted_offset = alignOffset(offset, text_width);
   2585    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2586    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2587    assert_equals(tm_position,
   2588                  doc_position,
   2589                  "for " + text + " offset " + offset);
   2590 
   2591    offset = text_width - step;
   2592    adjusted_offset = alignOffset(offset, text_width);
   2593    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2594    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2595    assert_equals(tm_position,
   2596                  doc_position,
   2597                  "for " + text + " offset " + offset);
   2598  }
   2599 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and directional-override.");
   2600 
   2601 test(t => {
   2602  const canvas = new OffscreenCanvas(100, 50);
   2603  const ctx = canvas.getContext('2d');
   2604 
   2605  function alignOffset(offset, width) {
   2606    if ('right' == 'center') {
   2607      offset -= width / 2;
   2608    } else if ('right' == 'right' ||
   2609             ('ltr' == 'ltr' && 'right' == 'end') ||
   2610             ('ltr' == 'rtl' && 'right' == 'start')) {
   2611      offset -= width;
   2612    }
   2613    return offset;
   2614  }
   2615 
   2616  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2617    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2618    const LTR_OVERRIDE = '\u202D';
   2619    const RTL_OVERRIDE = '\u202E';
   2620    const OVERRIDE_END = '\u202C';
   2621    if (direction_is_ltr) {
   2622      return LTR_OVERRIDE + text + OVERRIDE_END;
   2623    }
   2624    return RTL_OVERRIDE + text + OVERRIDE_END;
   2625  }
   2626 
   2627  function placeAndMeasureTextInDOM(text, text_width, point) {
   2628    const el = document.createElement("p");
   2629    el.innerHTML = text;
   2630    el.style.font = '50px sans-serif';
   2631    el.style.direction = 'ltr';
   2632    el.style.letterSpacing = '0px';
   2633    // Put the text top left to make offsets simpler.
   2634    el.style.padding = '0px';
   2635    el.style.margin = '0px';
   2636    el.style.position = 'absolute';
   2637    el.style.x = '0px';
   2638    el.style.y = '0px';
   2639    document.body.appendChild(el);
   2640    text_bound = el.getBoundingClientRect();
   2641    text_x = text_bound.x;
   2642    text_y = text_bound.y + text_bound.height / 2;
   2643 
   2644    // Offset to the requested point determined by textAlign and direction.
   2645    let text_align_dx = 0;
   2646    if ('right' == 'center') {
   2647      text_align_dx = text_width / 2;
   2648    } else if ('right' == 'right' ||
   2649             ('ltr' == 'ltr' && 'right' == 'end') ||
   2650             ('ltr' == 'rtl' && 'right' == 'start')) {
   2651      text_align_dx = text_width;
   2652    }
   2653    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   2654    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   2655 
   2656    document.body.removeChild(el);
   2657 
   2658    return position.offset;
   2659  }
   2660 
   2661  ctx.font = '50px sans-serif';
   2662  ctx.direction = 'ltr';
   2663  ctx.textAlign = 'right';
   2664  ctx.letterSpacing = '0px';
   2665 
   2666  const kTexts = [
   2667    'UNAVAILABLE',
   2668    '🏁🎢🏁',
   2669    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   2670    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   2671    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   2672    '--abcd__'
   2673  ]
   2674 
   2675  for (text of kTexts) {
   2676    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2677    const tm = ctx.measureText(text);
   2678    text_width = tm.width;
   2679    step = 30;
   2680    if ('0px' == '10px') {
   2681      step = 40;
   2682    }
   2683 
   2684    offset = step;
   2685    adjusted_offset = alignOffset(offset, text_width);
   2686    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2687    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2688    assert_equals(tm_position,
   2689                  doc_position,
   2690                  "for " + text + " offset " + offset);
   2691 
   2692    offset = text_width / 2 - 10;
   2693    adjusted_offset = alignOffset(offset, text_width);
   2694    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2695    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2696    assert_equals(tm_position,
   2697                  doc_position,
   2698                  "for " + text + " offset " + offset);
   2699 
   2700    offset = text_width / 2 + 10;
   2701    adjusted_offset = alignOffset(offset, text_width);
   2702    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2703    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2704    assert_equals(tm_position,
   2705                  doc_position,
   2706                  "for " + text + " offset " + offset);
   2707 
   2708    offset = text_width - step;
   2709    adjusted_offset = alignOffset(offset, text_width);
   2710    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2711    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2712    assert_equals(tm_position,
   2713                  doc_position,
   2714                  "for " + text + " offset " + offset);
   2715  }
   2716 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and directional-override.");
   2717 
   2718 test(t => {
   2719  const canvas = new OffscreenCanvas(100, 50);
   2720  const ctx = canvas.getContext('2d');
   2721 
   2722  function alignOffset(offset, width) {
   2723    if ('right' == 'center') {
   2724      offset -= width / 2;
   2725    } else if ('right' == 'right' ||
   2726             ('rtl' == 'ltr' && 'right' == 'end') ||
   2727             ('rtl' == 'rtl' && 'right' == 'start')) {
   2728      offset -= width;
   2729    }
   2730    return offset;
   2731  }
   2732 
   2733  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2734    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2735    const LTR_OVERRIDE = '\u202D';
   2736    const RTL_OVERRIDE = '\u202E';
   2737    const OVERRIDE_END = '\u202C';
   2738    if (direction_is_ltr) {
   2739      return LTR_OVERRIDE + text + OVERRIDE_END;
   2740    }
   2741    return RTL_OVERRIDE + text + OVERRIDE_END;
   2742  }
   2743 
   2744  function placeAndMeasureTextInDOM(text, text_width, point) {
   2745    const el = document.createElement("p");
   2746    el.innerHTML = text;
   2747    el.style.font = '50px sans-serif';
   2748    el.style.direction = 'rtl';
   2749    el.style.letterSpacing = '0px';
   2750    // Put the text top left to make offsets simpler.
   2751    el.style.padding = '0px';
   2752    el.style.margin = '0px';
   2753    el.style.position = 'absolute';
   2754    el.style.x = '0px';
   2755    el.style.y = '0px';
   2756    document.body.appendChild(el);
   2757    text_bound = el.getBoundingClientRect();
   2758    text_x = text_bound.x;
   2759    text_y = text_bound.y + text_bound.height / 2;
   2760 
   2761    // Offset to the requested point determined by textAlign and direction.
   2762    let text_align_dx = 0;
   2763    if ('right' == 'center') {
   2764      text_align_dx = text_width / 2;
   2765    } else if ('right' == 'right' ||
   2766             ('rtl' == 'ltr' && 'right' == 'end') ||
   2767             ('rtl' == 'rtl' && 'right' == 'start')) {
   2768      text_align_dx = text_width;
   2769    }
   2770    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   2771    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   2772 
   2773    document.body.removeChild(el);
   2774 
   2775    return position.offset;
   2776  }
   2777 
   2778  ctx.font = '50px sans-serif';
   2779  ctx.direction = 'rtl';
   2780  ctx.textAlign = 'right';
   2781  ctx.letterSpacing = '0px';
   2782 
   2783  const kTexts = [
   2784    'UNAVAILABLE',
   2785    '🏁🎢🏁',
   2786    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   2787    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   2788    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   2789    '--abcd__'
   2790  ]
   2791 
   2792  for (text of kTexts) {
   2793    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2794    const tm = ctx.measureText(text);
   2795    text_width = tm.width;
   2796    step = 30;
   2797    if ('0px' == '10px') {
   2798      step = 40;
   2799    }
   2800 
   2801    offset = step;
   2802    adjusted_offset = alignOffset(offset, text_width);
   2803    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2804    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2805    assert_equals(tm_position,
   2806                  doc_position,
   2807                  "for " + text + " offset " + offset);
   2808 
   2809    offset = text_width / 2 - 10;
   2810    adjusted_offset = alignOffset(offset, text_width);
   2811    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2812    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2813    assert_equals(tm_position,
   2814                  doc_position,
   2815                  "for " + text + " offset " + offset);
   2816 
   2817    offset = text_width / 2 + 10;
   2818    adjusted_offset = alignOffset(offset, text_width);
   2819    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2820    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2821    assert_equals(tm_position,
   2822                  doc_position,
   2823                  "for " + text + " offset " + offset);
   2824 
   2825    offset = text_width - step;
   2826    adjusted_offset = alignOffset(offset, text_width);
   2827    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2828    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2829    assert_equals(tm_position,
   2830                  doc_position,
   2831                  "for " + text + " offset " + offset);
   2832  }
   2833 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and directional-override.");
   2834 
   2835 test(t => {
   2836  const canvas = new OffscreenCanvas(100, 50);
   2837  const ctx = canvas.getContext('2d');
   2838 
   2839  function alignOffset(offset, width) {
   2840    if ('start' == 'center') {
   2841      offset -= width / 2;
   2842    } else if ('start' == 'right' ||
   2843             ('ltr' == 'ltr' && 'start' == 'end') ||
   2844             ('ltr' == 'rtl' && 'start' == 'start')) {
   2845      offset -= width;
   2846    }
   2847    return offset;
   2848  }
   2849 
   2850  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2851    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2852    const LTR_OVERRIDE = '\u202D';
   2853    const RTL_OVERRIDE = '\u202E';
   2854    const OVERRIDE_END = '\u202C';
   2855    if (direction_is_ltr) {
   2856      return LTR_OVERRIDE + text + OVERRIDE_END;
   2857    }
   2858    return RTL_OVERRIDE + text + OVERRIDE_END;
   2859  }
   2860 
   2861  function placeAndMeasureTextInDOM(text, text_width, point) {
   2862    const el = document.createElement("p");
   2863    el.innerHTML = text;
   2864    el.style.font = '50px sans-serif';
   2865    el.style.direction = 'ltr';
   2866    el.style.letterSpacing = '0px';
   2867    // Put the text top left to make offsets simpler.
   2868    el.style.padding = '0px';
   2869    el.style.margin = '0px';
   2870    el.style.position = 'absolute';
   2871    el.style.x = '0px';
   2872    el.style.y = '0px';
   2873    document.body.appendChild(el);
   2874    text_bound = el.getBoundingClientRect();
   2875    text_x = text_bound.x;
   2876    text_y = text_bound.y + text_bound.height / 2;
   2877 
   2878    // Offset to the requested point determined by textAlign and direction.
   2879    let text_align_dx = 0;
   2880    if ('start' == 'center') {
   2881      text_align_dx = text_width / 2;
   2882    } else if ('start' == 'right' ||
   2883             ('ltr' == 'ltr' && 'start' == 'end') ||
   2884             ('ltr' == 'rtl' && 'start' == 'start')) {
   2885      text_align_dx = text_width;
   2886    }
   2887    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   2888    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   2889 
   2890    document.body.removeChild(el);
   2891 
   2892    return position.offset;
   2893  }
   2894 
   2895  ctx.font = '50px sans-serif';
   2896  ctx.direction = 'ltr';
   2897  ctx.textAlign = 'start';
   2898  ctx.letterSpacing = '0px';
   2899 
   2900  const kTexts = [
   2901    'UNAVAILABLE',
   2902    '🏁🎢🏁',
   2903    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   2904    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   2905    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   2906    '--abcd__'
   2907  ]
   2908 
   2909  for (text of kTexts) {
   2910    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   2911    const tm = ctx.measureText(text);
   2912    text_width = tm.width;
   2913    step = 30;
   2914    if ('0px' == '10px') {
   2915      step = 40;
   2916    }
   2917 
   2918    offset = step;
   2919    adjusted_offset = alignOffset(offset, text_width);
   2920    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2921    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2922    assert_equals(tm_position,
   2923                  doc_position,
   2924                  "for " + text + " offset " + offset);
   2925 
   2926    offset = text_width / 2 - 10;
   2927    adjusted_offset = alignOffset(offset, text_width);
   2928    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2929    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2930    assert_equals(tm_position,
   2931                  doc_position,
   2932                  "for " + text + " offset " + offset);
   2933 
   2934    offset = text_width / 2 + 10;
   2935    adjusted_offset = alignOffset(offset, text_width);
   2936    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2937    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2938    assert_equals(tm_position,
   2939                  doc_position,
   2940                  "for " + text + " offset " + offset);
   2941 
   2942    offset = text_width - step;
   2943    adjusted_offset = alignOffset(offset, text_width);
   2944    tm_position = tm.getIndexFromOffset(adjusted_offset);
   2945    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   2946    assert_equals(tm_position,
   2947                  doc_position,
   2948                  "for " + text + " offset " + offset);
   2949  }
   2950 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and directional-override.");
   2951 
   2952 test(t => {
   2953  const canvas = new OffscreenCanvas(100, 50);
   2954  const ctx = canvas.getContext('2d');
   2955 
   2956  function alignOffset(offset, width) {
   2957    if ('start' == 'center') {
   2958      offset -= width / 2;
   2959    } else if ('start' == 'right' ||
   2960             ('rtl' == 'ltr' && 'start' == 'end') ||
   2961             ('rtl' == 'rtl' && 'start' == 'start')) {
   2962      offset -= width;
   2963    }
   2964    return offset;
   2965  }
   2966 
   2967  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   2968    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   2969    const LTR_OVERRIDE = '\u202D';
   2970    const RTL_OVERRIDE = '\u202E';
   2971    const OVERRIDE_END = '\u202C';
   2972    if (direction_is_ltr) {
   2973      return LTR_OVERRIDE + text + OVERRIDE_END;
   2974    }
   2975    return RTL_OVERRIDE + text + OVERRIDE_END;
   2976  }
   2977 
   2978  function placeAndMeasureTextInDOM(text, text_width, point) {
   2979    const el = document.createElement("p");
   2980    el.innerHTML = text;
   2981    el.style.font = '50px sans-serif';
   2982    el.style.direction = 'rtl';
   2983    el.style.letterSpacing = '0px';
   2984    // Put the text top left to make offsets simpler.
   2985    el.style.padding = '0px';
   2986    el.style.margin = '0px';
   2987    el.style.position = 'absolute';
   2988    el.style.x = '0px';
   2989    el.style.y = '0px';
   2990    document.body.appendChild(el);
   2991    text_bound = el.getBoundingClientRect();
   2992    text_x = text_bound.x;
   2993    text_y = text_bound.y + text_bound.height / 2;
   2994 
   2995    // Offset to the requested point determined by textAlign and direction.
   2996    let text_align_dx = 0;
   2997    if ('start' == 'center') {
   2998      text_align_dx = text_width / 2;
   2999    } else if ('start' == 'right' ||
   3000             ('rtl' == 'ltr' && 'start' == 'end') ||
   3001             ('rtl' == 'rtl' && 'start' == 'start')) {
   3002      text_align_dx = text_width;
   3003    }
   3004    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3005    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3006 
   3007    document.body.removeChild(el);
   3008 
   3009    return position.offset;
   3010  }
   3011 
   3012  ctx.font = '50px sans-serif';
   3013  ctx.direction = 'rtl';
   3014  ctx.textAlign = 'start';
   3015  ctx.letterSpacing = '0px';
   3016 
   3017  const kTexts = [
   3018    'UNAVAILABLE',
   3019    '🏁🎢🏁',
   3020    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3021    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3022    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3023    '--abcd__'
   3024  ]
   3025 
   3026  for (text of kTexts) {
   3027    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3028    const tm = ctx.measureText(text);
   3029    text_width = tm.width;
   3030    step = 30;
   3031    if ('0px' == '10px') {
   3032      step = 40;
   3033    }
   3034 
   3035    offset = step;
   3036    adjusted_offset = alignOffset(offset, text_width);
   3037    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3038    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3039    assert_equals(tm_position,
   3040                  doc_position,
   3041                  "for " + text + " offset " + offset);
   3042 
   3043    offset = text_width / 2 - 10;
   3044    adjusted_offset = alignOffset(offset, text_width);
   3045    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3046    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3047    assert_equals(tm_position,
   3048                  doc_position,
   3049                  "for " + text + " offset " + offset);
   3050 
   3051    offset = text_width / 2 + 10;
   3052    adjusted_offset = alignOffset(offset, text_width);
   3053    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3054    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3055    assert_equals(tm_position,
   3056                  doc_position,
   3057                  "for " + text + " offset " + offset);
   3058 
   3059    offset = text_width - step;
   3060    adjusted_offset = alignOffset(offset, text_width);
   3061    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3062    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3063    assert_equals(tm_position,
   3064                  doc_position,
   3065                  "for " + text + " offset " + offset);
   3066  }
   3067 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and directional-override.");
   3068 
   3069 test(t => {
   3070  const canvas = new OffscreenCanvas(100, 50);
   3071  const ctx = canvas.getContext('2d');
   3072 
   3073  function alignOffset(offset, width) {
   3074    if ('end' == 'center') {
   3075      offset -= width / 2;
   3076    } else if ('end' == 'right' ||
   3077             ('ltr' == 'ltr' && 'end' == 'end') ||
   3078             ('ltr' == 'rtl' && 'end' == 'start')) {
   3079      offset -= width;
   3080    }
   3081    return offset;
   3082  }
   3083 
   3084  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   3085    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   3086    const LTR_OVERRIDE = '\u202D';
   3087    const RTL_OVERRIDE = '\u202E';
   3088    const OVERRIDE_END = '\u202C';
   3089    if (direction_is_ltr) {
   3090      return LTR_OVERRIDE + text + OVERRIDE_END;
   3091    }
   3092    return RTL_OVERRIDE + text + OVERRIDE_END;
   3093  }
   3094 
   3095  function placeAndMeasureTextInDOM(text, text_width, point) {
   3096    const el = document.createElement("p");
   3097    el.innerHTML = text;
   3098    el.style.font = '50px sans-serif';
   3099    el.style.direction = 'ltr';
   3100    el.style.letterSpacing = '0px';
   3101    // Put the text top left to make offsets simpler.
   3102    el.style.padding = '0px';
   3103    el.style.margin = '0px';
   3104    el.style.position = 'absolute';
   3105    el.style.x = '0px';
   3106    el.style.y = '0px';
   3107    document.body.appendChild(el);
   3108    text_bound = el.getBoundingClientRect();
   3109    text_x = text_bound.x;
   3110    text_y = text_bound.y + text_bound.height / 2;
   3111 
   3112    // Offset to the requested point determined by textAlign and direction.
   3113    let text_align_dx = 0;
   3114    if ('end' == 'center') {
   3115      text_align_dx = text_width / 2;
   3116    } else if ('end' == 'right' ||
   3117             ('ltr' == 'ltr' && 'end' == 'end') ||
   3118             ('ltr' == 'rtl' && 'end' == 'start')) {
   3119      text_align_dx = text_width;
   3120    }
   3121    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3122    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3123 
   3124    document.body.removeChild(el);
   3125 
   3126    return position.offset;
   3127  }
   3128 
   3129  ctx.font = '50px sans-serif';
   3130  ctx.direction = 'ltr';
   3131  ctx.textAlign = 'end';
   3132  ctx.letterSpacing = '0px';
   3133 
   3134  const kTexts = [
   3135    'UNAVAILABLE',
   3136    '🏁🎢🏁',
   3137    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3138    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3139    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3140    '--abcd__'
   3141  ]
   3142 
   3143  for (text of kTexts) {
   3144    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3145    const tm = ctx.measureText(text);
   3146    text_width = tm.width;
   3147    step = 30;
   3148    if ('0px' == '10px') {
   3149      step = 40;
   3150    }
   3151 
   3152    offset = step;
   3153    adjusted_offset = alignOffset(offset, text_width);
   3154    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3155    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3156    assert_equals(tm_position,
   3157                  doc_position,
   3158                  "for " + text + " offset " + offset);
   3159 
   3160    offset = text_width / 2 - 10;
   3161    adjusted_offset = alignOffset(offset, text_width);
   3162    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3163    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3164    assert_equals(tm_position,
   3165                  doc_position,
   3166                  "for " + text + " offset " + offset);
   3167 
   3168    offset = text_width / 2 + 10;
   3169    adjusted_offset = alignOffset(offset, text_width);
   3170    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3171    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3172    assert_equals(tm_position,
   3173                  doc_position,
   3174                  "for " + text + " offset " + offset);
   3175 
   3176    offset = text_width - step;
   3177    adjusted_offset = alignOffset(offset, text_width);
   3178    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3179    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3180    assert_equals(tm_position,
   3181                  doc_position,
   3182                  "for " + text + " offset " + offset);
   3183  }
   3184 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and directional-override.");
   3185 
   3186 test(t => {
   3187  const canvas = new OffscreenCanvas(100, 50);
   3188  const ctx = canvas.getContext('2d');
   3189 
   3190  function alignOffset(offset, width) {
   3191    if ('end' == 'center') {
   3192      offset -= width / 2;
   3193    } else if ('end' == 'right' ||
   3194             ('rtl' == 'ltr' && 'end' == 'end') ||
   3195             ('rtl' == 'rtl' && 'end' == 'start')) {
   3196      offset -= width;
   3197    }
   3198    return offset;
   3199  }
   3200 
   3201  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   3202    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   3203    const LTR_OVERRIDE = '\u202D';
   3204    const RTL_OVERRIDE = '\u202E';
   3205    const OVERRIDE_END = '\u202C';
   3206    if (direction_is_ltr) {
   3207      return LTR_OVERRIDE + text + OVERRIDE_END;
   3208    }
   3209    return RTL_OVERRIDE + text + OVERRIDE_END;
   3210  }
   3211 
   3212  function placeAndMeasureTextInDOM(text, text_width, point) {
   3213    const el = document.createElement("p");
   3214    el.innerHTML = text;
   3215    el.style.font = '50px sans-serif';
   3216    el.style.direction = 'rtl';
   3217    el.style.letterSpacing = '0px';
   3218    // Put the text top left to make offsets simpler.
   3219    el.style.padding = '0px';
   3220    el.style.margin = '0px';
   3221    el.style.position = 'absolute';
   3222    el.style.x = '0px';
   3223    el.style.y = '0px';
   3224    document.body.appendChild(el);
   3225    text_bound = el.getBoundingClientRect();
   3226    text_x = text_bound.x;
   3227    text_y = text_bound.y + text_bound.height / 2;
   3228 
   3229    // Offset to the requested point determined by textAlign and direction.
   3230    let text_align_dx = 0;
   3231    if ('end' == 'center') {
   3232      text_align_dx = text_width / 2;
   3233    } else if ('end' == 'right' ||
   3234             ('rtl' == 'ltr' && 'end' == 'end') ||
   3235             ('rtl' == 'rtl' && 'end' == 'start')) {
   3236      text_align_dx = text_width;
   3237    }
   3238    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3239    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3240 
   3241    document.body.removeChild(el);
   3242 
   3243    return position.offset;
   3244  }
   3245 
   3246  ctx.font = '50px sans-serif';
   3247  ctx.direction = 'rtl';
   3248  ctx.textAlign = 'end';
   3249  ctx.letterSpacing = '0px';
   3250 
   3251  const kTexts = [
   3252    'UNAVAILABLE',
   3253    '🏁🎢🏁',
   3254    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3255    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3256    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3257    '--abcd__'
   3258  ]
   3259 
   3260  for (text of kTexts) {
   3261    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3262    const tm = ctx.measureText(text);
   3263    text_width = tm.width;
   3264    step = 30;
   3265    if ('0px' == '10px') {
   3266      step = 40;
   3267    }
   3268 
   3269    offset = step;
   3270    adjusted_offset = alignOffset(offset, text_width);
   3271    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3272    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3273    assert_equals(tm_position,
   3274                  doc_position,
   3275                  "for " + text + " offset " + offset);
   3276 
   3277    offset = text_width / 2 - 10;
   3278    adjusted_offset = alignOffset(offset, text_width);
   3279    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3280    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3281    assert_equals(tm_position,
   3282                  doc_position,
   3283                  "for " + text + " offset " + offset);
   3284 
   3285    offset = text_width / 2 + 10;
   3286    adjusted_offset = alignOffset(offset, text_width);
   3287    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3288    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3289    assert_equals(tm_position,
   3290                  doc_position,
   3291                  "for " + text + " offset " + offset);
   3292 
   3293    offset = text_width - step;
   3294    adjusted_offset = alignOffset(offset, text_width);
   3295    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3296    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3297    assert_equals(tm_position,
   3298                  doc_position,
   3299                  "for " + text + " offset " + offset);
   3300  }
   3301 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and directional-override.");
   3302 
   3303 test(t => {
   3304  const canvas = new OffscreenCanvas(100, 50);
   3305  const ctx = canvas.getContext('2d');
   3306 
   3307  function alignOffset(offset, width) {
   3308    if ('left' == 'center') {
   3309      offset -= width / 2;
   3310    } else if ('left' == 'right' ||
   3311             ('ltr' == 'ltr' && 'left' == 'end') ||
   3312             ('ltr' == 'rtl' && 'left' == 'start')) {
   3313      offset -= width;
   3314    }
   3315    return offset;
   3316  }
   3317 
   3318  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   3319    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   3320    const LTR_OVERRIDE = '\u202D';
   3321    const RTL_OVERRIDE = '\u202E';
   3322    const OVERRIDE_END = '\u202C';
   3323    if (direction_is_ltr) {
   3324      return LTR_OVERRIDE + text + OVERRIDE_END;
   3325    }
   3326    return RTL_OVERRIDE + text + OVERRIDE_END;
   3327  }
   3328 
   3329  function placeAndMeasureTextInDOM(text, text_width, point) {
   3330    const el = document.createElement("p");
   3331    el.innerHTML = text;
   3332    el.style.font = '50px sans-serif';
   3333    el.style.direction = 'ltr';
   3334    el.style.letterSpacing = '10px';
   3335    // Put the text top left to make offsets simpler.
   3336    el.style.padding = '0px';
   3337    el.style.margin = '0px';
   3338    el.style.position = 'absolute';
   3339    el.style.x = '0px';
   3340    el.style.y = '0px';
   3341    document.body.appendChild(el);
   3342    text_bound = el.getBoundingClientRect();
   3343    text_x = text_bound.x;
   3344    text_y = text_bound.y + text_bound.height / 2;
   3345 
   3346    // Offset to the requested point determined by textAlign and direction.
   3347    let text_align_dx = 0;
   3348    if ('left' == 'center') {
   3349      text_align_dx = text_width / 2;
   3350    } else if ('left' == 'right' ||
   3351             ('ltr' == 'ltr' && 'left' == 'end') ||
   3352             ('ltr' == 'rtl' && 'left' == 'start')) {
   3353      text_align_dx = text_width;
   3354    }
   3355    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3356    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3357 
   3358    document.body.removeChild(el);
   3359 
   3360    return position.offset;
   3361  }
   3362 
   3363  ctx.font = '50px sans-serif';
   3364  ctx.direction = 'ltr';
   3365  ctx.textAlign = 'left';
   3366  ctx.letterSpacing = '10px';
   3367 
   3368  const kTexts = [
   3369    'UNAVAILABLE',
   3370    '🏁🎢🏁',
   3371    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3372    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3373    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3374    '--abcd__'
   3375  ]
   3376 
   3377  for (text of kTexts) {
   3378    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3379    const tm = ctx.measureText(text);
   3380    text_width = tm.width;
   3381    step = 30;
   3382    if ('10px' == '10px') {
   3383      step = 40;
   3384    }
   3385 
   3386    offset = step;
   3387    adjusted_offset = alignOffset(offset, text_width);
   3388    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3389    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3390    assert_equals(tm_position,
   3391                  doc_position,
   3392                  "for " + text + " offset " + offset);
   3393 
   3394    offset = text_width / 2 - 10;
   3395    adjusted_offset = alignOffset(offset, text_width);
   3396    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3397    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3398    assert_equals(tm_position,
   3399                  doc_position,
   3400                  "for " + text + " offset " + offset);
   3401 
   3402    offset = text_width / 2 + 10;
   3403    adjusted_offset = alignOffset(offset, text_width);
   3404    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3405    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3406    assert_equals(tm_position,
   3407                  doc_position,
   3408                  "for " + text + " offset " + offset);
   3409 
   3410    offset = text_width - step;
   3411    adjusted_offset = alignOffset(offset, text_width);
   3412    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3413    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3414    assert_equals(tm_position,
   3415                  doc_position,
   3416                  "for " + text + " offset " + offset);
   3417  }
   3418 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and directional-override.");
   3419 
   3420 test(t => {
   3421  const canvas = new OffscreenCanvas(100, 50);
   3422  const ctx = canvas.getContext('2d');
   3423 
   3424  function alignOffset(offset, width) {
   3425    if ('left' == 'center') {
   3426      offset -= width / 2;
   3427    } else if ('left' == 'right' ||
   3428             ('rtl' == 'ltr' && 'left' == 'end') ||
   3429             ('rtl' == 'rtl' && 'left' == 'start')) {
   3430      offset -= width;
   3431    }
   3432    return offset;
   3433  }
   3434 
   3435  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   3436    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   3437    const LTR_OVERRIDE = '\u202D';
   3438    const RTL_OVERRIDE = '\u202E';
   3439    const OVERRIDE_END = '\u202C';
   3440    if (direction_is_ltr) {
   3441      return LTR_OVERRIDE + text + OVERRIDE_END;
   3442    }
   3443    return RTL_OVERRIDE + text + OVERRIDE_END;
   3444  }
   3445 
   3446  function placeAndMeasureTextInDOM(text, text_width, point) {
   3447    const el = document.createElement("p");
   3448    el.innerHTML = text;
   3449    el.style.font = '50px sans-serif';
   3450    el.style.direction = 'rtl';
   3451    el.style.letterSpacing = '10px';
   3452    // Put the text top left to make offsets simpler.
   3453    el.style.padding = '0px';
   3454    el.style.margin = '0px';
   3455    el.style.position = 'absolute';
   3456    el.style.x = '0px';
   3457    el.style.y = '0px';
   3458    document.body.appendChild(el);
   3459    text_bound = el.getBoundingClientRect();
   3460    text_x = text_bound.x;
   3461    text_y = text_bound.y + text_bound.height / 2;
   3462 
   3463    // Offset to the requested point determined by textAlign and direction.
   3464    let text_align_dx = 0;
   3465    if ('left' == 'center') {
   3466      text_align_dx = text_width / 2;
   3467    } else if ('left' == 'right' ||
   3468             ('rtl' == 'ltr' && 'left' == 'end') ||
   3469             ('rtl' == 'rtl' && 'left' == 'start')) {
   3470      text_align_dx = text_width;
   3471    }
   3472    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3473    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3474 
   3475    document.body.removeChild(el);
   3476 
   3477    return position.offset;
   3478  }
   3479 
   3480  ctx.font = '50px sans-serif';
   3481  ctx.direction = 'rtl';
   3482  ctx.textAlign = 'left';
   3483  ctx.letterSpacing = '10px';
   3484 
   3485  const kTexts = [
   3486    'UNAVAILABLE',
   3487    '🏁🎢🏁',
   3488    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3489    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3490    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3491    '--abcd__'
   3492  ]
   3493 
   3494  for (text of kTexts) {
   3495    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3496    const tm = ctx.measureText(text);
   3497    text_width = tm.width;
   3498    step = 30;
   3499    if ('10px' == '10px') {
   3500      step = 40;
   3501    }
   3502 
   3503    offset = step;
   3504    adjusted_offset = alignOffset(offset, text_width);
   3505    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3506    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3507    assert_equals(tm_position,
   3508                  doc_position,
   3509                  "for " + text + " offset " + offset);
   3510 
   3511    offset = text_width / 2 - 10;
   3512    adjusted_offset = alignOffset(offset, text_width);
   3513    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3514    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3515    assert_equals(tm_position,
   3516                  doc_position,
   3517                  "for " + text + " offset " + offset);
   3518 
   3519    offset = text_width / 2 + 10;
   3520    adjusted_offset = alignOffset(offset, text_width);
   3521    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3522    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3523    assert_equals(tm_position,
   3524                  doc_position,
   3525                  "for " + text + " offset " + offset);
   3526 
   3527    offset = text_width - step;
   3528    adjusted_offset = alignOffset(offset, text_width);
   3529    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3530    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3531    assert_equals(tm_position,
   3532                  doc_position,
   3533                  "for " + text + " offset " + offset);
   3534  }
   3535 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and directional-override.");
   3536 
   3537 test(t => {
   3538  const canvas = new OffscreenCanvas(100, 50);
   3539  const ctx = canvas.getContext('2d');
   3540 
   3541  function alignOffset(offset, width) {
   3542    if ('center' == 'center') {
   3543      offset -= width / 2;
   3544    } else if ('center' == 'right' ||
   3545             ('ltr' == 'ltr' && 'center' == 'end') ||
   3546             ('ltr' == 'rtl' && 'center' == 'start')) {
   3547      offset -= width;
   3548    }
   3549    return offset;
   3550  }
   3551 
   3552  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   3553    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   3554    const LTR_OVERRIDE = '\u202D';
   3555    const RTL_OVERRIDE = '\u202E';
   3556    const OVERRIDE_END = '\u202C';
   3557    if (direction_is_ltr) {
   3558      return LTR_OVERRIDE + text + OVERRIDE_END;
   3559    }
   3560    return RTL_OVERRIDE + text + OVERRIDE_END;
   3561  }
   3562 
   3563  function placeAndMeasureTextInDOM(text, text_width, point) {
   3564    const el = document.createElement("p");
   3565    el.innerHTML = text;
   3566    el.style.font = '50px sans-serif';
   3567    el.style.direction = 'ltr';
   3568    el.style.letterSpacing = '10px';
   3569    // Put the text top left to make offsets simpler.
   3570    el.style.padding = '0px';
   3571    el.style.margin = '0px';
   3572    el.style.position = 'absolute';
   3573    el.style.x = '0px';
   3574    el.style.y = '0px';
   3575    document.body.appendChild(el);
   3576    text_bound = el.getBoundingClientRect();
   3577    text_x = text_bound.x;
   3578    text_y = text_bound.y + text_bound.height / 2;
   3579 
   3580    // Offset to the requested point determined by textAlign and direction.
   3581    let text_align_dx = 0;
   3582    if ('center' == 'center') {
   3583      text_align_dx = text_width / 2;
   3584    } else if ('center' == 'right' ||
   3585             ('ltr' == 'ltr' && 'center' == 'end') ||
   3586             ('ltr' == 'rtl' && 'center' == 'start')) {
   3587      text_align_dx = text_width;
   3588    }
   3589    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3590    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3591 
   3592    document.body.removeChild(el);
   3593 
   3594    return position.offset;
   3595  }
   3596 
   3597  ctx.font = '50px sans-serif';
   3598  ctx.direction = 'ltr';
   3599  ctx.textAlign = 'center';
   3600  ctx.letterSpacing = '10px';
   3601 
   3602  const kTexts = [
   3603    'UNAVAILABLE',
   3604    '🏁🎢🏁',
   3605    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3606    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3607    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3608    '--abcd__'
   3609  ]
   3610 
   3611  for (text of kTexts) {
   3612    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3613    const tm = ctx.measureText(text);
   3614    text_width = tm.width;
   3615    step = 30;
   3616    if ('10px' == '10px') {
   3617      step = 40;
   3618    }
   3619 
   3620    offset = step;
   3621    adjusted_offset = alignOffset(offset, text_width);
   3622    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3623    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3624    assert_equals(tm_position,
   3625                  doc_position,
   3626                  "for " + text + " offset " + offset);
   3627 
   3628    offset = text_width / 2 - 10;
   3629    adjusted_offset = alignOffset(offset, text_width);
   3630    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3631    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3632    assert_equals(tm_position,
   3633                  doc_position,
   3634                  "for " + text + " offset " + offset);
   3635 
   3636    offset = text_width / 2 + 10;
   3637    adjusted_offset = alignOffset(offset, text_width);
   3638    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3639    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3640    assert_equals(tm_position,
   3641                  doc_position,
   3642                  "for " + text + " offset " + offset);
   3643 
   3644    offset = text_width - step;
   3645    adjusted_offset = alignOffset(offset, text_width);
   3646    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3647    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3648    assert_equals(tm_position,
   3649                  doc_position,
   3650                  "for " + text + " offset " + offset);
   3651  }
   3652 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and directional-override.");
   3653 
   3654 test(t => {
   3655  const canvas = new OffscreenCanvas(100, 50);
   3656  const ctx = canvas.getContext('2d');
   3657 
   3658  function alignOffset(offset, width) {
   3659    if ('center' == 'center') {
   3660      offset -= width / 2;
   3661    } else if ('center' == 'right' ||
   3662             ('rtl' == 'ltr' && 'center' == 'end') ||
   3663             ('rtl' == 'rtl' && 'center' == 'start')) {
   3664      offset -= width;
   3665    }
   3666    return offset;
   3667  }
   3668 
   3669  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   3670    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   3671    const LTR_OVERRIDE = '\u202D';
   3672    const RTL_OVERRIDE = '\u202E';
   3673    const OVERRIDE_END = '\u202C';
   3674    if (direction_is_ltr) {
   3675      return LTR_OVERRIDE + text + OVERRIDE_END;
   3676    }
   3677    return RTL_OVERRIDE + text + OVERRIDE_END;
   3678  }
   3679 
   3680  function placeAndMeasureTextInDOM(text, text_width, point) {
   3681    const el = document.createElement("p");
   3682    el.innerHTML = text;
   3683    el.style.font = '50px sans-serif';
   3684    el.style.direction = 'rtl';
   3685    el.style.letterSpacing = '10px';
   3686    // Put the text top left to make offsets simpler.
   3687    el.style.padding = '0px';
   3688    el.style.margin = '0px';
   3689    el.style.position = 'absolute';
   3690    el.style.x = '0px';
   3691    el.style.y = '0px';
   3692    document.body.appendChild(el);
   3693    text_bound = el.getBoundingClientRect();
   3694    text_x = text_bound.x;
   3695    text_y = text_bound.y + text_bound.height / 2;
   3696 
   3697    // Offset to the requested point determined by textAlign and direction.
   3698    let text_align_dx = 0;
   3699    if ('center' == 'center') {
   3700      text_align_dx = text_width / 2;
   3701    } else if ('center' == 'right' ||
   3702             ('rtl' == 'ltr' && 'center' == 'end') ||
   3703             ('rtl' == 'rtl' && 'center' == 'start')) {
   3704      text_align_dx = text_width;
   3705    }
   3706    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3707    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3708 
   3709    document.body.removeChild(el);
   3710 
   3711    return position.offset;
   3712  }
   3713 
   3714  ctx.font = '50px sans-serif';
   3715  ctx.direction = 'rtl';
   3716  ctx.textAlign = 'center';
   3717  ctx.letterSpacing = '10px';
   3718 
   3719  const kTexts = [
   3720    'UNAVAILABLE',
   3721    '🏁🎢🏁',
   3722    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3723    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3724    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3725    '--abcd__'
   3726  ]
   3727 
   3728  for (text of kTexts) {
   3729    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3730    const tm = ctx.measureText(text);
   3731    text_width = tm.width;
   3732    step = 30;
   3733    if ('10px' == '10px') {
   3734      step = 40;
   3735    }
   3736 
   3737    offset = step;
   3738    adjusted_offset = alignOffset(offset, text_width);
   3739    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3740    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3741    assert_equals(tm_position,
   3742                  doc_position,
   3743                  "for " + text + " offset " + offset);
   3744 
   3745    offset = text_width / 2 - 10;
   3746    adjusted_offset = alignOffset(offset, text_width);
   3747    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3748    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3749    assert_equals(tm_position,
   3750                  doc_position,
   3751                  "for " + text + " offset " + offset);
   3752 
   3753    offset = text_width / 2 + 10;
   3754    adjusted_offset = alignOffset(offset, text_width);
   3755    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3756    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3757    assert_equals(tm_position,
   3758                  doc_position,
   3759                  "for " + text + " offset " + offset);
   3760 
   3761    offset = text_width - step;
   3762    adjusted_offset = alignOffset(offset, text_width);
   3763    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3764    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3765    assert_equals(tm_position,
   3766                  doc_position,
   3767                  "for " + text + " offset " + offset);
   3768  }
   3769 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and directional-override.");
   3770 
   3771 test(t => {
   3772  const canvas = new OffscreenCanvas(100, 50);
   3773  const ctx = canvas.getContext('2d');
   3774 
   3775  function alignOffset(offset, width) {
   3776    if ('right' == 'center') {
   3777      offset -= width / 2;
   3778    } else if ('right' == 'right' ||
   3779             ('ltr' == 'ltr' && 'right' == 'end') ||
   3780             ('ltr' == 'rtl' && 'right' == 'start')) {
   3781      offset -= width;
   3782    }
   3783    return offset;
   3784  }
   3785 
   3786  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   3787    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   3788    const LTR_OVERRIDE = '\u202D';
   3789    const RTL_OVERRIDE = '\u202E';
   3790    const OVERRIDE_END = '\u202C';
   3791    if (direction_is_ltr) {
   3792      return LTR_OVERRIDE + text + OVERRIDE_END;
   3793    }
   3794    return RTL_OVERRIDE + text + OVERRIDE_END;
   3795  }
   3796 
   3797  function placeAndMeasureTextInDOM(text, text_width, point) {
   3798    const el = document.createElement("p");
   3799    el.innerHTML = text;
   3800    el.style.font = '50px sans-serif';
   3801    el.style.direction = 'ltr';
   3802    el.style.letterSpacing = '10px';
   3803    // Put the text top left to make offsets simpler.
   3804    el.style.padding = '0px';
   3805    el.style.margin = '0px';
   3806    el.style.position = 'absolute';
   3807    el.style.x = '0px';
   3808    el.style.y = '0px';
   3809    document.body.appendChild(el);
   3810    text_bound = el.getBoundingClientRect();
   3811    text_x = text_bound.x;
   3812    text_y = text_bound.y + text_bound.height / 2;
   3813 
   3814    // Offset to the requested point determined by textAlign and direction.
   3815    let text_align_dx = 0;
   3816    if ('right' == 'center') {
   3817      text_align_dx = text_width / 2;
   3818    } else if ('right' == 'right' ||
   3819             ('ltr' == 'ltr' && 'right' == 'end') ||
   3820             ('ltr' == 'rtl' && 'right' == 'start')) {
   3821      text_align_dx = text_width;
   3822    }
   3823    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3824    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3825 
   3826    document.body.removeChild(el);
   3827 
   3828    return position.offset;
   3829  }
   3830 
   3831  ctx.font = '50px sans-serif';
   3832  ctx.direction = 'ltr';
   3833  ctx.textAlign = 'right';
   3834  ctx.letterSpacing = '10px';
   3835 
   3836  const kTexts = [
   3837    'UNAVAILABLE',
   3838    '🏁🎢🏁',
   3839    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3840    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3841    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3842    '--abcd__'
   3843  ]
   3844 
   3845  for (text of kTexts) {
   3846    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3847    const tm = ctx.measureText(text);
   3848    text_width = tm.width;
   3849    step = 30;
   3850    if ('10px' == '10px') {
   3851      step = 40;
   3852    }
   3853 
   3854    offset = step;
   3855    adjusted_offset = alignOffset(offset, text_width);
   3856    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3857    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3858    assert_equals(tm_position,
   3859                  doc_position,
   3860                  "for " + text + " offset " + offset);
   3861 
   3862    offset = text_width / 2 - 10;
   3863    adjusted_offset = alignOffset(offset, text_width);
   3864    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3865    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3866    assert_equals(tm_position,
   3867                  doc_position,
   3868                  "for " + text + " offset " + offset);
   3869 
   3870    offset = text_width / 2 + 10;
   3871    adjusted_offset = alignOffset(offset, text_width);
   3872    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3873    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3874    assert_equals(tm_position,
   3875                  doc_position,
   3876                  "for " + text + " offset " + offset);
   3877 
   3878    offset = text_width - step;
   3879    adjusted_offset = alignOffset(offset, text_width);
   3880    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3881    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3882    assert_equals(tm_position,
   3883                  doc_position,
   3884                  "for " + text + " offset " + offset);
   3885  }
   3886 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and directional-override.");
   3887 
   3888 test(t => {
   3889  const canvas = new OffscreenCanvas(100, 50);
   3890  const ctx = canvas.getContext('2d');
   3891 
   3892  function alignOffset(offset, width) {
   3893    if ('right' == 'center') {
   3894      offset -= width / 2;
   3895    } else if ('right' == 'right' ||
   3896             ('rtl' == 'ltr' && 'right' == 'end') ||
   3897             ('rtl' == 'rtl' && 'right' == 'start')) {
   3898      offset -= width;
   3899    }
   3900    return offset;
   3901  }
   3902 
   3903  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   3904    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   3905    const LTR_OVERRIDE = '\u202D';
   3906    const RTL_OVERRIDE = '\u202E';
   3907    const OVERRIDE_END = '\u202C';
   3908    if (direction_is_ltr) {
   3909      return LTR_OVERRIDE + text + OVERRIDE_END;
   3910    }
   3911    return RTL_OVERRIDE + text + OVERRIDE_END;
   3912  }
   3913 
   3914  function placeAndMeasureTextInDOM(text, text_width, point) {
   3915    const el = document.createElement("p");
   3916    el.innerHTML = text;
   3917    el.style.font = '50px sans-serif';
   3918    el.style.direction = 'rtl';
   3919    el.style.letterSpacing = '10px';
   3920    // Put the text top left to make offsets simpler.
   3921    el.style.padding = '0px';
   3922    el.style.margin = '0px';
   3923    el.style.position = 'absolute';
   3924    el.style.x = '0px';
   3925    el.style.y = '0px';
   3926    document.body.appendChild(el);
   3927    text_bound = el.getBoundingClientRect();
   3928    text_x = text_bound.x;
   3929    text_y = text_bound.y + text_bound.height / 2;
   3930 
   3931    // Offset to the requested point determined by textAlign and direction.
   3932    let text_align_dx = 0;
   3933    if ('right' == 'center') {
   3934      text_align_dx = text_width / 2;
   3935    } else if ('right' == 'right' ||
   3936             ('rtl' == 'ltr' && 'right' == 'end') ||
   3937             ('rtl' == 'rtl' && 'right' == 'start')) {
   3938      text_align_dx = text_width;
   3939    }
   3940    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   3941    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   3942 
   3943    document.body.removeChild(el);
   3944 
   3945    return position.offset;
   3946  }
   3947 
   3948  ctx.font = '50px sans-serif';
   3949  ctx.direction = 'rtl';
   3950  ctx.textAlign = 'right';
   3951  ctx.letterSpacing = '10px';
   3952 
   3953  const kTexts = [
   3954    'UNAVAILABLE',
   3955    '🏁🎢🏁',
   3956    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   3957    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   3958    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   3959    '--abcd__'
   3960  ]
   3961 
   3962  for (text of kTexts) {
   3963    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   3964    const tm = ctx.measureText(text);
   3965    text_width = tm.width;
   3966    step = 30;
   3967    if ('10px' == '10px') {
   3968      step = 40;
   3969    }
   3970 
   3971    offset = step;
   3972    adjusted_offset = alignOffset(offset, text_width);
   3973    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3974    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3975    assert_equals(tm_position,
   3976                  doc_position,
   3977                  "for " + text + " offset " + offset);
   3978 
   3979    offset = text_width / 2 - 10;
   3980    adjusted_offset = alignOffset(offset, text_width);
   3981    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3982    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3983    assert_equals(tm_position,
   3984                  doc_position,
   3985                  "for " + text + " offset " + offset);
   3986 
   3987    offset = text_width / 2 + 10;
   3988    adjusted_offset = alignOffset(offset, text_width);
   3989    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3990    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3991    assert_equals(tm_position,
   3992                  doc_position,
   3993                  "for " + text + " offset " + offset);
   3994 
   3995    offset = text_width - step;
   3996    adjusted_offset = alignOffset(offset, text_width);
   3997    tm_position = tm.getIndexFromOffset(adjusted_offset);
   3998    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   3999    assert_equals(tm_position,
   4000                  doc_position,
   4001                  "for " + text + " offset " + offset);
   4002  }
   4003 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and directional-override.");
   4004 
   4005 test(t => {
   4006  const canvas = new OffscreenCanvas(100, 50);
   4007  const ctx = canvas.getContext('2d');
   4008 
   4009  function alignOffset(offset, width) {
   4010    if ('start' == 'center') {
   4011      offset -= width / 2;
   4012    } else if ('start' == 'right' ||
   4013             ('ltr' == 'ltr' && 'start' == 'end') ||
   4014             ('ltr' == 'rtl' && 'start' == 'start')) {
   4015      offset -= width;
   4016    }
   4017    return offset;
   4018  }
   4019 
   4020  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   4021    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   4022    const LTR_OVERRIDE = '\u202D';
   4023    const RTL_OVERRIDE = '\u202E';
   4024    const OVERRIDE_END = '\u202C';
   4025    if (direction_is_ltr) {
   4026      return LTR_OVERRIDE + text + OVERRIDE_END;
   4027    }
   4028    return RTL_OVERRIDE + text + OVERRIDE_END;
   4029  }
   4030 
   4031  function placeAndMeasureTextInDOM(text, text_width, point) {
   4032    const el = document.createElement("p");
   4033    el.innerHTML = text;
   4034    el.style.font = '50px sans-serif';
   4035    el.style.direction = 'ltr';
   4036    el.style.letterSpacing = '10px';
   4037    // Put the text top left to make offsets simpler.
   4038    el.style.padding = '0px';
   4039    el.style.margin = '0px';
   4040    el.style.position = 'absolute';
   4041    el.style.x = '0px';
   4042    el.style.y = '0px';
   4043    document.body.appendChild(el);
   4044    text_bound = el.getBoundingClientRect();
   4045    text_x = text_bound.x;
   4046    text_y = text_bound.y + text_bound.height / 2;
   4047 
   4048    // Offset to the requested point determined by textAlign and direction.
   4049    let text_align_dx = 0;
   4050    if ('start' == 'center') {
   4051      text_align_dx = text_width / 2;
   4052    } else if ('start' == 'right' ||
   4053             ('ltr' == 'ltr' && 'start' == 'end') ||
   4054             ('ltr' == 'rtl' && 'start' == 'start')) {
   4055      text_align_dx = text_width;
   4056    }
   4057    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   4058    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   4059 
   4060    document.body.removeChild(el);
   4061 
   4062    return position.offset;
   4063  }
   4064 
   4065  ctx.font = '50px sans-serif';
   4066  ctx.direction = 'ltr';
   4067  ctx.textAlign = 'start';
   4068  ctx.letterSpacing = '10px';
   4069 
   4070  const kTexts = [
   4071    'UNAVAILABLE',
   4072    '🏁🎢🏁',
   4073    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   4074    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   4075    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   4076    '--abcd__'
   4077  ]
   4078 
   4079  for (text of kTexts) {
   4080    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   4081    const tm = ctx.measureText(text);
   4082    text_width = tm.width;
   4083    step = 30;
   4084    if ('10px' == '10px') {
   4085      step = 40;
   4086    }
   4087 
   4088    offset = step;
   4089    adjusted_offset = alignOffset(offset, text_width);
   4090    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4091    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4092    assert_equals(tm_position,
   4093                  doc_position,
   4094                  "for " + text + " offset " + offset);
   4095 
   4096    offset = text_width / 2 - 10;
   4097    adjusted_offset = alignOffset(offset, text_width);
   4098    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4099    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4100    assert_equals(tm_position,
   4101                  doc_position,
   4102                  "for " + text + " offset " + offset);
   4103 
   4104    offset = text_width / 2 + 10;
   4105    adjusted_offset = alignOffset(offset, text_width);
   4106    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4107    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4108    assert_equals(tm_position,
   4109                  doc_position,
   4110                  "for " + text + " offset " + offset);
   4111 
   4112    offset = text_width - step;
   4113    adjusted_offset = alignOffset(offset, text_width);
   4114    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4115    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4116    assert_equals(tm_position,
   4117                  doc_position,
   4118                  "for " + text + " offset " + offset);
   4119  }
   4120 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and directional-override.");
   4121 
   4122 test(t => {
   4123  const canvas = new OffscreenCanvas(100, 50);
   4124  const ctx = canvas.getContext('2d');
   4125 
   4126  function alignOffset(offset, width) {
   4127    if ('start' == 'center') {
   4128      offset -= width / 2;
   4129    } else if ('start' == 'right' ||
   4130             ('rtl' == 'ltr' && 'start' == 'end') ||
   4131             ('rtl' == 'rtl' && 'start' == 'start')) {
   4132      offset -= width;
   4133    }
   4134    return offset;
   4135  }
   4136 
   4137  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   4138    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   4139    const LTR_OVERRIDE = '\u202D';
   4140    const RTL_OVERRIDE = '\u202E';
   4141    const OVERRIDE_END = '\u202C';
   4142    if (direction_is_ltr) {
   4143      return LTR_OVERRIDE + text + OVERRIDE_END;
   4144    }
   4145    return RTL_OVERRIDE + text + OVERRIDE_END;
   4146  }
   4147 
   4148  function placeAndMeasureTextInDOM(text, text_width, point) {
   4149    const el = document.createElement("p");
   4150    el.innerHTML = text;
   4151    el.style.font = '50px sans-serif';
   4152    el.style.direction = 'rtl';
   4153    el.style.letterSpacing = '10px';
   4154    // Put the text top left to make offsets simpler.
   4155    el.style.padding = '0px';
   4156    el.style.margin = '0px';
   4157    el.style.position = 'absolute';
   4158    el.style.x = '0px';
   4159    el.style.y = '0px';
   4160    document.body.appendChild(el);
   4161    text_bound = el.getBoundingClientRect();
   4162    text_x = text_bound.x;
   4163    text_y = text_bound.y + text_bound.height / 2;
   4164 
   4165    // Offset to the requested point determined by textAlign and direction.
   4166    let text_align_dx = 0;
   4167    if ('start' == 'center') {
   4168      text_align_dx = text_width / 2;
   4169    } else if ('start' == 'right' ||
   4170             ('rtl' == 'ltr' && 'start' == 'end') ||
   4171             ('rtl' == 'rtl' && 'start' == 'start')) {
   4172      text_align_dx = text_width;
   4173    }
   4174    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   4175    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   4176 
   4177    document.body.removeChild(el);
   4178 
   4179    return position.offset;
   4180  }
   4181 
   4182  ctx.font = '50px sans-serif';
   4183  ctx.direction = 'rtl';
   4184  ctx.textAlign = 'start';
   4185  ctx.letterSpacing = '10px';
   4186 
   4187  const kTexts = [
   4188    'UNAVAILABLE',
   4189    '🏁🎢🏁',
   4190    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   4191    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   4192    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   4193    '--abcd__'
   4194  ]
   4195 
   4196  for (text of kTexts) {
   4197    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   4198    const tm = ctx.measureText(text);
   4199    text_width = tm.width;
   4200    step = 30;
   4201    if ('10px' == '10px') {
   4202      step = 40;
   4203    }
   4204 
   4205    offset = step;
   4206    adjusted_offset = alignOffset(offset, text_width);
   4207    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4208    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4209    assert_equals(tm_position,
   4210                  doc_position,
   4211                  "for " + text + " offset " + offset);
   4212 
   4213    offset = text_width / 2 - 10;
   4214    adjusted_offset = alignOffset(offset, text_width);
   4215    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4216    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4217    assert_equals(tm_position,
   4218                  doc_position,
   4219                  "for " + text + " offset " + offset);
   4220 
   4221    offset = text_width / 2 + 10;
   4222    adjusted_offset = alignOffset(offset, text_width);
   4223    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4224    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4225    assert_equals(tm_position,
   4226                  doc_position,
   4227                  "for " + text + " offset " + offset);
   4228 
   4229    offset = text_width - step;
   4230    adjusted_offset = alignOffset(offset, text_width);
   4231    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4232    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4233    assert_equals(tm_position,
   4234                  doc_position,
   4235                  "for " + text + " offset " + offset);
   4236  }
   4237 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and directional-override.");
   4238 
   4239 test(t => {
   4240  const canvas = new OffscreenCanvas(100, 50);
   4241  const ctx = canvas.getContext('2d');
   4242 
   4243  function alignOffset(offset, width) {
   4244    if ('end' == 'center') {
   4245      offset -= width / 2;
   4246    } else if ('end' == 'right' ||
   4247             ('ltr' == 'ltr' && 'end' == 'end') ||
   4248             ('ltr' == 'rtl' && 'end' == 'start')) {
   4249      offset -= width;
   4250    }
   4251    return offset;
   4252  }
   4253 
   4254  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   4255    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   4256    const LTR_OVERRIDE = '\u202D';
   4257    const RTL_OVERRIDE = '\u202E';
   4258    const OVERRIDE_END = '\u202C';
   4259    if (direction_is_ltr) {
   4260      return LTR_OVERRIDE + text + OVERRIDE_END;
   4261    }
   4262    return RTL_OVERRIDE + text + OVERRIDE_END;
   4263  }
   4264 
   4265  function placeAndMeasureTextInDOM(text, text_width, point) {
   4266    const el = document.createElement("p");
   4267    el.innerHTML = text;
   4268    el.style.font = '50px sans-serif';
   4269    el.style.direction = 'ltr';
   4270    el.style.letterSpacing = '10px';
   4271    // Put the text top left to make offsets simpler.
   4272    el.style.padding = '0px';
   4273    el.style.margin = '0px';
   4274    el.style.position = 'absolute';
   4275    el.style.x = '0px';
   4276    el.style.y = '0px';
   4277    document.body.appendChild(el);
   4278    text_bound = el.getBoundingClientRect();
   4279    text_x = text_bound.x;
   4280    text_y = text_bound.y + text_bound.height / 2;
   4281 
   4282    // Offset to the requested point determined by textAlign and direction.
   4283    let text_align_dx = 0;
   4284    if ('end' == 'center') {
   4285      text_align_dx = text_width / 2;
   4286    } else if ('end' == 'right' ||
   4287             ('ltr' == 'ltr' && 'end' == 'end') ||
   4288             ('ltr' == 'rtl' && 'end' == 'start')) {
   4289      text_align_dx = text_width;
   4290    }
   4291    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   4292    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   4293 
   4294    document.body.removeChild(el);
   4295 
   4296    return position.offset;
   4297  }
   4298 
   4299  ctx.font = '50px sans-serif';
   4300  ctx.direction = 'ltr';
   4301  ctx.textAlign = 'end';
   4302  ctx.letterSpacing = '10px';
   4303 
   4304  const kTexts = [
   4305    'UNAVAILABLE',
   4306    '🏁🎢🏁',
   4307    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   4308    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   4309    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   4310    '--abcd__'
   4311  ]
   4312 
   4313  for (text of kTexts) {
   4314    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   4315    const tm = ctx.measureText(text);
   4316    text_width = tm.width;
   4317    step = 30;
   4318    if ('10px' == '10px') {
   4319      step = 40;
   4320    }
   4321 
   4322    offset = step;
   4323    adjusted_offset = alignOffset(offset, text_width);
   4324    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4325    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4326    assert_equals(tm_position,
   4327                  doc_position,
   4328                  "for " + text + " offset " + offset);
   4329 
   4330    offset = text_width / 2 - 10;
   4331    adjusted_offset = alignOffset(offset, text_width);
   4332    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4333    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4334    assert_equals(tm_position,
   4335                  doc_position,
   4336                  "for " + text + " offset " + offset);
   4337 
   4338    offset = text_width / 2 + 10;
   4339    adjusted_offset = alignOffset(offset, text_width);
   4340    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4341    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4342    assert_equals(tm_position,
   4343                  doc_position,
   4344                  "for " + text + " offset " + offset);
   4345 
   4346    offset = text_width - step;
   4347    adjusted_offset = alignOffset(offset, text_width);
   4348    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4349    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4350    assert_equals(tm_position,
   4351                  doc_position,
   4352                  "for " + text + " offset " + offset);
   4353  }
   4354 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and directional-override.");
   4355 
   4356 test(t => {
   4357  const canvas = new OffscreenCanvas(100, 50);
   4358  const ctx = canvas.getContext('2d');
   4359 
   4360  function alignOffset(offset, width) {
   4361    if ('end' == 'center') {
   4362      offset -= width / 2;
   4363    } else if ('end' == 'right' ||
   4364             ('rtl' == 'ltr' && 'end' == 'end') ||
   4365             ('rtl' == 'rtl' && 'end' == 'start')) {
   4366      offset -= width;
   4367    }
   4368    return offset;
   4369  }
   4370 
   4371  function addDirectionalOverrideCharacters(text, direction_is_ltr) {
   4372    // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
   4373    const LTR_OVERRIDE = '\u202D';
   4374    const RTL_OVERRIDE = '\u202E';
   4375    const OVERRIDE_END = '\u202C';
   4376    if (direction_is_ltr) {
   4377      return LTR_OVERRIDE + text + OVERRIDE_END;
   4378    }
   4379    return RTL_OVERRIDE + text + OVERRIDE_END;
   4380  }
   4381 
   4382  function placeAndMeasureTextInDOM(text, text_width, point) {
   4383    const el = document.createElement("p");
   4384    el.innerHTML = text;
   4385    el.style.font = '50px sans-serif';
   4386    el.style.direction = 'rtl';
   4387    el.style.letterSpacing = '10px';
   4388    // Put the text top left to make offsets simpler.
   4389    el.style.padding = '0px';
   4390    el.style.margin = '0px';
   4391    el.style.position = 'absolute';
   4392    el.style.x = '0px';
   4393    el.style.y = '0px';
   4394    document.body.appendChild(el);
   4395    text_bound = el.getBoundingClientRect();
   4396    text_x = text_bound.x;
   4397    text_y = text_bound.y + text_bound.height / 2;
   4398 
   4399    // Offset to the requested point determined by textAlign and direction.
   4400    let text_align_dx = 0;
   4401    if ('end' == 'center') {
   4402      text_align_dx = text_width / 2;
   4403    } else if ('end' == 'right' ||
   4404             ('rtl' == 'ltr' && 'end' == 'end') ||
   4405             ('rtl' == 'rtl' && 'end' == 'start')) {
   4406      text_align_dx = text_width;
   4407    }
   4408    position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
   4409    _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
   4410 
   4411    document.body.removeChild(el);
   4412 
   4413    return position.offset;
   4414  }
   4415 
   4416  ctx.font = '50px sans-serif';
   4417  ctx.direction = 'rtl';
   4418  ctx.textAlign = 'end';
   4419  ctx.letterSpacing = '10px';
   4420 
   4421  const kTexts = [
   4422    'UNAVAILABLE',
   4423    '🏁🎢🏁',
   4424    'οΌ‰οΌˆγ‚οΌ‰οΌˆ',
   4425    'οΌ‰οΌˆγ‚γ‚οΌ‰οΌˆ',
   4426    'γ‚γ‚οΌ‰οΌˆγ‚γ‚',
   4427    '--abcd__'
   4428  ]
   4429 
   4430  for (text of kTexts) {
   4431    text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
   4432    const tm = ctx.measureText(text);
   4433    text_width = tm.width;
   4434    step = 30;
   4435    if ('10px' == '10px') {
   4436      step = 40;
   4437    }
   4438 
   4439    offset = step;
   4440    adjusted_offset = alignOffset(offset, text_width);
   4441    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4442    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4443    assert_equals(tm_position,
   4444                  doc_position,
   4445                  "for " + text + " offset " + offset);
   4446 
   4447    offset = text_width / 2 - 10;
   4448    adjusted_offset = alignOffset(offset, text_width);
   4449    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4450    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4451    assert_equals(tm_position,
   4452                  doc_position,
   4453                  "for " + text + " offset " + offset);
   4454 
   4455    offset = text_width / 2 + 10;
   4456    adjusted_offset = alignOffset(offset, text_width);
   4457    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4458    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4459    assert_equals(tm_position,
   4460                  doc_position,
   4461                  "for " + text + " offset " + offset);
   4462 
   4463    offset = text_width - step;
   4464    adjusted_offset = alignOffset(offset, text_width);
   4465    tm_position = tm.getIndexFromOffset(adjusted_offset);
   4466    doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
   4467    assert_equals(tm_position,
   4468                  doc_position,
   4469                  "for " + text + " offset " + offset);
   4470  }
   4471 }, "Check that TextMetrics::getIndexFromOffset() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and directional-override.");
   4472 
   4473 </script>