Implement getActualBoundingBox feature for TextMetrics
This change creates a new API method for TextMetrics objects in canvas
that uses font data and precalculated text runs, to calculate the
bounding box for an interval of characters. This interval is defined
by character positions in the input text.
The method returns a single rectangle, the actual bounding box of the
substring of the input text defined by the input indexes.
The new API was enabled under the `ExtendedTextMetrics` flag.
Bug: 341213359
Change-Id: I289a52283d0bd5a0001c5ec7a514cf16abba9611
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5546024
Reviewed-by: Koji Ishii <kojii@chromium.org>
Commit-Queue: Andres Ricardo Perez <andresrperez@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1322331}
diff --git a/third_party/blink/renderer/core/html/canvas/text_metrics.cc b/third_party/blink/renderer/core/html/canvas/text_metrics.cc
index 989d15f..f0d63b5 100644
--- a/third_party/blink/renderer/core/html/canvas/text_metrics.cc
+++ b/third_party/blink/renderer/core/html/canvas/text_metrics.cc
@@ -149,7 +149,7 @@
kAlphabeticBaseline, FontMetrics::ApplyBaselineTable(true));
const float descent = font_metrics.FloatDescent(
kAlphabeticBaseline, FontMetrics::ApplyBaselineTable(true));
- const float baseline_y = GetFontBaseline(baseline, *font_data);
+ baseline_y = GetFontBaseline(baseline, *font_data);
font_bounding_box_ascent_ = ascent - baseline_y;
font_bounding_box_descent_ = descent + baseline_y;
actual_bounding_box_ascent_ = -glyph_bounds.y() - baseline_y;
@@ -291,4 +291,61 @@
return selection_rects;
}
+const DOMRectReadOnly* TextMetrics::getActualBoundingBox(
+ uint32_t start,
+ uint32_t end,
+ ExceptionState& exception_state) {
+ gfx::RectF bounding_box;
+
+ // Checks indexes that go over the maximum for the text. For indexes less than
+ // 0, an exception is thrown by [EnforceRange] in the idl binding.
+ if (start >= text_length_ || end > text_length_) {
+ exception_state.ThrowDOMException(
+ DOMExceptionCode::kIndexSizeError,
+ String::Format("The %s index is out of bounds.",
+ start >= text_length_ ? "start" : "end"));
+ return DOMRectReadOnly::FromRectF(bounding_box);
+ }
+
+ ShapeTextIfNeeded();
+
+ for (const auto& run_with_offset : runs_with_offset_) {
+ const unsigned int run_start_index = run_with_offset.character_offset_;
+ const unsigned int run_end_index =
+ run_start_index + run_with_offset.num_characters_;
+
+ // Outside the required interval.
+ if (run_end_index <= start || run_start_index >= end) {
+ continue;
+ }
+
+ // Position of the left border for this run.
+ const double left_border = run_with_offset.x_position_;
+
+ // Calculate the required indexes for this specific run.
+ const unsigned int starting_index =
+ start > run_start_index ? start - run_start_index : 0;
+ const unsigned int ending_index = end < run_end_index
+ ? end - run_start_index
+ : run_with_offset.num_characters_;
+
+ const ShapeResultView* view = ShapeResultView::Create(
+ run_with_offset.shape_result_, 0, run_with_offset.num_characters_);
+ view->ForEachGlyph(
+ left_border, starting_index, ending_index, 0,
+ [](void* context, unsigned character_index, Glyph glyph,
+ gfx::Vector2dF glyph_offset, float total_advance, bool is_horizontal,
+ CanvasRotationInVertical rotation, const SimpleFontData* font_data) {
+ auto* bounding_box = static_cast<gfx::RectF*>(context);
+ gfx::RectF glyph_bounds = font_data->BoundsForGlyph(glyph);
+ glyph_bounds.Offset(total_advance, 0.0);
+ glyph_bounds.Offset(glyph_offset);
+ bounding_box->Union(glyph_bounds);
+ },
+ static_cast<void*>(&bounding_box));
+ }
+ bounding_box.Offset(-text_align_dx_, baseline_y);
+ return DOMRectReadOnly::FromRectF(bounding_box);
+}
+
} // namespace blink
diff --git a/third_party/blink/renderer/core/html/canvas/text_metrics.h b/third_party/blink/renderer/core/html/canvas/text_metrics.h
index 9ec6ff80..3219963 100644
--- a/third_party/blink/renderer/core/html/canvas/text_metrics.h
+++ b/third_party/blink/renderer/core/html/canvas/text_metrics.h
@@ -71,6 +71,9 @@
uint32_t start,
uint32_t end,
ExceptionState& exception_state);
+ const DOMRectReadOnly* getActualBoundingBox(uint32_t start,
+ uint32_t end,
+ ExceptionState& exception_state);
void Trace(Visitor*) const override;
@@ -110,9 +113,10 @@
double actual_bounding_box_descent_ = 0.0;
double em_height_ascent_ = 0.0;
double em_height_descent_ = 0.0;
+ float baseline_y = 0.0;
Member<Baselines> baselines_;
- // Needed for selection rects.
+ // Needed for selection rects and bounding boxes.
Font font_;
uint32_t text_length_ = 0;
diff --git a/third_party/blink/renderer/core/html/canvas/text_metrics.idl b/third_party/blink/renderer/core/html/canvas/text_metrics.idl
index 33b847e..33462991 100644
--- a/third_party/blink/renderer/core/html/canvas/text_metrics.idl
+++ b/third_party/blink/renderer/core/html/canvas/text_metrics.idl
@@ -45,4 +45,6 @@
// For selection
[RuntimeEnabled=ExtendedTextMetrics, RaisesException] sequence<DOMRectReadOnly> getSelectionRects([EnforceRange] unsigned long start, [EnforceRange] unsigned long end);
+ // For bounding box
+ [RuntimeEnabled=ExtendedTextMetrics, RaisesException] DOMRectReadOnly getActualBoundingBox([EnforceRange] unsigned long start, [EnforceRange] unsigned long end);
};
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html
new file mode 100644
index 0000000..e60d1e3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.getActualBoundingBox-exceptions.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>2d.text.measure.getActualBoundingBox-exceptions.tentative</h1>
+<p class="desc">Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.");
+_addTest(function(canvas, ctx) {
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd_'
+ ]
+
+ for (const text of kTexts) {
+ const tm = ctx.measureText(text);
+ // Handled by the IDL binding.
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
+ // Thrown in TextMetrics.
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, 0) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(0, text.length + 1) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, text.length + 1) );
+ }
+
+});
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html
new file mode 100644
index 0000000..510bce84
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html
@@ -0,0 +1,278 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.getActualBoundingBox-full-text.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+
+<h1>2d.text.measure.getActualBoundingBox-full-text.tentative</h1>
+
+<script>
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ 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;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ 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;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override");
+
+</script>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html
new file mode 100644
index 0000000..6f3b66a3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html
@@ -0,0 +1,502 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.getActualBoundingBox.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+
+<h1>2d.text.measure.getActualBoundingBox.tentative</h1>
+
+<style>
+ @font-face {
+ font-family: CanvasTest;
+ src: url("/fonts/CanvasTest.ttf");
+ }
+</style>
+<span style="font-family: CanvasTest;
+ position: absolute; visibility: hidden">A</span>
+<script>
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.");
+
+</script>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html
new file mode 100644
index 0000000..551f2c1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.getActualBoundingBox-exceptions.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.getActualBoundingBox-exceptions.tentative</h1>
+<p class="desc">Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.</p>
+
+
+<script>
+var t = async_test("Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+ throw reason;
+});
+t.step(function() {
+
+ var canvas = new OffscreenCanvas(100, 50);
+ var ctx = canvas.getContext('2d');
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd_'
+ ]
+
+ for (const text of kTexts) {
+ const tm = ctx.measureText(text);
+ // Handled by the IDL binding.
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
+ // Thrown in TextMetrics.
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, 0) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(0, text.length + 1) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, text.length + 1) );
+ }
+ t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js
new file mode 100644
index 0000000..5b5b7c76
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js
@@ -0,0 +1,42 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.text.measure.getActualBoundingBox-exceptions.tentative
+// Description:Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+ throw reason;
+});
+t.step(function() {
+
+ var canvas = new OffscreenCanvas(100, 50);
+ var ctx = canvas.getContext('2d');
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd_'
+ ]
+
+ for (const text of kTexts) {
+ const tm = ctx.measureText(text);
+ // Handled by the IDL binding.
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
+ // Thrown in TextMetrics.
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, 0) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(0, text.length + 1) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, text.length + 1) );
+ }
+ t.done();
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html
new file mode 100644
index 0000000..44b107e0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html
@@ -0,0 +1,276 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.getActualBoundingBox-full-text.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.getActualBoundingBox-full-text.tentative</h1>
+
+<script>
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ 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;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ 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;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js
new file mode 100644
index 0000000..54a4cb41
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js
@@ -0,0 +1,273 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.text.measure.getActualBoundingBox-full-text.tentative
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ 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;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ 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;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override");
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html
new file mode 100644
index 0000000..e6a2b07
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html
@@ -0,0 +1,510 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.getActualBoundingBox.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.getActualBoundingBox.tentative</h1>
+
+<script>
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js
new file mode 100644
index 0000000..9530d177
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js
@@ -0,0 +1,507 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.text.measure.getActualBoundingBox.tentative
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.");
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/text.yaml b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/text.yaml
index 8155497e..a9a44bc3 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/text.yaml
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/text.yaml
@@ -235,8 +235,9 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: &load-font-variant-definition
- - HtmlCanvas:
+ variants:
+ - &load-font-variant-definition
+ HtmlCanvas:
append_variants_to_name: false
canvas_types: ['HtmlCanvas']
load_font: |-
@@ -271,7 +272,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.baseline.middle
desc: textBaseline middle is the middle of the em square (not the bounding box)
@@ -293,7 +295,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.baseline.alphabetic
test_type: promise
@@ -314,7 +317,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.baseline.ideographic
test_type: promise
@@ -335,7 +339,8 @@
@assert pixel 5,45 ==~ 0,255,0,255; @moz-todo
@assert pixel 95,45 ==~ 0,255,0,255; @moz-todo
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.baseline.hanging
test_type: promise
@@ -356,7 +361,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.space
desc: Space characters are converted to U+0020, and are NOT collapsed
@@ -373,7 +379,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 255,0,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.other
desc: Space characters are converted to U+0020, and are NOT collapsed
@@ -390,7 +397,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 255,0,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.start
desc: Space characters at the start of a line are NOT collapsed
@@ -407,7 +415,8 @@
@assert pixel 25,25 ==~ 255,0,0,255; @moz-todo
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.end
desc: Space characters at the end of a line are NOT collapsed
@@ -425,7 +434,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 255,0,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.width.space
desc: Space characters are converted to U+0020 and NOT collapsed
@@ -442,7 +452,8 @@
@assert ctx.measureText(' AB').width === 150;
@assert ctx.measureText('AB ').width === 150;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.drawing.style.measure.rtl.text
desc: Measurement should follow canvas direction instead text direction
@@ -704,7 +715,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.fill.maxWidth.bound
desc: fillText handles maxWidth based on line size, not bounding box size
@@ -723,7 +735,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.fontface
fonts:
@@ -741,7 +754,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.fontface.repeat
desc: Draw with the font immediately, then wait a bit until and draw again. (This
@@ -765,7 +779,8 @@
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.fontface.notinpage
desc: '@font-face fonts should work even if they are not used in the page'
@@ -785,7 +800,8 @@
@assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
@assert pixel 75,25 ==~ 0,255,0,255; @moz-todo
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.left
desc: textAlign left is the left of the first em square (not the bounding box)
@@ -807,7 +823,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.right
desc: textAlign right is the right of the last em square (not the bounding box)
@@ -829,7 +846,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.start.ltr
desc: textAlign start with ltr is the left edge
@@ -855,7 +873,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.start.rtl
desc: textAlign start with rtl is the right edge
@@ -881,7 +900,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.end.ltr
desc: textAlign end with ltr is the right edge
@@ -907,7 +927,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.end.rtl
desc: textAlign end with rtl is the left edge
@@ -933,7 +954,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.center
desc: textAlign center is the center of the em squares (not the bounding box)
@@ -955,7 +977,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.basic
@@ -973,7 +996,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.nonspace
desc: Non-space characters are not converted to U+0020 and collapsed
@@ -990,7 +1014,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.width.basic
desc: The width of character is same as font used
@@ -1006,7 +1031,8 @@
ctx.font = '100px CanvasTest';
@assert ctx.measureText('A').width === 100;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.width.empty
desc: The empty string has zero width
@@ -1017,7 +1043,8 @@
{{ load_font }}
ctx.font = '50px CanvasTest';
@assert ctx.measureText("").width === 0;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.actualBoundingBox
desc: Testing actualBoundingBox
@@ -1052,7 +1079,8 @@
@assert ctx.measureText('ABCD').actualBoundingBoxRight >= 200;
@assert ctx.measureText('ABCD').actualBoundingBoxAscent >= 85;
@assert ctx.measureText('ABCD').actualBoundingBoxDescent >= 37;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox
desc: Testing fontBoundingBox measurements
@@ -1069,7 +1097,8 @@
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 30;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox.ahem
desc: Testing fontBoundingBox for font ahem
@@ -1085,7 +1114,8 @@
@assert ctx.measureText('A').fontBoundingBoxDescent === 10;
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 40;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox-reduced-ascent
desc: Testing fontBoundingBox for OffscreenCanvas with reduced ascent metric
@@ -1102,7 +1132,8 @@
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 10;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox-zero-descent
desc: Testing fontBoundingBox for OffscreenCanvas with zero descent metric
@@ -1119,7 +1150,8 @@
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 30;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 0;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.emHeights
desc: Testing emHeights
@@ -1138,7 +1170,8 @@
@assert ctx.measureText('ABCD').emHeightAscent === 30;
@assert ctx.measureText('ABCD').emHeightDescent === 10;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.emHeights-low-ascent
desc: Testing emHeights with reduced ascent metric
@@ -1157,7 +1190,8 @@
@assert ctx.measureText('ABCD').emHeightAscent === 20;
@assert ctx.measureText('ABCD').emHeightDescent === 20;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.emHeights-zero-descent
desc: Testing emHeights with zero descent metric
@@ -1176,7 +1210,8 @@
@assert ctx.measureText('ABCD').emHeightAscent === 40;
@assert ctx.measureText('ABCD').emHeightDescent === 0;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.baselines
desc: Testing baselines
@@ -1195,7 +1230,8 @@
@assert Math.abs(ctx.measureText('ABCD').alphabeticBaseline) === 0;
@assert ctx.measureText('ABCD').ideographicBaseline === 6.25;
@assert ctx.measureText('ABCD').hangingBaseline === 25;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.selection-rects.tentative
desc: >-
@@ -1432,6 +1468,219 @@
() => tm.getSelectionRects(text.length + 1, text.length + 1) );
}
+- name: 2d.text.measure.getActualBoundingBox.tentative
+ desc: >-
+ Test TextMetrics::getActualBoundingBox(), with text align {{ text_align }}
+ , and {{ letter_spacing }} letter spacing.
+ test_type: promise
+ fonts:
+ - CanvasTest
+ size: [800, 200]
+ code: |
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ {{ load_font }}
+ ctx.textAlign = '{{ text_align }}';
+ ctx.letterSpacing = '{{ letter_spacing }}';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+ variants_layout:
+ [multi_files, single_file, single_file]
+ variants:
+ - *load-font-variant-definition
+ - align-left:
+ text_align: left
+ align-center:
+ text_align: center
+ align-right:
+ text_align: right
+ - no-spacing:
+ letter_spacing: 0px
+ spacing:
+ letter_spacing: 10px
+
+- name: 2d.text.measure.getActualBoundingBox-full-text.tentative
+ desc: >-
+ Test TextMetrics::getActualBoundingBox() for the full length of the string
+ for some edge cases, with direction {{ text_direction }} and
+ {{ variant_names[1] }}
+ size: [800, 200]
+ code: |
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+ {% if use_directional_override %}
+
+ 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;
+ }
+ {% endif %}
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = '{{ text_direction }}';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ {% if use_directional_override %}
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ {% endif %}
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+ variants_layout: [single_file, single_file]
+ variants:
+ - direction-ltr:
+ text_direction: ltr
+ direction-rtl:
+ text_direction: rtl
+ - no-directional-override:
+ use_directional_override: false
+ directional-override:
+ use_directional_override: true
+
+- name: 2d.text.measure.getActualBoundingBox-exceptions.tentative
+ desc: >-
+ Check that TextMetrics::getActualBoundingBox() throws when using invalid
+ indexes.
+ code: |
+ const kTexts = [
+ 'UNAVAILABLE',
+ 'ππΆπ',
+ 'οΌοΌγοΌοΌ',
+ '-abcd_'
+ ]
+
+ for (const text of kTexts) {
+ const tm = ctx.measureText(text);
+ // Handled by the IDL binding.
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
+ // Thrown in TextMetrics.
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, 0) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(0, text.length + 1) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, text.length + 1) );
+ }
+
- name: 2d.text.drawing.style.absolute.spacing
desc: Testing letter spacing and word spacing with absolute length
code: |
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index ebf01fd9..dbf2eb0 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -1893,6 +1893,7 @@
getter ideographicBaseline
getter width
method constructor
+ method getActualBoundingBox
method getSelectionRects
interface TimestampTrigger
attribute @@toStringTag
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index dcccbe0..08b798b7 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -2127,6 +2127,7 @@
[Worker] getter ideographicBaseline
[Worker] getter width
[Worker] method constructor
+[Worker] method getActualBoundingBox
[Worker] method getSelectionRects
[Worker] interface TimestampTrigger
[Worker] attribute @@toStringTag
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index fe50f61..8b934ec 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -10042,6 +10042,7 @@
getter ideographicBaseline
getter width
method constructor
+ method getActualBoundingBox
method getSelectionRects
interface TextTrack : EventTarget
attribute @@toStringTag
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
index a3aa6f6..e323d80 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -1803,6 +1803,7 @@
[Worker] getter ideographicBaseline
[Worker] getter width
[Worker] method constructor
+[Worker] method getActualBoundingBox
[Worker] method getSelectionRects
[Worker] interface TimestampTrigger
[Worker] attribute @@toStringTag