w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.42k stars 652 forks source link

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

Open jfkthame opened 4 months ago

jfkthame commented 4 months ago

Summary

Proposed spec change

Change the description of letter-spacing from

Specifies additional spacing between typographic character units. Values may be negative, but there may be implementation-dependent limits.

to

Specifies additional spacing applied to each 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 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

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 (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:

image

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:

image

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.

xiaochengh commented 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.

jfkthame commented 4 months ago

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:

image

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: image whereas with symmetrical spacing, we get: image which looks better balanced.)

xiaochengh commented 4 months ago

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"? */
frivoal commented 4 months ago

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:

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.

xiaochengh commented 4 months ago

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.

woody-li commented 3 months ago

How about adding a new property, such as:

letter-spacing-justify: [ before | after | left | right | between | around]

For compatibility:

jfkthame commented 3 months ago

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.

css-meeting-bot commented 2 months ago

The CSS Working Group just discussed [css-text] Reconsidering the CSS letter-spacing model, and agreed to the following:

The full IRC log of that discussion <emeyer> jfkthame: We've talked about letter-spacing before, but never made progress
<dbaron> ScribeNick: emeyer
<emeyer> …Current situation: browxser all implement letter-spacing by adding extra width to every character
<emeyer> …But inconsistently: Safari does it to the right, FF to the framing box of each character
<emeyer> …Neither is good, as the issue shows
<dbaron> s/Safari does/Blink and WebKit do/
<dbaron> s/FF to the framing box/FF to the inline-end side/
<emeyer> …For RTL text, the Blink implementation isn't good, but Gecko isn't good for mixed directions
<emeyer> …The spec implements something difference
<emeyer> …A few years ago, the compat risk of changing the behavior was felt to be too great
<florian> q+
<emeyer> …So we should take a different direction than the spec
<emeyer> …We should make letter spacing symmetrical instead of all on one side
<astearns> ack emilio
<emeyer> …That removes all the problems that current implenetations have, and the bidi issues that make it all ugly
<emeyer> …Because there's null effect on the overall layout, the compat risk is much lower than doing what the spec currently describes
<astearns> q+
<emeyer> …There are a many ideas and syntax proposals in the issue, but that's a little different than whether or not we should address the underlying problem by adopting symmetrical expansion
<emeyer> …A font designer asked for looser tracking would expand to both sides of characters, not all to one side
<emeyer> …Another point is that a couple of months ago we enabled this in FF Nightly
<jfkthame> https://bugzilla.mozilla.org/show_bug.cgi?id=1892262
<emeyer> …We've gotten a couple of bug reports
<dbaron> s/A font designer/If a font designer/
<emeyer> …See the screenshots in the linked bug report for some fascinating approaches
<iank_> q+
<florian> q- later
<emeyer> …I'm curious if other engines are willing to consider this change
<emilio> q-
<dbaron> (I feel like that's what 10% of OTP fields look like already!)
<emeyer> astearns: In terms of line-start and line-end spacing, I'm surprised trimming this at line-start is an optional thing
<emeyer> jfkthame: I’m not sure I understand why you'd get a ragged left edge
<emeyer> astearns: If you applied letter spacing to a range inside a paragraph, and any characters were at the beginning of a line, that would create raggedness
<emeyer> jfkthame: I suspect in practice that spacing used on a range within a paragraph would be small
<emeyer> astearns: Is the trimming at the line edges a factor in the web compat problems that were seen with the spec?
<emeyer> jfkthame: I think that's one issue
<dbaron> jfkthame: trimming at the line edges would mean that you would affect the overall layout
<emeyer> …The bigger issue is probably that authors assume they can wrap every character in a span, apply spacing, and get a result
<emeyer> astearns: To your point of applied letter-spacing being small, I think German emphasis has relatively high letter spacing
<dbaron> astearns: ... and is applied to spans in paragraphs
<emeyer> jfkthame: Okay, I've not seen that as a problem in practice but I can see it could be an issue
<astearns> ack astearns
<astearns> ack iank_
<emeyer> …I'm not sure whether having the space at the beginning of a line would be seen as bad or a natural consequence
<emeyer> iank_: Might have been koji who was in the original assessment, but the bug report breaking Zoom would make me very nervous to ship this
<emeyer> …If we got a couple of breakages on major sites in Canary, we wouldn't ship this
<emeyer> …The fact that it's breaking stuff in Nightly for you make me very nervous here
<emeyer> …That said, if you can work with Zoom to fix, I'd be less nervous
<astearns> trimming at line-start would fix the zoom issue
<emeyer> jfkthame: If you can ship this in Canary, Zoom would probably be more likely to fix it
<emeyer> dbaron: Having worked on both FF and Chrome, the FF bug reporting ratio between Nightly and stable is substantially different than Canary/Chrome
<emeyer> …Getting that level of bug reporting on FFNightly seems a much weaker signal to me than it would be on Canary
<astearns> ack florian
<Zakim> dbaron, you wanted to react to iank_ to respond to Ian about breakage
<emeyer> florian: I continue to think the currently specced behavior is better
<emeyer> …But if that's impossible, this is probably better than what's currently shipped, so I'd like to explore this
<kizu> q+
<emeyer> …Maybe addressing Alan's concern about trimming line edges would help
<emeyer> …It would vary text metrics a little bit, but these sorts of things are already dangerous and not very realiable
<emeyer> …Keeping the current behavior as an opt-in would be nice as I think it's better, but what's shipping now is bad
<astearns> ack fantasai
<emeyer> fantasai: My recollection is that people tend to hack around the trailing-space problem by putting in negative stuff to compensate
<emeyer> …My concern is the pages that care too much are the most likely to break
<emeyer> …I do think the glyph centering is probably better in most cases
<emeyer> …I don't think we should have a property to opt into current shipping behavior
<emeyer> …I also think trimming at line edges is better
<emeyer> …Authors go to great lengths to make things line up
<florian> q+
<emeyer> …I'm inclined to say let's try centering with line trimming
<kizu> https://codepen.io/stoumann/details/bGQdgpw
<astearns> ack kizu
<emeyer> kizu: The first random CodePen I found to do what Zoom does indicates a lot of people do like Zoom does, and it does break in Nightly
<emeyer> …I like the centering behavior more, but with the jagged edge, I wonder if it's possible to register when you need to trim to the left or right
<emeyer> fantasai: I think you just want to trim that space rather than redistribute it to the end of the line
<emeyer> kizu: I think it will be better visually than keeping it this way
<astearns> ack florian
<emeyer> florian: I think the workaround Koji mentioned using negative margins to get rid of terminal space is usually used at the end of inline boxes, not usually the end of a line
<emeyer> …So the end of an <em> gets a negative margin
<emeyer> fantasai: The trimming would interfere in the sense that if we center things, there would be too much trimming
<dbaron> s/gets a negative margin/gets a negative margin... but the proposal here is to trim at the ends of lines, not at the ends of inline boxes/
<emeyer> florian: At the margin, yes, but I think it's much less bad than the previous problem we had
<emeyer> …The scope here seems a lot more limited and I suspect won't be that bad in practice
<emeyer> jfkthame: The proposal to create trim at line edge is preferable
<emeyer> …Should that be under author control, and should it be oopt in or out?
<emeyer> fantasai: I suspect there won't be a lot of demand to not have line edge trimming, except maybe in compat situations
<emeyer> …I doubt that we'll get a lot of demand for that control; usually people want to get rid of extra space at line edges, so I think we should just do it
<emeyer> …Running it through all the experimental browsers would be a good way to flush out any problems
<emeyer> astearns: Agree with Elika about all that
<emeyer> …If we did have a switch, I suspect the default would be to opt out
<emeyer> …This may make things more complicated, but for left justified text, we could trim at the line starts only
<emeyer> astearns: Is there anyone who has concerns about trying this way of distributing letter spacing with trimming at the line ends?
<emeyer> iank_: I wouldn't object to trying it, but the compat still makes me very nervous
<emeyer> fantasai: I propose we adopt this into the spec with a note that we're trying it for compat analysis
<emeyer> astearns: Okay with you, Florian?
<emeyer> florian: Yes, but Elika, how do you mean your proposal?
<emeyer> fantasai: We can do it for the next six months or something
<emeyer> florian: So you want to spec it out with a warning on top?
<emeyer> fantasai: Yes, to see where we're at once we have more prototypes and compat data
<astearns> ack dbaron
<emeyer> astearns: Proposed resolution is to add this new method of letter spacing with line-edge trimming with an issue saying we're trying this out and will come back with compat data
<emeyer> dbaron: One other thought is might be useful to enable feature detection for this
<emeyer> …I don't know if there's a natrual way to do that, but we should think about it
<emeyer> astearns: That might be informed by the compat data; we might be something we need to do that for
<emeyer> fantasai: It might be that if we can release in a coordinated fashion, people will decide to all drop their hacks
<emeyer> astearns: Can't depend on that
<dbaron> s/should think about it/should think about it... it might be an @supports feature() or something else/
<emeyer> florian: Right, but this being largely cosmetic, you can drop the hacks and your page might be slightly uglier in old browsers
<emeyer> iank_: There are Chromium browsers that release on a six month cadence that it would be nice for their users not to be broken for six months
<fantasai> You can always use JS to detect
<emeyer> astearns: Objections to the proposed resolution?
<emeyer> (silence)
<emeyer> 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
<fantasai> s/new/space-around/
<ChrisL> q+