use app_units::Au;
use base::print_tree::PrintTree;
use serde::Serialize;
use servo_arc::Arc as ServoArc;
use style::computed_values::overflow_x::T as ComputedOverflow;
use style::computed_values::position::T as ComputedPosition;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::Zero;
use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment};
use crate::cell::ArcRefCell;
use crate::formatting_contexts::Baselines;
use crate::fragment_tree::FragmentFlags;
use crate::geom::{
AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical,
};
use crate::style_ext::ComputedValuesExt;
pub(crate) enum BackgroundMode {
Extra(Vec<ExtraBackground>),
None,
Normal,
}
pub(crate) struct ExtraBackground {
pub style: ServoArc<ComputedValues>,
pub rect: PhysicalRect<Au>,
}
#[derive(Serialize)]
pub(crate) struct BoxFragment {
pub base: BaseFragment,
#[serde(skip_serializing)]
pub style: ServoArc<ComputedValues>,
pub children: Vec<ArcRefCell<Fragment>>,
pub content_rect: PhysicalRect<Au>,
pub padding: PhysicalSides<Au>,
pub border: PhysicalSides<Au>,
pub margin: PhysicalSides<Au>,
pub clearance: Option<Au>,
baselines: Baselines,
pub block_margins_collapsed_with_children: CollapsedBlockMargins,
pub scrollable_overflow_from_children: PhysicalRect<Au>,
pub(crate) resolved_sticky_insets: Option<PhysicalSides<AuOrAuto>>,
#[serde(skip_serializing)]
pub background_mode: BackgroundMode,
}
impl BoxFragment {
#[allow(clippy::too_many_arguments)]
pub fn new(
base_fragment_info: BaseFragmentInfo,
style: ServoArc<ComputedValues>,
children: Vec<Fragment>,
content_rect: PhysicalRect<Au>,
padding: PhysicalSides<Au>,
border: PhysicalSides<Au>,
margin: PhysicalSides<Au>,
clearance: Option<Au>,
block_margins_collapsed_with_children: CollapsedBlockMargins,
) -> BoxFragment {
let scrollable_overflow_from_children =
children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(&child.scrollable_overflow())
});
BoxFragment {
base: base_fragment_info.into(),
style,
children: children.into_iter().map(ArcRefCell::new).collect(),
content_rect,
padding,
border,
margin,
clearance,
baselines: Baselines::default(),
block_margins_collapsed_with_children,
scrollable_overflow_from_children,
resolved_sticky_insets: None,
background_mode: BackgroundMode::Normal,
}
}
pub fn with_baselines(mut self, baselines: Baselines) -> Self {
self.baselines = baselines;
self
}
pub fn baselines(&self, writing_mode: WritingMode) -> Baselines {
let mut baselines =
if writing_mode.is_horizontal() == self.style.writing_mode.is_horizontal() {
self.baselines
} else {
Baselines::default()
};
if self.style.establishes_scroll_container() {
let content_rect_size = self.content_rect.size.to_logical(writing_mode);
let padding = self.padding.to_logical(writing_mode);
let border = self.border.to_logical(writing_mode);
let margin = self.margin.to_logical(writing_mode);
baselines.last = Some(
content_rect_size.block + padding.block_end + border.block_end + margin.block_end,
)
}
baselines
}
pub fn add_extra_background(&mut self, extra_background: ExtraBackground) {
match self.background_mode {
BackgroundMode::Extra(ref mut backgrounds) => backgrounds.push(extra_background),
_ => self.background_mode = BackgroundMode::Extra(vec![extra_background]),
}
}
pub fn set_does_not_paint_background(&mut self) {
self.background_mode = BackgroundMode::None;
}
pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
let physical_padding_rect = self.padding_rect();
let content_origin = self.content_rect.origin.to_vector();
physical_padding_rect.union(
&self
.scrollable_overflow_from_children
.translate(content_origin),
)
}
pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
self.content_rect.outer_rect(self.padding)
}
pub(crate) fn border_rect(&self) -> PhysicalRect<Au> {
self.padding_rect().outer_rect(self.border)
}
pub(crate) fn margin_rect(&self) -> PhysicalRect<Au> {
self.border_rect().outer_rect(self.margin)
}
pub(crate) fn padding_border_margin(&self) -> PhysicalSides<Au> {
self.margin + self.border + self.padding
}
pub fn print(&self, tree: &mut PrintTree) {
tree.new_level(format!(
"Box\
\nbase={:?}\
\ncontent={:?}\
\npadding rect={:?}\
\nborder rect={:?}\
\nmargin={:?}\
\nclearance={:?}\
\nscrollable_overflow={:?}\
\nbaselines={:?}\
\noverflow={:?}",
self.base,
self.content_rect,
self.padding_rect(),
self.border_rect(),
self.margin,
self.clearance,
self.scrollable_overflow(),
self.baselines,
self.style.effective_overflow(),
));
for child in &self.children {
child.borrow().print(tree);
}
tree.end_level();
}
pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
let mut overflow = self.border_rect();
if self.style.establishes_scroll_container() {
return overflow;
}
let scrollable_overflow = self.scrollable_overflow();
let bottom_right = PhysicalPoint::new(
overflow.max_x().max(scrollable_overflow.max_x()),
overflow.max_y().max(scrollable_overflow.max_y()),
);
let overflow_style = self.style.effective_overflow();
if overflow_style.y == ComputedOverflow::Visible {
overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
overflow.size.height = bottom_right.y - overflow.origin.y;
}
if overflow_style.x == ComputedOverflow::Visible {
overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
overflow.size.width = bottom_right.x - overflow.origin.x;
}
overflow
}
pub(crate) fn calculate_resolved_insets_if_positioned(
&self,
containing_block: &PhysicalRect<Au>,
) -> PhysicalSides<AuOrAuto> {
let position = self.style.get_box().position;
debug_assert_ne!(
position,
ComputedPosition::Static,
"Should not call this method on statically positioned box."
);
if let Some(resolved_sticky_insets) = self.resolved_sticky_insets {
return resolved_sticky_insets;
}
let convert_to_au_or_auto = |sides: PhysicalSides<Au>| {
PhysicalSides::new(
AuOrAuto::LengthPercentage(sides.top),
AuOrAuto::LengthPercentage(sides.right),
AuOrAuto::LengthPercentage(sides.bottom),
AuOrAuto::LengthPercentage(sides.left),
)
};
let insets = self.style.physical_box_offsets();
let (cb_width, cb_height) = (containing_block.width(), containing_block.height());
if position == ComputedPosition::Relative {
let get_resolved_axis = |start: &LengthPercentageOrAuto,
end: &LengthPercentageOrAuto,
container_length: Au| {
let start = start.map(|value| value.to_used_value(container_length));
let end = end.map(|value| value.to_used_value(container_length));
match (start.non_auto(), end.non_auto()) {
(None, None) => (Au::zero(), Au::zero()),
(None, Some(end)) => (-end, end),
(Some(start), None) => (start, -start),
(Some(start), Some(end)) => (start, end),
}
};
let (left, right) = get_resolved_axis(&insets.left, &insets.right, cb_width);
let (top, bottom) = get_resolved_axis(&insets.top, &insets.bottom, cb_height);
return convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left));
}
debug_assert!(
position == ComputedPosition::Fixed || position == ComputedPosition::Absolute
);
let margin_rect = self.margin_rect();
let (top, bottom) = match (&insets.top, &insets.bottom) {
(
LengthPercentageOrAuto::LengthPercentage(top),
LengthPercentageOrAuto::LengthPercentage(bottom),
) => (
top.to_used_value(cb_height),
bottom.to_used_value(cb_height),
),
_ => (margin_rect.origin.y, cb_height - margin_rect.max_y()),
};
let (left, right) = match (&insets.left, &insets.right) {
(
LengthPercentageOrAuto::LengthPercentage(left),
LengthPercentageOrAuto::LengthPercentage(right),
) => (left.to_used_value(cb_width), right.to_used_value(cb_width)),
_ => (margin_rect.origin.x, cb_width - margin_rect.max_x()),
};
convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left))
}
pub(crate) fn is_inline_box(&self) -> bool {
self.style.get_box().display.is_inline_flow() &&
!self.base.flags.contains(FragmentFlags::IS_REPLACED)
}
}