diff options
-rw-r--r-- | components/gfx/paint_context.rs | 4 | ||||
-rw-r--r-- | components/gfx/text/text_run.rs | 162 | ||||
-rw-r--r-- | components/layout/fragment.rs | 244 | ||||
-rw-r--r-- | components/layout/inline.rs | 13 | ||||
-rw-r--r-- | components/style/properties/mod.rs.mako | 13 | ||||
-rw-r--r-- | tests/ref/basic.list | 1 | ||||
-rw-r--r-- | tests/ref/overflow_wrap_a.html | 22 | ||||
-rw-r--r-- | tests/ref/overflow_wrap_ref.html | 28 |
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> + |