Commit graph

31 commits

Author SHA1 Message Date
Tim Ledbetter
360bfd66f6 LibWeb: Apply the font-variant-emoji property to font selection 2026-06-20 23:51:46 +02:00
Tim Ledbetter
57103da08d LibGfx+LibWeb: Make emoji font fallback presentation-aware
Previously, font selection ignored the Unicode emoji presentation of a
code point. Emoji-capable code points were always resolved through
pre-baked color emoji and symbol fonts. Text-default code points which
lacked the `Emoji_Presentation` property, were therefore rendered as
color emoji rather than text. We now classify each code point's default
presentation from its `Emoji_Presentation` property and any trailing
variation selector.
2026-06-20 23:51:46 +02:00
Andreas Kling
9340d2d1a3 LibWeb: Make layout nodes refcounted
Move the layout tree from GC allocation to refcounted ownership so
removed layout and paint subtrees are destroyed synchronously instead
of waiting for the next GC sweep. This dramatically reduces GC memory
usage peaks after layout tree churn and makes it easier for memory use
to fall back after large document updates.

Update layout factories, tree traversal, SVG layout node creation,
paintable back-pointers, and pseudo-element layout links to use RefPtr
ownership.

Make display: contents follow the same shape as Blink and WebKit: the
element itself does not create a layout node, and its children are
flattened into the nearest layout parent. Wrap direct non-whitespace
text in an anonymous inline node when the boxless element contributes
inherited style to that text.

Use an internal inline wrapper for display: contents pseudo-elements
so generated content can still participate in layout, painting, hit
testing, and pseudo-element queries. Keep CSSOM reporting the computed
display value from the pseudo style, not the internal wrapper.

Remove the retained out-of-tree layout node list and its testing hook,
since the flattened model does not need a side owner for boxless
elements. Add coverage for inherited text style, dynamic insertion
order, pseudo-element hit testing, and computed style queries.
2026-06-07 20:52:49 +02:00
Andreas Kling
9c040f1dd3 LibWeb: Make generated content text layout-owned
Stop creating unattached DOM Text nodes for string items in generated
CSS content. These text nodes are layout artifacts, so store their text
directly in a GeneratedTextNode instead of pretending they have DOM
backing.

Teach text shaping and first-letter splitting to read text through the
layout text source while keeping DOM-specific behavior behind an
optional DOM text accessor. This preserves generated first-letter
handling without rooting fake DOM nodes.

Add crash coverage for generated content surviving GC and rebaseline
layout dumps that now identify generated text explicitly.
2026-06-07 20:52:49 +02:00
Andreas Kling
c90b79a282 LibWeb: Cache TextNode chunks between layouts
Keep one per-TextNode chunk list so repeated inline layouts can skip
segmenting text and resolving fonts for each chunk. The cache entry is
keyed by the wrapping mode, whitespace behavior, word-break value, and
font cascade used to produce it.

Store rendered text, segmenters, and chunks behind one text-dependent
cache key. This keeps cached Utf16Views and segmenter state anchored to
current text when inherited text style changes without rebuilding the
layout tree.

Keep the cached font cascade alive while chunks are cached so a later
font-list allocation cannot compare equal by reusing the same address.

Add innerText coverage for style-only text-transform and white-space
changes, since that path constructs ChunkIterator directly.
2026-05-24 23:41:57 +02:00
Tim Ledbetter
b67d73a661 LibWeb: Apply ::first-letter pseudo-element styles
We now apply first letter styles by splitting text with a first-letter
style applied into 2 `TextSliceNode` objects.  The
`DOM::Text` layout  node always points at the non first-letter slice
and the first-letter slice is  reachable via
`TextSliceNode::first_letter_slice()`.

First letter splitting works by `TreeBuilder` walking a block
container's inline descendants to find the first typographic letter
unit per the pattern given in  css-pseudo level 4, which is then
wrapped in an anonymous inline box styled with the `::first-letter`
computed properties.

Consumers that map between DOM offsets and layout geometry
are updated to visit all slices of a `DOM::Text` through
`TextOffsetMapping`.
2026-05-20 12:09:19 +01:00
Tim Ledbetter
ca7a09d0ef LibWeb: Track PaintableFragment offsets relative to the owning DOM::Text 2026-05-20 12:09:19 +01:00
Tim Ledbetter
d69c3c26b1 LibWeb: Make TextNode subclassable 2026-05-20 12:09:19 +01:00
Aliaksandr Kalenik
568b7ce7ea LibWeb: Make Paintable tree ref-counted
The Paintable tree and its supplemental painting data structures were
GC allocated because that was the easiest way to manage it and avoid
leaks introduced by ref cycles. This included the Paintable subclasses
themselves plus StackingContext, ChromeWidget, Scrollbar, ResizeHandle,
and scroll-frame state.

