diff --git a/Libraries/LibGfx/Font/Font.h b/Libraries/LibGfx/Font/Font.h index b538bbe39458b..155c0d600c2e3 100644 --- a/Libraries/LibGfx/Font/Font.h +++ b/Libraries/LibGfx/Font/Font.h @@ -32,6 +32,7 @@ struct ShapingCacheKey { Utf16String text; u8 text_type { 0 }; u32 letter_spacing_bit_pattern { 0 }; + u32 word_spacing_bit_pattern { 0 }; bool operator==(ShapingCacheKey const&) const = default; }; @@ -154,7 +155,7 @@ template<> struct Traits : public DefaultTraits { static unsigned hash(Gfx::ShapingCacheKey const& key) { - return pair_int_hash(key.text.hash(), pair_int_hash(key.text_type, key.letter_spacing_bit_pattern)); + return pair_int_hash(key.text.hash(), pair_int_hash(key.text_type, pair_int_hash(key.letter_spacing_bit_pattern, key.word_spacing_bit_pattern))); } }; diff --git a/Libraries/LibGfx/Rust/src/text_layout.rs b/Libraries/LibGfx/Rust/src/text_layout.rs index d3eb3b5e57315..5ba31cfa0ca0b 100644 --- a/Libraries/LibGfx/Rust/src/text_layout.rs +++ b/Libraries/LibGfx/Rust/src/text_layout.rs @@ -50,6 +50,8 @@ pub struct ShapedRunView { pub glyphs: *const DrawGlyph, pub glyph_count: usize, pub width: f32, + pub trailing_whitespace_length_in_code_units: usize, + pub trailing_whitespace_advance: f32, pub retained: *mut c_void, } @@ -61,6 +63,7 @@ unsafe extern "C" { text_type: TextType, baseline_start_x: f32, letter_spacing: f32, + word_spacing: f32, ) -> ShapedRunView; fn ladybird_gfx_glyph_run_unref(retained: *mut c_void); @@ -82,6 +85,8 @@ impl Drop for RetainedGlyphRun { pub struct ShapedText { glyphs: Vec, width: f32, + trailing_whitespace_length_in_code_units: usize, + trailing_whitespace_advance: f32, } impl ShapedText { @@ -94,6 +99,16 @@ impl ShapedText { pub fn width(&self) -> f32 { self.width } + + #[inline] + pub fn trailing_whitespace_length_in_code_units(&self) -> usize { + self.trailing_whitespace_length_in_code_units + } + + #[inline] + pub fn trailing_whitespace_advance(&self) -> f32 { + self.trailing_whitespace_advance + } } pub fn shape_text( @@ -102,6 +117,7 @@ pub fn shape_text( text_type: TextType, baseline_start_x: f32, letter_spacing: f32, + word_spacing: f32, ) -> ShapedText { // SAFETY: FontRef keeps the font live, and the text slice remains valid // for the duration of the synchronous shaping call. @@ -113,6 +129,7 @@ pub fn shape_text( text_type, baseline_start_x, letter_spacing, + word_spacing, ) }; let retained = RetainedGlyphRun { @@ -127,6 +144,13 @@ pub fn shape_text( unsafe { std::slice::from_raw_parts(view.glyphs, view.glyph_count) }.to_vec() }; let width = view.width; + let trailing_whitespace_length_in_code_units = view.trailing_whitespace_length_in_code_units; + let trailing_whitespace_advance = view.trailing_whitespace_advance; drop(retained); - ShapedText { glyphs, width } + ShapedText { + glyphs, + width, + trailing_whitespace_length_in_code_units, + trailing_whitespace_advance, + } } diff --git a/Libraries/LibGfx/TextLayout.cpp b/Libraries/LibGfx/TextLayout.cpp index c198faa93fb9b..67861167f97de 100644 --- a/Libraries/LibGfx/TextLayout.cpp +++ b/Libraries/LibGfx/TextLayout.cpp @@ -137,7 +137,7 @@ Vector> shape_text(FloatPoint baseline_start, Utf16View FloatPoint last_position = baseline_start; auto add_run = [&runs, &last_position, letter_spacing](Utf16View const& string, Font const& font) { - auto run = shape_text(last_position, letter_spacing, string, font, GlyphRun::TextType::Common); + auto run = shape_text(last_position, letter_spacing, 0.f, string, font, GlyphRun::TextType::Common); last_position.translate_by(run->width(), 0); runs.append(*run); }; @@ -207,7 +207,27 @@ static hb_buffer_t* setup_text_shaping(Utf16View const& string, Font const& font return buffer; } -static NonnullOwnPtr build_origin_relative_shape(Utf16View const& string, Font const& font, GlyphRun::TextType text_type, float letter_spacing) +// https://drafts.csswg.org/css-text-4/#word-separator +static bool is_word_separator(u32 code_point) +{ + // Word-separator characters include the space (U+0020), the no-break space (U+00A0), the Ethiopic word space + // (U+1361), the Aegean word separators (U+10100,U+10101), the Ugaritic word divider (U+1039F), and the Phoenician + // Word Separator (U+1091F). + // AD-HOC: Only the space and no-break space are treated as word separators, matching other engines. The line feed + // is also included because whitespace collapsing can leave a segment break in shaped text, where it + // behaves as a space. + return code_point == 0x0020 || code_point == 0x00A0 || code_point == 0x000A; +} + +static size_t length_of_trailing_whitespace_run(Utf16View const& string) +{ + size_t length = 0; + while (length < string.length_in_code_units() && is_ascii_space(string.code_unit_at(string.length_in_code_units() - length - 1))) + ++length; + return length; +} + +static NonnullOwnPtr build_origin_relative_shape(Utf16View const& string, Font const& font, GlyphRun::TextType text_type, float letter_spacing, float word_spacing) { auto const& metrics = font.pixel_metrics(); auto* buffer = setup_text_shaping(string, font, text_type); @@ -220,6 +240,11 @@ static NonnullOwnPtr build_origin_relative_shape(Utf16View const& glyphs.ensure_capacity(glyph_count); FloatPoint point; + // The trailing whitespace advance is recorded so that trimming it at line end can subtract exactly what shaping + // added, spacing included. + TrailingWhitespace trailing_whitespace { .length_in_code_units = length_of_trailing_whitespace_run(string), .advance = 0 }; + auto first_trailing_whitespace_offset = string.length_in_code_units() - trailing_whitespace.length_in_code_units; + // We track the code unit length rather than just the code unit offset because LibWeb may later collapse glyph runs. // Updating the offset of each glyph gets tricky when handling text direction (LTR/RTL). So rather than doing that, // we just provide the glyph's code unit length and base LibWeb algorithms on that. @@ -250,10 +275,15 @@ static NonnullOwnPtr build_origin_relative_shape(Utf16View const& - FloatPoint { 0, metrics.ascent } + FloatPoint { positions[i].x_offset, positions[i].y_offset } / text_shaping_resolution; + auto extra_advance = letter_spacing; + if (auto starting_offset = glyph_info[i].cluster; starting_offset < string.length_in_code_units() + && is_word_separator(string.code_point_at(starting_offset))) + extra_advance += word_spacing; + glyphs.unchecked_append({ .position = position, .length_in_code_units = glyph_length_in_code_units(i), - .glyph_width = should_paint ? positions[i].x_advance / text_shaping_resolution + letter_spacing : 0, + .glyph_width = should_paint ? positions[i].x_advance / text_shaping_resolution + extra_advance : 0, .glyph_id = glyph_info[i].codepoint, .should_paint = should_paint, }); @@ -265,19 +295,24 @@ static NonnullOwnPtr build_origin_relative_shape(Utf16View const& // NOTE: The spec says that we "really should not" apply letter-spacing to the trailing edge of a line but // other browsers do so we will as well. https://drafts.csswg.org/css-text/#example-7880704e - point.translate_by(letter_spacing, 0); + point.translate_by(extra_advance, 0); + + if (glyph_info[i].cluster >= first_trailing_whitespace_offset) + trailing_whitespace.advance += glyphs.last().glyph_width; } hb_buffer_destroy(buffer); - return make(move(glyphs), point.x()); + return make(move(glyphs), point.x(), trailing_whitespace); } -NonnullRefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf16View const& string, Font const& font, GlyphRun::TextType text_type) +NonnullRefPtr shape_text(FloatPoint baseline_start, float letter_spacing, float word_spacing, Utf16View const& string, Font const& font, GlyphRun::TextType text_type, TrailingWhitespace* out_trailing_whitespace) { auto& shaping_cache = font.shaping_cache(); auto build_glyph_run = [&](ShapedGlyphs const& shape) -> NonnullRefPtr { + if (out_trailing_whitespace) + *out_trailing_whitespace = shape.trailing_whitespace; Vector glyphs = shape.glyphs; if (!baseline_start.is_zero()) { for (auto& glyph : glyphs) @@ -287,32 +322,34 @@ NonnullRefPtr shape_text(FloatPoint baseline_start, float letter_spaci }; // FIXME: The cache currently grows unbounded. We should have some limit and LRU mechanism. - if (string.length_in_code_units() == 1 && letter_spacing == 0.f && text_type == GlyphRun::TextType::Common) { + if (string.length_in_code_units() == 1 && letter_spacing == 0.f && word_spacing == 0.f && text_type == GlyphRun::TextType::Common) { auto code_unit = string.code_unit_at(0); if (code_unit < 128) { auto& cache_slot = shaping_cache.single_ascii_character_map[code_unit]; if (!cache_slot) - cache_slot = build_origin_relative_shape(string, font, text_type, letter_spacing); + cache_slot = build_origin_relative_shape(string, font, text_type, letter_spacing, word_spacing); return build_glyph_run(*cache_slot); } } auto text_type_bits = static_cast(to_underlying(text_type)); auto letter_spacing_bit_pattern = bit_cast(letter_spacing); - auto key_hash = pair_int_hash(string.hash(), pair_int_hash(text_type_bits, letter_spacing_bit_pattern)); + auto word_spacing_bit_pattern = bit_cast(word_spacing); + auto key_hash = pair_int_hash(string.hash(), pair_int_hash(text_type_bits, pair_int_hash(letter_spacing_bit_pattern, word_spacing_bit_pattern))); if (auto it = shaping_cache.map.find(key_hash, [&](auto const& candidate) { return candidate.key.text_type == text_type_bits && candidate.key.letter_spacing_bit_pattern == letter_spacing_bit_pattern + && candidate.key.word_spacing_bit_pattern == word_spacing_bit_pattern && candidate.key.text == string; }); it != shaping_cache.map.end()) { return build_glyph_run(*it->value); } - auto shape = build_origin_relative_shape(string, font, text_type, letter_spacing); + auto shape = build_origin_relative_shape(string, font, text_type, letter_spacing, word_spacing); auto run = build_glyph_run(*shape); - shaping_cache.map.set({ Utf16String::from_utf16(string), text_type_bits, letter_spacing_bit_pattern }, move(shape)); + shaping_cache.map.set({ Utf16String::from_utf16(string), text_type_bits, letter_spacing_bit_pattern, word_spacing_bit_pattern }, move(shape)); return run; } @@ -351,7 +388,7 @@ static_assert(offsetof(Gfx::FFI::DrawGlyph, glyph_id) == offsetof(Gfx::DrawGlyph static_assert(offsetof(Gfx::FFI::DrawGlyph, should_paint) == offsetof(Gfx::DrawGlyph, should_paint)); extern "C" { -Gfx::FFI::ShapedRunView ladybird_gfx_shape_text(void const*, u16 const*, size_t, Gfx::FFI::TextType, float, float); +Gfx::FFI::ShapedRunView ladybird_gfx_shape_text(void const*, u16 const*, size_t, Gfx::FFI::TextType, float, float, float); void ladybird_gfx_glyph_run_unref(void*); } @@ -361,24 +398,30 @@ extern "C" Gfx::FFI::ShapedRunView ladybird_gfx_shape_text( size_t length_in_code_units, Gfx::FFI::TextType text_type, float baseline_start_x, - float letter_spacing) + float letter_spacing, + float word_spacing) { VERIFY(font); VERIFY(text_utf16 || length_in_code_units == 0); auto text = length_in_code_units == 0 ? Utf16View {} : Utf16View { reinterpret_cast(text_utf16), length_in_code_units }; + Gfx::TrailingWhitespace trailing_whitespace; auto run = Gfx::shape_text( { baseline_start_x, 0 }, letter_spacing, + word_spacing, text, *static_cast(font), - static_cast(text_type)); + static_cast(text_type), + &trailing_whitespace); auto* retained = &run.leak_ref(); return { .glyphs = reinterpret_cast(retained->glyphs().data()), .glyph_count = retained->glyphs().size(), .width = retained->width(), + .trailing_whitespace_length_in_code_units = trailing_whitespace.length_in_code_units, + .trailing_whitespace_advance = trailing_whitespace.advance, .retained = retained, }; } diff --git a/Libraries/LibGfx/TextLayout.h b/Libraries/LibGfx/TextLayout.h index 3f17185123a9f..d368ed54685fb 100644 --- a/Libraries/LibGfx/TextLayout.h +++ b/Libraries/LibGfx/TextLayout.h @@ -31,9 +31,15 @@ struct DrawGlyph { bool should_paint { true }; }; +struct TrailingWhitespace { + size_t length_in_code_units { 0 }; + float advance { 0 }; +}; + struct ShapedGlyphs { Vector glyphs; float width { 0 }; + TrailingWhitespace trailing_whitespace; }; class GlyphRun : public AtomicRefCounted { @@ -74,7 +80,7 @@ class GlyphRun : public AtomicRefCounted { mutable OwnPtr m_cached_text_blob; }; -NonnullRefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf16View const&, Gfx::Font const& font, GlyphRun::TextType); +NonnullRefPtr shape_text(FloatPoint baseline_start, float letter_spacing, float word_spacing, Utf16View const&, Gfx::Font const& font, GlyphRun::TextType, TrailingWhitespace* = nullptr); Vector> shape_text(FloatPoint baseline_start, Utf16View const&, FontCascadeList const&, float letter_spacing = 0.f); float measure_text_width(Utf16View const&, Font const& font, float letter_spacing = 0.f); diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index aa61534904819..cb38e84fcfd9b 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -585,7 +585,7 @@ void DisplayListRecorder::draw_text(Gfx::IntRect const& rect, Utf16String const& if (rect.is_empty() || color.alpha() == 0) return; - auto glyph_run = Gfx::shape_text({}, 0, raw_text.utf16_view(), font, Gfx::GlyphRun::TextType::Ltr); + auto glyph_run = Gfx::shape_text({}, 0, 0, raw_text.utf16_view(), font, Gfx::GlyphRun::TextType::Ltr); float baseline_x = 0; if (alignment == Gfx::TextAlignment::CenterLeft) { baseline_x = rect.x(); diff --git a/Libraries/LibWeb/Rust/src/layout/font.rs b/Libraries/LibWeb/Rust/src/layout/font.rs index cb6e9531c1c32..8be1708edc39c 100644 --- a/Libraries/LibWeb/Rust/src/layout/font.rs +++ b/Libraries/LibWeb/Rust/src/layout/font.rs @@ -18,20 +18,28 @@ pub(crate) fn font_glyph_id(font: *const c_void, code_point: u32) -> u32 { unsafe { libgfx_rust::font::FontRef::from_raw(font) }.glyph_id_for_code_point(code_point) } +pub(crate) struct ShapedRun { + pub(crate) glyphs: Vec, + pub(crate) width: f32, + pub(crate) trailing_whitespace_length_in_code_units: usize, + pub(crate) trailing_whitespace_advance: f32, +} + pub(crate) fn shape_text_with_font( font: *const c_void, text: &[u16], text_type: u8, baseline_start_x: f32, letter_spacing: f32, -) -> (Vec, f32) { + word_spacing: f32, +) -> ShapedRun { // SAFETY: Font pointers in layout snapshots are borrowed from the host for // the synchronous layout pass. let font = unsafe { libgfx_rust::font::FontRef::from_raw(font) }; let text_type = libgfx_rust::text_layout::TextType::try_from(text_type).expect("invalid Gfx::GlyphRun::TextType"); let shaped = - libgfx_rust::text_layout::shape_text(font, text, text_type, baseline_start_x, letter_spacing); + libgfx_rust::text_layout::shape_text(font, text, text_type, baseline_start_x, letter_spacing, word_spacing); let glyphs = shaped .glyphs() .iter() @@ -44,5 +52,10 @@ pub(crate) fn shape_text_with_font( should_paint: glyph.should_paint, }) .collect(); - (glyphs, shaped.width()) + ShapedRun { + glyphs, + width: shaped.width(), + trailing_whitespace_length_in_code_units: shaped.trailing_whitespace_length_in_code_units(), + trailing_whitespace_advance: shaped.trailing_whitespace_advance(), + } } diff --git a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs index 0010b333bcd70..c8be597a2ed9b 100644 --- a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs @@ -1102,6 +1102,7 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { item.inline_size, line_height, item.glyphs.take().unwrap(), + item.trailing_whitespace, ); } } @@ -1109,7 +1110,7 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { let line_count = self.line_data().line_boxes.len(); for line_index in 0..line_count { - self.line_data_mut().line_boxes[line_index].trim_trailing_whitespace(self); + self.line_data_mut().line_boxes[line_index].trim_trailing_whitespace(); } if self.text_overflow_applies() { apply(self.line_data_mut().line_boxes.as_mut_slice(), self); @@ -1278,15 +1279,9 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { } } -impl LineBoxTextProvider for InlineFormattingContext<'_, '_> { - fn font_glyph_width(&self, font: *const c_void, code_point: u32) -> f32 { - font_glyph_width(font, code_point) - } -} - impl EllipsisFontProvider for InlineFormattingContext<'_, '_> { fn font_glyph_width(&self, font: *const c_void, code_point: u32) -> f32 { - ::font_glyph_width(self, font, code_point) + font_glyph_width(font, code_point) } fn font_glyph_id(&self, font: *const c_void, code_point: u32) -> u32 { diff --git a/Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs b/Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs index 9510277e1da39..a89df43202dec 100644 --- a/Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs +++ b/Libraries/LibWeb/Rust/src/layout/inline_level_iterator.rs @@ -32,6 +32,7 @@ pub(crate) struct Item { pub(crate) can_break_before: bool, pub(crate) preceded_by_unattached_inline_start_edges: bool, pub(crate) content_baselines: DerivedBaselines, + pub(crate) trailing_whitespace: TrailingWhitespace, } impl Item { @@ -53,6 +54,7 @@ impl Item { can_break_before: false, preceded_by_unattached_inline_start_edges: false, content_baselines: DerivedBaselines::default(), + trailing_whitespace: TrailingWhitespace::default(), } } @@ -353,14 +355,20 @@ impl<'iterator, 'context, 'pass> InlineLevelIteratorGenerator<'iterator, 'contex text_type: u8, baseline_start_x: f32, letter_spacing: f32, - ) -> GlyphData { - let (glyphs, width) = shape_text_with_font(font, text, text_type, baseline_start_x, letter_spacing); - GlyphData { - glyphs, + word_spacing: f32, + ) -> (GlyphData, TrailingWhitespace) { + let shaped = shape_text_with_font(font, text, text_type, baseline_start_x, letter_spacing, word_spacing); + let glyph_data = GlyphData { + glyphs: shaped.glyphs, font, text_type, - width, - } + width: shaped.width, + }; + let trailing_whitespace = TrailingWhitespace { + length_in_code_units: shaped.trailing_whitespace_length_in_code_units, + inline_size: CssPixels::nearest_value_for_f32(shaped.trailing_whitespace_advance), + }; + (glyph_data, trailing_whitespace) } fn add_extra_box_model_metrics_to_item( @@ -443,13 +451,13 @@ impl<'iterator, 'context, 'pass> InlineLevelIteratorGenerator<'iterator, 'contex let full_text = text_context.text; let mut shaped_start = chunk.start; let mut shaped_length = chunk.length; + let word_spacing = style.word_spacing(); if chunk.has_breaking_tab { let tab_inline_size = if style.tab_size_is_number() { let space = font_glyph_width(chunk.font, b' ' as u32); CssPixels::nearest_value_for( style.tab_size_number() - * (space + style.word_spacing().to_double() as f32 + style.letter_spacing().to_double() as f32) - as f64, + * (space + word_spacing.to_double() as f32 + style.letter_spacing().to_double() as f32) as f64, ) } else { style.tab_size() @@ -474,12 +482,13 @@ impl<'iterator, 'context, 'pass> InlineLevelIteratorGenerator<'iterator, 'contex inline_offset = tab_stop_distance.to_double() as f32; } let shaped_text = &full_text[shaped_start..shaped_start + shaped_length]; - let glyphs = self.shape_text( + let (glyphs, shaped_trailing_whitespace) = self.shape_text( shaped_text, chunk.font, text_type, inline_offset, style.letter_spacing().to_double() as f32, + word_spacing.to_double() as f32, ); let chunk_inline_size = CssPixels::nearest_value_for_f32(glyphs.width + inline_offset); let generated_empty = synthesize_zero_length_chunk @@ -489,6 +498,14 @@ impl<'iterator, 'context, 'pass> InlineLevelIteratorGenerator<'iterator, 'contex item.offset_in_node = chunk.start; item.length_in_node = chunk.length; item.inline_size = chunk_inline_size; + item.trailing_whitespace = if chunk.is_all_whitespace { + TrailingWhitespace { + length_in_code_units: chunk.length, + inline_size: chunk_inline_size, + } + } else { + shaped_trailing_whitespace + }; item.is_collapsible_whitespace = text_context.should_collapse_whitespace && chunk.is_all_whitespace && !generated_empty; item.can_break_before = self.previous_chunk_can_break_after; diff --git a/Libraries/LibWeb/Rust/src/layout/line_box.rs b/Libraries/LibWeb/Rust/src/layout/line_box.rs index d2400f5d79576..8bb25ad0efc11 100644 --- a/Libraries/LibWeb/Rust/src/layout/line_box.rs +++ b/Libraries/LibWeb/Rust/src/layout/line_box.rs @@ -4,10 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -pub(crate) trait LineBoxTextProvider { - fn font_glyph_width(&self, font: *const c_void, code_point: u32) -> f32; -} - #[derive(Clone, Copy, Debug)] pub(crate) struct StaticPositionMarker { pub(crate) box_: Node, @@ -88,6 +84,7 @@ impl LineBoxData { glyphs: Option, facts: FragmentBuildFacts, text_align_is_justify: bool, + trailing_whitespace: TrailingWhitespace, ) { let can_merge = glyphs.as_ref().is_some_and(|glyphs| { !text_align_is_justify @@ -104,9 +101,15 @@ impl LineBoxData { let last = self.fragments.last_mut().unwrap(); last.length_in_code_units += length; last.append_glyph_run(glyphs.unwrap(), content_inline_size); + if trailing_whitespace.length_in_code_units == length { + last.trailing_whitespace.length_in_code_units += trailing_whitespace.length_in_code_units; + last.trailing_whitespace.inline_size += trailing_whitespace.inline_size; + } else { + last.trailing_whitespace = trailing_whitespace; + } } else { let inline_offset = leading_margin + leading_size + self.inline_length; - self.fragments.push(LineBoxFragmentData::new( + let mut fragment = LineBoxFragmentData::new( layout_node, start, length, @@ -119,7 +122,9 @@ impl LineBoxData { self.writing_mode, glyphs, facts, - )); + ); + fragment.trailing_whitespace = trailing_whitespace; + self.fragments.push(fragment); } self.inline_length += leading_margin + leading_size + content_inline_size + trailing_size + trailing_margin; self.block_length = self @@ -145,11 +150,7 @@ impl LineBoxData { } } - fn calculate_or_trim_trailing_whitespace( - &mut self, - provider: &impl LineBoxTextProvider, - should_remove: bool, - ) -> CssPixels { + fn calculate_or_trim_trailing_whitespace(&mut self, should_remove: bool) -> CssPixels { let mut whitespace_inline_size = CssPixels::default(); let mut trailing_whitespace_inline_size = CssPixels::default(); let mut fragment_index = self.fragments.len(); @@ -187,31 +188,17 @@ impl LineBoxData { return whitespace_inline_size; } - // Trim trailing whitespace characters from the last fragment. - let mut last_text_length = self.fragments[last_fragment_index].text().len(); - while last_text_length != 0 { - last_text_length -= 1; - let last_character = self.fragments[last_fragment_index].text()[last_text_length]; - if !is_ascii_space(last_character) { - break; - } - let font = self.fragments[last_fragment_index] - .glyphs - .as_ref() - .map(|glyphs| glyphs.font) - .unwrap_or(self.fragments[last_fragment_index].first_available_font); - let character_inline_size = - CssPixels::nearest_value_for_f32(provider.font_glyph_width(font, last_character as u32)) - + self.fragments[last_fragment_index].letter_spacing; - whitespace_inline_size += character_inline_size; - trailing_whitespace_inline_size += character_inline_size; - if should_remove { - let fragment = &mut self.fragments[last_fragment_index]; - fragment.length_in_code_units -= 1; - fragment.inline_length -= character_inline_size; - self.inline_length -= character_inline_size; - self.clamp_static_position_markers_to_inline_length(); - } + // Trim the last fragment's trailing whitespace by the advance recorded when it was shaped. + let fragment_trailing_whitespace = last_fragment.trailing_whitespace; + whitespace_inline_size += fragment_trailing_whitespace.inline_size; + trailing_whitespace_inline_size += fragment_trailing_whitespace.inline_size; + if should_remove { + let fragment = &mut self.fragments[last_fragment_index]; + fragment.length_in_code_units -= fragment_trailing_whitespace.length_in_code_units; + fragment.inline_length -= fragment_trailing_whitespace.inline_size; + fragment.trailing_whitespace = TrailingWhitespace::default(); + self.inline_length -= fragment_trailing_whitespace.inline_size; + self.clamp_static_position_markers_to_inline_length(); } if should_remove @@ -224,12 +211,12 @@ impl LineBoxData { whitespace_inline_size } - pub(crate) fn trailing_whitespace_inline_size(&mut self, provider: &impl LineBoxTextProvider) -> CssPixels { - self.calculate_or_trim_trailing_whitespace(provider, false) + pub(crate) fn trailing_whitespace_inline_size(&mut self) -> CssPixels { + self.calculate_or_trim_trailing_whitespace(false) } - pub(crate) fn trim_trailing_whitespace(&mut self, provider: &impl LineBoxTextProvider) { - self.calculate_or_trim_trailing_whitespace(provider, true); + pub(crate) fn trim_trailing_whitespace(&mut self) { + self.calculate_or_trim_trailing_whitespace(true); } pub(crate) fn is_empty_or_ends_in_whitespace(&self) -> bool { diff --git a/Libraries/LibWeb/Rust/src/layout/line_box_fragment.rs b/Libraries/LibWeb/Rust/src/layout/line_box_fragment.rs index 49fe70aea0630..44b70447b29cf 100644 --- a/Libraries/LibWeb/Rust/src/layout/line_box_fragment.rs +++ b/Libraries/LibWeb/Rust/src/layout/line_box_fragment.rs @@ -23,6 +23,14 @@ pub(crate) struct GlyphData { pub(crate) width: f32, } +// The advance of a run's trailing whitespace, recorded at shaping time so that trimming it subtracts exactly what +// shaping added. +#[derive(Clone, Copy, Debug, Default)] +pub(crate) struct TrailingWhitespace { + pub(crate) length_in_code_units: usize, + pub(crate) inline_size: CssPixels, +} + #[derive(Clone, Debug)] pub(crate) struct LineBoxFragmentData { pub(crate) layout_node: Node, @@ -45,8 +53,7 @@ pub(crate) struct LineBoxFragmentData { pub(crate) is_fully_truncated: bool, pub(crate) is_atomic_inline: bool, pub(crate) white_space_collapse: u8, - pub(crate) letter_spacing: CssPixels, - pub(crate) first_available_font: *const c_void, + pub(crate) trailing_whitespace: TrailingWhitespace, pub(crate) text_utf16: *const u16, pub(crate) text_length_in_code_units: usize, pub(crate) content_baselines: Option, @@ -57,8 +64,6 @@ pub(crate) struct FragmentBuildFacts { pub(crate) style_source: Node, pub(crate) is_atomic_inline: bool, pub(crate) white_space_collapse: u8, - pub(crate) letter_spacing: CssPixels, - pub(crate) first_available_font: *const c_void, pub(crate) text_utf16: *const u16, pub(crate) text_length_in_code_units: usize, } @@ -100,8 +105,7 @@ impl LineBoxFragmentData { is_fully_truncated: false, is_atomic_inline: facts.is_atomic_inline, white_space_collapse: facts.white_space_collapse, - letter_spacing: facts.letter_spacing, - first_available_font: facts.first_available_font, + trailing_whitespace: TrailingWhitespace::default(), text_utf16: facts.text_utf16, text_length_in_code_units: facts.text_length_in_code_units, content_baselines: None, diff --git a/Libraries/LibWeb/Rust/src/layout/line_builder.rs b/Libraries/LibWeb/Rust/src/layout/line_builder.rs index 25c87f0674571..0d0757a518274 100644 --- a/Libraries/LibWeb/Rust/src/layout/line_builder.rs +++ b/Libraries/LibWeb/Rust/src/layout/line_builder.rs @@ -128,8 +128,6 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { style_source, is_atomic_inline: facts.is_atomic_inline(), white_space_collapse: style.white_space_collapse(), - letter_spacing: style.letter_spacing(), - first_available_font: style.first_available_font(), text_utf16, text_length_in_code_units: text_length, } @@ -254,6 +252,7 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { None, fragment_facts, text_align_is_justify, + TrailingWhitespace::default(), ); self.line_mut(line_index).fragments[fragment_index].content_baselines = Some(content_baselines); self.max_block_size_on_current_line = self.max_block_size_on_current_line.max(margin_block_size); @@ -281,6 +280,7 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { content_inline_size: CssPixels, content_block_size: CssPixels, glyphs: GlyphData, + trailing_whitespace: TrailingWhitespace, ) { self.prepare_to_append_inline_content(); let line_index = self.ensure_last_line_index(); @@ -301,6 +301,7 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { Some(glyphs), facts, text_align_is_justify, + trailing_whitespace, ); let line_block_length = self.line(line_index).block_length; self.max_block_size_on_current_line = self.max_block_size_on_current_line.max(line_block_length); @@ -433,8 +434,7 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { let mut candidate = self.current_block_offset; let line_index = self.ensure_last_line_index(); let mut line = self.line_mut(line_index); - let current_line_inline_size = - line.physical_horizontal_extent() - line.trailing_whitespace_inline_size(self.context()); + let current_line_inline_size = line.physical_horizontal_extent() - line.trailing_whitespace_inline_size(); let line_is_empty_or_whitespace = line.is_empty_or_ends_in_whitespace(); drop(line); let mut needed = current_line_inline_size; diff --git a/Meta/import-wpt-test.py b/Meta/import-wpt-test.py index 8500af7e2f280..2ccd4d3b32d7a 100755 --- a/Meta/import-wpt-test.py +++ b/Meta/import-wpt-test.py @@ -202,9 +202,7 @@ def is_crash_test(url_string): return True file_name = path_segments[-1] file_name_parts = file_name.split(".") - if len(file_name_parts) > 1 and any([part.endswith("-crash") for part in file_name_parts[:-1]]): - return True - return False + return len(file_name_parts) > 1 and any(part.endswith("-crash") for part in file_name_parts[:-1]) def modify_sources(files, resources: list[ResourceAndType]) -> None: @@ -229,7 +227,7 @@ def modify_sources(files, resources: list[ResourceAndType]) -> None: page_source = f.read() # Iterate all scripts and overwrite the src attribute - for resource in map(lambda r: r.resource, resources): + for resource in dict.fromkeys(r.resource for r in resources): if resource.startswith("/"): new_src_value = parent_folder_path + resource[1::] page_source = page_source.replace(resource.encode(), new_src_value.encode()) @@ -366,7 +364,7 @@ def main(): # replacing undecodable bytes with U+FFFD is safe and avoids a UnicodeDecodeError. page = response.read().decode("utf-8", errors="replace") - global test_type, reference_paths, raw_reference_paths + global test_type, raw_reference_paths if is_crash_test(url_to_import): test_type = TestType.CRASH else: @@ -408,7 +406,7 @@ def main(): input_parser = LinkedResourceFinder() input_parser.feed(page) - additional_resources = list(map(lambda s: ResourceAndType(s, ResourceType.INPUT), input_parser.resources)) + additional_resources = [ResourceAndType(s, ResourceType.INPUT) for s in input_parser.resources] expected_parser = LinkedResourceFinder() for path in main_paths[1:]: @@ -418,9 +416,7 @@ def main(): # replacing undecodable bytes with U+FFFD is safe and avoids a UnicodeDecodeError. page = response.read().decode("utf-8", errors="replace") expected_parser.feed(page) - additional_resources.extend( - list(map(lambda s: ResourceAndType(s, ResourceType.EXPECTED), expected_parser.resources)) - ) + additional_resources.extend(ResourceAndType(s, ResourceType.EXPECTED) for s in expected_parser.resources) modify_sources(files_to_modify, additional_resources) script_paths = map_to_path(additional_resources, wpt_base_url, True, resource_path) diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-001-ref.xht b/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-001-ref.xht new file mode 100644 index 0000000000000..7ffb2521286a3 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-001-ref.xht @@ -0,0 +1,31 @@ + + + + test reference + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
XXXXX           XXXXXX
+
+
+ + diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-007-ref.xht b/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-007-ref.xht new file mode 100644 index 0000000000000..e09e760ae6d4b --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-007-ref.xht @@ -0,0 +1,41 @@ + + + + test reference + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
XXXXX           XXXXXX
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-012-ref.xht b/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-012-ref.xht new file mode 100644 index 0000000000000..3028fe43af86c --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/CSS2/text/word-spacing-applies-to-012-ref.xht @@ -0,0 +1,46 @@ + + + + test reference + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
+
XXXXX XXXXX
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/reference/ref-filled-green-100px-square.xht b/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/reference/ref-filled-green-100px-square.xht new file mode 100644 index 0000000000000..05a13794482a9 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/reference/ref-filled-green-100px-square.xht @@ -0,0 +1,19 @@ + + + + CSS Reftest Reference + + + + +

Test passes if there is a filled green square and no red.

+
+ + diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-001-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-001-ref.html new file mode 100644 index 0000000000000..df616ef43125d --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-001-ref.html @@ -0,0 +1,26 @@ + +CSS Text Test: Word Spacing + + + + + +

Test passes if the space between the words starts at zero and increases by + one on each subsequent line.

+
ABcDefGhij
+
A Bc Def Ghij
+
A  Bc  Def  Ghij
+
A   Bc   Def   Ghij
+
A    Bc    Def    Ghij
+
A     Bc     Def     Ghij
+
A     Bc     Def     Ghij     
+ diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-002-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-002-ref.html new file mode 100644 index 0000000000000..b14ee66e29992 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-002-ref.html @@ -0,0 +1,19 @@ + +CSS Text Reference: Word Spacing + + + + +

Test passes if the space between the words starts at zero and increases by + an even amount on each subsequent line.

+
Bc Def Ghij
+
A Bc Def Ghij
+
A  Bc Def Ghij
+
A  Bc Def Ghij
+
A  Bc Def Ghij
+ diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-003-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-003-ref.html new file mode 100644 index 0000000000000..1b65bbcd66f1e --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-text/word-spacing/word-spacing-003-ref.html @@ -0,0 +1,19 @@ + +CSS Text Reference: Word Spacing + + + + + +

Test passes if there is the one rectangle

+
a
+
a
+ diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-001.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-001.xht new file mode 100644 index 0000000000000..9258e20e3a30c --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-001.xht @@ -0,0 +1,38 @@ + + + + CSS Test: Word-spacing application to 'display: inline' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
XXXXX XXXXXX
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-002.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-002.xht new file mode 100644 index 0000000000000..1161b15af0655 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-002.xht @@ -0,0 +1,38 @@ + + + + CSS Test: Word-spacing application to 'display: block' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+ XXXXX XXXXXX +
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-005.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-005.xht new file mode 100644 index 0000000000000..0d4cf581b889f --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-005.xht @@ -0,0 +1,38 @@ + + + + CSS Test: Word-spacing application to 'display: inline-block' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+ XXXXX XXXXXX +
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-006.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-006.xht new file mode 100644 index 0000000000000..132f075d6554b --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-006.xht @@ -0,0 +1,48 @@ + + + + CSS Test: Word-spacing application to 'display: table' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
XXXXX XXXXXX
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-007.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-007.xht new file mode 100644 index 0000000000000..679172fcacf1a --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-007.xht @@ -0,0 +1,48 @@ + + + + CSS Test: Word-spacing application to 'display: inline-table' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
XXXXX XXXXXX
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-008.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-008.xht new file mode 100644 index 0000000000000..cb0e55413bcf1 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-008.xht @@ -0,0 +1,54 @@ + + + + CSS Test: Word-spacing application to 'display: table-row-group' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
+
XXXXX XXXXXX
+
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-009.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-009.xht new file mode 100644 index 0000000000000..92a23d79abbd1 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-009.xht @@ -0,0 +1,54 @@ + + + + CSS Test: Word-spacing application to 'display: table-header-group' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
+
XXXXX XXXXXX
+
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-010.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-010.xht new file mode 100644 index 0000000000000..c22ed8dfc1210 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-010.xht @@ -0,0 +1,54 @@ + + + + CSS Test: Word-spacing application to 'display: table-footer-group' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
+
XXXXX XXXXXX
+
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-011.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-011.xht new file mode 100644 index 0000000000000..8ce10ee7a0986 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-011.xht @@ -0,0 +1,48 @@ + + + + CSS Test: Word-spacing application to 'display: table-row' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
XXXXX XXXXXX
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-012.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-012.xht new file mode 100644 index 0000000000000..2edb97228f28d --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-012.xht @@ -0,0 +1,53 @@ + + + + CSS Test: Word-spacing application to 'display: table-column-group' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
+
XXXXX XXXXX
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-013.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-013.xht new file mode 100644 index 0000000000000..13790436549e0 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-013.xht @@ -0,0 +1,53 @@ + + + + CSS Test: Word-spacing application to 'display: table-column' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
+
XXXXX XXXXX
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-014.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-014.xht new file mode 100644 index 0000000000000..73d19c33b52c0 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-014.xht @@ -0,0 +1,48 @@ + + + + CSS Test: Word-spacing application to 'display: table-cell' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
+
XXXXX XXXXXX
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-015.xht b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-015.xht new file mode 100644 index 0000000000000..2185a64426670 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/CSS2/text/word-spacing-applies-to-015.xht @@ -0,0 +1,54 @@ + + + + CSS Test: Word-spacing application to 'display: table-caption' element + + + + + + + + + + + +

Test passes if there are two black boxes below and the last black box ends when the blue box ends.

+
+
XXXXX XXXXXX
+
+
+
+
+
+ + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-001.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-001.html new file mode 100644 index 0000000000000..346bec0c39692 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-001.html @@ -0,0 +1,35 @@ + +CSS Text Test: Word Spacing + + + + + + + + +

Test passes if the space between the words starts at zero and increases by + one on each subsequent line.

+
A Bc Def Ghij
+
A Bc Def Ghij
+
A Bc Def Ghij
+
A     Bc     Def     Ghij
+
A Bc Def Ghij
+
A    Bc    Def    Ghij
+
A Bc Def Ghij
+ diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-002.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-002.html new file mode 100644 index 0000000000000..a45e7fd4035e3 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-002.html @@ -0,0 +1,28 @@ + +CSS Text Test: Word Spacing + + + + + + + + + +

Test passes if the space between the words starts at zero and increases by + an even amount on each subsequent line.

+
A Bc Def Ghij
+
A Bc Def Ghij
+
A Bc Def Ghij
+
A Bc Def Ghij
+
A  Bc Def  Ghij
+ diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-003.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-003.html new file mode 100644 index 0000000000000..28e5aaf996c1c --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-003.html @@ -0,0 +1,23 @@ + +CSS Text Test: Word Spacing + + + + + + + + +

Test passes if there is the one rectangle

+
a
+
a
+ diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-negative-value-001.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-negative-value-001.html new file mode 100644 index 0000000000000..7eb1fafc25a1f --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-text/word-spacing/word-spacing-negative-value-001.html @@ -0,0 +1,31 @@ + + + + + CSS Text Test: 'word-spacing' value may be negative (basic) + + + + + + + + + + +

Test passes if there is a filled green square and no red. + +

T E
+ +
S T