Open jfkthame opened 4 months ago
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.
I have concerns about this, as it breaks alignment. Currently, a paragraph of text with letter-spacing
still appears aligned to the inline-start edge of the block (at least on LTR pages), but with the proposal, the text will be off by half of the letter spacing.
A real example is this Hong Kong gov website. With the proposal, the main text, which has letter spacing, will no longer be left-aligned with the header text.
And I don't see an easy fix. We can manually apply some shifting to the text, but it doesn't sound like the right approach.
Yes, this does indeed break alignment in such cases. I would suggest, though, that the "breakage" is pretty insignificant. The amount of letter-spacing used in the main text of that HK gov site seems quite unusual to me; I rarely see so much spacing applied to blocks of body text. Yet in my opinion, the result still looks perfectly acceptable:
It's true that the main text is not perfectly left-aligned with the header, but I suspect most users won't even notice this.
I think this is a reasonable price to pay for getting more consistent behavior in right-aligned or centered content, bidi cases, etc.
(Incidentally, that page also includes an example of how symmetrical letter-spacing visibly improves results: with left-aligned letter-spacing, the caption under the photograph appears misaligned:
whereas with symmetrical spacing, we get:
which looks better balanced.)
The amount of letter-spacing used in the main text of that HK gov site seems quite unusual to me; I rarely see so much spacing applied to blocks of body text.
From my years of experience living in HK, such kind of large spacing is used quite a lot in Chinese text in HK.
Since letter-spacing
is such an old feature with pretty stable behavior over the years, I would prefer a more cautious approach:
letter-spacing: 1px end; /* default value */
letter-spacing: 1px start;
letter-spacing: 1px symmetric; /* or just "center"? */
Effectively, using terminology found in other properties (ruby-align or justify-content) and along the lines of what @xiaochengh is saying, we can think of this as:
letter-spacing: 1px space-between;
letter-spacing: 1px space-after;
letter-spacing: 1px space-right;
letter-spacing: 1px space-around;
The currently specified behavior remains preferable in my opinion, but if we're not going to get it, I find this to be an interesting proposal. That said, should get just one behavior, or should we give authors choice as @xiaochengh suggests? If there's choice, what's the default?
It's pretty easy to imagine specifying something like this:
letter-spacing: [normal | <length-percentage>] &&
[ space-left | space-right | space-before | space-after | space-between | space-around]?
and either default to a particular value when omitted, or leave it up to the UA.
But this seems overkill.
An alternative could be to specify and implement the behavior proposed by @jfkthame, but also add a toggle to trim away the first and last half-spaces in the line. Making that opt-in is more compatible, opt-out gives better behavior by default. So something like:
letter-spacing: [normal | <length-percentage>] && trim?
or
letter-spacing: [normal | <length-percentage>] && no-trim?
Possibly with long-hands (letter-spacing-amount & letter-spacing-trim ?), if you want to to cascade independently.
An alternative could be to specify and implement the behavior proposed by @jfkthame, but also add a toggle to trim away the first and last half-spaces in the line
This looks much better. My original suggestion does look like an overkill.
Making that opt-in is more compatible, opt-out gives better behavior by default.
I think making trim
the default value has both better compat (as it doesn't break left alignment) and better behavior (as it fixes center alignment). The only compat risk I can think of is that line wrapping can be slightly different, but I don't see how this can be a real issue yet.
Or if we really want to be cautious about compat, then just add an auto
option to let browsers keep their current behaviors.
How about adding a new property, such as:
letter-spacing-justify: [ before | after | left | right | between | around]
For compatibility:
From an implementor's point of view, I think it would be straightforward to provide before | after | left | right | around
options. The between
option (which I assume corresponds to what the current spec describes but no-one implements) would require a substantially different implementation, and I am unconvinced that it offers enough value to authors to justify the added complexity.
Allowing browsers to set their current behavior as the default wouldn't address the current lack of interoperability for content that doesn't explicitly choose one of the options.
The CSS Working Group just discussed [css-text] Reconsidering the CSS letter-spacing model
, and agreed to the following:
RESOLVED: add this new method of letter spacing with line-edge trimming, with a note saying we're trying this out and will come back with compat data
Summary
Proposed spec change
Change the description of letter-spacing from
to
Much of the following explanation and the associated examples can then be considerably simplified.
Background
The current CSS spec for letter-spacing 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:
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
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 (abc), 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:
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:
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.