Skip to content

Commit

Permalink
Add new methods to Fragment for size calculation of replaced element
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stshine committed Dec 12, 2016
1 parent 39780e8 commit d3ab919
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 2 deletions.
180 changes: 178 additions & 2 deletions components/layout/fragment.rs
Expand Up @@ -24,6 +24,7 @@ use ipc_channel::ipc::IpcSender;
#[cfg(debug_assertions)]
use layout_debug;
use model::{self, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, SizeConstraint};
use model::style_length;
use msg::constellation_msg::PipelineId;
use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache_thread::{ImageOrMetadataAvailable, UsePlaceholder};
Expand All @@ -34,7 +35,7 @@ use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayou
use serde::{Serialize, Serializer};
use servo_url::ServoUrl;
use std::borrow::ToOwned;
use std::cmp::{max, min};
use std::cmp::{Ordering, max, min};
use std::collections::LinkedList;
use std::fmt;
use std::sync::{Arc, Mutex};
Expand All @@ -59,6 +60,10 @@ use text::TextRunScanner;
static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
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
/// themselves. In general, fragments do not have a simple correspondence with CSS fragments in the
/// specification:
Expand Down Expand Up @@ -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 object’s intrinsic
// dimensions, the missing dimension is taken from the object’s 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.
/// To take `box-sizing: border-box` into account, the `border_padding` field
Expand Down Expand Up @@ -1275,7 +1451,7 @@ impl Fragment {
}

/// 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 {
match (self.style().get_position().box_sizing, direction) {
(box_sizing::T::border_box, Direction::Inline) => {
Expand Down
15 changes: 15 additions & 0 deletions components/layout/model.rs
Expand Up @@ -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> {
match length {
LengthOrPercentageOrNone::None => None,
Expand Down

0 comments on commit d3ab919

Please sign in to comment.