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.
This commit is contained in:
Patrick Walton 2015-03-30 14:09:37 -07:00
parent 88aa07b7e0
commit 6d61468160
12 changed files with 485 additions and 461 deletions

View file

@ -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)

View file

@ -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
}
}

View file

@ -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)
}
// 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);
// 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
}

View file

@ -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,
white_space::T::nowrap => NO_WRAP_INLINE_REFLOW_FLAG,
white_space::T::pre => {
WRAP_ON_NEWLINE_INLINE_REFLOW_FLAG | NO_WRAP_INLINE_REFLOW_FLAG
}
};
// Try to append the fragment, 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::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()
}
}
}
// 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));
}
}

View file

@ -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);
}

View file

@ -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:

View file

@ -151,6 +151,7 @@ flaky_cpu == append_style_a.html append_style_b.html
!= img_simple.html img_simple_ref.html
== img_size_a.html img_size_b.html
== incremental_float_a.html incremental_float_ref.html
== incremental_inline_layout_a.html incremental_inline_layout_ref.html
!= inline_background_a.html inline_background_ref.html
== inline_block_baseline_a.html inline_block_baseline_ref.html
== inline_block_border_a.html inline_block_border_ref.html
@ -184,6 +185,7 @@ flaky_cpu == append_style_a.html append_style_b.html
== legacy_td_bgcolor_attribute_a.html legacy_td_bgcolor_attribute_ref.html
== legacy_td_width_attribute_a.html legacy_td_width_attribute_ref.html
== letter_spacing_a.html letter_spacing_ref.html
== line_breaking_whitespace_collapse_a.html line_breaking_whitespace_collapse_ref.html
== line_height_a.html line_height_ref.html
!= linear_gradients_corners_a.html linear_gradients_corners_ref.html
== linear_gradients_lengths_a.html linear_gradients_lengths_ref.html

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<style>
h1 {
font-size: 72px;
}
</style>
</head>
<body>
<h1 id=h1>
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
</h1>
<script>
document.getElementById('h1').getBoundingClientRect(); // Trigger one layout.
document.getElementById('h1').getBoundingClientRect(); // Trigger another layout.
</script>
</body>
</html>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<style>
h1 {
font-size: 72px;
}
</style>
</head>
<body>
<h1 id=h1>
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
Incremental Inline Layout
</h1>
</body>
</html>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/ahem.css">
<style>
p {
font-size: 10px;
font-family: Ahem, monospace;
width: 300px;
}
a {
color: blue;
}
</style>
</head>
<body>
<p>xxxxx xxxxx <a>xxxxx xxx xxxxx</a> xxxxxxxxxxx</p>
</body>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/ahem.css">
<style>
p {
font-size: 10px;
font-family: Ahem, monospace;
width: 300px;
}
a {
color: blue;
}
</style>
</head>
<body>
<p>xxxxx xxxxx <a>xxxxx xxx xxxxx</a><br>xxxxxxxxxxx</p>
</body>

View file

@ -18,9 +18,8 @@ fn test_transform_compress_none() {
let mode = CompressionMode::CompressNone;
for &test in test_strs.iter() {
let mut new_line_pos = vec![];
let mut trimmed_str = String::new();
transform_text(test, mode, true, &mut trimmed_str, &mut new_line_pos);
transform_text(test, mode, true, &mut trimmed_str);
assert_eq!(trimmed_str, test)
}
}
@ -52,9 +51,8 @@ fn test_transform_discard_newline() {
let mode = CompressionMode::DiscardNewline;
for &(test, oracle) in test_strs.iter() {
let mut new_line_pos = vec![];
let mut trimmed_str = String::new();
transform_text(test, mode, true, &mut trimmed_str, &mut new_line_pos);
transform_text(test, mode, true, &mut trimmed_str);
assert_eq!(trimmed_str, oracle)
}
}
@ -86,9 +84,8 @@ fn test_transform_compress_whitespace() {
let mode = CompressionMode::CompressWhitespace;
for &(test, oracle) in test_strs.iter() {
let mut new_line_pos = vec![];
let mut trimmed_str = String::new();
transform_text(test, mode, true, &mut trimmed_str, &mut new_line_pos);
transform_text(test, mode, true, &mut trimmed_str);
assert_eq!(&*trimmed_str, oracle)
}
}
@ -120,9 +117,8 @@ fn test_transform_compress_whitespace_newline() {
let mode = CompressionMode::CompressWhitespaceNewline;
for &(test, oracle) in test_strs.iter() {
let mut new_line_pos = vec![];
let mut trimmed_str = String::new();
transform_text(test, mode, true, &mut trimmed_str, &mut new_line_pos);
transform_text(test, mode, true, &mut trimmed_str);
assert_eq!(&*trimmed_str, oracle)
}
}
@ -157,9 +153,8 @@ fn test_transform_compress_whitespace_newline_no_incoming() {
let mode = CompressionMode::CompressWhitespaceNewline;
for &(test, oracle) in test_strs.iter() {
let mut new_line_pos = vec![];
let mut trimmed_str = String::new();
transform_text(test, mode, false, &mut trimmed_str, &mut new_line_pos);
transform_text(test, mode, false, &mut trimmed_str);
assert_eq!(trimmed_str, oracle)
}
}