Commit graph

42 commits

Author SHA1 Message Date
Andreas Kling
e8a5233b94 LibWeb: Don't round fragment widths while accumulating in LineBuilder
By rounding the fragment widths, we sometimes inserted line breaks
prematurely, even though the fragment *would* fit.
2022-09-29 20:09:56 +02:00
Andreas Kling
5efd63741f LibWeb: Perform horizontal inline alignment based on available space
Previously, we were using the full containing block width as a reference
for text-align values "right" and "center". This didn't take intruding
floats into account.
2022-09-22 17:06:48 +02:00
Andreas Kling
389f47f6fe LibWeb: Check both top and bottom of float position when looking for fit
We have to check that there's enough space at both the top and bottom of
the float's margin box, otherwise we risk overlapping existing content.
2022-09-22 16:54:12 +02:00
Andreas Kling
412b2313f3 LibWeb: Improve inline flow around floating boxes
This patch combines a number of techniques to make inline content flow
more correctly around floats:

- During inline layout, BFC now lets LineBuilder decide the Y coordinate
  when inserting a new float. LineBuilder has more information about the
  currently accumulated line, and can make better breaking decisions.

- When inserting a float on one side, and the top of the newly inserted
  float is below the bottommost float on the opposite side, we now reset
  the opposite side back to the start of that edge. This improves
  breaking behavior between opposite-side floats.

- After inserting a float during inline layout, we now recalculate the
  available space on the line, but don't adjust X offsets of already
  existing fragments. This is handled by update_last_line() anyway,
  so it was pointless busywork.

- When measuring whether a line can fit at a given Y coordinate, we now
  consider both the top and bottom Y values of the line. This fixes an
  issue where the bottom part of a line would bleed over other content
  (since we had only checked that the top Y coordinate of that line
  would fit.)

There are some pretty brain-dead algorithms in here that we need to make
smarter, but I didn't want to complicate this any further so I've left
FIXMEs about them instead.
2022-09-16 15:15:50 +02:00
Andreas Kling
71ca857b67 LibWeb: Break lines until we have enough space between floats
Before this change, we'd always insert one line box fragment, even when
a float was taking up too much space on the line, and the fragment
didn't actually fit.

We now perform line breaks until we have enough space between floats.
This fixes many page layouts where we'd previously see small fragments
of inline content outside the right edge of the containing block.
2022-09-13 17:03:31 +02:00
Andreas Kling
ab6af4c9a0 LibWeb: Remove unused member LineBuilder::m_layout_mode 2022-08-14 23:33:28 +02:00
Andreas Kling
ed8930fff5 LibWeb: Add accessors for UsedValues::computed_{width,height}
This is preparation for doing some more work when assigning to these
values.
2022-07-19 15:40:41 +02:00
Andreas Kling
9b46091f38 LibWeb: Rename LayoutState::NodeState => LayoutState::UsedValues
This object contains all the CSS "used values" as seen during the layout
process, so calling it "used values" seems appropriate. :^)
2022-07-17 14:11:37 +02:00
Andreas Kling
52862c72d0 LibWeb: Rename FormattingState to LayoutState
This seems a bit more descriptive (and also a bit shorter).
2022-07-17 14:11:36 +02:00
Andreas Kling
e78282aebb LibWeb: Make sure we always apply size constraints in IFC
Pre-compute the effective containing block width in the IFC constructor
and use that throughout.
2022-07-11 18:57:45 +02:00
Andreas Kling
64959a8504 LibWeb: Express intrinsic size layout via size constraints
Previously, we had three layout modes:

- Normal:
    - Everything uses the computed values from CSS.

- MinContent:
    - Containing blocks act as if they have 0 width.
    - All line breaking opportunities are taken.

- MaxContent:
    - Containing blocks act as if they have infinite width.
    - Only forced line breaks are accepted.

The above was based on a set of misunderstandings of CSS sizing.
A major problem with the above was that *all* containing blocks
behaved differently during intrinsic size layout, not just the
relevant one.

With this patch there are only two layout modes:

- Normal:
    - Everything uses the computed values from CSS.

- IntrinsicSizeDetermination:
    - One or more boxes have size constraints applied.

There are two size constraints per layout box, set here:

- FormattingState::NodeState::width_constraint
- FormattingState::NodeState::height_constraint

They are of type SizeConstraint and can be one of None, MinContent,
or MaxContent. The default is None.

When performing an IntrinsicSizeDetermination layout, we now assign
a size constraint to the box we're trying to determine the intrinsic
size of, which is then honored by using two new helpers to query
the dimensions of containing blocks:

- FormattingContext::containing_block_width_for(Box)
- FormattingContext::containing_block_height_for(Box)

If there's a relevant constraint in effect on the Box, the size of
its containing block is adjusted accordingly.

This is essentially an implementation of the "available space"
constraints from CSS-SIZING-3. I'm sure some things will break from
this, and we'll have to deal with that separately.

Spec: https://drafts.csswg.org/css-sizing-3/#available
2022-07-11 18:57:45 +02:00
Andreas Kling
6a4247bee9 LibWeb: Use more precise font metrics when doing inline layout
We now position inline-level boxes based on ascent and descent metrics
from the font in use. This makes our basic text layouts look a lot more
like those produced by other browsers. :^)

I've tried to match the terminology used by the CSS Inline Layout spec.

This will regress Acid2 a little bit, and probably various other sites,
but on the whole it's the direction we should be heading, so let's go.
2022-03-30 01:12:44 +02:00
Andreas Kling
1c88536298 LibWeb: Use the new Gfx::Painter::draw_text_run() API for drawing text
This avoids a bunch of unnecessary work in Painter which not only took
time, but sometimes also led to alignment issues. draw_text_run() will
draw the text where we tell it, and that's it.
2022-03-30 00:57:15 +02:00
Andreas Kling
aefe1727fc LibWeb: Make text newlines in "pre" mode emit a ForcedBreak item
Instead of emitting a Text item with the "should_force_break" flag set
to true, newlines in newline-preserving text content now timply turn
into ForcedBreak items. This makes the <pre> element work again.
2022-03-26 20:04:56 +01:00
Andreas Kling
6cffabef03 LibWeb: Support CSS vertical-align values "top" and "bottom" 2022-03-24 22:57:01 +01:00
Andreas Kling
195ef5e26f LibWeb: Bring CSS line-height implementation closer to spec
We now distribute the line-height evenly between the space above and
below inline-level boxes. This noticeably improves our baseline
alignment in many cases.

Note that the "vertical-align: <length>" case is quite awkward, as the
extra height added by the offset baseline must count towards the line
box height.

There's a lot of room for improvement here, but this makes the buckets
container on Acid3 show up in the right place, with the right size.
2022-03-24 22:52:44 +01:00
Andreas Kling
ed1576eea8 LibWeb: Align baseline of inline-block with non-zero top border/padding
We were not adjusting the fragment baseline for inline-blocks that had
some border and/or padding size up top.
2022-03-24 18:30:56 +01:00
Andreas Kling
de6f7f0029 LibWeb: Support CSS floats in inline flow
CSS floats are now emitted by the InlineLevelIterator. When this
happens, IFC coordinates with the parent BFC to float the box to the
side, using the current LineBuilder state for vertical placement.

This makes the "instructions" text on Acid3 render as a single
contiguous flow of inline content.
2022-03-22 19:26:51 +01:00
Andreas Kling
c1f0d21bbe LibWeb: Rename the LayoutMode enum values and explain them
The old mode names, while mechanically accurate, didn't really reflect
their relationship to the CSS specifications.

This patch renames them as follows:

    Default => Normal
    AllPossibleLineBreaks => MinContent
    OnlyRequiredLineBreaks => MaxContent

There's also now an explainer comment with the LayoutMode enum about the
specific implications of layout in each mode.
2022-03-19 15:46:15 +01:00
Andreas Kling
39b7fbfeb9 LibWeb: Rewrite CSS float implementation to use offset-from-edge
The previous implementation used relative X offsets for both left and
right-side floats. This made right-side floats super awkward, since we
could only determine their X position once the width of the BFC root was
known, and for BFC roots with automatic width, this was not even working
at all most of the time.

This patch changes the way we deal with floats so that BFC keeps track
of the offset-from-edge for each float. The offset is the distance from
the BFC root edge (left or right, depending on float direction) to the
"innermost" margin edge of the floating box.

Floating box are now laid out in two passes: while going through the
normal flow layout, we put floats in their *static* position (i.e the
position they would have occupied if they weren't floating) and then
update the Y position value to the final one.

The second pass occurs later on, when the BFC root has had its width
assigned by the parent context. Once we know the root width, we can
set the X position value of floating boxes. (Because the X position of
right-side floats is relative to the right edge of the BFC root.)
2022-03-18 15:18:48 +01:00
Andreas Kling
28642de6ed LibWeb: Make LineBuilder aware of the current LayoutMode
This will allow us to override the available space correctly when doing
intrinsic sizing.
2022-03-18 15:18:48 +01:00
sin-ack
7fe3f2d970 LibWeb: Refactor text justification code + only justify below threshold
All the justification-related code is now in
InlineFormattingContext::apply_justification_to_fragments and is
performed after all the line boxes have been added.

Text justification now only happens on the last line if the excess space
including whitespace is below a certain threshold. 10% seemed reasonable
since it prevents the "over-justification" of text. Note that fragments
in line boxes before the last one are always justified.
2022-03-12 21:51:38 +01:00
Andreas Kling
45f717cfad LibWeb: Respect inline-axis margins between line box fragments :^)
This makes the buckets on ACID3 space out nicely.
2022-03-09 18:47:32 +01:00
Andreas Kling
ef33a40b99 LibWeb: Remove bogus line box width adjustment
This basically reverts 95715f0c8f, as it
was totally wrong for text-align:center and text-align:right.
2022-03-03 12:57:53 +01:00
Andreas Kling
7dae895704 LibWeb: Align actual baselines, not just bottoms
Until now, we've been treating the bottom of every line box fragment as
its baseline, and just aligning all the bottoms to the bottom of the
line box. That gave decent results in many cases, but was not correct.

This patch starts moving towards actual baseline calculations as
specified by CSS2.

Note that once layout is finished with a line box, we also store the
baseline of the line box in LineBox::m_baseline. This allows us to align
the real baseline of display:inline-block elements with other inline
content on the same line.
2022-03-03 12:57:53 +01:00
Andreas Kling
f8aa0c144f LibWeb: Store box's containing line box fragment in FormattingState
Layout should not change any properties of a box until the moment a
FormattingState is committed.
2022-02-28 14:17:44 +01:00
Andreas Kling
c6cf240f9a LibWeb: Store bottom edge location with each LineBox
Previously we were computing the bottom edge of a line box by finding
the bottommost fragment on the line.

That method didn't give correct results for line boxes with no fragments
(which is exactly what you get when inserting a bunch of <br> elements.)

To cover all situations, we now keep track of the bottommost edge in the
LineBox object itself.
2022-02-28 14:17:44 +01:00
Andreas Kling
16a47165ee LibWeb: Use coordinate instead of WeakPtr for box->fragment connection
Using WeakPtr to remember which LineBoxFragment owns which Box was
imposing some annoying constraints on the layout code. Importantly, it
was forcing us to heap-allocate fragments, which makes it much harder to
clone a FormattingState.

This patch replaces the WeakPtr with a coordinate system instead.
Fragments are referred to by their line box index + fragment index
within the line box.
2022-02-28 14:17:44 +01:00
Andreas Kling
00146005bb LibWeb: Very basic support for CSS vertical-align: <length>
If vertical-align is a length value, we lift each line box fragment that
far from the baseline of the line box.

This is rather messy, and we'll have to improve factoring as we add
support for more alignment types.
2022-02-26 09:30:17 +01:00
Andreas Kling
797f51e122 LibWeb: Add border box top/bottom metrics to line box fragments
This will allow us to support more kinds of vertical alignment.
2022-02-26 09:24:40 +01:00
Andreas Kling
95715f0c8f LibWeb: Fix rounding errors in calculation of final line box width
Instead of re-measuring the distance between the left and right edges of
a line box, we now simply adjust the final width based on how much the
rightmost fragment moved during the alignment process.
2022-02-25 19:38:31 +01:00
Andreas Kling
db5bf6e64c LibWeb: Rename FormattingState::ensure() -> get_mutable()
This makes it much more obvious what the difference between get() and
get_mutable() is.
2022-02-21 18:35:12 +01:00
Andreas Kling
c9700e100e LibWeb: Start making our layout system "transactional"
This patch adds a map of Layout::Node to FormattingState::NodeState.
Instead of updating layout nodes incrementally as layout progresses
through the formatting contexts, all updates are now written to the
corresponding NodeState instead.

At the end of layout, FormattingState::commit() is called, which
transfers all the values from the NodeState objects to the Node.

This will soon allow us to perform completely non-destructive layouts
which don't affect the tree.

Note that there are many imperfections here, and still many places
where we assign to the NodeState, but later read directly from the Node
instead. I'm just committing at this stage to make subsequent diffs
easier to understand.
2022-02-21 18:35:12 +01:00
Andreas Kling
f2a917229a LibWeb: Support inline-level padding and border properly
Here's roughly how this works:

- InlineLevelIterator keeps a nesting stack of inline-level nodes with
  box model metrics.
- When entering a node with box model metrics, we add them to the
  current "leading metrics".
- When exiting a node with box model metrics, we add them to the
  current "trailing metrics".
- Pending leading metrics are consumed by the first fragment added
  to the line.
- Pending trailing metrics are consumed by the last fragment added
  to the line.

Like before, the position of a line box fragment is the top left of its
content box. However, fragments are placed horizontally along the line
with space inserted for padding and border.

InlineNode::paint() now expands the content rect as appropriate when
painting background and borders.

Note that margins and margin collapsing is not yet implemented.

This makes the eyes on ACID2 horizontally centered. :^)
2022-02-14 18:00:21 +01:00
Andreas Kling
0608de8c12 LibWeb: Rename Layout::Box::size() to content_size()
This property represents the CSS content size, so let's reduce ambiguity
by using the spec terminology.

We also bring a bunch of related functions along for the ride.
2022-02-06 01:07:47 +01:00
Andreas Kling
b60e19fd34 LibWeb: Make LineBuilder assign height to empty line boxes
This ensures that <br> produces empty line boxes with the line-height
property as their height.
2022-01-23 01:36:13 +01:00
Andreas Kling
70a56d21dc LibWeb: Don't do horizontal inline line layout twice for last line
After pruning empty last line boxes, we now avoid re-running the
horizontal fragment positioning step, since that would be wasted work.
2022-01-23 01:22:41 +01:00
Andreas Kling
ba49dc82e0 LibWeb: Align inline-level boxes to the baseline of the line box
Vertical inline alignment is still very unsophisticated, but let's at
least put everything within each line on the same baseline.
2022-01-23 01:22:41 +01:00
Andreas Kling
2b631cde45 LibWeb: Avoid creating an empty first line box in block containers 2022-01-23 01:22:41 +01:00
Andreas Kling
251b2f49a2 LibWeb: Make LineBuilder respect LayoutMode::OnlyRequiredLineBreaks
In this layout mode, we should only break when forced (e.g by an
explicit <br> tag.) This is used when determining intrinsic sizes.)
2022-01-23 01:22:41 +01:00
Andreas Kling
d3adc94ce8 LibWeb: Dimension inline-block boxes before deciding about line breaks
We won't know if we need to break before the inline-block box until
after we've dimensioned it.
2022-01-23 01:22:41 +01:00
Andreas Kling
00bde9ca51 LibWeb: Add Layout::LineBuilder class for incremental line box layout
This class will be used to place items on lines incrementally instead of
the current two-phase approach.
2022-01-23 01:22:41 +01:00