Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 24 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<meta charset="UTF-8">
<title>OffscreenCanvas test: 2d.text.measure.selection-rects.tentative</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/canvas/resources/canvas-tests.js"></script>
<h1>2d.text.measure.selection-rects.tentative</h1>
<script>
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'left';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'left';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'left';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'left';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'center';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'center';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'center';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'center';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'right';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'right';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'right';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'right';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'left';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'left';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'left';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'left';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'center';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'center';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'center';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'center';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'right';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'right';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'right';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'right';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'left';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'left';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'left';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'left';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'center';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'center';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'center';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'center';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'right';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'right';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'right';
el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'right';
ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'left';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'left';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'left';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'left';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'center';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'center';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'center';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'center';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'ltr';
el.style.textAlign = 'right';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'ltr';
ctx.textAlign = 'right';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override.");
test(t => {
const canvas = new OffscreenCanvas(100, 50);
const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = 'rtl';
el.style.textAlign = 'right';
el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
_assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
ctx.font = '50px sans-serif';
ctx.direction = 'rtl';
ctx.textAlign = 'right';
ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'ππΆπ',
'οΌοΌγοΌοΌ',
'-abcd_'
]
for (text of kTexts) {
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override.");
</script>