/* 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_block)]
use css::node_style::StyledNode;
use context::LayoutContext;
use floats::{FloatLeft, Floats, PlacementInfo};
use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass};
use flow;
use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo};
use model::IntrinsicISizes;
use model;
use text;
use wrapper::ThreadSafeLayoutNode;
use collections::{Deque, RingBuf};
use geom::Rect;
use gfx::display_list::ContentLevel;
use gfx::font::FontMetrics;
use gfx::font_context::FontContext;
use gfx::text::glyph::CharIndex;
use servo_util::geometry::Au;
use servo_util::geometry;
use servo_util::logical_geometry::{LogicalRect, LogicalMargin, LogicalSize};
use servo_util::range;
use servo_util::range::{EachIndex, Range, RangeIndex, IntRangeIndex};
use std::iter::Enumerate;
use std::fmt;
use std::mem;
use std::num;
use std::slice::{Items, MutItems};
use std::u16;
use style::computed_values::{text_align, vertical_align, white_space};
use style::ComputedValues;
use sync::Arc;
/// `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.
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.0, 1.4) | [1.5, 2.0) | [2.0, 3.4) | [3.4, 4.0) |
/// |------------|-------------|-------------|------------|
/// | '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-size |
/// | I like truffles, | | v |
/// | + - - - - - - - - +---------+---- |
/// | | | |
/// | |<------ size.inline-size ------->| |
/// | |
/// | |
/// +-----------------------------------------------------------+
/// ~~~
pub bounds: LogicalRect,
/// The green zone is the greatest extent from wich 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
}
int_range_index! {
#[doc = "The index of a fragment in a flattened vector of DOM elements."]
struct FragmentIndex(int)
}
/// A line index consists of two indices: a fragment index that refers to the
/// index of a DOM fragment within a flattened inline element; and a glyph index
/// where the 0th glyph refers to the first glyph of that fragment.
#[deriving(Clone, PartialEq, PartialOrd, Eq, Ord, Zero)]
pub struct LineIndices {
/// The index of a fragment into the flattened vector of DOM elements.
///
/// For example, given the HTML below:
///
/// ~~~html
/// I like truffles,
yes I do.
/// ~~~
///
/// The fragments would be indexed as follows:
///
/// | 0 | 1 | 2 | 3 |
/// |------|------------------|---------|--------------|
/// | 'I ' | 'like truffles,' | `
` | ' yes I do.' |
pub fragment_index: FragmentIndex,
/// The index of a character in a DOM fragment. Continuous runs of whitespace
/// are treated as single characters. Non-breakable DOM fragments such as
/// images are treated as having a range length of `1`.
///
/// For example, given the HTML below:
///
/// ~~~html
/// I like truffles,
yes I do.
/// ~~~
///
/// The characters would be indexed as follows:
///
/// | 0 | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
/// |---|---|---|---|---|---|---|---|---|---|---|---|----|----|----|----|----|
/// | I | | l | i | k | e | | t | r | u | f | f | l | e | s | , | |
///
/// | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
/// |---------|---|---|---|---|---|---|---|---|---|---|
/// | `
` | | y | e | s | | I | | d | o | . |
pub char_index: CharIndex,
}
impl RangeIndex for LineIndices {}
impl Add for LineIndices {
fn add(&self, other: &LineIndices) -> LineIndices {
// TODO: use debug_assert! after rustc upgrade
if cfg!(not(ndebug)) {
assert!(other.fragment_index == num::zero() || other.char_index == num::zero(),
"Attempted to add {} to {}. Both the fragment_index and \
char_index of the RHS are non-zero. This probably was a \
mistake!", self, other);
}
LineIndices {
fragment_index: self.fragment_index + other.fragment_index,
char_index: self.char_index + other.char_index,
}
}
}
impl Sub for LineIndices {
fn sub(&self, other: &LineIndices) -> LineIndices {
// TODO: use debug_assert! after rustc upgrade
if cfg!(not(ndebug)) {
assert!(other.fragment_index == num::zero() || other.char_index == num::zero(),
"Attempted to subtract {} from {}. Both the fragment_index \
and char_index of the RHS are non-zero. This probably was \
a mistake!", self, other);
}
LineIndices {
fragment_index: self.fragment_index - other.fragment_index,
char_index: self.char_index - other.char_index,
}
}
}
impl Neg for LineIndices {
fn neg(&self) -> LineIndices {
// TODO: use debug_assert! after rustc upgrade
if cfg!(not(ndebug)) {
assert!(self.fragment_index == num::zero() || self.char_index == num::zero(),
"Attempted to negate {}. Both the fragment_index and \
char_index are non-zero. This probably was a mistake!",
self);
}
LineIndices {
fragment_index: -self.fragment_index,
char_index: -self.char_index,
}
}
}
impl fmt::Show for LineIndices {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}", self.fragment_index, self.char_index)
}
}
pub fn each_fragment_index(range: &Range) -> EachIndex {
range::each_index(range.begin().fragment_index, range.end().fragment_index)
}
pub fn each_char_index(range: &Range) -> EachIndex {
range::each_index(range.begin().char_index, range.end().char_index)
}
struct LineBreaker {
pub floats: Floats,
pub new_fragments: Vec,
pub work_list: RingBuf,
pub pending_line: Line,
pub lines: Vec,
pub cur_b: Au, // Current position on the block direction
}
impl LineBreaker {
pub fn new(float_ctx: Floats) -> LineBreaker {
LineBreaker {
new_fragments: Vec::new(),
work_list: RingBuf::new(),
pending_line: Line {
range: Range::empty(),
bounds: LogicalRect::zero(float_ctx.writing_mode),
green_zone: LogicalSize::zero(float_ctx.writing_mode)
},
floats: float_ctx,
lines: Vec::new(),
cur_b: Au::new(0)
}
}
pub fn floats(&mut self) -> Floats {
self.floats.clone()
}
fn reset_scanner(&mut self) {
debug!("Resetting LineBreaker's state for flow.");
self.lines = Vec::new();
self.new_fragments = Vec::new();
self.cur_b = Au(0);
self.reset_line();
}
fn reset_line(&mut self) {
self.pending_line.range.reset(num::zero(), num::zero());
self.pending_line.bounds = LogicalRect::new(
self.floats.writing_mode, Au::new(0), self.cur_b, Au::new(0), Au::new(0));
self.pending_line.green_zone = LogicalSize::zero(self.floats.writing_mode)
}
pub fn scan_for_lines(&mut self, flow: &mut InlineFlow, layout_context: &LayoutContext) {
self.reset_scanner();
let mut old_fragments = mem::replace(&mut flow.fragments, InlineFragments::new());
{ // Enter a new scope so that old_fragment_iter's borrow is released
let mut old_fragment_iter = old_fragments.fragments.iter();
loop {
// acquire the next fragment to lay out from work list or fragment list
let cur_fragment = if self.work_list.is_empty() {
match old_fragment_iter.next() {
None => break,
Some(fragment) => {
debug!("LineBreaker: Working with fragment from flow: b{}",
fragment.debug_id());
(*fragment).clone()
}
}
} else {
let fragment = self.work_list.pop_front().unwrap();
debug!("LineBreaker: Working with fragment from work list: b{}",
fragment.debug_id());
fragment
};
let fragment_was_appended = match cur_fragment.white_space() {
white_space::normal => self.try_append_to_line(cur_fragment, flow, layout_context),
white_space::pre => self.try_append_to_line_by_new_line(cur_fragment),
};
if !fragment_was_appended {
debug!("LineBreaker: Fragment wasn't appended, because line {:u} was full.",
self.lines.len());
self.flush_current_line();
} else {
debug!("LineBreaker: appended a fragment to line {:u}", self.lines.len());
}
}
if self.pending_line.range.length() > num::zero() {
debug!("LineBreaker: Partially full line {:u} inline_start at end of scanning.",
self.lines.len());
self.flush_current_line();
}
}
old_fragments.fixup(mem::replace(&mut self.new_fragments, vec![]));
flow.fragments = old_fragments;
flow.lines = mem::replace(&mut self.lines, Vec::new());
}
fn flush_current_line(&mut self) {
debug!("LineBreaker: Flushing line {:u}: {:?}",
self.lines.len(), self.pending_line);
// clear line and add line mapping
debug!("LineBreaker: Saving information for flushed line {:u}.", self.lines.len());
self.lines.push(self.pending_line);
self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block;
self.reset_line();
}
// 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_block_size_for_line(&self, new_fragment: &Fragment, layout_context: &LayoutContext) -> Au {
let fragment_block_size = new_fragment.content_block_size(layout_context);
if fragment_block_size > self.pending_line.bounds.size.block {
fragment_block_size
} else {
self.pending_line.bounds.size.block
}
}
/// 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, first_fragment: &Fragment, ceiling: Au, flow: &InlineFlow)
-> (LogicalRect, Au) {
debug!("LineBreaker: Trying to place first fragment of line {}", self.lines.len());
let first_fragment_size = first_fragment.border_box.size;
let splittable = first_fragment.can_split();
debug!("LineBreaker: fragment size: {}, splittable: {}", first_fragment_size, splittable);
// Initally, pretend a splittable fragment has 0 inline-size.
// We will move it later if it has nonzero inline-size
// and that causes problems.
let placement_inline_size = if splittable {
Au::new(0)
} else {
first_fragment_size.inline
};
let info = PlacementInfo {
size: LogicalSize::new(
self.floats.writing_mode, placement_inline_size, first_fragment_size.block),
ceiling: ceiling,
max_inline_size: flow.base.position.size.inline,
kind: FloatLeft,
};
let line_bounds = self.floats.place_between_floats(&info);
debug!("LineBreaker: found position for line: {} using placement_info: {:?}",
line_bounds,
info);
// Simple case: if the fragment fits, then we can stop here
if line_bounds.size.inline > first_fragment_size.inline {
debug!("LineBreaker: case=fragment fits");
return (line_bounds, first_fragment_size.inline);
}
// If not, but we can't split the fragment, then we'll place
// the line here and it will overflow.
if !splittable {
debug!("LineBreaker: case=line doesn't fit, but is unsplittable");
return (line_bounds, first_fragment_size.inline);
}
debug!("LineBreaker: used to call split_to_inline_size here");
return (line_bounds, first_fragment_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,
in_fragment: Fragment,
flow: &InlineFlow,
new_block_size: Au,
line_is_empty: bool)
-> bool {
debug!("LineBreaker: entering float collision avoider!");
// First predict where the next line is going to be.
let this_line_y = self.pending_line.bounds.start.b;
let (next_line, first_fragment_inline_size) = self.initial_line_placement(&in_fragment, this_line_y, flow);
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;
assert!(!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
}
fn try_append_to_line_by_new_line(&mut self, in_fragment: Fragment) -> bool {
if in_fragment.new_line_pos.len() == 0 {
debug!("LineBreaker: Did not find a new-line character, so pushing the fragment to \
the line without splitting.");
self.push_fragment_to_line(in_fragment);
true
} else {
debug!("LineBreaker: Found a new-line character, so splitting theline.");
let (inline_start, inline_end, run) = in_fragment.find_split_info_by_new_line()
.expect("LineBreaker: This split case makes no sense!");
let writing_mode = self.floats.writing_mode;
// TODO(bjz): Remove fragment splitting
let split_fragment = |split: SplitInfo| {
let info = ScannedTextFragmentInfo::new(run.clone(), split.range);
let specific = ScannedTextFragment(info);
let size = LogicalSize::new(
writing_mode, split.inline_size, in_fragment.border_box.size.block);
in_fragment.transform(size, specific)
};
debug!("LineBreaker: Pushing the fragment to the inline_start of the new-line character \
to the line.");
let mut inline_start = split_fragment(inline_start);
inline_start.new_line_pos = vec![];
self.push_fragment_to_line(inline_start);
for inline_end in inline_end.move_iter() {
debug!("LineBreaker: Deferring the fragment to the inline_end of the new-line \
character to the line.");
let mut inline_end = split_fragment(inline_end);
inline_end.new_line_pos = in_fragment.new_line_pos.clone();
self.work_list.push_front(inline_end);
}
false
}
}
/// Tries to append the given fragment to the line, splitting it if necessary. Returns false only if
/// we should break the line.
fn try_append_to_line(&mut self, in_fragment: Fragment, flow: &InlineFlow, layout_context: &LayoutContext) -> bool {
let line_is_empty = self.pending_line.range.length() == num::zero();
if line_is_empty {
let (line_bounds, _) = self.initial_line_placement(&in_fragment, self.cur_b, flow);
self.pending_line.bounds.start = line_bounds.start;
self.pending_line.green_zone = line_bounds.size;
}
debug!("LineBreaker: Trying to append fragment to line {:u} (fragment size: {}, green zone: \
{}): {}",
self.lines.len(),
in_fragment.border_box.size,
self.pending_line.green_zone,
in_fragment);
let green_zone = self.pending_line.green_zone;
// NB: At this point, if `green_zone.inline-size < self.pending_line.bounds.size.inline-size` or
// `green_zone.block-size < self.pending_line.bounds.size.block-size`, then we committed a line
// that overlaps with floats.
let new_block_size = self.new_block_size_for_line(&in_fragment, layout_context);
if new_block_size > green_zone.block {
// Uh-oh. Float collision imminent. Enter the float collision avoider
return self.avoid_floats(in_fragment, flow, new_block_size, line_is_empty)
}
// 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 new_inline_size = self.pending_line.bounds.size.inline + in_fragment.border_box.size.inline;
if new_inline_size <= green_zone.inline {
debug!("LineBreaker: case=fragment fits without splitting");
self.push_fragment_to_line(in_fragment);
return true
}
if !in_fragment.can_split() {
// TODO(eatkinson, issue #224): Signal that horizontal overflow happened?
if line_is_empty {
debug!("LineBreaker: case=fragment can't split and line {:u} is empty, so \
overflowing.",
self.lines.len());
self.push_fragment_to_line(in_fragment);
return true
}
}
let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline;
let split = in_fragment.find_split_info_for_inline_size(CharIndex(0), available_inline_size, line_is_empty);
match split.map(|(inline_start, inline_end, run)| {
// TODO(bjz): Remove fragment splitting
let split_fragment = |split: SplitInfo| {
let info = ScannedTextFragmentInfo::new(run.clone(), split.range);
let specific = ScannedTextFragment(info);
let size = LogicalSize::new(
self.floats.writing_mode, split.inline_size, in_fragment.border_box.size.block);
in_fragment.transform(size, specific)
};
(inline_start.map(|x| { debug!("LineBreaker: Left split {}", x); split_fragment(x) }),
inline_end.map(|x| { debug!("LineBreaker: Right split {}", x); split_fragment(x) }))
}) {
None => {
debug!("LineBreaker: Tried to split unsplittable render fragment! Deferring to next \
line. {}", in_fragment);
self.work_list.push_front(in_fragment);
false
},
Some((Some(inline_start_fragment), Some(inline_end_fragment))) => {
debug!("LineBreaker: Line break found! Pushing inline_start fragment to line and deferring \
inline_end fragment to next line.");
self.push_fragment_to_line(inline_start_fragment);
self.work_list.push_front(inline_end_fragment);
true
},
Some((Some(inline_start_fragment), None)) => {
debug!("LineBreaker: Pushing inline_start fragment to line.");
self.push_fragment_to_line(inline_start_fragment);
true
},
Some((None, Some(inline_end_fragment))) => {
debug!("LineBreaker: Pushing inline_end fragment to line.");
self.push_fragment_to_line(inline_end_fragment);
true
},
Some((None, None)) => {
error!("LineBreaker: This split case makes no sense!");
true
},
}
}
// An unconditional push
fn push_fragment_to_line(&mut self, fragment: Fragment) {
debug!("LineBreaker: Pushing fragment {} to line {:u}", fragment.debug_id(), self.lines.len());
if self.pending_line.range.length() == num::zero() {
assert!(self.new_fragments.len() <= (u16::MAX as uint));
self.pending_line.range.reset(
LineIndices {
fragment_index: FragmentIndex(self.new_fragments.len() as int),
char_index: CharIndex(0) /* unused for now */,
},
num::zero()
);
}
self.pending_line.range.extend_by(LineIndices {
fragment_index: FragmentIndex(1),
char_index: CharIndex(0) /* unused for now */ ,
});
self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
fragment.border_box.size.inline;
self.pending_line.bounds.size.block = Au::max(self.pending_line.bounds.size.block,
fragment.border_box.size.block);
self.new_fragments.push(fragment);
}
}
/// Iterator over fragments.
pub struct FragmentIterator<'a> {
iter: Enumerate>,
ranges: &'a Vec,
}
impl<'a> Iterator<(&'a Fragment, InlineFragmentContext<'a>)> for FragmentIterator<'a> {
#[inline]
fn next(&mut self) -> Option<(&'a Fragment, InlineFragmentContext<'a>)> {
self.iter.next().map(|(i, fragment)| {
(fragment, InlineFragmentContext::new(self.ranges, FragmentIndex(i as int)))
})
}
}
/// Mutable iterator over fragments.
pub struct MutFragmentIterator<'a> {
iter: Enumerate>,
ranges: &'a Vec,
}
impl<'a> Iterator<(&'a mut Fragment, InlineFragmentContext<'a>)> for MutFragmentIterator<'a> {
#[inline]
fn next(&mut self) -> Option<(&'a mut Fragment, InlineFragmentContext<'a>)> {
self.iter.next().map(|(i, fragment)| {
(fragment, InlineFragmentContext::new(self.ranges, FragmentIndex(i as int)))
})
}
}
/// Represents a list of inline fragments, including element ranges.
pub struct InlineFragments {
/// The fragments themselves.
pub fragments: Vec,
/// Tracks the elements that made up the fragments above. This is used to
/// recover the DOM structure from the `fragments` when it's needed.
pub ranges: Vec,
}
impl InlineFragments {
/// Creates an empty set of inline fragments.
pub fn new() -> InlineFragments {
InlineFragments {
fragments: vec![],
ranges: vec![],
}
}
/// Returns the number of inline fragments.
pub fn len(&self) -> uint {
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.len() == 0
}
/// Pushes a new inline fragment.
pub fn push(&mut self, fragment: Fragment, style: Arc) {
self.ranges.push(InlineFragmentRange::new(
style, Range::new(FragmentIndex(self.fragments.len() as int), FragmentIndex(1)),
));
self.fragments.push(fragment)
}
/// Merges another set of inline fragments with this one.
pub fn push_all(&mut self, InlineFragments { fragments, ranges }: InlineFragments) {
let adjustment = FragmentIndex(self.fragments.len() as int);
self.push_all_ranges(ranges, adjustment);
self.fragments.push_all_move(fragments);
}
/// Returns an iterator that iterates over all fragments along with the appropriate context.
pub fn iter<'a>(&'a self) -> FragmentIterator<'a> {
FragmentIterator {
iter: self.fragments.as_slice().iter().enumerate(),
ranges: &self.ranges,
}
}
/// Returns an iterator that iterates over all fragments along with the appropriate context and
/// allows those fragments to be mutated.
pub fn mut_iter<'a>(&'a mut self) -> MutFragmentIterator<'a> {
MutFragmentIterator {
iter: self.fragments.as_mut_slice().mut_iter().enumerate(),
ranges: &self.ranges,
}
}
/// A convenience function to return the fragment at a given index.
pub fn get<'a>(&'a self, index: uint) -> &'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: uint) -> &'a mut Fragment {
self.fragments.get_mut(index)
}
/// Adds the given node to the fragment map.
pub fn push_range(&mut self, style: Arc, range: Range) {
self.ranges.push(InlineFragmentRange::new(style, range))
}
/// Pushes the ranges in a fragment map, adjusting indices as necessary.
fn push_all_ranges(&mut self, ranges: Vec, adjustment: FragmentIndex) {
for other_range in ranges.move_iter() {
let InlineFragmentRange {
style: other_style,
range: mut other_range
} = other_range;
other_range.shift_by(adjustment);
self.push_range(other_style, other_range)
}
}
/// Returns the range with the given index.
pub fn get_mut_range<'a>(&'a mut self, index: FragmentIndex) -> &'a mut InlineFragmentRange {
self.ranges.get_mut(index.to_uint())
}
/// Rebuilds the list after the fragments have been split or deleted (for example, for line
/// breaking). This assumes that the overall structure of the DOM has not changed; if the
/// DOM has changed, then the flow constructor will need to do more complicated surgery than
/// this function can provide.
///
/// FIXME(#2267, pcwalton): It would be more efficient to not have to clone fragments all the time;
/// i.e. if `self.fragments` contained less info than the entire range of fragments. See
/// `layout::construct::strip_ignorable_whitespace_from_start` for an example of some code that
/// needlessly has to clone fragments.
pub fn fixup(&mut self, new_fragments: Vec) {
// TODO(pcwalton): Post Rust upgrade, use `with_capacity` here.
let old_list = mem::replace(&mut self.ranges, vec![]);
let mut worklist = vec![]; // FIXME(#2269, pcwalton): was smallvec4
let mut old_list_iter = old_list.move_iter().peekable();
{ // Enter a new scope so that new_fragments_iter's borrow is released
let mut new_fragments_iter = new_fragments.iter().enumerate().peekable();
// FIXME(#2270, pcwalton): I don't think this will work if multiple old fragments
// correspond to the same node.
for (i, old_fragment) in self.fragments.iter().enumerate() {
let old_fragment_index = FragmentIndex(i as int);
// Find the start of the corresponding new fragment.
let new_fragment_start = match new_fragments_iter.peek() {
Some(&(index, new_fragment)) if new_fragment.node == old_fragment.node => {
// We found the start of the corresponding new fragment.
FragmentIndex(index as int)
}
Some(_) | None => {
// The old fragment got deleted entirely.
continue
}
};
drop(new_fragments_iter.next());
// Eat any additional fragments that the old fragment got split into.
loop {
match new_fragments_iter.peek() {
Some(&(_, new_fragment)) if new_fragment.node == old_fragment.node => {}
Some(_) | None => break,
}
drop(new_fragments_iter.next());
}
// Find all ranges that started at this old fragment and add them onto the worklist.
loop {
match old_list_iter.peek() {
None => break,
Some(fragment_range) => {
if fragment_range.range.begin() > old_fragment_index {
// We haven't gotten to the appropriate old fragment yet, so stop.
break
}
// Note that it can be the case that `fragment_range.range.begin() < i`.
// This is OK, as it corresponds to the case in which a fragment got
// deleted entirely (e.g. ignorable whitespace got nuked). In that case we
// want to keep the range, but shorten it.
}
};
let InlineFragmentRange {
style: style,
range: old_range,
} = old_list_iter.next().unwrap();
worklist.push(InlineFragmentFixupWorkItem {
style: style,
new_start_index: new_fragment_start,
old_end_index: old_range.end(),
});
}
// Pop off any ranges that ended at this fragment.
loop {
match worklist.as_slice().last() {
None => break,
Some(last_work_item) => {
if last_work_item.old_end_index > old_fragment_index + FragmentIndex(1) {
// Haven't gotten to it yet.
break
}
}
}
let new_last_index = match new_fragments_iter.peek() {
None => {
// At the end.
FragmentIndex(new_fragments.len() as int)
}
Some(&(index, _)) => {
FragmentIndex(index as int)
},
};
let InlineFragmentFixupWorkItem {
style,
new_start_index,
..
} = worklist.pop().unwrap();
let range = Range::new(new_start_index, new_last_index - new_start_index);
self.ranges.push(InlineFragmentRange::new(style, range))
}
}
}
self.fragments = new_fragments;
}
/// Strips ignorable whitespace from the start of a list of fragments.
pub fn strip_ignorable_whitespace_from_start(&mut self) {
if self.is_empty() { return }; // Fast path
// FIXME (rust#16151): This can be reverted back to using skip_while once
// the upstream bug is fixed.
let mut fragments = mem::replace(&mut self.fragments, vec![]).move_iter();
let mut new_fragments = Vec::new();
let mut skipping = true;
for fragment in fragments {
if skipping && fragment.is_whitespace_only() {
debug!("stripping ignorable whitespace from start");
continue
}
skipping = false;
new_fragments.push(fragment);
}
self.fixup(new_fragments);
}
/// Strips ignorable whitespace from the end of a list of fragments.
pub fn strip_ignorable_whitespace_from_end(&mut self) {
if self.is_empty() {
return;
}
let mut new_fragments = self.fragments.clone();
while new_fragments.len() > 0 && new_fragments.as_slice().last().get_ref().is_whitespace_only() {
debug!("stripping ignorable whitespace from end");
drop(new_fragments.pop());
}
self.fixup(new_fragments);
}
}
/// Flows for inline layout.
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 block-size and
/// font style.
pub minimum_block_size_above_baseline: Au,
/// The minimum depth below the baseline for each line, as specified by the line block-size and
/// font style.
pub minimum_depth_below_baseline: Au,
}
impl InlineFlow {
pub fn from_fragments(node: ThreadSafeLayoutNode, fragments: InlineFragments) -> InlineFlow {
InlineFlow {
base: BaseFlow::new(node),
fragments: fragments,
lines: Vec::new(),
minimum_block_size_above_baseline: Au(0),
minimum_depth_below_baseline: Au(0),
}
}
pub fn build_display_list_inline(&mut self, layout_context: &LayoutContext) {
let size = self.base.position.size.to_physical(self.base.writing_mode);
if !Rect(self.base.abs_position, size).intersects(&layout_context.shared.dirty) {
return
}
// TODO(#228): Once we form lines and have their cached bounds, we can be smarter and
// not recurse on a line if nothing in it can intersect the dirty region.
debug!("Flow: building display list for {:u} inline fragments", self.fragments.len());
for (fragment, context) in self.fragments.mut_iter() {
let rel_offset = fragment.relative_position(&self.base
.absolute_position_info
.relative_containing_block_size,
Some(context));
drop(fragment.build_display_list(&mut self.base.display_list,
layout_context,
self.base.abs_position.add_size(
&rel_offset.to_physical(self.base.writing_mode)),
ContentLevel,
Some(context)));
}
// TODO(#225): Should `inline-block` elements have flows as children of the inline flow or
// should the flow be nested inside the fragment somehow?
// For now, don't traverse the subtree rooted here.
}
/// 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 `biggest_block-start` and/or `biggest_block-end` were updated.
/// That is, if the box has a `block-start` or `block-end` value, 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) {
match fragment.vertical_align() {
vertical_align::baseline => (-ascent, false),
vertical_align::middle => {
// TODO: x-block-size value should be used from font info.
let xblock_size = Au(0);
let fragment_block_size = fragment.content_block_size(layout_context);
let offset_block_start = -(xblock_size + fragment_block_size).scale_by(0.5);
*block_size_above_baseline = offset_block_start.scale_by(-1.0);
*depth_below_baseline = fragment_block_size - *block_size_above_baseline;
(offset_block_start, false)
},
vertical_align::sub => {
// TODO: The proper position for subscripts should be used. Lower the baseline to
// the proper position for subscripts.
let sub_offset = Au(0);
(sub_offset - ascent, false)
},
vertical_align::super_ => {
// TODO: The proper position for superscripts should be used. Raise the baseline to
// the proper position for superscripts.
let super_offset = Au(0);
(-super_offset - ascent, false)
},
vertical_align::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;
(*depth_below_baseline - prev_depth_below_baseline - ascent, false)
},
vertical_align::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;
(*depth_below_baseline - prev_depth_below_baseline - ascent, false)
},
vertical_align::top => {
*largest_block_size_for_top_fragments =
Au::max(*largest_block_size_for_top_fragments,
*block_size_above_baseline + *depth_below_baseline);
let offset_top = *block_size_above_baseline - ascent;
(offset_top, true)
},
vertical_align::bottom => {
*largest_block_size_for_bottom_fragments =
Au::max(*largest_block_size_for_bottom_fragments,
*block_size_above_baseline + *depth_below_baseline);
let offset_bottom = -(*depth_below_baseline + ascent);
(offset_bottom, true)
},
vertical_align::Length(length) => (-(length + ascent), false),
vertical_align::Percentage(p) => {
let line_height = fragment.calculate_line_height(layout_context);
let percent_offset = line_height.scale_by(p);
(-(percent_offset + ascent), false)
}
}
}
/// Sets fragment X positions based on alignment for one line.
fn set_horizontal_fragment_positions(fragments: &mut InlineFragments,
line: &Line,
line_align: text_align::T) {
// Figure out how much inline-size we have.
let slack_inline_size = Au::max(Au(0), line.green_zone.inline - line.bounds.size.inline);
// Set the fragment x positions based on that alignment.
let mut offset_x = line.bounds.start.i;
offset_x = offset_x + match line_align {
// So sorry, but justified text is more complicated than shuffling line
// coordinates.
//
// TODO(burg, issue #213): Implement `text-align: justify`.
text_align::left | text_align::justify => Au(0),
text_align::center => slack_inline_size.scale_by(0.5),
text_align::right => slack_inline_size,
};
for i in each_fragment_index(&line.range) {
let fragment = fragments.get_mut(i.to_uint());
let size = fragment.border_box.size;
fragment.border_box = LogicalRect::new(
fragment.style.writing_mode, offset_x, fragment.border_box.start.b,
size.inline, size.block);
offset_x = offset_x + size.inline;
}
}
/// 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) {
let font_style = text::computed_style_to_font_style(style);
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);
(inline_metrics.block_size_above_baseline, inline_metrics.depth_below_baseline)
}
}
impl Flow for InlineFlow {
fn class(&self) -> FlowClass {
InlineFlowClass
}
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, _: &LayoutContext) {
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_inline_sizes = IntrinsicISizes::new();
for (fragment, context) in self.fragments.mut_iter() {
debug!("Flow: measuring {}", *fragment);
let fragment_intrinsic_inline_sizes =
fragment.intrinsic_inline_sizes(Some(context));
intrinsic_inline_sizes.minimum_inline_size = geometry::max(
intrinsic_inline_sizes.minimum_inline_size,
fragment_intrinsic_inline_sizes.minimum_inline_size);
intrinsic_inline_sizes.preferred_inline_size =
intrinsic_inline_sizes.preferred_inline_size +
fragment_intrinsic_inline_sizes.preferred_inline_size;
}
self.base.intrinsic_inline_sizes = intrinsic_inline_sizes;
}
/// 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) {
// 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.position.size.inline;
let this = &mut *self;
for (fragment, context) in this.fragments.mut_iter() {
fragment.assign_replaced_inline_size_if_necessary(inline_size,
Some(context))
}
}
assert!(self.base.children.len() == 0,
"InlineFlow: should not have children flows in the current layout implementation.");
// There are no child contexts, so stop here.
// TODO(Issue #225): once there are 'inline-block' elements, this won't be
// true. In that case, set the InlineBlockFragment's inline-size to the
// shrink-to-fit inline-size, perform inline flow, and set the block
// flow context's inline-size as the assigned inline-size of the
// 'inline-block' fragment that created this flow before recursing.
}
/// Calculate and set the block-size of this flow. See CSS 2.1 § 10.6.1.
fn assign_block_size(&mut self, ctx: &LayoutContext) {
debug!("assign_block_size_inline: assigning block_size for flow");
// Divide the fragments into lines.
//
// TODO(#226): Get the CSS `line-block-size` property from the containing block's style to
// determine minimum line block-size.
//
// TODO(#226): Get the CSS `line-block-size` property from each non-replaced inline element to
// determine its block-size for computing line block-size.
//
// TODO(pcwalton): Cache the line scanner?
debug!("assign_block_size_inline: floats in: {:?}", self.base.floats);
// assign block-size for inline fragments
for (fragment, _) in self.fragments.mut_iter() {
fragment.assign_replaced_block_size_if_necessary();
}
let scanner_floats = self.base.floats.clone();
let mut scanner = LineBreaker::new(scanner_floats);
scanner.scan_for_lines(self, ctx);
// All lines use text alignment of the flow.
let text_align = self.base.flags.text_align();
// Now, go through each line and lay out the fragments inside.
let mut line_distance_from_flow_block_start = Au(0);
for line in self.lines.mut_iter() {
// Lay out fragments horizontally.
InlineFlow::set_horizontal_fragment_positions(&mut self.fragments, line, text_align);
// Set the block-start y 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_i in each_fragment_index(&line.range) {
let fragment = self.fragments.fragments.get_mut(fragment_i.to_uint());
let InlineMetrics {
block_size_above_baseline: mut block_size_above_baseline,
depth_below_baseline: mut depth_below_baseline,
ascent
} = fragment.inline_metrics(ctx);
// 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. Previous versions of the code used
// the parent's font; this code uses the current font.
let parent_text_top = fragment.style().get_font().font_size;
// We should calculate the distance from baseline to the bottom of the parent's
// content area. But for now we assume it's zero.
let parent_text_bottom = Au(0);
// 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,
parent_text_top,
parent_text_bottom,
&mut block_size_above_baseline,
&mut depth_below_baseline,
&mut largest_block_size_for_top_fragments,
&mut largest_block_size_for_bottom_fragments,
ctx);
// 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 = Au::max(block_size_above_baseline,
largest_block_size_above_baseline);
largest_depth_below_baseline = Au::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 =
Au::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 =
Au::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.
for fragment_i in each_fragment_index(&line.range) {
let fragment = self.fragments.get_mut(fragment_i.to_uint());
match fragment.vertical_align() {
vertical_align::top => {
fragment.border_box.start.b = fragment.border_box.start.b +
line_distance_from_flow_block_start
}
vertical_align::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
}
}
}
// This is used to set the block-start y 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;
} // End of `lines.each` loop.
self.base.position.size.block =
if self.lines.len() > 0 {
self.lines.as_slice().last().get_ref().bounds.start.b +
self.lines.as_slice().last().get_ref().bounds.size.block
} else {
Au::new(0)
};
self.base.floats = scanner.floats();
self.base.floats.translate(LogicalSize::new(
self.base.writing_mode, Au::new(0), -self.base.position.size.block));
}
}
impl fmt::Show for InlineFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "InlineFlow"));
for (i, (fragment, _)) in self.fragments.iter().enumerate() {
if i == 0 {
try!(write!(f, ": {}", fragment))
} else {
try!(write!(f, ", {}", fragment))
}
}
Ok(())
}
}
/// Information that inline flows keep about a single nested element. This is used to recover the
/// DOM structure from the flat fragment list when it's needed.
pub struct InlineFragmentRange {
/// The style of the DOM node that this range refers to.
pub style: Arc,
/// The range, in indices into the fragment list.
pub range: Range,
}
impl InlineFragmentRange {
/// Creates a new fragment range from the given values.
fn new(style: Arc, range: Range) -> InlineFragmentRange {
InlineFragmentRange {
style: style,
range: range,
}
}
/// Returns the dimensions of the border in this fragment range.
#[inline]
pub fn border(&self) -> LogicalMargin {
self.style.logical_border_width()
}
/// Returns the dimensions of the padding in this fragment range.
#[inline]
pub fn padding(&self) -> LogicalMargin {
// FIXME(#2266, pcwalton): Is Au(0) right here for the containing block?
model::padding_from_style(&*self.style, Au(0))
}
}
struct InlineFragmentFixupWorkItem {
style: Arc,
new_start_index: FragmentIndex,
old_end_index: FragmentIndex,
}
/// The type of an iterator over fragment ranges in the fragment map.
pub struct RangeIterator<'a> {
iter: Items<'a,InlineFragmentRange>,
index: FragmentIndex,
is_first: bool,
}
impl<'a> Iterator<&'a InlineFragmentRange> for RangeIterator<'a> {
fn next(&mut self) -> Option<&'a InlineFragmentRange> {
if !self.is_first {
// Yield the next fragment range if it contains the index
self.iter.next().and_then(|frag_range| {
if frag_range.range.contains(self.index) { Some(frag_range) } else { None }
})
} else {
// Find the first fragment range that contains the index if it exists
let index = self.index;
let first = self.iter.by_ref().find(|frag_range| {
frag_range.range.contains(index)
});
self.is_first = false; // We have made our first iteration
first
}
}
}
/// The context that an inline fragment appears in. This allows the fragment map to be passed in
/// conveniently to various fragment functions.
pub struct InlineFragmentContext<'a> {
ranges: &'a Vec,
index: FragmentIndex,
}
impl<'a> InlineFragmentContext<'a> {
pub fn new<'a>(ranges: &'a Vec, index: FragmentIndex) -> InlineFragmentContext<'a> {
InlineFragmentContext {
ranges: ranges,
index: index,
}
}
/// Iterates over all ranges that contain the fragment at context's index, outermost first.
#[inline(always)]
pub fn ranges(&self) -> RangeIterator<'a> {
// TODO: It would be more straightforward to return an existing iterator
// rather defining our own `RangeIterator`, but this requires unboxed
// closures in order to satisfy the borrow checker:
//
// ~~~rust
// let index = self.index;
// self.ranges.iter()
// .skip_while(|fr| fr.range.contains(index))
// .take_while(|fr| fr.range.contains(index))
// ~~~
RangeIterator {
iter: self.ranges.iter(),
index: self.index,
is_first: true,
}
}
}
/// BSize above the baseline, depth below the baseline, and ascent for a fragment. See CSS 2.1 §
/// 10.8.1.
pub struct InlineMetrics {
pub block_size_above_baseline: Au,
pub depth_below_baseline: Au,
pub ascent: Au,
}
impl InlineMetrics {
/// 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,
}
}
}