mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
layout: Split LineItem
layout into a new file (#31039)
This is just a bit of code movement that trims down the size of the `inline.rs` file in order to make it a bit more manageable. It leads the way to more refactoring and cleanup in the future.
This commit is contained in:
parent
bfa853a1cc
commit
3555671864
4 changed files with 658 additions and 603 deletions
|
@ -4,10 +4,8 @@
|
||||||
|
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::vec::IntoIter;
|
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use atomic_refcell::AtomicRef;
|
|
||||||
use gfx::font::FontMetrics;
|
use gfx::font::FontMetrics;
|
||||||
use gfx::text::glyph::GlyphStore;
|
use gfx::text::glyph::GlyphStore;
|
||||||
use gfx::text::text_run::GlyphRun;
|
use gfx::text::text_run::GlyphRun;
|
||||||
|
@ -21,7 +19,6 @@ use style::properties::ComputedValues;
|
||||||
use style::values::computed::{Length, LengthPercentage};
|
use style::values::computed::{Length, LengthPercentage};
|
||||||
use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
|
use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
|
||||||
use style::values::generics::text::LineHeight;
|
use style::values::generics::text::LineHeight;
|
||||||
use style::values::specified::box_::DisplayOutside as StyloDisplayOutside;
|
|
||||||
use style::values::specified::text::{TextAlignKeyword, TextDecorationLine};
|
use style::values::specified::text::{TextAlignKeyword, TextDecorationLine};
|
||||||
use style::values::specified::{TextAlignLast, TextJustify};
|
use style::values::specified::{TextAlignLast, TextJustify};
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
|
@ -29,6 +26,10 @@ use webrender_api::FontInstanceKey;
|
||||||
use xi_unicode::{linebreak_property, LineBreakLeafIter};
|
use xi_unicode::{linebreak_property, LineBreakLeafIter};
|
||||||
|
|
||||||
use super::float::PlacementAmongFloats;
|
use super::float::PlacementAmongFloats;
|
||||||
|
use super::line::{
|
||||||
|
layout_line_items, AbsolutelyPositionedLineItem, AtomicLineItem, FloatLineItem,
|
||||||
|
InlineBoxLineItem, LineItem, LineItemLayoutState, LineMetrics, TextRunLineItem,
|
||||||
|
};
|
||||||
use super::CollapsibleWithParentStartMargin;
|
use super::CollapsibleWithParentStartMargin;
|
||||||
use crate::cell::ArcRefCell;
|
use crate::cell::ArcRefCell;
|
||||||
use crate::context::LayoutContext;
|
use crate::context::LayoutContext;
|
||||||
|
@ -37,16 +38,12 @@ use crate::flow::FlowLayout;
|
||||||
use crate::formatting_contexts::IndependentFormattingContext;
|
use crate::formatting_contexts::IndependentFormattingContext;
|
||||||
use crate::fragment_tree::{
|
use crate::fragment_tree::{
|
||||||
AnonymousFragment, BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin,
|
AnonymousFragment, BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin,
|
||||||
Fragment, HoistedSharedFragment, TextFragment,
|
Fragment,
|
||||||
};
|
};
|
||||||
use crate::geom::{LogicalRect, LogicalVec2};
|
use crate::geom::{LogicalRect, LogicalVec2};
|
||||||
use crate::positioned::{
|
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
|
||||||
relative_adjustement, AbsolutelyPositionedBox, PositioningContext, PositioningContextLength,
|
|
||||||
};
|
|
||||||
use crate::sizing::ContentSizes;
|
use crate::sizing::ContentSizes;
|
||||||
use crate::style_ext::{
|
use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin};
|
||||||
ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside, PaddingBorderMargin,
|
|
||||||
};
|
|
||||||
use crate::ContainingBlock;
|
use crate::ContainingBlock;
|
||||||
|
|
||||||
// These constants are the xi-unicode line breaking classes that are defined in
|
// These constants are the xi-unicode line breaking classes that are defined in
|
||||||
|
@ -1669,7 +1666,7 @@ impl InlineContainerState {
|
||||||
// The baseline offset from `vertical-align` might adjust where our block size contribution is
|
// The baseline offset from `vertical-align` might adjust where our block size contribution is
|
||||||
// within the line.
|
// within the line.
|
||||||
baseline_offset = parent_container.get_cumulative_baseline_offset_for_child(
|
baseline_offset = parent_container.get_cumulative_baseline_offset_for_child(
|
||||||
effective_vertical_for_inline_container(&style),
|
style.effective_vertical_align_for_inline_layout(),
|
||||||
&strut_block_sizes,
|
&strut_block_sizes,
|
||||||
);
|
);
|
||||||
strut_block_sizes.adjust_for_baseline_offset(baseline_offset);
|
strut_block_sizes.adjust_for_baseline_offset(baseline_offset);
|
||||||
|
@ -1698,7 +1695,7 @@ impl InlineContainerState {
|
||||||
font_metrics: &FontMetrics,
|
font_metrics: &FontMetrics,
|
||||||
line_height: Length,
|
line_height: Length,
|
||||||
) -> LineBlockSizes {
|
) -> LineBlockSizes {
|
||||||
let vertical_align = effective_vertical_for_inline_container(style);
|
let vertical_align = style.effective_vertical_align_for_inline_layout();
|
||||||
if !is_baseline_relative(vertical_align) {
|
if !is_baseline_relative(vertical_align) {
|
||||||
return LineBlockSizes {
|
return LineBlockSizes {
|
||||||
line_height,
|
line_height,
|
||||||
|
@ -2282,81 +2279,6 @@ impl<'box_tree> Iterator for InlineBoxChildIter<'box_tree> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LineMetrics {
|
|
||||||
/// The block offset of the line start in the containing [`InlineFormattingContext`].
|
|
||||||
block_offset: Length,
|
|
||||||
|
|
||||||
/// The block size of this line.
|
|
||||||
block_size: Length,
|
|
||||||
|
|
||||||
/// The block offset of this line's baseline from [`Self:block_offset`].
|
|
||||||
baseline_block_offset: Length,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State used when laying out the [`LineItem`]s collected for the line currently being
|
|
||||||
/// laid out.
|
|
||||||
struct LineItemLayoutState<'a> {
|
|
||||||
inline_position: Length,
|
|
||||||
|
|
||||||
/// The offset of the parent, relative to the start position of the line.
|
|
||||||
parent_offset: LogicalVec2<Length>,
|
|
||||||
|
|
||||||
/// The block offset of the parent's baseline relative to the block start of the line. This
|
|
||||||
/// is often the same as [`Self::block_offset_of_parent`], but can be different for the root
|
|
||||||
/// element.
|
|
||||||
baseline_offset: Length,
|
|
||||||
|
|
||||||
ifc_containing_block: &'a ContainingBlock<'a>,
|
|
||||||
positioning_context: &'a mut PositioningContext,
|
|
||||||
|
|
||||||
/// The amount of space to add to each justification opportunity in order to implement
|
|
||||||
/// `text-align: justify`.
|
|
||||||
justification_adjustment: Length,
|
|
||||||
|
|
||||||
/// The metrics of this line, which should remain constant throughout the
|
|
||||||
/// layout process.
|
|
||||||
line_metrics: &'a LineMetrics,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout_line_items(
|
|
||||||
iterator: &mut IntoIter<LineItem>,
|
|
||||||
layout_context: &LayoutContext,
|
|
||||||
state: &mut LineItemLayoutState,
|
|
||||||
saw_end: &mut bool,
|
|
||||||
) -> Vec<Fragment> {
|
|
||||||
let mut fragments = vec![];
|
|
||||||
while let Some(item) = iterator.next() {
|
|
||||||
match item {
|
|
||||||
LineItem::TextRun(text_line_item) => {
|
|
||||||
if let Some(fragment) = text_line_item.layout(state) {
|
|
||||||
fragments.push(Fragment::Text(fragment));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LineItem::StartInlineBox(box_line_item) => {
|
|
||||||
if let Some(fragment) = box_line_item.layout(iterator, layout_context, state) {
|
|
||||||
fragments.push(Fragment::Box(fragment))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LineItem::EndInlineBox => {
|
|
||||||
*saw_end = true;
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
LineItem::Atomic(atomic_line_item) => {
|
|
||||||
fragments.push(Fragment::Box(atomic_line_item.layout(state)));
|
|
||||||
},
|
|
||||||
LineItem::AbsolutelyPositioned(absolute_line_item) => {
|
|
||||||
fragments.push(Fragment::AbsoluteOrFixedPositioned(
|
|
||||||
absolute_line_item.layout(state),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
LineItem::Float(float_line_item) => {
|
|
||||||
fragments.push(Fragment::Float(float_line_item.layout(state)));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragments
|
|
||||||
}
|
|
||||||
|
|
||||||
fn place_pending_floats(ifc: &mut InlineFormattingContextState, line_items: &mut Vec<LineItem>) {
|
fn place_pending_floats(ifc: &mut InlineFormattingContextState, line_items: &mut Vec<LineItem>) {
|
||||||
for item in line_items.into_iter() {
|
for item in line_items.into_iter() {
|
||||||
match item {
|
match item {
|
||||||
|
@ -2370,60 +2292,6 @@ fn place_pending_floats(ifc: &mut InlineFormattingContextState, line_items: &mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LineItem {
|
|
||||||
TextRun(TextRunLineItem),
|
|
||||||
StartInlineBox(InlineBoxLineItem),
|
|
||||||
EndInlineBox,
|
|
||||||
Atomic(AtomicLineItem),
|
|
||||||
AbsolutelyPositioned(AbsolutelyPositionedLineItem),
|
|
||||||
Float(FloatLineItem),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineItem {
|
|
||||||
fn trim_whitespace_at_end(
|
|
||||||
&mut self,
|
|
||||||
whitespace_trimmed: &mut Length,
|
|
||||||
spaces_trimmed: &mut usize,
|
|
||||||
) -> bool {
|
|
||||||
match self {
|
|
||||||
LineItem::TextRun(ref mut item) => {
|
|
||||||
item.trim_whitespace_at_end(whitespace_trimmed, spaces_trimmed)
|
|
||||||
},
|
|
||||||
LineItem::StartInlineBox(_) => true,
|
|
||||||
LineItem::EndInlineBox => true,
|
|
||||||
LineItem::Atomic(_) => false,
|
|
||||||
LineItem::AbsolutelyPositioned(_) => true,
|
|
||||||
LineItem::Float(_) => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trim_whitespace_at_start(
|
|
||||||
&mut self,
|
|
||||||
whitespace_trimmed: &mut Length,
|
|
||||||
spaces_trimmed: &mut usize,
|
|
||||||
) -> bool {
|
|
||||||
match self {
|
|
||||||
LineItem::TextRun(ref mut item) => {
|
|
||||||
item.trim_whitespace_at_start(whitespace_trimmed, spaces_trimmed)
|
|
||||||
},
|
|
||||||
LineItem::StartInlineBox(_) => true,
|
|
||||||
LineItem::EndInlineBox => true,
|
|
||||||
LineItem::Atomic(_) => false,
|
|
||||||
LineItem::AbsolutelyPositioned(_) => true,
|
|
||||||
LineItem::Float(_) => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TextRunLineItem {
|
|
||||||
base_fragment_info: BaseFragmentInfo,
|
|
||||||
parent_style: Arc<ComputedValues>,
|
|
||||||
text: Vec<std::sync::Arc<GlyphStore>>,
|
|
||||||
font_metrics: FontMetrics,
|
|
||||||
font_key: FontInstanceKey,
|
|
||||||
text_decoration_line: TextDecorationLine,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_height(parent_style: &ComputedValues, font_metrics: &FontMetrics) -> Length {
|
fn line_height(parent_style: &ComputedValues, font_metrics: &FontMetrics) -> Length {
|
||||||
let font_size = parent_style.get_font().font_size.computed_size();
|
let font_size = parent_style.get_font().font_size.computed_size();
|
||||||
match parent_style.get_inherited_text().line_height {
|
match parent_style.get_inherited_text().line_height {
|
||||||
|
@ -2448,458 +2316,6 @@ fn font_metrics_from_style(layout_context: &LayoutContext, style: &ComputedValue
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextRunLineItem {
|
|
||||||
fn trim_whitespace_at_end(
|
|
||||||
&mut self,
|
|
||||||
whitespace_trimmed: &mut Length,
|
|
||||||
spaces_trimmed: &mut usize,
|
|
||||||
) -> bool {
|
|
||||||
if self
|
|
||||||
.parent_style
|
|
||||||
.get_inherited_text()
|
|
||||||
.white_space
|
|
||||||
.preserve_spaces()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let index_of_last_non_whitespace = self
|
|
||||||
.text
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.position(|glyph| !glyph.is_whitespace())
|
|
||||||
.map(|offset_from_end| self.text.len() - offset_from_end);
|
|
||||||
|
|
||||||
let first_whitespace_index = index_of_last_non_whitespace.unwrap_or(0);
|
|
||||||
*spaces_trimmed += self.text.len() - first_whitespace_index;
|
|
||||||
*whitespace_trimmed += self
|
|
||||||
.text
|
|
||||||
.drain(first_whitespace_index..)
|
|
||||||
.map(|glyph| Length::from(glyph.total_advance()))
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
// Only keep going if we only encountered whitespace.
|
|
||||||
index_of_last_non_whitespace.is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trim_whitespace_at_start(
|
|
||||||
&mut self,
|
|
||||||
whitespace_trimmed: &mut Length,
|
|
||||||
spaces_trimmed: &mut usize,
|
|
||||||
) -> bool {
|
|
||||||
if self
|
|
||||||
.parent_style
|
|
||||||
.get_inherited_text()
|
|
||||||
.white_space
|
|
||||||
.preserve_spaces()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let index_of_first_non_whitespace = self
|
|
||||||
.text
|
|
||||||
.iter()
|
|
||||||
.position(|glyph| !glyph.is_whitespace())
|
|
||||||
.unwrap_or(self.text.len());
|
|
||||||
|
|
||||||
*spaces_trimmed += index_of_first_non_whitespace;
|
|
||||||
*whitespace_trimmed += self
|
|
||||||
.text
|
|
||||||
.drain(0..index_of_first_non_whitespace)
|
|
||||||
.map(|glyph| Length::from(glyph.total_advance()))
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
// Only keep going if we only encountered whitespace.
|
|
||||||
self.text.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout(self, state: &mut LineItemLayoutState) -> Option<TextFragment> {
|
|
||||||
if self.text.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut number_of_justification_opportunities = 0;
|
|
||||||
let mut inline_advance: Length = self
|
|
||||||
.text
|
|
||||||
.iter()
|
|
||||||
.map(|glyph_store| {
|
|
||||||
number_of_justification_opportunities += glyph_store.total_word_separators();
|
|
||||||
Length::from(glyph_store.total_advance())
|
|
||||||
})
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
if !state.justification_adjustment.is_zero() {
|
|
||||||
inline_advance +=
|
|
||||||
state.justification_adjustment * number_of_justification_opportunities as f32;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The block start of the TextRun is often zero (meaning it has the same font metrics as the
|
|
||||||
// inline box's strut), but for children of the inline formatting context root or for
|
|
||||||
// fallback fonts that use baseline relatve alignment, it might be different.
|
|
||||||
let mut start_corner = &LogicalVec2 {
|
|
||||||
inline: state.inline_position,
|
|
||||||
block: state.baseline_offset - self.font_metrics.ascent.into(),
|
|
||||||
} - &state.parent_offset;
|
|
||||||
if !is_baseline_relative(effective_vertical_for_inline_container(&self.parent_style)) {
|
|
||||||
start_corner.block = Length::zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
let rect = LogicalRect {
|
|
||||||
start_corner,
|
|
||||||
size: LogicalVec2 {
|
|
||||||
block: self.font_metrics.line_gap.into(),
|
|
||||||
inline: inline_advance,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
state.inline_position += inline_advance;
|
|
||||||
Some(TextFragment {
|
|
||||||
base: self.base_fragment_info.into(),
|
|
||||||
parent_style: self.parent_style,
|
|
||||||
rect,
|
|
||||||
font_metrics: self.font_metrics,
|
|
||||||
font_key: self.font_key,
|
|
||||||
glyphs: self.text,
|
|
||||||
text_decoration_line: self.text_decoration_line,
|
|
||||||
justification_adjustment: state.justification_adjustment,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct InlineBoxLineItem {
|
|
||||||
base_fragment_info: BaseFragmentInfo,
|
|
||||||
style: Arc<ComputedValues>,
|
|
||||||
pbm: PaddingBorderMargin,
|
|
||||||
|
|
||||||
/// Whether this is the first fragment for this inline box. This means that it's the
|
|
||||||
/// first potentially split box of a block-in-inline-split (or only if there's no
|
|
||||||
/// split) and also the first appearance of this fragment on any line.
|
|
||||||
is_first_fragment: bool,
|
|
||||||
|
|
||||||
/// Whether this is the last fragment for this inline box. This means that it's the
|
|
||||||
/// last potentially split box of a block-in-inline-split (or the only fragment if
|
|
||||||
/// there's no split).
|
|
||||||
is_last_fragment_of_ib_split: bool,
|
|
||||||
|
|
||||||
/// The FontMetrics for the default font used in this inline box.
|
|
||||||
font_metrics: FontMetrics,
|
|
||||||
|
|
||||||
/// The block offset of this baseline relative to the baseline of the line. This will be
|
|
||||||
/// zero for boxes with `vertical-align: top` and `vertical-align: bottom` since their
|
|
||||||
/// baselines are calculated late in layout.
|
|
||||||
baseline_offset: Au,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InlineBoxLineItem {
|
|
||||||
fn layout(
|
|
||||||
self,
|
|
||||||
iterator: &mut IntoIter<LineItem>,
|
|
||||||
layout_context: &LayoutContext,
|
|
||||||
state: &mut LineItemLayoutState,
|
|
||||||
) -> Option<BoxFragment> {
|
|
||||||
let style = self.style.clone();
|
|
||||||
let mut padding = self.pbm.padding.clone();
|
|
||||||
let mut border = self.pbm.border.clone();
|
|
||||||
let mut margin = self.pbm.margin.auto_is(Length::zero);
|
|
||||||
|
|
||||||
if !self.is_first_fragment {
|
|
||||||
padding.inline_start = Length::zero();
|
|
||||||
border.inline_start = Length::zero();
|
|
||||||
margin.inline_start = Length::zero();
|
|
||||||
}
|
|
||||||
if !self.is_last_fragment_of_ib_split {
|
|
||||||
padding.inline_end = Length::zero();
|
|
||||||
border.inline_end = Length::zero();
|
|
||||||
margin.inline_end = Length::zero();
|
|
||||||
}
|
|
||||||
let pbm_sums = &(&padding + &border) + &margin;
|
|
||||||
state.inline_position += pbm_sums.inline_start;
|
|
||||||
|
|
||||||
let space_above_baseline = self.calculate_space_above_baseline();
|
|
||||||
let block_start_offset = self.calculate_block_start(state, space_above_baseline);
|
|
||||||
|
|
||||||
let mut positioning_context = PositioningContext::new_for_style(&style);
|
|
||||||
let nested_positioning_context = match positioning_context.as_mut() {
|
|
||||||
Some(positioning_context) => positioning_context,
|
|
||||||
None => &mut state.positioning_context,
|
|
||||||
};
|
|
||||||
let original_nested_positioning_context_length = nested_positioning_context.len();
|
|
||||||
|
|
||||||
let mut nested_state = LineItemLayoutState {
|
|
||||||
inline_position: state.inline_position,
|
|
||||||
parent_offset: LogicalVec2 {
|
|
||||||
inline: state.inline_position,
|
|
||||||
block: block_start_offset,
|
|
||||||
},
|
|
||||||
ifc_containing_block: state.ifc_containing_block,
|
|
||||||
positioning_context: nested_positioning_context,
|
|
||||||
justification_adjustment: state.justification_adjustment,
|
|
||||||
line_metrics: state.line_metrics,
|
|
||||||
baseline_offset: block_start_offset + space_above_baseline,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut saw_end = false;
|
|
||||||
let fragments =
|
|
||||||
layout_line_items(iterator, layout_context, &mut nested_state, &mut saw_end);
|
|
||||||
|
|
||||||
// Only add ending padding, border, margin if this is the last fragment of a
|
|
||||||
// potential block-in-inline split and this line included the actual end of this
|
|
||||||
// fragment (it doesn't continue on the next line).
|
|
||||||
if !self.is_last_fragment_of_ib_split || !saw_end {
|
|
||||||
padding.inline_end = Length::zero();
|
|
||||||
border.inline_end = Length::zero();
|
|
||||||
margin.inline_end = Length::zero();
|
|
||||||
}
|
|
||||||
let pbm_sums = &(&padding + &border) + &margin;
|
|
||||||
|
|
||||||
// If the inline box didn't have any content at all, don't add a Fragment for it.
|
|
||||||
let box_has_padding_border_or_margin = pbm_sums.inline_sum() > Length::zero();
|
|
||||||
let box_had_absolutes =
|
|
||||||
original_nested_positioning_context_length != nested_state.positioning_context.len();
|
|
||||||
if !self.is_first_fragment &&
|
|
||||||
fragments.is_empty() &&
|
|
||||||
!box_has_padding_border_or_margin &&
|
|
||||||
!box_had_absolutes
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut content_rect = LogicalRect {
|
|
||||||
start_corner: LogicalVec2 {
|
|
||||||
inline: state.inline_position,
|
|
||||||
block: block_start_offset,
|
|
||||||
},
|
|
||||||
size: LogicalVec2 {
|
|
||||||
inline: nested_state.inline_position - state.inline_position,
|
|
||||||
block: self.font_metrics.line_gap.into(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Make `content_rect` relative to the parent Fragment.
|
|
||||||
content_rect.start_corner = &content_rect.start_corner - &state.parent_offset;
|
|
||||||
|
|
||||||
// Relative adjustment should not affect the rest of line layout, so we can
|
|
||||||
// do it right before creating the Fragment.
|
|
||||||
if style.clone_position().is_relative() {
|
|
||||||
content_rect.start_corner += &relative_adjustement(&style, state.ifc_containing_block);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut fragment = BoxFragment::new(
|
|
||||||
self.base_fragment_info,
|
|
||||||
self.style.clone(),
|
|
||||||
fragments,
|
|
||||||
content_rect,
|
|
||||||
padding,
|
|
||||||
border,
|
|
||||||
margin,
|
|
||||||
None, /* clearance */
|
|
||||||
// There is no need to set a baseline offset for this BoxFragment, because
|
|
||||||
// the last baseline of this InlineFormattingContext is what will propagate
|
|
||||||
// to `display: inline-block` ancestors.
|
|
||||||
None, /* last_inflow_baseline_offset */
|
|
||||||
CollapsedBlockMargins::zero(),
|
|
||||||
);
|
|
||||||
|
|
||||||
state.inline_position = nested_state.inline_position + pbm_sums.inline_end;
|
|
||||||
|
|
||||||
if let Some(mut positioning_context) = positioning_context.take() {
|
|
||||||
assert!(original_nested_positioning_context_length == PositioningContextLength::zero());
|
|
||||||
positioning_context.layout_collected_children(layout_context, &mut fragment);
|
|
||||||
positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
|
|
||||||
&fragment.content_rect.start_corner,
|
|
||||||
PositioningContextLength::zero(),
|
|
||||||
);
|
|
||||||
state.positioning_context.append(positioning_context);
|
|
||||||
} else {
|
|
||||||
state
|
|
||||||
.positioning_context
|
|
||||||
.adjust_static_position_of_hoisted_fragments_with_offset(
|
|
||||||
&fragment.content_rect.start_corner,
|
|
||||||
original_nested_positioning_context_length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(fragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given our font metrics, calculate the space above the baseline we need for our content.
|
|
||||||
/// Note that this space does not include space for any content in child inline boxes, as
|
|
||||||
/// they are not included in our content rect.
|
|
||||||
fn calculate_space_above_baseline(&self) -> Length {
|
|
||||||
let (ascent, descent, line_gap) = (
|
|
||||||
self.font_metrics.ascent,
|
|
||||||
self.font_metrics.descent,
|
|
||||||
self.font_metrics.line_gap,
|
|
||||||
);
|
|
||||||
let leading = line_gap - (ascent + descent);
|
|
||||||
(leading.scale_by(0.5) + ascent).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given the state for a line item layout and the space above the baseline for this inline
|
|
||||||
/// box, find the block start position relative to the line block start position.
|
|
||||||
fn calculate_block_start(
|
|
||||||
&self,
|
|
||||||
state: &LineItemLayoutState,
|
|
||||||
space_above_baseline: Length,
|
|
||||||
) -> Length {
|
|
||||||
let vertical_align = effective_vertical_for_inline_container(&self.style);
|
|
||||||
let line_gap = self.font_metrics.line_gap;
|
|
||||||
|
|
||||||
// The baseline offset that we have in `Self::baseline_offset` is relative to the line
|
|
||||||
// baseline, so we need to make it relative to the line block start.
|
|
||||||
match vertical_align {
|
|
||||||
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => {
|
|
||||||
let line_height = line_height(&self.style, &self.font_metrics);
|
|
||||||
(line_height - line_gap.into()).scale_by(0.5)
|
|
||||||
},
|
|
||||||
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
|
|
||||||
let line_height = line_height(&self.style, &self.font_metrics);
|
|
||||||
let half_leading = (line_height - line_gap.into()).scale_by(0.5);
|
|
||||||
state.line_metrics.block_size - line_height + half_leading
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
state.line_metrics.baseline_block_offset + Length::from(self.baseline_offset) -
|
|
||||||
space_above_baseline
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AtomicLineItem {
|
|
||||||
fragment: BoxFragment,
|
|
||||||
size: LogicalVec2<Length>,
|
|
||||||
positioning_context: Option<PositioningContext>,
|
|
||||||
|
|
||||||
/// The block offset of this items' baseline relative to the baseline of the line.
|
|
||||||
/// This will be zero for boxes with `vertical-align: top` and `vertical-align:
|
|
||||||
/// bottom` since their baselines are calculated late in layout.
|
|
||||||
baseline_offset_in_parent: Au,
|
|
||||||
|
|
||||||
/// The offset of the baseline inside this item.
|
|
||||||
baseline_offset_in_item: Au,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AtomicLineItem {
|
|
||||||
fn layout(mut self, state: &mut LineItemLayoutState) -> BoxFragment {
|
|
||||||
// The initial `start_corner` of the Fragment is only the PaddingBorderMargin sum start
|
|
||||||
// offset, which is the sum of the start component of the padding, border, and margin.
|
|
||||||
// This needs to be added to the calculated block and inline positions.
|
|
||||||
self.fragment.content_rect.start_corner.inline += state.inline_position;
|
|
||||||
self.fragment.content_rect.start_corner.block +=
|
|
||||||
self.calculate_block_start(&state.line_metrics);
|
|
||||||
|
|
||||||
// Make the final result relative to the parent box.
|
|
||||||
self.fragment.content_rect.start_corner =
|
|
||||||
&self.fragment.content_rect.start_corner - &state.parent_offset;
|
|
||||||
|
|
||||||
if self.fragment.style.clone_position().is_relative() {
|
|
||||||
self.fragment.content_rect.start_corner +=
|
|
||||||
&relative_adjustement(&self.fragment.style, state.ifc_containing_block);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.inline_position += self.size.inline;
|
|
||||||
|
|
||||||
if let Some(mut positioning_context) = self.positioning_context {
|
|
||||||
positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
|
|
||||||
&self.fragment.content_rect.start_corner,
|
|
||||||
PositioningContextLength::zero(),
|
|
||||||
);
|
|
||||||
state.positioning_context.append(positioning_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fragment
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given the metrics for a line, our vertical alignment, and our block size, find a block start
|
|
||||||
/// position relative to the top of the line.
|
|
||||||
fn calculate_block_start(&self, line_metrics: &LineMetrics) -> Length {
|
|
||||||
match self.fragment.style.clone_vertical_align() {
|
|
||||||
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Length::zero(),
|
|
||||||
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
|
|
||||||
line_metrics.block_size - self.size.block
|
|
||||||
},
|
|
||||||
|
|
||||||
// This covers all baseline-relative vertical alignment.
|
|
||||||
_ => {
|
|
||||||
let baseline = line_metrics.baseline_block_offset +
|
|
||||||
Length::from(self.baseline_offset_in_parent);
|
|
||||||
baseline - Length::from(self.baseline_offset_in_item)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AbsolutelyPositionedLineItem {
|
|
||||||
absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AbsolutelyPositionedLineItem {
|
|
||||||
fn layout(self, state: &mut LineItemLayoutState) -> ArcRefCell<HoistedSharedFragment> {
|
|
||||||
let box_ = self.absolutely_positioned_box;
|
|
||||||
let style = AtomicRef::map(box_.borrow(), |box_| box_.context.style());
|
|
||||||
let initial_start_corner = match Display::from(style.get_box().original_display) {
|
|
||||||
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside: _ }) => {
|
|
||||||
LogicalVec2 {
|
|
||||||
inline: match outside {
|
|
||||||
DisplayOutside::Inline => {
|
|
||||||
state.inline_position - state.parent_offset.inline
|
|
||||||
},
|
|
||||||
DisplayOutside::Block => Length::zero(),
|
|
||||||
},
|
|
||||||
// The blocks start position of the absolute should be at the top of the line.
|
|
||||||
block: -state.parent_offset.block,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(_)) => {
|
|
||||||
unreachable!(
|
|
||||||
"The result of blockification should never be a layout-internal value."
|
|
||||||
);
|
|
||||||
},
|
|
||||||
Display::Contents => {
|
|
||||||
panic!("display:contents does not generate an abspos box")
|
|
||||||
},
|
|
||||||
Display::None => {
|
|
||||||
panic!("display:none does not generate an abspos box")
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
|
|
||||||
box_.clone(),
|
|
||||||
initial_start_corner,
|
|
||||||
state.ifc_containing_block,
|
|
||||||
);
|
|
||||||
let hoisted_fragment = hoisted_box.fragment.clone();
|
|
||||||
state.positioning_context.push(hoisted_box);
|
|
||||||
hoisted_fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FloatLineItem {
|
|
||||||
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 {
|
|
||||||
fn layout(mut self, state: &mut LineItemLayoutState<'_>) -> BoxFragment {
|
|
||||||
// The `BoxFragment` for this float is positioned relative to the IFC, so we need
|
|
||||||
// to move it to be positioned relative to our parent InlineBox line item. Floats
|
|
||||||
// fragments are children of these InlineBoxes and not children of the inline
|
|
||||||
// formatting context, so that they are parented properly for StackingContext
|
|
||||||
// properties such as opacity & filters.
|
|
||||||
let distance_from_parent_to_ifc = LogicalVec2 {
|
|
||||||
inline: state.parent_offset.inline,
|
|
||||||
block: state.line_metrics.block_offset + state.parent_offset.block,
|
|
||||||
};
|
|
||||||
self.fragment.content_rect.start_corner =
|
|
||||||
&self.fragment.content_rect.start_corner - &distance_from_parent_to_ifc;
|
|
||||||
self.fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether or not this character prevents a soft line wrap opportunity when it
|
|
||||||
/// comes before or after an atomic inline element.
|
/// comes before or after an atomic inline element.
|
||||||
///
|
///
|
||||||
/// From https://www.w3.org/TR/css-text-3/#line-break-details:
|
/// From https://www.w3.org/TR/css-text-3/#line-break-details:
|
||||||
|
@ -2928,15 +2344,6 @@ fn is_baseline_relative(vertical_align: GenericVerticalAlign<LengthPercentage>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn effective_vertical_for_inline_container(
|
|
||||||
style: &ComputedValues,
|
|
||||||
) -> GenericVerticalAlign<LengthPercentage> {
|
|
||||||
match style.clone_display().outside() {
|
|
||||||
StyloDisplayOutside::Block => GenericVerticalAlign::Keyword(VerticalAlignKeyword::Baseline),
|
|
||||||
_ => style.clone_vertical_align(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether or not a strut should be created for an inline container. Normally
|
/// Whether or not a strut should be created for an inline container. Normally
|
||||||
/// all inline containers get struts. In quirks mode this isn't always the case
|
/// all inline containers get struts. In quirks mode this isn't always the case
|
||||||
/// though.
|
/// though.
|
||||||
|
|
633
components/layout_2020/flow/line.rs
Normal file
633
components/layout_2020/flow/line.rs
Normal file
|
@ -0,0 +1,633 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 std::vec::IntoIter;
|
||||||
|
|
||||||
|
use app_units::Au;
|
||||||
|
use atomic_refcell::AtomicRef;
|
||||||
|
use gfx::font::FontMetrics;
|
||||||
|
use gfx::text::glyph::GlyphStore;
|
||||||
|
use servo_arc::Arc;
|
||||||
|
use style::properties::ComputedValues;
|
||||||
|
use style::values::computed::{Length, LengthPercentage};
|
||||||
|
use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
|
||||||
|
use style::values::generics::text::LineHeight;
|
||||||
|
use style::values::specified::text::TextDecorationLine;
|
||||||
|
use style::Zero;
|
||||||
|
use webrender_api::FontInstanceKey;
|
||||||
|
|
||||||
|
use crate::cell::ArcRefCell;
|
||||||
|
use crate::context::LayoutContext;
|
||||||
|
use crate::fragment_tree::{
|
||||||
|
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, Fragment, HoistedSharedFragment,
|
||||||
|
TextFragment,
|
||||||
|
};
|
||||||
|
use crate::geom::{LogicalRect, LogicalVec2};
|
||||||
|
use crate::positioned::{
|
||||||
|
relative_adjustement, AbsolutelyPositionedBox, PositioningContext, PositioningContextLength,
|
||||||
|
};
|
||||||
|
use crate::style_ext::{
|
||||||
|
ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside, PaddingBorderMargin,
|
||||||
|
};
|
||||||
|
use crate::ContainingBlock;
|
||||||
|
|
||||||
|
pub(super) struct LineMetrics {
|
||||||
|
/// The block offset of the line start in the containing [`InlineFormattingContext`].
|
||||||
|
pub block_offset: Length,
|
||||||
|
|
||||||
|
/// The block size of this line.
|
||||||
|
pub block_size: Length,
|
||||||
|
|
||||||
|
/// The block offset of this line's baseline from [`Self:block_offset`].
|
||||||
|
pub baseline_block_offset: Length,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State used when laying out the [`LineItem`]s collected for the line currently being
|
||||||
|
/// laid out.
|
||||||
|
pub(super) struct LineItemLayoutState<'a> {
|
||||||
|
pub inline_position: Length,
|
||||||
|
|
||||||
|
/// The offset of the parent, relative to the start position of the line.
|
||||||
|
pub parent_offset: LogicalVec2<Length>,
|
||||||
|
|
||||||
|
/// The block offset of the parent's baseline relative to the block start of the line. This
|
||||||
|
/// is often the same as [`Self::block_offset_of_parent`], but can be different for the root
|
||||||
|
/// element.
|
||||||
|
pub baseline_offset: Length,
|
||||||
|
|
||||||
|
pub ifc_containing_block: &'a ContainingBlock<'a>,
|
||||||
|
pub positioning_context: &'a mut PositioningContext,
|
||||||
|
|
||||||
|
/// The amount of space to add to each justification opportunity in order to implement
|
||||||
|
/// `text-align: justify`.
|
||||||
|
pub justification_adjustment: Length,
|
||||||
|
|
||||||
|
/// The metrics of this line, which should remain constant throughout the
|
||||||
|
/// layout process.
|
||||||
|
pub line_metrics: &'a LineMetrics,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn layout_line_items(
|
||||||
|
iterator: &mut IntoIter<LineItem>,
|
||||||
|
layout_context: &LayoutContext,
|
||||||
|
state: &mut LineItemLayoutState,
|
||||||
|
saw_end: &mut bool,
|
||||||
|
) -> Vec<Fragment> {
|
||||||
|
let mut fragments = vec![];
|
||||||
|
while let Some(item) = iterator.next() {
|
||||||
|
match item {
|
||||||
|
LineItem::TextRun(text_line_item) => {
|
||||||
|
if let Some(fragment) = text_line_item.layout(state) {
|
||||||
|
fragments.push(Fragment::Text(fragment));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LineItem::StartInlineBox(box_line_item) => {
|
||||||
|
if let Some(fragment) = box_line_item.layout(iterator, layout_context, state) {
|
||||||
|
fragments.push(Fragment::Box(fragment))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LineItem::EndInlineBox => {
|
||||||
|
*saw_end = true;
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
LineItem::Atomic(atomic_line_item) => {
|
||||||
|
fragments.push(Fragment::Box(atomic_line_item.layout(state)));
|
||||||
|
},
|
||||||
|
LineItem::AbsolutelyPositioned(absolute_line_item) => {
|
||||||
|
fragments.push(Fragment::AbsoluteOrFixedPositioned(
|
||||||
|
absolute_line_item.layout(state),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
LineItem::Float(float_line_item) => {
|
||||||
|
fragments.push(Fragment::Float(float_line_item.layout(state)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragments
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) enum LineItem {
|
||||||
|
TextRun(TextRunLineItem),
|
||||||
|
StartInlineBox(InlineBoxLineItem),
|
||||||
|
EndInlineBox,
|
||||||
|
Atomic(AtomicLineItem),
|
||||||
|
AbsolutelyPositioned(AbsolutelyPositionedLineItem),
|
||||||
|
Float(FloatLineItem),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineItem {
|
||||||
|
pub(super) fn trim_whitespace_at_end(
|
||||||
|
&mut self,
|
||||||
|
whitespace_trimmed: &mut Length,
|
||||||
|
spaces_trimmed: &mut usize,
|
||||||
|
) -> bool {
|
||||||
|
match self {
|
||||||
|
LineItem::TextRun(ref mut item) => {
|
||||||
|
item.trim_whitespace_at_end(whitespace_trimmed, spaces_trimmed)
|
||||||
|
},
|
||||||
|
LineItem::StartInlineBox(_) => true,
|
||||||
|
LineItem::EndInlineBox => true,
|
||||||
|
LineItem::Atomic(_) => false,
|
||||||
|
LineItem::AbsolutelyPositioned(_) => true,
|
||||||
|
LineItem::Float(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn trim_whitespace_at_start(
|
||||||
|
&mut self,
|
||||||
|
whitespace_trimmed: &mut Length,
|
||||||
|
spaces_trimmed: &mut usize,
|
||||||
|
) -> bool {
|
||||||
|
match self {
|
||||||
|
LineItem::TextRun(ref mut item) => {
|
||||||
|
item.trim_whitespace_at_start(whitespace_trimmed, spaces_trimmed)
|
||||||
|
},
|
||||||
|
LineItem::StartInlineBox(_) => true,
|
||||||
|
LineItem::EndInlineBox => true,
|
||||||
|
LineItem::Atomic(_) => false,
|
||||||
|
LineItem::AbsolutelyPositioned(_) => true,
|
||||||
|
LineItem::Float(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct TextRunLineItem {
|
||||||
|
pub base_fragment_info: BaseFragmentInfo,
|
||||||
|
pub parent_style: Arc<ComputedValues>,
|
||||||
|
pub text: Vec<std::sync::Arc<GlyphStore>>,
|
||||||
|
pub font_metrics: FontMetrics,
|
||||||
|
pub font_key: FontInstanceKey,
|
||||||
|
pub text_decoration_line: TextDecorationLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextRunLineItem {
|
||||||
|
fn trim_whitespace_at_end(
|
||||||
|
&mut self,
|
||||||
|
whitespace_trimmed: &mut Length,
|
||||||
|
spaces_trimmed: &mut usize,
|
||||||
|
) -> bool {
|
||||||
|
if self
|
||||||
|
.parent_style
|
||||||
|
.get_inherited_text()
|
||||||
|
.white_space
|
||||||
|
.preserve_spaces()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index_of_last_non_whitespace = self
|
||||||
|
.text
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.position(|glyph| !glyph.is_whitespace())
|
||||||
|
.map(|offset_from_end| self.text.len() - offset_from_end);
|
||||||
|
|
||||||
|
let first_whitespace_index = index_of_last_non_whitespace.unwrap_or(0);
|
||||||
|
*spaces_trimmed += self.text.len() - first_whitespace_index;
|
||||||
|
*whitespace_trimmed += self
|
||||||
|
.text
|
||||||
|
.drain(first_whitespace_index..)
|
||||||
|
.map(|glyph| Length::from(glyph.total_advance()))
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
// Only keep going if we only encountered whitespace.
|
||||||
|
index_of_last_non_whitespace.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trim_whitespace_at_start(
|
||||||
|
&mut self,
|
||||||
|
whitespace_trimmed: &mut Length,
|
||||||
|
spaces_trimmed: &mut usize,
|
||||||
|
) -> bool {
|
||||||
|
if self
|
||||||
|
.parent_style
|
||||||
|
.get_inherited_text()
|
||||||
|
.white_space
|
||||||
|
.preserve_spaces()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index_of_first_non_whitespace = self
|
||||||
|
.text
|
||||||
|
.iter()
|
||||||
|
.position(|glyph| !glyph.is_whitespace())
|
||||||
|
.unwrap_or(self.text.len());
|
||||||
|
|
||||||
|
*spaces_trimmed += index_of_first_non_whitespace;
|
||||||
|
*whitespace_trimmed += self
|
||||||
|
.text
|
||||||
|
.drain(0..index_of_first_non_whitespace)
|
||||||
|
.map(|glyph| Length::from(glyph.total_advance()))
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
// Only keep going if we only encountered whitespace.
|
||||||
|
self.text.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(self, state: &mut LineItemLayoutState) -> Option<TextFragment> {
|
||||||
|
if self.text.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut number_of_justification_opportunities = 0;
|
||||||
|
let mut inline_advance: Length = self
|
||||||
|
.text
|
||||||
|
.iter()
|
||||||
|
.map(|glyph_store| {
|
||||||
|
number_of_justification_opportunities += glyph_store.total_word_separators();
|
||||||
|
Length::from(glyph_store.total_advance())
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
if !state.justification_adjustment.is_zero() {
|
||||||
|
inline_advance +=
|
||||||
|
state.justification_adjustment * number_of_justification_opportunities as f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The block start of the TextRun is often zero (meaning it has the same font metrics as the
|
||||||
|
// inline box's strut), but for children of the inline formatting context root or for
|
||||||
|
// fallback fonts that use baseline relatve alignment, it might be different.
|
||||||
|
let mut start_corner = &LogicalVec2 {
|
||||||
|
inline: state.inline_position,
|
||||||
|
block: state.baseline_offset - self.font_metrics.ascent.into(),
|
||||||
|
} - &state.parent_offset;
|
||||||
|
if !is_baseline_relative(
|
||||||
|
self.parent_style
|
||||||
|
.effective_vertical_align_for_inline_layout(),
|
||||||
|
) {
|
||||||
|
start_corner.block = Length::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
let rect = LogicalRect {
|
||||||
|
start_corner,
|
||||||
|
size: LogicalVec2 {
|
||||||
|
block: self.font_metrics.line_gap.into(),
|
||||||
|
inline: inline_advance,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
state.inline_position += inline_advance;
|
||||||
|
Some(TextFragment {
|
||||||
|
base: self.base_fragment_info.into(),
|
||||||
|
parent_style: self.parent_style,
|
||||||
|
rect,
|
||||||
|
font_metrics: self.font_metrics,
|
||||||
|
font_key: self.font_key,
|
||||||
|
glyphs: self.text,
|
||||||
|
text_decoration_line: self.text_decoration_line,
|
||||||
|
justification_adjustment: state.justification_adjustment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct InlineBoxLineItem {
|
||||||
|
pub base_fragment_info: BaseFragmentInfo,
|
||||||
|
pub style: Arc<ComputedValues>,
|
||||||
|
pub pbm: PaddingBorderMargin,
|
||||||
|
|
||||||
|
/// Whether this is the first fragment for this inline box. This means that it's the
|
||||||
|
/// first potentially split box of a block-in-inline-split (or only if there's no
|
||||||
|
/// split) and also the first appearance of this fragment on any line.
|
||||||
|
pub is_first_fragment: bool,
|
||||||
|
|
||||||
|
/// Whether this is the last fragment for this inline box. This means that it's the
|
||||||
|
/// last potentially split box of a block-in-inline-split (or the only fragment if
|
||||||
|
/// there's no split).
|
||||||
|
pub is_last_fragment_of_ib_split: bool,
|
||||||
|
|
||||||
|
/// The FontMetrics for the default font used in this inline box.
|
||||||
|
pub font_metrics: FontMetrics,
|
||||||
|
|
||||||
|
/// The block offset of this baseline relative to the baseline of the line. This will be
|
||||||
|
/// zero for boxes with `vertical-align: top` and `vertical-align: bottom` since their
|
||||||
|
/// baselines are calculated late in layout.
|
||||||
|
pub baseline_offset: Au,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlineBoxLineItem {
|
||||||
|
fn layout(
|
||||||
|
self,
|
||||||
|
iterator: &mut IntoIter<LineItem>,
|
||||||
|
layout_context: &LayoutContext,
|
||||||
|
state: &mut LineItemLayoutState,
|
||||||
|
) -> Option<BoxFragment> {
|
||||||
|
let style = self.style.clone();
|
||||||
|
let mut padding = self.pbm.padding.clone();
|
||||||
|
let mut border = self.pbm.border.clone();
|
||||||
|
let mut margin = self.pbm.margin.auto_is(Length::zero);
|
||||||
|
|
||||||
|
if !self.is_first_fragment {
|
||||||
|
padding.inline_start = Length::zero();
|
||||||
|
border.inline_start = Length::zero();
|
||||||
|
margin.inline_start = Length::zero();
|
||||||
|
}
|
||||||
|
if !self.is_last_fragment_of_ib_split {
|
||||||
|
padding.inline_end = Length::zero();
|
||||||
|
border.inline_end = Length::zero();
|
||||||
|
margin.inline_end = Length::zero();
|
||||||
|
}
|
||||||
|
let pbm_sums = &(&padding + &border) + &margin;
|
||||||
|
state.inline_position += pbm_sums.inline_start;
|
||||||
|
|
||||||
|
let space_above_baseline = self.calculate_space_above_baseline();
|
||||||
|
let block_start_offset = self.calculate_block_start(state, space_above_baseline);
|
||||||
|
|
||||||
|
let mut positioning_context = PositioningContext::new_for_style(&style);
|
||||||
|
let nested_positioning_context = match positioning_context.as_mut() {
|
||||||
|
Some(positioning_context) => positioning_context,
|
||||||
|
None => &mut state.positioning_context,
|
||||||
|
};
|
||||||
|
let original_nested_positioning_context_length = nested_positioning_context.len();
|
||||||
|
|
||||||
|
let mut nested_state = LineItemLayoutState {
|
||||||
|
inline_position: state.inline_position,
|
||||||
|
parent_offset: LogicalVec2 {
|
||||||
|
inline: state.inline_position,
|
||||||
|
block: block_start_offset,
|
||||||
|
},
|
||||||
|
ifc_containing_block: state.ifc_containing_block,
|
||||||
|
positioning_context: nested_positioning_context,
|
||||||
|
justification_adjustment: state.justification_adjustment,
|
||||||
|
line_metrics: state.line_metrics,
|
||||||
|
baseline_offset: block_start_offset + space_above_baseline,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut saw_end = false;
|
||||||
|
let fragments =
|
||||||
|
layout_line_items(iterator, layout_context, &mut nested_state, &mut saw_end);
|
||||||
|
|
||||||
|
// Only add ending padding, border, margin if this is the last fragment of a
|
||||||
|
// potential block-in-inline split and this line included the actual end of this
|
||||||
|
// fragment (it doesn't continue on the next line).
|
||||||
|
if !self.is_last_fragment_of_ib_split || !saw_end {
|
||||||
|
padding.inline_end = Length::zero();
|
||||||
|
border.inline_end = Length::zero();
|
||||||
|
margin.inline_end = Length::zero();
|
||||||
|
}
|
||||||
|
let pbm_sums = &(&padding + &border) + &margin;
|
||||||
|
|
||||||
|
// If the inline box didn't have any content at all, don't add a Fragment for it.
|
||||||
|
let box_has_padding_border_or_margin = pbm_sums.inline_sum() > Length::zero();
|
||||||
|
let box_had_absolutes =
|
||||||
|
original_nested_positioning_context_length != nested_state.positioning_context.len();
|
||||||
|
if !self.is_first_fragment &&
|
||||||
|
fragments.is_empty() &&
|
||||||
|
!box_has_padding_border_or_margin &&
|
||||||
|
!box_had_absolutes
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut content_rect = LogicalRect {
|
||||||
|
start_corner: LogicalVec2 {
|
||||||
|
inline: state.inline_position,
|
||||||
|
block: block_start_offset,
|
||||||
|
},
|
||||||
|
size: LogicalVec2 {
|
||||||
|
inline: nested_state.inline_position - state.inline_position,
|
||||||
|
block: self.font_metrics.line_gap.into(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make `content_rect` relative to the parent Fragment.
|
||||||
|
content_rect.start_corner = &content_rect.start_corner - &state.parent_offset;
|
||||||
|
|
||||||
|
// Relative adjustment should not affect the rest of line layout, so we can
|
||||||
|
// do it right before creating the Fragment.
|
||||||
|
if style.clone_position().is_relative() {
|
||||||
|
content_rect.start_corner += &relative_adjustement(&style, state.ifc_containing_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fragment = BoxFragment::new(
|
||||||
|
self.base_fragment_info,
|
||||||
|
self.style.clone(),
|
||||||
|
fragments,
|
||||||
|
content_rect,
|
||||||
|
padding,
|
||||||
|
border,
|
||||||
|
margin,
|
||||||
|
None, /* clearance */
|
||||||
|
// There is no need to set a baseline offset for this BoxFragment, because
|
||||||
|
// the last baseline of this InlineFormattingContext is what will propagate
|
||||||
|
// to `display: inline-block` ancestors.
|
||||||
|
None, /* last_inflow_baseline_offset */
|
||||||
|
CollapsedBlockMargins::zero(),
|
||||||
|
);
|
||||||
|
|
||||||
|
state.inline_position = nested_state.inline_position + pbm_sums.inline_end;
|
||||||
|
|
||||||
|
if let Some(mut positioning_context) = positioning_context.take() {
|
||||||
|
assert!(original_nested_positioning_context_length == PositioningContextLength::zero());
|
||||||
|
positioning_context.layout_collected_children(layout_context, &mut fragment);
|
||||||
|
positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
|
||||||
|
&fragment.content_rect.start_corner,
|
||||||
|
PositioningContextLength::zero(),
|
||||||
|
);
|
||||||
|
state.positioning_context.append(positioning_context);
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
.positioning_context
|
||||||
|
.adjust_static_position_of_hoisted_fragments_with_offset(
|
||||||
|
&fragment.content_rect.start_corner,
|
||||||
|
original_nested_positioning_context_length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given our font metrics, calculate the space above the baseline we need for our content.
|
||||||
|
/// Note that this space does not include space for any content in child inline boxes, as
|
||||||
|
/// they are not included in our content rect.
|
||||||
|
fn calculate_space_above_baseline(&self) -> Length {
|
||||||
|
let (ascent, descent, line_gap) = (
|
||||||
|
self.font_metrics.ascent,
|
||||||
|
self.font_metrics.descent,
|
||||||
|
self.font_metrics.line_gap,
|
||||||
|
);
|
||||||
|
let leading = line_gap - (ascent + descent);
|
||||||
|
(leading.scale_by(0.5) + ascent).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given the state for a line item layout and the space above the baseline for this inline
|
||||||
|
/// box, find the block start position relative to the line block start position.
|
||||||
|
fn calculate_block_start(
|
||||||
|
&self,
|
||||||
|
state: &LineItemLayoutState,
|
||||||
|
space_above_baseline: Length,
|
||||||
|
) -> Length {
|
||||||
|
let vertical_align = self.style.effective_vertical_align_for_inline_layout();
|
||||||
|
let line_gap = self.font_metrics.line_gap;
|
||||||
|
|
||||||
|
// The baseline offset that we have in `Self::baseline_offset` is relative to the line
|
||||||
|
// baseline, so we need to make it relative to the line block start.
|
||||||
|
match vertical_align {
|
||||||
|
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => {
|
||||||
|
let line_height = line_height(&self.style, &self.font_metrics);
|
||||||
|
(line_height - line_gap.into()).scale_by(0.5)
|
||||||
|
},
|
||||||
|
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
|
||||||
|
let line_height = line_height(&self.style, &self.font_metrics);
|
||||||
|
let half_leading = (line_height - line_gap.into()).scale_by(0.5);
|
||||||
|
state.line_metrics.block_size - line_height + half_leading
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
state.line_metrics.baseline_block_offset + Length::from(self.baseline_offset) -
|
||||||
|
space_above_baseline
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct AtomicLineItem {
|
||||||
|
pub fragment: BoxFragment,
|
||||||
|
pub size: LogicalVec2<Length>,
|
||||||
|
pub positioning_context: Option<PositioningContext>,
|
||||||
|
|
||||||
|
/// The block offset of this items' baseline relative to the baseline of the line.
|
||||||
|
/// This will be zero for boxes with `vertical-align: top` and `vertical-align:
|
||||||
|
/// bottom` since their baselines are calculated late in layout.
|
||||||
|
pub baseline_offset_in_parent: Au,
|
||||||
|
|
||||||
|
/// The offset of the baseline inside this item.
|
||||||
|
pub baseline_offset_in_item: Au,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomicLineItem {
|
||||||
|
fn layout(mut self, state: &mut LineItemLayoutState) -> BoxFragment {
|
||||||
|
// The initial `start_corner` of the Fragment is only the PaddingBorderMargin sum start
|
||||||
|
// offset, which is the sum of the start component of the padding, border, and margin.
|
||||||
|
// This needs to be added to the calculated block and inline positions.
|
||||||
|
self.fragment.content_rect.start_corner.inline += state.inline_position;
|
||||||
|
self.fragment.content_rect.start_corner.block +=
|
||||||
|
self.calculate_block_start(&state.line_metrics);
|
||||||
|
|
||||||
|
// Make the final result relative to the parent box.
|
||||||
|
self.fragment.content_rect.start_corner =
|
||||||
|
&self.fragment.content_rect.start_corner - &state.parent_offset;
|
||||||
|
|
||||||
|
if self.fragment.style.clone_position().is_relative() {
|
||||||
|
self.fragment.content_rect.start_corner +=
|
||||||
|
&relative_adjustement(&self.fragment.style, state.ifc_containing_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.inline_position += self.size.inline;
|
||||||
|
|
||||||
|
if let Some(mut positioning_context) = self.positioning_context {
|
||||||
|
positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
|
||||||
|
&self.fragment.content_rect.start_corner,
|
||||||
|
PositioningContextLength::zero(),
|
||||||
|
);
|
||||||
|
state.positioning_context.append(positioning_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given the metrics for a line, our vertical alignment, and our block size, find a block start
|
||||||
|
/// position relative to the top of the line.
|
||||||
|
fn calculate_block_start(&self, line_metrics: &LineMetrics) -> Length {
|
||||||
|
match self.fragment.style.clone_vertical_align() {
|
||||||
|
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) => Length::zero(),
|
||||||
|
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => {
|
||||||
|
line_metrics.block_size - self.size.block
|
||||||
|
},
|
||||||
|
|
||||||
|
// This covers all baseline-relative vertical alignment.
|
||||||
|
_ => {
|
||||||
|
let baseline = line_metrics.baseline_block_offset +
|
||||||
|
Length::from(self.baseline_offset_in_parent);
|
||||||
|
baseline - Length::from(self.baseline_offset_in_item)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct AbsolutelyPositionedLineItem {
|
||||||
|
pub absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbsolutelyPositionedLineItem {
|
||||||
|
fn layout(self, state: &mut LineItemLayoutState) -> ArcRefCell<HoistedSharedFragment> {
|
||||||
|
let box_ = self.absolutely_positioned_box;
|
||||||
|
let style = AtomicRef::map(box_.borrow(), |box_| box_.context.style());
|
||||||
|
let initial_start_corner = match Display::from(style.get_box().original_display) {
|
||||||
|
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside: _ }) => {
|
||||||
|
LogicalVec2 {
|
||||||
|
inline: match outside {
|
||||||
|
DisplayOutside::Inline => {
|
||||||
|
state.inline_position - state.parent_offset.inline
|
||||||
|
},
|
||||||
|
DisplayOutside::Block => Length::zero(),
|
||||||
|
},
|
||||||
|
// The blocks start position of the absolute should be at the top of the line.
|
||||||
|
block: -state.parent_offset.block,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(_)) => {
|
||||||
|
unreachable!(
|
||||||
|
"The result of blockification should never be a layout-internal value."
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Display::Contents => {
|
||||||
|
panic!("display:contents does not generate an abspos box")
|
||||||
|
},
|
||||||
|
Display::None => {
|
||||||
|
panic!("display:none does not generate an abspos box")
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
|
||||||
|
box_.clone(),
|
||||||
|
initial_start_corner,
|
||||||
|
state.ifc_containing_block,
|
||||||
|
);
|
||||||
|
let hoisted_fragment = hoisted_box.fragment.clone();
|
||||||
|
state.positioning_context.push(hoisted_box);
|
||||||
|
hoisted_fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct FloatLineItem {
|
||||||
|
pub 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.
|
||||||
|
pub needs_placement: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatLineItem {
|
||||||
|
fn layout(mut self, state: &mut LineItemLayoutState<'_>) -> BoxFragment {
|
||||||
|
// The `BoxFragment` for this float is positioned relative to the IFC, so we need
|
||||||
|
// to move it to be positioned relative to our parent InlineBox line item. Floats
|
||||||
|
// fragments are children of these InlineBoxes and not children of the inline
|
||||||
|
// formatting context, so that they are parented properly for StackingContext
|
||||||
|
// properties such as opacity & filters.
|
||||||
|
let distance_from_parent_to_ifc = LogicalVec2 {
|
||||||
|
inline: state.parent_offset.inline,
|
||||||
|
block: state.line_metrics.block_offset + state.parent_offset.block,
|
||||||
|
};
|
||||||
|
self.fragment.content_rect.start_corner =
|
||||||
|
&self.fragment.content_rect.start_corner - &distance_from_parent_to_ifc;
|
||||||
|
self.fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_baseline_relative(vertical_align: GenericVerticalAlign<LengthPercentage>) -> bool {
|
||||||
|
match vertical_align {
|
||||||
|
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) |
|
||||||
|
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_height(parent_style: &ComputedValues, font_metrics: &FontMetrics) -> Length {
|
||||||
|
let font_size = parent_style.get_font().font_size.computed_size();
|
||||||
|
match parent_style.get_inherited_text().line_height {
|
||||||
|
LineHeight::Normal => Length::from(font_metrics.line_gap),
|
||||||
|
LineHeight::Number(number) => font_size * number.0,
|
||||||
|
LineHeight::Length(length) => length.0,
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ use crate::ContainingBlock;
|
||||||
mod construct;
|
mod construct;
|
||||||
pub mod float;
|
pub mod float;
|
||||||
pub mod inline;
|
pub mod inline;
|
||||||
|
mod line;
|
||||||
mod root;
|
mod root;
|
||||||
|
|
||||||
pub(crate) use construct::BlockContainerBuilder;
|
pub(crate) use construct::BlockContainerBuilder;
|
||||||
|
|
|
@ -13,8 +13,9 @@ use style::properties::longhands::column_span::computed_value::T as ColumnSpan;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::values::computed::image::Image as ComputedImageLayer;
|
use style::values::computed::image::Image as ComputedImageLayer;
|
||||||
use style::values::computed::{Length, LengthPercentage, NonNegativeLengthPercentage, Size};
|
use style::values::computed::{Length, LengthPercentage, NonNegativeLengthPercentage, Size};
|
||||||
use style::values::generics::box_::Perspective;
|
use style::values::generics::box_::{GenericVerticalAlign, Perspective, VerticalAlignKeyword};
|
||||||
use style::values::generics::length::MaxSize;
|
use style::values::generics::length::MaxSize;
|
||||||
|
use style::values::specified::box_::DisplayOutside as StyloDisplayOutside;
|
||||||
use style::values::specified::{box_ as stylo, Overflow};
|
use style::values::specified::{box_ as stylo, Overflow};
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
use webrender_api as wr;
|
use webrender_api as wr;
|
||||||
|
@ -168,6 +169,7 @@ pub(crate) trait ComputedValuesExt {
|
||||||
fn establishes_containing_block_for_all_descendants(&self) -> bool;
|
fn establishes_containing_block_for_all_descendants(&self) -> bool;
|
||||||
fn background_is_transparent(&self) -> bool;
|
fn background_is_transparent(&self) -> bool;
|
||||||
fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
|
fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
|
||||||
|
fn effective_vertical_align_for_inline_layout(&self) -> GenericVerticalAlign<LengthPercentage>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComputedValuesExt for ComputedValues {
|
impl ComputedValuesExt for ComputedValues {
|
||||||
|
@ -533,6 +535,18 @@ impl ComputedValuesExt for ComputedValues {
|
||||||
BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(),
|
BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the effective `vertical-align` property for inline layout. Essentially, if this style
|
||||||
|
/// has outside block display, this is the inline formatting context root and `vertical-align`
|
||||||
|
/// doesn't come into play for inline layout.
|
||||||
|
fn effective_vertical_align_for_inline_layout(&self) -> GenericVerticalAlign<LengthPercentage> {
|
||||||
|
match self.clone_display().outside() {
|
||||||
|
StyloDisplayOutside::Block => {
|
||||||
|
GenericVerticalAlign::Keyword(VerticalAlignKeyword::Baseline)
|
||||||
|
},
|
||||||
|
_ => self.clone_vertical_align(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<stylo::Display> for Display {
|
impl From<stylo::Display> for Display {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue