Add new methods to Fragment for size calculation of replaced element

These new methods calculate both the used width and height of an
replaced element and the same time. The `has_intrinsic_ratio()` method
also exposes information about whether a fragment has intrinsic aspect
ratio.
This commit is contained in:
Pu Xingyu 2016-12-11 01:37:48 +08:00
parent 39780e894b
commit d3ab919745
2 changed files with 193 additions and 2 deletions

View file

@ -24,6 +24,7 @@ use ipc_channel::ipc::IpcSender;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
use layout_debug; use layout_debug;
use model::{self, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, SizeConstraint}; use model::{self, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, SizeConstraint};
use model::style_length;
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache_thread::{ImageOrMetadataAvailable, UsePlaceholder}; use net_traits::image_cache_thread::{ImageOrMetadataAvailable, UsePlaceholder};
@ -34,7 +35,7 @@ use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayou
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use servo_url::ServoUrl; use servo_url::ServoUrl;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cmp::{max, min}; use std::cmp::{Ordering, max, min};
use std::collections::LinkedList; use std::collections::LinkedList;
use std::fmt; use std::fmt;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -59,6 +60,10 @@ use text::TextRunScanner;
static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34; static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34;
// https://drafts.csswg.org/css-images/#default-object-size
static DEFAULT_REPLACED_WIDTH: i32 = 300;
static DEFAULT_REPLACED_HEIGHT: i32 = 150;
/// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position /// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position
/// themselves. In general, fragments do not have a simple correspondence with CSS fragments in the /// themselves. In general, fragments do not have a simple correspondence with CSS fragments in the
/// specification: /// specification:
@ -1202,6 +1207,177 @@ impl Fragment {
} }
} }
/// intrinsic width of this replaced element.
#[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::Canvas(ref info) => info.dom_width,
SpecificFragmentInfo::Svg(ref info) => info.dom_width,
// Note: Currently for replaced element with no intrinsic size,
// this function simply returns the default object size. As long as
// these elements do not have intrinsic aspect ratio this should be
// sufficient, but we may need to investigate if this is enough for
// use cases like SVG.
SpecificFragmentInfo::Iframe(_) => Au::from_px(DEFAULT_REPLACED_WIDTH),
_ => panic!("Trying to get intrinsic width on non-replaced element!")
}
}
/// intrinsic width of this 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::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!")
}
}
/// Whether this replace element has intrinsic aspect ratio.
pub fn has_intrinsic_ratio(&self) -> bool {
match self.specific {
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::Canvas(_) |
// TODO(stshine): According to the SVG spec, whether a SVG element has intrinsic
// aspect ratio is determined by the `preserveAspectRatio` attribute. Since for
// now SVG is far from implemented, we simply choose the default behavior that
// the intrinsic aspect ratio is preserved.
// https://svgwg.org/svg2-draft/coords.html#PreserveAspectRatioAttribute
SpecificFragmentInfo::Svg(_) =>
self.intrinsic_width() != Au(0) && self.intrinsic_height() != Au(0),
_ => false
}
}
/// CSS 2.1 § 10.3.2 & 10.6.2 Calculate the used width and height of a replaced element.
/// When a parameter is `None` it means the specified size in certain direction
/// is unconstrained. The inline containing size can also be `None` since this
/// method is also used for calculating intrinsic inline size contribution.
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())
};
// Make sure the size we used here is for content box since they may be
// transferred by the intrinsic aspect ratio.
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);
// https://drafts.csswg.org/css-images-3/#default-sizing
match (inline_size, block_size) {
// If the specified size is a definite width and height, the concrete
// object size is given that width and height.
(MaybeAuto::Specified(inline_size), MaybeAuto::Specified(block_size)) =>
(inline_constraint.clamp(inline_size), block_constraint.clamp(block_size)),
// If the specified size is only a width or height (but not both)
// then the concrete object size is given that specified width or
// height. The other dimension is calculated as follows:
//
// If the object has an intrinsic aspect ratio, the missing dimension
// of the concrete object size is calculated using the intrinsic
// aspect ratio and the present dimension.
//
// Otherwise, if the missing dimension is present in the objects intrinsic
// dimensions, the missing dimension is taken from the objects intrinsic
// dimensions. Otherwise it is taken from the default object size.
(MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => {
let inline_size = inline_constraint.clamp(inline_size);
let block_size = if self.has_intrinsic_ratio() {
// Note: We can not precompute the ratio and store it as a float, because
// doing so may result one pixel difference in calculation for certain
// images, thus make some tests fail.
inline_size * intrinsic_block_size.0 / intrinsic_inline_size.0
} 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() {
block_size * intrinsic_inline_size.0 / intrinsic_block_size.0
} else {
intrinsic_inline_size
};
(inline_constraint.clamp(inline_size), block_size)
}
// https://drafts.csswg.org/css2/visudet.html#min-max-widths
(MaybeAuto::Auto, MaybeAuto::Auto) => {
if self.has_intrinsic_ratio() {
// This approch follows the spirit of cover and contain constraint.
// https://drafts.csswg.org/css-images-3/#cover-contain
// First, create two rectangles that keep aspect ratio while may be clamped
// by the contraints;
let first_isize = inline_constraint.clamp(intrinsic_inline_size);
let first_bsize = first_isize * intrinsic_block_size.0 / intrinsic_inline_size.0;
let second_bsize = block_constraint.clamp(intrinsic_block_size);
let second_isize = second_bsize * intrinsic_inline_size.0 / intrinsic_block_size.0;
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),
// When only one rectangle is clamped, use it;
(Ordering::Equal, _) =>
(second_isize, second_bsize),
(_, Ordering::Equal) =>
(first_isize, first_bsize),
// When both rectangles grow (smaller than min sizes),
// Choose the larger one;
(Ordering::Greater, Ordering::Greater) =>
if first_isize > second_isize {
(first_isize, first_bsize)
} else {
(second_isize, second_bsize)
},
// When both rectangles shrink (larger than max sizes),
// Choose the smaller one;
(Ordering::Less, Ordering::Less) =>
if first_isize > second_isize {
(second_isize, second_bsize)
} else {
(first_isize, first_bsize)
},
// It does not matter which we choose here, because both sizes
// will be clamped to constraint;
(Ordering::Less, Ordering::Greater) | (Ordering::Greater, Ordering::Less) =>
(first_isize, first_bsize)
};
// Clamp the result and we are done :-)
(inline_constraint.clamp(inline_size), block_constraint.clamp(block_size))
} else {
(inline_constraint.clamp(intrinsic_inline_size),
block_constraint.clamp(intrinsic_block_size))
}
}
}
}
/// Return a size constraint that can be used the clamp size in given direction. /// Return a size constraint that can be used the clamp size in given direction.
/// To take `box-sizing: border-box` into account, the `border_padding` field /// To take `box-sizing: border-box` into account, the `border_padding` field
@ -1275,7 +1451,7 @@ impl Fragment {
} }
/// Returns the border width in given direction if this fragment has property /// Returns the border width in given direction if this fragment has property
/// 'box-sizing: border-box'. The `border_padding` field should have been initialized. /// 'box-sizing: border-box'. The `border_padding` field must have been initialized.
pub fn box_sizing_boundary(&self, direction: Direction) -> Au { pub fn box_sizing_boundary(&self, direction: Direction) -> Au {
match (self.style().get_position().box_sizing, direction) { match (self.style().get_position().box_sizing, direction) {
(box_sizing::T::border_box, Direction::Inline) => { (box_sizing::T::border_box, Direction::Inline) => {

View file

@ -436,6 +436,21 @@ impl MaybeAuto {
} }
} }
/// Receive an optional container size and return used value for width or height.
///
/// `style_length`: content size as given in the CSS.
pub fn style_length(style_length: LengthOrPercentageOrAuto,
container_size: Option<Au>) -> MaybeAuto {
match container_size {
Some(length) => MaybeAuto::from_style(style_length, length),
None => if let LengthOrPercentageOrAuto::Length(length) = style_length {
MaybeAuto::Specified(length)
} else {
MaybeAuto::Auto
}
}
}
pub fn specified_or_none(length: LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> { pub fn specified_or_none(length: LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> {
match length { match length {
LengthOrPercentageOrNone::None => None, LengthOrPercentageOrNone::None => None,