mirror of
https://github.com/servo/servo.git
synced 2025-08-04 21:20:23 +01:00
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:
parent
88aa07b7e0
commit
6d61468160
12 changed files with 485 additions and 461 deletions
|
@ -16,6 +16,7 @@ use text::glyph::{CharIndex, GlyphStore};
|
||||||
/// A single "paragraph" of text in one font size and style.
|
/// A single "paragraph" of text in one font size and style.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TextRun {
|
pub struct TextRun {
|
||||||
|
/// The UTF-8 string represented by this text run.
|
||||||
pub text: Arc<String>,
|
pub text: Arc<String>,
|
||||||
pub font_template: Arc<FontTemplateData>,
|
pub font_template: Arc<FontTemplateData>,
|
||||||
pub actual_pt_size: Au,
|
pub actual_pt_size: Au,
|
||||||
|
@ -310,7 +311,8 @@ impl<'a> TextRun {
|
||||||
self.font_metrics.descent)
|
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),
|
RunMetrics::new(glyphs.advance_for_char_range(slice_range),
|
||||||
self.font_metrics.ascent,
|
self.font_metrics.ascent,
|
||||||
self.font_metrics.descent)
|
self.font_metrics.descent)
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use text::glyph::CharIndex;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy)]
|
#[derive(PartialEq, Eq, Copy)]
|
||||||
pub enum CompressionMode {
|
pub enum CompressionMode {
|
||||||
CompressNone,
|
CompressNone,
|
||||||
|
@ -25,12 +23,10 @@ pub enum CompressionMode {
|
||||||
pub fn transform_text(text: &str,
|
pub fn transform_text(text: &str,
|
||||||
mode: CompressionMode,
|
mode: CompressionMode,
|
||||||
incoming_whitespace: bool,
|
incoming_whitespace: bool,
|
||||||
output_text: &mut String,
|
output_text: &mut String)
|
||||||
new_line_pos: &mut Vec<CharIndex>)
|
|
||||||
-> bool {
|
-> bool {
|
||||||
let out_whitespace = match mode {
|
let out_whitespace = match mode {
|
||||||
CompressionMode::CompressNone | CompressionMode::DiscardNewline => {
|
CompressionMode::CompressNone | CompressionMode::DiscardNewline => {
|
||||||
let mut new_line_index = CharIndex(0);
|
|
||||||
for ch in text.chars() {
|
for ch in text.chars() {
|
||||||
if is_discardable_char(ch, mode) {
|
if is_discardable_char(ch, mode) {
|
||||||
// TODO: record skipped char
|
// TODO: record skipped char
|
||||||
|
@ -38,15 +34,6 @@ pub fn transform_text(text: &str,
|
||||||
// TODO: record kept char
|
// TODO: record kept char
|
||||||
if ch == '\t' {
|
if ch == '\t' {
|
||||||
// TODO: set "has tab" flag
|
// 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);
|
output_text.push(ch);
|
||||||
}
|
}
|
||||||
|
@ -124,6 +111,6 @@ pub fn fixed_to_rounded_int(before: isize, f: i32) -> isize {
|
||||||
if f > 0i32 {
|
if f > 0i32 {
|
||||||
((half + f) >> before) as isize
|
((half + f) >> before) as isize
|
||||||
} else {
|
} else {
|
||||||
-((half - f) >> before) as isize
|
-((half - f) >> before as usize) as isize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,11 @@ use geom::{Point2D, Rect, Size2D};
|
||||||
use gfx::display_list::{BLUR_INFLATION_FACTOR, OpaqueNode};
|
use gfx::display_list::{BLUR_INFLATION_FACTOR, OpaqueNode};
|
||||||
use gfx::text::glyph::CharIndex;
|
use gfx::text::glyph::CharIndex;
|
||||||
use gfx::text::text_run::{TextRun, TextRunSlice};
|
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 msg::constellation_msg::{ConstellationChan, Msg, PipelineId, SubpageId};
|
||||||
use net_traits::image::holder::ImageHolder;
|
use net_traits::image::holder::ImageHolder;
|
||||||
use net_traits::local_image_cache::LocalImageCache;
|
use net_traits::local_image_cache::LocalImageCache;
|
||||||
use util::geometry::{self, Au, ZERO_POINT};
|
use rustc_serialize::{Encodable, Encoder};
|
||||||
use util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin, WritingMode};
|
use script_traits::UntrustedNodeAddress;
|
||||||
use util::range::*;
|
|
||||||
use util::smallvec::SmallVec;
|
|
||||||
use util::str::is_whitespace;
|
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
|
@ -56,6 +51,12 @@ use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto};
|
||||||
use style::values::computed::{LengthOrPercentageOrNone};
|
use style::values::computed::{LengthOrPercentageOrNone};
|
||||||
use text::TextRunScanner;
|
use text::TextRunScanner;
|
||||||
use url::Url;
|
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
|
/// 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
|
/// 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.
|
/// The text run that this represents.
|
||||||
pub run: Arc<Box<TextRun>>,
|
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.
|
/// The range within the above text run that this represents.
|
||||||
pub range: Range<CharIndex>,
|
pub range: Range<CharIndex>,
|
||||||
|
|
||||||
/// The positions of newlines within this scanned text fragment.
|
/// 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
|
||||||
/// FIXME(#2260, pcwalton): Can't this go somewhere else, like in the text run or something?
|
/// performing incremental reflow.
|
||||||
/// Or can we just remove it?
|
pub range_end_including_stripped_whitespace: CharIndex,
|
||||||
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>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScannedTextFragmentInfo {
|
impl ScannedTextFragmentInfo {
|
||||||
/// Creates the information specific to a scanned text fragment from a range and a text run.
|
/// Creates the information specific to a scanned text fragment from a range and a text run.
|
||||||
pub fn new(run: Arc<Box<TextRun>>,
|
pub fn new(run: Arc<Box<TextRun>>, range: Range<CharIndex>, content_size: LogicalSize<Au>)
|
||||||
range: Range<CharIndex>,
|
|
||||||
new_line_positions: Vec<CharIndex>,
|
|
||||||
content_size: LogicalSize<Au>)
|
|
||||||
-> ScannedTextFragmentInfo {
|
-> ScannedTextFragmentInfo {
|
||||||
ScannedTextFragmentInfo {
|
ScannedTextFragmentInfo {
|
||||||
run: run,
|
run: run,
|
||||||
range: range,
|
range: range,
|
||||||
new_line_pos: new_line_positions,
|
|
||||||
original_new_line_pos: None,
|
|
||||||
content_size: content_size,
|
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);
|
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
|
/// Returns a debug ID of this fragment. This ID should not be considered stable across
|
||||||
/// multiple layouts or fragment manipulations.
|
/// multiple layouts or fragment manipulations.
|
||||||
pub fn debug_id(&self) -> u16 {
|
pub fn debug_id(&self) -> u16 {
|
||||||
|
@ -823,14 +789,12 @@ impl Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms this fragment using the given `SplitInfo`, preserving all the other data.
|
/// Transforms this fragment using the given `SplitInfo`, preserving all the other data.
|
||||||
pub fn transform_with_split_info(&self,
|
pub fn transform_with_split_info(&self, split: &SplitInfo, text_run: Arc<Box<TextRun>>)
|
||||||
split: &SplitInfo,
|
|
||||||
text_run: Arc<Box<TextRun>>)
|
|
||||||
-> Fragment {
|
-> Fragment {
|
||||||
let size = LogicalSize::new(self.style.writing_mode,
|
let size = LogicalSize::new(self.style.writing_mode,
|
||||||
split.inline_size,
|
split.inline_size,
|
||||||
self.border_box.size.block);
|
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))
|
self.transform(size, SpecificFragmentInfo::ScannedText(info))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,7 +821,6 @@ impl Fragment {
|
||||||
style: Arc<ComputedValues>,
|
style: Arc<ComputedValues>,
|
||||||
first_frag: bool,
|
first_frag: bool,
|
||||||
last_frag: bool) {
|
last_frag: bool) {
|
||||||
|
|
||||||
if self.inline_context.is_none() {
|
if self.inline_context.is_none() {
|
||||||
self.inline_context = Some(InlineFragmentContext::new());
|
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 {
|
pub fn can_split(&self) -> bool {
|
||||||
self.is_scanned_text_fragment()
|
self.is_scanned_text_fragment() &&
|
||||||
}
|
self.style.get_inheritedtext().white_space != white_space::T::pre
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this fragment is a generated content fragment.
|
/// 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
|
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
|
/// Attempts to find the split positions of a text fragment so that its inline-size is no more
|
||||||
/// than `max_inline_size`.
|
/// than `max_inline_size`.
|
||||||
///
|
///
|
||||||
|
@ -1495,13 +1386,13 @@ impl Fragment {
|
||||||
|
|
||||||
/// A helper method that uses the breaking strategy described by `slice_iterator` (at present,
|
/// 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.
|
/// either natural word breaking or character breaking) to split this fragment.
|
||||||
fn calculate_split_position_using_breaking_strategy<'a,I>(&self,
|
fn calculate_split_position_using_breaking_strategy<'a,I>(
|
||||||
|
&self,
|
||||||
slice_iterator: I,
|
slice_iterator: I,
|
||||||
max_inline_size: Au,
|
max_inline_size: Au,
|
||||||
flags: SplitOptions)
|
flags: SplitOptions)
|
||||||
-> Option<SplitResult>
|
-> Option<SplitResult>
|
||||||
where I: Iterator<Item=
|
where I: Iterator<Item=TextRunSlice<'a>> {
|
||||||
TextRunSlice<'a>> {
|
|
||||||
let text_fragment_info =
|
let text_fragment_info =
|
||||||
if let SpecificFragmentInfo::ScannedText(ref text_fragment_info) = self.specific {
|
if let SpecificFragmentInfo::ScannedText(ref text_fragment_info) = self.specific {
|
||||||
text_fragment_info
|
text_fragment_info
|
||||||
|
@ -1515,31 +1406,35 @@ impl Fragment {
|
||||||
let mut inline_end_range = None;
|
let mut inline_end_range = None;
|
||||||
let mut overflowing = false;
|
let mut overflowing = false;
|
||||||
|
|
||||||
debug!("calculate_split_position: splitting text fragment (strlen={}, range={:?}, \
|
debug!("calculate_split_position_using_breaking_strategy: splitting text fragment \
|
||||||
max_inline_size={:?})",
|
(strlen={}, range={:?}, max_inline_size={:?})",
|
||||||
text_fragment_info.run.text.len(),
|
text_fragment_info.run.text.len(),
|
||||||
text_fragment_info.range,
|
text_fragment_info.range,
|
||||||
max_inline_size);
|
max_inline_size);
|
||||||
|
|
||||||
for slice in slice_iterator {
|
for slice in slice_iterator {
|
||||||
debug!("calculate_split_position: considering slice (offset={:?}, slice range={:?}, \
|
debug!("calculate_split_position_using_breaking_strategy: considering slice \
|
||||||
remaining_inline_size={:?})",
|
(offset={:?}, slice range={:?}, remaining_inline_size={:?})",
|
||||||
slice.offset,
|
slice.offset,
|
||||||
slice.range,
|
slice.range,
|
||||||
remaining_inline_size);
|
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 metrics = text_fragment_info.run.metrics_for_slice(slice.glyphs, &slice.range);
|
||||||
let advance = metrics.advance_width;
|
let advance = metrics.advance_width;
|
||||||
|
|
||||||
// Have we found the split point?
|
// Have we found the split point?
|
||||||
if advance <= remaining_inline_size || slice.glyphs.is_whitespace() {
|
if advance <= remaining_inline_size || slice.glyphs.is_whitespace() {
|
||||||
// Keep going; we haven't found the split point yet.
|
// 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() {
|
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());
|
inline_start_range.shift_by(slice.range.length());
|
||||||
} else {
|
} else {
|
||||||
debug!("split_to_inline_size: enlarging span");
|
debug!("calculate_split_position_using_breaking_strategy: enlarging span");
|
||||||
remaining_inline_size = remaining_inline_size - advance;
|
remaining_inline_size = remaining_inline_size - advance;
|
||||||
inline_start_range.extend_by(slice.range.length());
|
inline_start_range.extend_by(slice.range.length());
|
||||||
}
|
}
|
||||||
|
@ -1570,16 +1465,15 @@ impl Fragment {
|
||||||
inline_end);
|
inline_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
// If we failed to find a suitable split point, we're on the verge of overflowing the
|
||||||
}
|
// line.
|
||||||
|
|
||||||
// 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 inline_start_range.is_empty() || overflowing {
|
||||||
// If we've been instructed to retry at character boundaries (probably via
|
// If we've been instructed to retry at character boundaries (probably via
|
||||||
// `overflow-wrap: break-word`), do so.
|
// `overflow-wrap: break-word`), do so.
|
||||||
if flags.contains(RETRY_AT_CHARACTER_BOUNDARIES) {
|
if flags.contains(RETRY_AT_CHARACTER_BOUNDARIES) {
|
||||||
let character_breaking_strategy =
|
let character_breaking_strategy =
|
||||||
text_fragment_info.run.character_slices_in_range(&text_fragment_info.range);
|
text_fragment_info.run
|
||||||
|
.character_slices_in_range(&text_fragment_info.range);
|
||||||
let mut flags = flags;
|
let mut flags = flags;
|
||||||
flags.remove(RETRY_AT_CHARACTER_BOUNDARIES);
|
flags.remove(RETRY_AT_CHARACTER_BOUNDARIES);
|
||||||
return self.calculate_split_position_using_breaking_strategy(
|
return self.calculate_split_position_using_breaking_strategy(
|
||||||
|
@ -1588,43 +1482,15 @@ impl Fragment {
|
||||||
flags)
|
flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We aren't at the start of the line, so don't overflow. Let inline layout wrap to the
|
// We aren't at the start of the line, so don't overflow. Let inline layout wrap to
|
||||||
// next line instead.
|
// the next line instead.
|
||||||
if !flags.contains(STARTS_LINE) {
|
if !flags.contains(STARTS_LINE) {
|
||||||
return None
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
let inline_start = if !inline_start_range.is_empty() {
|
let inline_start = if !inline_start_range.is_empty() {
|
||||||
Some(SplitInfo::new(inline_start_range, &**text_fragment_info))
|
Some(SplitInfo::new(inline_start_range, &**text_fragment_info))
|
||||||
|
@ -1642,22 +1508,21 @@ impl Fragment {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to strip trailing whitespace from this fragment by adjusting the text run range.
|
/// The opposite of `calculate_split_position_using_breaking_strategy`: merges this fragment
|
||||||
/// Returns true if any modifications were made.
|
/// with the next one.
|
||||||
pub fn strip_trailing_whitespace_if_necessary(&mut self) -> bool {
|
pub fn merge_with(&mut self, next_fragment: Fragment) {
|
||||||
let text_fragment_info =
|
match (&mut self.specific, &next_fragment.specific) {
|
||||||
if let SpecificFragmentInfo::ScannedText(ref mut text_fragment_info) = self.specific {
|
(&mut SpecificFragmentInfo::ScannedText(ref mut this_info),
|
||||||
text_fragment_info
|
&SpecificFragmentInfo::ScannedText(ref other_info)) => {
|
||||||
} else {
|
debug_assert!(util::arc_ptr_eq(&this_info.run, &other_info.run));
|
||||||
return false
|
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;
|
||||||
let run = text_fragment_info.run.clone();
|
self.border_box.size.inline = this_info.content_size.inline +
|
||||||
if strip_trailing_whitespace(&**run, &mut text_fragment_info.range) {
|
self.border_padding.inline_start_end();
|
||||||
self.border_box.size.inline = run.advance_for_range(&text_fragment_info.range);
|
}
|
||||||
return true
|
_ => panic!("Can only merge two scanned-text fragments!"),
|
||||||
}
|
}
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this fragment is an unscanned text fragment that consists entirely of
|
/// Returns true if this fragment is an unscanned text fragment that consists entirely of
|
||||||
|
@ -1669,7 +1534,7 @@ impl Fragment {
|
||||||
}
|
}
|
||||||
match self.specific {
|
match self.specific {
|
||||||
SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => {
|
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,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -1890,11 +1755,14 @@ impl Fragment {
|
||||||
/// Returns true if this fragment can merge with another adjacent fragment or false otherwise.
|
/// Returns true if this fragment can merge with another adjacent fragment or false otherwise.
|
||||||
pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool {
|
pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool {
|
||||||
match (&self.specific, &other.specific) {
|
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)
|
// 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.style().get_font() == other.style().get_font() &&
|
||||||
self.text_decoration() == other.text_decoration() &&
|
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,
|
_ => 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 {
|
impl fmt::Debug for Fragment {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
try!(write!(f, "({} {} ", self.debug_id(), self.specific.get_type()));
|
try!(write!(f, "({} {} ", self.debug_id(), self.specific.get_type()));
|
||||||
try!(write!(f, "bp {:?}", self.border_padding));
|
try!(write!(f, "bb {:?} bp {:?} m {:?}",
|
||||||
try!(write!(f, " "));
|
self.border_box,
|
||||||
try!(write!(f, "m {:?}", self.margin));
|
self.border_padding,
|
||||||
|
self.margin));
|
||||||
write!(f, ")")
|
write!(f, ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2105,7 +2016,7 @@ bitflags! {
|
||||||
const STARTS_LINE = 0x01,
|
const STARTS_LINE = 0x01,
|
||||||
#[doc="True if we should attempt to split at character boundaries if this split fails. \
|
#[doc="True if we should attempt to split at character boundaries if this split fails. \
|
||||||
This is used to implement `overflow-wrap: break-word`."]
|
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,
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,9 @@ use css::node_style::StyledNode;
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding};
|
use display_list_builder::{FragmentDisplayListBuilding, InlineFlowDisplayListBuilding};
|
||||||
use floats::{FloatKind, Floats, PlacementInfo};
|
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::{IS_ABSOLUTELY_POSITIONED};
|
||||||
use flow;
|
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
|
||||||
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, ScannedTextFragmentInfo};
|
|
||||||
use fragment::{SpecificFragmentInfo, SplitInfo};
|
|
||||||
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW, RESOLVE_GENERATED_CONTENT};
|
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW, RESOLVE_GENERATED_CONTENT};
|
||||||
use layout_debug;
|
use layout_debug;
|
||||||
use model::IntrinsicISizesContribution;
|
use model::IntrinsicISizesContribution;
|
||||||
|
@ -23,20 +21,21 @@ use geom::{Point2D, Rect};
|
||||||
use gfx::font::FontMetrics;
|
use gfx::font::FontMetrics;
|
||||||
use gfx::font_context::FontContext;
|
use gfx::font_context::FontContext;
|
||||||
use gfx::text::glyph::CharIndex;
|
use gfx::text::glyph::CharIndex;
|
||||||
use util::arc_ptr_eq;
|
use gfx::text::text_run::TextRun;
|
||||||
use util::geometry::{Au, ZERO_RECT};
|
|
||||||
use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
|
|
||||||
use util::range::{Range, RangeIndex};
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::num::ToPrimitive;
|
use std::num::ToPrimitive;
|
||||||
use std::ops::{Add, Sub, Mul, Div, Rem, Neg, Shl, Shr, Not, BitOr, BitAnd, BitXor};
|
use std::ops::{Add, Sub, Mul, Div, Rem, Neg, Shl, Shr, Not, BitOr, BitAnd, BitXor};
|
||||||
|
use std::sync::Arc;
|
||||||
use std::u16;
|
use std::u16;
|
||||||
use style::computed_values::{overflow_x, text_align, text_justify, text_overflow, vertical_align};
|
use style::computed_values::{overflow_x, text_align, text_justify, text_overflow, vertical_align};
|
||||||
use style::computed_values::{white_space};
|
use style::computed_values::{white_space};
|
||||||
use style::properties::ComputedValues;
|
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
|
// From gfxFontConstants.h in Firefox
|
||||||
static FONT_SUBSCRIPT_OFFSET_RATIO: f64 = 0.20;
|
static FONT_SUBSCRIPT_OFFSET_RATIO: f64 = 0.20;
|
||||||
|
@ -160,7 +159,9 @@ int_range_index! {
|
||||||
bitflags! {
|
bitflags! {
|
||||||
flags InlineReflowFlags: u8 {
|
flags InlineReflowFlags: u8 {
|
||||||
#[doc="The `white-space: nowrap` property from CSS 2.1 § 16.6 is in effect."]
|
#[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 {
|
pending_line: Line {
|
||||||
range: Range::empty(),
|
range: Range::empty(),
|
||||||
bounds: LogicalRect::zero(float_context.writing_mode),
|
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,
|
floats: float_context,
|
||||||
lines: Vec::new(),
|
lines: Vec::new(),
|
||||||
|
@ -216,7 +217,7 @@ impl LineBreaker {
|
||||||
self.cur_b,
|
self.cur_b,
|
||||||
Au(0),
|
Au(0),
|
||||||
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.
|
/// Reflows fragments for the given inline flow.
|
||||||
|
@ -226,23 +227,13 @@ impl LineBreaker {
|
||||||
// Create our fragment iterator.
|
// Create our fragment iterator.
|
||||||
debug!("LineBreaker: scanning for lines, {} fragments", flow.fragments.len());
|
debug!("LineBreaker: scanning for lines, {} fragments", flow.fragments.len());
|
||||||
let mut old_fragments = mem::replace(&mut flow.fragments, InlineFragments::new());
|
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
|
// 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
|
// 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
|
// 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.
|
// really is in practice, but it's probably worth handling.
|
||||||
self.lines = mem::replace(&mut flow.lines, Vec::new());
|
self.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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the reflow.
|
// Do the reflow.
|
||||||
self.reflow_fragments(old_fragment_iter, flow, layout_context);
|
self.reflow_fragments(old_fragment_iter, flow, layout_context);
|
||||||
|
@ -270,30 +261,14 @@ impl LineBreaker {
|
||||||
// Set up our reflow flags.
|
// Set up our reflow flags.
|
||||||
let flags = match fragment.style().get_inheritedtext().white_space {
|
let flags = match fragment.style().get_inheritedtext().white_space {
|
||||||
white_space::T::normal => InlineReflowFlags::empty(),
|
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
|
// Try to append the fragment.
|
||||||
// line) if we couldn't.
|
self.reflow_fragment(fragment, flow, layout_context, flags);
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.pending_line_is_empty() {
|
if !self.pending_line_is_empty() {
|
||||||
|
@ -301,37 +276,25 @@ impl LineBreaker {
|
||||||
self.lines.len());
|
self.lines.len());
|
||||||
self.flush_current_line()
|
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.
|
/// 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
|
/// If the fragment was at the end of an old line, undoes the line break for that fragment.
|
||||||
/// incremental-reflow-safe; try `next_unbroken_fragment` instead.
|
/// 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>
|
fn next_fragment<I>(&mut self, old_fragment_iter: &mut I) -> Option<Fragment>
|
||||||
where I: Iterator<Item=Fragment> {
|
where I: Iterator<Item=Fragment> {
|
||||||
|
let mut fragment;
|
||||||
if self.work_list.is_empty() {
|
if self.work_list.is_empty() {
|
||||||
return match old_fragment_iter.next() {
|
match old_fragment_iter.next() {
|
||||||
None => None,
|
None => return None,
|
||||||
Some(fragment) => {
|
Some(this_fragment) => fragment = this_fragment,
|
||||||
debug!("LineBreaker: working with fragment from flow: {:?}", fragment);
|
|
||||||
Some(fragment)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return self.work_list.pop_front()
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("LineBreaker: working with fragment from work list: {:?}", self.work_list.front());
|
Some(fragment)
|
||||||
self.work_list.pop_front()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acquires a new fragment to lay out from the work list or fragment list, merging it with any
|
/// 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 {
|
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) {
|
let candidate = match self.next_fragment(old_fragment_iter) {
|
||||||
None => return Some(result),
|
None => return Some(result),
|
||||||
Some(fragment) => fragment,
|
Some(fragment) => fragment,
|
||||||
|
@ -357,31 +316,58 @@ impl LineBreaker {
|
||||||
|
|
||||||
let need_to_merge = match (&mut result.specific, &candidate.specific) {
|
let need_to_merge = match (&mut result.specific, &candidate.specific) {
|
||||||
(&mut SpecificFragmentInfo::ScannedText(ref mut result_info),
|
(&mut SpecificFragmentInfo::ScannedText(ref mut result_info),
|
||||||
&SpecificFragmentInfo::ScannedText(ref candidate_info))
|
&SpecificFragmentInfo::ScannedText(ref candidate_info)) => {
|
||||||
if arc_ptr_eq(&result_info.run, &candidate_info.run) &&
|
util::arc_ptr_eq(&result_info.run, &candidate_info.run) &&
|
||||||
result_info.range.end() + CharIndex(1) == candidate_info.range.begin() => {
|
inline_contexts_are_equal(&result.inline_context,
|
||||||
// We found a previously-broken fragment. Merge it up.
|
&candidate.inline_context)
|
||||||
result_info.range.extend_by(candidate_info.range.length() + CharIndex(1));
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !need_to_merge {
|
|
||||||
|
if need_to_merge {
|
||||||
|
result.merge_with(candidate);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
self.work_list.push_front(candidate);
|
self.work_list.push_front(candidate);
|
||||||
return Some(result)
|
return Some(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Commits a line to the list.
|
/// Commits a line to the list.
|
||||||
fn flush_current_line(&mut self) {
|
fn flush_current_line(&mut self) {
|
||||||
debug!("LineBreaker: flushing line {}: {:?}", self.lines.len(), self.pending_line);
|
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.lines.push(self.pending_line);
|
||||||
self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block;
|
self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block;
|
||||||
self.reset_line();
|
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
|
// 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.
|
// 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)
|
fn new_block_size_for_line(&self, new_fragment: &Fragment, layout_context: &LayoutContext)
|
||||||
|
@ -488,71 +474,16 @@ impl LineBreaker {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to append the given fragment to the line for `pre`-formatted text, splitting it if
|
/// Tries to append the given fragment to the line, splitting it if necessary. Commits the
|
||||||
/// necessary. Returns true if we successfully pushed the fragment to the line or false if we
|
/// current line if needed.
|
||||||
/// couldn't.
|
fn reflow_fragment(&mut self,
|
||||||
fn try_append_to_line_by_new_line(&mut self,
|
mut fragment: Fragment,
|
||||||
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,
|
flow: &InlineFlow,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
flags: InlineReflowFlags)
|
flags: InlineReflowFlags) {
|
||||||
-> bool {
|
|
||||||
// Determine initial placement for the fragment if we need to.
|
// Determine initial placement for the fragment if we need to.
|
||||||
if self.pending_line_is_empty() {
|
if self.pending_line_is_empty() {
|
||||||
|
fragment.strip_leading_whitespace_if_necessary();
|
||||||
let (line_bounds, _) = self.initial_line_placement(flow, &fragment, self.cur_b);
|
let (line_bounds, _) = self.initial_line_placement(flow, &fragment, self.cur_b);
|
||||||
self.pending_line.bounds.start = line_bounds.start;
|
self.pending_line.bounds.start = line_bounds.start;
|
||||||
self.pending_line.green_zone = line_bounds.size;
|
self.pending_line.green_zone = line_bounds.size;
|
||||||
|
@ -572,8 +503,21 @@ impl LineBreaker {
|
||||||
let new_block_size = self.new_block_size_for_line(&fragment, layout_context);
|
let new_block_size = self.new_block_size_for_line(&fragment, layout_context);
|
||||||
if new_block_size > green_zone.block {
|
if new_block_size > green_zone.block {
|
||||||
// Uh-oh. Float collision imminent. Enter the float collision avoider!
|
// 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
|
// 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
|
// horizontally. We'll try to place the whole fragment on this line and break somewhere if
|
||||||
|
@ -583,23 +527,27 @@ impl LineBreaker {
|
||||||
fragment.border_box.size.inline + indentation;
|
fragment.border_box.size.inline + indentation;
|
||||||
if new_inline_size <= green_zone.inline {
|
if new_inline_size <= green_zone.inline {
|
||||||
debug!("LineBreaker: fragment fits without splitting");
|
debug!("LineBreaker: fragment fits without splitting");
|
||||||
self.push_fragment_to_line(layout_context, fragment);
|
self.push_fragment_to_line(layout_context, fragment, line_flush_mode);
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we can't split the fragment or aren't allowed to because of the wrapping mode, then
|
// If we can't split the fragment or aren't allowed to because of the wrapping mode, then
|
||||||
// just overflow.
|
// just overflow.
|
||||||
if (!fragment.can_split() && self.pending_line_is_empty()) ||
|
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",
|
debug!("LineBreaker: fragment can't split and line {} is empty, so overflowing",
|
||||||
self.lines.len());
|
self.lines.len());
|
||||||
self.push_fragment_to_line(layout_context, fragment);
|
self.push_fragment_to_line(layout_context, fragment, LineFlushMode::No);
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split it up!
|
// Split it up!
|
||||||
let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline -
|
let available_inline_size = if !flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) {
|
||||||
indentation;
|
green_zone.inline - self.pending_line.bounds.size.inline - indentation
|
||||||
|
} else {
|
||||||
|
MAX_AU
|
||||||
|
};
|
||||||
let inline_start_fragment;
|
let inline_start_fragment;
|
||||||
let inline_end_fragment;
|
let inline_end_fragment;
|
||||||
let split_result = match fragment.calculate_split_position(available_inline_size,
|
let split_result = match fragment.calculate_split_position(available_inline_size,
|
||||||
|
@ -607,7 +555,8 @@ impl LineBreaker {
|
||||||
None => {
|
None => {
|
||||||
debug!("LineBreaker: fragment was unsplittable; deferring to next line");
|
debug!("LineBreaker: fragment was unsplittable; deferring to next line");
|
||||||
self.work_list.push_front(fragment);
|
self.work_list.push_front(fragment);
|
||||||
return false
|
self.flush_current_line();
|
||||||
|
return
|
||||||
}
|
}
|
||||||
Some(split_result) => split_result,
|
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.
|
// the second fragment. If there's no second fragment, the next line will start off empty.
|
||||||
match (inline_start_fragment, inline_end_fragment) {
|
match (inline_start_fragment, inline_end_fragment) {
|
||||||
(Some(inline_start_fragment), Some(inline_end_fragment)) => {
|
(Some(inline_start_fragment), Some(inline_end_fragment)) => {
|
||||||
self.push_fragment_to_line(layout_context, inline_start_fragment);
|
self.push_fragment_to_line(layout_context,
|
||||||
self.flush_current_line();
|
inline_start_fragment,
|
||||||
|
LineFlushMode::Flush);
|
||||||
self.work_list.push_front(inline_end_fragment)
|
self.work_list.push_front(inline_end_fragment)
|
||||||
},
|
},
|
||||||
(Some(fragment), None) => {
|
(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) => {}
|
(None, None) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes a fragment to the current line unconditionally, possibly truncating it and placing
|
/// Pushes a fragment to the current line unconditionally, possibly truncating it and placing
|
||||||
/// an ellipsis based on the value of `text-overflow`.
|
/// an ellipsis based on the value of `text-overflow`. If `flush_line` is `Flush`, then flushes
|
||||||
fn push_fragment_to_line(&mut self, layout_context: &LayoutContext, fragment: Fragment) {
|
/// 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();
|
let indentation = self.indentation_for_pending_fragment();
|
||||||
if self.pending_line_is_empty() {
|
if self.pending_line_is_empty() {
|
||||||
assert!(self.new_fragments.len() <= (u16::MAX as usize));
|
assert!(self.new_fragments.len() <= (u16::MAX as usize));
|
||||||
|
@ -661,9 +617,7 @@ impl LineBreaker {
|
||||||
|
|
||||||
if !need_ellipsis {
|
if !need_ellipsis {
|
||||||
self.push_fragment_to_line_ignoring_text_overflow(fragment);
|
self.push_fragment_to_line_ignoring_text_overflow(fragment);
|
||||||
return
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
let ellipsis = fragment.transform_into_ellipsis(layout_context);
|
let ellipsis = fragment.transform_into_ellipsis(layout_context);
|
||||||
if let Some(truncation_info) =
|
if let Some(truncation_info) =
|
||||||
fragment.truncate_to_inline_size(available_inline_size -
|
fragment.truncate_to_inline_size(available_inline_size -
|
||||||
|
@ -675,11 +629,15 @@ impl LineBreaker {
|
||||||
self.push_fragment_to_line_ignoring_text_overflow(ellipsis);
|
self.push_fragment_to_line_ignoring_text_overflow(ellipsis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if line_flush_mode == LineFlushMode::Flush {
|
||||||
|
self.flush_current_line()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pushes a fragment to the current line unconditionally, without placing an ellipsis in the
|
/// Pushes a fragment to the current line unconditionally, without placing an ellipsis in the
|
||||||
/// case of `text-overflow: ellipsis`.
|
/// case of `text-overflow: ellipsis`.
|
||||||
fn push_fragment_to_line_ignoring_text_overflow(&mut self, fragment: Fragment) {
|
fn push_fragment_to_line_ignoring_text_overflow(&mut self, fragment: Fragment) {
|
||||||
let indentation = self.indentation_for_pending_fragment();
|
let indentation = self.indentation_for_pending_fragment();
|
||||||
|
|
||||||
self.pending_line.range.extend_by(FragmentIndex(1));
|
self.pending_line.range.extend_by(FragmentIndex(1));
|
||||||
self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
|
self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
|
||||||
fragment.border_box.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
|
/// 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
|
/// `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,
|
fn distance_from_baseline(fragment: &Fragment,
|
||||||
ascent: Au,
|
ascent: Au,
|
||||||
parent_text_block_start: Au,
|
parent_text_block_start: Au,
|
||||||
|
@ -1180,7 +1138,7 @@ impl Flow for InlineFlow {
|
||||||
// Reset our state, so that we handle incremental reflow correctly.
|
// Reset our state, so that we handle incremental reflow correctly.
|
||||||
//
|
//
|
||||||
// TODO(pcwalton): Do something smarter, like Gecko and WebKit?
|
// TODO(pcwalton): Do something smarter, like Gecko and WebKit?
|
||||||
self.lines = Vec::new();
|
self.lines.clear();
|
||||||
|
|
||||||
// Determine how much indentation the first line wants.
|
// Determine how much indentation the first line wants.
|
||||||
let mut indentation = if self.fragments.is_empty() {
|
let mut indentation = if self.fragments.is_empty() {
|
||||||
|
@ -1431,6 +1389,30 @@ impl InlineFragmentContext {
|
||||||
styles: vec!()
|
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
|
/// 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
use fragment::{Fragment, SpecificFragmentInfo, ScannedTextFragmentInfo};
|
use fragment::{Fragment, SpecificFragmentInfo, ScannedTextFragmentInfo, UnscannedTextFragmentInfo};
|
||||||
use inline::InlineFragments;
|
use inline::InlineFragments;
|
||||||
|
|
||||||
use gfx::font::{DISABLE_KERNING_SHAPING_FLAG, FontMetrics, IGNORE_LIGATURES_SHAPING_FLAG};
|
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::glyph::CharIndex;
|
||||||
use gfx::text::text_run::TextRun;
|
use gfx::text::text_run::TextRun;
|
||||||
use gfx::text::util::{self, CompressionMode};
|
use gfx::text::util::{self, CompressionMode};
|
||||||
use util::linked_list::split_off_head;
|
use std::borrow::ToOwned;
|
||||||
use util::geometry::Au;
|
|
||||||
use util::logical_geometry::{LogicalSize, WritingMode};
|
|
||||||
use util::range::Range;
|
|
||||||
use util::smallvec::{SmallVec, SmallVec1};
|
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::sync::Arc;
|
||||||
use style::computed_values::{line_height, text_orientation, text_rendering, text_transform};
|
use style::computed_values::{line_height, text_orientation, text_rendering, text_transform};
|
||||||
use style::computed_values::{white_space};
|
use style::computed_values::{white_space};
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::properties::style_structs::Font as FontStyle;
|
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.
|
/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
|
||||||
pub struct TextRunScanner {
|
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 {
|
-> InlineFragments {
|
||||||
debug!("TextRunScanner: scanning {} fragments for text runs...", fragments.len());
|
debug!("TextRunScanner: scanning {} fragments for text runs...", fragments.len());
|
||||||
|
|
||||||
|
@ -50,12 +53,14 @@ impl TextRunScanner {
|
||||||
let mut last_whitespace = true;
|
let mut last_whitespace = true;
|
||||||
while !fragments.is_empty() {
|
while !fragments.is_empty() {
|
||||||
// Create a clump.
|
// Create a clump.
|
||||||
|
split_first_fragment_at_newline_if_necessary(&mut fragments);
|
||||||
self.clump.append(&mut split_off_head(&mut fragments));
|
self.clump.append(&mut split_off_head(&mut fragments));
|
||||||
while !fragments.is_empty() && self.clump
|
while !fragments.is_empty() && self.clump
|
||||||
.back()
|
.back()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.can_merge_with_fragment(fragments.front()
|
.can_merge_with_fragment(fragments.front()
|
||||||
.unwrap()) {
|
.unwrap()) {
|
||||||
|
split_first_fragment_at_newline_if_necessary(&mut fragments);
|
||||||
self.clump.append(&mut split_off_head(&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.
|
// Concatenate all of the transformed strings together, saving the new character indices.
|
||||||
let mut new_ranges: SmallVec1<Range<CharIndex>> = SmallVec1::new();
|
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 mut char_total = CharIndex(0);
|
||||||
let run = {
|
let run = {
|
||||||
let fontgroup;
|
let fontgroup;
|
||||||
|
@ -137,14 +141,11 @@ impl TextRunScanner {
|
||||||
_ => panic!("Expected an unscanned text fragment!"),
|
_ => panic!("Expected an unscanned text fragment!"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut new_line_pos = Vec::new();
|
|
||||||
let old_length = CharIndex(run_text.chars().count() as isize);
|
let old_length = CharIndex(run_text.chars().count() as isize);
|
||||||
last_whitespace = util::transform_text(in_fragment.as_slice(),
|
last_whitespace = util::transform_text(in_fragment.as_slice(),
|
||||||
compression,
|
compression,
|
||||||
last_whitespace,
|
last_whitespace,
|
||||||
&mut run_text,
|
&mut run_text);
|
||||||
&mut new_line_pos);
|
|
||||||
new_line_positions.push(NewLinePositions(new_line_pos));
|
|
||||||
|
|
||||||
let added_chars = CharIndex(run_text.chars().count() as isize) - old_length;
|
let added_chars = CharIndex(run_text.chars().count() as isize) - old_length;
|
||||||
new_ranges.push(Range::new(char_total, added_chars));
|
new_ranges.push(Range::new(char_total, added_chars));
|
||||||
|
@ -200,13 +201,8 @@ impl TextRunScanner {
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_size = old_fragment.border_box.size;
|
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 =
|
let mut new_text_fragment_info =
|
||||||
box ScannedTextFragmentInfo::new(run.clone(),
|
box ScannedTextFragmentInfo::new(run.clone(), range, text_size);
|
||||||
range,
|
|
||||||
mem::replace(new_line_positions, Vec::new()),
|
|
||||||
text_size);
|
|
||||||
let new_metrics = new_text_fragment_info.run.metrics_for_range(&range);
|
let new_metrics = new_text_fragment_info.run.metrics_for_range(&range);
|
||||||
let bounding_box_size = bounding_box_for_run_metrics(&new_metrics,
|
let bounding_box_size = bounding_box_for_run_metrics(&new_metrics,
|
||||||
old_fragment.style.writing_mode);
|
old_fragment.style.writing_mode);
|
||||||
|
@ -270,8 +266,6 @@ impl TextRunScanner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NewLinePositions(Vec<CharIndex>);
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode)
|
fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode)
|
||||||
-> LogicalSize<Au> {
|
-> LogicalSize<Au> {
|
||||||
|
@ -318,3 +312,45 @@ pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) ->
|
||||||
line_height::T::Length(l) => l
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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'];
|
const WHITESPACE: &'static [char] = &[' ', '\t', '\x0a', '\x0c', '\x0d'];
|
||||||
|
|
||||||
pub fn is_whitespace(s: &str) -> bool {
|
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:
|
/// A "space character" according to:
|
||||||
|
|
|
@ -151,6 +151,7 @@ flaky_cpu == append_style_a.html append_style_b.html
|
||||||
!= img_simple.html img_simple_ref.html
|
!= img_simple.html img_simple_ref.html
|
||||||
== img_size_a.html img_size_b.html
|
== img_size_a.html img_size_b.html
|
||||||
== incremental_float_a.html incremental_float_ref.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_background_a.html inline_background_ref.html
|
||||||
== inline_block_baseline_a.html inline_block_baseline_ref.html
|
== inline_block_baseline_a.html inline_block_baseline_ref.html
|
||||||
== inline_block_border_a.html inline_block_border_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_bgcolor_attribute_a.html legacy_td_bgcolor_attribute_ref.html
|
||||||
== legacy_td_width_attribute_a.html legacy_td_width_attribute_ref.html
|
== legacy_td_width_attribute_a.html legacy_td_width_attribute_ref.html
|
||||||
== letter_spacing_a.html letter_spacing_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
|
== line_height_a.html line_height_ref.html
|
||||||
!= linear_gradients_corners_a.html linear_gradients_corners_ref.html
|
!= linear_gradients_corners_a.html linear_gradients_corners_ref.html
|
||||||
== linear_gradients_lengths_a.html linear_gradients_lengths_ref.html
|
== linear_gradients_lengths_a.html linear_gradients_lengths_ref.html
|
||||||
|
|
32
tests/ref/incremental_inline_layout_a.html
Normal file
32
tests/ref/incremental_inline_layout_a.html
Normal 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>
|
||||||
|
|
28
tests/ref/incremental_inline_layout_ref.html
Normal file
28
tests/ref/incremental_inline_layout_ref.html
Normal 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>
|
||||||
|
|
19
tests/ref/line_breaking_whitespace_collapse_a.html
Normal file
19
tests/ref/line_breaking_whitespace_collapse_a.html
Normal 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>
|
||||||
|
|
19
tests/ref/line_breaking_whitespace_collapse_ref.html
Normal file
19
tests/ref/line_breaking_whitespace_collapse_ref.html
Normal 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>
|
||||||
|
|
|
@ -18,9 +18,8 @@ fn test_transform_compress_none() {
|
||||||
|
|
||||||
let mode = CompressionMode::CompressNone;
|
let mode = CompressionMode::CompressNone;
|
||||||
for &test in test_strs.iter() {
|
for &test in test_strs.iter() {
|
||||||
let mut new_line_pos = vec![];
|
|
||||||
let mut trimmed_str = String::new();
|
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)
|
assert_eq!(trimmed_str, test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,9 +51,8 @@ fn test_transform_discard_newline() {
|
||||||
|
|
||||||
let mode = CompressionMode::DiscardNewline;
|
let mode = CompressionMode::DiscardNewline;
|
||||||
for &(test, oracle) in test_strs.iter() {
|
for &(test, oracle) in test_strs.iter() {
|
||||||
let mut new_line_pos = vec![];
|
|
||||||
let mut trimmed_str = String::new();
|
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)
|
assert_eq!(trimmed_str, oracle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,9 +84,8 @@ fn test_transform_compress_whitespace() {
|
||||||
|
|
||||||
let mode = CompressionMode::CompressWhitespace;
|
let mode = CompressionMode::CompressWhitespace;
|
||||||
for &(test, oracle) in test_strs.iter() {
|
for &(test, oracle) in test_strs.iter() {
|
||||||
let mut new_line_pos = vec![];
|
|
||||||
let mut trimmed_str = String::new();
|
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)
|
assert_eq!(&*trimmed_str, oracle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,9 +117,8 @@ fn test_transform_compress_whitespace_newline() {
|
||||||
|
|
||||||
let mode = CompressionMode::CompressWhitespaceNewline;
|
let mode = CompressionMode::CompressWhitespaceNewline;
|
||||||
for &(test, oracle) in test_strs.iter() {
|
for &(test, oracle) in test_strs.iter() {
|
||||||
let mut new_line_pos = vec![];
|
|
||||||
let mut trimmed_str = String::new();
|
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)
|
assert_eq!(&*trimmed_str, oracle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,9 +153,8 @@ fn test_transform_compress_whitespace_newline_no_incoming() {
|
||||||
|
|
||||||
let mode = CompressionMode::CompressWhitespaceNewline;
|
let mode = CompressionMode::CompressWhitespaceNewline;
|
||||||
for &(test, oracle) in test_strs.iter() {
|
for &(test, oracle) in test_strs.iter() {
|
||||||
let mut new_line_pos = vec![];
|
|
||||||
let mut trimmed_str = String::new();
|
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)
|
assert_eq!(trimmed_str, oracle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue