use std::borrow::ToOwned;
use std::cmp::{max, min, Ordering};
use std::collections::LinkedList;
use std::sync::{Arc, Mutex};
use std::{f32, fmt};
use app_units::Au;
use base::id::{BrowsingContextId, PipelineId};
use base::text::is_bidi_control;
use bitflags::bitflags;
use canvas_traits::canvas::{CanvasId, CanvasMsg};
use euclid::default::{Point2D, Rect, Size2D, Vector2D};
use fonts::ByteIndex;
use html5ever::{local_name, namespace_url, ns};
use ipc_channel::ipc::IpcSender;
use log::debug;
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
use pixels::{Image, ImageMetadata};
use range::*;
use script_layout_interface::wrapper_traits::{
PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
use script_layout_interface::{
HTMLCanvasData, HTMLCanvasDataSource, HTMLMediaData, MediaFrame, SVGSVGData,
};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use servo_url::ServoUrl;
use style::computed_values::border_collapse::T as BorderCollapse;
use style::computed_values::box_sizing::T as BoxSizing;
use style::computed_values::clear::T as Clear;
use style::computed_values::color::T as Color;
use style::computed_values::display::T as Display;
use style::computed_values::mix_blend_mode::T as MixBlendMode;
use style::computed_values::overflow_wrap::T as OverflowWrap;
use style::computed_values::overflow_x::T as StyleOverflow;
use style::computed_values::position::T as Position;
use style::computed_values::text_decoration_line::T as TextDecorationLine;
use style::computed_values::text_wrap_mode::T as TextWrapMode;
use style::computed_values::transform_style::T as TransformStyle;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::computed_values::word_break::T as WordBreak;
use style::logical_geometry::{Direction, LogicalMargin, LogicalRect, LogicalSize, WritingMode};
use style::properties::ComputedValues;
use style::selector_parser::RestyleDamage;
use style::servo::restyle_damage::ServoRestyleDamage;
use style::str::char_is_whitespace;
use style::values::computed::counters::ContentItem;
use style::values::computed::{Length, VerticalAlign};
use style::values::generics::box_::{Perspective, VerticalAlignKeyword};
use style::values::generics::transform;
use webrender_api::units::LayoutTransform;
use webrender_api::{self, ImageKey};
use crate::context::LayoutContext;
use crate::display_list::items::{ClipScrollNodeIndex, OpaqueNode, BLUR_INFLATION_FACTOR};
use crate::display_list::{StackingContextId, ToLayout};
use crate::floats::ClearType;
use crate::flow::{GetBaseFlow, ImmutableFlowUtils};
use crate::flow_ref::FlowRef;
use crate::inline::{
InlineFragmentContext, InlineFragmentNodeFlags, InlineFragmentNodeInfo, InlineMetrics,
LineMetrics,
};
#[cfg(debug_assertions)]
use crate::layout_debug;
use crate::model::{
self, style_length, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, SizeConstraint,
};
use crate::text::TextRunScanner;
use crate::text_run::{TextRun, TextRunSlice};
use crate::wrapper::ThreadSafeLayoutNodeHelpers;
use crate::{text, ServoArc};
static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34;
static DEFAULT_REPLACED_WIDTH: i32 = 300;
static DEFAULT_REPLACED_HEIGHT: i32 = 150;
#[derive(Clone)]
pub struct Fragment {
pub node: OpaqueNode,
pub style: ServoArc<ComputedValues>,
pub selected_style: ServoArc<ComputedValues>,
pub border_box: LogicalRect<Au>,
pub border_padding: LogicalMargin<Au>,
pub margin: LogicalMargin<Au>,
pub specific: SpecificFragmentInfo,
pub inline_context: Option<InlineFragmentContext>,
pub restyle_damage: RestyleDamage,
pub pseudo: PseudoElementType,
pub flags: FragmentFlags,
debug_id: DebugId,
pub stacking_context_id: StackingContextId,
pub established_reference_frame: Option<ClipScrollNodeIndex>,
}
impl Serialize for Fragment {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut serializer = serializer.serialize_struct("fragment", 3)?;
serializer.serialize_field("id", &self.debug_id)?;
serializer.serialize_field("border_box", &self.border_box)?;
serializer.serialize_field("margin", &self.margin)?;
serializer.end()
}
}
#[derive(Clone)]
pub enum SpecificFragmentInfo {
Generic,
GeneratedContent(Box<GeneratedContentInfo>),
Iframe(IframeFragmentInfo),
Image(Box<ImageFragmentInfo>),
Media(Box<MediaFragmentInfo>),
Canvas(Box<CanvasFragmentInfo>),
Svg(Box<SvgFragmentInfo>),
InlineAbsoluteHypothetical(InlineAbsoluteHypotheticalFragmentInfo),
InlineBlock(InlineBlockFragmentInfo),
InlineAbsolute(InlineAbsoluteFragmentInfo),
ScannedText(Box<ScannedTextFragmentInfo>),
Table,
TableCell,
TableColumn(TableColumnFragmentInfo),
TableRow,
TableWrapper,
Multicol,
MulticolColumn,
UnscannedText(Box<UnscannedTextFragmentInfo>),
TruncatedFragment(Box<TruncatedFragmentInfo>),
}
impl SpecificFragmentInfo {
fn restyle_damage(&self) -> RestyleDamage {
let flow = match *self {
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::Svg(_) |
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableWrapper |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::MulticolColumn |
SpecificFragmentInfo::UnscannedText(_) |
SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Generic => return RestyleDamage::empty(),
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => &info.flow_ref,
SpecificFragmentInfo::InlineAbsolute(ref info) => &info.flow_ref,
SpecificFragmentInfo::InlineBlock(ref info) => &info.flow_ref,
};
flow.base().restyle_damage
}
pub fn get_type(&self) -> &'static str {
match *self {
SpecificFragmentInfo::Canvas(_) => "SpecificFragmentInfo::Canvas",
SpecificFragmentInfo::Media(_) => "SpecificFragmentInfo::Media",
SpecificFragmentInfo::Generic => "SpecificFragmentInfo::Generic",
SpecificFragmentInfo::GeneratedContent(_) => "SpecificFragmentInfo::GeneratedContent",
SpecificFragmentInfo::Iframe(_) => "SpecificFragmentInfo::Iframe",
SpecificFragmentInfo::Image(_) => "SpecificFragmentInfo::Image",
SpecificFragmentInfo::InlineAbsolute(_) => "SpecificFragmentInfo::InlineAbsolute",
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {
"SpecificFragmentInfo::InlineAbsoluteHypothetical"
},
SpecificFragmentInfo::InlineBlock(_) => "SpecificFragmentInfo::InlineBlock",
SpecificFragmentInfo::ScannedText(_) => "SpecificFragmentInfo::ScannedText",
SpecificFragmentInfo::Svg(_) => "SpecificFragmentInfo::Svg",
SpecificFragmentInfo::Table => "SpecificFragmentInfo::Table",
SpecificFragmentInfo::TableCell => "SpecificFragmentInfo::TableCell",
SpecificFragmentInfo::TableColumn(_) => "SpecificFragmentInfo::TableColumn",
SpecificFragmentInfo::TableRow => "SpecificFragmentInfo::TableRow",
SpecificFragmentInfo::TableWrapper => "SpecificFragmentInfo::TableWrapper",
SpecificFragmentInfo::Multicol => "SpecificFragmentInfo::Multicol",
SpecificFragmentInfo::MulticolColumn => "SpecificFragmentInfo::MulticolColumn",
SpecificFragmentInfo::UnscannedText(_) => "SpecificFragmentInfo::UnscannedText",
SpecificFragmentInfo::TruncatedFragment(_) => "SpecificFragmentInfo::TruncatedFragment",
}
}
}
impl fmt::Debug for SpecificFragmentInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
SpecificFragmentInfo::ScannedText(ref info) => write!(f, "{:?}", info.text()),
SpecificFragmentInfo::UnscannedText(ref info) => write!(f, "{:?}", info.text),
_ => Ok(()),
}
}
}
#[derive(Clone)]
pub enum GeneratedContentInfo {
ListItem,
ContentItem(ContentItem),
Empty,
}
#[derive(Clone)]
pub struct InlineAbsoluteHypotheticalFragmentInfo {
pub flow_ref: FlowRef,
}
impl InlineAbsoluteHypotheticalFragmentInfo {
pub fn new(flow_ref: FlowRef) -> InlineAbsoluteHypotheticalFragmentInfo {
InlineAbsoluteHypotheticalFragmentInfo { flow_ref }
}
}
#[derive(Clone)]
pub struct InlineBlockFragmentInfo {
pub flow_ref: FlowRef,
}
impl InlineBlockFragmentInfo {
pub fn new(flow_ref: FlowRef) -> InlineBlockFragmentInfo {
InlineBlockFragmentInfo { flow_ref }
}
}
#[derive(Clone)]
pub struct InlineAbsoluteFragmentInfo {
pub flow_ref: FlowRef,
}
impl InlineAbsoluteFragmentInfo {
pub fn new(flow_ref: FlowRef) -> InlineAbsoluteFragmentInfo {
InlineAbsoluteFragmentInfo { flow_ref }
}
}
#[derive(Clone)]
pub enum CanvasFragmentSource {
WebGL(ImageKey),
Image(Arc<Mutex<IpcSender<CanvasMsg>>>),
WebGPU(ImageKey),
Empty,
}
#[derive(Clone)]
pub struct CanvasFragmentInfo {
pub source: CanvasFragmentSource,
pub dom_width: Au,
pub dom_height: Au,
pub canvas_id: CanvasId,
}
impl CanvasFragmentInfo {
pub fn new(data: HTMLCanvasData) -> CanvasFragmentInfo {
let source = match data.source {
HTMLCanvasDataSource::WebGL(texture_id) => CanvasFragmentSource::WebGL(texture_id),
HTMLCanvasDataSource::Image(ipc_sender) => {
CanvasFragmentSource::Image(Arc::new(Mutex::new(ipc_sender)))
},
HTMLCanvasDataSource::WebGPU(image_key) => CanvasFragmentSource::WebGPU(image_key),
HTMLCanvasDataSource::Empty => CanvasFragmentSource::Empty,
};
CanvasFragmentInfo {
source,
dom_width: Au::from_px(data.width as i32),
dom_height: Au::from_px(data.height as i32),
canvas_id: data.canvas_id,
}
}
}
#[derive(Clone)]
pub struct MediaFragmentInfo {
pub current_frame: Option<MediaFrame>,
}
impl MediaFragmentInfo {
pub fn new(data: HTMLMediaData) -> MediaFragmentInfo {
MediaFragmentInfo {
current_frame: data.current_frame,
}
}
}
#[derive(Clone)]
pub struct SvgFragmentInfo {
pub dom_width: Au,
pub dom_height: Au,
}
impl SvgFragmentInfo {
pub fn new(data: SVGSVGData) -> SvgFragmentInfo {
SvgFragmentInfo {
dom_width: Au::from_px(data.width as i32),
dom_height: Au::from_px(data.height as i32),
}
}
}
#[derive(Clone)]
pub struct ImageFragmentInfo {
pub image: Option<Arc<Image>>,
pub metadata: Option<ImageMetadata>,
}
enum ImageOrMetadata {
Image(Arc<Image>),
Metadata(ImageMetadata),
}
impl ImageFragmentInfo {
pub fn new<'dom>(
url: Option<ServoUrl>,
density: Option<f64>,
node: &impl ThreadSafeLayoutNode<'dom>,
layout_context: &LayoutContext,
) -> ImageFragmentInfo {
let image_or_metadata = node
.image_data()
.and_then(|(image, metadata)| match (image, metadata) {
(Some(image), _) => Some(ImageOrMetadata::Image(image)),
(None, Some(metadata)) => Some(ImageOrMetadata::Metadata(metadata)),
_ => None,
})
.or_else(|| {
url.and_then(|url| {
layout_context
.get_or_request_image_or_meta(node.opaque(), url, UsePlaceholder::Yes)
.map(|result| match result {
ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
ImageOrMetadata::Image(image)
},
ImageOrMetadataAvailable::MetadataAvailable(m) => {
ImageOrMetadata::Metadata(m)
},
})
})
});
let current_pixel_density = density.unwrap_or(1f64);
let (image, metadata) = match image_or_metadata {
Some(ImageOrMetadata::Image(i)) => {
let height = (i.height as f64 / current_pixel_density) as u32;
let width = (i.width as f64 / current_pixel_density) as u32;
(
Some(Arc::new(Image {
height,
width,
..(*i).clone()
})),
Some(ImageMetadata { height, width }),
)
},
Some(ImageOrMetadata::Metadata(m)) => (
None,
Some(ImageMetadata {
height: (m.height as f64 / current_pixel_density) as u32,
width: (m.width as f64 / current_pixel_density) as u32,
}),
),
None => (None, None),
};
ImageFragmentInfo { image, metadata }
}
}
#[derive(Clone)]
pub struct IframeFragmentInfo {
pub browsing_context_id: Option<BrowsingContextId>,
pub pipeline_id: Option<PipelineId>,
}
impl IframeFragmentInfo {
pub fn new<'dom>(node: &impl ThreadSafeLayoutNode<'dom>) -> IframeFragmentInfo {
let browsing_context_id = node.iframe_browsing_context_id();
let pipeline_id = node.iframe_pipeline_id();
IframeFragmentInfo {
browsing_context_id,
pipeline_id,
}
}
}
#[derive(Clone)]
pub struct ScannedTextFragmentInfo {
pub run: Arc<TextRun>,
pub content_size: LogicalSize<Au>,
pub insertion_point: Option<ByteIndex>,
pub range: Range<ByteIndex>,
pub range_end_including_stripped_whitespace: ByteIndex,
pub flags: ScannedTextFlags,
}
bitflags! {
#[derive(Clone, Copy)]
pub struct ScannedTextFlags: u8 {
const REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES = 0x01;
const SELECTED = 0x02;
const SUPPRESS_LINE_BREAK_BEFORE = 0x04;
}
}
impl ScannedTextFragmentInfo {
pub fn new(
run: Arc<TextRun>,
range: Range<ByteIndex>,
content_size: LogicalSize<Au>,
insertion_point: Option<ByteIndex>,
flags: ScannedTextFlags,
) -> ScannedTextFragmentInfo {
ScannedTextFragmentInfo {
run,
range,
insertion_point,
content_size,
range_end_including_stripped_whitespace: range.end(),
flags,
}
}
pub fn text(&self) -> &str {
&self.run.text[self.range.begin().to_usize()..self.range.end().to_usize()]
}
pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
self.flags
.contains(ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES)
}
pub fn selected(&self) -> bool {
self.flags.contains(ScannedTextFlags::SELECTED)
}
}
#[derive(Clone, Debug)]
pub struct SplitInfo {
pub range: Range<ByteIndex>,
pub inline_size: Au,
}
impl SplitInfo {
fn new(range: Range<ByteIndex>, info: &ScannedTextFragmentInfo) -> SplitInfo {
let inline_size = info.run.advance_for_range(&range);
SplitInfo { range, inline_size }
}
}
pub struct SplitResult {
pub inline_start: Option<SplitInfo>,
pub inline_end: Option<SplitInfo>,
pub text_run: Arc<TextRun>,
}
struct TruncationResult {
split: SplitInfo,
text_run: Arc<TextRun>,
}
#[derive(Clone)]
pub struct UnscannedTextFragmentInfo {
pub text: Box<str>,
pub selection: Option<Range<ByteIndex>>,
}
impl UnscannedTextFragmentInfo {
#[inline]
pub fn new(text: Box<str>, selection: Option<Range<ByteIndex>>) -> UnscannedTextFragmentInfo {
UnscannedTextFragmentInfo { text, selection }
}
}
#[derive(Clone, Copy)]
pub struct TableColumnFragmentInfo {
pub span: u32,
}
impl TableColumnFragmentInfo {
pub fn new<'dom>(node: &impl ThreadSafeLayoutNode<'dom>) -> TableColumnFragmentInfo {
let element = node.as_element().unwrap();
let span = element
.get_attr(&ns!(), &local_name!("span"))
.and_then(|string| string.parse().ok())
.unwrap_or(0);
TableColumnFragmentInfo { span }
}
}
#[derive(Clone)]
pub struct TruncatedFragmentInfo {
pub text_info: Option<ScannedTextFragmentInfo>,
pub full: Fragment,
}
impl Fragment {
pub fn new<'dom>(
node: &impl ThreadSafeLayoutNode<'dom>,
specific: SpecificFragmentInfo,
ctx: &LayoutContext,
) -> Fragment {
let shared_context = ctx.shared_context();
let style = node.style(shared_context);
let writing_mode = style.writing_mode;
let mut restyle_damage = node.restyle_damage();
restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
let mut flags = FragmentFlags::empty();
let is_body = node
.as_element()
.map(|element| element.is_body_element_of_html_element_root())
.unwrap_or(false);
if is_body {
flags |= FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT;
}
Fragment {
node: node.opaque(),
style,
selected_style: node.selected_style(),
restyle_damage,
border_box: LogicalRect::zero(writing_mode),
border_padding: LogicalMargin::zero(writing_mode),
margin: LogicalMargin::zero(writing_mode),
specific,
inline_context: None,
pseudo: node.get_pseudo_element_type(),
flags,
debug_id: DebugId::new(),
stacking_context_id: StackingContextId::root(),
established_reference_frame: None,
}
}
pub fn from_opaque_node_and_style(
node: OpaqueNode,
pseudo: PseudoElementType,
style: ServoArc<ComputedValues>,
selected_style: ServoArc<ComputedValues>,
mut restyle_damage: RestyleDamage,
specific: SpecificFragmentInfo,
) -> Fragment {
let writing_mode = style.writing_mode;
restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
Fragment {
node,
style,
selected_style,
restyle_damage,
border_box: LogicalRect::zero(writing_mode),
border_padding: LogicalMargin::zero(writing_mode),
margin: LogicalMargin::zero(writing_mode),
specific,
inline_context: None,
pseudo,
flags: FragmentFlags::empty(),
debug_id: DebugId::new(),
stacking_context_id: StackingContextId::root(),
established_reference_frame: None,
}
}
pub fn create_similar_anonymous_fragment(
&self,
style: ServoArc<ComputedValues>,
specific: SpecificFragmentInfo,
) -> Fragment {
let writing_mode = style.writing_mode;
Fragment {
node: self.node,
style,
selected_style: self.selected_style.clone(),
restyle_damage: self.restyle_damage,
border_box: LogicalRect::zero(writing_mode),
border_padding: LogicalMargin::zero(writing_mode),
margin: LogicalMargin::zero(writing_mode),
specific,
inline_context: None,
pseudo: self.pseudo,
flags: FragmentFlags::empty(),
debug_id: DebugId::new(),
stacking_context_id: StackingContextId::root(),
established_reference_frame: None,
}
}
pub fn transform(&self, size: LogicalSize<Au>, info: SpecificFragmentInfo) -> Fragment {
let new_border_box =
LogicalRect::from_point_size(self.style.writing_mode, self.border_box.start, size);
let mut restyle_damage = RestyleDamage::rebuild_and_reflow();
restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
Fragment {
node: self.node,
style: self.style.clone(),
selected_style: self.selected_style.clone(),
restyle_damage,
border_box: new_border_box,
border_padding: self.border_padding,
margin: self.margin,
specific: info,
inline_context: self.inline_context.clone(),
pseudo: self.pseudo,
flags: FragmentFlags::empty(),
debug_id: self.debug_id.clone(),
stacking_context_id: StackingContextId::root(),
established_reference_frame: None,
}
}
pub fn transform_with_split_info(
&self,
split: &SplitInfo,
text_run: Arc<TextRun>,
first: bool,
) -> Fragment {
let size = LogicalSize::new(
self.style.writing_mode,
split.inline_size,
self.border_box.size.block,
);
let (mut flags, insertion_point) = match self.specific {
SpecificFragmentInfo::ScannedText(ref info) => match info.insertion_point {
Some(index) if split.range.contains(index) => (info.flags, info.insertion_point),
Some(index)
if index == ByteIndex(text_run.text.chars().count() as isize - 1) &&
index == split.range.end() =>
{
(info.flags, info.insertion_point)
},
_ => (info.flags, None),
},
_ => (ScannedTextFlags::empty(), None),
};
if !first {
flags.set(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE, false);
}
let info = Box::new(ScannedTextFragmentInfo::new(
text_run,
split.range,
size,
insertion_point,
flags,
));
self.transform(size, SpecificFragmentInfo::ScannedText(info))
}
pub fn transform_into_ellipsis(
&self,
layout_context: &LayoutContext,
text_overflow_string: String,
) -> Fragment {
let mut unscanned_ellipsis_fragments = LinkedList::new();
let mut ellipsis_fragment = self.transform(
self.border_box.size,
SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
text_overflow_string.into_boxed_str(),
None,
))),
);
unscanned_ellipsis_fragments.push_back(ellipsis_fragment);
let ellipsis_fragments = TextRunScanner::new()
.scan_for_runs(&layout_context.font_context, unscanned_ellipsis_fragments);
debug_assert_eq!(ellipsis_fragments.len(), 1);
ellipsis_fragment = ellipsis_fragments.fragments.into_iter().next().unwrap();
ellipsis_fragment.flags |= FragmentFlags::IS_ELLIPSIS;
ellipsis_fragment
}
pub fn restyle_damage(&self) -> RestyleDamage {
self.restyle_damage | self.specific.restyle_damage()
}
pub fn contains_node(&self, node_address: OpaqueNode) -> bool {
node_address == self.node ||
self.inline_context
.as_ref()
.is_some_and(|ctx| ctx.contains_node(node_address))
}
pub fn add_inline_context_style(&mut self, node_info: InlineFragmentNodeInfo) {
if self.inline_context.is_none() {
self.inline_context = Some(InlineFragmentContext::new());
}
self.inline_context.as_mut().unwrap().nodes.push(node_info);
}
fn quantities_included_in_intrinsic_inline_size(
&self,
) -> QuantitiesIncludedInIntrinsicInlineSizes {
match self.specific {
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::Svg(_) => {
QuantitiesIncludedInIntrinsicInlineSizes::all()
}
SpecificFragmentInfo::Table => {
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED |
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING |
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER
}
SpecificFragmentInfo::TableCell => {
let base_quantities = QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING |
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED;
if self.style.get_inherited_table().border_collapse ==
BorderCollapse::Separate {
base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER
} else {
base_quantities
}
}
SpecificFragmentInfo::TableWrapper => {
let base_quantities = QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS |
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED;
if self.style.get_inherited_table().border_collapse ==
BorderCollapse::Separate {
base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER
} else {
base_quantities
}
}
SpecificFragmentInfo::TableRow => {
let base_quantities =
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED;
if self.style.get_inherited_table().border_collapse ==
BorderCollapse::Separate {
base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER
} else {
base_quantities
}
}
SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::UnscannedText(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineBlock(_) |
SpecificFragmentInfo::MulticolColumn => {
QuantitiesIncludedInIntrinsicInlineSizes::empty()
}
}
}
pub fn surrounding_intrinsic_inline_size(&self) -> (Au, Au) {
let flags = self.quantities_included_in_intrinsic_inline_size();
let style = self.style();
let margin = if flags.contains(
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS,
) {
let margin = style.logical_margin();
MaybeAuto::from_margin(margin.inline_start, Au(0)).specified_or_zero() +
MaybeAuto::from_margin(margin.inline_end, Au(0)).specified_or_zero()
} else {
Au(0)
};
let padding = if flags.contains(
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING,
) {
let padding = style.logical_padding();
padding.inline_start.to_used_value(Au(0)) + padding.inline_end.to_used_value(Au(0))
} else {
Au(0)
};
let border = if flags.contains(
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER,
) {
self.border_width().inline_start_end()
} else {
Au(0)
};
(border + padding, margin)
}
pub fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizesContribution {
let flags = self.quantities_included_in_intrinsic_inline_size();
let style = self.style();
let (border_padding, margin) = self.surrounding_intrinsic_inline_size();
let mut specified = Au(0);
if flags.contains(
QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED,
) {
specified = style
.content_inline_size()
.to_used_value(Au(0))
.unwrap_or(Au(0));
specified = max(
style
.min_inline_size()
.to_used_value(Au(0))
.unwrap_or(Au(0)),
specified,
);
if let Some(max) = style.max_inline_size().to_used_value(Au(0)) {
specified = min(specified, max)
}
if self.style.get_position().box_sizing == BoxSizing::BorderBox {
specified = max(Au(0), specified - border_padding);
}
}
IntrinsicISizesContribution {
content_intrinsic_sizes: IntrinsicISizes {
minimum_inline_size: specified,
preferred_inline_size: specified,
},
surrounding_size: border_padding + margin,
}
}
#[inline]
pub fn intrinsic_width(&self) -> Au {
match self.specific {
SpecificFragmentInfo::Image(ref info) => {
if let Some(ref data) = info.metadata {
Au::from_px(data.width as i32)
} else {
Au(0)
}
},
SpecificFragmentInfo::Media(ref info) => info
.current_frame
.map_or(Au(0), |frame| Au::from_px(frame.width)),
SpecificFragmentInfo::Canvas(ref info) => info.dom_width,
SpecificFragmentInfo::Svg(ref info) => info.dom_width,
SpecificFragmentInfo::Iframe(_) => Au::from_px(DEFAULT_REPLACED_WIDTH),
_ => panic!("Trying to get intrinsic width on non-replaced element!"),
}
}
#[inline]
pub fn intrinsic_height(&self) -> Au {
match self.specific {
SpecificFragmentInfo::Image(ref info) => {
if let Some(ref data) = info.metadata {
Au::from_px(data.height as i32)
} else {
Au(0)
}
},
SpecificFragmentInfo::Media(ref info) => info
.current_frame
.map_or(Au(0), |frame| Au::from_px(frame.height)),
SpecificFragmentInfo::Canvas(ref info) => info.dom_height,
SpecificFragmentInfo::Svg(ref info) => info.dom_height,
SpecificFragmentInfo::Iframe(_) => Au::from_px(DEFAULT_REPLACED_HEIGHT),
_ => panic!("Trying to get intrinsic height on non-replaced element!"),
}
}
pub fn has_intrinsic_ratio(&self) -> bool {
match self.specific {
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::Svg(_) =>
self.intrinsic_width() != Au(0) && self.intrinsic_height() != Au(0),
_ => false
}
}
pub fn calculate_replaced_sizes(
&self,
containing_inline_size: Option<Au>,
containing_block_size: Option<Au>,
) -> (Au, Au) {
let (intrinsic_inline_size, intrinsic_block_size) = if self.style.writing_mode.is_vertical()
{
(self.intrinsic_height(), self.intrinsic_width())
} else {
(self.intrinsic_width(), self.intrinsic_height())
};
let inline_size = style_length(self.style.content_inline_size(), containing_inline_size)
.map(|x| x - self.box_sizing_boundary(Direction::Inline));
let block_size = style_length(self.style.content_block_size(), containing_block_size)
.map(|x| x - self.box_sizing_boundary(Direction::Block));
let inline_constraint = self.size_constraint(containing_inline_size, Direction::Inline);
let block_constraint = self.size_constraint(containing_block_size, Direction::Block);
match (inline_size, block_size) {
(MaybeAuto::Specified(inline_size), MaybeAuto::Specified(block_size)) => (
inline_constraint.clamp(inline_size),
block_constraint.clamp(block_size),
),
(MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => {
let inline_size = inline_constraint.clamp(inline_size);
let block_size = if self.has_intrinsic_ratio() {
Au::new(
(inline_size.0 as i64 * intrinsic_block_size.0 as i64 /
intrinsic_inline_size.0 as i64) as i32,
)
} else {
intrinsic_block_size
};
(inline_size, block_constraint.clamp(block_size))
},
(MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => {
let block_size = block_constraint.clamp(block_size);
let inline_size = if self.has_intrinsic_ratio() {
Au::new(
(block_size.0 as i64 * intrinsic_inline_size.0 as i64 /
intrinsic_block_size.0 as i64) as i32,
)
} else {
intrinsic_inline_size
};
(inline_constraint.clamp(inline_size), block_size)
},
(MaybeAuto::Auto, MaybeAuto::Auto) => {
if self.has_intrinsic_ratio() {
let first_isize = inline_constraint.clamp(intrinsic_inline_size);
let first_bsize = Au::new(
(first_isize.0 as i64 * intrinsic_block_size.0 as i64 /
intrinsic_inline_size.0 as i64) as i32,
);
let second_bsize = block_constraint.clamp(intrinsic_block_size);
let second_isize = Au::new(
(second_bsize.0 as i64 * intrinsic_inline_size.0 as i64 /
intrinsic_block_size.0 as i64) as i32,
);
let (inline_size, block_size) = match (
first_isize.cmp(&intrinsic_inline_size),
second_isize.cmp(&intrinsic_inline_size),
) {
(Ordering::Equal, Ordering::Equal) => (first_isize, first_bsize),
(Ordering::Equal, _) => (second_isize, second_bsize),
(_, Ordering::Equal) => (first_isize, first_bsize),
(Ordering::Greater, Ordering::Greater) => {
if first_isize > second_isize {
(first_isize, first_bsize)
} else {
(second_isize, second_bsize)
}
},
(Ordering::Less, Ordering::Less) => {
if first_isize > second_isize {
(second_isize, second_bsize)
} else {
(first_isize, first_bsize)
}
},
(Ordering::Less, Ordering::Greater) |
(Ordering::Greater, Ordering::Less) => (first_isize, first_bsize),
};
(
inline_constraint.clamp(inline_size),
block_constraint.clamp(block_size),
)
} else {
(
inline_constraint.clamp(intrinsic_inline_size),
block_constraint.clamp(intrinsic_block_size),
)
}
},
}
}
pub fn size_constraint(
&self,
containing_size: Option<Au>,
direction: Direction,
) -> SizeConstraint {
let (style_min_size, style_max_size) = match direction {
Direction::Inline => (self.style.min_inline_size(), self.style.max_inline_size()),
Direction::Block => (self.style.min_block_size(), self.style.max_block_size()),
};
let border = if self.style().get_position().box_sizing == BoxSizing::BorderBox {
Some(self.border_padding.start_end(direction))
} else {
None
};
SizeConstraint::new(containing_size, style_min_size, style_max_size, border)
}
pub fn guess_inline_content_edge_offsets(&self) -> SpeculatedInlineContentEdgeOffsets {
let logical_margin = self.style.logical_margin();
let logical_padding = self.style.logical_padding();
let border_width = self.border_width();
SpeculatedInlineContentEdgeOffsets {
start: MaybeAuto::from_margin(logical_margin.inline_start, Au(0)).specified_or_zero() +
logical_padding.inline_start.to_used_value(Au(0)) +
border_width.inline_start,
end: MaybeAuto::from_margin(logical_margin.inline_end, Au(0)).specified_or_zero() +
logical_padding.inline_end.to_used_value(Au(0)) +
border_width.inline_end,
}
}
#[inline]
pub fn border_width(&self) -> LogicalMargin<Au> {
let style_border_width = self.style().logical_border_width();
let writing_mode = self.style.writing_mode;
let context_border = match self.inline_context {
None => LogicalMargin::zero(writing_mode),
Some(ref inline_fragment_context) => inline_fragment_context.nodes.iter().fold(
style_border_width,
|accumulator, node| {
let mut this_border_width =
node.style.border_width_for_writing_mode(writing_mode);
if !node
.flags
.contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
{
this_border_width.inline_start = Au(0)
}
if !node
.flags
.contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
{
this_border_width.inline_end = Au(0)
}
accumulator + this_border_width
},
),
};
style_border_width + context_border
}
pub fn box_sizing_boundary(&self, direction: Direction) -> Au {
match (self.style().get_position().box_sizing, direction) {
(BoxSizing::BorderBox, Direction::Inline) => self.border_padding.inline_start_end(),
(BoxSizing::BorderBox, Direction::Block) => self.border_padding.block_start_end(),
_ => Au(0),
}
}
pub fn compute_inline_direction_margins(&mut self, containing_block_inline_size: Au) {
match self.specific {
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {
self.margin.inline_start = Au(0);
self.margin.inline_end = Au(0);
return;
},
_ => {
let (inline_start, inline_end) = {
let margin = self.style().logical_margin();
(
MaybeAuto::from_margin(margin.inline_start, containing_block_inline_size)
.specified_or_zero(),
MaybeAuto::from_margin(margin.inline_end, containing_block_inline_size)
.specified_or_zero(),
)
};
self.margin.inline_start = inline_start;
self.margin.inline_end = inline_end;
},
}
if let Some(ref inline_context) = self.inline_context {
for node in &inline_context.nodes {
let margin = node.style.logical_margin();
let this_inline_start_margin = if !node
.flags
.contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
{
Au(0)
} else {
MaybeAuto::from_margin(margin.inline_start, containing_block_inline_size)
.specified_or_zero()
};
let this_inline_end_margin = if !node
.flags
.contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
{
Au(0)
} else {
MaybeAuto::from_margin(margin.inline_end, containing_block_inline_size)
.specified_or_zero()
};
self.margin.inline_start += this_inline_start_margin;
self.margin.inline_end += this_inline_end_margin;
}
}
}
pub fn compute_block_direction_margins(&mut self, containing_block_inline_size: Au) {
match self.specific {
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableColumn(_) => {
self.margin.block_start = Au(0);
self.margin.block_end = Au(0)
},
_ => {
let (block_start, block_end) = {
let margin = self.style().logical_margin();
(
MaybeAuto::from_margin(margin.block_start, containing_block_inline_size)
.specified_or_zero(),
MaybeAuto::from_margin(margin.block_end, containing_block_inline_size)
.specified_or_zero(),
)
};
self.margin.block_start = block_start;
self.margin.block_end = block_end;
},
}
}
pub fn compute_border_and_padding(&mut self, containing_block_inline_size: Au) {
let border = match self.style.get_inherited_table().border_collapse {
BorderCollapse::Separate => self.border_width(),
BorderCollapse::Collapse => LogicalMargin::zero(self.style.writing_mode),
};
let padding_from_style = match self.specific {
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableWrapper => LogicalMargin::zero(self.style.writing_mode),
_ => model::padding_from_style(
self.style(),
containing_block_inline_size,
self.style().writing_mode,
),
};
let padding_from_inline_fragment_context = match (&self.specific, &self.inline_context) {
(_, &None) |
(&SpecificFragmentInfo::TableColumn(_), _) |
(&SpecificFragmentInfo::TableRow, _) |
(&SpecificFragmentInfo::TableWrapper, _) => {
LogicalMargin::zero(self.style.writing_mode)
},
(_, Some(inline_fragment_context)) => {
let writing_mode = self.style.writing_mode;
let zero_padding = LogicalMargin::zero(writing_mode);
inline_fragment_context
.nodes
.iter()
.fold(zero_padding, |accumulator, node| {
let mut padding =
model::padding_from_style(&node.style, Au(0), writing_mode);
if !node
.flags
.contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
{
padding.inline_start = Au(0)
}
if !node
.flags
.contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
{
padding.inline_end = Au(0)
}
accumulator + padding
})
},
};
self.border_padding = border + padding_from_style + padding_from_inline_fragment_context
}
pub fn relative_position(&self, containing_block_size: &LogicalSize<Au>) -> LogicalSize<Au> {
fn from_style(style: &ComputedValues, container_size: &LogicalSize<Au>) -> LogicalSize<Au> {
let offsets = style.logical_position();
let offset_i = if !offsets.inline_start.is_auto() {
MaybeAuto::from_inset(offsets.inline_start, container_size.inline)
.specified_or_zero()
} else {
-MaybeAuto::from_inset(offsets.inline_end, container_size.inline)
.specified_or_zero()
};
let offset_b = if offsets.block_start.is_auto() {
MaybeAuto::from_inset(offsets.block_start, container_size.block).specified_or_zero()
} else {
-MaybeAuto::from_inset(offsets.block_end, container_size.block).specified_or_zero()
};
LogicalSize::new(style.writing_mode, offset_i, offset_b)
}
let mut rel_pos = if self.style().get_box().position == Position::Relative {
from_style(self.style(), containing_block_size)
} else {
LogicalSize::zero(self.style.writing_mode)
};
if let Some(ref inline_fragment_context) = self.inline_context {
for node in &inline_fragment_context.nodes {
if node.style.get_box().position == Position::Relative {
rel_pos = rel_pos + from_style(&node.style, containing_block_size);
}
}
}
rel_pos
}
#[inline(always)]
pub fn clear(&self) -> Option<ClearType> {
let style = self.style();
match style.get_box().clear {
Clear::None => None,
Clear::Left => Some(ClearType::Left),
Clear::Right => Some(ClearType::Right),
Clear::Both => Some(ClearType::Both),
}
}
#[inline(always)]
pub fn style(&self) -> &ComputedValues {
&self.style
}
#[inline(always)]
pub fn selected_style(&self) -> &ComputedValues {
&self.selected_style
}
pub fn white_space_collapse(&self) -> WhiteSpaceCollapse {
self.style().get_inherited_text().white_space_collapse
}
pub fn text_wrap_mode(&self) -> TextWrapMode {
self.style().get_inherited_text().text_wrap_mode
}
pub fn color(&self) -> Color {
self.style().get_inherited_text().color
}
pub fn text_decoration_line(&self) -> TextDecorationLine {
self.style().get_text().text_decoration_line
}
pub fn inline_start_offset(&self) -> Au {
match self.specific {
SpecificFragmentInfo::TableWrapper => self.margin.inline_start,
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableRow => self.border_padding.inline_start,
SpecificFragmentInfo::TableColumn(_) => Au(0),
_ => self.margin.inline_start + self.border_padding.inline_start,
}
}
pub fn column_span(&self) -> u32 {
match self.specific {
SpecificFragmentInfo::TableColumn(col_fragment) => max(col_fragment.span, 1),
_ => panic!("non-table-column fragment inside table column?!"),
}
}
pub fn can_split(&self) -> bool {
self.is_scanned_text_fragment() && self.text_wrap_mode() == TextWrapMode::Wrap
}
pub fn is_unscanned_generated_content(&self) -> bool {
match self.specific {
SpecificFragmentInfo::GeneratedContent(ref content) => {
!matches!(**content, GeneratedContentInfo::Empty)
},
_ => false,
}
}
pub fn is_scanned_text_fragment(&self) -> bool {
matches!(self.specific, SpecificFragmentInfo::ScannedText(..))
}
pub fn suppress_line_break_before(&self) -> bool {
match self.specific {
SpecificFragmentInfo::ScannedText(ref st) => st
.flags
.contains(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE),
_ => false,
}
}
pub fn compute_intrinsic_inline_sizes(&mut self) -> IntrinsicISizesContribution {
let mut result = self.style_specified_intrinsic_inline_size();
match self.specific {
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableWrapper |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::MulticolColumn |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {},
SpecificFragmentInfo::InlineBlock(ref info) => {
let block_flow = info.flow_ref.as_block();
result.union_block(&block_flow.base.intrinsic_inline_sizes)
},
SpecificFragmentInfo::InlineAbsolute(ref info) => {
let block_flow = info.flow_ref.as_block();
result.union_block(&block_flow.base.intrinsic_inline_sizes)
},
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Svg(_) => {
let inline_size = self.style.content_inline_size().maybe_to_used_value(None);
let mut inline_size = inline_size.unwrap_or_else(|| {
let padding = self.style.logical_padding();
self.border_padding.inline_start = padding.inline_start.to_used_value(Au(0));
self.border_padding.inline_end = padding.inline_end.to_used_value(Au(0));
self.border_padding.block_start = padding.block_start.to_used_value(Au(0));
self.border_padding.block_end = padding.block_end.to_used_value(Au(0));
let border = self.border_width();
self.border_padding.inline_start += border.inline_start;
self.border_padding.inline_end += border.inline_end;
self.border_padding.block_start += border.block_start;
self.border_padding.block_end += border.block_end;
let (result_inline, _) = self.calculate_replaced_sizes(None, None);
result_inline
});
let size_constraint = self.size_constraint(None, Direction::Inline);
inline_size = size_constraint.clamp(inline_size);
result.union_block(&IntrinsicISizes {
minimum_inline_size: inline_size,
preferred_inline_size: inline_size,
});
},
SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
let text_fragment_info = t.text_info.as_ref().unwrap();
handle_text(text_fragment_info, self, &mut result)
},
SpecificFragmentInfo::ScannedText(ref text_fragment_info) => {
handle_text(text_fragment_info, self, &mut result)
},
SpecificFragmentInfo::TruncatedFragment(_) => {
return IntrinsicISizesContribution::new()
},
SpecificFragmentInfo::UnscannedText(..) => {
panic!("Unscanned text fragments should have been scanned by now!")
},
};
fn handle_text(
text_fragment_info: &ScannedTextFragmentInfo,
self_: &Fragment,
result: &mut IntrinsicISizesContribution,
) {
let range = &text_fragment_info.range;
let max_line_inline_size = text_fragment_info
.run
.metrics_for_range(range)
.advance_width;
let min_line_inline_size = if self_.text_wrap_mode() == TextWrapMode::Wrap {
text_fragment_info.run.min_width_for_range(range)
} else {
max_line_inline_size
};
result.union_block(&IntrinsicISizes {
minimum_inline_size: min_line_inline_size,
preferred_inline_size: max_line_inline_size,
})
}
let writing_mode = self.style.writing_mode;
if let Some(ref context) = self.inline_context {
for node in &context.nodes {
let mut border_width = node.style.logical_border_width();
let mut padding = model::padding_from_style(&node.style, Au(0), writing_mode);
let mut margin = model::specified_margin_from_style(&node.style, writing_mode);
if !node
.flags
.contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
{
border_width.inline_start = Au(0);
padding.inline_start = Au(0);
margin.inline_start = Au(0);
}
if !node
.flags
.contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
{
border_width.inline_end = Au(0);
padding.inline_end = Au(0);
margin.inline_end = Au(0);
}
result.surrounding_size = result.surrounding_size +
border_width.inline_start_end() +
padding.inline_start_end() +
margin.inline_start_end();
}
}
result
}
pub fn minimum_splittable_inline_size(&self) -> Au {
match self.specific {
SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
let text = t.text_info.as_ref().unwrap();
text.run.minimum_splittable_inline_size(&text.range)
},
SpecificFragmentInfo::ScannedText(ref text) => {
text.run.minimum_splittable_inline_size(&text.range)
},
_ => Au(0),
}
}
#[inline]
pub fn content_box(&self) -> LogicalRect<Au> {
self.border_box - self.border_padding
}
pub fn calculate_split_position(
&self,
max_inline_size: Au,
starts_line: bool,
) -> Option<SplitResult> {
let text_fragment_info = match self.specific {
SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info,
_ => return None,
};
let mut flags = SplitOptions::empty();
if starts_line {
flags.insert(SplitOptions::STARTS_LINE);
if self.style().get_inherited_text().overflow_wrap == OverflowWrap::BreakWord {
flags.insert(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES)
}
}
match self.style().get_inherited_text().word_break {
WordBreak::Normal | WordBreak::KeepAll => {
let natural_word_breaking_strategy = text_fragment_info
.run
.natural_word_slices_in_range(&text_fragment_info.range);
self.calculate_split_position_using_breaking_strategy(
natural_word_breaking_strategy,
max_inline_size,
flags,
)
},
WordBreak::BreakAll => {
let character_breaking_strategy = text_fragment_info
.run
.character_slices_in_range(&text_fragment_info.range);
flags.remove(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES);
self.calculate_split_position_using_breaking_strategy(
character_breaking_strategy,
max_inline_size,
flags,
)
},
}
}
pub fn is_on_glyph_run_boundary(&self) -> bool {
let text_fragment_info = match self.specific {
SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info,
_ => return true,
};
text_fragment_info
.run
.on_glyph_run_boundary(text_fragment_info.range.begin())
}
pub fn truncate_to_inline_size(self, max_inline_size: Au) -> Fragment {
if let SpecificFragmentInfo::TruncatedFragment(_) = self.specific {
panic!("Cannot truncate an already truncated fragment");
}
let info = self.calculate_truncate_to_inline_size(max_inline_size);
let (size, text_info) = match info {
Some(TruncationResult {
split: SplitInfo { inline_size, range },
text_run,
}) => {
let size = LogicalSize::new(
self.style.writing_mode,
inline_size,
self.border_box.size.block,
);
let (flags, insertion_point) = match self.specific {
SpecificFragmentInfo::ScannedText(ref info) => match info.insertion_point {
Some(index) if range.contains(index) => (info.flags, info.insertion_point),
Some(index)
if index == ByteIndex(text_run.text.chars().count() as isize - 1) &&
index == range.end() =>
{
(info.flags, info.insertion_point)
},
_ => (info.flags, None),
},
_ => (ScannedTextFlags::empty(), None),
};
let text_info =
ScannedTextFragmentInfo::new(text_run, range, size, insertion_point, flags);
(size, Some(text_info))
},
None => (LogicalSize::zero(self.style.writing_mode), None),
};
let mut result = self.transform(size, SpecificFragmentInfo::Generic);
result.specific =
SpecificFragmentInfo::TruncatedFragment(Box::new(TruncatedFragmentInfo {
text_info,
full: self,
}));
result
}
fn calculate_truncate_to_inline_size(&self, max_inline_size: Au) -> Option<TruncationResult> {
let text_fragment_info =
if let SpecificFragmentInfo::ScannedText(ref text_fragment_info) = self.specific {
text_fragment_info
} else {
return None;
};
let character_breaking_strategy = text_fragment_info
.run
.character_slices_in_range(&text_fragment_info.range);
let split_info = self.calculate_split_position_using_breaking_strategy(
character_breaking_strategy,
max_inline_size,
SplitOptions::empty(),
)?;
let split = split_info.inline_start?;
Some(TruncationResult {
split,
text_run: split_info.text_run.clone(),
})
}
fn calculate_split_position_using_breaking_strategy<'a, I>(
&self,
slice_iterator: I,
max_inline_size: Au,
flags: SplitOptions,
) -> Option<SplitResult>
where
I: Iterator<Item = TextRunSlice<'a>>,
{
let text_fragment_info = match self.specific {
SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info,
_ => return None,
};
let mut remaining_inline_size = max_inline_size - self.border_padding.inline_start_end();
let mut inline_start_range = Range::new(text_fragment_info.range.begin(), ByteIndex(0));
let mut inline_end_range = None;
let mut overflowing = false;
debug!(
"calculate_split_position_using_breaking_strategy: splitting text fragment \
(strlen={}, range={:?}, max_inline_size={:?})",
text_fragment_info.run.text.len(),
text_fragment_info.range,
max_inline_size
);
for slice in slice_iterator {
debug!(
"calculate_split_position_using_breaking_strategy: considering slice \
(offset={:?}, slice range={:?}, remaining_inline_size={:?})",
slice.offset, slice.range, remaining_inline_size
);
let metrics = text_fragment_info
.run
.metrics_for_slice(slice.glyphs, &slice.range);
let advance = metrics.advance_width;
if advance <= remaining_inline_size || slice.glyphs.is_whitespace() {
debug!("calculate_split_position_using_breaking_strategy: enlarging span");
remaining_inline_size -= advance;
inline_start_range.extend_by(slice.range.length());
continue;
}
let mut remaining_range = slice.text_run_range();
let split_is_empty = inline_start_range.is_empty() &&
(self.text_wrap_mode() == TextWrapMode::Wrap ||
!self.requires_line_break_afterward_if_wrapping_on_newlines());
if split_is_empty {
overflowing = true;
inline_start_range = slice.text_run_range();
remaining_range = Range::new(slice.text_run_range().end(), ByteIndex(0));
remaining_range.extend_to(text_fragment_info.range.end());
}
let slice_begin = remaining_range.begin();
if slice_begin < text_fragment_info.range.end() {
let mut inline_end = remaining_range;
inline_end.extend_to(text_fragment_info.range.end());
inline_end_range = Some(inline_end);
debug!(
"calculate_split_position: splitting remainder with inline-end range={:?}",
inline_end
);
}
if split_is_empty || overflowing {
if flags.contains(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES) {
let character_breaking_strategy = text_fragment_info
.run
.character_slices_in_range(&text_fragment_info.range);
let mut flags = flags;
flags.remove(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES);
return self.calculate_split_position_using_breaking_strategy(
character_breaking_strategy,
max_inline_size,
flags,
);
}
if !flags.contains(SplitOptions::STARTS_LINE) {
return None;
}
}
break;
}
let split_is_empty = inline_start_range.is_empty() &&
!self.requires_line_break_afterward_if_wrapping_on_newlines();
let inline_start = if !split_is_empty {
Some(SplitInfo::new(inline_start_range, text_fragment_info))
} else {
None
};
let inline_end = inline_end_range
.map(|inline_end_range| SplitInfo::new(inline_end_range, text_fragment_info));
Some(SplitResult {
inline_start,
inline_end,
text_run: text_fragment_info.run.clone(),
})
}
pub fn merge_with(&mut self, next_fragment: Fragment) {
match (&mut self.specific, &next_fragment.specific) {
(
&mut SpecificFragmentInfo::ScannedText(ref mut this_info),
SpecificFragmentInfo::ScannedText(other_info),
) => {
debug_assert!(Arc::ptr_eq(&this_info.run, &other_info.run));
this_info.range_end_including_stripped_whitespace =
other_info.range_end_including_stripped_whitespace;
if other_info.requires_line_break_afterward_if_wrapping_on_newlines() {
this_info.flags.insert(
ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES,
);
}
if other_info.insertion_point.is_some() {
this_info.insertion_point = other_info.insertion_point;
}
self.border_padding.inline_end = next_fragment.border_padding.inline_end;
self.margin.inline_end = next_fragment.margin.inline_end;
},
_ => panic!("Can only merge two scanned-text fragments!"),
}
self.reset_text_range_and_inline_size();
self.meld_with_next_inline_fragment(&next_fragment);
}
pub fn reset_text_range_and_inline_size(&mut self) {
if let SpecificFragmentInfo::ScannedText(ref mut info) = self.specific {
if info.run.extra_word_spacing != Au(0) {
Arc::make_mut(&mut info.run).extra_word_spacing = Au(0);
}
let range_end = info.range_end_including_stripped_whitespace;
if info.range.end() == range_end {
return;
}
info.range.extend_to(range_end);
info.content_size.inline = info.run.metrics_for_range(&info.range).advance_width;
self.border_box.size.inline =
info.content_size.inline + self.border_padding.inline_start_end();
}
}
pub fn assign_replaced_inline_size_if_necessary(
&mut self,
container_inline_size: Au,
container_block_size: Option<Au>,
) {
match self.specific {
SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_none() => return,
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableWrapper |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::MulticolColumn => return,
SpecificFragmentInfo::TableColumn(_) => {
panic!("Table column fragments do not have inline size")
},
SpecificFragmentInfo::UnscannedText(_) => {
panic!("Unscanned text fragments should have been scanned by now!")
},
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::InlineBlock(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Svg(_) => {},
};
match self.specific {
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => {
let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block();
block_flow.base.position.size.inline =
block_flow.base.intrinsic_inline_sizes.preferred_inline_size;
self.border_box.size.inline = Au(0);
},
SpecificFragmentInfo::InlineBlock(ref mut info) => {
let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block();
self.border_box.size.inline = max(
block_flow.base.intrinsic_inline_sizes.minimum_inline_size,
block_flow.base.intrinsic_inline_sizes.preferred_inline_size,
);
block_flow.base.block_container_inline_size = self.border_box.size.inline;
block_flow.base.block_container_writing_mode = self.style.writing_mode;
},
SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block();
self.border_box.size.inline = max(
block_flow.base.intrinsic_inline_sizes.minimum_inline_size,
block_flow.base.intrinsic_inline_sizes.preferred_inline_size,
);
block_flow.base.block_container_inline_size = self.border_box.size.inline;
block_flow.base.block_container_writing_mode = self.style.writing_mode;
},
SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
let info = t.text_info.as_ref().unwrap();
self.border_box.size.inline =
info.content_size.inline + self.border_padding.inline_start_end();
},
SpecificFragmentInfo::ScannedText(ref info) => {
self.border_box.size.inline =
info.content_size.inline + self.border_padding.inline_start_end();
},
_ if self.is_replaced() => {
let (inline_size, block_size) = self
.calculate_replaced_sizes(Some(container_inline_size), container_block_size);
self.border_box.size.inline = inline_size + self.border_padding.inline_start_end();
self.border_box.size.block = block_size + self.border_padding.block_start_end();
},
ref unhandled => {
panic!("this case should have been handled above: {:?}", unhandled)
},
}
}
pub fn assign_replaced_block_size_if_necessary(&mut self) {
match self.specific {
SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_none() => return,
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableWrapper |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::MulticolColumn => return,
SpecificFragmentInfo::TableColumn(_) => {
panic!("Table column fragments do not have block size")
},
SpecificFragmentInfo::UnscannedText(_) => {
panic!("Unscanned text fragments should have been scanned by now!")
},
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::InlineBlock(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Svg(_) => {},
}
match self.specific {
SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
let info = t.text_info.as_ref().unwrap();
self.border_box.size.block =
info.content_size.block + self.border_padding.block_start_end();
},
SpecificFragmentInfo::ScannedText(ref info) => {
self.border_box.size.block =
info.content_size.block + self.border_padding.block_start_end();
},
SpecificFragmentInfo::InlineBlock(ref mut info) => {
let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block();
self.border_box.size.block = block_flow.base.position.size.block +
block_flow.fragment.margin.block_start_end()
},
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => {
let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block();
self.border_box.size.block = block_flow.base.position.size.block;
},
SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block();
self.border_box.size.block = block_flow.base.position.size.block +
block_flow.fragment.margin.block_start_end()
},
_ if self.is_replaced() => {},
ref unhandled => panic!("should have been handled above: {:?}", unhandled),
}
}
pub fn is_replaced(&self) -> bool {
matches!(
self.specific,
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::Svg(_)
)
}
pub fn is_replaced_or_inline_block(&self) -> bool {
match self.specific {
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineBlock(_) => true,
_ => self.is_replaced(),
}
}
fn content_inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics {
let inline_metrics = match self.specific {
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::Svg(_) |
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::GeneratedContent(_) => {
let ascent = self.border_box.size.block + self.margin.block_end;
InlineMetrics {
space_above_baseline: ascent + self.margin.block_start,
space_below_baseline: Au(0),
ascent,
}
},
SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
let info = t.text_info.as_ref().unwrap();
inline_metrics_of_text(info, self, layout_context)
},
SpecificFragmentInfo::ScannedText(ref info) => {
inline_metrics_of_text(info, self, layout_context)
},
SpecificFragmentInfo::InlineBlock(ref info) => {
inline_metrics_of_block(&info.flow_ref, &self.style)
},
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => {
inline_metrics_of_block(&info.flow_ref, &self.style)
},
SpecificFragmentInfo::TruncatedFragment(..) |
SpecificFragmentInfo::InlineAbsolute(_) => InlineMetrics::new(Au(0), Au(0), Au(0)),
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableWrapper |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::MulticolColumn |
SpecificFragmentInfo::UnscannedText(_) => {
unreachable!("Shouldn't see fragments of this type here!")
},
};
return inline_metrics;
fn inline_metrics_of_text(
info: &ScannedTextFragmentInfo,
self_: &Fragment,
layout_context: &LayoutContext,
) -> InlineMetrics {
if info.insertion_point.is_none() && info.content_size.inline == Au(0) {
return InlineMetrics::new(Au(0), Au(0), Au(0));
}
let font_metrics = text::font_metrics_for_style(
&layout_context.font_context,
self_.style.clone_font(),
);
let line_height = text::line_height_from_style(&self_.style, &font_metrics);
InlineMetrics::from_font_metrics(&info.run.font_metrics, line_height)
}
fn inline_metrics_of_block(flow: &FlowRef, style: &ComputedValues) -> InlineMetrics {
let block_flow = flow.as_block();
let start_margin = block_flow.fragment.margin.block_start;
let end_margin = block_flow.fragment.margin.block_end;
let border_box_block_size = block_flow.fragment.border_box.size.block;
let ascent = match (
flow.baseline_offset_of_last_line_box_in_flow(),
style.get_box().overflow_y,
) {
(Some(baseline_offset), StyleOverflow::Visible) => baseline_offset,
_ => border_box_block_size + end_margin,
};
let space_below_baseline = border_box_block_size + end_margin - ascent;
let space_above_baseline = ascent + start_margin;
InlineMetrics::new(space_above_baseline, space_below_baseline, ascent)
}
}
fn vertical_alignment_offset(
&self,
layout_context: &LayoutContext,
content_inline_metrics: &InlineMetrics,
minimum_line_metrics: &LineMetrics,
actual_line_metrics: Option<&LineMetrics>,
) -> Au {
let mut offset = Au(0);
for style in self.inline_styles() {
match style.get_box().vertical_align {
VerticalAlign::Keyword(kw) => match kw {
VerticalAlignKeyword::Baseline => {},
VerticalAlignKeyword::Middle => {
let font_metrics = text::font_metrics_for_style(
&layout_context.font_context,
self.style.clone_font(),
);
offset += (content_inline_metrics.ascent -
content_inline_metrics.space_below_baseline -
font_metrics.x_height)
.scale_by(0.5)
},
VerticalAlignKeyword::Sub => {
offset += minimum_line_metrics
.space_needed()
.scale_by(FONT_SUBSCRIPT_OFFSET_RATIO)
},
VerticalAlignKeyword::Super => {
offset -= minimum_line_metrics
.space_needed()
.scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO)
},
VerticalAlignKeyword::TextTop => {
offset = self.content_inline_metrics(layout_context).ascent -
minimum_line_metrics.space_above_baseline
},
VerticalAlignKeyword::TextBottom => {
offset = minimum_line_metrics.space_below_baseline -
self.content_inline_metrics(layout_context)
.space_below_baseline
},
VerticalAlignKeyword::Top => {
if let Some(actual_line_metrics) = actual_line_metrics {
offset = content_inline_metrics.ascent -
actual_line_metrics.space_above_baseline
}
},
VerticalAlignKeyword::Bottom => {
if let Some(actual_line_metrics) = actual_line_metrics {
offset = actual_line_metrics.space_below_baseline -
content_inline_metrics.space_below_baseline
}
},
},
VerticalAlign::Length(ref lp) => {
offset -= lp.to_used_value(minimum_line_metrics.space_needed());
},
}
}
offset
}
pub fn aligned_inline_metrics(
&self,
layout_context: &LayoutContext,
minimum_line_metrics: &LineMetrics,
actual_line_metrics: Option<&LineMetrics>,
) -> InlineMetrics {
let content_inline_metrics = self.content_inline_metrics(layout_context);
let vertical_alignment_offset = self.vertical_alignment_offset(
layout_context,
&content_inline_metrics,
minimum_line_metrics,
actual_line_metrics,
);
let mut space_above_baseline = match actual_line_metrics {
None => content_inline_metrics.space_above_baseline,
Some(actual_line_metrics) => actual_line_metrics.space_above_baseline,
};
space_above_baseline -= vertical_alignment_offset;
let space_below_baseline =
content_inline_metrics.space_below_baseline + vertical_alignment_offset;
let ascent = content_inline_metrics.ascent - vertical_alignment_offset;
InlineMetrics::new(space_above_baseline, space_below_baseline, ascent)
}
pub fn is_hypothetical(&self) -> bool {
matches!(
self.specific,
SpecificFragmentInfo::InlineAbsoluteHypothetical(_)
)
}
pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool {
match (&self.specific, &other.specific) {
(
SpecificFragmentInfo::UnscannedText(first_unscanned_text),
&SpecificFragmentInfo::UnscannedText(_),
) => {
if self.style().get_font() != other.style().get_font() ||
self.text_decoration_line() != other.text_decoration_line() ||
self.white_space_collapse() != other.white_space_collapse() ||
self.text_wrap_mode() != other.text_wrap_mode() ||
self.color() != other.color()
{
return false;
}
if first_unscanned_text.text.ends_with('\n') {
return false;
}
if let Some(ref inline_context) = self.inline_context {
for inline_context_node in inline_context.nodes.iter() {
if !inline_context_node
.flags
.contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
{
continue;
}
if !inline_context_node
.style
.logical_margin()
.inline_end
.is_definitely_zero()
{
return false;
}
if !inline_context_node
.style
.logical_padding()
.inline_end
.is_definitely_zero()
{
return false;
}
if inline_context_node.style.logical_border_width().inline_end != Au(0) {
return false;
}
}
}
if let Some(ref inline_context) = other.inline_context {
for inline_context_node in inline_context.nodes.iter() {
if !inline_context_node
.flags
.contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
{
continue;
}
if !inline_context_node
.style
.logical_margin()
.inline_start
.is_definitely_zero()
{
return false;
}
if !inline_context_node
.style
.logical_padding()
.inline_start
.is_definitely_zero()
{
return false;
}
if inline_context_node
.style
.logical_border_width()
.inline_start !=
Au(0)
{
return false;
}
}
}
true
},
_ => false,
}
}
pub fn is_primary_fragment(&self) -> bool {
match self.specific {
SpecificFragmentInfo::InlineBlock(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::MulticolColumn |
SpecificFragmentInfo::TableWrapper => false,
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::Svg(_) |
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::UnscannedText(_) => true,
}
}
pub fn update_late_computed_replaced_inline_size_if_necessary(&mut self) {
if let SpecificFragmentInfo::InlineBlock(ref mut inline_block_info) = self.specific {
let block_flow = FlowRef::deref_mut(&mut inline_block_info.flow_ref).as_block();
self.border_box.size.inline = block_flow.fragment.margin_box_inline_size();
}
}
pub fn update_late_computed_inline_position_if_necessary(&mut self) {
if let SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) = self.specific {
let position = self.border_box.start.i;
FlowRef::deref_mut(&mut info.flow_ref)
.update_late_computed_inline_position_if_necessary(position)
}
}
pub fn update_late_computed_block_position_if_necessary(&mut self) {
if let SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) = self.specific {
let position = self.border_box.start.b;
FlowRef::deref_mut(&mut info.flow_ref)
.update_late_computed_block_position_if_necessary(position)
}
}
pub fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
self.style = (*new_style).clone()
}
pub fn stacking_relative_border_box(
&self,
stacking_relative_flow_origin: &Vector2D<Au>,
relative_containing_block_size: &LogicalSize<Au>,
relative_containing_block_mode: WritingMode,
coordinate_system: CoordinateSystem,
) -> Rect<Au> {
let container_size =
relative_containing_block_size.to_physical(relative_containing_block_mode);
let border_box = self
.border_box
.to_physical(self.style.writing_mode, container_size);
if coordinate_system == CoordinateSystem::Own && self.establishes_stacking_context() {
return Rect::new(Point2D::zero(), border_box.size);
}
let relative_position = self.relative_position(relative_containing_block_size);
border_box
.translate(
relative_position
.to_physical(self.style.writing_mode)
.to_vector(),
)
.translate(*stacking_relative_flow_origin)
}
pub fn stacking_relative_content_box(
&self,
stacking_relative_border_box: Rect<Au>,
) -> Rect<Au> {
let border_padding = self.border_padding.to_physical(self.style.writing_mode);
Rect::new(
Point2D::new(
stacking_relative_border_box.origin.x + border_padding.left,
stacking_relative_border_box.origin.y + border_padding.top,
),
Size2D::new(
stacking_relative_border_box.size.width - border_padding.horizontal(),
stacking_relative_border_box.size.height - border_padding.vertical(),
),
)
}
pub fn can_establish_reference_frame(&self) -> bool {
!self.style().get_box().transform.0.is_empty() ||
self.style().get_box().perspective != Perspective::None
}
pub fn has_filter_transform_or_perspective(&self) -> bool {
!self.style().get_box().transform.0.is_empty() ||
!self.style().get_effects().filter.0.is_empty() ||
self.style().get_box().perspective != Perspective::None
}
pub fn has_non_invertible_transform_or_zero_scale(&self) -> bool {
self.transform_matrix(&Rect::default())
.is_some_and(|matrix| !matrix.is_invertible() || matrix.m11 == 0. || matrix.m22 == 0.)
}
pub fn establishes_stacking_context(&self) -> bool {
match self.specific {
SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::UnscannedText(_) => return false,
_ => {},
}
if self.style().get_effects().opacity != 1.0 {
return true;
}
if self.style().get_effects().mix_blend_mode != MixBlendMode::Normal {
return true;
}
if self.has_filter_transform_or_perspective() {
return true;
}
if self.style().get_box().transform_style == TransformStyle::Preserve3d ||
self.style().overrides_transform_style()
{
return true;
}
if self.style().get_box().position == Position::Fixed ||
self.style().get_box().position == Position::Sticky
{
return true;
}
if self.style().get_box().position == Position::Static {
return false;
}
!self.style().get_position().z_index.is_auto()
}
pub fn effective_z_index(&self) -> i32 {
match self.style().get_box().position {
Position::Static => {},
_ => return self.style().get_position().z_index.integer_or(0),
}
if !self.style().get_box().transform.0.is_empty() {
return self.style().get_position().z_index.integer_or(0);
}
match self.style().get_box().display {
Display::Flex => self.style().get_position().z_index.integer_or(0),
_ => 0,
}
}
pub fn compute_overflow(
&self,
flow_size: &Size2D<Au>,
relative_containing_block_size: &LogicalSize<Au>,
) -> Overflow {
let mut border_box = self
.border_box
.to_physical(self.style.writing_mode, *flow_size);
let relative_position = self.relative_position(relative_containing_block_size);
border_box = border_box.translate(
relative_position
.to_physical(self.style.writing_mode)
.to_vector(),
);
let mut overflow = Overflow::from_rect(&border_box);
for box_shadow in &*self.style().get_effects().box_shadow.0 {
let offset = Vector2D::new(
Au::from(box_shadow.base.horizontal),
Au::from(box_shadow.base.vertical),
);
let inflation = Au::from(box_shadow.spread) +
Au::from(box_shadow.base.blur) * BLUR_INFLATION_FACTOR;
overflow.paint = overflow
.paint
.union(&border_box.translate(offset).inflate(inflation, inflation))
}
let outline_width = self.style.get_outline().outline_width;
if outline_width != Au(0) {
overflow.paint = overflow
.paint
.union(&border_box.inflate(outline_width, outline_width))
}
match self.specific {
SpecificFragmentInfo::InlineBlock(ref info) => {
let block_flow = info.flow_ref.as_block();
overflow.union(&block_flow.base().overflow);
},
SpecificFragmentInfo::InlineAbsolute(ref info) => {
let block_flow = info.flow_ref.as_block();
overflow.union(&block_flow.base().overflow);
},
_ => (),
}
overflow
}
pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
match self.specific {
SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
let text = t.text_info.as_ref().unwrap();
text.requires_line_break_afterward_if_wrapping_on_newlines()
},
SpecificFragmentInfo::ScannedText(ref text) => {
text.requires_line_break_afterward_if_wrapping_on_newlines()
},
_ => false,
}
}
pub fn strip_leading_whitespace_if_necessary(&mut self) -> WhitespaceStrippingResult {
if self.white_space_collapse() == WhiteSpaceCollapse::Preserve {
return WhitespaceStrippingResult::RetainFragment;
}
return match self.specific {
SpecificFragmentInfo::TruncatedFragment(ref mut t) if t.text_info.is_some() => {
let scanned_text_fragment_info = t.text_info.as_mut().unwrap();
scanned_text(scanned_text_fragment_info, &mut self.border_box)
},
SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => {
scanned_text(scanned_text_fragment_info, &mut self.border_box)
},
SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
let mut new_text_string = String::new();
let mut modified = false;
for (i, character) in unscanned_text_fragment_info.text.char_indices() {
if is_bidi_control(character) {
new_text_string.push(character);
continue;
}
if char_is_whitespace(character) {
modified = true;
continue;
}
if modified {
new_text_string.push_str(&unscanned_text_fragment_info.text[i..]);
}
break;
}
if modified {
unscanned_text_fragment_info.text = new_text_string.into_boxed_str();
}
WhitespaceStrippingResult::from_unscanned_text_fragment_info(
unscanned_text_fragment_info,
)
},
_ => WhitespaceStrippingResult::RetainFragment,
};
fn scanned_text(
scanned_text_fragment_info: &mut ScannedTextFragmentInfo,
border_box: &mut LogicalRect<Au>,
) -> WhitespaceStrippingResult {
let leading_whitespace_byte_count = scanned_text_fragment_info
.text()
.find(|c| !char_is_whitespace(c))
.unwrap_or(scanned_text_fragment_info.text().len());
let whitespace_len = ByteIndex(leading_whitespace_byte_count as isize);
let whitespace_range =
Range::new(scanned_text_fragment_info.range.begin(), whitespace_len);
let text_bounds = scanned_text_fragment_info
.run
.metrics_for_range(&whitespace_range)
.bounding_box;
border_box.size.inline -= text_bounds.size.width;
scanned_text_fragment_info.content_size.inline -= text_bounds.size.width;
scanned_text_fragment_info
.range
.adjust_by(whitespace_len, -whitespace_len);
WhitespaceStrippingResult::RetainFragment
}
}
pub fn strip_trailing_whitespace_if_necessary(&mut self) -> WhitespaceStrippingResult {
if self.white_space_collapse() == WhiteSpaceCollapse::Preserve {
return WhitespaceStrippingResult::RetainFragment;
}
return match self.specific {
SpecificFragmentInfo::TruncatedFragment(ref mut t) if t.text_info.is_some() => {
let scanned_text_fragment_info = t.text_info.as_mut().unwrap();
scanned_text(scanned_text_fragment_info, &mut self.border_box)
},
SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => {
scanned_text(scanned_text_fragment_info, &mut self.border_box)
},
SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
let mut trailing_bidi_control_characters_to_retain = Vec::new();
let (mut modified, mut last_character_index) = (true, 0);
for (i, character) in unscanned_text_fragment_info.text.char_indices().rev() {
if is_bidi_control(character) {
trailing_bidi_control_characters_to_retain.push(character);
continue;
}
if char_is_whitespace(character) {
modified = true;
continue;
}
last_character_index = i + character.len_utf8();
break;
}
if modified {
let mut text = unscanned_text_fragment_info.text.to_string();
text.truncate(last_character_index);
for character in trailing_bidi_control_characters_to_retain.iter().rev() {
text.push(*character);
}
unscanned_text_fragment_info.text = text.into_boxed_str();
}
WhitespaceStrippingResult::from_unscanned_text_fragment_info(
unscanned_text_fragment_info,
)
},
_ => WhitespaceStrippingResult::RetainFragment,
};
fn scanned_text(
scanned_text_fragment_info: &mut ScannedTextFragmentInfo,
border_box: &mut LogicalRect<Au>,
) -> WhitespaceStrippingResult {
let mut trailing_whitespace_start_byte = 0;
for (i, c) in scanned_text_fragment_info.text().char_indices().rev() {
if !char_is_whitespace(c) {
trailing_whitespace_start_byte = i + c.len_utf8();
break;
}
}
let whitespace_start = ByteIndex(trailing_whitespace_start_byte as isize);
let whitespace_len = scanned_text_fragment_info.range.length() - whitespace_start;
let mut whitespace_range = Range::new(whitespace_start, whitespace_len);
whitespace_range.shift_by(scanned_text_fragment_info.range.begin());
let text_bounds = scanned_text_fragment_info
.run
.metrics_for_range(&whitespace_range)
.bounding_box;
border_box.size.inline -= text_bounds.size.width;
scanned_text_fragment_info.content_size.inline -= text_bounds.size.width;
scanned_text_fragment_info.range.extend_by(-whitespace_len);
WhitespaceStrippingResult::RetainFragment
}
}
pub fn inline_styles(&self) -> InlineStyleIterator {
InlineStyleIterator::new(self)
}
pub fn margin_box_inline_size(&self) -> Au {
self.border_box.size.inline + self.margin.inline_start_end()
}
pub fn is_positioned(&self) -> bool {
if self.style.get_box().position != Position::Static {
return true;
}
if let Some(ref inline_context) = self.inline_context {
for node in inline_context.nodes.iter() {
if node.style.get_box().position != Position::Static {
return true;
}
}
}
false
}
pub fn is_absolutely_positioned(&self) -> bool {
self.style.get_box().position == Position::Absolute
}
pub fn is_inline_absolute(&self) -> bool {
matches!(self.specific, SpecificFragmentInfo::InlineAbsolute(..))
}
pub fn meld_with_next_inline_fragment(&mut self, next_fragment: &Fragment) {
if let Some(ref mut inline_context_of_this_fragment) = self.inline_context {
if let Some(ref inline_context_of_next_fragment) = next_fragment.inline_context {
for (
inline_context_node_from_this_fragment,
inline_context_node_from_next_fragment,
) in inline_context_of_this_fragment
.nodes
.iter_mut()
.rev()
.zip(inline_context_of_next_fragment.nodes.iter().rev())
{
if !inline_context_node_from_next_fragment
.flags
.contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
{
continue;
}
if inline_context_node_from_next_fragment.address !=
inline_context_node_from_this_fragment.address
{
continue;
}
inline_context_node_from_this_fragment
.flags
.insert(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT);
}
}
}
}
pub fn meld_with_prev_inline_fragment(&mut self, prev_fragment: &Fragment) {
if let Some(ref mut inline_context_of_this_fragment) = self.inline_context {
if let Some(ref inline_context_of_prev_fragment) = prev_fragment.inline_context {
for (
inline_context_node_from_prev_fragment,
inline_context_node_from_this_fragment,
) in inline_context_of_prev_fragment
.nodes
.iter()
.rev()
.zip(inline_context_of_this_fragment.nodes.iter_mut().rev())
{
if !inline_context_node_from_prev_fragment
.flags
.contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
{
continue;
}
if inline_context_node_from_prev_fragment.address !=
inline_context_node_from_this_fragment.address
{
continue;
}
inline_context_node_from_this_fragment
.flags
.insert(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT);
}
}
}
}
pub fn is_vertically_aligned_to_top_or_bottom(&self) -> bool {
fn is_top_or_bottom(v: &VerticalAlign) -> bool {
matches!(
*v,
VerticalAlign::Keyword(VerticalAlignKeyword::Top) |
VerticalAlign::Keyword(VerticalAlignKeyword::Bottom)
)
}
if is_top_or_bottom(&self.style.get_box().vertical_align) {
return true;
}
if let Some(ref inline_context) = self.inline_context {
for node in &inline_context.nodes {
if is_top_or_bottom(&node.style.get_box().vertical_align) {
return true;
}
}
}
false
}
pub fn is_text_or_replaced(&self) -> bool {
match self.specific {
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineBlock(_) |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::MulticolColumn |
SpecificFragmentInfo::Table |
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::TableRow |
SpecificFragmentInfo::TableWrapper => false,
SpecificFragmentInfo::Canvas(_) |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Media(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Svg(_) |
SpecificFragmentInfo::UnscannedText(_) => true,
}
}
pub fn transform_matrix(
&self,
stacking_relative_border_box: &Rect<Au>,
) -> Option<LayoutTransform> {
let list = &self.style.get_box().transform;
let border_box_as_length = Rect::new(
Point2D::new(
Length::new(stacking_relative_border_box.origin.x.to_f32_px()),
Length::new(stacking_relative_border_box.origin.y.to_f32_px()),
),
Size2D::new(
Length::new(stacking_relative_border_box.size.width.to_f32_px()),
Length::new(stacking_relative_border_box.size.height.to_f32_px()),
),
);
let transform = LayoutTransform::from_untyped(
&list
.to_transform_3d_matrix(Some(&border_box_as_length))
.ok()?
.0,
);
let transform_origin = &self.style.get_box().transform_origin;
let transform_origin_x = transform_origin
.horizontal
.to_used_value(stacking_relative_border_box.size.width)
.to_f32_px();
let transform_origin_y = transform_origin
.vertical
.to_used_value(stacking_relative_border_box.size.height)
.to_f32_px();
let transform_origin_z = transform_origin.depth.px();
let pre_transform = LayoutTransform::translation(
transform_origin_x,
transform_origin_y,
transform_origin_z,
);
let post_transform = LayoutTransform::translation(
-transform_origin_x,
-transform_origin_y,
-transform_origin_z,
);
Some(post_transform.then(&transform).then(&pre_transform))
}
pub fn perspective_matrix(
&self,
stacking_relative_border_box: &Rect<Au>,
) -> Option<LayoutTransform> {
match self.style().get_box().perspective {
Perspective::Length(length) => {
let perspective_origin = &self.style().get_box().perspective_origin;
let perspective_origin = Point2D::new(
perspective_origin
.horizontal
.to_used_value(stacking_relative_border_box.size.width),
perspective_origin
.vertical
.to_used_value(stacking_relative_border_box.size.height),
)
.to_layout();
let pre_transform =
LayoutTransform::translation(perspective_origin.x, perspective_origin.y, 0.0);
let post_transform =
LayoutTransform::translation(-perspective_origin.x, -perspective_origin.y, 0.0);
let perspective_matrix = LayoutTransform::from_untyped(
&transform::create_perspective_matrix(length.px()),
);
Some(
post_transform
.then(&perspective_matrix)
.then(&pre_transform),
)
},
Perspective::None => None,
}
}
}
impl fmt::Debug for Fragment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let border_padding_string = if !self.border_padding.is_zero() {
format!("\nborder_padding={:?}", self.border_padding)
} else {
"".to_owned()
};
let margin_string = if !self.margin.is_zero() {
format!("\nmargin={:?}", self.margin)
} else {
"".to_owned()
};
let damage_string = if self.restyle_damage != RestyleDamage::empty() {
format!("\ndamage={:?}", self.restyle_damage)
} else {
"".to_owned()
};
let flags_string = if !self.flags.is_empty() {
format!("\nflags={:?}", self.flags)
} else {
"".to_owned()
};
write!(
f,
"\n{}({}) [{:?}]\
\nborder_box={:?}\
{border_padding_string}\
{margin_string}\
{damage_string}\
{flags_string}",
self.specific.get_type(),
self.debug_id,
self.specific,
self.border_box,
)
}
}
bitflags! {
struct QuantitiesIncludedInIntrinsicInlineSizes: u8 {
const INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS = 0x01;
const INTRINSIC_INLINE_SIZE_INCLUDES_PADDING = 0x02;
const INTRINSIC_INLINE_SIZE_INCLUDES_BORDER = 0x04;
const INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED = 0x08;
}
}
bitflags! {
struct SplitOptions: u8 {
const STARTS_LINE = 0x01;
const RETRY_AT_CHARACTER_BOUNDARIES = 0x02;
}
}
pub trait FragmentBorderBoxIterator {
fn process(&mut self, fragment: &Fragment, level: i32, overflow: &Rect<Au>);
fn should_process(&mut self, fragment: &Fragment) -> bool;
}
#[derive(Clone, Debug, PartialEq)]
pub enum CoordinateSystem {
Parent,
Own,
}
pub struct InlineStyleIterator<'a> {
fragment: &'a Fragment,
inline_style_index: usize,
primary_style_yielded: bool,
}
impl<'a> Iterator for InlineStyleIterator<'a> {
type Item = &'a ComputedValues;
fn next(&mut self) -> Option<&'a ComputedValues> {
if !self.primary_style_yielded {
self.primary_style_yielded = true;
return Some(&*self.fragment.style);
}
let inline_context = self.fragment.inline_context.as_ref()?;
let inline_style_index = self.inline_style_index;
if inline_style_index == inline_context.nodes.len() {
return None;
}
self.inline_style_index += 1;
Some(&*inline_context.nodes[inline_style_index].style)
}
}
impl<'a> InlineStyleIterator<'a> {
fn new(fragment: &Fragment) -> InlineStyleIterator {
InlineStyleIterator {
fragment,
inline_style_index: 0,
primary_style_yielded: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum WhitespaceStrippingResult {
RetainFragment,
FragmentContainedOnlyBidiControlCharacters,
FragmentContainedOnlyWhitespace,
}
impl WhitespaceStrippingResult {
fn from_unscanned_text_fragment_info(
info: &UnscannedTextFragmentInfo,
) -> WhitespaceStrippingResult {
if info.text.is_empty() {
WhitespaceStrippingResult::FragmentContainedOnlyWhitespace
} else if info.text.chars().all(is_bidi_control) {
WhitespaceStrippingResult::FragmentContainedOnlyBidiControlCharacters
} else {
WhitespaceStrippingResult::RetainFragment
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Overflow {
pub scroll: Rect<Au>,
pub paint: Rect<Au>,
}
impl Overflow {
pub fn new() -> Overflow {
Overflow {
scroll: Rect::zero(),
paint: Rect::zero(),
}
}
pub fn from_rect(border_box: &Rect<Au>) -> Overflow {
Overflow {
scroll: *border_box,
paint: *border_box,
}
}
pub fn union(&mut self, other: &Overflow) {
self.scroll = self.scroll.union(&other.scroll);
self.paint = self.paint.union(&other.paint);
}
pub fn translate(&mut self, by: &Vector2D<Au>) {
self.scroll = self.scroll.translate(*by);
self.paint = self.paint.translate(*by);
}
}
impl Default for Overflow {
fn default() -> Self {
Self::new()
}
}
bitflags! {
#[derive(Clone, Debug)]
pub struct FragmentFlags: u8 {
const IS_INLINE_FLEX_ITEM = 0b0000_0001;
const IS_BLOCK_FLEX_ITEM = 0b0000_0010;
const IS_ELLIPSIS = 0b0000_0100;
const IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT = 0b0000_1000;
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpeculatedInlineContentEdgeOffsets {
pub start: Au,
pub end: Au,
}
#[cfg(not(debug_assertions))]
#[derive(Clone)]
struct DebugId;
#[cfg(debug_assertions)]
#[derive(Clone)]
struct DebugId(u16);
#[cfg(not(debug_assertions))]
impl DebugId {
pub fn new() -> DebugId {
DebugId
}
}
#[cfg(debug_assertions)]
impl DebugId {
pub fn new() -> DebugId {
DebugId(layout_debug::generate_unique_debug_id())
}
}
#[cfg(not(debug_assertions))]
impl fmt::Display for DebugId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:p}", &self)
}
}
#[cfg(debug_assertions)]
impl fmt::Display for DebugId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(not(debug_assertions))]
impl Serialize for DebugId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&format!("{:p}", &self))
}
}
#[cfg(debug_assertions)]
impl Serialize for DebugId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u16(self.0)
}
}