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
14 changes: 14 additions & 0 deletions Libraries/LibGfx/Font/Font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
extern "C" {
float ladybird_gfx_font_glyph_width(void const*, u32);
u32 ladybird_gfx_font_glyph_id(void const*, u32);
bool ladybird_gfx_font_contains_glyph(void const*, u32);
bool ladybird_gfx_font_is_emoji_font(void const*);
}

namespace Gfx {
Expand Down Expand Up @@ -260,3 +262,15 @@ extern "C" u32 ladybird_gfx_font_glyph_id(void const* font, u32 code_point)
VERIFY(font);
return static_cast<Gfx::Font const*>(font)->glyph_id_for_code_point(code_point);
}

extern "C" bool ladybird_gfx_font_contains_glyph(void const* font, u32 code_point)
{
VERIFY(font);
return static_cast<Gfx::Font const*>(font)->contains_glyph(code_point);
}

extern "C" bool ladybird_gfx_font_is_emoji_font(void const* font)
{
VERIFY(font);
return static_cast<Gfx::Font const*>(font)->is_emoji_font();
}
49 changes: 49 additions & 0 deletions Libraries/LibGfx/FontCascadeList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,52 @@ bool FontCascadeList::equals(FontCascadeList const& other) const
}

}

extern "C" {
void const* ladybird_gfx_font_cascade_list_font_for_code_point(void const*, u32, bool, bool, bool);
void const* ladybird_gfx_font_cascade_list_first(void const*);
void ladybird_gfx_font_cascade_list_ref(void const*);
void ladybird_gfx_font_cascade_list_unref(void const*);
u8 ladybird_gfx_emoji_presentation_for_code_point(u32, u32, bool);
}

extern "C" void const* ladybird_gfx_font_cascade_list_font_for_code_point(void const* list, u32 code_point, bool trigger_pending_loads, bool emoji_presentation, bool forced_presentation)
{
VERIFY(list);
auto const& cascade_list = *static_cast<Gfx::FontCascadeList const*>(list);
return &cascade_list.font_for_code_point(
code_point,
trigger_pending_loads ? Gfx::FontCascadeList::TriggerPendingLoads::Yes : Gfx::FontCascadeList::TriggerPendingLoads::No,
{ emoji_presentation ? Gfx::EmojiPresentation::Emoji : Gfx::EmojiPresentation::Text,
forced_presentation ? Gfx::ForcedPresentation::Yes : Gfx::ForcedPresentation::No });
}

extern "C" void const* ladybird_gfx_font_cascade_list_first(void const* list)
{
VERIFY(list);
return &static_cast<Gfx::FontCascadeList const*>(list)->first();
}

extern "C" void ladybird_gfx_font_cascade_list_ref(void const* list)
{
VERIFY(list);
static_cast<Gfx::FontCascadeList const*>(list)->ref();
}

extern "C" void ladybird_gfx_font_cascade_list_unref(void const* list)
{
VERIFY(list);
static_cast<Gfx::FontCascadeList const*>(list)->unref();
}

extern "C" u8 ladybird_gfx_emoji_presentation_for_code_point(u32 code_point, u32 next_code_point, bool has_next_code_point)
{
auto result = Gfx::emoji_presentation_for_code_point(
code_point, has_next_code_point ? Optional<u32> { next_code_point } : Optional<u32> {});
u8 encoded = 0;
if (result.presentation == Gfx::EmojiPresentation::Emoji)
encoded |= 1;
if (result.forced == Gfx::ForcedPresentation::Yes)
encoded |= 2;
return encoded;
}
142 changes: 142 additions & 0 deletions Libraries/LibGfx/Rust/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ use std::ptr::NonNull;
unsafe extern "C" {
fn ladybird_gfx_font_glyph_width(font: *const c_void, code_point: u32) -> f32;
fn ladybird_gfx_font_glyph_id(font: *const c_void, code_point: u32) -> u32;
fn ladybird_gfx_font_contains_glyph(font: *const c_void, code_point: u32) -> bool;
fn ladybird_gfx_font_is_emoji_font(font: *const c_void) -> bool;
fn ladybird_gfx_font_cascade_list_font_for_code_point(
list: *const c_void,
code_point: u32,
trigger_pending_loads: bool,
emoji_presentation: bool,
forced_presentation: bool,
) -> *const c_void;
fn ladybird_gfx_font_cascade_list_first(list: *const c_void) -> *const c_void;
fn ladybird_gfx_font_cascade_list_ref(list: *const c_void);
fn ladybird_gfx_font_cascade_list_unref(list: *const c_void);
fn ladybird_gfx_emoji_presentation_for_code_point(
code_point: u32,
next_code_point: u32,
has_next_code_point: bool,
) -> u8;
}

#[derive(Clone, Copy)]
Expand All @@ -19,6 +36,14 @@ pub struct FontRef<'a> {
_lifetime: PhantomData<&'a c_void>,
}

impl PartialEq for FontRef<'_> {
fn eq(&self, other: &Self) -> bool {
self.raw == other.raw
}
}

impl Eq for FontRef<'_> {}

impl<'a> FontRef<'a> {
/// # Safety
///
Expand Down Expand Up @@ -46,8 +71,125 @@ impl<'a> FontRef<'a> {
unsafe { ladybird_gfx_font_glyph_id(self.raw.as_ptr(), code_point) }
}

#[inline]
pub fn contains_glyph(self, code_point: u32) -> bool {
// SAFETY: FontRef's constructor requires the Gfx::Font to remain live
// for this reference's lifetime.
unsafe { ladybird_gfx_font_contains_glyph(self.raw.as_ptr(), code_point) }
}

#[inline]
pub fn is_emoji_font(self) -> bool {
// SAFETY: FontRef's constructor requires the Gfx::Font to remain live
// for this reference's lifetime.
unsafe { ladybird_gfx_font_is_emoji_font(self.raw.as_ptr()) }
}

#[inline]
pub(crate) fn as_ptr(self) -> *const c_void {
self.raw.as_ptr()
}

#[inline]
pub fn as_raw(self) -> *const c_void {
self.raw.as_ptr()
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EmojiPresentation {
pub is_emoji: bool,
pub forced: bool,
}

pub fn emoji_presentation_for_code_point(code_point: u32, next_code_point: Option<u32>) -> EmojiPresentation {
// SAFETY: The lookup reads only immutable Unicode tables.
let encoded = unsafe {
ladybird_gfx_emoji_presentation_for_code_point(
code_point,
next_code_point.unwrap_or(0),
next_code_point.is_some(),
)
};
EmojiPresentation {
is_emoji: encoded & 1 != 0,
forced: encoded & 2 != 0,
}
}

#[derive(Clone, Copy)]
pub struct FontCascadeListRef<'a> {
raw: NonNull<c_void>,
_lifetime: PhantomData<&'a c_void>,
}

impl<'a> FontCascadeListRef<'a> {
/// # Safety
///
/// `raw` must point to a live `Gfx::FontCascadeList` for the returned
/// reference's lifetime.
#[inline]
pub unsafe fn from_raw(raw: *const c_void) -> Self {
Self {
raw: NonNull::new(raw.cast_mut()).expect("Gfx::FontCascadeList pointer must not be null"),
_lifetime: PhantomData,
}
}

pub fn font_for_code_point(
self,
code_point: u32,
trigger_pending_loads: bool,
presentation: EmojiPresentation,
) -> FontRef<'a> {
// SAFETY: The constructor requires the Gfx::FontCascadeList to remain
// live for this reference's lifetime, and the fonts it resolves are
// owned by it.
unsafe {
FontRef::from_raw(ladybird_gfx_font_cascade_list_font_for_code_point(
self.raw.as_ptr(),
code_point,
trigger_pending_loads,
presentation.is_emoji,
presentation.forced,
))
}
}

pub fn first(self) -> FontRef<'a> {
// SAFETY: The constructor requires the Gfx::FontCascadeList to remain
// live for this reference's lifetime.
unsafe { FontRef::from_raw(ladybird_gfx_font_cascade_list_first(self.raw.as_ptr())) }
}
}

/// A strong reference to a `Gfx::FontCascadeList`, keeping the list and every
/// font it can resolve alive until dropped.
pub struct RetainedFontCascadeList {
raw: NonNull<c_void>,
}

impl RetainedFontCascadeList {
/// # Safety
///
/// `raw` must point to a live `Gfx::FontCascadeList` at the time of the
/// call.
pub unsafe fn retain(raw: *const c_void) -> Self {
let raw = NonNull::new(raw.cast_mut()).expect("Gfx::FontCascadeList pointer must not be null");
// SAFETY: The caller guarantees the list is live, and ref() keeps it
// that way until this reference drops.
unsafe { ladybird_gfx_font_cascade_list_ref(raw.as_ptr()) };
Self { raw }
}

pub fn as_raw(&self) -> *const c_void {
self.raw.as_ptr()
}
}

impl Drop for RetainedFontCascadeList {
fn drop(&mut self) {
// SAFETY: retain() took a strong reference on construction.
unsafe { ladybird_gfx_font_cascade_list_unref(self.raw.as_ptr()) };
}
}
76 changes: 76 additions & 0 deletions Libraries/LibUnicode/Segmenter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,3 +745,79 @@ bool Segmenter::should_continue_beyond_word(Utf16View const& word)
}

}

struct UnicodeLayoutSegmenterHandle {
NonnullOwnPtr<Unicode::Segmenter> segmenter;
Vector<char> ascii_storage;
};

extern "C" {
void* unicode_layout_grapheme_segmenter_create(u16 const*, size_t);
void* unicode_layout_line_segmenter_create(u16 const*, size_t);
i64 unicode_layout_segmenter_next_boundary(void*, size_t, bool);
void unicode_layout_segmenter_destroy(void*);
}

static Unicode::Segmenter& unicode_layout_segmenter_prototype(Unicode::SegmenterGranularity granularity)
{
static thread_local Unicode::Segmenter* grapheme_prototype = nullptr;
static thread_local Unicode::Segmenter* line_prototype = nullptr;
auto& prototype = granularity == Unicode::SegmenterGranularity::Grapheme ? grapheme_prototype : line_prototype;
if (!prototype)
prototype = Unicode::Segmenter::create(granularity).leak_ptr();
return *prototype;
}

extern "C" void* unicode_layout_grapheme_segmenter_create(u16 const* text, size_t length_in_code_units)
{
auto view = Utf16View { reinterpret_cast<char16_t const*>(text), length_in_code_units };
if (view.is_ascii()) {
return new UnicodeLayoutSegmenterHandle {
.segmenter = Unicode::Segmenter::create_for_ascii_grapheme(length_in_code_units),
.ascii_storage = {},
};
}
auto segmenter = unicode_layout_segmenter_prototype(Unicode::SegmenterGranularity::Grapheme).clone();
segmenter->set_segmented_text(view);
return new UnicodeLayoutSegmenterHandle { .segmenter = move(segmenter), .ascii_storage = {} };
}

extern "C" void* unicode_layout_line_segmenter_create(u16 const* text, size_t length_in_code_units)
{
auto view = Utf16View { reinterpret_cast<char16_t const*>(text), length_in_code_units };
if (view.is_ascii()) {
Vector<char> ascii_storage;
ascii_storage.ensure_capacity(length_in_code_units);
for (size_t index = 0; index < length_in_code_units; ++index)
ascii_storage.unchecked_append(static_cast<char>(text[index]));
auto ascii_view = ascii_storage.is_empty()
? Utf16View {}
: Utf16View { StringView { ascii_storage.data(), ascii_storage.size() } };
if (auto segmenter = Unicode::Segmenter::try_create_for_ascii_line(ascii_view)) {
return new UnicodeLayoutSegmenterHandle {
.segmenter = segmenter.release_nonnull(),
.ascii_storage = move(ascii_storage),
};
}
}
auto segmenter = unicode_layout_segmenter_prototype(Unicode::SegmenterGranularity::Line).clone();
segmenter->set_segmented_text(view);
return new UnicodeLayoutSegmenterHandle { .segmenter = move(segmenter), .ascii_storage = {} };
}

extern "C" i64 unicode_layout_segmenter_next_boundary(void* handle, size_t index, bool inclusive)
{
VERIFY(handle);
auto& segmenter = *static_cast<UnicodeLayoutSegmenterHandle*>(handle)->segmenter;
auto boundary = segmenter.next_boundary(
index, inclusive ? Unicode::Segmenter::Inclusive::Yes : Unicode::Segmenter::Inclusive::No);
if (!boundary.has_value())
return -1;
return static_cast<i64>(*boundary);
}

extern "C" void unicode_layout_segmenter_destroy(void* handle)
{
VERIFY(handle);
delete static_cast<UnicodeLayoutSegmenterHandle*>(handle);
}
26 changes: 20 additions & 6 deletions Libraries/LibWeb/HTML/HTMLElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,13 +348,27 @@ static Vector<Variant<Utf16String, RequiredLineBreakCount>> rendered_text_collec
if (auto const* layout_text_node = as_if<Layout::TextNode>(layout_node)) {
Layout::TextOffsetMapping mapping { layout_text_node->dom_node() };
mapping.for_each_fragment([&](Layout::TextNode const& slice) {
Layout::TextNode::ChunkIterator iterator { slice, false, false };
while (true) {
auto chunk = iterator.next();
if (!chunk.has_value())
break;
items.append(Utf16String::from_utf16(chunk.release_value().view));
// Collapse whitespace like the text chunker feeding inline layout
// does: within each run of collapsible whitespace, every code
// point after the first is removed.
auto const& text = slice.text_for_rendering();
auto should_collapse_whitespace = first_is_one_of(
slice.parent()->computed_values().white_space_collapse(),
CSS::WhiteSpaceCollapse::Collapse, CSS::WhiteSpaceCollapse::PreserveBreaks);
if (!should_collapse_whitespace) {
items.append(text);
return;
}
Utf16StringBuilder builder { text.length_in_code_units() };
bool previous_code_unit_is_collapsible = false;
for (size_t index = 0; index < text.length_in_code_units(); ++index) {
auto code_unit = text.utf16_view().code_unit_at(index);
auto is_collapsible = code_unit < 0x80 && is_ascii_space(code_unit);
if (!is_collapsible || !previous_code_unit_is_collapsible)
builder.append_code_unit(code_unit);
previous_code_unit_is_collapsible = is_collapsible;
}
items.append(builder.to_string());
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
return items;
}
Expand Down
Loading
Loading