diff --git a/components/gfx/font.rs b/components/gfx/font.rs index c2d4504e720..6ee0fd58acd 100644 --- a/components/gfx/font.rs +++ b/components/gfx/font.rs @@ -115,6 +115,8 @@ pub struct ShapingOptions { /// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property. /// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null. pub letter_spacing: Option, + /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property. + pub word_spacing: Au, /// Various flags. pub flags: ShapingFlags, } diff --git a/components/gfx/text/shaping/harfbuzz.rs b/components/gfx/text/shaping/harfbuzz.rs index aec15f0014f..d510126e7fd 100644 --- a/components/gfx/text/shaping/harfbuzz.rs +++ b/components/gfx/text/shaping/harfbuzz.rs @@ -42,8 +42,8 @@ use harfbuzz::{hb_shape, hb_buffer_get_glyph_infos}; use libc::{c_uint, c_int, c_void, c_char}; use servo_util::geometry::Au; use servo_util::range::Range; -use std::mem; use std::char; +use std::mem; use std::cmp; use std::ptr; @@ -436,8 +436,12 @@ impl Shaper { // for now, just pretend that every character is a cluster start. // (i.e., pretend there are no combining character sequences). // 1-to-1 mapping of character to glyph also treated as ligature start. + // + // NB: When we acquire the ability to handle ligatures that cross word boundaries, + // we'll need to do something special to handle `word-spacing` properly. let shape = glyph_data.get_entry_for_glyph(glyph_span.begin(), &mut y_pos); - let advance = self.advance_for_shaped_glyph(shape.advance, options); + let character = text.char_at(char_byte_span.begin() as uint); + let advance = self.advance_for_shaped_glyph(shape.advance, character, options); let data = GlyphData::new(shape.codepoint, advance, shape.offset, @@ -488,11 +492,22 @@ impl Shaper { glyphs.finalize_changes(); } - fn advance_for_shaped_glyph(&self, advance: Au, options: &ShapingOptions) -> Au { + fn advance_for_shaped_glyph(&self, mut advance: Au, character: char, options: &ShapingOptions) + -> Au { match options.letter_spacing { - None => advance, - Some(spacing) => advance + spacing, + None => {} + Some(letter_spacing) => advance = advance + letter_spacing, + }; + + // CSS 2.1 § 16.4 states that "word spacing affects each space (U+0020) and non-breaking + // space (U+00A0) left in the text after the white space processing rules have been + // applied. The effect of the property on other word-separator characters is undefined." + // We elect to only space the two required code points. + if character == ' ' || character == '\u00a0' { + advance = advance + options.word_spacing } + + advance } } diff --git a/components/layout/text.rs b/components/layout/text.rs index ac96967522b..ca837bf6117 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -107,16 +107,19 @@ impl TextRunScanner { let compression; let text_transform; let letter_spacing; + let word_spacing; { let in_fragment = self.clump.front().unwrap(); let font_style = in_fragment.style().get_font_arc(); + let inherited_text_style = in_fragment.style().get_inheritedtext(); fontgroup = font_context.get_layout_font_group_for_style(font_style); compression = match in_fragment.white_space() { white_space::normal | white_space::nowrap => CompressWhitespaceNewline, white_space::pre => CompressNone, }; - text_transform = in_fragment.style().get_inheritedtext().text_transform; - letter_spacing = in_fragment.style().get_inheritedtext().letter_spacing; + text_transform = inherited_text_style.text_transform; + letter_spacing = inherited_text_style.letter_spacing; + word_spacing = inherited_text_style.word_spacing.unwrap_or(Au(0)); } // First, transform/compress text of all the nodes. @@ -160,6 +163,7 @@ impl TextRunScanner { // `fi n a l l y`. let options = ShapingOptions { letter_spacing: letter_spacing, + word_spacing: word_spacing, flags: match letter_spacing { Some(Au(0)) | None => ShapingFlags::empty(), Some(_) => IGNORE_LIGATURES_SHAPING_FLAG, diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index ef0b28d78fc..c35d2dfaa7c 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -1111,6 +1111,29 @@ pub mod longhands { } + <%self:single_component_value name="word-spacing"> + pub type SpecifiedValue = Option; + pub mod computed_value { + use super::super::Au; + pub type T = Option; + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + None + } + #[inline] + pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) + -> computed_value::T { + value.map(|length| computed::compute_Au(length, context)) + } + pub fn from_component_value(input: &ComponentValue, _: &Url) -> Result { + match input { + &Ident(ref value) if value.eq_ignore_ascii_case("normal") => Ok(None), + _ => specified::Length::parse_non_negative(input).map(|length| Some(length)), + } + } + + ${predefined_type("text-indent", "LengthOrPercentage", "computed::LP_Length(Au(0))")} ${new_style_struct("Text", is_inherited=False)} diff --git a/tests/ref/basic.list b/tests/ref/basic.list index b508104adef..ae118c7a6b9 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -198,3 +198,4 @@ fragment=top != ../html/acid2.html acid2_ref.html != border_black_ridge.html border_black_solid.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 diff --git a/tests/ref/word_spacing_a.html b/tests/ref/word_spacing_a.html new file mode 100644 index 00000000000..335c6c6142f --- /dev/null +++ b/tests/ref/word_spacing_a.html @@ -0,0 +1,16 @@ + + + + + +X XX + + diff --git a/tests/ref/word_spacing_ref.html b/tests/ref/word_spacing_ref.html new file mode 100644 index 00000000000..79ed5571ed9 --- /dev/null +++ b/tests/ref/word_spacing_ref.html @@ -0,0 +1,26 @@ + + + + +
+ + + +