LibWeb: Start implementing letter spacing

Letter spacing is applied during text shaping and `shape_text` is used
in places other `InlineLevelIterator` so way may have more work to do,
however this is a good start :^).
This commit is contained in:
Kostya Farber 2024-11-05 07:11:34 +00:00 committed by Sam Atkins
commit 373c80db68
Notes: github-actions[bot] 2024-11-05 10:41:24 +00:00
7 changed files with 60 additions and 11 deletions

View file

@ -0,0 +1,31 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x120 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x104 children: not-inline
BlockContainer <div> 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 <div> 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 <span> 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<HTML>) [0,0 800x120]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x104]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x52]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [8,60 784x0]
PaintableWithLines (BlockContainer<DIV>) [8,60 784x52]
PaintableWithLines (BlockContainer<SPAN>) [8,60 85.0625x52]
TextPaintable (TextNode<#text>)
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [8,112 784x0]

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<style>
div {
font-family: monospace;
font-size: 3em;
}
span {
float: left;
letter-spacing: 1ch;
}
</style>
<div>12345</div>
<div><span>aa</span>a</div>

View file

@ -12,7 +12,7 @@
namespace Gfx {
RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type)
RefPtr<GlyphRun> 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<GlyphRun> 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<GlyphRun> 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();
}

View file

@ -64,7 +64,7 @@ private:
float m_width { 0 };
};
RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, Utf8View string, Gfx::Font const& font, GlyphRun::TextType);
RefPtr<GlyphRun> 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);
}

View file

@ -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<int>(glyph_run->width()), static_cast<int>(height) } };

View file

@ -241,6 +241,10 @@ Optional<InlineLevelIterator::Item> 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::Item> 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::Item> 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());

View file

@ -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();