mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
layout: Implement word-spacing
per CSS 2.1 § 16.4.
This assumes that there are no ligatures that span across multiple words. Since we have a per-word shape cache, this is a safe assumption as of now. I have left comments to ensure that, if and when this is revisted, we make sure to handle it properly.
This commit is contained in:
parent
ea39b878ac
commit
106fdb1d32
7 changed files with 94 additions and 7 deletions
|
@ -115,6 +115,8 @@ pub struct ShapingOptions {
|
||||||
/// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property.
|
/// 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.
|
/// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null.
|
||||||
pub letter_spacing: Option<Au>,
|
pub letter_spacing: Option<Au>,
|
||||||
|
/// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property.
|
||||||
|
pub word_spacing: Au,
|
||||||
/// Various flags.
|
/// Various flags.
|
||||||
pub flags: ShapingFlags,
|
pub flags: ShapingFlags,
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,8 @@ use harfbuzz::{hb_shape, hb_buffer_get_glyph_infos};
|
||||||
use libc::{c_uint, c_int, c_void, c_char};
|
use libc::{c_uint, c_int, c_void, c_char};
|
||||||
use servo_util::geometry::Au;
|
use servo_util::geometry::Au;
|
||||||
use servo_util::range::Range;
|
use servo_util::range::Range;
|
||||||
use std::mem;
|
|
||||||
use std::char;
|
use std::char;
|
||||||
|
use std::mem;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
|
@ -436,8 +436,12 @@ impl Shaper {
|
||||||
// for now, just pretend that every character is a cluster start.
|
// for now, just pretend that every character is a cluster start.
|
||||||
// (i.e., pretend there are no combining character sequences).
|
// (i.e., pretend there are no combining character sequences).
|
||||||
// 1-to-1 mapping of character to glyph also treated as ligature start.
|
// 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 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,
|
let data = GlyphData::new(shape.codepoint,
|
||||||
advance,
|
advance,
|
||||||
shape.offset,
|
shape.offset,
|
||||||
|
@ -488,11 +492,22 @@ impl Shaper {
|
||||||
glyphs.finalize_changes();
|
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 {
|
match options.letter_spacing {
|
||||||
None => advance,
|
None => {}
|
||||||
Some(spacing) => advance + spacing,
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,16 +107,19 @@ impl TextRunScanner {
|
||||||
let compression;
|
let compression;
|
||||||
let text_transform;
|
let text_transform;
|
||||||
let letter_spacing;
|
let letter_spacing;
|
||||||
|
let word_spacing;
|
||||||
{
|
{
|
||||||
let in_fragment = self.clump.front().unwrap();
|
let in_fragment = self.clump.front().unwrap();
|
||||||
let font_style = in_fragment.style().get_font_arc();
|
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);
|
fontgroup = font_context.get_layout_font_group_for_style(font_style);
|
||||||
compression = match in_fragment.white_space() {
|
compression = match in_fragment.white_space() {
|
||||||
white_space::normal | white_space::nowrap => CompressWhitespaceNewline,
|
white_space::normal | white_space::nowrap => CompressWhitespaceNewline,
|
||||||
white_space::pre => CompressNone,
|
white_space::pre => CompressNone,
|
||||||
};
|
};
|
||||||
text_transform = in_fragment.style().get_inheritedtext().text_transform;
|
text_transform = inherited_text_style.text_transform;
|
||||||
letter_spacing = in_fragment.style().get_inheritedtext().letter_spacing;
|
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.
|
// First, transform/compress text of all the nodes.
|
||||||
|
@ -160,6 +163,7 @@ impl TextRunScanner {
|
||||||
// `fi n a l l y`.
|
// `fi n a l l y`.
|
||||||
let options = ShapingOptions {
|
let options = ShapingOptions {
|
||||||
letter_spacing: letter_spacing,
|
letter_spacing: letter_spacing,
|
||||||
|
word_spacing: word_spacing,
|
||||||
flags: match letter_spacing {
|
flags: match letter_spacing {
|
||||||
Some(Au(0)) | None => ShapingFlags::empty(),
|
Some(Au(0)) | None => ShapingFlags::empty(),
|
||||||
Some(_) => IGNORE_LIGATURES_SHAPING_FLAG,
|
Some(_) => IGNORE_LIGATURES_SHAPING_FLAG,
|
||||||
|
|
|
@ -1111,6 +1111,29 @@ pub mod longhands {
|
||||||
}
|
}
|
||||||
</%self:single_component_value>
|
</%self:single_component_value>
|
||||||
|
|
||||||
|
<%self:single_component_value name="word-spacing">
|
||||||
|
pub type SpecifiedValue = Option<specified::Length>;
|
||||||
|
pub mod computed_value {
|
||||||
|
use super::super::Au;
|
||||||
|
pub type T = Option<Au>;
|
||||||
|
}
|
||||||
|
#[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<SpecifiedValue,()> {
|
||||||
|
match input {
|
||||||
|
&Ident(ref value) if value.eq_ignore_ascii_case("normal") => Ok(None),
|
||||||
|
_ => specified::Length::parse_non_negative(input).map(|length| Some(length)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%self:single_component_value>
|
||||||
|
|
||||||
${predefined_type("text-indent", "LengthOrPercentage", "computed::LP_Length(Au(0))")}
|
${predefined_type("text-indent", "LengthOrPercentage", "computed::LP_Length(Au(0))")}
|
||||||
|
|
||||||
${new_style_struct("Text", is_inherited=False)}
|
${new_style_struct("Text", is_inherited=False)}
|
||||||
|
|
|
@ -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_solid.html
|
||||||
!= border_black_ridge.html border_black_groove.html
|
!= border_black_ridge.html border_black_groove.html
|
||||||
== text_indent_a.html text_indent_ref.html
|
== text_indent_a.html text_indent_ref.html
|
||||||
|
== word_spacing_a.html word_spacing_ref.html
|
||||||
|
|
16
tests/ref/word_spacing_a.html
Normal file
16
tests/ref/word_spacing_a.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Tests that `word-spacing` works. -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/ahem.css">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
word-spacing: 100px;
|
||||||
|
color: chartreuse;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>X XX</body>
|
||||||
|
</head>
|
||||||
|
|
26
tests/ref/word_spacing_ref.html
Normal file
26
tests/ref/word_spacing_ref.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Tests that `word-spacing` works. -->
|
||||||
|
<style>
|
||||||
|
section, nav, main {
|
||||||
|
background-color: chartreuse;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
left: 300px;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
left: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body><section></section><nav></nav><main></main></body>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue