mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Flow inlines around floats (#30243)
This implements the rest of the bulk of float support. Now inline element flow around floats and floats can be pushed down by inline elements before them. Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
cb2c876919
commit
96c51ba2e7
63 changed files with 410 additions and 220 deletions
|
@ -2,6 +2,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use super::float::PlacementAmongFloats;
|
||||
use super::CollapsibleWithParentStartMargin;
|
||||
use crate::cell::ArcRefCell;
|
||||
use crate::context::LayoutContext;
|
||||
use crate::flow::float::{FloatBox, SequentialLayoutState};
|
||||
|
@ -26,6 +28,7 @@ use atomic_refcell::AtomicRef;
|
|||
use gfx::text::glyph::GlyphStore;
|
||||
use gfx::text::text_run::GlyphRun;
|
||||
use servo_arc::Arc;
|
||||
use std::cell::OnceCell;
|
||||
use style::computed_values::white_space::T as WhiteSpace;
|
||||
use style::logical_geometry::WritingMode;
|
||||
use style::properties::ComputedValues;
|
||||
|
@ -37,7 +40,6 @@ use style::Zero;
|
|||
use webrender_api::FontInstanceKey;
|
||||
use xi_unicode::LineBreakLeafIter;
|
||||
|
||||
use super::CollapsibleWithParentStartMargin;
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct InlineFormattingContext {
|
||||
pub(super) inline_level_boxes: Vec<ArcRefCell<InlineLevelBox>>,
|
||||
|
@ -110,6 +112,74 @@ struct PartialInlineBoxFragment<'box_tree> {
|
|||
parent_nesting_level: InlineNestingLevelState<'box_tree>,
|
||||
}
|
||||
|
||||
/// Information about the current line under construction for a particular
|
||||
/// [`InlineFormattingContextState`]. This tracks position and size information while
|
||||
/// [`LineItem`]s are collected and is used as input when those [`LineItem`]s are
|
||||
/// converted into [`Fragment`]s during the final phase of line layout. Note that this
|
||||
/// does not store the [`LineItem`]s themselves, as they are stored as part of the
|
||||
/// nesting state in the [`InlineFormattingContextState`].
|
||||
struct LineUnderConstruction {
|
||||
/// The position where this line will start once it is laid out. This includes any
|
||||
/// offset from `text-indent`.
|
||||
start_position: Vec2<Length>,
|
||||
|
||||
/// The current inline position in the line being laid out into [`LineItems`] in this
|
||||
/// [`InlineFormattingContext`] independent of the depth in the nesting level.
|
||||
inline_position: Length,
|
||||
|
||||
/// If the current line ends with whitespace, this tracks the advance width of that
|
||||
/// whitespace. This is used to find the "real" width of a line if trailing whitespace
|
||||
/// is trimmed from the end.
|
||||
trailing_whitespace_advance: Length,
|
||||
|
||||
/// The currently calculated block size of this line, taking into account all inline
|
||||
/// content already laid out into [`LineItem`]s. Later content may increase the block
|
||||
/// size.
|
||||
block_size: Length,
|
||||
|
||||
/// Whether any active linebox has added a glyph, border, margin, or padding
|
||||
/// to this line, which indicates that the next run that exceeds the line length
|
||||
/// can cause a line break.
|
||||
has_content: bool,
|
||||
|
||||
/// Whether or not there are floats that did not fit on the current line. Before
|
||||
/// the [`LineItems`] of this line are laid out, these floats will need to be
|
||||
/// placed directly below this line, but still as children of this line's Fragments.
|
||||
has_floats_waiting_to_be_placed: bool,
|
||||
|
||||
/// A rectangular area (relative to the containing block / inline formatting
|
||||
/// context boundaries) where we can fit the line box without overlapping floats.
|
||||
/// Note that when this is not empty, its start corner takes precedence over
|
||||
/// [`LineUnderConstruction::start_position`].
|
||||
placement_among_floats: OnceCell<Rect<Length>>,
|
||||
}
|
||||
|
||||
impl LineUnderConstruction {
|
||||
fn new(start_position: Vec2<Length>) -> Self {
|
||||
Self {
|
||||
inline_position: start_position.inline.clone(),
|
||||
trailing_whitespace_advance: Length::zero(),
|
||||
start_position: start_position,
|
||||
block_size: Length::zero(),
|
||||
has_content: false,
|
||||
has_floats_waiting_to_be_placed: false,
|
||||
placement_among_floats: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn line_block_start_considering_placement_among_floats(&self) -> Length {
|
||||
match self.placement_among_floats.get() {
|
||||
Some(placement_among_floats) => placement_among_floats.start_corner.block,
|
||||
None => self.start_position.block,
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_placement_among_floats(&mut self, new_placement: Rect<Length>) {
|
||||
self.placement_among_floats.take();
|
||||
let _ = self.placement_among_floats.set(new_placement);
|
||||
}
|
||||
}
|
||||
|
||||
struct InlineFormattingContextState<'box_tree, 'a, 'b> {
|
||||
positioning_context: &'a mut PositioningContext,
|
||||
containing_block: &'b ContainingBlock<'b>,
|
||||
|
@ -120,17 +190,9 @@ struct InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|||
/// are currently laid out at the top-level of each [`InlineFormattingContext`].
|
||||
fragments: Vec<Fragment>,
|
||||
|
||||
/// The position of where the next line will start.
|
||||
current_line_start_position: Vec2<Length>,
|
||||
|
||||
/// The current inline position in the line being laid out into [`LineItems`] in this
|
||||
/// [`InlineFormattingContext`] independent of the depth in the nesting level.
|
||||
current_inline_position: Length,
|
||||
|
||||
/// Whether any active line box has added a glyph, border, margin, or padding
|
||||
/// to this line, which indicates that the next run that exceeds the line length
|
||||
/// can cause a line break.
|
||||
line_had_any_content: bool,
|
||||
/// Information about the line currently being laid out into [`LineItems`]s. The
|
||||
/// [`LineItem`]s themselves are stored in the nesting state.
|
||||
current_line: LineUnderConstruction,
|
||||
|
||||
/// The line breaking state for this inline formatting context.
|
||||
linebreaker: Option<LineBreakLeafIter>,
|
||||
|
@ -142,10 +204,19 @@ struct InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|||
impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> {
|
||||
/// Push a completed [LineItem] to the current nesteding level of this
|
||||
/// [InlineFormattingContext].
|
||||
fn push_line_item(&mut self, inline_size: Length, line_item: LineItem) {
|
||||
fn push_line_item(
|
||||
&mut self,
|
||||
inline_size: Length,
|
||||
line_item: LineItem,
|
||||
last_whitespace_advance: Length,
|
||||
) {
|
||||
self.current_line.has_content = true;
|
||||
self.current_line.inline_position += inline_size;
|
||||
self.current_line.trailing_whitespace_advance = last_whitespace_advance;
|
||||
self.current_line
|
||||
.block_size
|
||||
.max_assign(line_item.block_size());
|
||||
self.current_nesting_level.line_items_so_far.push(line_item);
|
||||
self.line_had_any_content = true;
|
||||
self.current_inline_position += inline_size;
|
||||
}
|
||||
|
||||
/// Finish layout of all the partial inline boxes in the current line,
|
||||
|
@ -155,7 +226,7 @@ impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|||
for partial in self.partial_inline_boxes_stack.iter_mut().rev() {
|
||||
partial.finish_layout(
|
||||
nesting_level,
|
||||
&mut self.current_inline_position,
|
||||
&mut self.current_line.inline_position,
|
||||
false, /* at_end_of_inline_element */
|
||||
);
|
||||
nesting_level = &mut partial.parent_nesting_level;
|
||||
|
@ -163,9 +234,6 @@ impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|||
|
||||
let line_items = std::mem::take(&mut nesting_level.line_items_so_far);
|
||||
self.finish_current_line(layout_context, line_items, self.containing_block);
|
||||
|
||||
self.current_inline_position = Length::zero();
|
||||
self.line_had_any_content = false;
|
||||
}
|
||||
|
||||
fn finish_current_line(
|
||||
|
@ -187,13 +255,29 @@ impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|||
|
||||
let inline_start_position =
|
||||
self.calculate_inline_start_for_current_line(containing_block, whitespace_trimmed);
|
||||
let block_start_position = self
|
||||
.current_line
|
||||
.line_block_start_considering_placement_among_floats();
|
||||
let block_end_position = block_start_position + self.current_line.block_size;
|
||||
|
||||
if let Some(sequential_layout_state) = self.sequential_layout_state.as_mut() {
|
||||
// This amount includes both the block size of the line and any extra space
|
||||
// added to move the line down in order to avoid overlapping floats.
|
||||
let increment = block_end_position - self.current_line.start_position.block;
|
||||
sequential_layout_state.advance_block_position(increment);
|
||||
}
|
||||
|
||||
if self.current_line.has_floats_waiting_to_be_placed {
|
||||
place_pending_floats(self, &mut line_items);
|
||||
}
|
||||
|
||||
let mut state = LineItemLayoutState {
|
||||
inline_position: inline_start_position,
|
||||
max_block_size: Length::zero(),
|
||||
inline_start_of_parent: Length::zero(),
|
||||
ifc_containing_block: containing_block,
|
||||
positioning_context: &mut self.positioning_context,
|
||||
line_block_start: self.current_line_start_position.block,
|
||||
line_block_start: block_start_position,
|
||||
};
|
||||
|
||||
let positioning_context_length = state.positioning_context.len();
|
||||
|
@ -209,16 +293,8 @@ impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|||
// we do not need to include it in the `start_corner` of the line's main Fragment.
|
||||
let start_corner = Vec2 {
|
||||
inline: Length::zero(),
|
||||
block: self.current_line_start_position.block,
|
||||
block: block_start_position,
|
||||
};
|
||||
self.current_line_start_position = Vec2 {
|
||||
inline: Length::zero(),
|
||||
block: self.current_line_start_position.block + size.block,
|
||||
};
|
||||
|
||||
if let Some(sequential_layout_state) = self.sequential_layout_state.as_mut() {
|
||||
sequential_layout_state.advance_block_position(size.block);
|
||||
}
|
||||
|
||||
let line_had_content =
|
||||
!fragments.is_empty() || state.positioning_context.len() != positioning_context_length;
|
||||
|
@ -237,6 +313,11 @@ impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|||
containing_block.style.writing_mode,
|
||||
)));
|
||||
}
|
||||
|
||||
self.current_line = LineUnderConstruction::new(Vec2 {
|
||||
inline: Length::zero(),
|
||||
block: block_end_position,
|
||||
});
|
||||
}
|
||||
|
||||
/// Given the amount of whitespace trimmed from the line and taking into consideration
|
||||
|
@ -286,22 +367,136 @@ impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> {
|
|||
},
|
||||
};
|
||||
|
||||
let (line_start, available_space) = match self.current_line.placement_among_floats.get() {
|
||||
Some(placement_among_floats) => (
|
||||
placement_among_floats.start_corner.inline,
|
||||
placement_among_floats.size.inline,
|
||||
),
|
||||
None => (Length::zero(), self.containing_block.inline_size),
|
||||
};
|
||||
|
||||
// Properly handling text-indent requires that we do not align the text
|
||||
// into the text-indent.
|
||||
// See <https://drafts.csswg.org/css-text/#text-indent-property>
|
||||
// "This property specifies the indentation applied to lines of inline content in
|
||||
// a block. The indent is treated as a margin applied to the start edge of the
|
||||
// line box."
|
||||
let text_indent = self.current_line_start_position.inline;
|
||||
let line_length = self.current_inline_position - whitespace_trimmed - text_indent;
|
||||
match text_align {
|
||||
TextAlign::Start => text_indent,
|
||||
TextAlign::End => (containing_block.inline_size - line_length).max(text_indent),
|
||||
TextAlign::Center => {
|
||||
let available_space = containing_block.inline_size - text_indent;
|
||||
((available_space - line_length) / 2.) + text_indent
|
||||
},
|
||||
let text_indent = self.current_line.start_position.inline;
|
||||
let line_length = self.current_line.inline_position - whitespace_trimmed - text_indent;
|
||||
line_start +
|
||||
match text_align {
|
||||
TextAlign::Start => text_indent,
|
||||
TextAlign::End => (available_space - line_length).max(text_indent),
|
||||
TextAlign::Center => (available_space - line_length + text_indent) / 2.,
|
||||
}
|
||||
}
|
||||
|
||||
fn place_float_fragment(&mut self, fragment: &mut BoxFragment) {
|
||||
let state = self
|
||||
.sequential_layout_state
|
||||
.as_mut()
|
||||
.expect("Tried to lay out a float with no sequential placement state!");
|
||||
|
||||
let block_offset_from_containining_block_top = state
|
||||
.current_block_position_including_margins() -
|
||||
state.current_containing_block_offset();
|
||||
state.place_float_fragment(
|
||||
fragment,
|
||||
CollapsedMargin::zero(),
|
||||
block_offset_from_containining_block_top,
|
||||
);
|
||||
}
|
||||
|
||||
/// Given a new potential line size for the current line, create a "placement" for that line.
|
||||
/// This tells us whether or not the new potential line will fit in the current block position
|
||||
/// or need to be moved. In addition, the placement rect determines the inline start and end
|
||||
/// of the line if it's used as the final placement among floats.
|
||||
fn place_line_among_floats(&self, potential_line_size: &Vec2<Length>) -> Rect<Length> {
|
||||
let sequential_layout_state = self
|
||||
.sequential_layout_state
|
||||
.as_ref()
|
||||
.expect("Should not have called this function without having floats.");
|
||||
|
||||
let ifc_offset_in_float_container = Vec2 {
|
||||
inline: sequential_layout_state
|
||||
.floats
|
||||
.containing_block_info
|
||||
.inline_start,
|
||||
block: sequential_layout_state.current_containing_block_offset(),
|
||||
};
|
||||
|
||||
let ceiling = self
|
||||
.current_line
|
||||
.line_block_start_considering_placement_among_floats();
|
||||
let mut placement = PlacementAmongFloats::new(
|
||||
&sequential_layout_state.floats,
|
||||
ceiling + ifc_offset_in_float_container.block,
|
||||
potential_line_size.clone(),
|
||||
&PaddingBorderMargin::zero(),
|
||||
);
|
||||
|
||||
let mut placement_rect = placement.place();
|
||||
placement_rect.start_corner = &placement_rect.start_corner - &ifc_offset_in_float_container;
|
||||
placement_rect
|
||||
}
|
||||
|
||||
/// Returns true if a new potential line size for the current line would require a line
|
||||
/// break. This takes into account floats and will also update the "placement among
|
||||
/// floats" for this line if the potential line size would not cause a line break.
|
||||
/// Thus, calling this method has side effects and should only be done while in the
|
||||
/// process of laying out line content that is always going to be committed to this
|
||||
/// line or the next.
|
||||
fn new_potential_line_size_causes_line_break(
|
||||
&mut self,
|
||||
potential_line_size: &Vec2<Length>,
|
||||
) -> bool {
|
||||
// If this is the first content on the line and we already have a float placement,
|
||||
// that means that the placement was initialized by a leading float in the IFC.
|
||||
// This placement needs to be updated, because the first line content might push
|
||||
// the block start of the line downward.
|
||||
if !self.current_line.has_content && self.sequential_layout_state.is_some() {
|
||||
let new_placement = self.place_line_among_floats(potential_line_size);
|
||||
self.current_line
|
||||
.replace_placement_among_floats(new_placement);
|
||||
}
|
||||
|
||||
if potential_line_size.inline > self.containing_block.inline_size {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we already have a placement among floats for this line, and the new potential
|
||||
// line size causes a change in the block position, then we will need a line
|
||||
// break. This block of code also takes the opportunity to update the placement
|
||||
// among floats in the case that line does fit at the same block position (because
|
||||
// its inline start may change).
|
||||
let old_placement = self.current_line.placement_among_floats.get().cloned();
|
||||
if let Some(old_placement) = old_placement {
|
||||
if potential_line_size.block > old_placement.size.block {
|
||||
let new_placement = self.place_line_among_floats(potential_line_size);
|
||||
if new_placement.start_corner.block != old_placement.start_corner.block {
|
||||
return true;
|
||||
} else {
|
||||
self.current_line
|
||||
.replace_placement_among_floats(new_placement);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise the new potential line size will require a newline if it fits in the
|
||||
// inline space available for this line. This space may be smaller than the
|
||||
// containing block if floats shrink the available inline space.
|
||||
let available_inline_space = if self.sequential_layout_state.is_some() {
|
||||
let placement_among_floats = self
|
||||
.current_line
|
||||
.placement_among_floats
|
||||
.get_or_init(|| self.place_line_among_floats(potential_line_size));
|
||||
placement_among_floats.size.inline
|
||||
} else {
|
||||
self.containing_block.inline_size
|
||||
};
|
||||
|
||||
potential_line_size.inline > available_inline_space
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -481,12 +676,10 @@ impl InlineFormattingContext {
|
|||
containing_block,
|
||||
sequential_layout_state,
|
||||
fragments: Vec::new(),
|
||||
current_line_start_position: Vec2 {
|
||||
current_line: LineUnderConstruction::new(Vec2 {
|
||||
inline: first_line_inline_start,
|
||||
block: Length::zero(),
|
||||
},
|
||||
current_inline_position: first_line_inline_start,
|
||||
line_had_any_content: false,
|
||||
}),
|
||||
linebreaker: None,
|
||||
partial_inline_boxes_stack: Vec::new(),
|
||||
current_nesting_level: InlineNestingLevelState {
|
||||
|
@ -499,8 +692,10 @@ impl InlineFormattingContext {
|
|||
|
||||
// FIXME(pcwalton): This assumes that margins never collapse through inline formatting
|
||||
// contexts (i.e. that inline formatting contexts are never empty). Is that right?
|
||||
// FIXME(mrobinson): This should not happen if the IFC collapses through.
|
||||
if let Some(ref mut sequential_layout_state) = ifc.sequential_layout_state {
|
||||
sequential_layout_state.collapse_margins();
|
||||
// FIXME(mrobinson): Collapse margins in the containing block offsets as well??
|
||||
}
|
||||
|
||||
loop {
|
||||
|
@ -533,7 +728,7 @@ impl InlineFormattingContext {
|
|||
// start working on the parent nesting level again.
|
||||
partial.finish_layout(
|
||||
&mut ifc.current_nesting_level,
|
||||
&mut ifc.current_inline_position,
|
||||
&mut ifc.current_line.inline_position,
|
||||
true, /* at_end_of_inline_element */
|
||||
);
|
||||
ifc.current_nesting_level = partial.parent_nesting_level
|
||||
|
@ -547,7 +742,7 @@ impl InlineFormattingContext {
|
|||
ifc.finish_current_line(layout_context, line_items, containing_block);
|
||||
|
||||
let mut collapsible_margins_in_children = CollapsedBlockMargins::zero();
|
||||
let content_block_size = ifc.current_line_start_position.block;
|
||||
let content_block_size = ifc.current_line.start_position.block;
|
||||
collapsible_margins_in_children.collapsed_through =
|
||||
content_block_size == Length::zero() && collapsible_with_parent_start_margin.0;
|
||||
|
||||
|
@ -592,7 +787,7 @@ impl<'box_tree> PartialInlineBoxFragment<'box_tree> {
|
|||
let mut pbm = style.padding_border_margin(&ifc.containing_block);
|
||||
|
||||
if inline_box.first_fragment {
|
||||
ifc.current_inline_position += pbm.padding.inline_start +
|
||||
ifc.current_line.inline_position += pbm.padding.inline_start +
|
||||
pbm.border.inline_start +
|
||||
pbm.margin.inline_start.auto_is(Length::zero)
|
||||
} else {
|
||||
|
@ -786,15 +981,18 @@ impl IndependentFormattingContext {
|
|||
},
|
||||
};
|
||||
|
||||
if fragment.content_rect.size.inline + pbm_sums.inline_sum() >
|
||||
ifc.containing_block.inline_size - ifc.current_inline_position &&
|
||||
ifc.current_nesting_level.white_space.allow_wrap() &&
|
||||
ifc.current_nesting_level.line_items_so_far.len() != 0
|
||||
{
|
||||
let size = &pbm_sums.sum() + &fragment.content_rect.size;
|
||||
let new_potential_line_size = Vec2 {
|
||||
inline: ifc.current_line.inline_position + size.inline,
|
||||
block: ifc.current_line.block_size.max(size.block),
|
||||
};
|
||||
|
||||
let can_break = ifc.current_nesting_level.white_space.allow_wrap() &&
|
||||
ifc.current_nesting_level.line_items_so_far.len() != 0;
|
||||
if ifc.new_potential_line_size_causes_line_break(&new_potential_line_size) && can_break {
|
||||
ifc.finish_line_and_reset(layout_context);
|
||||
}
|
||||
|
||||
let size = &pbm_sums.sum() + &fragment.content_rect.size;
|
||||
ifc.push_line_item(
|
||||
size.inline,
|
||||
LineItem::Atomic(AtomicLineItem {
|
||||
|
@ -802,6 +1000,7 @@ impl IndependentFormattingContext {
|
|||
size,
|
||||
positioning_context: child_positioning_context,
|
||||
}),
|
||||
Length::zero(),
|
||||
);
|
||||
|
||||
// After every atomic, we need to create a line breaking opportunity for the next TextRun.
|
||||
|
@ -903,6 +1102,7 @@ impl TextRun {
|
|||
break_at_start,
|
||||
} = self.break_and_shape(layout_context, &mut ifc.linebreaker);
|
||||
|
||||
let white_space = self.parent_style.get_inherited_text().white_space;
|
||||
let add_glyphs_to_current_line =
|
||||
|ifc: &mut InlineFormattingContextState,
|
||||
glyphs: Vec<std::sync::Arc<GlyphStore>>,
|
||||
|
@ -912,6 +1112,13 @@ impl TextRun {
|
|||
return;
|
||||
}
|
||||
|
||||
let last_whitespace_advance = match (white_space.preserve_spaces(), glyphs.last()) {
|
||||
(false, Some(last_glyph)) if last_glyph.is_whitespace() => {
|
||||
last_glyph.total_advance()
|
||||
},
|
||||
_ => Au::zero(),
|
||||
};
|
||||
|
||||
ifc.push_line_item(
|
||||
inline_advance,
|
||||
LineItem::TextRun(TextRunLineItem {
|
||||
|
@ -922,12 +1129,15 @@ impl TextRun {
|
|||
font_key,
|
||||
text_decoration_line: ifc.current_nesting_level.text_decoration_line,
|
||||
}),
|
||||
Length::from(last_whitespace_advance),
|
||||
);
|
||||
};
|
||||
|
||||
let white_space = self.parent_style.get_inherited_text().white_space;
|
||||
let line_height = line_height(&self.parent_style, &font_metrics);
|
||||
let new_max_height_of_line = ifc.current_line.block_size.max(line_height);
|
||||
|
||||
let mut glyphs = vec![];
|
||||
let mut inline_advance = Length::zero();
|
||||
let mut advance_from_text_run = Length::zero();
|
||||
let mut iterator = runs.iter().enumerate();
|
||||
while let Some((run_index, run)) = iterator.next() {
|
||||
// If this whitespace forces a line break, finish the line and reset everything.
|
||||
|
@ -940,30 +1150,39 @@ impl TextRun {
|
|||
add_glyphs_to_current_line(
|
||||
ifc,
|
||||
glyphs.drain(..).collect(),
|
||||
inline_advance,
|
||||
advance_from_text_run,
|
||||
true,
|
||||
);
|
||||
ifc.finish_line_and_reset(layout_context);
|
||||
inline_advance = Length::zero();
|
||||
advance_from_text_run = Length::zero();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// We break the line if this new advance and any advances from pending
|
||||
// whitespace bring us past the inline end of the containing block.
|
||||
let new_advance = Length::from(run.glyph_store.total_advance());
|
||||
let will_advance_past_containing_block =
|
||||
(new_advance + inline_advance + ifc.current_inline_position) >
|
||||
ifc.containing_block.inline_size;
|
||||
let new_advance_from_glyph_run = Length::from(run.glyph_store.total_advance());
|
||||
let new_total_advance = new_advance_from_glyph_run +
|
||||
advance_from_text_run +
|
||||
ifc.current_line.inline_position;
|
||||
|
||||
let new_potential_line_size = Vec2 {
|
||||
inline: new_total_advance,
|
||||
block: new_max_height_of_line,
|
||||
};
|
||||
|
||||
// We can only break the line, if this isn't the first actual content (non-whitespace or
|
||||
// preserved whitespace) on the line and this isn't the unbreakable run of this text run
|
||||
// (or we can break at the start according to the text breaker).
|
||||
let can_break = ifc.line_had_any_content && (break_at_start || run_index != 0);
|
||||
if will_advance_past_containing_block && can_break {
|
||||
add_glyphs_to_current_line(ifc, glyphs.drain(..).collect(), inline_advance, false);
|
||||
let can_break = ifc.current_line.has_content && (break_at_start || run_index != 0);
|
||||
if ifc.new_potential_line_size_causes_line_break(&new_potential_line_size) && can_break
|
||||
{
|
||||
add_glyphs_to_current_line(
|
||||
ifc,
|
||||
glyphs.drain(..).collect(),
|
||||
advance_from_text_run,
|
||||
true,
|
||||
);
|
||||
ifc.finish_line_and_reset(layout_context);
|
||||
inline_advance = Length::zero();
|
||||
advance_from_text_run = Length::zero();
|
||||
}
|
||||
|
||||
// From <https://www.w3.org/TR/css-text-3/#white-space-phase-2>:
|
||||
|
@ -978,17 +1197,22 @@ impl TextRun {
|
|||
// width.
|
||||
if run.glyph_store.is_whitespace() &&
|
||||
!white_space.preserve_spaces() &&
|
||||
!ifc.line_had_any_content
|
||||
!ifc.current_line.has_content
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
inline_advance += Length::from(run.glyph_store.total_advance());
|
||||
advance_from_text_run += Length::from(run.glyph_store.total_advance());
|
||||
glyphs.push(run.glyph_store.clone());
|
||||
ifc.line_had_any_content = true;
|
||||
ifc.current_line.has_content = true;
|
||||
}
|
||||
|
||||
add_glyphs_to_current_line(ifc, glyphs.drain(..).collect(), inline_advance, false);
|
||||
add_glyphs_to_current_line(
|
||||
ifc,
|
||||
glyphs.drain(..).collect(),
|
||||
advance_from_text_run,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -998,28 +1222,53 @@ impl FloatBox {
|
|||
layout_context: &LayoutContext,
|
||||
ifc: &mut InlineFormattingContextState,
|
||||
) {
|
||||
let mut box_fragment = self.layout(
|
||||
let mut fragment = self.layout(
|
||||
layout_context,
|
||||
ifc.positioning_context,
|
||||
ifc.containing_block,
|
||||
);
|
||||
|
||||
let state = ifc
|
||||
.sequential_layout_state
|
||||
.as_mut()
|
||||
.expect("Tried to lay out a float with no sequential placement state!");
|
||||
let margin_box = fragment.border_rect().inflate(&fragment.margin);
|
||||
let inline_size = margin_box.size.inline.max(Length::zero());
|
||||
|
||||
let available_inline_size = match ifc.current_line.placement_among_floats.get() {
|
||||
Some(placement_among_floats) => placement_among_floats.size.inline,
|
||||
None => ifc.containing_block.inline_size,
|
||||
} - (ifc.current_line.inline_position -
|
||||
ifc.current_line.trailing_whitespace_advance);
|
||||
|
||||
// If this float doesn't fit on the current line or a previous float didn't fit on
|
||||
// the current line, we need to place it starting at the next line BUT still as
|
||||
// children of this line's hierarchy of inline boxes (for the purposes of properly
|
||||
// parenting in their stacking contexts). Once all the line content is gathered we
|
||||
// will place them later.
|
||||
let fits_on_line = !ifc.current_line.has_content || inline_size <= available_inline_size;
|
||||
let needs_placement_later =
|
||||
ifc.current_line.has_floats_waiting_to_be_placed || !fits_on_line;
|
||||
|
||||
if needs_placement_later {
|
||||
ifc.current_line.has_floats_waiting_to_be_placed = true;
|
||||
} else {
|
||||
ifc.place_float_fragment(&mut fragment);
|
||||
|
||||
// We've added a new float to the IFC, but this may have actually changed the
|
||||
// position of the current line. In order to determine that we regenerate the
|
||||
// placement among floats for the current line, which may adjust its inline
|
||||
// start position.
|
||||
let new_placement = ifc.place_line_among_floats(&Vec2 {
|
||||
inline: ifc.current_line.inline_position,
|
||||
block: ifc.current_line.block_size,
|
||||
});
|
||||
ifc.current_line
|
||||
.replace_placement_among_floats(new_placement);
|
||||
}
|
||||
|
||||
let block_offset_from_containining_block_top = state
|
||||
.current_block_position_including_margins() -
|
||||
state.current_containing_block_offset();
|
||||
state.place_float_fragment(
|
||||
&mut box_fragment,
|
||||
CollapsedMargin::zero(),
|
||||
block_offset_from_containining_block_top,
|
||||
);
|
||||
ifc.current_nesting_level
|
||||
.line_items_so_far
|
||||
.push(LineItem::Float(FloatLineItem { box_fragment }));
|
||||
.push(LineItem::Float(FloatLineItem {
|
||||
fragment,
|
||||
needs_placement: needs_placement_later,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1123,6 +1372,22 @@ fn layout_line_items(
|
|||
fragments
|
||||
}
|
||||
|
||||
fn place_pending_floats(ifc: &mut InlineFormattingContextState, line_items: &mut Vec<LineItem>) {
|
||||
for item in line_items.into_iter() {
|
||||
match item {
|
||||
LineItem::InlineBox(box_line_item) => {
|
||||
place_pending_floats(ifc, &mut box_line_item.children);
|
||||
},
|
||||
LineItem::Float(float_line_item) => {
|
||||
if float_line_item.needs_placement {
|
||||
ifc.place_float_fragment(&mut float_line_item.fragment);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LineItem {
|
||||
TextRun(TextRunLineItem),
|
||||
InlineBox(InlineBoxLineItem),
|
||||
|
@ -1148,6 +1413,19 @@ impl LineItem {
|
|||
LineItem::Float(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn block_size(&self) -> Length {
|
||||
match self {
|
||||
LineItem::TextRun(text_run) => text_run.line_height(),
|
||||
LineItem::InlineBox(_) => {
|
||||
// TODO(mrobinson): This should get the line height from the font.
|
||||
Length::zero()
|
||||
},
|
||||
LineItem::Atomic(atomic) => atomic.size.block,
|
||||
LineItem::AbsolutelyPositioned(_) => Length::zero(),
|
||||
LineItem::Float(_) => Length::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TextRunLineItem {
|
||||
|
@ -1159,6 +1437,15 @@ struct TextRunLineItem {
|
|||
text_decoration_line: TextDecorationLine,
|
||||
}
|
||||
|
||||
fn line_height(parent_style: &Arc<ComputedValues>, font_metrics: &FontMetrics) -> Length {
|
||||
let font_size = parent_style.get_font().font_size.size.0;
|
||||
match parent_style.get_inherited_text().line_height {
|
||||
LineHeight::Normal => font_metrics.line_gap,
|
||||
LineHeight::Number(n) => font_size * n.0,
|
||||
LineHeight::Length(l) => l.0,
|
||||
}
|
||||
}
|
||||
|
||||
impl TextRunLineItem {
|
||||
fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Length) -> bool {
|
||||
if self
|
||||
|
@ -1188,14 +1475,12 @@ impl TextRunLineItem {
|
|||
index_of_last_non_whitespace.is_none()
|
||||
}
|
||||
|
||||
fn line_height(&self) -> Length {
|
||||
line_height(&self.parent_style, &self.font_metrics)
|
||||
}
|
||||
|
||||
fn layout(self, state: &mut LineItemLayoutState) -> Option<TextFragment> {
|
||||
let font_size = self.parent_style.get_font().font_size.size.0;
|
||||
let line_height = match self.parent_style.get_inherited_text().line_height {
|
||||
LineHeight::Normal => self.font_metrics.line_gap,
|
||||
LineHeight::Number(n) => font_size * n.0,
|
||||
LineHeight::Length(l) => l.0,
|
||||
};
|
||||
state.max_block_size.max_assign(line_height);
|
||||
state.max_block_size.max_assign(self.line_height());
|
||||
|
||||
// This happens after updating the `max_block_size`, because even trimmed newlines
|
||||
// should affect the height of the line.
|
||||
|
@ -1214,7 +1499,7 @@ impl TextRunLineItem {
|
|||
inline: state.inline_position - state.inline_start_of_parent,
|
||||
},
|
||||
size: Vec2 {
|
||||
block: line_height,
|
||||
block: self.line_height(),
|
||||
inline: inline_advance,
|
||||
},
|
||||
};
|
||||
|
@ -1412,7 +1697,11 @@ impl AbsolutelyPositionedLineItem {
|
|||
}
|
||||
|
||||
struct FloatLineItem {
|
||||
box_fragment: BoxFragment,
|
||||
fragment: BoxFragment,
|
||||
/// Whether or not this float Fragment has been placed yet. Fragments that
|
||||
/// do not fit on a line need to be placed after the hypothetical block start
|
||||
/// of the next line.
|
||||
needs_placement: bool,
|
||||
}
|
||||
|
||||
impl FloatLineItem {
|
||||
|
@ -1426,8 +1715,8 @@ impl FloatLineItem {
|
|||
inline: state.inline_start_of_parent,
|
||||
block: state.line_block_start,
|
||||
};
|
||||
self.box_fragment.content_rect.start_corner =
|
||||
&self.box_fragment.content_rect.start_corner - &distance_from_parent_to_ifc;
|
||||
self.box_fragment
|
||||
self.fragment.content_rect.start_corner =
|
||||
&self.fragment.content_rect.start_corner - &distance_from_parent_to_ifc;
|
||||
self.fragment
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,11 +105,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl flow_relative::Vec2<Length> {
|
||||
impl<T: Zero> flow_relative::Vec2<T> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inline: Length::zero(),
|
||||
block: Length::zero(),
|
||||
inline: T::zero(),
|
||||
block: T::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ impl flow_relative::Vec2<Option<&'_ LengthPercentage>> {
|
|||
}
|
||||
}
|
||||
|
||||
impl flow_relative::Rect<Length> {
|
||||
impl<T: Zero> flow_relative::Rect<T> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
start_corner: flow_relative::Vec2::zero(),
|
||||
|
@ -346,6 +346,17 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Zero> flow_relative::Sides<T> {
|
||||
pub(crate) fn zero() -> flow_relative::Sides<T> {
|
||||
flow_relative::Sides {
|
||||
inline_start: T::zero(),
|
||||
inline_end: T::zero(),
|
||||
block_start: T::zero(),
|
||||
block_end: T::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> flow_relative::Rect<T> {
|
||||
pub fn max_inline_position(&self) -> T
|
||||
where
|
||||
|
|
|
@ -65,6 +65,17 @@ pub(crate) struct PaddingBorderMargin {
|
|||
pub padding_border_sums: flow_relative::Vec2<Length>,
|
||||
}
|
||||
|
||||
impl PaddingBorderMargin {
|
||||
pub(crate) fn zero() -> Self {
|
||||
Self {
|
||||
padding: flow_relative::Sides::zero(),
|
||||
border: flow_relative::Sides::zero(),
|
||||
margin: flow_relative::Sides::zero(),
|
||||
padding_border_sums: flow_relative::Vec2::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ComputedValuesExt {
|
||||
fn inline_size_is_length(&self) -> bool;
|
||||
fn inline_box_offsets_are_both_non_auto(&self) -> bool;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue