Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
jfkthame opened this issue Apr 10, 2024 · 8 comments
Open

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

jfkthame opened this issue Apr 10, 2024 · 8 comments
Assignees
Labels
Closed Accepted by CSSWG Resolution css-text-4 i18n-tracker Group bringing to attention of Internationalization, or tracked by i18n but not needing response. Needs Edits

Comments

@jfkthame
Copy link
Contributor

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 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 #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 #1518 (comment), 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.

@emilio emilio added the Agenda+ label Apr 15, 2024
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Apr 15, 2024
…htly builds. r=layout-reviewers,emilio

This implements the behavior proposed in w3c/csswg-drafts#10193.
Pending discussion of the issue there, and as input to that eventual debate, I'd like to
try enabling this behavior on Nightly to see how it goes in the wild.

This results in a bunch of failures on existing reftests, in an entirely predictable way,
because any text with letter-spacing gets offset slightly, and no longer matches with
references that assumed glyphs remain flush-left in their advance. If we do adopt the
symmetrical model across the web platform, corresponding test adjustments will be needed.

(We also get a few new passes, on tests involving letter-spacing + bidi, which is the case
where our legacy behavior is particularly poor, and the symmetrical model resolves the
issues.)

For the time being, with the pref change only on Nightly, I propose to just add pref
overrides in the manifests for the affected tests so that they stay green for both Nightly
and non-Nightly builds.

Differential Revision: https://phabricator.services.mozilla.com/D207421
ErichDonGubler pushed a commit to ErichDonGubler/firefox that referenced this issue Apr 16, 2024
…htly builds. r=layout-reviewers,emilio

This implements the behavior proposed in w3c/csswg-drafts#10193.
Pending discussion of the issue there, and as input to that eventual debate, I'd like to
try enabling this behavior on Nightly to see how it goes in the wild.

This results in a bunch of failures on existing reftests, in an entirely predictable way,
because any text with letter-spacing gets offset slightly, and no longer matches with
references that assumed glyphs remain flush-left in their advance. If we do adopt the
symmetrical model across the web platform, corresponding test adjustments will be needed.

(We also get a few new passes, on tests involving letter-spacing + bidi, which is the case
where our legacy behavior is particularly poor, and the symmetrical model resolves the
issues.)

For the time being, with the pref change only on Nightly, I propose to just add pref
overrides in the manifests for the affected tests so that they stay green for both Nightly
and non-Nightly builds.

Differential Revision: https://phabricator.services.mozilla.com/D207421
@xiaochengh
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Contributor

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:

  • Revise the spec to match what browsers are currently doing (at least on the interoperable part)
  • Introduce something new to specify where to apply spacing:
letter-spacing: 1px end; /* default value */
letter-spacing: 1px start;
letter-spacing: 1px symmetric; /* or just "center"? */
@frivoal
Copy link
Collaborator

frivoal commented Apr 17, 2024

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 spec currently describing letter-spacing: 1px space-between;
  • Firefox implementing letter-spacing: 1px space-after;
  • Webkit/Blink implementing letter-spacing: 1px space-right;
  • @jfkthame proposing 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.

@xiaochengh
Copy link
Contributor

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
Copy link

woody-li commented Jun 7, 2024

How about adding a new property, such as:

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

For compatibility:

  • Browsers may set the current behavior value as the default value.
  • New property is better for compatibility with current syntax.
@jfkthame
Copy link
Contributor Author

jfkthame commented Jun 7, 2024

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
Copy link
Member

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
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+
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Closed Accepted by CSSWG Resolution css-text-4 i18n-tracker Group bringing to attention of Internationalization, or tracked by i18n but not needing response. Needs Edits
8 participants