/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![deny(unsafe_code)]
use css::node_style::StyledNode;
use context::LayoutContext;
use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding};
use floats::{FloatKind, Floats, PlacementInfo};
use flow::{self, BaseFlow, FlowClass, Flow, ForceNonfloatedFlag, IS_ABSOLUTELY_POSITIONED};
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW, RESOLVE_GENERATED_CONTENT};
use layout_debug;
use model::IntrinsicISizesContribution;
use text;
use collections::VecDeque;
use geom::{Point2D, Rect};
use gfx::font::FontMetrics;
use gfx::font_context::FontContext;
use gfx::text::glyph::CharIndex;
use gfx::text::text_run::TextRun;
use std::cmp::max;
use std::fmt;
use std::mem;
use std::num::ToPrimitive;
use std::ops::{Add, Sub, Mul, Div, Rem, Neg, Shl, Shr, Not, BitOr, BitAnd, BitXor};
use std::sync::Arc;
use std::u16;
use style::computed_values::{display, overflow_x, text_align, text_justify, text_overflow};
use style::computed_values::{vertical_align, white_space};
use style::properties::ComputedValues;
use util::geometry::{Au, MAX_AU, ZERO_RECT};
use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
use util::range::{Range, RangeIndex};
use util;
// From gfxFontConstants.h in Firefox
static FONT_SUBSCRIPT_OFFSET_RATIO: f64 = 0.20;
static FONT_SUPERSCRIPT_OFFSET_RATIO: f64 = 0.34;
/// `Line`s are represented as offsets into the child list, rather than
/// as an object that "owns" fragments. Choosing a different set of line
/// breaks requires a new list of offsets, and possibly some splitting and
/// merging of TextFragments.
///
/// A similar list will keep track of the mapping between CSS fragments and
/// the corresponding fragments in the inline flow.
///
/// After line breaks are determined, render fragments in the inline flow may
/// overlap visually. For example, in the case of nested inline CSS fragments,
/// outer inlines must be at least as large as the inner inlines, for
/// purposes of drawing noninherited things like backgrounds, borders,
/// outlines.
///
/// N.B. roc has an alternative design where the list instead consists of
/// things like "start outer fragment, text, start inner fragment, text, end inner
/// fragment, text, end outer fragment, text". This seems a little complicated to
/// serve as the starting point, but the current design doesn't make it
/// hard to try out that alternative.
///
/// Line fragments also contain some metadata used during line breaking. The
/// green zone is the area that the line can expand to before it collides
/// with a float or a horizontal wall of the containing block. The block-start
/// inline-start corner of the green zone is the same as that of the line, but
/// the green zone can be taller and wider than the line itself.
#[derive(RustcEncodable, Debug, Copy)]
pub struct Line {
/// A range of line indices that describe line breaks.
///
/// For example, consider the following HTML and rendered element with
/// linebreaks:
///
/// ~~~html
/// I like truffles,
yes I do.
/// ~~~
///
/// ~~~text
/// +------------+
/// | I like |
/// | truffles, |
/// | +----+ |
/// | | | |
/// | +----+ yes |
/// | I do. |
/// +------------+
/// ~~~
///
/// The ranges that describe these lines would be:
///
/// | [0, 2) | [2, 3) | [3, 4) | [4, 5) |
/// |----------|-------------|-------------|----------|
/// | 'I like' | 'truffles,' | '
yes' | 'I do.' |
pub range: Range,
/// The bounds are the exact position and extents of the line with respect
/// to the parent box.
///
/// For example, for the HTML below...
///
/// ~~~html
/// I like truffles, ![]()
/// ~~~
///
/// ...the bounds would be:
///
/// ~~~text
/// +-----------------------------------------------------------+
/// | ^ |
/// | | |
/// | origin.y |
/// | | |
/// | v |
/// |< - origin.x ->+ - - - - - - - - +---------+---- |
/// | | | | ^ |
/// | | |
| size.block |
/// | I like truffles, | | v |
/// | + - - - - - - - - +---------+---- |
/// | | | |
/// | |<------ size.inline ------>| |
/// | |
/// | |
/// +-----------------------------------------------------------+
/// ~~~
pub bounds: LogicalRect,
/// The green zone is the greatest extent from which a line can extend to
/// before it collides with a float.
///
/// ~~~text
/// +-----------------------+
/// |::::::::::::::::: |
/// |:::::::::::::::::FFFFFF|
/// |============:::::FFFFFF|
/// |:::::::::::::::::FFFFFF|
/// |:::::::::::::::::FFFFFF|
/// |::::::::::::::::: |
/// | FFFFFFFFF |
/// | FFFFFFFFF |
/// | FFFFFFFFF |
/// | |
/// +-----------------------+
///
/// === line
/// ::: green zone
/// FFF float
/// ~~~
pub green_zone: LogicalSize,
/// The inline metrics for this line.
pub inline_metrics: InlineMetrics,
}
int_range_index! {
#[derive(RustcEncodable)]
#[doc = "The index of a fragment in a flattened vector of DOM elements."]
struct FragmentIndex(isize)
}
bitflags! {
flags InlineReflowFlags: u8 {
#[doc="The `white-space: nowrap` property from CSS 2.1 § 16.6 is in effect."]
const NO_WRAP_INLINE_REFLOW_FLAG = 0x01,
#[doc="The `white-space: pre` property from CSS 2.1 § 16.6 is in effect."]
const WRAP_ON_NEWLINE_INLINE_REFLOW_FLAG = 0x02
}
}
/// Arranges fragments into lines, splitting them up as necessary.
struct LineBreaker {
/// The floats we need to flow around.
floats: Floats,
/// The resulting fragment list for the flow, consisting of possibly-broken fragments.
new_fragments: Vec,
/// The next fragment or fragments that we need to work on.
work_list: VecDeque,
/// The line we're currently working on.
pending_line: Line,
/// The lines we've already committed.
lines: Vec,
/// The current position in the block direction.
cur_b: Au,
/// The computed value of the indentation for the first line (`text-indent`, CSS 2.1 § 16.1).
first_line_indentation: Au,
/// The minimum block-size above the baseline for each line, as specified by the line height
/// and font style.
minimum_block_size_above_baseline: Au,
/// The minimum depth below the baseline for each line, as specified by the line height and
/// font style.
minimum_depth_below_baseline: Au,
}
impl LineBreaker {
/// Creates a new `LineBreaker` with a set of floats and the indentation of the first line.
fn new(float_context: Floats,
first_line_indentation: Au,
minimum_block_size_above_baseline: Au,
minimum_depth_below_baseline: Au)
-> LineBreaker {
LineBreaker {
new_fragments: Vec::new(),
work_list: VecDeque::new(),
pending_line: Line {
range: Range::empty(),
bounds: LogicalRect::zero(float_context.writing_mode),
green_zone: LogicalSize::zero(float_context.writing_mode),
inline_metrics: InlineMetrics::new(minimum_block_size_above_baseline,
minimum_depth_below_baseline,
minimum_block_size_above_baseline),
},
floats: float_context,
lines: Vec::new(),
cur_b: Au(0),
first_line_indentation: first_line_indentation,
minimum_block_size_above_baseline: minimum_block_size_above_baseline,
minimum_depth_below_baseline: minimum_depth_below_baseline,
}
}
/// Resets the `LineBreaker` to the initial state it had after a call to `new`.
fn reset_scanner(&mut self) {
self.lines = Vec::new();
self.new_fragments = Vec::new();
self.cur_b = Au(0);
self.reset_line();
}
/// Reinitializes the pending line to blank data.
fn reset_line(&mut self) {
self.pending_line.range.reset(FragmentIndex(0), FragmentIndex(0));
self.pending_line.bounds = LogicalRect::new(self.floats.writing_mode,
Au(0),
self.cur_b,
Au(0),
Au(0));
self.pending_line.green_zone = LogicalSize::zero(self.floats.writing_mode);
self.pending_line.inline_metrics =
InlineMetrics::new(self.minimum_block_size_above_baseline,
self.minimum_depth_below_baseline,
self.minimum_block_size_above_baseline)
}
/// Reflows fragments for the given inline flow.
fn scan_for_lines(&mut self, flow: &mut InlineFlow, layout_context: &LayoutContext) {
self.reset_scanner();
// Create our fragment iterator.
debug!("LineBreaker: scanning for lines, {} fragments", flow.fragments.len());
let mut old_fragments = mem::replace(&mut flow.fragments, InlineFragments::new());
let old_fragment_iter = old_fragments.fragments.into_iter();
// TODO(pcwalton): This would likely be better as a list of dirty line indices. That way we
// could resynchronize if we discover during reflow that all subsequent fragments must have
// the same position as they had in the previous reflow. I don't know how common this case
// really is in practice, but it's probably worth handling.
self.lines = Vec::new();
// Do the reflow.
self.reflow_fragments(old_fragment_iter, flow, layout_context);
// Place the fragments back into the flow.
old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]);
flow.fragments = old_fragments;
flow.lines = mem::replace(&mut self.lines, Vec::new());
}
/// Reflows the given fragments, which have been plucked out of the inline flow.
fn reflow_fragments<'a,I>(&mut self,
mut old_fragment_iter: I,
flow: &'a InlineFlow,
layout_context: &LayoutContext)
where I: Iterator- {
loop {
// Acquire the next fragment to lay out from the work list or fragment list, as
// appropriate.
let fragment = match self.next_unbroken_fragment(&mut old_fragment_iter) {
None => break,
Some(fragment) => fragment,
};
// Set up our reflow flags.
let flags = match fragment.style().get_inheritedtext().white_space {
white_space::T::normal => InlineReflowFlags::empty(),
white_space::T::nowrap => NO_WRAP_INLINE_REFLOW_FLAG,
white_space::T::pre => {
WRAP_ON_NEWLINE_INLINE_REFLOW_FLAG | NO_WRAP_INLINE_REFLOW_FLAG
}
};
// Try to append the fragment.
self.reflow_fragment(fragment, flow, layout_context, flags);
}
if !self.pending_line_is_empty() {
debug!("LineBreaker: partially full line {} at end of scanning; committing it",
self.lines.len());
self.flush_current_line()
}
}
/// Acquires a new fragment to lay out from the work list or fragment list as appropriate.
/// If the fragment was at the end of an old line, undoes the line break for that fragment.
/// Note that you probably don't want to call this method directly in order to be incremental-
/// reflow-safe; try `next_unbroken_fragment` instead.
fn next_fragment(&mut self, old_fragment_iter: &mut I) -> Option
where I: Iterator
- {
let mut fragment;
if self.work_list.is_empty() {
match old_fragment_iter.next() {
None => return None,
Some(this_fragment) => fragment = this_fragment,
}
} else {
return self.work_list.pop_front()
}
Some(fragment)
}
/// Acquires a new fragment to lay out from the work list or fragment list, merging it with any
/// subsequent fragments as appropriate. In effect, what this method does is to return the next
/// fragment to lay out, undoing line break operations that any previous reflows may have
/// performed. You probably want to be using this method instead of `next_fragment`.
fn next_unbroken_fragment(&mut self, old_fragment_iter: &mut I) -> Option
where I: Iterator
- {
let mut result = match self.next_fragment(old_fragment_iter) {
None => return None,
Some(fragment) => fragment,
};
loop {
let candidate = match self.next_fragment(old_fragment_iter) {
None => return Some(result),
Some(fragment) => fragment,
};
let need_to_merge = match (&mut result.specific, &candidate.specific) {
(&mut SpecificFragmentInfo::ScannedText(ref mut result_info),
&SpecificFragmentInfo::ScannedText(ref candidate_info)) => {
util::arc_ptr_eq(&result_info.run, &candidate_info.run) &&
inline_contexts_are_equal(&result.inline_context,
&candidate.inline_context)
}
_ => false,
};
if need_to_merge {
result.merge_with(candidate);
continue
}
self.work_list.push_front(candidate);
return Some(result)
}
}
/// Commits a line to the list.
fn flush_current_line(&mut self) {
debug!("LineBreaker: flushing line {}: {:?}", self.lines.len(), self.pending_line);
self.strip_trailing_whitespace_from_pending_line_if_necessary();
self.lines.push(self.pending_line);
self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block;
self.reset_line();
}
/// Removes trailing whitespace from the pending line if necessary. This is done right before
/// flushing it.
fn strip_trailing_whitespace_from_pending_line_if_necessary(&mut self) {
if self.pending_line.range.is_empty() {
return
}
let last_fragment_index = self.pending_line.range.end() - FragmentIndex(1);
let mut fragment = &mut self.new_fragments[last_fragment_index.get() as usize];
if let SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) =
fragment.specific {
let scanned_text_fragment_info = &mut **scanned_text_fragment_info;
let mut range = &mut scanned_text_fragment_info.range;
strip_trailing_whitespace_if_necessary(&**scanned_text_fragment_info.run, range);
let old_fragment_inline_size = fragment.border_box.size.inline;
scanned_text_fragment_info.content_size.inline =
scanned_text_fragment_info.run.metrics_for_range(range).advance_width;
fragment.border_box.size.inline = scanned_text_fragment_info.content_size.inline +
fragment.border_padding.inline_start_end();
self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline -
(old_fragment_inline_size - fragment.border_box.size.inline)
}
}
// FIXME(eatkinson): this assumes that the tallest fragment in the line determines the line
// block-size. This might not be the case with some weird text fonts.
fn new_inline_metrics_for_line(&self, new_fragment: &Fragment, layout_context: &LayoutContext)
-> InlineMetrics {
self.pending_line.inline_metrics.max(&new_fragment.inline_metrics(layout_context))
}
fn new_block_size_for_line(&self, new_fragment: &Fragment, layout_context: &LayoutContext)
-> Au {
Au::max(self.pending_line.bounds.size.block,
self.new_inline_metrics_for_line(new_fragment, layout_context).block_size())
}
/// Computes the position of a line that has only the provided fragment. Returns the bounding
/// rect of the line's green zone (whose origin coincides with the line's origin) and the
/// actual inline-size of the first fragment after splitting.
fn initial_line_placement(&self,
flow: &InlineFlow,
first_fragment: &Fragment,
ceiling: Au)
-> (LogicalRect, Au) {
debug!("LineBreaker: trying to place first fragment of line {}; fragment size: {:?}, \
splittable: {}",
self.lines.len(),
first_fragment.border_box.size,
first_fragment.can_split());
// Initially, pretend a splittable fragment has zero inline-size. We will move it later if
// it has nonzero inline-size and that causes problems.
let placement_inline_size = if first_fragment.can_split() {
Au(0)
} else {
first_fragment.border_box.size.inline + self.indentation_for_pending_fragment()
};
// Try to place the fragment between floats.
let line_bounds = self.floats.place_between_floats(&PlacementInfo {
size: LogicalSize::new(self.floats.writing_mode,
placement_inline_size,
first_fragment.border_box.size.block),
ceiling: ceiling,
max_inline_size: flow.base.position.size.inline,
kind: FloatKind::Left,
});
// Simple case: if the fragment fits, then we can stop here.
if line_bounds.size.inline > first_fragment.border_box.size.inline {
debug!("LineBreaker: fragment fits on line {}", self.lines.len());
return (line_bounds, first_fragment.border_box.size.inline);
}
// If not, but we can't split the fragment, then we'll place the line here and it will
// overflow.
if !first_fragment.can_split() {
debug!("LineBreaker: line doesn't fit, but is unsplittable");
}
(line_bounds, first_fragment.border_box.size.inline)
}
/// Performs float collision avoidance. This is called when adding a fragment is going to
/// increase the block-size, and because of that we will collide with some floats.
///
/// We have two options here:
/// 1) Move the entire line so that it doesn't collide any more.
/// 2) Break the line and put the new fragment on the next line.
///
/// The problem with option 1 is that we might move the line and then wind up breaking anyway,
/// which violates the standard. But option 2 is going to look weird sometimes.
///
/// So we'll try to move the line whenever we can, but break if we have to.
///
/// Returns false if and only if we should break the line.
fn avoid_floats(&mut self,
flow: &InlineFlow,
in_fragment: Fragment,
new_block_size: Au)
-> bool {
debug!("LineBreaker: entering float collision avoider!");
// First predict where the next line is going to be.
let (next_line, first_fragment_inline_size) =
self.initial_line_placement(flow,
&in_fragment,
self.pending_line.bounds.start.b);
let next_green_zone = next_line.size;
let new_inline_size = self.pending_line.bounds.size.inline + first_fragment_inline_size;
// Now, see if everything can fit at the new location.
if next_green_zone.inline >= new_inline_size && next_green_zone.block >= new_block_size {
debug!("LineBreaker: case=adding fragment collides vertically with floats: moving \
line");
self.pending_line.bounds.start = next_line.start;
self.pending_line.green_zone = next_green_zone;
debug_assert!(!self.pending_line_is_empty(), "Non-terminating line breaking");
self.work_list.push_front(in_fragment);
return true
}
debug!("LineBreaker: case=adding fragment collides vertically with floats: breaking line");
self.work_list.push_front(in_fragment);
false
}
/// Tries to append the given fragment to the line, splitting it if necessary. Commits the
/// current line if needed.
fn reflow_fragment(&mut self,
mut fragment: Fragment,
flow: &InlineFlow,
layout_context: &LayoutContext,
flags: InlineReflowFlags) {
// Determine initial placement for the fragment if we need to.
if self.pending_line_is_empty() {
fragment.strip_leading_whitespace_if_necessary();
let (line_bounds, _) = self.initial_line_placement(flow, &fragment, self.cur_b);
self.pending_line.bounds.start = line_bounds.start;
self.pending_line.green_zone = line_bounds.size;
}
debug!("LineBreaker: trying to append to line {} (fragment size: {:?}, green zone: {:?}): \
{:?}",
self.lines.len(),
fragment.border_box.size,
self.pending_line.green_zone,
fragment);
// NB: At this point, if `green_zone.inline < self.pending_line.bounds.size.inline` or
// `green_zone.block < self.pending_line.bounds.size.block`, then we committed a line that
// overlaps with floats.
let green_zone = self.pending_line.green_zone;
let new_block_size = self.new_block_size_for_line(&fragment, layout_context);
if new_block_size > green_zone.block {
// Uh-oh. Float collision imminent. Enter the float collision avoider!
if !self.avoid_floats(flow, fragment, new_block_size) {
self.flush_current_line();
}
return
}
// If we must flush the line after finishing this fragment due to `white-space: pre`,
// detect that.
let line_flush_mode =
if flags.contains(WRAP_ON_NEWLINE_INLINE_REFLOW_FLAG) &&
fragment.requires_line_break_afterward_if_wrapping_on_newlines() {
LineFlushMode::Flush
} else {
LineFlushMode::No
};
// If we're not going to overflow the green zone vertically, we might still do so
// horizontally. We'll try to place the whole fragment on this line and break somewhere if
// it doesn't fit.
let indentation = self.indentation_for_pending_fragment();
let new_inline_size = self.pending_line.bounds.size.inline +
fragment.border_box.size.inline + indentation;
if new_inline_size <= green_zone.inline {
debug!("LineBreaker: fragment fits without splitting");
self.push_fragment_to_line(layout_context, fragment, line_flush_mode);
return
}
// If we can't split the fragment or aren't allowed to because of the wrapping mode, then
// just overflow.
if (!fragment.can_split() && self.pending_line_is_empty()) ||
(flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) &&
!flags.contains(WRAP_ON_NEWLINE_INLINE_REFLOW_FLAG)) {
debug!("LineBreaker: fragment can't split and line {} is empty, so overflowing",
self.lines.len());
self.push_fragment_to_line(layout_context, fragment, LineFlushMode::No);
return
}
// Split it up!
let available_inline_size = if !flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) {
green_zone.inline - self.pending_line.bounds.size.inline - indentation
} else {
MAX_AU
};
let inline_start_fragment;
let inline_end_fragment;
let split_result = match fragment.calculate_split_position(available_inline_size,
self.pending_line_is_empty()) {
None => {
debug!("LineBreaker: fragment was unsplittable; deferring to next line");
self.work_list.push_front(fragment);
self.flush_current_line();
return
}
Some(split_result) => split_result,
};
inline_start_fragment = split_result.inline_start.as_ref().map(|x| {
fragment.transform_with_split_info(x, split_result.text_run.clone())
});
inline_end_fragment = split_result.inline_end.as_ref().map(|x| {
fragment.transform_with_split_info(x, split_result.text_run.clone())
});
// Push the first fragment onto the line we're working on and start off the next line with
// the second fragment. If there's no second fragment, the next line will start off empty.
match (inline_start_fragment, inline_end_fragment) {
(Some(inline_start_fragment), Some(inline_end_fragment)) => {
self.push_fragment_to_line(layout_context,
inline_start_fragment,
LineFlushMode::Flush);
self.work_list.push_front(inline_end_fragment)
},
(Some(fragment), None) => {
self.push_fragment_to_line(layout_context, fragment, line_flush_mode);
}
(None, Some(fragment)) => {
// Yes, this can happen!
self.flush_current_line();
self.work_list.push_front(fragment)
}
(None, None) => {}
}
}
/// Pushes a fragment to the current line unconditionally, possibly truncating it and placing
/// an ellipsis based on the value of `text-overflow`. If `flush_line` is `Flush`, then flushes
/// the line afterward;
fn push_fragment_to_line(&mut self,
layout_context: &LayoutContext,
fragment: Fragment,
line_flush_mode: LineFlushMode) {
let indentation = self.indentation_for_pending_fragment();
if self.pending_line_is_empty() {
assert!(self.new_fragments.len() <= (u16::MAX as usize));
self.pending_line.range.reset(FragmentIndex(self.new_fragments.len() as isize),
FragmentIndex(0));
}
// Determine if an ellipsis will be necessary to account for `text-overflow`.
let mut need_ellipsis = false;
let available_inline_size = self.pending_line.green_zone.inline -
self.pending_line.bounds.size.inline - indentation;
match (fragment.style().get_inheritedtext().text_overflow,
fragment.style().get_box().overflow_x) {
(text_overflow::T::clip, _) | (_, overflow_x::T::visible) => {}
(text_overflow::T::ellipsis, _) => {
need_ellipsis = fragment.border_box.size.inline > available_inline_size;
}
}
if !need_ellipsis {
self.push_fragment_to_line_ignoring_text_overflow(fragment, layout_context);
} else {
let ellipsis = fragment.transform_into_ellipsis(layout_context);
if let Some(truncation_info) =
fragment.truncate_to_inline_size(available_inline_size -
ellipsis.border_box.size.inline) {
let fragment = fragment.transform_with_split_info(&truncation_info.split,
truncation_info.text_run);
self.push_fragment_to_line_ignoring_text_overflow(fragment, layout_context);
}
self.push_fragment_to_line_ignoring_text_overflow(ellipsis, layout_context);
}
if line_flush_mode == LineFlushMode::Flush {
self.flush_current_line()
}
}
/// Pushes a fragment to the current line unconditionally, without placing an ellipsis in the
/// case of `text-overflow: ellipsis`.
fn push_fragment_to_line_ignoring_text_overflow(&mut self,
fragment: Fragment,
layout_context: &LayoutContext) {
let indentation = self.indentation_for_pending_fragment();
self.pending_line.range.extend_by(FragmentIndex(1));
self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
fragment.border_box.size.inline +
indentation;
self.pending_line.inline_metrics =
self.new_inline_metrics_for_line(&fragment, layout_context);
self.pending_line.bounds.size.block =
self.new_block_size_for_line(&fragment, layout_context);
self.new_fragments.push(fragment);
}
/// Returns the indentation that needs to be applied before the fragment we're reflowing.
fn indentation_for_pending_fragment(&self) -> Au {
if self.pending_line_is_empty() && self.lines.is_empty() {
self.first_line_indentation
} else {
Au(0)
}
}
/// Returns true if the pending line is empty and false otherwise.
fn pending_line_is_empty(&self) -> bool {
self.pending_line.range.length() == FragmentIndex(0)
}
}
/// Represents a list of inline fragments, including element ranges.
#[derive(RustcEncodable, Clone)]
pub struct InlineFragments {
/// The fragments themselves.
pub fragments: Vec,
}
impl fmt::Debug for InlineFragments {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.fragments)
}
}
impl InlineFragments {
/// Creates an empty set of inline fragments.
pub fn new() -> InlineFragments {
InlineFragments {
fragments: vec![],
}
}
/// Returns the number of inline fragments.
pub fn len(&self) -> usize {
self.fragments.len()
}
/// Returns true if this list contains no fragments and false if it contains at least one
/// fragment.
pub fn is_empty(&self) -> bool {
self.fragments.is_empty()
}
/// Pushes a new inline fragment.
pub fn push(&mut self, fragment: &mut Fragment) {
self.fragments.push(fragment.clone());
}
/// Merges another set of inline fragments with this one.
pub fn push_all(&mut self, mut other: InlineFragments) {
self.fragments.append(&mut other.fragments);
}
/// A convenience function to return the fragment at a given index.
pub fn get<'a>(&'a self, index: usize) -> &'a Fragment {
&self.fragments[index]
}
/// A convenience function to return a mutable reference to the fragment at a given index.
pub fn get_mut<'a>(&'a mut self, index: usize) -> &'a mut Fragment {
&mut self.fragments[index]
}
}
/// Flows for inline layout.
#[derive(RustcEncodable)]
pub struct InlineFlow {
/// Data common to all flows.
pub base: BaseFlow,
/// A vector of all inline fragments. Several fragments may correspond to one node/element.
pub fragments: InlineFragments,
/// A vector of ranges into fragments that represents line positions. These ranges are disjoint
/// and are the result of inline layout. This also includes some metadata used for positioning
/// lines.
pub lines: Vec,
/// The minimum block-size above the baseline for each line, as specified by the line height
/// and font style.
pub minimum_block_size_above_baseline: Au,
/// The minimum depth below the baseline for each line, as specified by the line height and
/// font style.
pub minimum_depth_below_baseline: Au,
/// The amount of indentation to use on the first line. This is determined by our block parent
/// (because percentages are relative to the containing block, and we aren't in a position to
/// compute things relative to our parent's containing block).
pub first_line_indentation: Au,
}
impl InlineFlow {
pub fn from_fragments(fragments: InlineFragments, writing_mode: WritingMode) -> InlineFlow {
let mut flow = InlineFlow {
base: BaseFlow::new(None, writing_mode, ForceNonfloatedFlag::ForceNonfloated),
fragments: fragments,
lines: Vec::new(),
minimum_block_size_above_baseline: Au(0),
minimum_depth_below_baseline: Au(0),
first_line_indentation: Au(0),
};
for fragment in flow.fragments.fragments.iter() {
if fragment.is_generated_content() {
flow.base.restyle_damage.insert(RESOLVE_GENERATED_CONTENT)
}
}
flow
}
/// Returns the distance from the baseline for the logical block-start inline-start corner of
/// this fragment, taking into account the value of the CSS `vertical-align` property.
/// Negative values mean "toward the logical block-start" and positive values mean "toward the
/// logical block-end".
///
/// The extra boolean is set if and only if `largest_block_size_for_top_fragments` and/or
/// `largest_block_size_for_bottom_fragments` were updated. That is, if the box has a `top` or
/// `bottom` value for `vertical-align`, true is returned.
fn distance_from_baseline(fragment: &Fragment,
ascent: Au,
parent_text_block_start: Au,
parent_text_block_end: Au,
block_size_above_baseline: &mut Au,
depth_below_baseline: &mut Au,
largest_block_size_for_top_fragments: &mut Au,
largest_block_size_for_bottom_fragments: &mut Au,
layout_context: &LayoutContext)
-> (Au, bool) {
let (mut offset_from_baseline, mut largest_size_updated) = (Au(0), false);
for style in fragment.inline_styles() {
// Ignore `vertical-align` values for table cells.
let box_style = style.get_box();
if box_style.display != display::T::inline &&
box_style.display != display::T::block {
continue
}
match box_style.vertical_align {
vertical_align::T::baseline => {}
vertical_align::T::middle => {
// TODO: x-height value should be used from font info.
// TODO: Doing nothing here passes our current reftests but doesn't work in
// all situations. Add vertical align reftests and fix this.
},
vertical_align::T::sub => {
let sub_offset = (parent_text_block_start + parent_text_block_end)
.scale_by(FONT_SUBSCRIPT_OFFSET_RATIO);
offset_from_baseline = offset_from_baseline + sub_offset
},
vertical_align::T::super_ => {
let super_offset = (parent_text_block_start + parent_text_block_end)
.scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO);
offset_from_baseline = offset_from_baseline - super_offset
},
vertical_align::T::text_top => {
let fragment_block_size = *block_size_above_baseline +
*depth_below_baseline;
let prev_depth_below_baseline = *depth_below_baseline;
*block_size_above_baseline = parent_text_block_start;
*depth_below_baseline = fragment_block_size - *block_size_above_baseline;
offset_from_baseline = offset_from_baseline + *depth_below_baseline -
prev_depth_below_baseline
},
vertical_align::T::text_bottom => {
let fragment_block_size = *block_size_above_baseline +
*depth_below_baseline;
let prev_depth_below_baseline = *depth_below_baseline;
*depth_below_baseline = parent_text_block_end;
*block_size_above_baseline = fragment_block_size - *depth_below_baseline;
offset_from_baseline = offset_from_baseline + *depth_below_baseline -
prev_depth_below_baseline
},
vertical_align::T::top => {
if !largest_size_updated {
largest_size_updated = true;
*largest_block_size_for_top_fragments =
max(*largest_block_size_for_top_fragments,
*block_size_above_baseline + *depth_below_baseline);
offset_from_baseline = offset_from_baseline +
*block_size_above_baseline
}
},
vertical_align::T::bottom => {
if !largest_size_updated {
largest_size_updated = true;
*largest_block_size_for_bottom_fragments =
max(*largest_block_size_for_bottom_fragments,
*block_size_above_baseline + *depth_below_baseline);
offset_from_baseline = offset_from_baseline - *depth_below_baseline
}
},
vertical_align::T::Length(length) => {
offset_from_baseline = offset_from_baseline - length
}
vertical_align::T::Percentage(p) => {
let line_height = fragment.calculate_line_height(layout_context);
let percent_offset = line_height.scale_by(p);
offset_from_baseline = offset_from_baseline - percent_offset
}
}
}
(offset_from_baseline - ascent, largest_size_updated)
}
/// Sets fragment positions in the inline direction based on alignment for one line. This
/// performs text justification if mandated by the style.
fn set_inline_fragment_positions(fragments: &mut InlineFragments,
line: &Line,
line_align: text_align::T,
indentation: Au,
is_last_line: bool) {
// Figure out how much inline-size we have.
let slack_inline_size = max(Au(0), line.green_zone.inline - line.bounds.size.inline);
// Compute the value we're going to use for `text-justify`.
let text_justify = if fragments.fragments.is_empty() {
return
} else {
fragments.fragments[0].style().get_inheritedtext().text_justify
};
// Set the fragment inline positions based on that alignment, and justify the text if
// necessary.
let mut inline_start_position_for_fragment = line.bounds.start.i + indentation;
match line_align {
text_align::T::justify if !is_last_line && text_justify != text_justify::T::none => {
InlineFlow::justify_inline_fragments(fragments, line, slack_inline_size)
}
text_align::T::left | text_align::T::justify => {}
text_align::T::center => {
inline_start_position_for_fragment = inline_start_position_for_fragment +
slack_inline_size.scale_by(0.5)
}
text_align::T::right => {
inline_start_position_for_fragment = inline_start_position_for_fragment +
slack_inline_size
}
}
for fragment_index in line.range.begin()..line.range.end() {
let fragment = fragments.get_mut(fragment_index.to_usize());
let size = fragment.border_box.size;
fragment.border_box = LogicalRect::new(fragment.style.writing_mode,
inline_start_position_for_fragment,
fragment.border_box.start.b,
size.inline,
size.block);
fragment.update_late_computed_inline_position_if_necessary();
inline_start_position_for_fragment = inline_start_position_for_fragment + size.inline;
}
}
/// Justifies the given set of inline fragments, distributing the `slack_inline_size` among all
/// of them according to the value of `text-justify`.
fn justify_inline_fragments(fragments: &mut InlineFragments,
line: &Line,
slack_inline_size: Au) {
// Fast path.
if slack_inline_size == Au(0) {
return
}
// First, calculate the number of expansion opportunities (spaces, normally).
let mut expansion_opportunities = 0i32;
for fragment_index in line.range.each_index() {
let fragment = fragments.get(fragment_index.to_usize());
let scanned_text_fragment_info =
if let SpecificFragmentInfo::ScannedText(ref info) = fragment.specific {
info
} else {
continue
};
for slice in scanned_text_fragment_info.run.character_slices_in_range(
&scanned_text_fragment_info.range) {
expansion_opportunities += slice.glyphs.space_count_in_range(&slice.range) as i32
}
}
// Then distribute all the space across the expansion opportunities.
let space_per_expansion_opportunity = slack_inline_size.to_subpx() /
(expansion_opportunities as f64);
for fragment_index in line.range.each_index() {
let fragment = fragments.get_mut(fragment_index.to_usize());
let mut scanned_text_fragment_info =
if let SpecificFragmentInfo::ScannedText(ref mut info) = fragment.specific {
info
} else {
continue
};
let fragment_range = scanned_text_fragment_info.range;
// FIXME(pcwalton): This is an awful lot of uniqueness making. I don't see any easy way
// to get rid of it without regressing the performance of the non-justified case,
// though.
let run = scanned_text_fragment_info.run.make_unique();
{
let glyph_runs = run.glyphs.make_unique();
for mut glyph_run in glyph_runs.iter_mut() {
let mut range = glyph_run.range.intersect(&fragment_range);
if range.is_empty() {
continue
}
range.shift_by(-glyph_run.range.begin());
let glyph_store = glyph_run.glyph_store.make_unique();
glyph_store.distribute_extra_space_in_range(&range,
space_per_expansion_opportunity);
}
}
// Recompute the fragment's border box size.
let new_inline_size = run.advance_for_range(&fragment_range);
let new_size = LogicalSize::new(fragment.style.writing_mode,
new_inline_size,
fragment.border_box.size.block);
fragment.border_box = LogicalRect::from_point_size(fragment.style.writing_mode,
fragment.border_box.start,
new_size);
}
}
/// Sets final fragment positions in the block direction for one line. Assumes that the
/// fragment positions were initially set to the distance from the baseline first.
fn set_block_fragment_positions(fragments: &mut InlineFragments,
line: &Line,
line_distance_from_flow_block_start: Au,
baseline_distance_from_block_start: Au,
largest_depth_below_baseline: Au) {
for fragment_index in line.range.begin()..line.range.end() {
// If any of the inline styles say `top` or `bottom`, adjust the vertical align
// appropriately.
//
// FIXME(#5624, pcwalton): This passes our current reftests but isn't the right thing
// to do.
let fragment = fragments.get_mut(fragment_index.to_usize());
let mut vertical_align = vertical_align::T::baseline;
for style in fragment.inline_styles() {
match (style.get_box().display, style.get_box().vertical_align) {
(display::T::inline, vertical_align::T::top) |
(display::T::block, vertical_align::T::top) => {
vertical_align = vertical_align::T::top;
break
}
(display::T::inline, vertical_align::T::bottom) |
(display::T::block, vertical_align::T::bottom) => {
vertical_align = vertical_align::T::bottom;
break
}
_ => {}
}
}
match vertical_align {
vertical_align::T::top => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start
}
vertical_align::T::bottom => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start +
baseline_distance_from_block_start +
largest_depth_below_baseline;
}
_ => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start + baseline_distance_from_block_start
}
}
fragment.update_late_computed_block_position_if_necessary();
}
}
/// Computes the minimum ascent and descent for each line. This is done during flow
/// construction.
///
/// `style` is the style of the block.
pub fn compute_minimum_ascent_and_descent(&self,
font_context: &mut FontContext,
style: &ComputedValues)
-> (Au, Au) {
// As a special case, if this flow contains only hypothetical fragments, then the entire
// flow is hypothetical and takes up no space. See CSS 2.1 § 10.3.7.
if self.fragments.fragments.iter().all(|fragment| fragment.is_hypothetical()) {
return (Au(0), Au(0))
}
let font_style = style.get_font_arc();
let font_metrics = text::font_metrics_for_style(font_context, font_style);
let line_height = text::line_height_from_style(style, &font_metrics);
let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height);
let mut block_size_above_baseline = inline_metrics.block_size_above_baseline;
let mut depth_below_baseline = inline_metrics.depth_below_baseline;
// According to CSS 2.1 § 10.8, `line-height` of any inline element specifies the minimal
// height of line boxes within the element.
for frag in self.fragments.fragments.iter() {
match frag.inline_context {
Some(ref inline_context) => {
for style in inline_context.styles.iter() {
let font_style = style.get_font_arc();
let font_metrics = text::font_metrics_for_style(font_context, font_style);
let line_height = text::line_height_from_style(&**style, &font_metrics);
let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics,
line_height);
block_size_above_baseline = max(block_size_above_baseline,
inline_metrics.block_size_above_baseline);
depth_below_baseline = max(depth_below_baseline,
inline_metrics.depth_below_baseline);
}
}
None => {}
}
}
(block_size_above_baseline, depth_below_baseline)
}
fn update_restyle_damage(&mut self) {
let mut damage = self.base.restyle_damage;
for frag in self.fragments.fragments.iter() {
damage.insert(frag.restyle_damage());
}
self.base.restyle_damage = damage;
}
}
impl Flow for InlineFlow {
fn class(&self) -> FlowClass {
FlowClass::Inline
}
fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow {
self
}
fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow {
self
}
fn bubble_inline_sizes(&mut self) {
self.update_restyle_damage();
let _scope = layout_debug_scope!("inline::bubble_inline_sizes {:x}", self.base.debug_id());
let writing_mode = self.base.writing_mode;
for kid in self.base.child_iter() {
flow::mut_base(kid).floats = Floats::new(writing_mode);
}
let mut intrinsic_sizes_for_flow = IntrinsicISizesContribution::new();
let mut intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new();
let mut intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
for fragment in self.fragments.fragments.iter_mut() {
let intrinsic_sizes_for_fragment = fragment.compute_intrinsic_inline_sizes().finish();
match fragment.style.get_inheritedtext().white_space {
white_space::T::nowrap => {
intrinsic_sizes_for_nonbroken_run.union_nonbreaking_inline(
&intrinsic_sizes_for_fragment)
}
white_space::T::pre => {
intrinsic_sizes_for_nonbroken_run.union_nonbreaking_inline(
&intrinsic_sizes_for_fragment);
// Flush the intrinsic sizes we've been gathering up in order to handle the
// line break, if necessary.
if fragment.requires_line_break_afterward_if_wrapping_on_newlines() {
intrinsic_sizes_for_inline_run.union_inline(
&intrinsic_sizes_for_nonbroken_run.finish());
intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
intrinsic_sizes_for_flow.union_block(
&intrinsic_sizes_for_inline_run.finish());
intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new();
}
}
white_space::T::normal => {
// Flush the intrinsic sizes we were gathering up for the nonbroken run, if
// necessary.
intrinsic_sizes_for_inline_run.union_inline(
&intrinsic_sizes_for_nonbroken_run.finish());
intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
intrinsic_sizes_for_nonbroken_run.union_inline(&intrinsic_sizes_for_fragment)
}
}
}
// Flush any remaining nonbroken-run and inline-run intrinsic sizes.
intrinsic_sizes_for_inline_run.union_inline(&intrinsic_sizes_for_nonbroken_run.finish());
intrinsic_sizes_for_flow.union_block(&intrinsic_sizes_for_inline_run.finish());
// Finish up the computation.
self.base.intrinsic_inline_sizes = intrinsic_sizes_for_flow.finish()
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
/// When called on this context, the context has had its inline-size set by the parent context.
fn assign_inline_sizes(&mut self, _: &LayoutContext) {
let _scope = layout_debug_scope!("inline::assign_inline_sizes {:x}", self.base.debug_id());
// Initialize content fragment inline-sizes if they haven't been initialized already.
//
// TODO: Combine this with `LineBreaker`'s walk in the fragment list, or put this into
// `Fragment`.
debug!("InlineFlow::assign_inline_sizes: floats in: {:?}", self.base.floats);
let inline_size = self.base.block_container_inline_size;
let container_mode = self.base.block_container_writing_mode;
self.base.position.size.inline = inline_size;
{
let this = &mut *self;
for fragment in this.fragments.fragments.iter_mut() {
let border_collapse = fragment.style.get_inheritedtable().border_collapse;
fragment.compute_border_and_padding(inline_size, border_collapse);
fragment.compute_block_direction_margins(inline_size);
fragment.compute_inline_direction_margins(inline_size);
fragment.assign_replaced_inline_size_if_necessary(inline_size);
}
}
// If there are any inline-block kids, propagate explicit block and inline
// sizes down to them.
let block_container_explicit_block_size = self.base.block_container_explicit_block_size;
for kid in self.base.child_iter() {
let kid_base = flow::mut_base(kid);
kid_base.block_container_inline_size = inline_size;
kid_base.block_container_writing_mode = container_mode;
kid_base.block_container_explicit_block_size = block_container_explicit_block_size;
}
}
/// Calculate and set the block-size of this flow. See CSS 2.1 § 10.6.1.
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("inline::assign_block_size {:x}", self.base.debug_id());
// Divide the fragments into lines.
//
// TODO(pcwalton, #226): Get the CSS `line-height` property from the style of the
// containing block to determine the minimum line block size.
//
// TODO(pcwalton, #226): Get the CSS `line-height` property from each non-replaced inline
// element to determine its block-size for computing the line's own block-size.
//
// TODO(pcwalton): Cache the line scanner?
debug!("assign_block_size_inline: floats in: {:?}", self.base.floats);
// Assign the block-size for the inline fragments.
let containing_block_block_size =
self.base.block_container_explicit_block_size.unwrap_or(Au(0));
for fragment in self.fragments.fragments.iter_mut() {
fragment.assign_replaced_block_size_if_necessary(
containing_block_block_size);
}
// Reset our state, so that we handle incremental reflow correctly.
//
// TODO(pcwalton): Do something smarter, like Gecko and WebKit?
self.lines.clear();
// Determine how much indentation the first line wants.
let mut indentation = if self.fragments.is_empty() {
Au(0)
} else {
self.first_line_indentation
};
// Perform line breaking.
let mut scanner = LineBreaker::new(self.base.floats.clone(),
indentation,
self.minimum_block_size_above_baseline,
self.minimum_depth_below_baseline);
scanner.scan_for_lines(self, layout_context);
// Now, go through each line and lay out the fragments inside.
let mut line_distance_from_flow_block_start = Au(0);
let line_count = self.lines.len();
for line_index in 0..line_count {
let line = &mut self.lines[line_index];
// Lay out fragments in the inline direction, and justify them if necessary.
InlineFlow::set_inline_fragment_positions(&mut self.fragments,
line,
self.base.flags.text_align(),
indentation,
line_index + 1 == line_count);
// Set the block-start position of the current line.
// `line_height_offset` is updated at the end of the previous loop.
line.bounds.start.b = line_distance_from_flow_block_start;
// Calculate the distance from the baseline to the block-start and block-end of the
// line.
let mut largest_block_size_above_baseline = self.minimum_block_size_above_baseline;
let mut largest_depth_below_baseline = self.minimum_depth_below_baseline;
// Calculate the largest block-size among fragments with 'top' and 'bottom' values
// respectively.
let (mut largest_block_size_for_top_fragments,
mut largest_block_size_for_bottom_fragments) = (Au(0), Au(0));
for fragment_index in line.range.begin()..line.range.end() {
let fragment = &mut self.fragments.fragments[fragment_index.to_usize()];
let InlineMetrics {
mut block_size_above_baseline,
mut depth_below_baseline,
ascent
} = fragment.inline_metrics(layout_context);
// To calculate text-top and text-bottom value when `vertical-align` is involved,
// we should find the top and bottom of the content area of the parent fragment.
// "Content area" is defined in CSS 2.1 § 10.6.1.
//
// TODO: We should extract em-box info from the font size of the parent and
// calculate the distances from the baseline to the block-start and the block-end
// of the parent's content area.
// We should calculate the distance from baseline to the top of parent's content
// area. But for now we assume it's the font size.
//
// CSS 2.1 does not state which font to use. This version of the code uses
// the parent's font.
// Calculate the final block-size above the baseline for this fragment.
//
// The no-update flag decides whether `largest_block_size_for_top_fragments` and
// `largest_block_size_for_bottom_fragments` are to be updated or not. This will be
// set if and only if the fragment has `vertical-align` set to `top` or `bottom`.
let (distance_from_baseline, no_update_flag) =
InlineFlow::distance_from_baseline(
fragment,
ascent,
self.minimum_block_size_above_baseline,
self.minimum_depth_below_baseline,
&mut block_size_above_baseline,
&mut depth_below_baseline,
&mut largest_block_size_for_top_fragments,
&mut largest_block_size_for_bottom_fragments,
layout_context);
// Unless the current fragment has `vertical-align` set to `top` or `bottom`,
// `largest_block_size_above_baseline` and `largest_depth_below_baseline` are
// updated.
if !no_update_flag {
largest_block_size_above_baseline = max(block_size_above_baseline,
largest_block_size_above_baseline);
largest_depth_below_baseline = max(depth_below_baseline,
largest_depth_below_baseline);
}
// Temporarily use `fragment.border_box.start.b` to mean "the distance from the
// baseline". We will assign the real value later.
fragment.border_box.start.b = distance_from_baseline
}
// Calculate the distance from the baseline to the top of the largest fragment with a
// value for `bottom`. Then, if necessary, update `largest_block-size_above_baseline`.
largest_block_size_above_baseline =
max(largest_block_size_above_baseline,
largest_block_size_for_bottom_fragments - largest_depth_below_baseline);
// Calculate the distance from baseline to the bottom of the largest fragment with a
// value for `top`. Then, if necessary, update `largest_depth_below_baseline`.
largest_depth_below_baseline =
max(largest_depth_below_baseline,
largest_block_size_for_top_fragments - largest_block_size_above_baseline);
// Now, the distance from the logical block-start of the line to the baseline can be
// computed as `largest_block-size_above_baseline`.
let baseline_distance_from_block_start = largest_block_size_above_baseline;
// Compute the final positions in the block direction of each fragment. Recall that
// `fragment.border_box.start.b` was set to the distance from the baseline above.
InlineFlow::set_block_fragment_positions(&mut self.fragments,
line,
line_distance_from_flow_block_start,
baseline_distance_from_block_start,
largest_depth_below_baseline);
// This is used to set the block-start position of the next line in the next loop.
line.bounds.size.block = largest_block_size_above_baseline +
largest_depth_below_baseline;
line_distance_from_flow_block_start = line_distance_from_flow_block_start +
line.bounds.size.block;
// We're no longer on the first line, so set indentation to zero.
indentation = Au(0)
} // End of `lines.iter_mut()` loop.
// Assign block sizes for any inline-block descendants.
let thread_id = self.base.thread_id;
for kid in self.base.child_iter() {
if flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED) ||
flow::base(kid).flags.is_float() {
continue
}
kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id);
}
self.base.position.size.block = match self.lines.last() {
Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block,
None => Au(0),
};
self.base.floats = scanner.floats.clone();
self.base.floats.translate(LogicalSize::new(self.base.writing_mode,
Au(0),
-self.base.position.size.block));
self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW);
}
fn compute_absolute_position(&mut self) {
for fragment in self.fragments.fragments.iter_mut() {
let stacking_relative_border_box =
fragment.stacking_relative_border_box(&self.base.stacking_relative_position,
&self.base
.absolute_position_info
.relative_containing_block_size,
self.base
.absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Parent);
let clip = fragment.clipping_region_for_children(&self.base.clip,
&stacking_relative_border_box);
match fragment.specific {
SpecificFragmentInfo::InlineBlock(ref mut info) => {
flow::mut_base(&mut *info.flow_ref).clip = clip;
let block_flow = info.flow_ref.as_block();
block_flow.base.absolute_position_info = self.base.absolute_position_info;
block_flow.base.stacking_relative_position =
stacking_relative_border_box.origin;
}
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => {
flow::mut_base(&mut *info.flow_ref).clip = clip;
let block_flow = info.flow_ref.as_block();
block_flow.base.absolute_position_info = self.base.absolute_position_info;
block_flow.base.stacking_relative_position =
stacking_relative_border_box.origin
}
_ => {}
}
}
}
fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}
fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {}
fn build_display_list(&mut self, layout_context: &LayoutContext) {
self.build_display_list_for_inline(layout_context)
}
fn repair_style(&mut self, _: &Arc) {}
fn compute_overflow(&self) -> Rect {
let mut overflow = ZERO_RECT;
for fragment in self.fragments.fragments.iter() {
overflow = overflow.union(&fragment.compute_overflow())
}
overflow
}
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
stacking_context_position: &Point2D) {
// FIXME(#2795): Get the real container size.
for fragment in self.fragments.fragments.iter() {
if !iterator.should_process(fragment) {
continue
}
let stacking_relative_position = &self.base.stacking_relative_position;
let relative_containing_block_size =
&self.base.absolute_position_info.relative_containing_block_size;
let relative_containing_block_mode =
self.base.absolute_position_info.relative_containing_block_mode;
iterator.process(fragment,
&fragment.stacking_relative_border_box(stacking_relative_position,
relative_containing_block_size,
relative_containing_block_mode,
CoordinateSystem::Parent)
.translate(stacking_context_position))
}
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
for fragment in self.fragments.fragments.iter_mut() {
(*mutator)(fragment)
}
}
}
impl fmt::Debug for InlineFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?} - {:x} - {:?}", self.class(), self.base.debug_id(), self.fragments)
}
}
#[derive(Clone)]
pub struct InlineFragmentContext {
pub styles: Vec>,
}
impl InlineFragmentContext {
pub fn new() -> InlineFragmentContext {
InlineFragmentContext {
styles: vec!()
}
}
fn ptr_eq(&self, other: &InlineFragmentContext) -> bool {
if self.styles.len() != other.styles.len() {
return false
}
for (this_style, other_style) in self.styles.iter().zip(other.styles.iter()) {
if !util::arc_ptr_eq(this_style, other_style) {
return false
}
}
true
}
}
fn inline_contexts_are_equal(inline_context_a: &Option,
inline_context_b: &Option)
-> bool {
match (inline_context_a, inline_context_b) {
(&Some(ref inline_context_a), &Some(ref inline_context_b)) => {
inline_context_a.ptr_eq(inline_context_b)
}
(&None, &None) => true,
(&Some(_), &None) | (&None, &Some(_)) => false,
}
}
/// Block-size above the baseline, depth below the baseline, and ascent for a fragment. See CSS 2.1
/// § 10.8.1.
#[derive(Clone, Copy, Debug, RustcEncodable)]
pub struct InlineMetrics {
pub block_size_above_baseline: Au,
pub depth_below_baseline: Au,
pub ascent: Au,
}
impl InlineMetrics {
/// Creates a new set of inline metrics.
pub fn new(block_size_above_baseline: Au, depth_below_baseline: Au, ascent: Au)
-> InlineMetrics {
InlineMetrics {
block_size_above_baseline: block_size_above_baseline,
depth_below_baseline: depth_below_baseline,
ascent: ascent,
}
}
/// Calculates inline metrics from font metrics and line block-size per CSS 2.1 § 10.8.1.
#[inline]
pub fn from_font_metrics(font_metrics: &FontMetrics, line_height: Au) -> InlineMetrics {
let leading = line_height - (font_metrics.ascent + font_metrics.descent);
InlineMetrics {
block_size_above_baseline: font_metrics.ascent + leading.scale_by(0.5),
depth_below_baseline: font_metrics.descent + leading.scale_by(0.5),
ascent: font_metrics.ascent,
}
}
/// Calculates inline metrics from font metrics and line block-size per CSS 2.1 § 10.8.1.
#[inline]
pub fn from_block_height(font_metrics: &FontMetrics, block_height: Au) -> InlineMetrics {
let leading = block_height - (font_metrics.ascent + font_metrics.descent);
InlineMetrics {
block_size_above_baseline: font_metrics.ascent + leading.scale_by(0.5),
depth_below_baseline: font_metrics.descent + leading.scale_by(0.5),
ascent: font_metrics.ascent + leading.scale_by(0.5),
}
}
pub fn block_size(&self) -> Au {
self.block_size_above_baseline + self.depth_below_baseline
}
pub fn max(&self, other: &InlineMetrics) -> InlineMetrics {
InlineMetrics {
block_size_above_baseline: Au::max(self.block_size_above_baseline,
other.block_size_above_baseline),
depth_below_baseline: Au::max(self.depth_below_baseline, other.depth_below_baseline),
ascent: Au::max(self.ascent, other.ascent),
}
}
}
#[derive(Copy, Clone, PartialEq)]
enum LineFlushMode {
No,
Flush,
}
/// Given a range and a text run, adjusts the range to eliminate trailing whitespace.
fn strip_trailing_whitespace_if_necessary(text_run: &TextRun, range: &mut Range) {
// FIXME(pcwalton): Is there a more clever (i.e. faster) way to do this?
debug!("stripping trailing whitespace: range={:?}, len={}",
range,
text_run.text.chars().count());
let text = text_run.text.slice_chars(range.begin().to_usize(), range.end().to_usize());
let mut trailing_whitespace_character_count = 0;
for ch in text.chars().rev() {
if util::str::char_is_whitespace(ch) {
trailing_whitespace_character_count += 1
} else {
break
}
}
if trailing_whitespace_character_count != 0 {
range.extend_by(CharIndex(-trailing_whitespace_character_count));
}
}