LibWeb: Use advance width for canvas text alignment
Previously, `text_path() used the glyph outline bounding box width to compute text alignment offsets and maxWidth scaling. The bounding box only covers visible glyph outlines, excluding the advance of spaces and glyph side bearings. This caused text with `textAlign` "end", "right", or "center" to be mispositioned. We now use the sum of glyph run advance widths instead.
This commit is contained in:
parent
8039419775
commit
3049d77979
12 changed files with 265 additions and 2 deletions
|
|
@ -311,11 +311,11 @@ Gfx::Path CanvasRenderingContext2D::text_path(Utf16String const& text, float x,
|
||||||
auto glyph_runs = Gfx::shape_text({ x, y }, text.utf16_view(), *font_cascade_list,
|
auto glyph_runs = Gfx::shape_text({ x, y }, text.utf16_view(), *font_cascade_list,
|
||||||
resolved_letter_spacing(drawing_state, canvas_element()));
|
resolved_letter_spacing(drawing_state, canvas_element()));
|
||||||
Gfx::Path path;
|
Gfx::Path path;
|
||||||
|
float text_width = 0;
|
||||||
for (auto const& glyph_run : glyph_runs) {
|
for (auto const& glyph_run : glyph_runs) {
|
||||||
path.glyph_run(glyph_run);
|
path.glyph_run(glyph_run);
|
||||||
|
text_width += glyph_run->width();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto text_width = path.bounding_box().width();
|
|
||||||
Gfx::AffineTransform transform = {};
|
Gfx::AffineTransform transform = {};
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/canvas.html#text-preparation-algorithm:
|
// https://html.spec.whatwg.org/multipage/canvas.html#text-preparation-algorithm:
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
|
@ -0,0 +1,6 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 1 tests
|
||||||
|
|
||||||
|
1 Pass
|
||||||
|
Pass textAlign center is the center of the em squares (not the bounding box)
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 1 tests
|
||||||
|
|
||||||
|
1 Pass
|
||||||
|
Pass textAlign end with ltr is the right edge
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 1 tests
|
||||||
|
|
||||||
|
1 Pass
|
||||||
|
Pass textAlign right is the right of the last em square (not the bounding box)
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 1 tests
|
||||||
|
|
||||||
|
1 Pass
|
||||||
|
Pass fillText handles maxWidth based on line size, not bounding box size
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 1 tests
|
||||||
|
|
||||||
|
1 Pass
|
||||||
|
Pass Space characters at the end of a line are NOT collapsed
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Canvas test: 2d.text.draw.align.center</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">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: CanvasTest;
|
||||||
|
src: url("../../../../fonts/CanvasTest.ttf");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body class="show_output">
|
||||||
|
|
||||||
|
<h1>2d.text.draw.align.center</h1>
|
||||||
|
<p class="desc">textAlign center is the center of the em squares (not the bounding box)</p>
|
||||||
|
|
||||||
|
|
||||||
|
<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
|
||||||
|
<p class="output">Actual output:</p>
|
||||||
|
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
|
||||||
|
<p class="output expectedtext">Expected output:<p><img src="../../../../images/green-100x50.png" class="output expected" id="expected" alt="">
|
||||||
|
<ul id="d"></ul>
|
||||||
|
<script>
|
||||||
|
promise_test(async t => {
|
||||||
|
|
||||||
|
var canvas = document.getElementById('c');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
await document.fonts.ready;
|
||||||
|
ctx.font = '50px CanvasTest';
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText('DD', 50, 37.5);
|
||||||
|
_assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 5,45, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 95,45, 0,255,0,255, 2);
|
||||||
|
|
||||||
|
}, "textAlign center is the center of the em squares (not the bounding box)");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Canvas test: 2d.text.draw.align.end.ltr</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">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: CanvasTest;
|
||||||
|
src: url("../../../../fonts/CanvasTest.ttf");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body class="show_output">
|
||||||
|
|
||||||
|
<h1>2d.text.draw.align.end.ltr</h1>
|
||||||
|
<p class="desc">textAlign end with ltr is the right edge</p>
|
||||||
|
|
||||||
|
|
||||||
|
<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
|
||||||
|
<p class="output">Actual output:</p>
|
||||||
|
<canvas id="c" class="output" width="100" height="50"dir="ltr"><p class="fallback">FAIL (fallback content)</p></canvas>
|
||||||
|
<p class="output expectedtext">Expected output:<p><img src="../../../../images/green-100x50.png" class="output expected" id="expected" alt="">
|
||||||
|
<ul id="d"></ul>
|
||||||
|
<script>
|
||||||
|
promise_test(async t => {
|
||||||
|
|
||||||
|
var canvas = document.getElementById('c');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
await document.fonts.ready;
|
||||||
|
ctx.font = '50px CanvasTest';
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.textAlign = 'end';
|
||||||
|
ctx.fillText('DD', 100, 37.5);
|
||||||
|
_assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 5,45, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 95,45, 0,255,0,255, 2);
|
||||||
|
|
||||||
|
}, "textAlign end with ltr is the right edge");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Canvas test: 2d.text.draw.align.right</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">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: CanvasTest;
|
||||||
|
src: url("../../../../fonts/CanvasTest.ttf");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body class="show_output">
|
||||||
|
|
||||||
|
<h1>2d.text.draw.align.right</h1>
|
||||||
|
<p class="desc">textAlign right is the right of the last em square (not the bounding box)</p>
|
||||||
|
|
||||||
|
|
||||||
|
<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
|
||||||
|
<p class="output">Actual output:</p>
|
||||||
|
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
|
||||||
|
<p class="output expectedtext">Expected output:<p><img src="../../../../images/green-100x50.png" class="output expected" id="expected" alt="">
|
||||||
|
<ul id="d"></ul>
|
||||||
|
<script>
|
||||||
|
promise_test(async t => {
|
||||||
|
|
||||||
|
var canvas = document.getElementById('c');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
await document.fonts.ready;
|
||||||
|
ctx.font = '50px CanvasTest';
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.textAlign = 'right';
|
||||||
|
ctx.fillText('DD', 100, 37.5);
|
||||||
|
_assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 5,45, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 95,45, 0,255,0,255, 2);
|
||||||
|
|
||||||
|
}, "textAlign right is the right of the last em square (not the bounding box)");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Canvas test: 2d.text.draw.fill.maxWidth.bound</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">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: CanvasTest;
|
||||||
|
src: url("../../../../fonts/CanvasTest.ttf");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body class="show_output">
|
||||||
|
|
||||||
|
<h1>2d.text.draw.fill.maxWidth.bound</h1>
|
||||||
|
<p class="desc">fillText handles maxWidth based on line size, not bounding box size</p>
|
||||||
|
|
||||||
|
|
||||||
|
<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
|
||||||
|
<p class="output">Actual output:</p>
|
||||||
|
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
|
||||||
|
<p class="output expectedtext">Expected output:<p><img src="../../../../images/green-100x50.png" class="output expected" id="expected" alt="">
|
||||||
|
<ul id="d"></ul>
|
||||||
|
<script>
|
||||||
|
promise_test(async t => {
|
||||||
|
|
||||||
|
var canvas = document.getElementById('c');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
await document.fonts.ready;
|
||||||
|
ctx.font = '50px CanvasTest';
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.fillText('DD', 0, 37.5, 100);
|
||||||
|
_assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
|
||||||
|
|
||||||
|
}, "fillText handles maxWidth based on line size, not bounding box size");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Canvas test: 2d.text.draw.space.collapse.end</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">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: CanvasTest;
|
||||||
|
src: url("../../../../fonts/CanvasTest.ttf");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body class="show_output">
|
||||||
|
|
||||||
|
<h1>2d.text.draw.space.collapse.end</h1>
|
||||||
|
<p class="desc">Space characters at the end of a line are NOT collapsed</p>
|
||||||
|
|
||||||
|
|
||||||
|
<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
|
||||||
|
<p class="output">Actual output:</p>
|
||||||
|
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
|
||||||
|
<p class="output expectedtext">Expected output:<p><img src="../../../../images/green-100x50.png" class="output expected" id="expected" alt="">
|
||||||
|
<ul id="d"></ul>
|
||||||
|
<script>
|
||||||
|
promise_test(async t => {
|
||||||
|
|
||||||
|
var canvas = document.getElementById('c');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
await document.fonts.ready;
|
||||||
|
ctx.font = '50px CanvasTest';
|
||||||
|
ctx.fillStyle = '#f00';
|
||||||
|
ctx.fillRect(0, 0, 100, 50);
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.textAlign = 'right';
|
||||||
|
ctx.fillText('EE ', 100, 37.5);
|
||||||
|
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
|
||||||
|
_assertPixelApprox(canvas, 75,25, 255,0,0,255, 2);
|
||||||
|
|
||||||
|
}, "Space characters at the end of a line are NOT collapsed");
|
||||||
|
</script>
|
||||||
|
|
||||||
Loading…
Reference in a new issue