diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2015-03-30 14:09:37 -0700 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2015-04-08 14:29:23 -0700 |
commit | 6d6146816031a04d9e4597ad85c77266c6dd1b8f (patch) | |
tree | 2367a5484f2c9295949bb81c3433d22b7834b076 /components | |
parent | 88aa07b7e0076c249283cc33235d0380a093bc6e (diff) | |
download | servo-6d6146816031a04d9e4597ad85c77266c6dd1b8f.tar.gz servo-6d6146816031a04d9e4597ad85c77266c6dd1b8f.zip |
layout: Simplify and improve the correctness of whitespace stripping in
text layout, and unify the inline layout paths for pre- and
normally-formatted text.
This fixes a lot of "jumpiness" and removes the `new_line_pos` stuff.
Closes #2260.
Diffstat (limited to 'components')
-rw-r--r-- | components/gfx/text/text_run.rs | 4 | ||||
-rw-r--r-- | components/gfx/text/util.rs | 17 | ||||
-rw-r--r-- | components/layout/fragment.rs | 369 | ||||
-rw-r--r-- | components/layout/inline.rs | 356 | ||||
-rw-r--r-- | components/layout/text.rs | 78 | ||||
-rw-r--r-- | components/util/str.rs | 7 |
6 files changed, 380 insertions, 451 deletions
diff --git a/components/gfx/text/text_run.rs b/components/gfx/text/text_run.rs index 93c4c6c50b9..4e963bf1b28 100644 --- a/components/gfx/text/text_run.rs +++ b/components/gfx/text/text_run.rs @@ -16,6 +16,7 @@ use text::glyph::{CharIndex, GlyphStore}; /// A single "paragraph" of text in one font size and style. #[derive(Clone)] pub struct TextRun { + /// The UTF-8 string represented by this text run. pub text: Arc<String>, pub font_template: Arc<FontTemplateData>, pub actual_pt_size: Au, @@ -310,7 +311,8 @@ impl<'a> TextRun { self.font_metrics.descent) } - pub fn metrics_for_slice(&self, glyphs: &GlyphStore, slice_range: &Range<CharIndex>) -> RunMetrics { + pub fn metrics_for_slice(&self, glyphs: &GlyphStore, slice_range: &Range<CharIndex>) + -> RunMetrics { RunMetrics::new(glyphs.advance_for_char_range(slice_range), self.font_metrics.ascent, self.font_metrics.descent) diff --git a/components/gfx/text/util.rs b/components/gfx/text/util.rs index 890069b401c..95654af9ce5 100644 --- a/components/gfx/text/util.rs +++ b/components/gfx/text/util.rs @@ -2,8 +2,6 @@ * 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/. */ -use text::glyph::CharIndex; - #[derive(PartialEq, Eq, Copy)] pub enum CompressionMode { CompressNone, @@ -25,12 +23,10 @@ pub enum CompressionMode { pub fn transform_text(text: &str, mode: CompressionMode, incoming_whitespace: bool, - output_text: &mut String, - new_line_pos: &mut Vec<CharIndex>) + output_text: &mut String) -> bool { let out_whitespace = match mode { CompressionMode::CompressNone | CompressionMode::DiscardNewline => { - let mut new_line_index = CharIndex(0); for ch in text.chars() { if is_discardable_char(ch, mode) { // TODO: record skipped char @@ -38,15 +34,6 @@ pub fn transform_text(text: &str, // TODO: record kept char if ch == '\t' { // TODO: set "has tab" flag - } else if ch == '\n' { - // Save new-line's position for line-break - // This value is relative(not absolute) - new_line_pos.push(new_line_index); - new_line_index = CharIndex(0); - } - - if ch != '\n' { - new_line_index = new_line_index + CharIndex(1); } output_text.push(ch); } @@ -124,6 +111,6 @@ pub fn fixed_to_rounded_int(before: isize, f: i32) -> isize { if f > 0i32 { ((half + f) >> before) as isize } else { - -((half - f) >> before) as isize + -((half - f) >> before as usize) as isize } } diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 4c3d3bdc4c6..b766b27752e 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -27,16 +27,11 @@ use geom::{Point2D, Rect, Size2D}; use gfx::display_list::{BLUR_INFLATION_FACTOR, OpaqueNode}; use gfx::text::glyph::CharIndex; use gfx::text::text_run::{TextRun, TextRunSlice}; -use script_traits::UntrustedNodeAddress; -use rustc_serialize::{Encodable, Encoder}; use msg::constellation_msg::{ConstellationChan, Msg, PipelineId, SubpageId}; use net_traits::image::holder::ImageHolder; use net_traits::local_image_cache::LocalImageCache; -use util::geometry::{self, Au, ZERO_POINT}; -use util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin, WritingMode}; -use util::range::*; -use util::smallvec::SmallVec; -use util::str::is_whitespace; +use rustc_serialize::{Encodable, Encoder}; +use script_traits::UntrustedNodeAddress; use std::borrow::ToOwned; use std::cmp::{max, min}; use std::collections::LinkedList; @@ -56,6 +51,12 @@ use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto}; use style::values::computed::{LengthOrPercentageOrNone}; use text::TextRunScanner; use url::Url; +use util::geometry::{self, Au, ZERO_POINT}; +use util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin, WritingMode}; +use util::range::*; +use util::smallvec::SmallVec; +use util::str::is_whitespace; +use util; /// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position /// themselves. In general, fragments do not have a simple correspondence with CSS fragments in the @@ -580,36 +581,27 @@ pub struct ScannedTextFragmentInfo { /// The text run that this represents. pub run: Arc<Box<TextRun>>, + /// The intrinsic size of the text fragment. + pub content_size: LogicalSize<Au>, + /// The range within the above text run that this represents. pub range: Range<CharIndex>, - /// The positions of newlines within this scanned text fragment. - /// - /// FIXME(#2260, pcwalton): Can't this go somewhere else, like in the text run or something? - /// Or can we just remove it? - pub new_line_pos: Vec<CharIndex>, - - /// The new_line_pos is eaten during line breaking. If we need to re-merge - /// fragments, it will have to be restored. - pub original_new_line_pos: Option<Vec<CharIndex>>, - - /// The intrinsic size of the text fragment. - pub content_size: LogicalSize<Au>, + /// The endpoint of the above range, including whitespace that was stripped out. This exists + /// so that we can restore the range to its original value (before line breaking occurred) when + /// performing incremental reflow. + pub range_end_including_stripped_whitespace: CharIndex, } impl ScannedTextFragmentInfo { /// Creates the information specific to a scanned text fragment from a range and a text run. - pub fn new(run: Arc<Box<TextRun>>, - range: Range<CharIndex>, - new_line_positions: Vec<CharIndex>, - content_size: LogicalSize<Au>) + pub fn new(run: Arc<Box<TextRun>>, range: Range<CharIndex>, content_size: LogicalSize<Au>) -> ScannedTextFragmentInfo { ScannedTextFragmentInfo { run: run, range: range, - new_line_pos: new_line_positions, - original_new_line_pos: None, content_size: content_size, + range_end_including_stripped_whitespace: range.end(), } } } @@ -769,32 +761,6 @@ impl Fragment { self.margin = LogicalMargin::zero(self.style.writing_mode); } - /// Saves the new_line_pos vector into a `SpecificFragmentInfo::ScannedText`. This will fail - /// if called on any other type of fragment. - pub fn save_new_line_pos(&mut self) { - match &mut self.specific { - &mut SpecificFragmentInfo::ScannedText(ref mut info) => { - if !info.new_line_pos.is_empty() { - info.original_new_line_pos = Some(info.new_line_pos.clone()); - } - } - _ => {} - } - } - - pub fn restore_new_line_pos(&mut self) { - match &mut self.specific { - &mut SpecificFragmentInfo::ScannedText(ref mut info) => { - match info.original_new_line_pos.take() { - None => {} - Some(new_line_pos) => info.new_line_pos = new_line_pos, - } - return - } - _ => {} - } - } - /// Returns a debug ID of this fragment. This ID should not be considered stable across /// multiple layouts or fragment manipulations. pub fn debug_id(&self) -> u16 { @@ -823,14 +789,12 @@ impl Fragment { } /// Transforms this fragment using the given `SplitInfo`, preserving all the other data. - pub fn transform_with_split_info(&self, - split: &SplitInfo, - text_run: Arc<Box<TextRun>>) + pub fn transform_with_split_info(&self, split: &SplitInfo, text_run: Arc<Box<TextRun>>) -> Fragment { let size = LogicalSize::new(self.style.writing_mode, split.inline_size, self.border_box.size.block); - let info = box ScannedTextFragmentInfo::new(text_run, split.range, Vec::new(), size); + let info = box ScannedTextFragmentInfo::new(text_run, split.range, size); self.transform(size, SpecificFragmentInfo::ScannedText(info)) } @@ -857,7 +821,6 @@ impl Fragment { style: Arc<ComputedValues>, first_frag: bool, last_frag: bool) { - if self.inline_context.is_none() { self.inline_context = Some(InlineFragmentContext::new()); } @@ -1179,25 +1142,11 @@ impl Fragment { } } - /// Returns true if this element can be split. This is true for text fragments. + /// Returns true if this element can be split. This is true for text fragments, unless + /// `white-space: pre` is set. pub fn can_split(&self) -> bool { - self.is_scanned_text_fragment() - } - - /// Returns the newline positions of this fragment, if it's a scanned text fragment. - pub fn newline_positions(&self) -> Option<&Vec<CharIndex>> { - match self.specific { - SpecificFragmentInfo::ScannedText(ref info) => Some(&info.new_line_pos), - _ => None, - } - } - - /// Returns the newline positions of this fragment, if it's a scanned text fragment. - pub fn newline_positions_mut(&mut self) -> Option<&mut Vec<CharIndex>> { - match self.specific { - SpecificFragmentInfo::ScannedText(ref mut info) => Some(&mut info.new_line_pos), - _ => None, - } + self.is_scanned_text_fragment() && + self.style.get_inheritedtext().white_space != white_space::T::pre } /// Returns true if and only if this fragment is a generated content fragment. @@ -1359,64 +1308,6 @@ impl Fragment { self.border_box - self.border_padding } - /// Find the split of a fragment that includes a new-line character. - /// - /// A return value of `None` indicates that the fragment is not splittable. - /// Otherwise the split information is returned. The right information is - /// optional due to the possibility of it being whitespace. - // - // TODO(bjz): The text run should be removed in the future, but it is currently needed for - // the current method of fragment splitting in the `inline::try_append_*` functions. - pub fn find_split_info_by_new_line(&self) - -> Option<(SplitInfo, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> { - match self.specific { - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper => { - None - } - SpecificFragmentInfo::TableColumn(_) => { - panic!("Table column fragments do not need to split") - } - SpecificFragmentInfo::UnscannedText(_) => { - panic!("Unscanned text fragments should have been scanned by now!") - } - SpecificFragmentInfo::InlineBlock(_) | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => { - panic!("Inline blocks or inline absolute hypothetical fragments do not get split") - } - SpecificFragmentInfo::ScannedText(ref text_fragment_info) => { - let mut new_line_pos = text_fragment_info.new_line_pos.clone(); - let cur_new_line_pos = new_line_pos.remove(0); - - let inline_start_range = Range::new(text_fragment_info.range.begin(), - cur_new_line_pos); - let inline_end_range = Range::new( - text_fragment_info.range.begin() + cur_new_line_pos + CharIndex(1), - text_fragment_info.range.length() - (cur_new_line_pos + CharIndex(1))); - - // Left fragment is for inline-start text of first founded new-line character. - let inline_start_fragment = SplitInfo::new(inline_start_range, - &**text_fragment_info); - - // Right fragment is for inline-end text of first founded new-line character. - let inline_end_fragment = if inline_end_range.length() > CharIndex(0) { - Some(SplitInfo::new(inline_end_range, &**text_fragment_info)) - } else { - None - }; - - Some((inline_start_fragment, inline_end_fragment, text_fragment_info.run.clone())) - } - } - } - /// Attempts to find the split positions of a text fragment so that its inline-size is no more /// than `max_inline_size`. /// @@ -1495,13 +1386,13 @@ impl Fragment { /// A helper method that uses the breaking strategy described by `slice_iterator` (at present, /// either natural word breaking or character breaking) to split this fragment. - fn calculate_split_position_using_breaking_strategy<'a,I>(&self, - slice_iterator: I, - max_inline_size: Au, - flags: SplitOptions) - -> Option<SplitResult> - where I: Iterator<Item= - TextRunSlice<'a>> { + fn calculate_split_position_using_breaking_strategy<'a,I>( + &self, + slice_iterator: I, + max_inline_size: Au, + flags: SplitOptions) + -> Option<SplitResult> + where I: Iterator<Item=TextRunSlice<'a>> { let text_fragment_info = if let SpecificFragmentInfo::ScannedText(ref text_fragment_info) = self.specific { text_fragment_info @@ -1515,31 +1406,35 @@ impl Fragment { let mut inline_end_range = None; let mut overflowing = false; - debug!("calculate_split_position: splitting text fragment (strlen={}, range={:?}, \ - max_inline_size={:?})", + debug!("calculate_split_position_using_breaking_strategy: splitting text fragment \ + (strlen={}, range={:?}, max_inline_size={:?})", text_fragment_info.run.text.len(), text_fragment_info.range, max_inline_size); for slice in slice_iterator { - debug!("calculate_split_position: considering slice (offset={:?}, slice range={:?}, \ - remaining_inline_size={:?})", + debug!("calculate_split_position_using_breaking_strategy: considering slice \ + (offset={:?}, slice range={:?}, remaining_inline_size={:?})", slice.offset, slice.range, remaining_inline_size); + // Use the `remaining_inline_size` to find a split point if possible. If not, go around + // the loop again with the next slice. let metrics = text_fragment_info.run.metrics_for_slice(slice.glyphs, &slice.range); let advance = metrics.advance_width; // Have we found the split point? if advance <= remaining_inline_size || slice.glyphs.is_whitespace() { // Keep going; we haven't found the split point yet. - if flags.contains(STARTS_LINE) && pieces_processed_count == 0 && + if flags.contains(STARTS_LINE) && + pieces_processed_count == 0 && slice.glyphs.is_whitespace() { - debug!("calculate_split_position: skipping leading trimmable whitespace"); + debug!("calculate_split_position_using_breaking_strategy: skipping \ + leading trimmable whitespace"); inline_start_range.shift_by(slice.range.length()); } else { - debug!("split_to_inline_size: enlarging span"); + debug!("calculate_split_position_using_breaking_strategy: enlarging span"); remaining_inline_size = remaining_inline_size - advance; inline_start_range.extend_by(slice.range.length()); } @@ -1570,60 +1465,31 @@ impl Fragment { inline_end); } - break - } - - // If we failed to find a suitable split point, we're on the verge of overflowing the line. - if inline_start_range.is_empty() || overflowing { - // If we've been instructed to retry at character boundaries (probably via - // `overflow-wrap: break-word`), do so. - if flags.contains(RETRY_AT_CHARACTER_BOUNDARIES) { - let character_breaking_strategy = - text_fragment_info.run.character_slices_in_range(&text_fragment_info.range); - let mut flags = flags; - flags.remove(RETRY_AT_CHARACTER_BOUNDARIES); - return self.calculate_split_position_using_breaking_strategy( - character_breaking_strategy, - max_inline_size, - flags) - } - - // We aren't at the start of the line, so don't overflow. Let inline layout wrap to the - // next line instead. - if !flags.contains(STARTS_LINE) { - return None - } - } - - // Remove trailing whitespace from the inline-start split, if necessary. - // - // FIXME(pcwalton): Is there a more clever (i.e. faster) way to do this? - strip_trailing_whitespace(&**text_fragment_info.run, &mut inline_start_range); + // If we failed to find a suitable split point, we're on the verge of overflowing the + // line. + if inline_start_range.is_empty() || overflowing { + // If we've been instructed to retry at character boundaries (probably via + // `overflow-wrap: break-word`), do so. + if flags.contains(RETRY_AT_CHARACTER_BOUNDARIES) { + let character_breaking_strategy = + text_fragment_info.run + .character_slices_in_range(&text_fragment_info.range); + let mut flags = flags; + flags.remove(RETRY_AT_CHARACTER_BOUNDARIES); + return self.calculate_split_position_using_breaking_strategy( + character_breaking_strategy, + max_inline_size, + flags) + } - // Remove leading whitespace from the inline-end split, if necessary. - // - // FIXME(pcwalton): Is there a more clever (i.e. faster) way to do this? - if let Some(ref mut inline_end_range) = inline_end_range { - let inline_end_fragment_text = - text_fragment_info.run.text.slice_chars(inline_end_range.begin().to_usize(), - inline_end_range.end().to_usize()); - let mut leading_whitespace_character_count = 0; - for ch in inline_end_fragment_text.chars() { - if ch.is_whitespace() { - leading_whitespace_character_count += 1 - } else { - break + // We aren't at the start of the line, so don't overflow. Let inline layout wrap to + // the next line instead. + if !flags.contains(STARTS_LINE) { + return None } } - inline_end_range.adjust_by(CharIndex(leading_whitespace_character_count), - -CharIndex(leading_whitespace_character_count)); - } - // Normalize our split so that the inline-end fragment will never be `Some` while the - // inline-start fragment is `None`. - if inline_start_range.is_empty() && inline_end_range.is_some() { - inline_start_range = inline_end_range.unwrap(); - inline_end_range = None + break } let inline_start = if !inline_start_range.is_empty() { @@ -1642,22 +1508,21 @@ impl Fragment { }) } - /// Attempts to strip trailing whitespace from this fragment by adjusting the text run range. - /// Returns true if any modifications were made. - pub fn strip_trailing_whitespace_if_necessary(&mut self) -> bool { - let text_fragment_info = - if let SpecificFragmentInfo::ScannedText(ref mut text_fragment_info) = self.specific { - text_fragment_info - } else { - return false - }; - - let run = text_fragment_info.run.clone(); - if strip_trailing_whitespace(&**run, &mut text_fragment_info.range) { - self.border_box.size.inline = run.advance_for_range(&text_fragment_info.range); - return true + /// The opposite of `calculate_split_position_using_breaking_strategy`: merges this fragment + /// with the next one. + pub fn merge_with(&mut self, next_fragment: Fragment) { + match (&mut self.specific, &next_fragment.specific) { + (&mut SpecificFragmentInfo::ScannedText(ref mut this_info), + &SpecificFragmentInfo::ScannedText(ref other_info)) => { + debug_assert!(util::arc_ptr_eq(&this_info.run, &other_info.run)); + this_info.range.extend_to(other_info.range_end_including_stripped_whitespace); + this_info.content_size.inline = + this_info.run.metrics_for_range(&this_info.range).advance_width; + self.border_box.size.inline = this_info.content_size.inline + + self.border_padding.inline_start_end(); + } + _ => panic!("Can only merge two scanned-text fragments!"), } - false } /// Returns true if this fragment is an unscanned text fragment that consists entirely of @@ -1669,7 +1534,7 @@ impl Fragment { } match self.specific { SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => { - is_whitespace(text_fragment_info.text.as_slice()) + util::str::is_whitespace(text_fragment_info.text.as_slice()) } _ => false, } @@ -1890,11 +1755,14 @@ impl Fragment { /// Returns true if this fragment can merge with another adjacent fragment or false otherwise. pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool { match (&self.specific, &other.specific) { - (&SpecificFragmentInfo::UnscannedText(_), &SpecificFragmentInfo::UnscannedText(_)) => { + (&SpecificFragmentInfo::UnscannedText(ref first_unscanned_text), + &SpecificFragmentInfo::UnscannedText(_)) => { // FIXME: Should probably use a whitelist of styles that can safely differ (#3165) + let length = first_unscanned_text.text.len(); self.style().get_font() == other.style().get_font() && self.text_decoration() == other.text_decoration() && - self.white_space() == other.white_space() + self.white_space() == other.white_space() && + (length == 0 || first_unscanned_text.text.char_at_reverse(length) != '\n') } _ => false, } @@ -2076,14 +1944,57 @@ impl Fragment { _ => {} } } + + pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool { + match self.specific { + SpecificFragmentInfo::ScannedText(ref scanned_text) => { + !scanned_text.range.is_empty() && + scanned_text.run.text.char_at_reverse(scanned_text.range + .end() + .get() as usize) == '\n' + } + _ => false, + } + } + + pub fn strip_leading_whitespace_if_necessary(&mut self) { + let mut scanned_text_fragment_info = match self.specific { + SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => { + scanned_text_fragment_info + } + _ => return, + }; + + if self.style.get_inheritedtext().white_space == white_space::T::pre { + return + } + + let mut leading_whitespace_character_count = 0; + { + let text = scanned_text_fragment_info.run.text.slice_chars( + scanned_text_fragment_info.range.begin().to_usize(), + scanned_text_fragment_info.range.end().to_usize()); + for character in text.chars() { + if util::str::char_is_whitespace(character) { + leading_whitespace_character_count += 1 + } else { + break + } + } + } + + scanned_text_fragment_info.range.adjust_by(CharIndex(leading_whitespace_character_count), + -CharIndex(leading_whitespace_character_count)); + } } impl fmt::Debug for Fragment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(write!(f, "({} {} ", self.debug_id(), self.specific.get_type())); - try!(write!(f, "bp {:?}", self.border_padding)); - try!(write!(f, " ")); - try!(write!(f, "m {:?}", self.margin)); + try!(write!(f, "bb {:?} bp {:?} m {:?}", + self.border_box, + self.border_padding, + self.margin)); write!(f, ")") } } @@ -2105,7 +2016,7 @@ bitflags! { const STARTS_LINE = 0x01, #[doc="True if we should attempt to split at character boundaries if this split fails. \ This is used to implement `overflow-wrap: break-word`."] - const RETRY_AT_CHARACTER_BOUNDARIES = 0x02 + const RETRY_AT_CHARACTER_BOUNDARIES = 0x02, } } @@ -2129,25 +2040,3 @@ pub enum CoordinateSystem { Own, } -/// Given a range and a text run, adjusts the range to eliminate trailing whitespace. Returns true -/// if any modifications were made. -fn strip_trailing_whitespace(text_run: &TextRun, range: &mut Range<CharIndex>) -> bool { - // FIXME(pcwalton): Is there a more clever (i.e. faster) way to do this? - 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 ch.is_whitespace() { - trailing_whitespace_character_count += 1 - } else { - break - } - } - - if trailing_whitespace_character_count == 0 { - return false - } - - range.extend_by(-CharIndex(trailing_whitespace_character_count)); - return true -} - diff --git a/components/layout/inline.rs b/components/layout/inline.rs index 2c2625225b2..1f9e59dba2e 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -8,11 +8,9 @@ use css::node_style::StyledNode; use context::LayoutContext; use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding}; use floats::{FloatKind, Floats, PlacementInfo}; -use flow::{BaseFlow, FlowClass, Flow, MutableFlowUtils, ForceNonfloatedFlag}; +use flow::{self, BaseFlow, FlowClass, Flow, MutableFlowUtils, ForceNonfloatedFlag}; use flow::{IS_ABSOLUTELY_POSITIONED}; -use flow; -use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, ScannedTextFragmentInfo}; -use fragment::{SpecificFragmentInfo, SplitInfo}; +use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo}; use incremental::{REFLOW, REFLOW_OUT_OF_FLOW, RESOLVE_GENERATED_CONTENT}; use layout_debug; use model::IntrinsicISizesContribution; @@ -23,20 +21,21 @@ use geom::{Point2D, Rect}; use gfx::font::FontMetrics; use gfx::font_context::FontContext; use gfx::text::glyph::CharIndex; -use util::arc_ptr_eq; -use util::geometry::{Au, ZERO_RECT}; -use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; -use util::range::{Range, RangeIndex}; +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::{overflow_x, text_align, text_justify, text_overflow, vertical_align}; use style::computed_values::{white_space}; use style::properties::ComputedValues; -use std::sync::Arc; +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; @@ -160,7 +159,9 @@ int_range_index! { 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 + 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 } } @@ -191,7 +192,7 @@ impl LineBreaker { pending_line: Line { range: Range::empty(), bounds: LogicalRect::zero(float_context.writing_mode), - green_zone: LogicalSize::zero(float_context.writing_mode) + green_zone: LogicalSize::zero(float_context.writing_mode), }, floats: float_context, lines: Vec::new(), @@ -216,7 +217,7 @@ impl LineBreaker { self.cur_b, Au(0), Au(0)); - self.pending_line.green_zone = LogicalSize::zero(self.floats.writing_mode) + self.pending_line.green_zone = LogicalSize::zero(self.floats.writing_mode); } /// Reflows fragments for the given inline flow. @@ -226,23 +227,13 @@ impl LineBreaker { // 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 mut old_fragment_iter = old_fragments.fragments.into_iter(); + let old_fragment_iter = old_fragments.fragments.into_iter(); - // Set up our initial line state with the clean lines from a previous reflow. - // // 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 = mem::replace(&mut flow.lines, Vec::new()); - match self.lines.as_slice().last() { - None => {} - Some(last_line) => { - for _ in range(FragmentIndex(0), last_line.range.end()) { - self.new_fragments.push(old_fragment_iter.next().unwrap()) - } - } - } + self.lines = Vec::new(); // Do the reflow. self.reflow_fragments(old_fragment_iter, flow, layout_context); @@ -270,30 +261,14 @@ impl LineBreaker { // Set up our reflow flags. let flags = match fragment.style().get_inheritedtext().white_space { white_space::T::normal => InlineReflowFlags::empty(), - white_space::T::pre | white_space::T::nowrap => NO_WRAP_INLINE_REFLOW_FLAG, - }; - - // Try to append the fragment, and commit the line (so we can try again with the next - // line) if we couldn't. - match fragment.style().get_inheritedtext().white_space { - white_space::T::normal | white_space::T::nowrap => { - if !self.append_fragment_to_line_if_possible(fragment, - flow, - layout_context, - flags) { - self.flush_current_line() - } - } + white_space::T::nowrap => NO_WRAP_INLINE_REFLOW_FLAG, white_space::T::pre => { - // FIXME(pcwalton): Surely we can unify - // `append_fragment_to_line_if_possible` and - // `try_append_to_line_by_new_line` by adding another bit in the reflow - // flags. - if !self.try_append_to_line_by_new_line(layout_context, fragment) { - self.flush_current_line() - } + 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() { @@ -301,37 +276,25 @@ impl LineBreaker { self.lines.len()); self.flush_current_line() } - - // Strip trailing whitespace from the last line if necessary. - if let Some(ref mut last_line) = self.lines.last_mut() { - if let Some(ref mut last_fragment) = self.new_fragments.last_mut() { - let previous_inline_size = last_line.bounds.size.inline - - last_fragment.border_box.size.inline; - if last_fragment.strip_trailing_whitespace_if_necessary() { - last_line.bounds.size.inline = previous_inline_size + - last_fragment.border_box.size.inline; - } - } - } } /// Acquires a new fragment to lay out from the work list or fragment list as appropriate. - /// Note that you probably don't want to call this method directly in order to be - /// incremental-reflow-safe; try `next_unbroken_fragment` instead. + /// 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<I>(&mut self, old_fragment_iter: &mut I) -> Option<Fragment> where I: Iterator<Item=Fragment> { + let mut fragment; if self.work_list.is_empty() { - return match old_fragment_iter.next() { - None => None, - Some(fragment) => { - debug!("LineBreaker: working with fragment from flow: {:?}", fragment); - Some(fragment) - } + match old_fragment_iter.next() { + None => return None, + Some(this_fragment) => fragment = this_fragment, } + } else { + return self.work_list.pop_front() } - debug!("LineBreaker: working with fragment from work list: {:?}", self.work_list.front()); - 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 @@ -346,10 +309,6 @@ impl LineBreaker { }; loop { - // FIXME(pcwalton): Yuck! I hate this `new_line_pos` stuff. Can we avoid having to do - // this? - result.restore_new_line_pos(); - let candidate = match self.next_fragment(old_fragment_iter) { None => return Some(result), Some(fragment) => fragment, @@ -357,31 +316,58 @@ impl LineBreaker { let need_to_merge = match (&mut result.specific, &candidate.specific) { (&mut SpecificFragmentInfo::ScannedText(ref mut result_info), - &SpecificFragmentInfo::ScannedText(ref candidate_info)) - if arc_ptr_eq(&result_info.run, &candidate_info.run) && - result_info.range.end() + CharIndex(1) == candidate_info.range.begin() => { - // We found a previously-broken fragment. Merge it up. - result_info.range.extend_by(candidate_info.range.length() + CharIndex(1)); - true + &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 { - self.work_list.push_front(candidate); - return Some(result) + + 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_block_size_for_line(&self, new_fragment: &Fragment, layout_context: &LayoutContext) @@ -488,71 +474,16 @@ impl LineBreaker { false } - /// Tries to append the given fragment to the line for `pre`-formatted text, splitting it if - /// necessary. Returns true if we successfully pushed the fragment to the line or false if we - /// couldn't. - fn try_append_to_line_by_new_line(&mut self, - layout_context: &LayoutContext, - in_fragment: Fragment) - -> bool { - let should_push = match in_fragment.newline_positions() { - None => true, - Some(ref positions) => positions.is_empty(), - }; - if should_push { - debug!("LineBreaker: did not find a newline character; pushing the fragment to \ - the line without splitting"); - self.push_fragment_to_line(layout_context, in_fragment); - return true - } - - debug!("LineBreaker: Found a new-line character, so splitting the line."); - - 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; - - let split_fragment = |split: SplitInfo| { - let size = LogicalSize::new(writing_mode, - split.inline_size, - in_fragment.border_box.size.block); - let info = box ScannedTextFragmentInfo::new(run.clone(), - split.range, - (*in_fragment.newline_positions() - .unwrap()).clone(), - size); - in_fragment.transform(size, SpecificFragmentInfo::ScannedText(info)) - }; - - 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.save_new_line_pos(); - *inline_start.newline_positions_mut().unwrap() = vec![]; - self.push_fragment_to_line(layout_context, inline_start); - - for inline_end in inline_end.into_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.newline_positions_mut().unwrap().remove(0); - self.work_list.push_front(inline_end); - } - - false - } - - /// Tries to append the given fragment to the line, splitting it if necessary. Returns true if - /// we successfully pushed the fragment to the line or false if we couldn't. - fn append_fragment_to_line_if_possible(&mut self, - fragment: Fragment, - flow: &InlineFlow, - layout_context: &LayoutContext, - flags: InlineReflowFlags) - -> bool { + /// 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; @@ -572,9 +503,22 @@ impl LineBreaker { 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! - return self.avoid_floats(flow, fragment, new_block_size) + 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. @@ -583,23 +527,27 @@ impl LineBreaker { 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); - return true + 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(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); - return false + self.push_fragment_to_line(layout_context, fragment, LineFlushMode::No); + return } // Split it up! - let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline - - indentation; + 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, @@ -607,7 +555,8 @@ impl LineBreaker { None => { debug!("LineBreaker: fragment was unsplittable; deferring to next line"); self.work_list.push_front(fragment); - return false + self.flush_current_line(); + return } Some(split_result) => split_result, }; @@ -623,23 +572,30 @@ impl LineBreaker { // 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); - self.flush_current_line(); + 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); + 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, Some(_)) => debug_assert!(false, "un-normalized split result"), (None, None) => {} } - - true } /// Pushes a fragment to the current line unconditionally, possibly truncating it and placing - /// an ellipsis based on the value of `text-overflow`. - fn push_fragment_to_line(&mut self, layout_context: &LayoutContext, fragment: Fragment) { + /// 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)); @@ -661,25 +617,27 @@ impl LineBreaker { if !need_ellipsis { self.push_fragment_to_line_ignoring_text_overflow(fragment); - return + } 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); + } + self.push_fragment_to_line_ignoring_text_overflow(ellipsis); } - 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); + if line_flush_mode == LineFlushMode::Flush { + self.flush_current_line() } - self.push_fragment_to_line_ignoring_text_overflow(ellipsis); } /// 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) { 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 + @@ -813,7 +771,7 @@ impl InlineFlow { /// /// 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. + /// `bottom` value for `vertical-align`, true is returned. fn distance_from_baseline(fragment: &Fragment, ascent: Au, parent_text_block_start: Au, @@ -1180,7 +1138,7 @@ impl Flow for InlineFlow { // Reset our state, so that we handle incremental reflow correctly. // // TODO(pcwalton): Do something smarter, like Gecko and WebKit? - self.lines = Vec::new(); + self.lines.clear(); // Determine how much indentation the first line wants. let mut indentation = if self.fragments.is_empty() { @@ -1431,6 +1389,30 @@ impl 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<InlineFragmentContext>, + inline_context_b: &Option<InlineFragmentContext>) + -> 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 @@ -1464,3 +1446,31 @@ impl InlineMetrics { } } } + +#[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<CharIndex>) { + // 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)); + } +} + diff --git a/components/layout/text.rs b/components/layout/text.rs index 6380a4fe6ce..0844eadb359 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -6,7 +6,7 @@ #![deny(unsafe_code)] -use fragment::{Fragment, SpecificFragmentInfo, ScannedTextFragmentInfo}; +use fragment::{Fragment, SpecificFragmentInfo, ScannedTextFragmentInfo, UnscannedTextFragmentInfo}; use inline::InlineFragments; use gfx::font::{DISABLE_KERNING_SHAPING_FLAG, FontMetrics, IGNORE_LIGATURES_SHAPING_FLAG}; @@ -15,18 +15,19 @@ use gfx::font_context::FontContext; use gfx::text::glyph::CharIndex; use gfx::text::text_run::TextRun; use gfx::text::util::{self, CompressionMode}; -use util::linked_list::split_off_head; -use util::geometry::Au; -use util::logical_geometry::{LogicalSize, WritingMode}; -use util::range::Range; -use util::smallvec::{SmallVec, SmallVec1}; +use std::borrow::ToOwned; use std::collections::LinkedList; use std::mem; +use std::sync::Arc; use style::computed_values::{line_height, text_orientation, text_rendering, text_transform}; use style::computed_values::{white_space}; use style::properties::ComputedValues; use style::properties::style_structs::Font as FontStyle; -use std::sync::Arc; +use util::geometry::Au; +use util::linked_list::split_off_head; +use util::logical_geometry::{LogicalSize, WritingMode}; +use util::range::Range; +use util::smallvec::{SmallVec, SmallVec1}; /// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s. pub struct TextRunScanner { @@ -40,7 +41,9 @@ impl TextRunScanner { } } - pub fn scan_for_runs(&mut self, font_context: &mut FontContext, mut fragments: LinkedList<Fragment>) + pub fn scan_for_runs(&mut self, + font_context: &mut FontContext, + mut fragments: LinkedList<Fragment>) -> InlineFragments { debug!("TextRunScanner: scanning {} fragments for text runs...", fragments.len()); @@ -50,12 +53,14 @@ impl TextRunScanner { let mut last_whitespace = true; while !fragments.is_empty() { // Create a clump. + split_first_fragment_at_newline_if_necessary(&mut fragments); self.clump.append(&mut split_off_head(&mut fragments)); while !fragments.is_empty() && self.clump .back() .unwrap() .can_merge_with_fragment(fragments.front() .unwrap()) { + split_first_fragment_at_newline_if_necessary(&mut fragments); self.clump.append(&mut split_off_head(&mut fragments)); } @@ -101,7 +106,6 @@ impl TextRunScanner { // // Concatenate all of the transformed strings together, saving the new character indices. let mut new_ranges: SmallVec1<Range<CharIndex>> = SmallVec1::new(); - let mut new_line_positions: SmallVec1<NewLinePositions> = SmallVec1::new(); let mut char_total = CharIndex(0); let run = { let fontgroup; @@ -137,14 +141,11 @@ impl TextRunScanner { _ => panic!("Expected an unscanned text fragment!"), }; - let mut new_line_pos = Vec::new(); let old_length = CharIndex(run_text.chars().count() as isize); last_whitespace = util::transform_text(in_fragment.as_slice(), compression, last_whitespace, - &mut run_text, - &mut new_line_pos); - new_line_positions.push(NewLinePositions(new_line_pos)); + &mut run_text); let added_chars = CharIndex(run_text.chars().count() as isize) - old_length; new_ranges.push(Range::new(char_total, added_chars)); @@ -200,13 +201,8 @@ impl TextRunScanner { } let text_size = old_fragment.border_box.size; - let &mut NewLinePositions(ref mut new_line_positions) = - new_line_positions.get_mut(logical_offset); let mut new_text_fragment_info = - box ScannedTextFragmentInfo::new(run.clone(), - range, - mem::replace(new_line_positions, Vec::new()), - text_size); + box ScannedTextFragmentInfo::new(run.clone(), range, text_size); let new_metrics = new_text_fragment_info.run.metrics_for_range(&range); let bounding_box_size = bounding_box_for_run_metrics(&new_metrics, old_fragment.style.writing_mode); @@ -270,8 +266,6 @@ impl TextRunScanner { } } -struct NewLinePositions(Vec<CharIndex>); - #[inline] fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode) -> LogicalSize<Au> { @@ -318,3 +312,45 @@ pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> line_height::T::Length(l) => l } } + +fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragment>) { + if fragments.len() < 1 { + return + } + + let new_fragment = { + let mut first_fragment = fragments.front_mut().unwrap(); + let string_before; + { + let unscanned_text_fragment_info = match first_fragment.specific { + SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => { + unscanned_text_fragment_info + } + _ => return, + }; + + if first_fragment.style.get_inheritedtext().white_space != white_space::T::pre { + return + } + + let position = match unscanned_text_fragment_info.text.find('\n') { + Some(position) if position < unscanned_text_fragment_info.text.len() - 1 => { + position + } + Some(_) | None => return, + }; + + string_before = + box unscanned_text_fragment_info.text[..(position + 1)].to_owned(); + unscanned_text_fragment_info.text = + box unscanned_text_fragment_info.text[(position + 1)..].to_owned(); + } + first_fragment.transform(first_fragment.border_box.size, + SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo { + text: string_before, + })) + }; + + fragments.push_front(new_fragment); +} + diff --git a/components/util/str.rs b/components/util/str.rs index e763215633f..a400edb9572 100644 --- a/components/util/str.rs +++ b/components/util/str.rs @@ -39,7 +39,12 @@ pub fn null_str_as_empty_ref<'a>(s: &'a Option<DOMString>) -> &'a str { const WHITESPACE: &'static [char] = &[' ', '\t', '\x0a', '\x0c', '\x0d']; pub fn is_whitespace(s: &str) -> bool { - s.chars().all(|c| WHITESPACE.contains(&c)) + s.chars().all(char_is_whitespace) +} + +#[inline] +pub fn char_is_whitespace(c: char) -> bool { + WHITESPACE.contains(&c) } /// A "space character" according to: |