We are now trying to reduce GC allocation churn on layout and painting
updates, so keeping this short-lived rendering tree outside the JS heap
is a better fit. Move Paintable to RefCountedTreeNode, make painting
helpers ref-counted or weakly reference Paintables, and update the
layout and event-handler call sites to use RefPtr/WeakPtr ownership.
2026-05-07 15:03:44 +02:00
Aliaksandr Kalenik
0bfe4677ae LibWeb: Verify whitespace font has the glyph in font_for_space()
When inline layout emits a whitespace chunk, it previously selected the
surrounding text's font without checking whether that font actually
contains a glyph for the whitespace codepoint. On pages that use
`@font-face` rules sharded by `unicode-range` (e.g. a Roboto webfont
split across one file for Cyrillic letters and another for basic Latin),
the shard covering the letters is picked for an adjacent space even
though the space codepoint lives in a different shard. HarfBuzz then
shapes the space with a font that has no glyph for it and emits
`.notdef`, rendering spaces as tofu boxes.

Check `contains_glyph(space_code_point)` on each candidate in
`font_for_space()` and fall through to
`FontCascadeList::font_for_code_point()` for the whitespace codepoint
when no surrounding font has the glyph.

Fixes whitespace rendering on web.telegram.org/a.
2026-04-22 20:27:41 +02:00
Tim Ledbetter
d49a2dcfa1 LibWeb: Implement word-break functionality using UAX#14 line breaking
We now use ICU's line break iterator to determine soft wrap
opportunities, enabling correct line breaking for CJK text.
2026-02-14 16:23:18 -05:00
Andreas Kling
baa9b6cc34 LibWeb: Prefer non-emoji font when deciding width of space between words
This fixes an issue where having an emoji font first in the font-family
cascade list would cause us to get the space width from the emoji font
as well.

We now look at adjacent text chunks when deciding which font to use for
space widths, instead of just blindly obeying the font-family value.
2025-11-06 23:42:01 +01:00
zac
8c29b0a848 LibWeb/Tests: Add empty text chunk in empty editables
Otherwise the cursor won't get rendered in empty input fields.
2025-09-24 12:33:17 +01:00
Jelle Raaijmakers
9e9db9a9dd LibWeb: Store correct text offsets in PaintableFragment
Previously, we were collapsing whitespace in Layout::TextNode and then
passed the resulting string for further processing through ChunkIterator
-> InlineLevelIterator -> InlineFormattingContext -> LineBuilder ->
LineBoxFragment -> PaintableFragment. Our painting tree is where we deal
with things like range offsets into the underlying text nodes, but since
we modified the original string, the offsets were wrong.

This changes the way we generate fragments:

  * Layout::TextNode no longer collapses whitespace as part of its
    stored "text for rendering", but moves this logic to ChunkIterator
    which splits up this text into separate views whenever whitespace
    needs to be collapsed.

  * Layout::LineBox now only extends the last fragment if its end offset
    is equal to the new fragment's start offset. Otherwise, there's a
    gap caused by collapsing whitespace and we need to generate a
    separate fragment for that in order to have a correct start offset.

Some tests need new baselines because of the fixed start offsets.

Fixes #566.
2025-09-12 15:34:09 -04:00
Luke Wilde
b17783bb10 Everywhere: Change west consts caught by clang-format-21 to east consts 2025-08-29 18:18:55 +01:00
Timothy Flynn
0e4fb9ae73 LibWeb: Ensure up/down arrow navigation is grapheme-aware
Previously, it was possible for an up/down arrow press to place the
cursor in the middle of a multi-code point grapheme cluster. We want to
prevent this in a way that matches the behavior of other browsers.

Both Chrome and Firefox will map the starting position to a visually
equivalent position in the target line with harfbuzz and ICU segmenters.
The need for this is explained in a code comment. The result is a much
more natural feeling of text navigation.
2025-08-18 13:17:28 +02:00
Timothy Flynn
97548f48de LibWeb: Port rendered text to UTF-16
This migrates TextNode::text_for_rendering() to Utf16String and deals
with the fallout. As a consequence, this effectively reverts commit
3df83dade8.

The layout test expecation file updates are because we now express text
lengths and offsets in UTF-16 code units.

The selection-over-multiple-code-units expectation file update actually
represents a correctness fix. Our result now matches Firefox.
2025-07-25 18:16:22 +02:00
Andrew Kaster
be2dd91289 LibGfx+LibWeb: Store Typeface and Font-related types in RefPtr to const 2025-04-16 10:41:44 -06:00
InvalidUsernameException
be47f95180 LibWeb: Reduce number of recompiled files for display list headers
This reduces the number of `.cpp` files that need to be recompiled when
one of the below header files changes as follows:

Painting/Command.h: 1030 -> 61
Painting/DisplayList.h: 1030 -> 60
Painting/DisplayListRecorder.h: 557 -> 59
2025-02-23 10:14:39 -05:00
Shannon Booth
f87041bf3a LibGC+Everywhere: Factor out a LibGC from LibJS
Resulting in a massive rename across almost everywhere! Alongside the
namespace change, we now have the following names:

 * JS::NonnullGCPtr -> GC::Ref
 * JS::GCPtr -> GC::Ptr
 * JS::HeapFunction -> GC::Function
 * JS::CellImpl -> GC::Cell
 * JS::Handle -> GC::Root
2024-11-15 14:49:20 +01:00
Timothy Flynn
93712b24bf Everywhere: Hoist the Libraries folder to the top-level 2024-11-10 12:50:45 +01:00
Andreas Kling
13d7c09125 Libraries: Move to Userland/Libraries/ 2021-01-12 12:17:46 +01:00
Andreas Kling
612827eff3 LibWeb: Don't prune whitespace nodes from layout tree
Various whitespace-related issues have been fixed, and support for the
different CSS white-space values is improved enough that I think we can
stop doing the hack where we just prune whitespace nodes from the tree
and actually let them show up.

This is a nice step forward for correctness with the slight downside of
cluttering up layout tree dumps with tons of whitespace text nodes.
But hey, that's the web we know & love. :^)

Fixes #4427.
2021-01-06 14:58:48 +01:00
Andreas Kling
d9b2650dcc LibWeb: Remove specified style from layout nodes
Layout nodes now only carry CSS computer values with them. The main
idea here is to give them only what they need to perform layout, and
leave the rest back in the DOM.
2021-01-06 14:58:48 +01:00
Andreas Kling
07dd73c351 LibWeb: Remove hand-rolled is_foo() helpers in Layout::Node classes 2021-01-01 18:56:49 +01:00
Andreas Kling
730af2c524 LibWeb: Simplify Layout::Node::class_name() with RTTI 2021-01-01 16:08:49 +01:00
Andreas Kling
865f524d5b AK+LibGUI+LibWeb: Remove AK::TypeTraits in favor of RTTI-based helpers
Now that we have RTTI in userspace, we can do away with all this manual
hackery and use dynamic_cast.

We keep the is<T> and downcast<T> helpers since they still provide good
readability improvements. Note that unlike dynamic_cast<T>, downcast<T>
does not fail in a recoverable way, but will assert if the object being
casted is not a T.
2021-01-01 15:33:30 +01:00
Andreas Kling
615a4d4f71 LibWeb: First slightly naive implementation of CSS floats :^)
Boxes can now be floated left or right, which makes text within the
same block formatting context flow around them.

We were creating way too many block formatting contexts. As it turns
out, we don't need one for every new block, but rather there's a set
of rules that determines whether a given block creates a new block
formatting context.

Each BFC keeps track of the floating boxes within it, and IFC's can
then query it to find the available space for line boxes.

There's a huge hack in here where we assume all lines are the exact
line-height. Making this work with vertically non-uniform lines will
require some architectural changes.
2020-12-05 22:51:03 +01:00
Andreas Kling
f35b406dfb LibWeb: Virtualize Layout::Node::paint_fragment()
LineBoxFragment no longer needs to care what type of layout node a
fragment refers to during paint, it can just call paint_fragment(). :^)
2020-12-03 21:46:01 +01:00
Andreas Kling
311e1039b5 LibWeb: Paint line box fragments during all paint phases
Fragment painting was very limited by only being called during the
foreground paint phase. We now paint fragments as part of every phase
(and the phase is passed to paint_fragment() of course!)
2020-12-03 21:45:46 +01:00
Andreas Kling
5aeab9878e LibWeb: Rename LayoutNode classes and move them into Layout namespace
Bring the names of various boxes closer to spec language. This should
hopefully make things easier to understand and hack on. :^)

Some notable changes:

- LayoutNode -> Layout::Node
- LayoutBox -> Layout::Box
- LayoutBlock -> Layout::BlockBox
- LayoutReplaced -> Layout::ReplacedBox
- LayoutDocument -> Layout::InitialContainingBlockBox
- LayoutText -> Layout::TextNode
- LayoutInline -> Layout::InlineNode

Note that this is not strictly a "box tree" as we also hang inline/text
nodes in the same tree, and they don't generate boxes. (Instead, they
contribute line box fragments to their containing block!)
2020-11-22 15:56:27 +01:00
Renamed from Libraries/LibWeb/Layout/LayoutText.h (Browse further)