servo/components/layout_2020/formatting_contexts.rs
Martin Robinson aa073c3dca
layout: Implement support for line-height and vertical-align (#30902)
* layout: Implement support for `line-height` and `vertical-align`

This is an initial implementation of proper `line-height` and
`vertical-align` support. While this change includes the bulk of the
work there are still many missing pieces for full support. In particular
some big missing things are:

 - Flex containers do not properly compute their baselines. The idea is
   to tackle this in a followup change. This causes various flex tests
   to start failing because everything used to be top aligned.
 - The implementation of the line-height quirks (only active in quirks
   mode) are incomplete. While the quirk works in many cases, there are
   still some cases where it is handled incorrectly. This requires more
   redesign and refinement, better suited for a followup.
 - Most of the features are CSS 3 such as precision control of the
   baseline and first and last baselines are not implemented. This
   change gets us close to CSS 2.x support.

While there are many new test passes with this change some tests are
starting to fail. An accounting of new failures:

Tests failing also in Layout 2013:
 - /css/css2/positioning/toogle-abspos-on-relpos-inline-child.html (only passes in Chrome)
 - /css/CSS2/fonts/font-applies-to-001.xht (potentially an issue with font size)

Invalid tests:
 - /css/CSS2/visudet/inline-block-baseline-003.xht
 - /css/CSS2/visudet/inline-block-baseline-004.xht
 - These are are failing in all browsers. See https://bugs.chromium.org/p/chromium/issues/detail?id=1222151.

Missing table support:
 - /_mozilla/mozilla/table_valign_middle.html

Missing `font-size-adjust` support :
 - /css/css-fonts/font-size-adjust-zero-2.html (also failing in 2013)

Incomplete form field support :
- /html/rendering/widgets/the-select-element/option-add-label-quirks.html (label isn't rendered so button isn't the right size in quirks mode due to line height quirk)

Need support for calculating flexbox baseline:
 - /css/css-flexbox/fieldset-baseline-alignment.html
 - /css/css-flexbox/flex-inline.html
 - /css/css-flexbox/flexbox-baseline-multi-line-horiz-001.html
 - /css/css-flexbox/flexbox-baseline-single-item-001a.html
 - /css/css-flexbox/flexbox-baseline-single-item-001b.html

Failing because we don't create anonymous inline boxes for text children of blocks:
- /css/CSS2/linebox/anonymous-inline-inherit-001.html

Passes locally (potentially related to fonts):
 - /css/CSS2/css1/c414-flt-fit-004.xht
 - /css/css-transforms/transform-input-017.html
 - /html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-min-intrinsic-size.html
 - /css/css-fonts/first-available-font-005.html
 - /css/css-fonts/first-available-font-006.html

* Some cleanups after live review with @mukilan

Also update results.
2024-01-08 14:49:50 +00:00

237 lines
8.7 KiB
Rust

/* 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::convert::TryInto;
use serde::Serialize;
use servo_arc::Arc;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::values::specified::text::TextDecorationLine;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::flexbox::FlexContainer;
use crate::flow::BlockFormattingContext;
use crate::fragment_tree::{BaseFragmentInfo, Fragment};
use crate::positioned::PositioningContext;
use crate::replaced::ReplacedContent;
use crate::sizing::{self, ContentSizes};
use crate::style_ext::DisplayInside;
use crate::table::Table;
use crate::ContainingBlock;
/// https://drafts.csswg.org/css-display/#independent-formatting-context
#[derive(Debug, Serialize)]
pub(crate) enum IndependentFormattingContext {
NonReplaced(NonReplacedFormattingContext),
Replaced(ReplacedFormattingContext),
}
#[derive(Debug, Serialize)]
pub(crate) struct NonReplacedFormattingContext {
pub base_fragment_info: BaseFragmentInfo,
#[serde(skip_serializing)]
pub style: Arc<ComputedValues>,
/// If it was requested during construction
pub content_sizes: Option<ContentSizes>,
pub contents: NonReplacedFormattingContextContents,
}
#[derive(Debug, Serialize)]
pub(crate) struct ReplacedFormattingContext {
pub base_fragment_info: BaseFragmentInfo,
#[serde(skip_serializing)]
pub style: Arc<ComputedValues>,
pub contents: ReplacedContent,
}
// Private so that code outside of this module cannot match variants.
// It should got through methods instead.
#[derive(Debug, Serialize)]
pub(crate) enum NonReplacedFormattingContextContents {
Flow(BlockFormattingContext),
Flex(FlexContainer),
Table(Table),
// Other layout modes go here
}
pub(crate) struct IndependentLayout {
pub fragments: Vec<Fragment>,
/// https://drafts.csswg.org/css2/visudet.html#root-height
pub content_block_size: Length,
/// The offset of the last inflow baseline of this layout in the content area, if
/// there was one. This is used to propagate baselines to the ancestors of `display:
/// inline-block`.
pub last_inflow_baseline_offset: Option<Length>,
}
impl IndependentFormattingContext {
pub fn construct<'dom>(
context: &LayoutContext,
node_and_style_info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
display_inside: DisplayInside,
contents: Contents,
propagated_text_decoration_line: TextDecorationLine,
) -> Self {
match contents.try_into() {
Ok(non_replaced_contents) => {
let contents = match display_inside {
DisplayInside::Flow { is_list_item } |
DisplayInside::FlowRoot { is_list_item } => {
NonReplacedFormattingContextContents::Flow(
BlockFormattingContext::construct(
context,
node_and_style_info,
non_replaced_contents,
propagated_text_decoration_line,
is_list_item,
),
)
},
DisplayInside::Flex => {
NonReplacedFormattingContextContents::Flex(FlexContainer::construct(
context,
node_and_style_info,
non_replaced_contents,
propagated_text_decoration_line,
))
},
DisplayInside::Table => {
NonReplacedFormattingContextContents::Table(Table::construct(
context,
node_and_style_info,
non_replaced_contents,
propagated_text_decoration_line,
))
},
};
Self::NonReplaced(NonReplacedFormattingContext {
base_fragment_info: node_and_style_info.into(),
style: Arc::clone(&node_and_style_info.style),
content_sizes: None,
contents,
})
},
Err(contents) => Self::Replaced(ReplacedFormattingContext {
base_fragment_info: node_and_style_info.into(),
style: Arc::clone(&node_and_style_info.style),
contents,
}),
}
}
pub fn construct_for_text_runs<'dom>(
node_and_style_info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
runs: impl Iterator<Item = crate::flow::inline::TextRun>,
propagated_text_decoration_line: TextDecorationLine,
) -> Self {
let bfc =
BlockFormattingContext::construct_for_text_runs(runs, propagated_text_decoration_line);
Self::NonReplaced(NonReplacedFormattingContext {
base_fragment_info: node_and_style_info.into(),
style: Arc::clone(&node_and_style_info.style),
content_sizes: None,
contents: NonReplacedFormattingContextContents::Flow(bfc),
})
}
pub fn style(&self) -> &Arc<ComputedValues> {
match self {
Self::NonReplaced(inner) => &inner.style,
Self::Replaced(inner) => &inner.style,
}
}
pub fn base_fragment_info(&self) -> BaseFragmentInfo {
match self {
Self::NonReplaced(inner) => inner.base_fragment_info,
Self::Replaced(inner) => inner.base_fragment_info,
}
}
pub fn inline_content_sizes(&self, layout_context: &LayoutContext) -> ContentSizes {
match self {
Self::NonReplaced(inner) => inner
.contents
.inline_content_sizes(layout_context, inner.style.writing_mode),
Self::Replaced(inner) => inner.contents.inline_content_sizes(&inner.style),
}
}
pub fn outer_inline_content_sizes(
&mut self,
layout_context: &LayoutContext,
containing_block_writing_mode: WritingMode,
) -> ContentSizes {
match self {
Self::NonReplaced(non_replaced) => {
let style = &non_replaced.style;
let content_sizes = &mut non_replaced.content_sizes;
let contents = &non_replaced.contents;
sizing::outer_inline(&style, containing_block_writing_mode, || {
content_sizes
.get_or_insert_with(|| {
contents.inline_content_sizes(layout_context, style.writing_mode)
})
.clone()
})
},
Self::Replaced(replaced) => {
sizing::outer_inline(&replaced.style, containing_block_writing_mode, || {
replaced.contents.inline_content_sizes(&replaced.style)
})
},
}
}
}
impl NonReplacedFormattingContext {
pub fn layout(
&self,
layout_context: &LayoutContext,
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
) -> IndependentLayout {
match &self.contents {
NonReplacedFormattingContextContents::Flow(bfc) => {
bfc.layout(layout_context, positioning_context, containing_block)
},
NonReplacedFormattingContextContents::Flex(fc) => {
fc.layout(layout_context, positioning_context, containing_block)
},
NonReplacedFormattingContextContents::Table(table) => {
table.layout(layout_context, positioning_context, containing_block)
},
}
}
pub fn inline_content_sizes(&mut self, layout_context: &LayoutContext) -> ContentSizes {
let writing_mode = self.style.writing_mode;
let contents = &self.contents;
self.content_sizes
.get_or_insert_with(|| contents.inline_content_sizes(layout_context, writing_mode))
.clone()
}
}
impl NonReplacedFormattingContextContents {
pub fn inline_content_sizes(
&self,
layout_context: &LayoutContext,
writing_mode: WritingMode,
) -> ContentSizes {
match self {
Self::Flow(inner) => inner
.contents
.inline_content_sizes(layout_context, writing_mode),
Self::Flex(inner) => inner.inline_content_sizes(),
Self::Table(table) => table.inline_content_sizes(),
}
}
}