use std::cmp::{max, min};
use std::fmt;
use app_units::Au;
use euclid::SideOffsets2D;
use serde::Serialize;
use style::logical_geometry::{LogicalMargin, WritingMode};
use style::properties::ComputedValues;
use style::values::computed::{Inset, LengthPercentageOrAuto, Margin, MaxSize, Size};
use crate::fragment::Fragment;
#[derive(Clone, Copy, Debug)]
pub struct AdjoiningMargins {
pub most_positive: Au,
pub most_negative: Au,
}
impl AdjoiningMargins {
pub fn new() -> AdjoiningMargins {
AdjoiningMargins {
most_positive: Au(0),
most_negative: Au(0),
}
}
pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
if margin_value >= Au(0) {
AdjoiningMargins {
most_positive: margin_value,
most_negative: Au(0),
}
} else {
AdjoiningMargins {
most_positive: Au(0),
most_negative: margin_value,
}
}
}
pub fn union(&mut self, other: AdjoiningMargins) {
self.most_positive = max(self.most_positive, other.most_positive);
self.most_negative = min(self.most_negative, other.most_negative)
}
pub fn collapse(&self) -> Au {
self.most_positive + self.most_negative
}
}
impl Default for AdjoiningMargins {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug)]
pub enum CollapsibleMargins {
None(Au, Au),
Collapse(AdjoiningMargins, AdjoiningMargins),
CollapseThrough(AdjoiningMargins),
}
impl CollapsibleMargins {
pub fn new() -> CollapsibleMargins {
CollapsibleMargins::None(Au(0), Au(0))
}
pub fn block_start_margin_for_noncollapsible_context(&self) -> Au {
match *self {
CollapsibleMargins::None(block_start, _) => block_start,
CollapsibleMargins::Collapse(ref block_start, _) |
CollapsibleMargins::CollapseThrough(ref block_start) => block_start.collapse(),
}
}
pub fn block_end_margin_for_noncollapsible_context(&self) -> Au {
match *self {
CollapsibleMargins::None(_, block_end) => block_end,
CollapsibleMargins::Collapse(_, ref block_end) |
CollapsibleMargins::CollapseThrough(ref block_end) => block_end.collapse(),
}
}
}
impl Default for CollapsibleMargins {
fn default() -> Self {
Self::new()
}
}
enum FinalMarginState {
MarginsCollapseThrough,
BottomMarginCollapses,
}
pub struct MarginCollapseInfo {
pub state: MarginCollapseState,
pub block_start_margin: AdjoiningMargins,
pub margin_in: AdjoiningMargins,
}
impl MarginCollapseInfo {
pub fn initialize_block_start_margin(
fragment: &Fragment,
can_collapse_block_start_margin_with_kids: bool,
) -> MarginCollapseInfo {
MarginCollapseInfo {
state: if can_collapse_block_start_margin_with_kids {
MarginCollapseState::AccumulatingCollapsibleTopMargin
} else {
MarginCollapseState::AccumulatingMarginIn
},
block_start_margin: AdjoiningMargins::from_margin(fragment.margin.block_start),
margin_in: AdjoiningMargins::new(),
}
}
pub fn finish_and_compute_collapsible_margins(
mut self,
fragment: &Fragment,
containing_block_size: Option<Au>,
can_collapse_block_end_margin_with_kids: bool,
mut may_collapse_through: bool,
) -> (CollapsibleMargins, Au) {
let state = match self.state {
MarginCollapseState::AccumulatingCollapsibleTopMargin => {
let content_block_size = fragment.style().content_block_size();
may_collapse_through = may_collapse_through &&
content_block_size.is_definitely_zero() ||
content_block_size
.maybe_to_used_value(containing_block_size)
.is_none();
if may_collapse_through {
if fragment.style.min_block_size().is_auto() ||
fragment.style().min_block_size().is_definitely_zero()
{
FinalMarginState::MarginsCollapseThrough
} else {
FinalMarginState::BottomMarginCollapses
}
} else {
FinalMarginState::BottomMarginCollapses
}
},
MarginCollapseState::AccumulatingMarginIn => FinalMarginState::BottomMarginCollapses,
};
let block_end_margin = fragment.margin.block_end;
if !can_collapse_block_end_margin_with_kids {
match state {
FinalMarginState::MarginsCollapseThrough => {
let advance = self.block_start_margin.collapse();
self.margin_in
.union(AdjoiningMargins::from_margin(block_end_margin));
(
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
advance,
)
},
FinalMarginState::BottomMarginCollapses => {
let advance = self.margin_in.collapse();
self.margin_in
.union(AdjoiningMargins::from_margin(block_end_margin));
(
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
advance,
)
},
}
} else {
match state {
FinalMarginState::MarginsCollapseThrough => {
self.block_start_margin
.union(AdjoiningMargins::from_margin(block_end_margin));
(
CollapsibleMargins::CollapseThrough(self.block_start_margin),
Au(0),
)
},
FinalMarginState::BottomMarginCollapses => {
self.margin_in
.union(AdjoiningMargins::from_margin(block_end_margin));
(
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
Au(0),
)
},
}
}
}
pub fn current_float_ceiling(&mut self) -> Au {
match self.state {
MarginCollapseState::AccumulatingCollapsibleTopMargin => {
Au(0)
},
MarginCollapseState::AccumulatingMarginIn => self.margin_in.collapse(),
}
}
pub fn advance_block_start_margin(
&mut self,
child_collapsible_margins: &CollapsibleMargins,
can_collapse_block_start_margin: bool,
) -> Au {
if !can_collapse_block_start_margin {
self.state = MarginCollapseState::AccumulatingMarginIn
}
match (self.state, *child_collapsible_margins) {
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::None(block_start, _),
) => {
self.state = MarginCollapseState::AccumulatingMarginIn;
block_start
},
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::Collapse(block_start, _),
) => {
self.block_start_margin.union(block_start);
self.state = MarginCollapseState::AccumulatingMarginIn;
Au(0)
},
(
MarginCollapseState::AccumulatingMarginIn,
CollapsibleMargins::None(block_start, _),
) => {
let previous_margin_value = self.margin_in.collapse();
self.margin_in = AdjoiningMargins::new();
previous_margin_value + block_start
},
(
MarginCollapseState::AccumulatingMarginIn,
CollapsibleMargins::Collapse(block_start, _),
) => {
self.margin_in.union(block_start);
let margin_value = self.margin_in.collapse();
self.margin_in = AdjoiningMargins::new();
margin_value
},
(_, CollapsibleMargins::CollapseThrough(_)) => {
Au(0)
},
}
}
pub fn advance_block_end_margin(
&mut self,
child_collapsible_margins: &CollapsibleMargins,
) -> Au {
match (self.state, *child_collapsible_margins) {
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::None(..),
) |
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::Collapse(..),
) => {
panic!("should not be accumulating collapsible block_start margins anymore!")
},
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::CollapseThrough(margin),
) => {
self.block_start_margin.union(margin);
Au(0)
},
(MarginCollapseState::AccumulatingMarginIn, CollapsibleMargins::None(_, block_end)) => {
assert_eq!(self.margin_in.most_positive, Au(0));
assert_eq!(self.margin_in.most_negative, Au(0));
block_end
},
(
MarginCollapseState::AccumulatingMarginIn,
CollapsibleMargins::Collapse(_, block_end),
) |
(
MarginCollapseState::AccumulatingMarginIn,
CollapsibleMargins::CollapseThrough(block_end),
) => {
self.margin_in.union(block_end);
Au(0)
},
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum MarginCollapseState {
AccumulatingCollapsibleTopMargin,
AccumulatingMarginIn,
}
#[derive(Clone, Copy, Serialize)]
pub struct IntrinsicISizes {
pub minimum_inline_size: Au,
pub preferred_inline_size: Au,
}
impl fmt::Debug for IntrinsicISizes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"min={:?}, pref={:?}",
self.minimum_inline_size, self.preferred_inline_size
)
}
}
impl IntrinsicISizes {
pub fn new() -> IntrinsicISizes {
IntrinsicISizes {
minimum_inline_size: Au(0),
preferred_inline_size: Au(0),
}
}
}
impl Default for IntrinsicISizes {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct IntrinsicISizesContribution {
pub content_intrinsic_sizes: IntrinsicISizes,
pub surrounding_size: Au,
}
impl IntrinsicISizesContribution {
pub fn new() -> IntrinsicISizesContribution {
IntrinsicISizesContribution {
content_intrinsic_sizes: IntrinsicISizes::new(),
surrounding_size: Au(0),
}
}
pub fn finish(self) -> IntrinsicISizes {
IntrinsicISizes {
minimum_inline_size: self.content_intrinsic_sizes.minimum_inline_size +
self.surrounding_size,
preferred_inline_size: self.content_intrinsic_sizes.preferred_inline_size +
self.surrounding_size,
}
}
pub fn union_inline(&mut self, sizes: &IntrinsicISizes) {
self.content_intrinsic_sizes.minimum_inline_size = max(
self.content_intrinsic_sizes.minimum_inline_size,
sizes.minimum_inline_size,
);
self.content_intrinsic_sizes.preferred_inline_size += sizes.preferred_inline_size
}
pub fn union_nonbreaking_inline(&mut self, sizes: &IntrinsicISizes) {
self.content_intrinsic_sizes.minimum_inline_size += sizes.minimum_inline_size;
self.content_intrinsic_sizes.preferred_inline_size += sizes.preferred_inline_size
}
pub fn union_block(&mut self, sizes: &IntrinsicISizes) {
self.content_intrinsic_sizes.minimum_inline_size = max(
self.content_intrinsic_sizes.minimum_inline_size,
sizes.minimum_inline_size,
);
self.content_intrinsic_sizes.preferred_inline_size = max(
self.content_intrinsic_sizes.preferred_inline_size,
sizes.preferred_inline_size,
)
}
}
impl Default for IntrinsicISizesContribution {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MaybeAuto {
Auto,
Specified(Au),
}
impl MaybeAuto {
#[inline]
pub fn from_style(length: &LengthPercentageOrAuto, containing_length: Au) -> MaybeAuto {
match length {
LengthPercentageOrAuto::Auto => MaybeAuto::Auto,
LengthPercentageOrAuto::LengthPercentage(ref lp) => {
MaybeAuto::Specified(lp.to_used_value(containing_length))
},
}
}
#[inline]
pub fn from_inset(length: &Inset, containing_length: Au) -> MaybeAuto {
match length {
Inset::Auto => MaybeAuto::Auto,
Inset::LengthPercentage(ref lp) => {
MaybeAuto::Specified(lp.to_used_value(containing_length))
},
Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"),
Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
}
}
#[inline]
pub fn from_margin(length: &Margin, containing_length: Au) -> MaybeAuto {
match length {
Margin::Auto => MaybeAuto::Auto,
Margin::LengthPercentage(ref lp) => {
MaybeAuto::Specified(lp.to_used_value(containing_length))
},
Margin::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
}
}
#[inline]
pub fn from_option(au: Option<Au>) -> MaybeAuto {
match au {
Some(l) => MaybeAuto::Specified(l),
_ => MaybeAuto::Auto,
}
}
#[inline]
pub fn as_option(&self) -> Option<Au> {
match *self {
MaybeAuto::Specified(value) => Some(value),
MaybeAuto::Auto => None,
}
}
#[inline]
pub fn specified_or_default(&self, default: Au) -> Au {
match *self {
MaybeAuto::Auto => default,
MaybeAuto::Specified(value) => value,
}
}
#[inline]
pub fn specified_or_zero(&self) -> Au {
self.specified_or_default(Au::new(0))
}
#[inline]
pub fn is_auto(&self) -> bool {
match *self {
MaybeAuto::Auto => true,
MaybeAuto::Specified(..) => false,
}
}
#[inline]
pub fn map<F>(&self, mapper: F) -> MaybeAuto
where
F: FnOnce(Au) -> Au,
{
match *self {
MaybeAuto::Auto => MaybeAuto::Auto,
MaybeAuto::Specified(value) => MaybeAuto::Specified(mapper(value)),
}
}
}
pub fn style_length(style_length: &Size, container_size: Option<Au>) -> MaybeAuto {
MaybeAuto::from_option(style_length.maybe_to_used_value(container_size))
}
#[inline]
pub fn padding_from_style(
style: &ComputedValues,
containing_block_inline_size: Au,
writing_mode: WritingMode,
) -> LogicalMargin<Au> {
let padding_style = style.get_padding();
LogicalMargin::from_physical(
writing_mode,
SideOffsets2D::new(
padding_style
.padding_top
.to_used_value(containing_block_inline_size),
padding_style
.padding_right
.to_used_value(containing_block_inline_size),
padding_style
.padding_bottom
.to_used_value(containing_block_inline_size),
padding_style
.padding_left
.to_used_value(containing_block_inline_size),
),
)
}
#[inline]
pub fn specified_margin_from_style(
style: &ComputedValues,
writing_mode: WritingMode,
) -> LogicalMargin<Au> {
let margin_style = style.get_margin();
LogicalMargin::from_physical(
writing_mode,
SideOffsets2D::new(
MaybeAuto::from_margin(&margin_style.margin_top, Au(0)).specified_or_zero(),
MaybeAuto::from_margin(&margin_style.margin_right, Au(0)).specified_or_zero(),
MaybeAuto::from_margin(&margin_style.margin_bottom, Au(0)).specified_or_zero(),
MaybeAuto::from_margin(&margin_style.margin_left, Au(0)).specified_or_zero(),
),
)
}
#[derive(Clone, Copy, Debug, Serialize)]
pub struct SizeConstraint {
min_size: Au,
max_size: Option<Au>,
}
impl SizeConstraint {
pub fn new(
container_size: Option<Au>,
min_size: &Size,
max_size: &MaxSize,
border: Option<Au>,
) -> SizeConstraint {
let mut min_size = min_size
.maybe_to_used_value(container_size)
.unwrap_or(Au(0));
let mut max_size = max_size.maybe_to_used_value(container_size);
max_size = max_size.map(|x| max(x, min_size));
if let Some(border) = border {
min_size = max(min_size - border, Au(0));
max_size = max_size.map(|x| max(x - border, Au(0)));
}
SizeConstraint { min_size, max_size }
}
pub fn clamp(&self, other: Au) -> Au {
if other < self.min_size {
self.min_size
} else {
match self.max_size {
Some(max_size) if max_size < other => max_size,
_ => other,
}
}
}
}