Skip to content

Commit 3049d77

Browse files
tcl3awesomekling
authored andcommitted
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.
1 parent 8039419 commit 3049d77

12 files changed

Lines changed: 265 additions & 2 deletions

Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,11 @@ Gfx::Path CanvasRenderingContext2D::text_path(Utf16String const& text, float x,
311311
auto glyph_runs = Gfx::shape_text({ x, y }, text.utf16_view(), *font_cascade_list,
312312
resolved_letter_spacing(drawing_state, canvas_element()));
313313
Gfx::Path path;
314+
float text_width = 0;
314315
for (auto const& glyph_run : glyph_runs) {
315316
path.glyph_run(glyph_run);
317+
text_width += glyph_run->width();
316318
}
317-
318-
auto text_width = path.bounding_box().width();
319319
Gfx::AffineTransform transform = {};
320320

321321
// https://html.spec.whatwg.org/multipage/canvas.html#text-preparation-algorithm:
101 Bytes
Loading
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Harness status: OK
2+
3+
Found 1 tests
4+
5+
1 Pass
6+
Pass textAlign center is the center of the em squares (not the bounding box)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Harness status: OK
2+
3+
Found 1 tests
4+
5+
1 Pass
6+
Pass textAlign end with ltr is the right edge
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Harness status: OK
2+
3+
Found 1 tests
4+
5+
1 Pass
6+
Pass textAlign right is the right of the last em square (not the bounding box)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Harness status: OK
2+
3+
Found 1 tests
4+
5+
1 Pass
6+
Pass fillText handles maxWidth based on line size, not bounding box size
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Harness status: OK
2+
3+
Found 1 tests
4+
5+
1 Pass
6+
Pass Space characters at the end of a line are NOT collapsed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE html>
2+
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
3+
<meta charset="UTF-8">
4+
<title>Canvas test: 2d.text.draw.align.center</title>
5+
<script src="../../../../resources/testharness.js"></script>
6+
<script src="../../../../resources/testharnessreport.js"></script>
7+
<script src="../../../../html/canvas/resources/canvas-tests.js"></script>
8+
<link rel="stylesheet" href="../../../../html/canvas/resources/canvas-tests.css">
9+
<style>
10+
@font-face {
11+
font-family: CanvasTest;
12+
src: url("../../../../fonts/CanvasTest.ttf");
13+
}
14+
</style>
15+
<body class="show_output">
16+
17+
<h1>2d.text.draw.align.center</h1>
18+
<p class="desc">textAlign center is the center of the em squares (not the bounding box)</p>
19+
20+
21+
<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
22+
<p class="output">Actual output:</p>
23+
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
24+
<p class="output expectedtext">Expected output:<p><img src="../../../../images/green-100x50.png" class="output expected" id="expected" alt="">
25+
<ul id="d"></ul>
26+
<script>
27+
promise_test(async t => {
28+
29+
var canvas = document.getElementById('c');
30+
var ctx = canvas.getContext('2d');
31+
32+
await document.fonts.ready;
33+
ctx.font = '50px CanvasTest';
34+
ctx.fillStyle = '#f00';
35+
ctx.fillRect(0, 0, 100, 50);
36+
ctx.fillStyle = '#0f0';
37+
ctx.textAlign = 'center';
38+
ctx.fillText('DD', 50, 37.5);
39+
_assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
40+
_assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
41+
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
42+
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
43+
_assertPixelApprox(canvas, 5,45, 0,255,0,255, 2);
44+
_assertPixelApprox(canvas, 95,45, 0,255,0,255, 2);
45+
46+
}, "textAlign center is the center of the em squares (not the bounding box)");
47+
</script>
48+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE html>
2+
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
3+
<meta charset="UTF-8">
4+
<title>Canvas test: 2d.text.draw.align.end.ltr</title>
5+
<script src="../../../../resources/testharness.js"></script>
6+
<script src="../../../../resources/testharnessreport.js"></script>
7+
<script src="../../../../html/canvas/resources/canvas-tests.js"></script>
8+
<link rel="stylesheet" href="../../../../html/canvas/resources/canvas-tests.css">
9+
<style>
10+
@font-face {
11+
font-family: CanvasTest;
12+
src: url("../../../../fonts/CanvasTest.ttf");
13+
}
14+
</style>
15+
<body class="show_output">
16+
17+
<h1>2d.text.draw.align.end.ltr</h1>
18+
<p class="desc">textAlign end with ltr is the right edge</p>
19+
20+
21+
<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
22+
<p class="output">Actual output:</p>
23+
<canvas id="c" class="output" width="100" height="50"dir="ltr"><p class="fallback">FAIL (fallback content)</p></canvas>
24+
<p class="output expectedtext">Expected output:<p><img src="../../../../images/green-100x50.png" class="output expected" id="expected" alt="">
25+
<ul id="d"></ul>
26+
<script>
27+
promise_test(async t => {
28+
29+
var canvas = document.getElementById('c');
30+
var ctx = canvas.getContext('2d');
31+
32+
await document.fonts.ready;
33+
ctx.font = '50px CanvasTest';
34+
ctx.fillStyle = '#f00';
35+
ctx.fillRect(0, 0, 100, 50);
36+
ctx.fillStyle = '#0f0';
37+
ctx.textAlign = 'end';
38+
ctx.fillText('DD', 100, 37.5);
39+
_assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
40+
_assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
41+
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
42+
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
43+
_assertPixelApprox(canvas, 5,45, 0,255,0,255, 2);
44+
_assertPixelApprox(canvas, 95,45, 0,255,0,255, 2);
45+
46+
}, "textAlign end with ltr is the right edge");
47+
</script>
48+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE html>
2+
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
3+
<meta charset="UTF-8">
4+
<title>Canvas test: 2d.text.draw.align.right</title>
5+
<script src="../../../../resources/testharness.js"></script>
6+
<script src="../../../../resources/testharnessreport.js"></script>
7+
<script src="../../../../html/canvas/resources/canvas-tests.js"></script>
8+
<link rel="stylesheet" href="../../../../html/canvas/resources/canvas-tests.css">
9+
<style>
10+
@font-face {
11+
font-family: CanvasTest;
12+
src: url("../../../../fonts/CanvasTest.ttf");
13+
}
14+
</style>
15+
<body class="show_output">
16+
17+
<h1>2d.text.draw.align.right</h1>
18+
<p class="desc">textAlign right is the right of the last em square (not the bounding box)</p>
19+
20+
21+
<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
22+
<p class="output">Actual output:</p>
23+
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
24+
<p class="output expectedtext">Expected output:<p><img src="../../../../images/green-100x50.png" class="output expected" id="expected" alt="">
25+
<ul id="d"></ul>
26+
<script>
27+
promise_test(async t => {
28+
29+
var canvas = document.getElementById('c');
30+
var ctx = canvas.getContext('2d');
31+
32+
await document.fonts.ready;
33+
ctx.font = '50px CanvasTest';
34+
ctx.fillStyle = '#f00';
35+
ctx.fillRect(0, 0, 100, 50);
36+
ctx.fillStyle = '#0f0';
37+
ctx.textAlign = 'right';
38+
ctx.fillText('DD', 100, 37.5);
39+
_assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
40+
_assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
41+
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
42+
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
43+
_assertPixelApprox(canvas, 5,45, 0,255,0,255, 2);
44+
_assertPixelApprox(canvas, 95,45, 0,255,0,255, 2);
45+
46+
}, "textAlign right is the right of the last em square (not the bounding box)");
47+
</script>
48+

0 commit comments

Comments
 (0)