aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/flow/inline.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout_2020/flow/inline.rs')
-rw-r--r--components/layout_2020/flow/inline.rs1022
1 files changed, 793 insertions, 229 deletions
diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs
index a4d66c85c62..3da0673dcc8 100644
--- a/components/layout_2020/flow/inline.rs
+++ b/components/layout_2020/flow/inline.rs
@@ -15,10 +15,13 @@ use log::warn;
use serde::Serialize;
use servo_arc::Arc;
use style::computed_values::white_space::T as WhiteSpace;
+use style::context::QuirksMode;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
-use style::values::computed::Length;
+use style::values::computed::{Length, LengthPercentage};
+use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
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::{TextAlignLast, TextJustify};
use style::Zero;
@@ -52,6 +55,10 @@ const XI_LINE_BREAKING_CLASS_GL: u8 = 12;
const XI_LINE_BREAKING_CLASS_WJ: u8 = 30;
const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 40;
+// From gfxFontConstants.h in Firefox.
+static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
+static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34;
+
#[derive(Debug, Serialize)]
pub(crate) struct InlineFormattingContext {
pub(super) inline_level_boxes: Vec<ArcRefCell<InlineLevelBox>>,
@@ -115,7 +122,9 @@ struct LineUnderConstruction {
inline_position: Length,
/// The maximum block size of all boxes that ended and are in progress in this line.
- max_block_size: Length,
+ /// This uses [`LineBlockSizes`] instead of a simple value, because the final block size
+ /// depends on vertical alignment.
+ max_block_size: LineBlockSizes,
/// Whether any active linebox has added a glyph or atomic element to this line, which
/// indicates that the next run that exceeds the line length can cause a line break.
@@ -145,7 +154,7 @@ impl LineUnderConstruction {
Self {
inline_position: start_position.inline.clone(),
start_position: start_position,
- max_block_size: Length::zero(),
+ max_block_size: LineBlockSizes::zero(),
has_content: false,
has_floats_waiting_to_be_placed: false,
placement_among_floats: OnceCell::new(),
@@ -184,12 +193,144 @@ impl LineUnderConstruction {
}
}
+/// A block size relative to a line's final baseline. This is to track the size
+/// contribution of a particular element of a line above and below the baseline.
+/// These sizes can be combined with other baseline relative sizes before the
+/// final baseline position is known. The values here are relative to the
+/// overall line's baseline and *not* the nested baseline of an inline box.
+#[derive(Clone, Debug)]
+struct BaselineRelativeSize {
+ /// The ascent above the baseline, where a positive value means a larger
+ /// ascent. Thus, the top of this size contribution is `baseline_offset -
+ /// ascent`.
+ ascent: Au,
+
+ /// The descent below the baseline, where a positive value means a larger
+ /// descent. Thus, the bottom of this size contribution is `baseline_offset +
+ /// descent`.
+ 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),
+ }
+ }
+
+ /// Given an offset from the line's root baseline, adjust this [`BaselineRelativeSize`]
+ /// by that offset. This is used to adjust a [`BaselineRelativeSize`] for different kinds
+ /// of baseline-relative `vertical-align`. This will "move" measured size of a particular
+ /// inline box's block size. For example, in the following HTML:
+ ///
+ /// ```html
+ /// <div>
+ /// <span style="vertical-align: 5px">child content</span>
+ /// </div>
+ /// ````
+ ///
+ /// If this [`BaselineRelativeSize`] is for the `<span>` then the adjustment
+ /// passed here would be equivalent to -5px.
+ 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: Length,
+ baseline_relative_size_for_line_height: Option<BaselineRelativeSize>,
+ size_for_baseline_positioning: BaselineRelativeSize,
+}
+
+impl LineBlockSizes {
+ fn zero() -> Self {
+ LineBlockSizes {
+ line_height: Length::zero(),
+ baseline_relative_size_for_line_height: None,
+ size_for_baseline_positioning: BaselineRelativeSize::zero(),
+ }
+ }
+
+ fn resolve(&self) -> Length {
+ let height_from_ascent_and_descent = self
+ .baseline_relative_size_for_line_height
+ .as_ref()
+ .map(|size| Length::from((size.ascent + size.descent).abs()))
+ .unwrap_or_else(Length::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) {
+ self.baseline_relative_size_for_line_height
+ .as_mut()
+ .map(|size| size.adjust_for_nested_baseline_offset(baseline_offset));
+ self.size_for_baseline_positioning
+ .adjust_for_nested_baseline_offset(baseline_offset);
+ }
+
+ /// From https://drafts.csswg.org/css2/visudet.html#line-height:
+ /// > The inline-level boxes are aligned vertically according to their 'vertical-align'
+ /// > property. In case they are aligned 'top' or 'bottom', they must be aligned so as
+ /// > to minimize the line box height. If such boxes are tall enough, there are multiple
+ /// > solutions and CSS 2 does not define the position of the line box's baseline (i.e.,
+ /// > the position of the strut, see below).
+ fn find_baseline_offset(&self) -> Length {
+ match self.baseline_relative_size_for_line_height.as_ref() {
+ Some(size) => size.ascent.into(),
+ None => {
+ // This is the case mentinoned above where there are multiple solutions.
+ // This code is putting the baseline roughly in the middle of the line.
+ let leading = self.resolve() -
+ (self.size_for_baseline_positioning.ascent +
+ self.size_for_baseline_positioning.descent)
+ .into();
+ leading.scale_by(0.5) + self.size_for_baseline_positioning.ascent.into()
+ },
+ }
+ }
+}
+
/// The current unbreakable segment under construction for an inline formatting context.
/// Items accumulate here until we reach a soft line break opportunity during processing
/// of inline content or we reach the end of the formatting context.
struct UnbreakableSegmentUnderConstruction {
/// The size of this unbreakable segment in both dimension.
- size: LogicalVec2<Length>,
+ inline_size: Length,
+
+ /// The maximum block size that this segment has. This uses [`LineBlockSizes`] instead of a
+ /// simple value, because the final block size depends on vertical alignment.
+ max_block_size: LineBlockSizes,
/// The LineItems for the segment under construction
line_items: Vec<LineItem>,
@@ -213,7 +354,12 @@ struct UnbreakableSegmentUnderConstruction {
impl UnbreakableSegmentUnderConstruction {
fn new() -> Self {
Self {
- size: LogicalVec2::zero(),
+ inline_size: Length::zero(),
+ max_block_size: LineBlockSizes {
+ line_height: Length::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,
@@ -225,7 +371,8 @@ impl UnbreakableSegmentUnderConstruction {
/// Reset this segment after its contents have been committed to a line.
fn reset(&mut self) {
assert!(self.line_items.is_empty()); // Preserve allocated memory.
- self.size = LogicalVec2::zero();
+ self.inline_size = Length::zero();
+ self.max_block_size = LineBlockSizes::zero();
self.inline_box_hierarchy_depth = None;
self.has_content = false;
self.trailing_whitespace_size = Length::zero();
@@ -261,7 +408,7 @@ impl UnbreakableSegmentUnderConstruction {
break;
}
}
- self.size.inline -= whitespace_trimmed;
+ self.inline_size -= whitespace_trimmed;
self.justification_opportunities -= spaces_trimmed;
}
@@ -323,6 +470,9 @@ impl UnbreakableSegmentUnderConstruction {
}
struct InlineContainerState {
+ /// The style of this inline container.
+ style: Arc<ComputedValues>,
+
/// Whether or not we have processed any content (an atomic element or text) for
/// this inline box on the current line OR any previous line.
has_content: bool,
@@ -333,11 +483,26 @@ struct InlineContainerState {
// an IFC..."
text_decoration_line: TextDecorationLine,
- /// The block size of this inline container maxed with the block sizes of all inline
- /// container ancestors. This isn't the block size of this container, but if this
- /// container adds content to a line, this is the block size necessary for that new
- /// content.
- nested_block_size: Length,
+ /// The block size contribution of this container's default font ie the size of the
+ /// "strut." Whether this is integrated into the [`Self::nested_strut_block_sizes`]
+ /// depends on the line-height quirk described in
+ /// https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk.
+ strut_block_sizes: LineBlockSizes,
+
+ /// The strut block size of this inline container maxed with the strut block
+ /// sizes of all inline container ancestors. In quirks mode, this will be
+ /// zero, until we know that an element has inline content.
+ nested_strut_block_sizes: LineBlockSizes,
+
+ /// The baseline offset of this container from the baseline of the line. The is the
+ /// cumulative offset of this container and all of its parents. In contrast to the
+ /// `vertical-align` property a positive value indicates an offset "below" the
+ /// baseline while a negative value indicates one "above" it (when the block direction
+ /// is vertical).
+ baseline_offset: Au,
+
+ /// The font metrics of the non-fallback font for this container.
+ font_metrics: FontMetrics,
}
struct InlineBoxContainerState {
@@ -345,9 +510,6 @@ struct InlineBoxContainerState {
/// [`InlineFormattingContext`].
base: InlineContainerState,
- /// The style of this inline box.
- style: Arc<ComputedValues>,
-
/// The [`BaseFragmentInfo`] of the [`InlineBox`] that this state tracks.
base_fragment_info: BaseFragmentInfo,
@@ -366,6 +528,22 @@ struct InlineFormattingContextState<'a, 'b> {
sequential_layout_state: Option<&'a mut SequentialLayoutState>,
layout_context: &'b LayoutContext<'b>,
+ /// The [`InlineContainerState`] for the container formed by the root of the
+ /// [`InlineFormattingContext`]. This is effectively the "root inline box" described
+ /// by https://drafts.csswg.org/css-inline/#model:
+ ///
+ /// > The block container also generates a root inline box, which is an anonymous
+ /// > inline box that holds all of its inline-level contents. (Thus, all text in an
+ /// > inline formatting context is directly contained by an inline box, whether the root
+ /// > inline box or one of its descendants.) The root inline box inherits from its
+ /// > parent block container, but is otherwise unstyleable.
+ root_nesting_level: InlineContainerState,
+
+ /// A stack of [`InlineBoxContainerState`] that is used to produce [`LineItem`]s either when we
+ /// reach the end of an inline box or when we reach the end of a line. Only at the end
+ /// of the inline box is the state popped from the stack.
+ inline_box_state_stack: Vec<InlineBoxContainerState>,
+
/// A vector of fragment that are laid out. This includes one [`Fragment::Anonymous`]
/// per line that is currently laid out plus fragments for all floats, which
/// are currently laid out at the top-level of each [`InlineFormattingContext`].
@@ -377,6 +555,9 @@ struct InlineFormattingContextState<'a, 'b> {
/// Information about the unbreakable line segment currently being laid out into [`LineItems`]s.
current_line_segment: UnbreakableSegmentUnderConstruction,
+ /// The line breaking state for this inline formatting context.
+ linebreaker: Option<LineBreakLeafIter>,
+
/// After a forced line break (for instance from a `<br>` element) we wait to actually
/// break the line until seeing more content. This allows ongoing inline boxes to finish,
/// since in the case where they have no more content they should not be on the next
@@ -397,9 +578,6 @@ struct InlineFormattingContextState<'a, 'b> {
/// [`InlineFormattingContextState::finish_inline_box()`].
linebreak_before_new_content: bool,
- /// The line breaking state for this inline formatting context.
- linebreaker: Option<LineBreakLeafIter>,
-
/// Whether or not a soft wrap opportunity is queued. Soft wrap opportunities are
/// queued after replaced content and they are processed when the next text content
/// is encountered.
@@ -411,20 +589,19 @@ struct InlineFormattingContextState<'a, 'b> {
/// details.
prevent_soft_wrap_opportunity_before_next_atomic: bool,
+ /// Whether or not this InlineFormattingContext has processed any in flow content at all.
+ had_inflow_content: bool,
+
/// The currently white-space setting of this line. This is stored on the
/// [`InlineFormattingContextState`] because when a soft wrap opportunity is defined
/// by the boundary between two characters, the white-space property of their nearest
/// common ancestor is used.
white_space: WhiteSpace,
- /// The [`InlineContainerState`] for the container formed by the root of the
- /// [`InlineFormattingContext`].
- root_nesting_level: InlineContainerState,
-
- /// A stack of [`InlineBoxContainerState`] that is used to produce [`LineItem`]s either when we
- /// reach the end of an inline box or when we reach the end of a line. Only at the end
- /// of the inline box is the state popped from the stack.
- inline_box_state_stack: Vec<InlineBoxContainerState>,
+ /// The offset of the last baseline in the inline formatting context that we
+ /// are laying out. This is used to propagate baselines to the ancestors of
+ /// `display: inline-block` elements.
+ last_baseline_offset: Option<Length>,
}
impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
@@ -442,15 +619,15 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
}
}
- fn current_line_max_block_size(&self) -> Length {
+ fn current_line_max_block_size_including_nested_containers(&self) -> LineBlockSizes {
self.current_inline_container_state()
- .nested_block_size
- .max(self.current_line.max_block_size)
+ .nested_strut_block_sizes
+ .max(&self.current_line.max_block_size)
}
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.style,
+ Some(inline_box_state) => &inline_box_state.base.style,
None => self.containing_block.style,
};
self.white_space = style.get_inherited_text().white_space;
@@ -459,17 +636,11 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
/// Start laying out a particular [`InlineBox`] into line items. This will push
/// a new [`InlineBoxContainerState`] onto [`Self::inline_box_state_stack`].
fn start_inline_box(&mut self, inline_box: &InlineBox) {
- let (text_decoration_of_parent, nested_block_size_of_parent) = {
- let parent = self.current_inline_container_state();
- (parent.text_decoration_line, parent.nested_block_size)
- };
-
let mut inline_box_state = InlineBoxContainerState::new(
inline_box,
&self.containing_block,
- text_decoration_of_parent,
- nested_block_size_of_parent,
self.layout_context,
+ self.current_inline_container_state(),
inline_box.is_last_fragment,
);
@@ -483,11 +654,8 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
.auto_is(Length::zero);
}
- let line_item = inline_box_state.layout_into_line_item(
- self.layout_context,
- inline_box.is_first_fragment,
- inline_box.is_last_fragment,
- );
+ let line_item = inline_box_state
+ .layout_into_line_item(inline_box.is_first_fragment, inline_box.is_last_fragment);
self.push_line_item_to_unbreakable_segment(LineItem::StartInlineBox(line_item));
self.inline_box_state_stack.push(inline_box_state);
}
@@ -503,9 +671,8 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
self.push_line_item_to_unbreakable_segment(LineItem::EndInlineBox);
self.current_line_segment
- .size
- .block
- .max_assign(inline_box_state.base.nested_block_size);
+ .max_block_size
+ .max_assign(&inline_box_state.base.nested_strut_block_sizes);
// If the inline box that we just finished had any content at all, we want to propagate
// the `white-space` property of its parent to future inline children. This is because
@@ -519,7 +686,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
let pbm_end = inline_box_state.pbm.padding.inline_end +
inline_box_state.pbm.border.inline_end +
inline_box_state.pbm.margin.inline_end.auto_is(Length::zero);
- self.current_line_segment.size.inline += pbm_end;
+ self.current_line_segment.inline_size += pbm_end;
}
}
@@ -561,12 +728,12 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
had_inline_advance ||
self.linebreak_before_new_content
{
- self.current_line_max_block_size()
+ self.current_line_max_block_size_including_nested_containers()
} else {
- Length::zero()
+ LineBlockSizes::zero()
};
- let block_end_position = block_start_position + effective_block_advance;
+ let block_end_position = block_start_position + effective_block_advance.resolve();
if let Some(sequential_layout_state) = self.sequential_layout_state.as_mut() {
// This amount includes both the block size of the line and any extra space
// added to move the line down in order to avoid overlapping floats.
@@ -579,13 +746,26 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
place_pending_floats(self, &mut line_items);
}
+ // Set up the new line now that we no longer need the old one.
+ self.current_line = LineUnderConstruction::new(LogicalVec2 {
+ inline: Length::zero(),
+ block: block_end_position,
+ });
+
+ let baseline_offset = effective_block_advance.find_baseline_offset();
+
let mut state = LineItemLayoutState {
inline_position: inline_start_position,
- inline_start_of_parent: Length::zero(),
+ parent_offset: LogicalVec2::zero(),
+ baseline_offset,
ifc_containing_block: self.containing_block,
positioning_context: &mut self.positioning_context,
- line_block_start: block_start_position,
justification_adjustment,
+ line_metrics: &LineMetrics {
+ block_offset: block_start_position,
+ block_size: effective_block_advance.resolve(),
+ baseline_block_offset: baseline_offset,
+ },
};
let positioning_context_length = state.positioning_context.len();
@@ -597,41 +777,42 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
&mut saw_end,
);
- let size = LogicalVec2 {
- inline: self.containing_block.inline_size,
- block: effective_block_advance,
- };
-
- // The inline part of this start offset was taken into account when determining
- // the inline start of the line in `calculate_inline_start_for_current_line` so
- // we do not need to include it in the `start_corner` of the line's main Fragment.
- let start_corner = LogicalVec2 {
- inline: Length::zero(),
- block: block_start_position,
- };
-
let line_had_content =
!fragments.is_empty() || state.positioning_context.len() != positioning_context_length;
- if line_had_content {
- state
- .positioning_context
- .adjust_static_position_of_hoisted_fragments_with_offset(
- &start_corner,
- positioning_context_length,
- );
- self.fragments
- .push(Fragment::Anonymous(AnonymousFragment::new(
- LogicalRect { start_corner, size },
- fragments,
- self.containing_block.style.writing_mode,
- )));
+ // If the line doesn't have any fragments, we don't need to add a containing fragment for it.
+ if !line_had_content {
+ return;
}
- self.current_line = LineUnderConstruction::new(LogicalVec2 {
- inline: Length::zero(),
- block: block_end_position,
- });
+ self.last_baseline_offset = Some(baseline_offset + block_start_position);
+ let line_rect = LogicalRect {
+ // The inline part of this start offset was taken into account when determining
+ // the inline start of the line in `calculate_inline_start_for_current_line` so
+ // we do not need to include it in the `start_corner` of the line's main Fragment.
+ start_corner: LogicalVec2 {
+ inline: Length::zero(),
+ block: block_start_position,
+ },
+ size: LogicalVec2 {
+ inline: self.containing_block.inline_size,
+ block: effective_block_advance.resolve(),
+ },
+ };
+
+ state
+ .positioning_context
+ .adjust_static_position_of_hoisted_fragments_with_offset(
+ &line_rect.start_corner,
+ positioning_context_length,
+ );
+
+ self.fragments
+ .push(Fragment::Anonymous(AnonymousFragment::new(
+ line_rect,
+ fragments,
+ self.containing_block.style.writing_mode,
+ )));
}
/// Given the amount of whitespace trimmed from the line and taking into consideration
@@ -798,7 +979,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
// start position.
let new_placement = self.place_line_among_floats(&LogicalVec2 {
inline: line_inline_size_without_trailing_whitespace,
- block: self.current_line.max_block_size,
+ block: self.current_line.max_block_size.resolve(),
});
self.current_line
.replace_placement_among_floats(new_placement);
@@ -937,10 +1118,11 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
// We need to ensure that the appropriate space for a linebox is created even if there
// was no other content on this line. We mark the line as having content (needing a
// advance) and having at least the height associated with this nesting of inline boxes.
- //self.current_line.has_content = true;
self.current_line
.max_block_size
- .max_assign(self.current_line_max_block_size());
+ .max_assign(&self.current_line_max_block_size_including_nested_containers());
+
+ self.had_inflow_content = true;
}
fn possibly_flush_deferred_forced_line_break(&mut self) {
@@ -966,69 +1148,84 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
font_metrics: &FontMetrics,
font_key: FontInstanceKey,
) {
- let inline_advance = Length::from(glyph_store.total_advance());
-
- let is_non_preserved_whitespace = glyph_store.is_whitespace() &&
- !parent_style
- .get_inherited_text()
- .white_space
- .preserve_spaces();
- if is_non_preserved_whitespace {
- self.current_line_segment.trailing_whitespace_size = inline_advance;
- }
self.current_line_segment.justification_opportunities +=
glyph_store.total_word_separators() as usize;
+ let inline_advance = Length::from(glyph_store.total_advance());
+ let preserve_spaces = parent_style
+ .get_inherited_text()
+ .white_space
+ .preserve_spaces();
+ let is_collapsible_whitespace = glyph_store.is_whitespace() && !preserve_spaces;
+
+ // Normally, the strut is incorporated into the nested block size. In quirks mode though
+ // if we find any text that isn't collapsed whitespace, we need to incorporate the strut.
+ // TODO(mrobinson): This isn't quite right for situations where collapsible white space
+ // ultimately does not collapse because it is between two other pieces of content.
+ // TODO(mrobinson): When we have font fallback, this should be calculating the
+ // block sizes of the fallback font.
+ let quirks_mode = self.layout_context.style_context.quirks_mode() != QuirksMode::NoQuirks;
+ let strut_size = if quirks_mode && !is_collapsible_whitespace {
+ self.current_inline_container_state()
+ .strut_block_sizes
+ .clone()
+ } else {
+ LineBlockSizes::zero()
+ };
+ self.update_unbreakable_segment_for_new_content(
+ &strut_size,
+ inline_advance,
+ is_collapsible_whitespace,
+ );
+
match self.current_line_segment.line_items.last_mut() {
Some(LineItem::TextRun(text_run)) => {
debug_assert!(font_key == text_run.font_key);
text_run.text.push(glyph_store);
- self.current_line_segment.size.inline += inline_advance;
-
- if !is_non_preserved_whitespace {
- self.current_line_segment.has_content = true;
- }
return;
},
_ => {},
}
- self.push_content_line_item_to_unbreakable_segment(
- inline_advance,
- LineItem::TextRun(TextRunLineItem {
- text: vec![glyph_store],
- base_fragment_info: base_fragment_info.into(),
- parent_style: parent_style.clone(),
- font_metrics: font_metrics.clone(),
- font_key,
- text_decoration_line: self.current_inline_container_state().text_decoration_line,
- }),
- !is_non_preserved_whitespace,
- );
+
+ self.push_line_item_to_unbreakable_segment(LineItem::TextRun(TextRunLineItem {
+ text: vec![glyph_store],
+ base_fragment_info: base_fragment_info.into(),
+ parent_style: parent_style.clone(),
+ font_metrics: font_metrics.clone(),
+ font_key,
+ text_decoration_line: self.current_inline_container_state().text_decoration_line,
+ }));
}
- fn push_content_line_item_to_unbreakable_segment(
+ fn update_unbreakable_segment_for_new_content(
&mut self,
+ block_sizes_of_content: &LineBlockSizes,
inline_size: Length,
- line_item: LineItem,
- counts_as_content: bool,
+ is_collapsible_whitespace: bool,
) {
- if counts_as_content {
+ if !is_collapsible_whitespace {
+ self.current_line_segment.trailing_whitespace_size = Length::zero();
self.current_line_segment.has_content = true;
+ self.had_inflow_content = true;
+ } else {
+ self.current_line_segment.trailing_whitespace_size = inline_size;
}
- self.current_line_segment.size.inline += inline_size;
+ // This may or may not include the size of the strut depending on the quirks mode setting.
+ let container_max_block_size = &self
+ .current_inline_container_state()
+ .nested_strut_block_sizes
+ .clone();
self.current_line_segment
- .size
- .block
- .max_assign(self.current_inline_container_state().nested_block_size);
+ .max_block_size
+ .max_assign(container_max_block_size);
self.current_line_segment
- .size
- .block
- .max_assign(line_item.block_size());
- self.push_line_item_to_unbreakable_segment(line_item);
+ .max_block_size
+ .max_assign(block_sizes_of_content);
- // We need to update the size of the current segment and also propagate the
- // whitespace setting to the current nesting level.
+ self.current_line_segment.inline_size += inline_size;
+
+ // Propagate the whitespace setting to the current nesting level.
let current_nesting_level = self.current_inline_container_state_mut();
current_nesting_level.has_content = true;
self.propagate_current_nesting_level_white_space_style();
@@ -1055,11 +1252,12 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
}
let potential_line_size = LogicalVec2 {
- inline: self.current_line.inline_position + self.current_line_segment.size.inline -
+ 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()
- .max(self.current_line_segment.size.block),
+ .current_line_max_block_size_including_nested_containers()
+ .max(&self.current_line_segment.max_block_size)
+ .resolve(),
};
if self.new_potential_line_size_causes_line_break(&potential_line_size) {
@@ -1079,10 +1277,10 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
self.current_line_segment.trim_leading_whitespace();
}
- self.current_line.inline_position += self.current_line_segment.size.inline;
+ self.current_line.inline_position += self.current_line_segment.inline_size;
self.current_line.max_block_size = self
- .current_line_max_block_size()
- .max(self.current_line_segment.size.block);
+ .current_line_max_block_size_including_nested_containers()
+ .max(&self.current_line_segment.max_block_size);
self.current_line.justification_opportunities +=
self.current_line_segment.justification_opportunities;
let line_inline_size_without_trailing_whitespace =
@@ -1109,7 +1307,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
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.size.block,
+ block: self.current_line_segment.max_block_size.resolve(),
});
assert!(!will_break);
}
@@ -1325,6 +1523,7 @@ impl InlineFormattingContext {
Length::zero()
};
+ let style = containing_block.style;
let mut ifc = InlineFormattingContextState {
positioning_context,
containing_block,
@@ -1335,18 +1534,22 @@ impl InlineFormattingContext {
inline: first_line_inline_start,
block: Length::zero(),
}),
+ root_nesting_level: InlineContainerState::new(
+ style.to_arc(),
+ layout_context,
+ None, /* parent_container */
+ self.text_decoration_line,
+ inline_container_needs_strut(style, layout_context, None),
+ ),
+ linebreaker: None,
+ inline_box_state_stack: Vec::new(),
current_line_segment: UnbreakableSegmentUnderConstruction::new(),
linebreak_before_new_content: false,
- white_space: containing_block.style.get_inherited_text().white_space,
- linebreaker: None,
have_deferred_soft_wrap_opportunity: false,
prevent_soft_wrap_opportunity_before_next_atomic: false,
- root_nesting_level: InlineContainerState {
- nested_block_size: line_height_from_style(layout_context, &containing_block.style),
- has_content: false,
- text_decoration_line: self.text_decoration_line,
- },
- inline_box_state_stack: Vec::new(),
+ had_inflow_content: false,
+ white_space: containing_block.style.get_inherited_text().white_space,
+ last_baseline_offset: None,
};
// FIXME(pcwalton): This assumes that margins never collapse through inline formatting
@@ -1411,13 +1614,15 @@ impl InlineFormattingContext {
let mut collapsible_margins_in_children = CollapsedBlockMargins::zero();
let content_block_size = ifc.current_line.start_position.block;
- collapsible_margins_in_children.collapsed_through =
- content_block_size == Length::zero() && collapsible_with_parent_start_margin.0;
+ collapsible_margins_in_children.collapsed_through = !ifc.had_inflow_content &&
+ content_block_size == Length::zero() &&
+ collapsible_with_parent_start_margin.0;
return FlowLayout {
fragments: ifc.fragments,
content_block_size,
collapsible_margins_in_children,
+ last_inflow_baseline_offset: ifc.last_baseline_offset,
};
}
@@ -1445,44 +1650,210 @@ impl InlineFormattingContext {
}
}
+impl InlineContainerState {
+ fn new(
+ style: Arc<ComputedValues>,
+ layout_context: &LayoutContext,
+ parent_container: Option<&InlineContainerState>,
+ parent_text_decoration_line: TextDecorationLine,
+ create_strut: bool,
+ ) -> Self {
+ let text_decoration_line = parent_text_decoration_line | style.clone_text_decoration_line();
+ let font_metrics = font_metrics_from_style(layout_context, &style);
+ let line_height = line_height(&style, &font_metrics);
+
+ let mut baseline_offset = Au::zero();
+ let mut strut_block_sizes =
+ Self::get_block_sizes_with_style(&style, &font_metrics, line_height);
+ if let Some(parent_container) = parent_container {
+ // The baseline offset from `vertical-align` might adjust where our block size contribution is
+ // within the line.
+ baseline_offset = parent_container.get_cumulative_baseline_offset_for_child(
+ effective_vertical_for_inline_container(&style),
+ &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 create_strut {
+ nested_block_sizes.max_assign(&strut_block_sizes);
+ }
+
+ Self {
+ style,
+ has_content: false,
+ text_decoration_line,
+ nested_strut_block_sizes: nested_block_sizes,
+ strut_block_sizes,
+ baseline_offset,
+ font_metrics,
+ }
+ }
+
+ fn get_block_sizes_with_style(
+ style: &ComputedValues,
+ font_metrics: &FontMetrics,
+ line_height: Length,
+ ) -> LineBlockSizes {
+ let vertical_align = effective_vertical_for_inline_container(style);
+ if !is_baseline_relative(vertical_align) {
+ return LineBlockSizes {
+ line_height,
+ baseline_relative_size_for_line_height: None,
+ size_for_baseline_positioning: BaselineRelativeSize::zero(),
+ };
+ }
+
+ // From https://drafts.csswg.org/css-inline/#inline-height
+ // > If line-height computes to `normal` and either `text-box-edge` is `leading` or this
+ // > is the root inline box, the font’s line gap metric may also be incorporated
+ // > into A and D by adding half to each side as half-leading.
+ //
+ // `text-box-edge` isn't implemented (and this is a draft specification), so it's
+ // always effectively `leading`, which means we always take into account the line gap
+ // when `line-height` is normal.
+ let mut ascent = font_metrics.ascent;
+ let mut descent = font_metrics.descent;
+ if style.get_inherited_text().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;
+ }
+
+ // The ascent and descent we use for computing the line's final line height isn't
+ // the same the ascent and descent we use for finding the baseline. For finding
+ // the baseline we want the content rect.
+ let size_for_baseline_positioning = BaselineRelativeSize { ascent, descent };
+
+ // From https://drafts.csswg.org/css-inline/#inline-height
+ // > When its computed line-height is not normal, its layout bounds are derived solely
+ // > from metrics of its first available font (ignoring glyphs from other fonts), and
+ // > leading is used to adjust the effective A and D to add up to the used line-height.
+ // > Calculate the leading L as L = line-height - (A + D). Half the leading (its
+ // > half-leading) is added above A of the first available font, and the other half
+ // > below D of the first available font, giving an effective ascent above the baseline
+ // > of A′ = A + L/2, and an effective descent of D′ = D + L/2.
+ //
+ // Note that leading might be negative here and the line-height might be zero. In
+ // the case where the height is zero, ascent and descent will move to the same
+ // point in the block axis. Even though the contribution to the line height is
+ // zero in this case, the line may get some height when taking them into
+ // considering with other zero line height boxes that converge on other block axis
+ // locations when using the above formula.
+ if style.get_inherited_text().line_height != LineHeight::Normal {
+ let half_leading =
+ (Au::from_f32_px(line_height.px()) - (ascent + descent)).scale_by(0.5);
+ ascent = ascent + half_leading;
+ descent = descent + half_leading;
+ }
+
+ LineBlockSizes {
+ line_height,
+ baseline_relative_size_for_line_height: Some(BaselineRelativeSize { ascent, descent }),
+ size_for_baseline_positioning,
+ }
+ }
+
+ fn get_block_size_contribution(&self, font_metrics: &FontMetrics) -> LineBlockSizes {
+ Self::get_block_sizes_with_style(
+ &self.style,
+ font_metrics,
+ line_height(&self.style, &font_metrics),
+ )
+ }
+
+ fn get_cumulative_baseline_offset_for_child(
+ &self,
+ child_vertical_align: GenericVerticalAlign<LengthPercentage>,
+ child_block_size: &LineBlockSizes,
+ ) -> Au {
+ let block_size = self.get_block_size_contribution(&self.font_metrics);
+ self.baseline_offset +
+ match child_vertical_align {
+ // `top` and `bottom are not actually relative to the baseline, but this value is unused
+ // in those cases.
+ // TODO: We should distinguish these from `baseline` in order to implement "aligned subtrees" properly.
+ // See https://drafts.csswg.org/css2/#aligned-subtree.
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Baseline) |
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) |
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => Au::zero(),
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Sub) => Au::from_f32_px(
+ block_size
+ .resolve()
+ .scale_by(FONT_SUBSCRIPT_OFFSET_RATIO)
+ .px(),
+ ),
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Super) => -Au::from_f32_px(
+ block_size
+ .resolve()
+ .scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO)
+ .px(),
+ ),
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::TextTop) => {
+ child_block_size.size_for_baseline_positioning.ascent - self.font_metrics.ascent
+ },
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Middle) => {
+ // "Align the vertical midpoint of the box with the baseline of the parent
+ // box plus half the x-height of the parent."
+ (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)
+ },
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::TextBottom) => {
+ (self.font_metrics.descent -
+ child_block_size.size_for_baseline_positioning.descent)
+ .into()
+ },
+ GenericVerticalAlign::Length(length_percentage) => {
+ Au::from_f32_px(-length_percentage.resolve(child_block_size.line_height).px())
+ },
+ }
+ }
+}
+
impl InlineBoxContainerState {
fn new(
inline_box: &InlineBox,
containing_block: &ContainingBlock,
- text_decoration_of_parent: TextDecorationLine,
- nested_block_size_of_parent: Length,
layout_context: &LayoutContext,
+ parent_container: &InlineContainerState,
is_last_fragment: bool,
) -> Self {
let style = inline_box.style.clone();
- let text_decoration_line = text_decoration_of_parent | style.clone_text_decoration_line();
+ let pbm = style.padding_border_margin(containing_block);
+ let create_strut = inline_container_needs_strut(&style, layout_context, Some(&pbm));
Self {
- base: InlineContainerState {
- has_content: false,
- text_decoration_line,
- nested_block_size: nested_block_size_of_parent
- .max(line_height_from_style(layout_context, &style)),
- },
- style,
+ base: InlineContainerState::new(
+ style,
+ layout_context,
+ Some(parent_container),
+ parent_container.text_decoration_line,
+ create_strut,
+ ),
base_fragment_info: inline_box.base_fragment_info,
- pbm: inline_box.style.padding_border_margin(containing_block),
+ pbm,
is_last_fragment,
}
}
fn layout_into_line_item(
&mut self,
- layout_context: &LayoutContext,
is_first_fragment: bool,
is_last_fragment_of_ib_split: bool,
) -> InlineBoxLineItem {
InlineBoxLineItem {
base_fragment_info: self.base_fragment_info,
- style: self.style.clone(),
- block_size: line_gap_from_style(layout_context, &self.style),
+ style: self.base.style.clone(),
pbm: self.pbm.clone(),
is_first_fragment,
is_last_fragment_of_ib_split,
+ font_metrics: self.base.font_metrics.clone(),
+ baseline_offset: self.base.baseline_offset,
}
}
}
@@ -1515,6 +1886,7 @@ impl IndependentFormattingContext {
start_corner: pbm_sums.start_offset(),
size,
};
+
BoxFragment::new(
replaced.base_fragment_info,
replaced.style.clone(),
@@ -1523,7 +1895,8 @@ impl IndependentFormattingContext {
pbm.padding,
pbm.border,
margin,
- None,
+ None, /* clearance */
+ None, /* last_inflow_baseline_offset */
CollapsedBlockMargins::zero(),
)
},
@@ -1604,6 +1977,7 @@ impl IndependentFormattingContext {
pbm.border,
margin,
None,
+ independent_layout.last_inflow_baseline_offset,
CollapsedBlockMargins::zero(),
)
},
@@ -1618,19 +1992,61 @@ impl IndependentFormattingContext {
}
let size = &pbm_sums.sum() + &fragment.content_rect.size;
- ifc.push_content_line_item_to_unbreakable_segment(
- size.inline,
- LineItem::Atomic(AtomicLineItem {
- fragment,
- size,
- positioning_context: child_positioning_context,
- }),
- true,
- );
+ let baseline_offset = fragment
+ .last_baseline_offset
+ .map(|baseline_offset| pbm_sums.block_start + baseline_offset)
+ .unwrap_or(size.block);
+ let baseline_offset = Au::from_f32_px(baseline_offset.px());
+
+ let (block_sizes, baseline_offset_in_parent) =
+ self.get_block_sizes_and_baseline_offset(ifc, size.block, baseline_offset);
+ ifc.update_unbreakable_segment_for_new_content(&block_sizes, size.inline, false);
+ ifc.push_line_item_to_unbreakable_segment(LineItem::Atomic(AtomicLineItem {
+ fragment,
+ size,
+ positioning_context: child_positioning_context,
+ baseline_offset_in_parent,
+ baseline_offset_in_item: baseline_offset,
+ }));
// Defer a soft wrap opportunity for when we next process text content.
ifc.have_deferred_soft_wrap_opportunity = true;
}
+
+ fn get_block_sizes_and_baseline_offset(
+ &self,
+ ifc: &InlineFormattingContextState,
+ block_size: Length,
+ baseline_offset_in_content_area: Au,
+ ) -> (LineBlockSizes, Au) {
+ let mut contribution = if !is_baseline_relative(self.style().clone_vertical_align()) {
+ 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: Au::from_f32_px(block_size.px()) - 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 baseline_offset = ifc
+ .current_inline_container_state()
+ .get_cumulative_baseline_offset_for_child(
+ self.style().clone_vertical_align(),
+ &contribution,
+ );
+ contribution.adjust_for_baseline_offset(baseline_offset);
+
+ (contribution, baseline_offset)
+ }
}
struct BreakAndShapeResult {
@@ -1866,22 +2282,40 @@ 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 inline start position of the parent (the inline box that established this state)
- /// relative to the edge of the containing block of this [`InlineFormattingCotnext`].
- inline_start_of_parent: 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,
- line_block_start: Length,
/// 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(
@@ -1979,20 +2413,6 @@ impl LineItem {
LineItem::Float(_) => true,
}
}
-
- fn block_size(&self) -> Length {
- match self {
- LineItem::TextRun(text_run) => text_run.line_height(),
- LineItem::StartInlineBox(_) => {
- // TODO(mrobinson): This should get the line height from the font.
- Length::zero()
- },
- LineItem::EndInlineBox => Length::zero(),
- LineItem::Atomic(atomic) => atomic.size.block,
- LineItem::AbsolutelyPositioned(_) => Length::zero(),
- LineItem::Float(_) => Length::zero(),
- }
- }
}
struct TextRunLineItem {
@@ -2013,33 +2433,18 @@ fn line_height(parent_style: &ComputedValues, font_metrics: &FontMetrics) -> Len
}
}
-fn line_gap_from_style(layout_context: &LayoutContext, style: &ComputedValues) -> Length {
- crate::context::with_thread_local_font_context(layout_context, |font_context| {
- let font_group = font_context.font_group(style.clone_font());
- let font = match font_group.borrow_mut().first(font_context) {
- Some(font) => font,
- None => {
- warn!("Could not find find for TextRun.");
- return Length::zero();
- },
- };
- let font = font.borrow();
- Length::from(font.metrics.line_gap)
- })
-}
-
-fn line_height_from_style(layout_context: &LayoutContext, style: &ComputedValues) -> Length {
+fn font_metrics_from_style(layout_context: &LayoutContext, style: &ComputedValues) -> FontMetrics {
crate::context::with_thread_local_font_context(layout_context, |font_context| {
let font_group = font_context.font_group(style.clone_font());
let font = match font_group.borrow_mut().first(font_context) {
Some(font) => font,
None => {
warn!("Could not find find for TextRun.");
- return Length::zero();
+ return FontMetrics::empty();
},
};
let font = font.borrow();
- line_height(style, &font.metrics)
+ font.metrics.clone()
})
}
@@ -2108,10 +2513,6 @@ impl TextRunLineItem {
self.text.is_empty()
}
- fn line_height(&self) -> Length {
- line_height(&self.parent_style, &self.font_metrics)
- }
-
fn layout(self, state: &mut LineItemLayoutState) -> Option<TextFragment> {
if self.text.is_empty() {
return None;
@@ -2132,13 +2533,21 @@ impl TextRunLineItem {
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: LogicalVec2 {
- block: Length::zero(),
- inline: state.inline_position - state.inline_start_of_parent,
- },
+ start_corner,
size: LogicalVec2 {
- block: self.line_height(),
+ block: self.font_metrics.line_gap.into(),
inline: inline_advance,
},
};
@@ -2162,7 +2571,6 @@ struct InlineBoxLineItem {
base_fragment_info: BaseFragmentInfo,
style: Arc<ComputedValues>,
pbm: PaddingBorderMargin,
- block_size: Length,
/// 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
@@ -2173,6 +2581,14 @@ struct InlineBoxLineItem {
/// 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 {
@@ -2200,6 +2616,9 @@ impl InlineBoxLineItem {
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,
@@ -2209,11 +2628,15 @@ impl InlineBoxLineItem {
let mut nested_state = LineItemLayoutState {
inline_position: state.inline_position,
- inline_start_of_parent: 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,
- line_block_start: state.line_block_start,
justification_adjustment: state.justification_adjustment,
+ line_metrics: state.line_metrics,
+ baseline_offset: block_start_offset + space_above_baseline,
};
let mut saw_end = false;
@@ -2244,16 +2667,17 @@ impl InlineBoxLineItem {
let mut content_rect = LogicalRect {
start_corner: LogicalVec2 {
- inline: state.inline_position - state.inline_start_of_parent,
- block: Length::zero(),
+ inline: state.inline_position,
+ block: block_start_offset,
},
size: LogicalVec2 {
inline: nested_state.inline_position - state.inline_position,
- block: self.block_size,
+ block: self.font_metrics.line_gap.into(),
},
};
- state.inline_position = nested_state.inline_position + pbm_sums.inline_end;
+ // 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.
@@ -2269,10 +2693,16 @@ impl InlineBoxLineItem {
padding,
border,
margin,
- None,
+ 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);
@@ -2292,22 +2722,76 @@ impl InlineBoxLineItem {
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 the PaddingBorderMargin sum
- // start offset, which is the sum of the start component of the padding,
- // border, and margin. Offset that value by the inline start position of the
- // line layout.
- self.fragment.content_rect.start_corner.inline +=
- state.inline_position - state.inline_start_of_parent;
+ // 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 +=
@@ -2326,6 +2810,24 @@ impl AtomicLineItem {
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 {
@@ -2341,11 +2843,12 @@ impl AbsolutelyPositionedLineItem {
LogicalVec2 {
inline: match outside {
DisplayOutside::Inline => {
- state.inline_position - state.inline_start_of_parent
+ state.inline_position - state.parent_offset.inline
},
DisplayOutside::Block => Length::zero(),
},
- 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(_)) => {
@@ -2387,8 +2890,8 @@ impl FloatLineItem {
// formatting context, so that they are parented properly for StackingContext
// properties such as opacity & filters.
let distance_from_parent_to_ifc = LogicalVec2 {
- inline: state.inline_start_of_parent,
- block: state.line_block_start,
+ 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;
@@ -2416,3 +2919,64 @@ fn char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character: ch
class == XI_LINE_BREAKING_CLASS_WJ ||
class == XI_LINE_BREAKING_CLASS_ZWJ
}
+
+fn is_baseline_relative(vertical_align: GenericVerticalAlign<LengthPercentage>) -> bool {
+ match vertical_align {
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) |
+ GenericVerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => false,
+ _ => true,
+ }
+}
+
+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
+/// all inline containers get struts. In quirks mode this isn't always the case
+/// though.
+///
+/// From https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk
+///
+/// > ### § 3.3. The line height calculation quirk
+/// > In quirks mode and limited-quirks mode, an inline box that matches the following
+/// > conditions, must, for the purpose of line height calculation, act as if the box had a
+/// > line-height of zero.
+/// >
+/// > - The border-top-width, border-bottom-width, padding-top and padding-bottom
+/// > properties have a used value of zero and the box has a vertical writing mode, or the
+/// > border-right-width, border-left-width, padding-right and padding-left properties have
+/// > a used value of zero and the box has a horizontal writing mode.
+/// > - It either contains no text or it contains only collapsed whitespace.
+/// >
+/// > ### § 3.4. The blocks ignore line-height quirk
+/// > In quirks mode and limited-quirks mode, for a block container element whose content is
+/// > composed of inline-level elements, the element’s line-height must be ignored for the
+/// > purpose of calculating the minimal height of line boxes within the element.
+///
+/// Since we incorporate the size of the strut into the line-height calculation when
+/// adding text, we can simply not incorporate the strut at the start of inline box
+/// processing. This also works the same for the root of the IFC.
+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;
+ }
+
+ // This is not in a standard yet, but all browsers disable this quirk for list items.
+ // See https://github.com/whatwg/quirks/issues/38.
+ if style.get_box().display.is_list_item() {
+ return true;
+ }
+
+ pbm.map(|pbm| !pbm.padding_border_sums.inline.is_zero())
+ .unwrap_or(false)
+}