Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Libraries/LibGfx/Font/Font.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -154,7 +155,7 @@ template<>
struct Traits<Gfx::ShapingCacheKey> : public DefaultTraits<Gfx::ShapingCacheKey> {
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)));
}
};

Expand Down
26 changes: 25 additions & 1 deletion Libraries/LibGfx/Rust/src/text_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand All @@ -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);
Expand All @@ -82,6 +85,8 @@ impl Drop for RetainedGlyphRun {
pub struct ShapedText {
glyphs: Vec<DrawGlyph>,
width: f32,
trailing_whitespace_length_in_code_units: usize,
trailing_whitespace_advance: f32,
}

impl ShapedText {
Expand All @@ -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(
Expand All @@ -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.
Expand All @@ -113,6 +129,7 @@ pub fn shape_text(
text_type,
baseline_start_x,
letter_spacing,
word_spacing,
)
};
let retained = RetainedGlyphRun {
Expand All @@ -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,
}
}
71 changes: 57 additions & 14 deletions Libraries/LibGfx/TextLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Vector<NonnullRefPtr<GlyphRun>> 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);
};
Expand Down Expand Up @@ -207,7 +207,27 @@ static hb_buffer_t* setup_text_shaping(Utf16View const& string, Font const& font
return buffer;
}

static NonnullOwnPtr<ShapedGlyphs> 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<ShapedGlyphs> 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);
Expand All @@ -220,6 +240,11 @@ static NonnullOwnPtr<ShapedGlyphs> 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.
Expand Down Expand Up @@ -250,10 +275,15 @@ static NonnullOwnPtr<ShapedGlyphs> 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,
});
Expand All @@ -265,19 +295,24 @@ static NonnullOwnPtr<ShapedGlyphs> 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<ShapedGlyphs>(move(glyphs), point.x());
return make<ShapedGlyphs>(move(glyphs), point.x(), trailing_whitespace);
}

NonnullRefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf16View const& string, Font const& font, GlyphRun::TextType text_type)
NonnullRefPtr<GlyphRun> 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<GlyphRun> {
if (out_trailing_whitespace)
*out_trailing_whitespace = shape.trailing_whitespace;
Vector<DrawGlyph> glyphs = shape.glyphs;
if (!baseline_start.is_zero()) {
for (auto& glyph : glyphs)
Expand All @@ -287,32 +322,34 @@ NonnullRefPtr<GlyphRun> 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<u8>(to_underlying(text_type));
auto letter_spacing_bit_pattern = bit_cast<u32>(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<u32>(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;
}

Expand Down Expand Up @@ -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*);
}

Expand All @@ -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<char16_t const*>(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<Gfx::Font const*>(font),
static_cast<Gfx::GlyphRun::TextType>(text_type));
static_cast<Gfx::GlyphRun::TextType>(text_type),
&trailing_whitespace);
auto* retained = &run.leak_ref();
return {
.glyphs = reinterpret_cast<Gfx::FFI::DrawGlyph const*>(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,
};
}
Expand Down
8 changes: 7 additions & 1 deletion Libraries/LibGfx/TextLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<DrawGlyph> glyphs;
float width { 0 };
TrailingWhitespace trailing_whitespace;
};

class GlyphRun : public AtomicRefCounted<GlyphRun> {
Expand Down Expand Up @@ -74,7 +80,7 @@ class GlyphRun : public AtomicRefCounted<GlyphRun> {
mutable OwnPtr<CachedTextBlob> m_cached_text_blob;
};

NonnullRefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf16View const&, Gfx::Font const& font, GlyphRun::TextType);
NonnullRefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, float word_spacing, Utf16View const&, Gfx::Font const& font, GlyphRun::TextType, TrailingWhitespace* = nullptr);
Vector<NonnullRefPtr<GlyphRun>> 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);

Expand Down
2 changes: 1 addition & 1 deletion Libraries/LibWeb/Painting/DisplayListRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
19 changes: 16 additions & 3 deletions Libraries/LibWeb/Rust/src/layout/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FfiDrawGlyph>,
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<FfiDrawGlyph>, 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()
Expand All @@ -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(),
}
}
11 changes: 3 additions & 8 deletions Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,14 +1102,15 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> {
item.inline_size,
line_height,
item.glyphs.take().unwrap(),
item.trailing_whitespace,
);
}
}
}

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);
Expand Down Expand Up @@ -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 {
<Self as LineBoxTextProvider>::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 {
Expand Down
Loading