[csswg-drafts] [css-text] Reconsidering the CSS letter-spacing model (#10193)

jfkthame has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-text] Reconsidering the CSS letter-spacing model ==
### Summary

- CSS letter-spacing is a long-established and widely-used feature;
- Behavior of existing letter-spacing implementations is unsatisfactory;
- There is incompatibility between Gecko and WebKit/Blink, with neither approach being clearly better;
- The behavior currently described in the spec would be better, but no browser implements it;
- Changing to the spec'd behavior would carry an unacceptable level of compat risk;
- I propose revising the spec to describe behavior that:
  - resolves the undesirable asymmetry of current implementations;
  - allows browsers to converge on a common behavior that is better than either existing version;
  - carries minimal compat risk as there will be no wider impact on layout.

### Proposed spec change

Change the description of [letter-spacing](https://drafts.csswg.org/css-text-4/#valdef-letter-spacing-length) from

> Specifies additional spacing between [typographic character units](https://drafts.csswg.org/css-text-4/#typographic-character-unit). Values may be negative, but there may be implementation-dependent limits.

to

> Specifies additional spacing applied to each [typographic character unit](https://drafts.csswg.org/css-text-4/#typographic-character-unit) except those with zero advance. The additional spacing is divided equally between the inline-start and -end sides of the typographic character unit. Values may be negative, but there may be implementation-dependent limits.

Much of the following explanation and the associated examples can then be considerably simplified.

### Background

The [current CSS spec for letter-spacing](https://drafts.csswg.org/css-text-4/#letter-spacing-property) bears little relation to the behavior actually seen in browsers. This has been the case “forever”, and no browser seems sufficiently interested in implementing the model described by the specification to overcome implementation inertia and webcompat fears. See for instance the extensive (and inconclusive) discussion in https://github.com/w3c/csswg-drafts/issues/1518.

Typical use cases for the letter-spacing feature include:
- Showing  e m p h a s i s  of some inline text within a block (an alternative presentation to italics or boldface) or as a heading;
- Adjusting the spacing of text for better legibility, e.g. spreading the glyphs out slightly at small font sizes, or tightening the spacing at a large headline size; in principle, this could be handled by optical sizing with a variable font, but not all fonts offer this;
- As part of a logo, branding, etc., where the text is used as a design element and characters may be deliberately squashed together (see the W3C logo) or spread apart for visual interest.

The spec currently says that “the total letter spacing between two adjacent typographic character units (after bidi reordering) is specified by and rendered within the innermost element that contains the boundary between the two typographic character units”. It goes on to give examples of what this implies: e.g. given
```
p    { letter-spacing: 1em; }
span { letter-spacing: 2em; }

<p>a<span>bb</span>c</p>
```
the increased letter-spacing of the span should apply only between the two “b”s, not between “a” and “b” or between “b” and “c”.

None of Firefox, Chrome or Safari behave this way. In effect they all apply added spacing to the individual characters based on the letter-spacing value of the current element, not to the boundaries based on the innermost containing element. So both “b”s get extra space after them.

One of the key shortcomings of current implementations is their asymmetry, whereby letter-spacing is applied on only one side of each affected character. This means that if letter-spacing is applied to a s i n g l e  word, for example, the following inter-word space will also grow, but the preceding one will not.

The model proposed in the current spec would resolve this, though its implication that letter-spacing applied to a single character in isolation has no effect would be surprising to authors, and more generally, the change from long-established behavior carries significant compatibility risks. Text measurement and line-breaks in existing documents would change, potentially resulting in layout shifts that could sometimes be quite drastic. (Four years ago in https://github.com/w3c/csswg-drafts/issues/1518#issuecomment-623865128, Koji noted that “For Blink … the breakage is beyond what we can accept”.)

### A way forward

I think rather than continuing to describe a letter-spacing model that is not actually found in browsers, the spec should be changed to reflect reality. So letter-spacing functions as an attribute of the characters in the text, just like font-family or -size, and is applied to each typographic character unit individually according to its computed value of the property. This is what the engines actually do, and I believe it corresponds to a typical typographer’s mental model, which is that letter-spacing (or tracking) represents an adjustment applied to the advance widths of the glyphs; it’s equivalent to using a modified version of the font. In particular, if letter-spacing is applied to a single character within a text (a<span style=letter-spacing:1em>b</span>c), users would expect to see some effect.

One issue where engines currently differ is in the treatment of RTL text. Chrome and Safari always add letter-spacing on the right-hand side of the character, while Firefox adds it on the trailing side, hence for RTL content it appears on the left. The Chrome/Safari behavior is arguably preferable for content with mixed directions, but seems undesirable for content that is primarily or exclusively RTL. Neither approach is entirely satisfactory.

Some simple examples are shown in https://codepen.io/jfkthame/pen/vYMjrwe. Screenshots of the current rendering in WebKit/Blink (_left_) and Gecko (_right_) show the unbalanced results of the asymmetrical implementations:

<img width="905" alt="image" src="https://github.com/w3c/csswg-drafts/assets/1706499/ba798ac6-d36d-4fdc-8b22-be3bc1fa5a2e">

Even for purely LTR content, applying letter-spacing entirely on the right-hand edge of the characters is less than ideal. As no browser currently implements trimming of the letter-spacing at end of line (as the spec suggests they should), a block of justified, right-aligned, or centered text ends up visually offset (to the left, assuming positive letter-spacing).

A more balanced result would be achieved, and the problems of mixed-direction content resolved, if the letter-spacing for each character were divided evenly between its two sides. This would mean that blocks of text with letter-spacing would be evenly inset from both margins, instead of offset.

In this model, where letter-spacing is an attribute of each character, what happens at element boundaries is obvious, and requires no special handling: the total spacing between the characters is simply half of the computed letter-spacing of the character before the boundary, and half of the computed letter-spacing of the character after the boundary.

The alternative proposed here thus addresses the asymmetry issues, but with much less compatibility risk than the spec’s current model: overall text measurement and layout is unaffected compared to existing behavior. All that changes is that glyphs appear centered within their (letter-spacing-adjusted) advance width instead of aligned to one edge (either left or inline-start, depending on the engine) of it.

With this simple change, the examples in the above codepen look much better:

<img width="435" alt="image" src="https://github.com/w3c/csswg-drafts/assets/1706499/5e152dd4-91f4-4f54-9967-d191f5aaa1d4">

I believe this would be straightforward to implement in existing browser engines. In many real-world uses where letter-spacing is small the change may well go unnoticed. In cases like naively-applied  German-style  e m p h a s i s  it will often be an improvement because of its symmetry; likewise in bidi content it will usually be a cosmetic improvement. It’ll only be a (cosmetic) regression in cases where an author has taken the trouble to explicitly account for the asymmetry of the existing behavior, e.g. by specifying a compensating margin on one side of the element only. Such cases would look subtly worse as the adjustment will no longer be appropriate, but I think this level of “breakage” should be relatively harmless.

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/10193 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Wednesday, 10 April 2024 11:25:03 UTC