pub mod construct;
pub mod inline_box;
pub mod line;
mod line_breaker;
pub mod text_run;
use std::cell::{OnceCell, RefCell};
use std::mem;
use std::rc::Rc;
use std::sync::Arc;
use app_units::{Au, MAX_AU};
use bitflags::bitflags;
use construct::InlineFormattingContextBuilder;
use fonts::{FontMetrics, GlyphStore};
use icu_segmenter::{LineBreakOptions, LineBreakStrictness, LineBreakWordOption};
use inline_box::{InlineBox, InlineBoxContainerState, InlineBoxIdentifier, InlineBoxes};
use layout_api::wrapper_traits::SharedSelection;
use line::{
AbsolutelyPositionedLineItem, AtomicLineItem, FloatLineItem, LineItem, LineItemLayout,
TextRunLineItem,
};
use line_breaker::LineBreaker;
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::ServoThreadSafeLayoutNode;
use servo_arc::Arc as ServoArc;
use style::Zero;
use style::computed_values::line_break::T as LineBreak;
use style::computed_values::text_wrap_mode::T as TextWrapMode;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::computed_values::word_break::T as WordBreak;
use style::context::{QuirksMode, SharedStyleContext};
use style::properties::ComputedValues;
use style::properties::style_structs::InheritedText;
use style::values::computed::BaselineShift;
use style::values::generics::box_::BaselineShiftKeyword;
use style::values::generics::font::LineHeight;
use style::values::specified::box_::BaselineSource;
use style::values::specified::text::TextAlignKeyword;
use style::values::specified::{AlignmentBaseline, TextAlignLast, TextJustify};
use text_run::{
TextRun, XI_LINE_BREAKING_CLASS_GL, XI_LINE_BREAKING_CLASS_WJ, XI_LINE_BREAKING_CLASS_ZWJ,
get_font_for_first_font_for_style,
};
use unicode_bidi::{BidiInfo, Level};
use xi_unicode::linebreak_property;
use super::float::{Clear, PlacementAmongFloats};
use super::{CacheableLayoutResult, IndependentFloatOrAtomicLayoutResult};
use crate::cell::{ArcRefCell, WeakRefCell};
use crate::context::LayoutContext;
use crate::dom::WeakLayoutBox;
use crate::dom_traversal::NodeAndStyleInfo;
use crate::flow::float::{FloatBox, SequentialLayoutState};
use crate::flow::inline::line::TextRunOffsets;
use crate::flow::inline::text_run::FontAndScriptInfo;
use crate::flow::{
BlockLevelBox, CollapsibleWithParentStartMargin, FloatSide, PlacementState,
compute_inline_content_sizes_for_block_level_boxes, layout_block_level_child,
};
use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
use crate::fragment_tree::{
BoxFragment, CollapsedMargin, Fragment, FragmentFlags, PositioningFragment,
};
use crate::geom::{LogicalRect, LogicalSides1D, LogicalVec2, ToLogical};
use crate::layout_box_base::LayoutBoxBase;
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin};
use crate::{ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, SharedStyle};
static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34;
#[derive(Debug, MallocSizeOf)]
pub(crate) struct InlineFormattingContext {
inline_items: Vec<InlineItem>,
inline_boxes: InlineBoxes,
text_content: String,
shared_inline_styles: SharedInlineStyles,
has_first_formatted_line: bool,
pub(super) contains_floats: bool,
is_single_line_text_input: bool,
has_right_to_left_content: bool,
#[ignore_malloc_size_of = "This is stored primarily in the DOM"]
shared_selection: Option<SharedSelection>,
}
#[derive(Clone, Debug, MallocSizeOf)]
pub(crate) struct SharedInlineStyles {
pub style: SharedStyle,
pub selected: SharedStyle,
}
impl SharedInlineStyles {
pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
self.style.ptr_eq(&other.style) && self.selected.ptr_eq(&other.selected)
}
pub(crate) fn from_info_and_context(info: &NodeAndStyleInfo, context: &LayoutContext) -> Self {
Self {
style: SharedStyle::new(info.style.clone()),
selected: SharedStyle::new(info.node.selected_style(&context.style_context)),
}
}
}
impl BlockLevelBox {
fn layout_into_line_items(&self, layout: &mut InlineFormattingContextLayout) {
layout.process_soft_wrap_opportunity();
layout.commit_current_segment_to_line();
layout.process_line_break(true);
layout.current_line.for_block_level = true;
let fragment = layout_block_level_child(
layout.layout_context,
layout.positioning_context,
self,
layout.sequential_layout_state.as_deref_mut(),
&mut layout.placement_state,
LogicalSides1D::new(false, false),
true,
);
let Fragment::Box(fragment) = fragment else {
unreachable!("The fragment should be a Fragment::Box()");
};
layout.depends_on_block_constraints |= fragment.borrow().base.flags.contains(
FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
);
layout.push_line_item_to_unbreakable_segment(LineItem::BlockLevel(
layout.current_inline_box_identifier(),
fragment,
));
layout.commit_current_segment_to_line();
layout.process_line_break(true);
layout.current_line.for_block_level = false;
}
}
#[derive(Clone, Debug, MallocSizeOf)]
pub(crate) enum InlineItem {
StartInlineBox(ArcRefCell<InlineBox>),
EndInlineBox,
TextRun(ArcRefCell<TextRun>),
OutOfFlowAbsolutelyPositionedBox(
ArcRefCell<AbsolutelyPositionedBox>,
usize,
),
OutOfFlowFloatBox(ArcRefCell<FloatBox>),
Atomic(
ArcRefCell<IndependentFormattingContext>,
usize,
Level,
),
BlockLevel(ArcRefCell<BlockLevelBox>),
}
impl InlineItem {
pub(crate) fn repair_style(
&self,
context: &SharedStyleContext,
node: &ServoThreadSafeLayoutNode,
new_style: &ServoArc<ComputedValues>,
) {
match self {
InlineItem::StartInlineBox(inline_box) => {
inline_box
.borrow_mut()
.repair_style(context, node, new_style);
},
InlineItem::EndInlineBox => {},
InlineItem::TextRun(..) => {},
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => positioned_box
.borrow_mut()
.context
.repair_style(context, node, new_style),
InlineItem::OutOfFlowFloatBox(float_box) => float_box
.borrow_mut()
.contents
.repair_style(context, node, new_style),
InlineItem::Atomic(atomic, ..) => {
atomic.borrow_mut().repair_style(context, node, new_style)
},
InlineItem::BlockLevel(block_level) => block_level
.borrow_mut()
.repair_style(context, node, new_style),
}
}
pub(crate) fn with_base<T>(&self, callback: impl FnOnce(&LayoutBoxBase) -> T) -> T {
match self {
InlineItem::StartInlineBox(inline_box) => callback(&inline_box.borrow().base),
InlineItem::EndInlineBox | InlineItem::TextRun(..) => {
unreachable!("Should never have these kind of fragments attached to a DOM node")
},
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
callback(&positioned_box.borrow().context.base)
},
InlineItem::OutOfFlowFloatBox(float_box) => callback(&float_box.borrow().contents.base),
InlineItem::Atomic(independent_formatting_context, ..) => {
callback(&independent_formatting_context.borrow().base)
},
InlineItem::BlockLevel(block_level) => block_level.borrow().with_base(callback),
}
}
pub(crate) fn with_base_mut<T>(&self, callback: impl FnOnce(&mut LayoutBoxBase) -> T) -> T {
match self {
InlineItem::StartInlineBox(inline_box) => callback(&mut inline_box.borrow_mut().base),
InlineItem::EndInlineBox | InlineItem::TextRun(..) => {
unreachable!("Should never have these kind of fragments attached to a DOM node")
},
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
callback(&mut positioned_box.borrow_mut().context.base)
},
InlineItem::OutOfFlowFloatBox(float_box) => {
callback(&mut float_box.borrow_mut().contents.base)
},
InlineItem::Atomic(independent_formatting_context, ..) => {
callback(&mut independent_formatting_context.borrow_mut().base)
},
InlineItem::BlockLevel(block_level) => block_level.borrow_mut().with_base_mut(callback),
}
}
pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
match self {
Self::StartInlineBox(_) | InlineItem::EndInlineBox => {
},
Self::TextRun(_) => {
},
Self::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
positioned_box.borrow().context.attached_to_tree(layout_box)
},
Self::OutOfFlowFloatBox(float_box) => {
float_box.borrow().contents.attached_to_tree(layout_box)
},
Self::Atomic(atomic, ..) => atomic.borrow().attached_to_tree(layout_box),
Self::BlockLevel(block_level) => block_level.borrow().attached_to_tree(layout_box),
}
}
pub(crate) fn downgrade(&self) -> WeakInlineItem {
match self {
Self::StartInlineBox(inline_box) => {
WeakInlineItem::StartInlineBox(inline_box.downgrade())
},
Self::EndInlineBox => WeakInlineItem::EndInlineBox,
Self::TextRun(text_run) => WeakInlineItem::TextRun(text_run.downgrade()),
Self::OutOfFlowAbsolutelyPositionedBox(positioned_box, offset_in_text) => {
WeakInlineItem::OutOfFlowAbsolutelyPositionedBox(
positioned_box.downgrade(),
*offset_in_text,
)
},
Self::OutOfFlowFloatBox(float_box) => {
WeakInlineItem::OutOfFlowFloatBox(float_box.downgrade())
},
Self::Atomic(atomic, offset_in_text, bidi_level) => {
WeakInlineItem::Atomic(atomic.downgrade(), *offset_in_text, *bidi_level)
},
Self::BlockLevel(block_level) => WeakInlineItem::BlockLevel(block_level.downgrade()),
}
}
}
#[derive(Clone, Debug, MallocSizeOf)]
pub(crate) enum WeakInlineItem {
StartInlineBox(WeakRefCell<InlineBox>),
EndInlineBox,
TextRun(WeakRefCell<TextRun>),
OutOfFlowAbsolutelyPositionedBox(
WeakRefCell<AbsolutelyPositionedBox>,
usize,
),
OutOfFlowFloatBox(WeakRefCell<FloatBox>),
Atomic(
WeakRefCell<IndependentFormattingContext>,
usize,
Level,
),
BlockLevel(WeakRefCell<BlockLevelBox>),
}
impl WeakInlineItem {
pub(crate) fn upgrade(&self) -> Option<InlineItem> {
Some(match self {
Self::StartInlineBox(inline_box) => InlineItem::StartInlineBox(inline_box.upgrade()?),
Self::EndInlineBox => InlineItem::EndInlineBox,
Self::TextRun(text_run) => InlineItem::TextRun(text_run.upgrade()?),
Self::OutOfFlowAbsolutelyPositionedBox(positioned_box, offset_in_text) => {
InlineItem::OutOfFlowAbsolutelyPositionedBox(
positioned_box.upgrade()?,
*offset_in_text,
)
},
Self::OutOfFlowFloatBox(float_box) => {
InlineItem::OutOfFlowFloatBox(float_box.upgrade()?)
},
Self::Atomic(atomic, offset_in_text, bidi_level) => {
InlineItem::Atomic(atomic.upgrade()?, *offset_in_text, *bidi_level)
},
Self::BlockLevel(block_level) => InlineItem::BlockLevel(block_level.upgrade()?),
})
}
}
struct LineUnderConstruction {
start_position: LogicalVec2<Au>,
inline_position: Au,
max_block_size: LineBlockSizes,
has_content: bool,
has_inline_pbm: bool,
has_floats_waiting_to_be_placed: bool,
placement_among_floats: OnceCell<LogicalRect<Au>>,
line_items: Vec<LineItem>,
for_block_level: bool,
}
impl LineUnderConstruction {
fn new(start_position: LogicalVec2<Au>) -> Self {
Self {
inline_position: start_position.inline,
start_position,
max_block_size: LineBlockSizes::zero(),
has_content: false,
has_inline_pbm: false,
has_floats_waiting_to_be_placed: false,
placement_among_floats: OnceCell::new(),
line_items: Vec::new(),
for_block_level: false,
}
}
fn replace_placement_among_floats(&mut self, new_placement: LogicalRect<Au>) {
self.placement_among_floats.take();
let _ = self.placement_among_floats.set(new_placement);
}
fn trim_trailing_whitespace(&mut self) -> Au {
let mut whitespace_trimmed = Au::zero();
for item in self.line_items.iter_mut().rev() {
if !item.trim_whitespace_at_end(&mut whitespace_trimmed) {
break;
}
}
whitespace_trimmed
}
fn count_justification_opportunities(&self) -> usize {
self.line_items
.iter()
.filter_map(|item| match item {
LineItem::TextRun(_, text_run) => Some(
text_run
.text
.iter()
.map(|glyph_store| glyph_store.total_word_separators())
.sum::<usize>(),
),
_ => None,
})
.sum()
}
fn is_phantom(&self) -> bool {
!self.has_content && !self.has_inline_pbm
}
}
#[derive(Clone, Debug)]
struct BaselineRelativeSize {
ascent: Au,
descent: Au,
}
impl BaselineRelativeSize {
fn zero() -> Self {
Self {
ascent: Au::zero(),
descent: Au::zero(),
}
}
fn max(&self, other: &Self) -> Self {
BaselineRelativeSize {
ascent: self.ascent.max(other.ascent),
descent: self.descent.max(other.descent),
}
}
fn adjust_for_nested_baseline_offset(&mut self, baseline_offset: Au) {
self.ascent -= baseline_offset;
self.descent += baseline_offset;
}
}
#[derive(Clone, Debug)]
struct LineBlockSizes {
line_height: Au,
baseline_relative_size_for_line_height: Option<BaselineRelativeSize>,
size_for_baseline_positioning: BaselineRelativeSize,
}
impl LineBlockSizes {
fn zero() -> Self {
LineBlockSizes {
line_height: Au::zero(),
baseline_relative_size_for_line_height: None,
size_for_baseline_positioning: BaselineRelativeSize::zero(),
}
}
fn resolve(&self) -> Au {
let height_from_ascent_and_descent = self
.baseline_relative_size_for_line_height
.as_ref()
.map(|size| (size.ascent + size.descent).abs())
.unwrap_or_else(Au::zero);
self.line_height.max(height_from_ascent_and_descent)
}
fn max(&self, other: &LineBlockSizes) -> LineBlockSizes {
let baseline_relative_size = match (
self.baseline_relative_size_for_line_height.as_ref(),
other.baseline_relative_size_for_line_height.as_ref(),
) {
(Some(our_size), Some(other_size)) => Some(our_size.max(other_size)),
(our_size, other_size) => our_size.or(other_size).cloned(),
};
Self {
line_height: self.line_height.max(other.line_height),
baseline_relative_size_for_line_height: baseline_relative_size,
size_for_baseline_positioning: self
.size_for_baseline_positioning
.max(&other.size_for_baseline_positioning),
}
}
fn max_assign(&mut self, other: &LineBlockSizes) {
*self = self.max(other);
}
fn adjust_for_baseline_offset(&mut self, baseline_offset: Au) {
if let Some(size) = self.baseline_relative_size_for_line_height.as_mut() {
size.adjust_for_nested_baseline_offset(baseline_offset)
}
self.size_for_baseline_positioning
.adjust_for_nested_baseline_offset(baseline_offset);
}
fn find_baseline_offset(&self) -> Au {
match self.baseline_relative_size_for_line_height.as_ref() {
Some(size) => size.ascent,
None => {
let leading = self.resolve() -
(self.size_for_baseline_positioning.ascent +
self.size_for_baseline_positioning.descent);
leading.scale_by(0.5) + self.size_for_baseline_positioning.ascent
},
}
}
}
struct UnbreakableSegmentUnderConstruction {
inline_size: Au,
max_block_size: LineBlockSizes,
line_items: Vec<LineItem>,
inline_box_hierarchy_depth: Option<usize>,
has_content: bool,
has_inline_pbm: bool,
trailing_whitespace_size: Au,
}
impl UnbreakableSegmentUnderConstruction {
fn new() -> Self {
Self {
inline_size: Au::zero(),
max_block_size: LineBlockSizes {
line_height: Au::zero(),
baseline_relative_size_for_line_height: None,
size_for_baseline_positioning: BaselineRelativeSize::zero(),
},
line_items: Vec::new(),
inline_box_hierarchy_depth: None,
has_content: false,
has_inline_pbm: false,
trailing_whitespace_size: Au::zero(),
}
}
fn reset(&mut self) {
assert!(self.line_items.is_empty()); self.inline_size = Au::zero();
self.max_block_size = LineBlockSizes::zero();
self.inline_box_hierarchy_depth = None;
self.has_content = false;
self.has_inline_pbm = false;
self.trailing_whitespace_size = Au::zero();
}
fn push_line_item(&mut self, line_item: LineItem, inline_box_hierarchy_depth: usize) {
if self.line_items.is_empty() {
self.inline_box_hierarchy_depth = Some(inline_box_hierarchy_depth);
}
self.line_items.push(line_item);
}
fn trim_leading_whitespace(&mut self) {
let mut whitespace_trimmed = Au::zero();
for item in self.line_items.iter_mut() {
if !item.trim_whitespace_at_start(&mut whitespace_trimmed) {
break;
}
}
self.inline_size -= whitespace_trimmed;
}
fn is_phantom(&self) -> bool {
!self.has_content && !self.has_inline_pbm
}
}
bitflags! {
struct InlineContainerStateFlags: u8 {
const CREATE_STRUT = 0b0001;
const IS_SINGLE_LINE_TEXT_INPUT = 0b0010;
}
}
struct InlineContainerState {
style: ServoArc<ComputedValues>,
flags: InlineContainerStateFlags,
has_content: RefCell<bool>,
strut_block_sizes: LineBlockSizes,
nested_strut_block_sizes: LineBlockSizes,
pub baseline_offset: Au,
font_metrics: Arc<FontMetrics>,
}
struct InlineFormattingContextLayout<'layout_data> {
positioning_context: &'layout_data mut PositioningContext,
placement_state: PlacementState<'layout_data>,
sequential_layout_state: Option<&'layout_data mut SequentialLayoutState>,
layout_context: &'layout_data LayoutContext<'layout_data>,
ifc: &'layout_data InlineFormattingContext,
root_nesting_level: InlineContainerState,
inline_box_state_stack: Vec<Rc<InlineBoxContainerState>>,
inline_box_states: Vec<Rc<InlineBoxContainerState>>,
fragments: Vec<Fragment>,
current_line: LineUnderConstruction,
current_line_segment: UnbreakableSegmentUnderConstruction,
linebreak_before_new_content: bool,
deferred_br_clear: Clear,
pub have_deferred_soft_wrap_opportunity: bool,
depends_on_block_constraints: bool,
white_space_collapse: WhiteSpaceCollapse,
text_wrap_mode: TextWrapMode,
}
impl InlineFormattingContextLayout<'_> {
fn current_inline_container_state(&self) -> &InlineContainerState {
match self.inline_box_state_stack.last() {
Some(inline_box_state) => &inline_box_state.base,
None => &self.root_nesting_level,
}
}
fn current_inline_box_identifier(&self) -> Option<InlineBoxIdentifier> {
self.inline_box_state_stack
.last()
.map(|state| state.identifier)
}
fn current_line_max_block_size_including_nested_containers(&self) -> LineBlockSizes {
self.current_inline_container_state()
.nested_strut_block_sizes
.max(&self.current_line.max_block_size)
}
fn current_line_block_start_considering_placement_among_floats(&self) -> Au {
self.current_line.placement_among_floats.get().map_or(
self.current_line.start_position.block,
|placement_among_floats| placement_among_floats.start_corner.block,
)
}
fn propagate_current_nesting_level_white_space_style(&mut self) {
let style = match self.inline_box_state_stack.last() {
Some(inline_box_state) => &inline_box_state.base.style,
None => self.placement_state.containing_block.style,
};
let style_text = style.get_inherited_text();
self.white_space_collapse = style_text.white_space_collapse;
self.text_wrap_mode = style_text.text_wrap_mode;
}
fn processing_br_element(&self) -> bool {
self.inline_box_state_stack.last().is_some_and(|state| {
state
.base_fragment_info
.flags
.contains(FragmentFlags::IS_BR_ELEMENT)
})
}
fn start_inline_box(&mut self, inline_box: &InlineBox) {
let containing_block = self.containing_block();
let inline_box_state = InlineBoxContainerState::new(
inline_box,
containing_block,
self.layout_context,
self.current_inline_container_state(),
inline_box
.default_font
.as_ref()
.map(|font| font.metrics.clone()),
);
self.depends_on_block_constraints |= inline_box
.base
.style
.depends_on_block_constraints_due_to_relative_positioning(
containing_block.style.writing_mode,
);
if inline_box_state
.base_fragment_info
.flags
.contains(FragmentFlags::IS_BR_ELEMENT) &&
self.deferred_br_clear == Clear::None
{
self.deferred_br_clear = Clear::from_style_and_container_writing_mode(
&inline_box_state.base.style,
self.containing_block().style.writing_mode,
);
}
let padding = inline_box_state.pbm.padding.inline_start;
let border = inline_box_state.pbm.border.inline_start;
let margin = inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
if !padding.is_zero() || !border.is_zero() || !margin.is_zero() {
self.current_line_segment.has_inline_pbm = true;
}
self.current_line_segment.inline_size += padding + border + margin;
self.current_line_segment
.line_items
.push(LineItem::InlineStartBoxPaddingBorderMargin(
inline_box.identifier,
));
let inline_box_state = Rc::new(inline_box_state);
assert_eq!(
self.inline_box_states.len(),
inline_box.identifier.index_in_inline_boxes as usize
);
self.inline_box_states.push(inline_box_state.clone());
self.inline_box_state_stack.push(inline_box_state);
}
fn finish_inline_box(&mut self) {
let inline_box_state = match self.inline_box_state_stack.pop() {
Some(inline_box_state) => inline_box_state,
None => return, };
self.current_line_segment
.max_block_size
.max_assign(&inline_box_state.base.nested_strut_block_sizes);
if *inline_box_state.base.has_content.borrow() {
self.propagate_current_nesting_level_white_space_style();
}
let padding = inline_box_state.pbm.padding.inline_end;
let border = inline_box_state.pbm.border.inline_end;
let margin = inline_box_state.pbm.margin.inline_end.auto_is(Au::zero);
if !padding.is_zero() || !border.is_zero() || !margin.is_zero() {
self.current_line_segment.has_inline_pbm = true;
}
self.current_line_segment.inline_size += padding + border + margin;
self.current_line_segment
.line_items
.push(LineItem::InlineEndBoxPaddingBorderMargin(
inline_box_state.identifier,
))
}
fn finish_last_line(&mut self) {
self.process_soft_wrap_opportunity();
self.commit_current_segment_to_line();
self.finish_current_line_and_reset(true );
}
fn finish_current_line_and_reset(&mut self, last_line_or_forced_line_break: bool) {
let whitespace_trimmed = self.current_line.trim_trailing_whitespace();
let (inline_start_position, justification_adjustment) = self
.calculate_current_line_inline_start_and_justification_adjustment(
whitespace_trimmed,
last_line_or_forced_line_break,
);
let is_phantom_line = self.current_line.is_phantom();
if !is_phantom_line {
self.current_line.start_position.block += self.placement_state.current_margin.solve();
self.placement_state.current_margin = CollapsedMargin::zero();
}
let block_start_position =
self.current_line_block_start_considering_placement_among_floats();
let effective_block_advance = if is_phantom_line {
LineBlockSizes::zero()
} else {
self.current_line_max_block_size_including_nested_containers()
};
let resolved_block_advance = effective_block_advance.resolve();
let block_end_position = if self.current_line.for_block_level {
self.placement_state.current_block_direction_position
} else {
let mut block_end_position = block_start_position + resolved_block_advance;
if let Some(sequential_layout_state) = self.sequential_layout_state.as_mut() {
if !is_phantom_line {
sequential_layout_state.collapse_margins();
}
let increment = block_end_position - self.current_line.start_position.block;
sequential_layout_state.advance_block_position(increment);
if let Some(clearance) = sequential_layout_state
.calculate_clearance(self.deferred_br_clear, &CollapsedMargin::zero())
{
sequential_layout_state.advance_block_position(clearance);
block_end_position += clearance;
};
self.deferred_br_clear = Clear::None;
}
block_end_position
};
let mut line_to_layout = std::mem::replace(
&mut self.current_line,
LineUnderConstruction::new(LogicalVec2 {
inline: Au::zero(),
block: block_end_position,
}),
);
if !line_to_layout.for_block_level {
self.placement_state.current_block_direction_position = block_end_position;
}
if line_to_layout.has_floats_waiting_to_be_placed {
place_pending_floats(self, &mut line_to_layout.line_items);
}
let start_position = LogicalVec2 {
block: block_start_position,
inline: inline_start_position,
};
let baseline_offset = effective_block_advance.find_baseline_offset();
let start_positioning_context_length = self.positioning_context.len();
let fragments = LineItemLayout::layout_line_items(
self,
line_to_layout.line_items,
start_position,
&effective_block_advance,
justification_adjustment,
is_phantom_line,
);
if !is_phantom_line {
let baseline = baseline_offset + block_start_position;
self.placement_state
.inflow_baselines
.first
.get_or_insert(baseline);
self.placement_state.inflow_baselines.last = Some(baseline);
self.placement_state
.next_in_flow_margin_collapses_with_parent_start_margin = false;
}
if fragments.is_empty() &&
self.positioning_context.len() == start_positioning_context_length
{
return;
}
let start_corner = LogicalVec2 {
inline: Au::zero(),
block: block_start_position,
};
let logical_origin_in_physical_coordinates =
start_corner.to_physical_vector(self.containing_block().style.writing_mode);
self.positioning_context
.adjust_static_position_of_hoisted_fragments_with_offset(
&logical_origin_in_physical_coordinates,
start_positioning_context_length,
);
let containing_block = self.containing_block();
let physical_line_rect = LogicalRect {
start_corner,
size: LogicalVec2 {
inline: containing_block.size.inline,
block: effective_block_advance.resolve(),
},
}
.as_physical(Some(containing_block));
self.fragments
.push(Fragment::Positioning(PositioningFragment::new_anonymous(
self.root_nesting_level.style.clone(),
physical_line_rect,
fragments,
)));
}
fn calculate_current_line_inline_start_and_justification_adjustment(
&self,
whitespace_trimmed: Au,
last_line_or_forced_line_break: bool,
) -> (Au, Au) {
enum TextAlign {
Start,
Center,
End,
}
let containing_block = self.containing_block();
let style = containing_block.style;
let mut text_align_keyword = style.clone_text_align();
if last_line_or_forced_line_break {
text_align_keyword = match style.clone_text_align_last() {
TextAlignLast::Auto if text_align_keyword == TextAlignKeyword::Justify => {
TextAlignKeyword::Start
},
TextAlignLast::Auto => text_align_keyword,
TextAlignLast::Start => TextAlignKeyword::Start,
TextAlignLast::End => TextAlignKeyword::End,
TextAlignLast::Left => TextAlignKeyword::Left,
TextAlignLast::Right => TextAlignKeyword::Right,
TextAlignLast::Center => TextAlignKeyword::Center,
TextAlignLast::Justify => TextAlignKeyword::Justify,
};
}
let text_align = match text_align_keyword {
TextAlignKeyword::Start => TextAlign::Start,
TextAlignKeyword::Center | TextAlignKeyword::MozCenter => TextAlign::Center,
TextAlignKeyword::End => TextAlign::End,
TextAlignKeyword::Left | TextAlignKeyword::MozLeft => {
if style.writing_mode.line_left_is_inline_start() {
TextAlign::Start
} else {
TextAlign::End
}
},
TextAlignKeyword::Right | TextAlignKeyword::MozRight => {
if style.writing_mode.line_left_is_inline_start() {
TextAlign::End
} else {
TextAlign::Start
}
},
TextAlignKeyword::Justify => TextAlign::Start,
};
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 => (Au::zero(), containing_block.size.inline),
};
let text_indent = self.current_line.start_position.inline;
let line_length = self.current_line.inline_position - whitespace_trimmed - text_indent;
let adjusted_line_start = 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)
.scale_by(0.5)
.max(text_indent),
};
let text_justify = containing_block.style.clone_text_justify();
let justification_adjustment = match (text_align_keyword, text_justify) {
(TextAlignKeyword::Justify, TextJustify::None) => Au::zero(),
(TextAlignKeyword::Justify, _) => {
match self.current_line.count_justification_opportunities() {
0 => Au::zero(),
num_justification_opportunities => {
(available_space - text_indent - line_length)
.scale_by(1. / num_justification_opportunities as f32)
},
}
},
_ => Au::zero(),
};
let justification_adjustment = justification_adjustment.max(Au::zero());
(adjusted_line_start, justification_adjustment)
}
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,
self.placement_state.containing_block,
CollapsedMargin::zero(),
block_offset_from_containining_block_top,
);
}
fn place_float_line_item_for_commit_to_line(
&mut self,
float_item: &mut FloatLineItem,
line_inline_size_without_trailing_whitespace: Au,
) {
let containing_block = self.containing_block();
let mut float_fragment = float_item.fragment.borrow_mut();
let logical_margin_rect_size = float_fragment
.margin_rect()
.size
.to_logical(containing_block.style.writing_mode);
let inline_size = logical_margin_rect_size.inline.max(Au::zero());
let available_inline_size = match self.current_line.placement_among_floats.get() {
Some(placement_among_floats) => placement_among_floats.size.inline,
None => containing_block.size.inline,
} - line_inline_size_without_trailing_whitespace;
let has_content = self.current_line.has_content || self.current_line_segment.has_content;
let fits_on_line = !has_content || inline_size <= available_inline_size;
let needs_placement_later =
self.current_line.has_floats_waiting_to_be_placed || !fits_on_line;
if needs_placement_later {
self.current_line.has_floats_waiting_to_be_placed = true;
} else {
self.place_float_fragment(&mut float_fragment);
float_item.needs_placement = false;
}
let new_placement = self.place_line_among_floats(&LogicalVec2 {
inline: line_inline_size_without_trailing_whitespace,
block: self.current_line.max_block_size.resolve(),
});
self.current_line
.replace_placement_among_floats(new_placement);
}
fn place_line_among_floats(&self, potential_line_size: &LogicalVec2<Au>) -> LogicalRect<Au> {
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 = LogicalVec2 {
inline: sequential_layout_state
.floats
.containing_block_info
.inline_start,
block: sequential_layout_state.current_containing_block_offset(),
};
let ceiling = self.current_line_block_start_considering_placement_among_floats();
let mut placement = PlacementAmongFloats::new(
&sequential_layout_state.floats,
ceiling + ifc_offset_in_float_container.block,
LogicalVec2 {
inline: potential_line_size.inline,
block: potential_line_size.block,
},
&PaddingBorderMargin::zero(),
);
let mut placement_rect = placement.place();
placement_rect.start_corner -= ifc_offset_in_float_container;
placement_rect
}
fn new_potential_line_size_causes_line_break(
&mut self,
potential_line_size: &LogicalVec2<Au>,
) -> bool {
let containing_block = self.containing_block();
let available_line_space = if self.sequential_layout_state.is_some() {
self.current_line
.placement_among_floats
.get_or_init(|| self.place_line_among_floats(potential_line_size))
.size
} else {
LogicalVec2 {
inline: containing_block.size.inline,
block: MAX_AU,
}
};
let inline_would_overflow = potential_line_size.inline > available_line_space.inline;
let block_would_overflow = potential_line_size.block > available_line_space.block;
let can_break = self.current_line.has_content;
if !can_break {
if self.sequential_layout_state.is_some() &&
(inline_would_overflow || block_would_overflow)
{
let new_placement = self.place_line_among_floats(potential_line_size);
self.current_line
.replace_placement_among_floats(new_placement);
}
return false;
}
if potential_line_size.inline > containing_block.size.inline {
return true;
}
if block_would_overflow {
assert!(self.sequential_layout_state.is_some());
let new_placement = self.place_line_among_floats(potential_line_size);
if new_placement.start_corner.block !=
self.current_line_block_start_considering_placement_among_floats()
{
return true;
} else {
self.current_line
.replace_placement_among_floats(new_placement);
return false;
}
}
inline_would_overflow
}
fn defer_forced_line_break(&mut self) {
if !self.unbreakable_segment_fits_on_line() {
self.process_line_break(false );
}
self.linebreak_before_new_content = true;
let line_is_empty =
!self.current_line_segment.has_content && !self.current_line.has_content;
if !self.processing_br_element() || line_is_empty {
let strut_size = self
.current_inline_container_state()
.strut_block_sizes
.clone();
self.update_unbreakable_segment_for_new_content(
&strut_size,
Au::zero(),
SegmentContentFlags::empty(),
);
}
}
fn possibly_flush_deferred_forced_line_break(&mut self) {
if !self.linebreak_before_new_content {
return;
}
self.commit_current_segment_to_line();
self.process_line_break(true );
self.linebreak_before_new_content = false;
}
fn push_line_item_to_unbreakable_segment(&mut self, line_item: LineItem) {
self.current_line_segment
.push_line_item(line_item, self.inline_box_state_stack.len());
}
fn push_glyph_store_to_unbreakable_segment(
&mut self,
glyph_store: Arc<GlyphStore>,
text_run: &TextRun,
info: &Arc<FontAndScriptInfo>,
offsets: Option<TextRunOffsets>,
) {
let inline_advance = glyph_store.total_advance();
let flags = if glyph_store.is_whitespace() {
SegmentContentFlags::from(text_run.inline_styles.style.borrow().get_inherited_text())
} else {
SegmentContentFlags::empty()
};
let mut block_contribution = LineBlockSizes::zero();
let quirks_mode = self.layout_context.style_context.quirks_mode() != QuirksMode::NoQuirks;
if quirks_mode && !flags.is_collapsible_whitespace() {
block_contribution.max_assign(&self.current_inline_container_state().strut_block_sizes);
}
let font_metrics = &info.font.metrics;
if self
.current_inline_container_state()
.font_metrics
.block_metrics_meaningfully_differ(font_metrics)
{
let container_state = self.current_inline_container_state();
let baseline_shift = effective_baseline_shift(
&container_state.style,
self.inline_box_state_stack.last().map(|c| &c.base),
);
let mut font_block_conribution = container_state.get_block_size_contribution(
baseline_shift,
font_metrics,
&container_state.font_metrics,
);
font_block_conribution.adjust_for_baseline_offset(container_state.baseline_offset);
block_contribution.max_assign(&font_block_conribution);
}
self.update_unbreakable_segment_for_new_content(&block_contribution, inline_advance, flags);
let current_inline_box_identifier = self.current_inline_box_identifier();
if let Some(LineItem::TextRun(inline_box_identifier, line_item)) =
self.current_line_segment.line_items.last_mut()
{
if *inline_box_identifier == current_inline_box_identifier &&
line_item.merge_if_possible(
info,
&glyph_store,
&offsets,
&text_run.inline_styles,
)
{
return;
}
}
self.push_line_item_to_unbreakable_segment(LineItem::TextRun(
current_inline_box_identifier,
TextRunLineItem {
text: vec![glyph_store],
base_fragment_info: text_run.base_fragment_info,
inline_styles: text_run.inline_styles.clone(),
info: info.clone(),
offsets: offsets.map(Box::new),
is_empty_for_text_cursor: false,
},
));
}
fn possibly_push_empty_text_run_to_unbreakable_segment(
&mut self,
text_run: &TextRun,
info: &Arc<FontAndScriptInfo>,
offsets: Option<TextRunOffsets>,
) {
if offsets.is_none() || self.current_line_segment.has_content {
return;
}
self.push_line_item_to_unbreakable_segment(LineItem::TextRun(
self.current_inline_box_identifier(),
TextRunLineItem {
text: Default::default(),
base_fragment_info: text_run.base_fragment_info,
inline_styles: text_run.inline_styles.clone(),
info: info.clone(),
offsets: offsets.map(Box::new),
is_empty_for_text_cursor: true,
},
));
self.current_line_segment.has_content = true;
}
fn update_unbreakable_segment_for_new_content(
&mut self,
block_sizes_of_content: &LineBlockSizes,
inline_size: Au,
flags: SegmentContentFlags,
) {
if flags.is_collapsible_whitespace() || flags.is_wrappable_and_hangable() {
self.current_line_segment.trailing_whitespace_size = inline_size;
} else {
self.current_line_segment.trailing_whitespace_size = Au::zero();
}
if !flags.is_collapsible_whitespace() {
self.current_line_segment.has_content = true;
}
let container_max_block_size = &self
.current_inline_container_state()
.nested_strut_block_sizes
.clone();
self.current_line_segment
.max_block_size
.max_assign(container_max_block_size);
self.current_line_segment
.max_block_size
.max_assign(block_sizes_of_content);
self.current_line_segment.inline_size += inline_size;
*self
.current_inline_container_state()
.has_content
.borrow_mut() = true;
self.propagate_current_nesting_level_white_space_style();
}
fn process_line_break(&mut self, forced_line_break: bool) {
self.current_line_segment.trim_leading_whitespace();
self.finish_current_line_and_reset(forced_line_break);
}
fn unbreakable_segment_fits_on_line(&mut self) -> bool {
let potential_line_size = LogicalVec2 {
inline: self.current_line.inline_position + self.current_line_segment.inline_size -
self.current_line_segment.trailing_whitespace_size,
block: self
.current_line_max_block_size_including_nested_containers()
.max(&self.current_line_segment.max_block_size)
.resolve(),
};
!self.new_potential_line_size_causes_line_break(&potential_line_size)
}
fn process_soft_wrap_opportunity(&mut self) {
if self.current_line_segment.line_items.is_empty() {
return;
}
if self.text_wrap_mode == TextWrapMode::Nowrap {
return;
}
if !self.unbreakable_segment_fits_on_line() {
self.process_line_break(false );
}
self.commit_current_segment_to_line();
}
fn commit_current_segment_to_line(&mut self) {
if self.current_line_segment.line_items.is_empty() && !self.current_line_segment.has_content
{
return;
}
if !self.current_line.has_content {
self.current_line_segment.trim_leading_whitespace();
}
self.current_line.inline_position += self.current_line_segment.inline_size;
self.current_line.max_block_size = self
.current_line_max_block_size_including_nested_containers()
.max(&self.current_line_segment.max_block_size);
let line_inline_size_without_trailing_whitespace =
self.current_line.inline_position - self.current_line_segment.trailing_whitespace_size;
let mut segment_items = mem::take(&mut self.current_line_segment.line_items);
for item in segment_items.iter_mut() {
if let LineItem::Float(_, float_item) = item {
self.place_float_line_item_for_commit_to_line(
float_item,
line_inline_size_without_trailing_whitespace,
);
}
}
if self.current_line.line_items.is_empty() {
let will_break = self.new_potential_line_size_causes_line_break(&LogicalVec2 {
inline: line_inline_size_without_trailing_whitespace,
block: self.current_line_segment.max_block_size.resolve(),
});
assert!(!will_break);
}
self.current_line.line_items.extend(segment_items);
self.current_line.has_content |= self.current_line_segment.has_content;
self.current_line.has_inline_pbm |= self.current_line_segment.has_inline_pbm;
self.current_line_segment.reset();
}
#[inline]
fn containing_block(&self) -> &ContainingBlock<'_> {
self.placement_state.containing_block
}
}
bitflags! {
struct SegmentContentFlags: u8 {
const COLLAPSIBLE_WHITESPACE = 0b00000001;
const WRAPPABLE_AND_HANGABLE_WHITESPACE = 0b00000010;
}
}
impl SegmentContentFlags {
fn is_collapsible_whitespace(&self) -> bool {
self.contains(Self::COLLAPSIBLE_WHITESPACE)
}
fn is_wrappable_and_hangable(&self) -> bool {
self.contains(Self::WRAPPABLE_AND_HANGABLE_WHITESPACE)
}
}
impl From<&InheritedText> for SegmentContentFlags {
fn from(style_text: &InheritedText) -> Self {
let mut flags = Self::empty();
if !matches!(
style_text.white_space_collapse,
WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
) {
flags.insert(Self::COLLAPSIBLE_WHITESPACE);
}
if style_text.text_wrap_mode == TextWrapMode::Wrap &&
style_text.white_space_collapse != WhiteSpaceCollapse::BreakSpaces
{
flags.insert(Self::WRAPPABLE_AND_HANGABLE_WHITESPACE);
}
flags
}
}
impl InlineFormattingContext {
#[servo_tracing::instrument(name = "InlineFormattingContext::new_with_builder", skip_all)]
fn new_with_builder(
mut builder: InlineFormattingContextBuilder,
layout_context: &LayoutContext,
has_first_formatted_line: bool,
is_single_line_text_input: bool,
starting_bidi_level: Level,
) -> Self {
let text_content: String = builder.text_segments.into_iter().collect();
let bidi_info = BidiInfo::new(&text_content, Some(starting_bidi_level));
let has_right_to_left_content = bidi_info.has_rtl();
let shared_inline_styles = builder
.shared_inline_styles_stack
.last()
.expect("Should have at least one SharedInlineStyle for the root of an IFC")
.clone();
let (word_break, line_break) = {
let styles = shared_inline_styles.style.borrow();
let text_style = styles.get_inherited_text();
(text_style.word_break, text_style.line_break)
};
let mut options = LineBreakOptions::default();
options.strictness = match line_break {
LineBreak::Loose => LineBreakStrictness::Loose,
LineBreak::Normal => LineBreakStrictness::Normal,
LineBreak::Strict => LineBreakStrictness::Strict,
LineBreak::Anywhere => LineBreakStrictness::Anywhere,
LineBreak::Auto => LineBreakStrictness::Normal,
};
options.word_option = match word_break {
WordBreak::Normal => LineBreakWordOption::Normal,
WordBreak::BreakAll => LineBreakWordOption::BreakAll,
WordBreak::KeepAll => LineBreakWordOption::KeepAll,
};
options.ja_zh = false;
let mut new_linebreaker = LineBreaker::new(text_content.as_str(), options);
for item in &mut builder.inline_items {
match item {
InlineItem::TextRun(text_run) => {
text_run.borrow_mut().segment_and_shape(
&text_content,
layout_context,
&mut new_linebreaker,
&bidi_info,
);
},
InlineItem::StartInlineBox(inline_box) => {
let inline_box = &mut *inline_box.borrow_mut();
if let Some(font) = get_font_for_first_font_for_style(
&inline_box.base.style,
&layout_context.font_context,
) {
inline_box.default_font = Some(font);
}
},
InlineItem::Atomic(_, index_in_text, bidi_level) => {
*bidi_level = bidi_info.levels[*index_in_text];
},
InlineItem::OutOfFlowAbsolutelyPositionedBox(..) |
InlineItem::OutOfFlowFloatBox(_) |
InlineItem::EndInlineBox |
InlineItem::BlockLevel { .. } => {},
}
}
InlineFormattingContext {
text_content,
inline_items: builder.inline_items,
inline_boxes: builder.inline_boxes,
shared_inline_styles,
has_first_formatted_line,
contains_floats: builder.contains_floats,
is_single_line_text_input,
has_right_to_left_content,
shared_selection: builder.shared_selection,
}
}
pub(crate) fn repair_style(
&self,
context: &SharedStyleContext,
node: &ServoThreadSafeLayoutNode,
new_style: &ServoArc<ComputedValues>,
) {
*self.shared_inline_styles.style.borrow_mut() = new_style.clone();
*self.shared_inline_styles.selected.borrow_mut() = node.selected_style(context);
}
fn inline_start_for_first_line(&self, containing_block: IndefiniteContainingBlock) -> Au {
if !self.has_first_formatted_line {
return Au::zero();
}
containing_block
.style
.get_inherited_text()
.text_indent
.length
.to_used_value(containing_block.size.inline.unwrap_or_default())
}
pub(super) fn layout(
&self,
layout_context: &LayoutContext,
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
sequential_layout_state: Option<&mut SequentialLayoutState>,
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
) -> CacheableLayoutResult {
for inline_box in self.inline_boxes.iter() {
inline_box.borrow().base.clear_fragments();
}
let style = containing_block.style;
let default_font_metrics =
get_font_for_first_font_for_style(style, &layout_context.font_context)
.map(|font| font.metrics.clone());
let style_text = containing_block.style.get_inherited_text();
let mut inline_container_state_flags = InlineContainerStateFlags::empty();
if inline_container_needs_strut(style, layout_context, None) {
inline_container_state_flags.insert(InlineContainerStateFlags::CREATE_STRUT);
}
if self.is_single_line_text_input {
inline_container_state_flags
.insert(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT);
}
let placement_state =
PlacementState::new(collapsible_with_parent_start_margin, containing_block);
let mut layout = InlineFormattingContextLayout {
positioning_context,
placement_state,
sequential_layout_state,
layout_context,
ifc: self,
fragments: Vec::new(),
current_line: LineUnderConstruction::new(LogicalVec2 {
inline: self.inline_start_for_first_line(containing_block.into()),
block: Au::zero(),
}),
root_nesting_level: InlineContainerState::new(
style.to_arc(),
inline_container_state_flags,
None,
default_font_metrics,
),
inline_box_state_stack: Vec::new(),
inline_box_states: Vec::with_capacity(self.inline_boxes.len()),
current_line_segment: UnbreakableSegmentUnderConstruction::new(),
linebreak_before_new_content: false,
deferred_br_clear: Clear::None,
have_deferred_soft_wrap_opportunity: false,
depends_on_block_constraints: false,
white_space_collapse: style_text.white_space_collapse,
text_wrap_mode: style_text.text_wrap_mode,
};
for item in self.inline_items.iter() {
if !matches!(item, InlineItem::EndInlineBox) {
layout.possibly_flush_deferred_forced_line_break();
}
match item {
InlineItem::StartInlineBox(inline_box) => {
layout.start_inline_box(&inline_box.borrow());
},
InlineItem::EndInlineBox => layout.finish_inline_box(),
InlineItem::TextRun(run) => run.borrow().layout_into_line_items(&mut layout),
InlineItem::Atomic(atomic_formatting_context, offset_in_text, bidi_level) => {
atomic_formatting_context.borrow().layout_into_line_items(
&mut layout,
*offset_in_text,
*bidi_level,
);
},
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, _) => {
layout.push_line_item_to_unbreakable_segment(LineItem::AbsolutelyPositioned(
layout.current_inline_box_identifier(),
AbsolutelyPositionedLineItem {
absolutely_positioned_box: positioned_box.clone(),
preceding_line_content_would_produce_phantom_line: layout
.current_line
.is_phantom() &&
layout.current_line_segment.is_phantom(),
},
));
},
InlineItem::OutOfFlowFloatBox(float_box) => {
float_box.borrow().layout_into_line_items(&mut layout);
},
InlineItem::BlockLevel(block_level) => {
block_level.borrow().layout_into_line_items(&mut layout);
},
}
}
layout.finish_last_line();
let (content_block_size, collapsible_margins_in_children, baselines) =
layout.placement_state.finish();
CacheableLayoutResult {
fragments: layout.fragments,
content_block_size,
collapsible_margins_in_children,
baselines,
depends_on_block_constraints: layout.depends_on_block_constraints,
content_inline_size_for_table: None,
specific_layout_info: None,
}
}
fn next_character_prevents_soft_wrap_opportunity(&self, index: usize) -> bool {
let Some(character) = self.text_content[index..].chars().nth(1) else {
return false;
};
char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character)
}
fn previous_character_prevents_soft_wrap_opportunity(&self, index: usize) -> bool {
let Some(character) = self.text_content[0..index].chars().next_back() else {
return false;
};
char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character)
}
pub(crate) fn find_block_margin_collapsing_with_parent(
&self,
layout_context: &LayoutContext,
collected_margin: &mut CollapsedMargin,
containing_block_for_children: &ContainingBlock,
) -> bool {
let mut nesting_levels_from_nonzero_end_pbm: u32 = 1;
let mut items_iter = self.inline_items.iter();
items_iter.all(|inline_item| match inline_item {
InlineItem::StartInlineBox(inline_box) => {
let pbm = inline_box
.borrow()
.layout_style()
.padding_border_margin(containing_block_for_children);
if pbm.padding.inline_end.is_zero() &&
pbm.border.inline_end.is_zero() &&
pbm.margin.inline_end.auto_is(Au::zero).is_zero()
{
nesting_levels_from_nonzero_end_pbm += 1;
} else {
nesting_levels_from_nonzero_end_pbm = 0;
}
pbm.padding.inline_start.is_zero() &&
pbm.border.inline_start.is_zero() &&
pbm.margin.inline_start.auto_is(Au::zero).is_zero()
},
InlineItem::EndInlineBox => {
if nesting_levels_from_nonzero_end_pbm == 0 {
false
} else {
nesting_levels_from_nonzero_end_pbm -= 1;
true
}
},
InlineItem::TextRun(text_run) => {
let text_run = &*text_run.borrow();
let parent_style = text_run.inline_styles.style.borrow();
text_run.shaped_text.iter().all(|segment| {
segment.runs.iter().all(|run| {
run.is_whitespace() &&
!run.is_single_preserved_newline() &&
!matches!(
parent_style.get_inherited_text().white_space_collapse,
WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
)
})
})
},
InlineItem::OutOfFlowAbsolutelyPositionedBox(..) => true,
InlineItem::OutOfFlowFloatBox(..) => true,
InlineItem::Atomic(..) => false,
InlineItem::BlockLevel(block_level) => block_level
.borrow()
.find_block_margin_collapsing_with_parent(
layout_context,
collected_margin,
containing_block_for_children,
),
})
}
pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
let mut parent_box_stack = Vec::new();
let current_parent_box = |parent_box_stack: &[WeakLayoutBox]| {
parent_box_stack.last().unwrap_or(&layout_box).clone()
};
for inline_item in &self.inline_items {
match inline_item {
InlineItem::StartInlineBox(inline_box) => {
inline_box
.borrow_mut()
.base
.parent_box
.replace(current_parent_box(&parent_box_stack));
parent_box_stack.push(WeakLayoutBox::InlineLevel(
WeakInlineItem::StartInlineBox(inline_box.downgrade()),
));
},
InlineItem::EndInlineBox => {
parent_box_stack.pop();
},
InlineItem::TextRun(text_run) => {
text_run
.borrow_mut()
.parent_box
.replace(current_parent_box(&parent_box_stack));
},
_ => inline_item.with_base_mut(|base| {
base.parent_box
.replace(current_parent_box(&parent_box_stack));
}),
}
}
}
}
impl InlineContainerState {
fn new(
style: ServoArc<ComputedValues>,
flags: InlineContainerStateFlags,
parent_container: Option<&InlineContainerState>,
font_metrics: Option<Arc<FontMetrics>>,
) -> Self {
let font_metrics = font_metrics.unwrap_or_else(FontMetrics::empty);
let mut baseline_offset = Au::zero();
let mut strut_block_sizes = Self::get_block_sizes_with_style(
effective_baseline_shift(&style, parent_container),
&style,
&font_metrics,
&font_metrics,
&flags,
);
if let Some(parent_container) = parent_container {
baseline_offset = parent_container.get_cumulative_baseline_offset_for_child(
style.clone_alignment_baseline(),
style.clone_baseline_shift(),
&strut_block_sizes,
);
strut_block_sizes.adjust_for_baseline_offset(baseline_offset);
}
let mut nested_block_sizes = parent_container
.map(|container| container.nested_strut_block_sizes.clone())
.unwrap_or_else(LineBlockSizes::zero);
if flags.contains(InlineContainerStateFlags::CREATE_STRUT) {
nested_block_sizes.max_assign(&strut_block_sizes);
}
Self {
style,
flags,
has_content: RefCell::new(false),
nested_strut_block_sizes: nested_block_sizes,
strut_block_sizes,
baseline_offset,
font_metrics,
}
}
fn get_block_sizes_with_style(
baseline_shift: BaselineShift,
style: &ComputedValues,
font_metrics: &FontMetrics,
font_metrics_of_first_font: &FontMetrics,
flags: &InlineContainerStateFlags,
) -> LineBlockSizes {
let line_height = line_height(style, font_metrics, flags);
if !is_baseline_relative(baseline_shift) {
return LineBlockSizes {
line_height,
baseline_relative_size_for_line_height: None,
size_for_baseline_positioning: BaselineRelativeSize::zero(),
};
}
let mut ascent = font_metrics.ascent;
let mut descent = font_metrics.descent;
if style.get_font().line_height == LineHeight::Normal {
let half_leading_from_line_gap =
(font_metrics.line_gap - descent - ascent).scale_by(0.5);
ascent += half_leading_from_line_gap;
descent += half_leading_from_line_gap;
}
let size_for_baseline_positioning = BaselineRelativeSize { ascent, descent };
if style.get_font().line_height != LineHeight::Normal {
ascent = font_metrics_of_first_font.ascent;
descent = font_metrics_of_first_font.descent;
let half_leading = (line_height - (ascent + descent)).scale_by(0.5);
ascent += half_leading;
descent = line_height - ascent;
}
LineBlockSizes {
line_height,
baseline_relative_size_for_line_height: Some(BaselineRelativeSize { ascent, descent }),
size_for_baseline_positioning,
}
}
fn get_block_size_contribution(
&self,
baseline_shift: BaselineShift,
font_metrics: &FontMetrics,
font_metrics_of_first_font: &FontMetrics,
) -> LineBlockSizes {
Self::get_block_sizes_with_style(
baseline_shift,
&self.style,
font_metrics,
font_metrics_of_first_font,
&self.flags,
)
}
fn get_cumulative_baseline_offset_for_child(
&self,
child_alignment_baseline: AlignmentBaseline,
child_baseline_shift: BaselineShift,
child_block_size: &LineBlockSizes,
) -> Au {
let block_size = self.get_block_size_contribution(
child_baseline_shift.clone(),
&self.font_metrics,
&self.font_metrics,
);
self.baseline_offset +
match child_alignment_baseline {
AlignmentBaseline::Baseline => Au::zero(),
AlignmentBaseline::TextTop => {
child_block_size.size_for_baseline_positioning.ascent - self.font_metrics.ascent
},
AlignmentBaseline::Middle => {
(child_block_size.size_for_baseline_positioning.ascent -
child_block_size.size_for_baseline_positioning.descent -
self.font_metrics.x_height)
.scale_by(0.5)
},
AlignmentBaseline::TextBottom => {
self.font_metrics.descent -
child_block_size.size_for_baseline_positioning.descent
},
AlignmentBaseline::Alphabetic |
AlignmentBaseline::Ideographic |
AlignmentBaseline::Central |
AlignmentBaseline::Mathematical |
AlignmentBaseline::Hanging => {
unreachable!("Got alignment-baseline value that should be disabled in Stylo")
},
} +
match child_baseline_shift {
BaselineShift::Keyword(
BaselineShiftKeyword::Top |
BaselineShiftKeyword::Bottom |
BaselineShiftKeyword::Center,
) => Au::zero(),
BaselineShift::Keyword(BaselineShiftKeyword::Sub) => {
block_size.resolve().scale_by(FONT_SUBSCRIPT_OFFSET_RATIO)
},
BaselineShift::Keyword(BaselineShiftKeyword::Super) => {
-block_size.resolve().scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO)
},
BaselineShift::Length(length_percentage) => {
-length_percentage.to_used_value(child_block_size.line_height)
},
}
}
}
impl IndependentFormattingContext {
fn layout_into_line_items(
&self,
layout: &mut InlineFormattingContextLayout,
offset_in_text: usize,
bidi_level: Level,
) {
let mut child_positioning_context = PositioningContext::default();
let IndependentFloatOrAtomicLayoutResult {
mut fragment,
baselines,
pbm_sums,
} = self.layout_float_or_atomic_inline(
layout.layout_context,
&mut child_positioning_context,
layout.containing_block(),
);
layout.depends_on_block_constraints |= fragment.base.flags.contains(
FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
);
let container_writing_mode = layout.containing_block().style.writing_mode;
let pbm_physical_offset = pbm_sums
.start_offset()
.to_physical_size(container_writing_mode);
fragment.base.rect.origin += pbm_physical_offset.to_vector();
fragment = fragment.with_baselines(baselines);
let positioning_context = if self.is_replaced() {
None
} else {
if fragment
.style()
.establishes_containing_block_for_absolute_descendants(fragment.base.flags)
{
child_positioning_context
.layout_collected_children(layout.layout_context, &mut fragment);
}
Some(child_positioning_context)
};
if layout.text_wrap_mode == TextWrapMode::Wrap &&
!layout
.ifc
.previous_character_prevents_soft_wrap_opportunity(offset_in_text)
{
layout.process_soft_wrap_opportunity();
}
let size = pbm_sums.sum() + fragment.base.rect.size.to_logical(container_writing_mode);
let baseline_offset = self
.pick_baseline(&fragment.baselines(container_writing_mode))
.map(|baseline| pbm_sums.block_start + baseline)
.unwrap_or(size.block);
let (block_sizes, baseline_offset_in_parent) =
self.get_block_sizes_and_baseline_offset(layout, size.block, baseline_offset);
layout.update_unbreakable_segment_for_new_content(
&block_sizes,
size.inline,
SegmentContentFlags::empty(),
);
let fragment = ArcRefCell::new(fragment);
self.base.set_fragment(Fragment::Box(fragment.clone()));
layout.push_line_item_to_unbreakable_segment(LineItem::Atomic(
layout.current_inline_box_identifier(),
AtomicLineItem {
fragment,
size,
positioning_context,
baseline_offset_in_parent,
baseline_offset_in_item: baseline_offset,
bidi_level,
},
));
if !layout
.ifc
.next_character_prevents_soft_wrap_opportunity(offset_in_text)
{
layout.have_deferred_soft_wrap_opportunity = true;
}
}
fn pick_baseline(&self, baselines: &Baselines) -> Option<Au> {
match self.style().clone_baseline_source() {
BaselineSource::First => baselines.first,
BaselineSource::Last => baselines.last,
BaselineSource::Auto if self.is_block_container() => baselines.last,
BaselineSource::Auto => baselines.first,
}
}
fn get_block_sizes_and_baseline_offset(
&self,
ifc: &InlineFormattingContextLayout,
block_size: Au,
baseline_offset_in_content_area: Au,
) -> (LineBlockSizes, Au) {
let mut contribution = if !is_baseline_relative(self.style().clone_baseline_shift()) {
LineBlockSizes {
line_height: block_size,
baseline_relative_size_for_line_height: None,
size_for_baseline_positioning: BaselineRelativeSize::zero(),
}
} else {
let baseline_relative_size = BaselineRelativeSize {
ascent: baseline_offset_in_content_area,
descent: block_size - baseline_offset_in_content_area,
};
LineBlockSizes {
line_height: block_size,
baseline_relative_size_for_line_height: Some(baseline_relative_size.clone()),
size_for_baseline_positioning: baseline_relative_size,
}
};
let style = self.style();
let baseline_offset = ifc
.current_inline_container_state()
.get_cumulative_baseline_offset_for_child(
style.clone_alignment_baseline(),
style.clone_baseline_shift(),
&contribution,
);
contribution.adjust_for_baseline_offset(baseline_offset);
(contribution, baseline_offset)
}
}
impl FloatBox {
fn layout_into_line_items(&self, layout: &mut InlineFormattingContextLayout) {
let fragment = ArcRefCell::new(self.layout(
layout.layout_context,
layout.positioning_context,
layout.placement_state.containing_block,
));
self.contents
.base
.set_fragment(Fragment::Box(fragment.clone()));
layout.push_line_item_to_unbreakable_segment(LineItem::Float(
layout.current_inline_box_identifier(),
FloatLineItem {
fragment,
needs_placement: true,
},
));
}
}
fn place_pending_floats(ifc: &mut InlineFormattingContextLayout, line_items: &mut [LineItem]) {
for item in line_items.iter_mut() {
if let LineItem::Float(_, float_line_item) = item {
if float_line_item.needs_placement {
ifc.place_float_fragment(&mut float_line_item.fragment.borrow_mut());
}
}
}
}
fn line_height(
parent_style: &ComputedValues,
font_metrics: &FontMetrics,
flags: &InlineContainerStateFlags,
) -> Au {
let font = parent_style.get_font();
let font_size = font.font_size.computed_size();
let mut line_height = match font.line_height {
LineHeight::Normal => font_metrics.line_gap,
LineHeight::Number(number) => (font_size * number.0).into(),
LineHeight::Length(length) => length.0.into(),
};
if flags.contains(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT) {
line_height.max_assign(font_metrics.line_gap);
}
line_height
}
fn effective_baseline_shift(
style: &ComputedValues,
container: Option<&InlineContainerState>,
) -> BaselineShift {
if container.is_none() {
BaselineShift::zero()
} else {
style.clone_baseline_shift()
}
}
fn is_baseline_relative(baseline_shift: BaselineShift) -> bool {
!matches!(
baseline_shift,
BaselineShift::Keyword(
BaselineShiftKeyword::Top | BaselineShiftKeyword::Bottom | BaselineShiftKeyword::Center
)
)
}
fn inline_container_needs_strut(
style: &ComputedValues,
layout_context: &LayoutContext,
pbm: Option<&PaddingBorderMargin>,
) -> bool {
if layout_context.style_context.quirks_mode() == QuirksMode::NoQuirks {
return true;
}
if style.get_box().display.is_list_item() {
return true;
}
pbm.is_some_and(|pbm| !pbm.padding_border_sums.inline.is_zero())
}
impl ComputeInlineContentSizes for InlineFormattingContext {
fn compute_inline_content_sizes(
&self,
layout_context: &LayoutContext,
constraint_space: &ConstraintSpace,
) -> InlineContentSizesResult {
ContentSizesComputation::compute(self, layout_context, constraint_space)
}
}
struct ContentSizesComputation<'layout_data> {
layout_context: &'layout_data LayoutContext<'layout_data>,
constraint_space: &'layout_data ConstraintSpace<'layout_data>,
paragraph: ContentSizes,
current_line: ContentSizes,
pending_whitespace: ContentSizes,
uncleared_floats: LogicalSides1D<ContentSizes>,
cleared_floats: LogicalSides1D<ContentSizes>,
had_content_yet_for_min_content: bool,
had_content_yet_for_max_content: bool,
ending_inline_pbm_stack: Vec<Au>,
depends_on_block_constraints: bool,
}
impl<'layout_data> ContentSizesComputation<'layout_data> {
fn traverse(
mut self,
inline_formatting_context: &InlineFormattingContext,
) -> InlineContentSizesResult {
self.add_inline_size(
inline_formatting_context.inline_start_for_first_line(self.constraint_space.into()),
);
for inline_item in &inline_formatting_context.inline_items {
self.process_item(inline_item, inline_formatting_context);
}
self.forced_line_break();
self.flush_floats();
InlineContentSizesResult {
sizes: self.paragraph,
depends_on_block_constraints: self.depends_on_block_constraints,
}
}
fn process_item(
&mut self,
inline_item: &InlineItem,
inline_formatting_context: &InlineFormattingContext,
) {
match inline_item {
InlineItem::StartInlineBox(inline_box) => {
let inline_box = inline_box.borrow();
let zero = Au::zero();
let writing_mode = self.constraint_space.style.writing_mode;
let layout_style = inline_box.layout_style();
let padding = layout_style
.padding(writing_mode)
.percentages_relative_to(zero);
let border = layout_style.border_width(writing_mode);
let margin = inline_box
.base
.style
.margin(writing_mode)
.percentages_relative_to(zero)
.auto_is(Au::zero);
let pbm = margin + padding + border;
self.add_inline_size(pbm.inline_start);
self.ending_inline_pbm_stack.push(pbm.inline_end);
},
InlineItem::EndInlineBox => {
let length = self.ending_inline_pbm_stack.pop().unwrap_or_else(Au::zero);
self.add_inline_size(length);
},
InlineItem::TextRun(text_run) => {
let text_run = &*text_run.borrow();
let parent_style = text_run.inline_styles.style.borrow();
for segment in text_run.shaped_text.iter() {
let style_text = parent_style.get_inherited_text();
let can_wrap = style_text.text_wrap_mode == TextWrapMode::Wrap;
let break_at_start =
segment.break_at_start && self.had_content_yet_for_min_content;
for (run_index, run) in segment.runs.iter().enumerate() {
if can_wrap && (run_index != 0 || break_at_start) {
self.line_break_opportunity();
}
let advance = run.total_advance();
if run.is_whitespace() {
if run.is_single_preserved_newline() {
self.forced_line_break();
continue;
}
if !matches!(
style_text.white_space_collapse,
WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces
) {
if self.had_content_yet_for_min_content {
if can_wrap {
self.line_break_opportunity();
} else {
self.pending_whitespace.min_content += advance;
}
}
if self.had_content_yet_for_max_content {
self.pending_whitespace.max_content += advance;
}
continue;
}
if can_wrap {
self.pending_whitespace.max_content += advance;
self.commit_pending_whitespace();
self.line_break_opportunity();
continue;
}
}
self.commit_pending_whitespace();
self.add_inline_size(advance);
if can_wrap && run.ends_with_whitespace() {
self.line_break_opportunity();
}
}
}
},
InlineItem::Atomic(atomic, offset_in_text, _level) => {
if self.had_content_yet_for_min_content &&
!inline_formatting_context
.previous_character_prevents_soft_wrap_opportunity(*offset_in_text)
{
self.line_break_opportunity();
}
self.commit_pending_whitespace();
let outer = self.outer_inline_content_sizes_of_float_or_atomic(&atomic.borrow());
self.current_line += outer;
if !inline_formatting_context
.next_character_prevents_soft_wrap_opportunity(*offset_in_text)
{
self.line_break_opportunity();
}
},
InlineItem::OutOfFlowFloatBox(float_box) => {
let float_box = float_box.borrow();
let sizes = self.outer_inline_content_sizes_of_float_or_atomic(&float_box.contents);
let style = &float_box.contents.style();
let container_writing_mode = self.constraint_space.style.writing_mode;
let clear =
Clear::from_style_and_container_writing_mode(style, container_writing_mode);
self.clear_floats(clear);
let float_side =
FloatSide::from_style_and_container_writing_mode(style, container_writing_mode);
match float_side.expect("A float box needs to float to some side") {
FloatSide::InlineStart => self.uncleared_floats.start.union_assign(&sizes),
FloatSide::InlineEnd => self.uncleared_floats.end.union_assign(&sizes),
}
},
InlineItem::BlockLevel(block_level) => {
self.forced_line_break();
self.flush_floats();
let inline_content_sizes_result =
compute_inline_content_sizes_for_block_level_boxes(
std::slice::from_ref(block_level),
self.layout_context,
&self.constraint_space.into(),
);
self.depends_on_block_constraints |=
inline_content_sizes_result.depends_on_block_constraints;
self.current_line = inline_content_sizes_result.sizes;
self.forced_line_break();
},
InlineItem::OutOfFlowAbsolutelyPositionedBox(..) => {},
}
}
fn add_inline_size(&mut self, l: Au) {
self.current_line.min_content += l;
self.current_line.max_content += l;
}
fn line_break_opportunity(&mut self) {
self.pending_whitespace.min_content = Au::zero();
let current_min_content = mem::take(&mut self.current_line.min_content);
self.paragraph.min_content.max_assign(current_min_content);
self.had_content_yet_for_min_content = false;
}
fn forced_line_break(&mut self) {
self.line_break_opportunity();
self.pending_whitespace.max_content = Au::zero();
let current_max_content = mem::take(&mut self.current_line.max_content);
self.paragraph.max_content.max_assign(current_max_content);
self.had_content_yet_for_max_content = false;
}
fn commit_pending_whitespace(&mut self) {
self.current_line += mem::take(&mut self.pending_whitespace);
self.had_content_yet_for_min_content = true;
self.had_content_yet_for_max_content = true;
}
fn outer_inline_content_sizes_of_float_or_atomic(
&mut self,
context: &IndependentFormattingContext,
) -> ContentSizes {
let result = context.outer_inline_content_sizes(
self.layout_context,
&self.constraint_space.into(),
&LogicalVec2::zero(),
false,
);
self.depends_on_block_constraints |= result.depends_on_block_constraints;
result.sizes
}
fn clear_floats(&mut self, clear: Clear) {
match clear {
Clear::InlineStart => {
let start_floats = mem::take(&mut self.uncleared_floats.start);
self.cleared_floats.start.max_assign(start_floats);
},
Clear::InlineEnd => {
let end_floats = mem::take(&mut self.uncleared_floats.end);
self.cleared_floats.end.max_assign(end_floats);
},
Clear::Both => {
let start_floats = mem::take(&mut self.uncleared_floats.start);
let end_floats = mem::take(&mut self.uncleared_floats.end);
self.cleared_floats.start.max_assign(start_floats);
self.cleared_floats.end.max_assign(end_floats);
},
Clear::None => {},
}
}
fn flush_floats(&mut self) {
self.clear_floats(Clear::Both);
let start_floats = mem::take(&mut self.cleared_floats.start);
let end_floats = mem::take(&mut self.cleared_floats.end);
self.paragraph.union_assign(&start_floats);
self.paragraph.union_assign(&end_floats);
}
fn compute(
inline_formatting_context: &InlineFormattingContext,
layout_context: &'layout_data LayoutContext,
constraint_space: &'layout_data ConstraintSpace,
) -> InlineContentSizesResult {
Self {
layout_context,
constraint_space,
paragraph: ContentSizes::zero(),
current_line: ContentSizes::zero(),
pending_whitespace: ContentSizes::zero(),
uncleared_floats: LogicalSides1D::default(),
cleared_floats: LogicalSides1D::default(),
had_content_yet_for_min_content: false,
had_content_yet_for_max_content: false,
ending_inline_pbm_stack: Vec::new(),
depends_on_block_constraints: false,
}
.traverse(inline_formatting_context)
}
}
fn char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character: char) -> bool {
if character == '\u{00A0}' {
return false;
}
let class = linebreak_property(character);
class == XI_LINE_BREAKING_CLASS_GL ||
class == XI_LINE_BREAKING_CLASS_WJ ||
class == XI_LINE_BREAKING_CLASS_ZWJ
}