diff options
Diffstat (limited to 'src/components/layout/inline.rs')
-rw-r--r-- | src/components/layout/inline.rs | 1170 |
1 files changed, 0 insertions, 1170 deletions
diff --git a/src/components/layout/inline.rs b/src/components/layout/inline.rs deleted file mode 100644 index 6c22b25746e..00000000000 --- a/src/components/layout/inline.rs +++ /dev/null @@ -1,1170 +0,0 @@ -/* 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 layout_debug; -use model::IntrinsicISizes; -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, LogicalSize}; -use servo_util::range; -use servo_util::range::{EachIndex, Range, RangeIndex, IntRangeIndex}; -use std::fmt; -use std::mem; -use std::num; -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. -#[deriving(Encodable)] -pub struct Line { - /// A range of line indices that describe line breaks. - /// - /// For example, consider the following HTML and rendered element with - /// linebreaks: - /// - /// ~~~html - /// <span>I <span>like truffles, <img></span> yes I do.</span> - /// ~~~ - /// - /// ~~~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,' | '<img> yes' | 'I do.' | - pub range: Range<LineIndices>, - /// The bounds are the exact position and extents of the line with respect - /// to the parent box. - /// - /// For example, for the HTML below... - /// - /// ~~~html - /// <div><span>I <span>like truffles, <img></span></div> - /// ~~~ - /// - /// ...the bounds would be: - /// - /// ~~~text - /// +-----------------------------------------------------------+ - /// | ^ | - /// | | | - /// | origin.y | - /// | | | - /// | v | - /// |< - origin.x ->+ - - - - - - - - +---------+---- | - /// | | | | ^ | - /// | | | <img> | size.block-size | - /// | I like truffles, | | v | - /// | + - - - - - - - - +---------+---- | - /// | | | | - /// | |<------ size.inline-size ------->| | - /// | | - /// | | - /// +-----------------------------------------------------------+ - /// ~~~ - pub bounds: LogicalRect<Au>, - /// 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<Au> -} - -int_range_index! { - #[deriving(Encodable)] - #[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, Encodable, 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 - /// <span>I <span>like truffles, <img></span> yes I do.</span> - /// ~~~ - /// - /// The fragments would be indexed as follows: - /// - /// | 0 | 1 | 2 | 3 | - /// |------|------------------|---------|--------------| - /// | 'I ' | 'like truffles,' | `<img>` | ' 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 - /// <span>I <span>like truffles, <img></span> yes I do.</span> - /// ~~~ - /// - /// 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 | - /// |---------|---|---|---|---|---|---|---|---|---|---| - /// | `<img>` | | y | e | s | | I | | d | o | . | - pub char_index: CharIndex, -} - -impl RangeIndex for LineIndices {} - -impl Add<LineIndices, LineIndices> 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<LineIndices, LineIndices> 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<LineIndices> 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<LineIndices>) -> EachIndex<int, FragmentIndex> { - range::each_index(range.begin().fragment_index, range.end().fragment_index) -} - -pub fn each_char_index(range: &Range<LineIndices>) -> EachIndex<int, CharIndex> { - range::each_index(range.begin().char_index, range.end().char_index) -} - -struct LineBreaker { - pub floats: Floats, - pub new_fragments: Vec<Fragment>, - pub work_list: RingBuf<Fragment>, - pub pending_line: Line, - pub lines: Vec<Line>, - 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.fragments = 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>, 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); - } -} - -/// Represents a list of inline fragments, including element ranges. -#[deriving(Encodable)] -pub struct InlineFragments { - /// The fragments themselves. - pub fragments: Vec<Fragment>, -} - -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) -> 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: &mut Fragment, style: Arc<ComputedValues>) { - fragment.add_inline_context_style(style); - self.fragments.push(fragment.clone()); - } - - /// Merges another set of inline fragments with this one. - pub fn push_all(&mut self, fragments: InlineFragments) { - self.fragments.push_all_move(fragments.fragments); - } - - /// 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) - } - - /// 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.fragments = 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.fragments = new_fragments; - } -} - -/// Flows for inline layout. -#[deriving(Encodable)] -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<Line>, - - /// 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 in self.fragments.fragments.mut_iter() { - let rel_offset = fragment.relative_position(&self.base - .absolute_position_info - .relative_containing_block_size); - 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)); - } - - // 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 _scope = layout_debug_scope!("inline::bubble_inline_sizes {:s}", 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_inline_sizes = IntrinsicISizes::new(); - for fragment in self.fragments.fragments.mut_iter() { - debug!("Flow: measuring {}", *fragment); - - let fragment_intrinsic_inline_sizes = - fragment.intrinsic_inline_sizes(); - 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) { - let _scope = layout_debug_scope!("inline::assign_inline_sizes {:s}", 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.position.size.inline; - let this = &mut *self; - for fragment in this.fragments.fragments.mut_iter() { - fragment.assign_replaced_inline_size_if_necessary(inline_size); - } - } - - 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) { - let _scope = layout_debug_scope!("inline::assign_block_size {:s}", self.base.debug_id()); - - // 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.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 = match self.lines.as_slice().last() { - Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block, - None => 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.fragments.iter().enumerate() { - if i == 0 { - try!(write!(f, ": {}", fragment)) - } else { - try!(write!(f, ", {}", fragment)) - } - } - Ok(()) - } -} - -#[deriving(Clone)] -pub struct InlineFragmentContext { - pub styles: Vec<Arc<ComputedValues>>, -} - -impl InlineFragmentContext { - pub fn new() -> InlineFragmentContext { - InlineFragmentContext { - styles: vec!() - } - } -} - -/// 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, - } - } -} - |