aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorPatrick Walton <pcwalton@mimiga.net>2015-03-30 14:09:37 -0700
committerPatrick Walton <pcwalton@mimiga.net>2015-04-08 14:29:23 -0700
commit6d6146816031a04d9e4597ad85c77266c6dd1b8f (patch)
tree2367a5484f2c9295949bb81c3433d22b7834b076 /components
parent88aa07b7e0076c249283cc33235d0380a093bc6e (diff)
downloadservo-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.rs4
-rw-r--r--components/gfx/text/util.rs17
-rw-r--r--components/layout/fragment.rs369
-rw-r--r--components/layout/inline.rs356
-rw-r--r--components/layout/text.rs78
-rw-r--r--components/util/str.rs7
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: