diff --git a/Tests/LibWeb/Layout/expected/letter-spacing-end-of-line-001.txt b/Tests/LibWeb/Layout/expected/letter-spacing-end-of-line-001.txt
new file mode 100644
index 00000000000..258f1f98eaa
--- /dev/null
+++ b/Tests/LibWeb/Layout/expected/letter-spacing-end-of-line-001.txt
@@ -0,0 +1,31 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+ BlockContainer at (0,0) content-size 800x120 [BFC] children: not-inline
+ BlockContainer
at (8,8) content-size 784x104 children: not-inline
+ BlockContainer at (8,8) content-size 784x52 children: inline
+ frag 0 from TextNode start: 0, length: 5, rect: [8,8 121.359375x52] baseline: 40.390625
+ "12345"
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (8,60) content-size 784x0 children: inline
+ TextNode <#text>
+ BlockContainer
at (8,60) content-size 784x52 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [93,60 28.03125x52] baseline: 40.390625
+ "a"
+ BlockContainer
at (8,60) content-size 85.0625x52 floating [BFC] children: inline
+ frag 0 from TextNode start: 0, length: 2, rect: [8,60 85.0625x52] baseline: 40.390625
+ "aa"
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (8,112) content-size 784x0 children: inline
+ TextNode <#text>
+
+ViewportPaintable (Viewport<#document>) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [0,0 800x120]
+ PaintableWithLines (BlockContainer) [8,8 784x104]
+ PaintableWithLines (BlockContainer) [8,8 784x52]
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [8,60 784x0]
+ PaintableWithLines (BlockContainer
) [8,60 784x52]
+ PaintableWithLines (BlockContainer
) [8,60 85.0625x52]
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [8,112 784x0]
diff --git a/Tests/LibWeb/Layout/input/letter-spacing-end-of-line-001.html b/Tests/LibWeb/Layout/input/letter-spacing-end-of-line-001.html
new file mode 100644
index 00000000000..a275eec0f67
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/letter-spacing-end-of-line-001.html
@@ -0,0 +1,14 @@
+
+
+
+12345
+aaa
diff --git a/Userland/Libraries/LibGfx/TextLayout.cpp b/Userland/Libraries/LibGfx/TextLayout.cpp
index e580c0759c6..c1ad71a4939 100644
--- a/Userland/Libraries/LibGfx/TextLayout.cpp
+++ b/Userland/Libraries/LibGfx/TextLayout.cpp
@@ -12,7 +12,7 @@
namespace Gfx {
-RefPtr shape_text(FloatPoint baseline_start, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type)
+RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type)
{
hb_buffer_t* buffer = hb_buffer_create();
ScopeGuard destroy_buffer = [&]() { hb_buffer_destroy(buffer); };
@@ -38,6 +38,11 @@ RefPtr shape_text(FloatPoint baseline_start, Utf8View string, Gfx::Fon
+ FloatPoint { positions[i].x_offset, positions[i].y_offset } / text_shaping_resolution;
glyph_run.append({ position, glyph_info[i].codepoint });
point += FloatPoint { positions[i].x_advance, positions[i].y_advance } / text_shaping_resolution;
+
+ // don't apply spacing to last glyph
+ // https://drafts.csswg.org/css-text/#example-7880704e
+ if (i != (glyph_count - 1))
+ point.translate_by(letter_spacing, 0);
}
return adopt_ref(*new Gfx::GlyphRun(move(glyph_run), font, text_type, point.x()));
@@ -45,7 +50,7 @@ RefPtr shape_text(FloatPoint baseline_start, Utf8View string, Gfx::Fon
float measure_text_width(Utf8View const& string, Gfx::Font const& font)
{
- auto glyph_run = shape_text({}, string, font, GlyphRun::TextType::Common);
+ auto glyph_run = shape_text({}, 0, string, font, GlyphRun::TextType::Common);
return glyph_run->width();
}
diff --git a/Userland/Libraries/LibGfx/TextLayout.h b/Userland/Libraries/LibGfx/TextLayout.h
index 8988244a2d7..8cbba66b902 100644
--- a/Userland/Libraries/LibGfx/TextLayout.h
+++ b/Userland/Libraries/LibGfx/TextLayout.h
@@ -64,7 +64,7 @@ private:
float m_width { 0 };
};
-RefPtr shape_text(FloatPoint baseline_start, Utf8View string, Gfx::Font const& font, GlyphRun::TextType);
+RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType);
float measure_text_width(Utf8View const& string, Gfx::Font const& font);
}
diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
index f97d9eba6af..641155154d6 100644
--- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
+++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
@@ -555,7 +555,7 @@ CanvasRenderingContext2D::PreparedText CanvasRenderingContext2D::prepare_text(By
Gfx::FloatPoint anchor { 0, 0 };
auto physical_alignment = Gfx::TextAlignment::CenterLeft;
- auto glyph_run = Gfx::shape_text(anchor, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr);
+ auto glyph_run = Gfx::shape_text(anchor, 0, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr);
// 8. Let result be an array constructed by iterating over each glyph in the inline box from left to right (if any), adding to the array, for each glyph, the shape of the glyph as it is in the inline box, positioned on a coordinate space using CSS pixels with its origin is at the anchor point.
PreparedText prepared_text { glyph_run, physical_alignment, { 0, 0, static_cast(glyph_run->width()), static_cast(height) } };
diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp
index cc683b65f44..7c9a3f8de1f 100644
--- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp
+++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp
@@ -241,6 +241,10 @@ Optional InlineLevelIterator::next_without_lookahead(
};
}
+ auto resolution_context = CSS::Length::ResolutionContext::for_layout_node(text_node);
+ auto letter_spacing = text_node.computed_values().letter_spacing().resolved(resolution_context).to_px(text_node);
+ auto word_spacing = text_node.computed_values().word_spacing().resolved(resolution_context).to_px(text_node);
+
auto x = 0.0f;
if (chunk.has_breaking_tab) {
CSSPixels accumulated_width;
@@ -262,11 +266,6 @@ Optional InlineLevelIterator::next_without_lookahead(
},
[&](CSS::NumberOrCalculated const& n) -> CSSPixels {
auto tab_number = n.resolved(text_node);
- auto computed_letter_spacing = text_node.computed_values().letter_spacing();
- auto computed_word_spacing = text_node.computed_values().word_spacing();
-
- auto letter_spacing = computed_letter_spacing.resolved(resolution_context).to_px(text_node);
- auto word_spacing = computed_word_spacing.resolved(resolution_context).to_px(text_node);
return CSSPixels::nearest_value_for(tab_number * (chunk.font->glyph_width(' ') + word_spacing.to_float() + letter_spacing.to_float()));
});
@@ -294,7 +293,7 @@ Optional InlineLevelIterator::next_without_lookahead(
x = tab_stop_dist.to_float();
}
- auto glyph_run = Gfx::shape_text({ x, 0 }, chunk.view, chunk.font, text_type);
+ auto glyph_run = Gfx::shape_text({ x, 0 }, letter_spacing.to_float(), chunk.view, chunk.font, text_type);
CSSPixels chunk_width = CSSPixels::nearest_value_for(glyph_run->width());
diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp
index 8967649a809..ffd8aed4b76 100644
--- a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp
+++ b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp
@@ -220,7 +220,7 @@ void DisplayListRecorder::draw_text(Gfx::IntRect const& rect, String raw_text, G
if (rect.is_empty())
return;
- auto glyph_run = Gfx::shape_text({}, raw_text.code_points(), font, Gfx::GlyphRun::TextType::Ltr);
+ auto glyph_run = Gfx::shape_text({}, 0, raw_text.code_points(), font, Gfx::GlyphRun::TextType::Ltr);
float baseline_x = 0;
if (alignment == Gfx::TextAlignment::CenterLeft) {
baseline_x = rect.x();