aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/gfx/paint_context.rs4
-rw-r--r--components/gfx/text/text_run.rs162
-rw-r--r--components/layout/fragment.rs244
-rw-r--r--components/layout/inline.rs13
-rw-r--r--components/style/properties/mod.rs.mako13
-rw-r--r--tests/ref/basic.list1
-rw-r--r--tests/ref/overflow_wrap_a.html22
-rw-r--r--tests/ref/overflow_wrap_ref.html28
8 files changed, 352 insertions, 135 deletions
diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs
index 23d54fdf1cb..0e3773c65a2 100644
--- a/components/gfx/paint_context.rs
+++ b/components/gfx/paint_context.rs
@@ -855,8 +855,8 @@ impl ScaledFontExtensionMethods for ScaledFont {
let mut azglyphs = vec!();
azglyphs.reserve(range.length().to_uint());
- for (glyphs, _offset, slice_range) in run.iter_slices_for_range(range) {
- for (_i, glyph) in glyphs.iter_glyphs_for_char_range(&slice_range) {
+ for slice in run.natural_word_slices_in_range(range) {
+ for (_i, glyph) in slice.glyphs.iter_glyphs_for_char_range(&slice.range) {
let glyph_advance = glyph.advance();
let glyph_offset = glyph.offset().unwrap_or(Zero::zero());
let azglyph = struct__AzGlyph {
diff --git a/components/gfx/text/text_run.rs b/components/gfx/text/text_run.rs
index 08d09bb48b5..68031657157 100644
--- a/components/gfx/text/text_run.rs
+++ b/components/gfx/text/text_run.rs
@@ -27,14 +27,14 @@ pub struct TextRun {
#[deriving(Clone)]
pub struct GlyphRun {
/// The glyphs.
- glyph_store: Arc<GlyphStore>,
+ pub glyph_store: Arc<GlyphStore>,
/// The range of characters in the containing run.
- range: Range<CharIndex>,
+ pub range: Range<CharIndex>,
}
-pub struct SliceIterator<'a> {
+pub struct NaturalWordSliceIterator<'a> {
glyph_iter: Items<'a, GlyphRun>,
- range: Range<CharIndex>,
+ range: Range<CharIndex>,
}
struct CharIndexComparator;
@@ -51,10 +51,31 @@ impl Comparator<CharIndex,GlyphRun> for CharIndexComparator {
}
}
-impl<'a> Iterator<(&'a GlyphStore, CharIndex, Range<CharIndex>)> for SliceIterator<'a> {
+/// A "slice" of a text run is a series of contiguous glyphs that all belong to the same glyph
+/// store. Line breaking strategies yield these.
+pub struct TextRunSlice<'a> {
+ /// The glyph store that the glyphs in this slice belong to.
+ pub glyphs: &'a GlyphStore,
+ /// The character index that this slice begins at, relative to the start of the *text run*.
+ pub offset: CharIndex,
+ /// The range that these glyphs encompass, relative to the start of the *glyph store*.
+ pub range: Range<CharIndex>,
+}
+
+impl<'a> TextRunSlice<'a> {
+ /// Returns the range that these glyphs encompass, relative to the start of the *text run*.
+ #[inline]
+ pub fn text_run_range(&self) -> Range<CharIndex> {
+ let mut range = self.range;
+ range.shift_by(self.offset);
+ range
+ }
+}
+
+impl<'a> Iterator<TextRunSlice<'a>> for NaturalWordSliceIterator<'a> {
// inline(always) due to the inefficient rt failures messing up inline heuristics, I think.
#[inline(always)]
- fn next(&mut self) -> Option<(&'a GlyphStore, CharIndex, Range<CharIndex>)> {
+ fn next(&mut self) -> Option<TextRunSlice<'a>> {
let slice_glyphs = self.glyph_iter.next();
if slice_glyphs.is_none() {
return None;
@@ -64,18 +85,58 @@ impl<'a> Iterator<(&'a GlyphStore, CharIndex, Range<CharIndex>)> for SliceIterat
let mut char_range = self.range.intersect(&slice_glyphs.range);
let slice_range_begin = slice_glyphs.range.begin();
char_range.shift_by(-slice_range_begin);
+
if !char_range.is_empty() {
- return Some((&*slice_glyphs.glyph_store, slice_range_begin, char_range))
+ Some(TextRunSlice {
+ glyphs: &*slice_glyphs.glyph_store,
+ offset: slice_range_begin,
+ range: char_range,
+ })
+ } else {
+ None
+ }
+ }
+}
+
+pub struct CharacterSliceIterator<'a> {
+ glyph_run: Option<&'a GlyphRun>,
+ glyph_run_iter: Items<'a, GlyphRun>,
+ range: Range<CharIndex>,
+}
+
+impl<'a> Iterator<TextRunSlice<'a>> for CharacterSliceIterator<'a> {
+ // inline(always) due to the inefficient rt failures messing up inline heuristics, I think.
+ #[inline(always)]
+ fn next(&mut self) -> Option<TextRunSlice<'a>> {
+ let glyph_run = match self.glyph_run {
+ None => return None,
+ Some(glyph_run) => glyph_run,
+ };
+
+ debug_assert!(!self.range.is_empty());
+ let index_to_return = self.range.begin();
+ self.range.adjust_by(CharIndex(1), CharIndex(0));
+ if self.range.is_empty() {
+ // We're done.
+ self.glyph_run = None
+ } else if self.range.intersect(&glyph_run.range).is_empty() {
+ // Move on to the next glyph run.
+ self.glyph_run = self.glyph_run_iter.next();
}
- return None;
+ let index_within_glyph_run = index_to_return - glyph_run.range.begin();
+ Some(TextRunSlice {
+ glyphs: &*glyph_run.glyph_store,
+ offset: glyph_run.range.begin(),
+ range: Range::new(index_within_glyph_run, CharIndex(1)),
+ })
}
}
pub struct LineIterator<'a> {
- range: Range<CharIndex>,
- clump: Option<Range<CharIndex>>,
- slices: SliceIterator<'a>,
+ range: Range<CharIndex>,
+ clump: Option<Range<CharIndex>>,
+ slices: NaturalWordSliceIterator<'a>,
}
impl<'a> Iterator<Range<CharIndex>> for LineIterator<'a> {
@@ -83,30 +144,30 @@ impl<'a> Iterator<Range<CharIndex>> for LineIterator<'a> {
// Loop until we hit whitespace and are in a clump.
loop {
match self.slices.next() {
- Some((glyphs, offset, slice_range)) => {
- match (glyphs.is_whitespace(), self.clump) {
+ Some(slice) => {
+ match (slice.glyphs.is_whitespace(), self.clump) {
(false, Some(ref mut c)) => {
- c.extend_by(slice_range.length());
+ c.extend_by(slice.range.length());
}
(false, None) => {
- let mut c = slice_range;
- c.shift_by(offset);
- self.clump = Some(c);
+ let mut range = slice.range;
+ range.shift_by(slice.offset);
+ self.clump = Some(range);
}
(true, None) => { /* chomp whitespace */ }
- (true, Some(c)) => {
+ (true, Some(clump)) => {
self.clump = None;
// The final whitespace clump is not included.
- return Some(c);
+ return Some(clump);
}
}
- },
+ }
None => {
- // flush any remaining chars as a line
+ // Flush any remaining characters as a line.
if self.clump.is_some() {
- let mut c = self.clump.take().unwrap();
- c.extend_to(self.range.end());
- return Some(c);
+ let mut range = self.clump.take().unwrap();
+ range.extend_to(self.range.end());
+ return Some(range);
} else {
return None;
}
@@ -216,9 +277,7 @@ impl<'a> TextRun {
}
pub fn range_is_trimmable_whitespace(&self, range: &Range<CharIndex>) -> bool {
- self.iter_slices_for_range(range).all(|(slice_glyphs, _, _)| {
- slice_glyphs.is_whitespace()
- })
+ self.natural_word_slices_in_range(range).all(|slice| slice.glyphs.is_whitespace())
}
pub fn ascent(&self) -> Au {
@@ -232,9 +291,9 @@ impl<'a> TextRun {
pub fn advance_for_range(&self, range: &Range<CharIndex>) -> Au {
// TODO(Issue #199): alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
- self.iter_slices_for_range(range)
- .fold(Au(0), |advance, (glyphs, _, slice_range)| {
- advance + glyphs.advance_for_char_range(&slice_range)
+ self.natural_word_slices_in_range(range)
+ .fold(Au(0), |advance, slice| {
+ advance + slice.glyphs.advance_for_char_range(&slice.range)
})
}
@@ -252,33 +311,58 @@ impl<'a> TextRun {
pub fn min_width_for_range(&self, range: &Range<CharIndex>) -> Au {
debug!("iterating outer range {}", range);
- self.iter_slices_for_range(range).fold(Au(0), |max_piece_width, (_, offset, slice_range)| {
- debug!("iterated on {}[{}]", offset, slice_range);
- Au::max(max_piece_width, self.advance_for_range(&slice_range))
+ self.natural_word_slices_in_range(range).fold(Au(0), |max_piece_width, slice| {
+ debug!("iterated on {}[{}]", slice.offset, slice.range);
+ Au::max(max_piece_width, self.advance_for_range(&slice.range))
})
}
+ /// Returns the first glyph run containing the given character index.
+ pub fn first_glyph_run_containing(&'a self, index: CharIndex) -> Option<&'a GlyphRun> {
+ self.index_of_first_glyph_run_containing(index).map(|index| &self.glyphs[index])
+ }
+
/// Returns the index of the first glyph run containing the given character index.
fn index_of_first_glyph_run_containing(&self, index: CharIndex) -> Option<uint> {
self.glyphs.as_slice().binary_search_index_by(&index, CharIndexComparator)
}
- pub fn iter_slices_for_range(&'a self, range: &Range<CharIndex>) -> SliceIterator<'a> {
+ /// Returns an iterator that will iterate over all slices of glyphs that represent natural
+ /// words in the given range.
+ pub fn natural_word_slices_in_range(&'a self, range: &Range<CharIndex>)
+ -> NaturalWordSliceIterator<'a> {
let index = match self.index_of_first_glyph_run_containing(range.begin()) {
None => self.glyphs.len(),
Some(index) => index,
};
- SliceIterator {
+ NaturalWordSliceIterator {
glyph_iter: self.glyphs.slice_from(index).iter(),
- range: *range,
+ range: *range,
+ }
+ }
+
+ /// Returns an iterator that will iterate over all slices of glyphs that represent individual
+ /// characters in the given range.
+ pub fn character_slices_in_range(&'a self, range: &Range<CharIndex>)
+ -> CharacterSliceIterator<'a> {
+ let index = match self.index_of_first_glyph_run_containing(range.begin()) {
+ None => self.glyphs.len(),
+ Some(index) => index,
+ };
+ let mut glyph_run_iter = self.glyphs.slice_from(index).iter();
+ let first_glyph_run = glyph_run_iter.next();
+ CharacterSliceIterator {
+ glyph_run: first_glyph_run,
+ glyph_run_iter: glyph_run_iter,
+ range: *range,
}
}
pub fn iter_natural_lines_for_range(&'a self, range: &Range<CharIndex>) -> LineIterator<'a> {
LineIterator {
- range: *range,
- clump: None,
- slices: self.iter_slices_for_range(range),
+ range: *range,
+ clump: None,
+ slices: self.natural_word_slices_in_range(range),
}
}
}
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
index 53aff17d0ac..0c4bf98a75e 100644
--- a/components/layout/fragment.rs
+++ b/components/layout/fragment.rs
@@ -25,7 +25,7 @@ use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
use geom::{Point2D, Rect, Size2D};
use gfx::display_list::OpaqueNode;
use gfx::text::glyph::CharIndex;
-use gfx::text::text_run::TextRun;
+use gfx::text::text_run::{TextRun, TextRunSlice};
use script_traits::UntrustedNodeAddress;
use serialize::{Encodable, Encoder};
use servo_msg::constellation_msg::{PipelineId, SubpageId};
@@ -44,8 +44,8 @@ use string_cache::Atom;
use style::{ComputedValues, TElement, TNode, cascade_anonymous};
use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto};
use style::computed_values::{LengthOrPercentageOrNone};
-use style::computed_values::{LPA_Auto, clear, position, text_align, text_decoration};
-use style::computed_values::{vertical_align, white_space};
+use style::computed_values::{LPA_Auto, clear, overflow_wrap, position, text_align};
+use style::computed_values::{text_decoration, vertical_align, white_space};
use sync::{Arc, Mutex};
use url::Url;
@@ -395,6 +395,8 @@ impl ScannedTextFragmentInfo {
}
}
+/// Describes how to split a fragment. This is used during line breaking as part of the return
+/// value of `find_split_info_for_inline_size()`.
#[deriving(Show)]
pub struct SplitInfo {
// TODO(bjz): this should only need to be a single character index, but both values are
@@ -412,6 +414,16 @@ impl SplitInfo {
}
}
+/// Describes how to split a fragment into two. This contains up to two `SplitInfo`s.
+pub struct SplitResult {
+ /// The part of the fragment that goes on the first line.
+ pub inline_start: Option<SplitInfo>,
+ /// The part of the fragment that goes on the second line.
+ pub inline_end: Option<SplitInfo>,
+ /// The text run which is being split.
+ pub text_run: Arc<Box<TextRun>>,
+}
+
/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow
/// construction that have not yet had their inline-size determined.
#[deriving(Clone)]
@@ -1097,104 +1109,149 @@ impl Fragment {
}
}
- /// Attempts to find the split positions of a text fragment so that its inline-size is
- /// no more than `max_inline-size`.
+ /// Attempts to find the split positions of a text fragment so that its inline-size is no more
+ /// than `max_inline_size`.
///
- /// A return value of `None` indicates that the fragment could not be split.
- /// Otherwise the information pertaining to the split is returned. The inline-start
- /// and inline-end split information are both optional due to the possibility of
- /// them being whitespace.
- pub fn find_split_info_for_inline_size(&self,
- start: CharIndex,
- max_inline_size: Au,
- starts_line: bool)
- -> Option<(Option<SplitInfo>,
- Option<SplitInfo>,
- Arc<Box<TextRun>>)> {
- match self.specific {
+ /// A return value of `None` indicates that the fragment could not be split. Otherwise the
+ /// information pertaining to the split is returned. The inline-start and inline-end split
+ /// information are both optional due to the possibility of them being whitespace.
+ pub fn calculate_split_position(&self, max_inline_size: Au, starts_line: bool)
+ -> Option<SplitResult> {
+ let text_fragment_info = match self.specific {
GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment |
TableCellFragment | TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) |
- InlineAbsoluteHypotheticalFragment(_) => None,
+ InlineAbsoluteHypotheticalFragment(_) => return None,
TableColumnFragment(_) => panic!("Table column fragments do not have inline_size"),
UnscannedTextFragment(_) => {
panic!("Unscanned text fragments should have been scanned by now!")
}
- ScannedTextFragment(ref text_fragment_info) => {
- let mut pieces_processed_count: uint = 0;
- let mut remaining_inline_size: Au = max_inline_size;
- let mut inline_start_range = Range::new(text_fragment_info.range.begin() + start,
- CharIndex(0));
- let mut inline_end_range: Option<Range<CharIndex>> = None;
-
- debug!("split_to_inline_size: splitting text fragment \
- (strlen={}, range={}, avail_inline_size={})",
- text_fragment_info.run.text.len(),
- text_fragment_info.range,
- max_inline_size);
-
- for (glyphs, offset, slice_range) in text_fragment_info.run.iter_slices_for_range(
- &text_fragment_info.range) {
- debug!("split_to_inline_size: considering slice (offset={}, range={}, \
- remain_inline_size={})",
- offset,
- slice_range,
- remaining_inline_size);
-
- let metrics = text_fragment_info.run.metrics_for_slice(glyphs, &slice_range);
- let advance = metrics.advance_width;
-
- let should_continue;
- if advance <= remaining_inline_size || glyphs.is_whitespace() {
- should_continue = true;
-
- if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() {
- debug!("split_to_inline_size: case=skipping leading trimmable whitespace");
- inline_start_range.shift_by(slice_range.length());
- } else {
- debug!("split_to_inline_size: case=enlarging span");
- remaining_inline_size = remaining_inline_size - advance;
- inline_start_range.extend_by(slice_range.length());
- }
- } else {
- // The advance is more than the remaining inline-size.
- should_continue = false;
- let slice_begin = offset + slice_range.begin();
-
- if slice_begin < text_fragment_info.range.end() {
- // There are still some things inline-start over at the end of the line. Create
- // the inline-end chunk.
- let inline_end_range_end = text_fragment_info.range.end() - slice_begin;
- inline_end_range = Some(Range::new(slice_begin, inline_end_range_end));
- debug!("split_to_inline_size: case=splitting remainder with inline_end range={}",
- inline_end_range);
- }
- }
-
- pieces_processed_count += 1;
+ ScannedTextFragment(ref text_fragment_info) => text_fragment_info,
+ };
- if !should_continue {
- break
- }
- }
+ let mut flags = SplitOptions::empty();
+ if starts_line {
+ flags.insert(STARTS_LINE);
+ if self.style().get_inheritedtext().overflow_wrap == overflow_wrap::break_word {
+ flags.insert(RETRY_AT_CHARACTER_BOUNDARIES)
+ }
+ }
- let inline_start_is_some = inline_start_range.length() > CharIndex(0);
+ let natural_word_breaking_strategy =
+ text_fragment_info.run.natural_word_slices_in_range(&text_fragment_info.range);
+ self.calculate_split_position_using_breaking_strategy(natural_word_breaking_strategy,
+ max_inline_size,
+ flags)
+ }
+
+ /// 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,
+ mut slice_iterator: I,
+ max_inline_size: Au,
+ flags: SplitOptions)
+ -> Option<SplitResult>
+ where I: Iterator<TextRunSlice<'a>> {
+ let text_fragment_info = match self.specific {
+ GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment |
+ TableCellFragment | TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) |
+ InlineAbsoluteHypotheticalFragment(_) => return None,
+ TableColumnFragment(_) => panic!("Table column fragments do not have inline_size"),
+ UnscannedTextFragment(_) => {
+ panic!("Unscanned text fragments should have been scanned by now!")
+ }
+ ScannedTextFragment(ref text_fragment_info) => text_fragment_info,
+ };
- if (pieces_processed_count == 1 || !inline_start_is_some) && !starts_line {
- None
+ let mut pieces_processed_count: uint = 0;
+ let mut remaining_inline_size = max_inline_size;
+ let mut inline_start_range = Range::new(text_fragment_info.range.begin(), CharIndex(0));
+ let mut inline_end_range = None;
+
+ debug!("calculate_split_position: 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={})",
+ slice.offset,
+ slice.range,
+ remaining_inline_size);
+
+ 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 &&
+ slice.glyphs.is_whitespace() {
+ debug!("calculate_split_position: skipping leading trimmable whitespace");
+ inline_start_range.shift_by(slice.range.length());
} else {
- let inline_start = if inline_start_is_some {
- Some(SplitInfo::new(inline_start_range, &**text_fragment_info))
- } else {
- None
- };
- let inline_end = inline_end_range.map(|inline_end_range| {
- SplitInfo::new(inline_end_range, &**text_fragment_info)
- });
-
- Some((inline_start, inline_end, text_fragment_info.run.clone()))
+ debug!("split_to_inline_size: enlarging span");
+ remaining_inline_size = remaining_inline_size - advance;
+ inline_start_range.extend_by(slice.range.length());
}
+ pieces_processed_count += 1;
+ continue
+ }
+
+ // The advance is more than the remaining inline-size, so split here.
+ let slice_begin = slice.text_run_range().begin();
+ if slice_begin < text_fragment_info.range.end() {
+ // There still some things left over at the end of the line, so create the
+ // inline-end chunk.
+ let mut inline_end = slice.text_run_range();
+ inline_end.extend_to(text_fragment_info.range.end());
+ inline_end_range = Some(inline_end);
+ debug!("calculate_split_position: splitting remainder with inline-end range={}",
+ inline_end);
+ }
+
+ pieces_processed_count += 1;
+ break
+ }
+
+ // If we failed to find a suitable split point, we're on the verge of overflowing the line.
+ let inline_start_is_some = inline_start_range.length() > CharIndex(0);
+ if pieces_processed_count == 1 || !inline_start_is_some {
+ // 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
}
}
+
+ let inline_start = if inline_start_is_some {
+ Some(SplitInfo::new(inline_start_range, &**text_fragment_info))
+ } else {
+ None
+ };
+ let inline_end = inline_end_range.map(|inline_end_range| {
+ SplitInfo::new(inline_end_range, &**text_fragment_info)
+ });
+
+ Some(SplitResult {
+ inline_start: inline_start,
+ inline_end: inline_end,
+ text_run: text_fragment_info.run.clone(),
+ })
}
/// Returns true if this fragment is an unscanned text fragment that consists entirely of
@@ -1531,6 +1588,18 @@ bitflags! {
}
}
+bitflags! {
+ // Various flags we can use when splitting fragments. See
+ // `calculate_split_position_using_breaking_strategy()`.
+ flags SplitOptions: u8 {
+ #[doc="True if this is the first fragment on the line."]
+ 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
+ }
+}
+
/// A top-down fragment bounds iteration handler.
pub trait FragmentBoundsIterator {
/// The operation to perform.
@@ -1540,3 +1609,4 @@ pub trait FragmentBoundsIterator {
/// we skip the operation for this fragment, but continue processing siblings.
fn should_process(&mut self, fragment: &Fragment) -> bool;
}
+
diff --git a/components/layout/inline.rs b/components/layout/inline.rs
index f7e7441ebbc..89b65b22fb6 100644
--- a/components/layout/inline.rs
+++ b/components/layout/inline.rs
@@ -512,18 +512,17 @@ impl LineBreaker {
let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline -
indentation;
let (inline_start_fragment, inline_end_fragment) =
- match fragment.find_split_info_for_inline_size(CharIndex(0),
- available_inline_size,
- self.pending_line_is_empty()) {
+ match fragment.calculate_split_position(available_inline_size,
+ self.pending_line_is_empty()) {
None => {
debug!("LineBreaker: fragment was unsplittable; deferring to next line: {}",
fragment);
self.work_list.push_front(fragment);
return false
}
- Some((start_split_info, end_split_info, run)) => {
+ Some(split_result) => {
let split_fragment = |split: SplitInfo| {
- let info = box ScannedTextFragmentInfo::new(run.clone(),
+ let info = box ScannedTextFragmentInfo::new(split_result.text_run.clone(),
split.range,
Vec::new(),
fragment.border_box.size);
@@ -532,8 +531,8 @@ impl LineBreaker {
fragment.border_box.size.block);
fragment.transform(size, info)
};
- (start_split_info.map(|x| split_fragment(x)),
- end_split_info.map(|x| split_fragment(x)))
+ (split_result.inline_start.map(|x| split_fragment(x)),
+ split_result.inline_end.map(|x| split_fragment(x)))
}
};
diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako
index c35d2dfaa7c..634c93b0419 100644
--- a/components/style/properties/mod.rs.mako
+++ b/components/style/properties/mod.rs.mako
@@ -1136,6 +1136,10 @@ pub mod longhands {
${predefined_type("text-indent", "LengthOrPercentage", "computed::LP_Length(Au(0))")}
+ // Also known as "word-wrap" (which is more popular because of IE), but this is the preferred
+ // name per CSS-TEXT 6.2.
+ ${single_keyword("overflow-wrap", "normal break-word")}
+
${new_style_struct("Text", is_inherited=False)}
<%self:longhand name="text-decoration">
@@ -1747,6 +1751,15 @@ pub mod shorthands {
})
</%self:shorthand>
+ // Per CSS-TEXT 6.2, "for legacy reasons, UAs must treat `word-wrap` as an alternate name for
+ // the `overflow-wrap` property, as if it were a shorthand of `overflow-wrap`."
+ <%self:shorthand name="word-wrap" sub_properties="overflow-wrap">
+ overflow_wrap::parse(input, base_url).map(|specified_value| {
+ Longhands {
+ overflow_wrap: Some(specified_value),
+ }
+ })
+ </%self:shorthand>
}
diff --git a/tests/ref/basic.list b/tests/ref/basic.list
index ae118c7a6b9..cfb21ad0d3f 100644
--- a/tests/ref/basic.list
+++ b/tests/ref/basic.list
@@ -199,3 +199,4 @@ fragment=top != ../html/acid2.html acid2_ref.html
!= border_black_ridge.html border_black_groove.html
== text_indent_a.html text_indent_ref.html
== word_spacing_a.html word_spacing_ref.html
+== overflow_wrap_a.html overflow_wrap_ref.html
diff --git a/tests/ref/overflow_wrap_a.html b/tests/ref/overflow_wrap_a.html
new file mode 100644
index 00000000000..8083eb9f3de
--- /dev/null
+++ b/tests/ref/overflow_wrap_a.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that `overflow-wrap: break-word` breaks words if it needs to, but only when
+ necessary. -->
+<link rel="stylesheet" type="text/css" href="css/ahem.css">
+<style>
+html, body {
+ margin: 0;
+}
+section {
+ word-wrap: break-word;
+ width: 300px;
+ color: purple;
+}
+</style>
+</head>
+<body>
+<section>X XXXXXX</section>
+</body>
+</html>
+
diff --git a/tests/ref/overflow_wrap_ref.html b/tests/ref/overflow_wrap_ref.html
new file mode 100644
index 00000000000..33d0de4c381
--- /dev/null
+++ b/tests/ref/overflow_wrap_ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that `overflow-wrap: break-word` breaks words if it needs to, but only when
+ necessary. -->
+<style>
+section, nav {
+ background: purple;
+ position: absolute;
+ left: 0;
+}
+section {
+ width: 100px;
+ top: 0;
+ height: 100px;
+}
+nav {
+ top: 100px;
+ width: 300px;
+ height: 200px;
+}
+</style>
+</head>
+<body>
+<section></section><nav></nav>
+</body>
+</html>
+