w3c / csswg-drafts

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

[css-values] ch units shouldn’t cause font downloads #3135

Closed litherum closed 2 years ago

litherum commented 6 years ago

Migrated from https://github.com/w3c/csswg-drafts/pull/3129

Right now, Values and Units says:

Equal to the used advance measure of the “0” (ZERO, U+0030) glyph in the font used to render it.

It’s pretty unfortunate that ch units can cause fonts to download.

WebKit doesn’t do this; it just uses the primary font, and if the primary font doesn’t support “0” then it uses that font’s .notdef glyph. No downloads necessary.

https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/graphics/Font.cpp#L123

https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/css/CSSPrimitiveValue.cpp#L661

In this comment, @emilio says that Firefox does something similar.

tabatkins commented 6 years ago

Yeah, it's not intentional that ch should cause the "0"-containing subface of a composite face to be downloaded. I agree we should clarify that you can use the "not-defined" glyph if "0" isn't present.

(Is ".notdef glyph" a widespread concept we can refer to?)

litherum commented 6 years ago

.notdef is well-understood, well-defined, and common. (It's just glyphid 0 in every font, commonly rendered as the tofu)

I'd propose making the spec somewhat flexible here, as I can think of a few reasonable behaviors off the top of my head, and we probably shouldn't prescribe any until we can do more research:

  1. Use the font's .notdef glyph's width
  2. Use the primary font's average character width
  3. Pick another character that is renderable somewhere in the fallback list, and is representative

We can make the spec say something like "if the primary font supports the '0' character, UAs must use the width of that character, otherwise, UAs should use an approximation of it using metrics from the primary font or a fallback font" and we can link to css-fonts for a definition of "supports." Given that almost all fonts support the '0' character, this is probably strong enough to be interoperable but flexible enough that we can find the best fallback.

emilio commented 6 years ago

Per the links in #3129, looks like WebKit is using (1) and Gecko is using (2). So yeah, if it's not clear one is superior to the other making the spec a bit flexible makes sense.

kojiishi commented 6 years ago

Blink does (1) too.

Is ".notdef glyph" a widespread concept we can refer to?

.notdef is well-understood, well-defined, and common. (It's just glyphid 0 in every font, commonly rendered as the tofu)

.notdef is defined in Recommendations for OpenType Fonts.

bradkemper commented 6 years ago

Today I discovered that a ‘ch’ unit in Font Awesome Pro (a popular icon font) is zero width in Safari. So, I think the .notdef glyph is not a reliable thing to fall back on. Average width doesn’t seem right either, as icons in an icon font tend to be much wider than a zero would be. Maybe something like 0.4em? Or use the width of an actual space character as a fallback first, if it is available?

bradkemper commented 6 years ago

Frankly, I don’t see why it can’t just use the next installed font from the font list (skipping past fonts that aren’t installed, or that would require a download).

litherum commented 6 years ago

We shouldn't be designing around the requirements of icon fonts. People shouldn't be using them in the first place.

bradkemper commented 6 years ago

People do use them. That’s the world we live in, and need to adapt to.

Also, I don’t think this is unique to icon fonts. I suspect that the one I’m connecting to (which I don’t have total control over), has been subsetted to save bandwidth, to remove characters that are not actually being used in content.

In those conditions, why not just...

use the next installed font from the font list (skipping past fonts that aren’t installed, or that would require a download).

It seems a perfectly reasonable thing to do, as it will always eventually find an already loaded (possibly system) font that has a zero in it.

kojiishi commented 6 years ago

I agree there are some such fonts. I've seen them before too. I don't think ch needs to take care of such fonts.

AmeliaBR commented 6 years ago

I'd strongly say that calculating a ch unit (or ex, ic, etc.) shouldn't cause a font to download, but I'm not sure that should mean giving up the use of the 0 as the reference glyph.

Consider the case of a custom font with just a few special characters (e.g., fancy ampersand) as the first font in the list, with a regular web font or system font as the next font in the stack. Using an artificially calculated value from the first font seems a poor choice if there is a font available (already downloaded or on the system) with a 0.

In most CSS environments, there will be at least a default font with a 0 in it. And if there really isn't, the spec already defines a fallback (0.5em).

kojiishi commented 6 years ago

Finding a system fallback font is not a cheap operation; it requires an RPC call in Blink due to sandbox, and the style recalc pauses during the RPC. I'm not very happy to bake it into the spec when no browsers do it today.

fantasai commented 6 years ago

@kojiishi Is it a problem only for system fallback, or also for fallback through the list of specified fonts? E.g. if I have font-family: web font, local font, sans-serif, is it a problem to check “local font” for '0'?

svgeesus commented 6 years ago

Mainly, I don't understand why there is any issue. The worries seem to be

ch is defined to use the actual font that is used for rendering. The two things noted above would only occur if:

Also, falling back to another font for the width of "0" or .notdef misses the point of using the actual font.

So my preference would be an ordered list of things to try:

1) the width of the "0" glyph. if it doesn't have one, 2) the width of the .notdef glyph. if it doesn't have one (and thus, the font is broken), 3) use 0.5em

No additional webfont is ever downloaded, no additional local font is loaded either.

AmeliaBR commented 6 years ago

@svgeesus That sounds like a good start, although your wording doesn't acknowledge that there may be 2 or more active fonts (meaning loaded & being used in the document) applying for the element. Would you would search for first the 0 and then the .notdef glyph in this filtered font stack of active fonts, or only use the first active font?

PS, has anyone looked into the other font-relative units & confirmed whether browsers are matching spec as far as the "first available font" being the first one with a space character available?

svgeesus commented 6 years ago

IIRC @florian had committed some tests to WPT checking for first available font with space.

bradkemper commented 6 years ago

I don’t understand why a .notdef glyph is considered to be an acceptable fallback. It it expected to be the same width as a zero? Isn’t it commonly a square shape instead? Mightn’t it also be zero width or just a single stroke wide?

If checking the font-family list is expensive, then how about just getting the system’s default serif or sanserif proportional font?

svgeesus commented 6 years ago

I don’t understand why a .notdef glyph is considered to be an acceptable fallback

Because all fonts are required to have one.

Isn’t it commonly a square shape instead?

No, it is typically a rectangle (and, I think, typically uses the default advance width)

It is recommended that the shape of the .notdef glyph be either an empty rectangle, a rectangle with a question mark inside of it, or a rectangle with an “X”. Creative shapes, like swirls or other symbols, may not be recognized by users as indicating that a glyph is missing from the font and is not being displayed at that location.

svgeesus commented 6 years ago

your wording doesn't acknowledge that there may be 2 or more active fonts (meaning loaded & being used in the document) applying for the element

Well spotted and yes, it doesn't. Specs referring to "the font used to render" need to be tightened up.

fantasai commented 3 years ago

I have to agree with @bradkemper and @AmeliaBR here. There are plenty of cases where the first available font isn't the one used to render zero and there is another font in the fallback list which both does have it and is expected to be used to render alphanumerics. And having ch not match that zero, but be something else random is not helpful to the author trying to size their content.

It's fine to restrict ch from triggering a font download, but I think it's less fine to pick an unrelated and potentially very wrong value because the author happened to use a more restricted-subset font as the first available font, instead of their main text font (or instead of their Latin text font, in i18n contexts).

r12a commented 3 years ago

I tend to agree with @fantasai, however stepping back a bit, it seems to me that this unit is really only something you'd want to use if you can be sure that a given mono-spaced (including CJK) font is available, and it contains a zero glyph.

Using a random fallback font's zero when falling back from a monospaced scenario is of course an even worse idea than usual, given that monospacing is all about predictable dimensions.

litherum commented 3 years ago

The particular case I'm concerned about is something like this:

@font-face {
    font-family: DoesNotSupportZero;
    src: url(...);
    unicode-range: a range that doesn't include the '0' character
}

@font-face {
    font-family: ShouldNotBeDownloaded;
    src: url(...);
}

<div style="
    font-family: DoesNotSupportZero, ShouldNotBeDownloaded;
    border-top: 1ch;
">
    Some text that doesn't have the zero character in it
</div>

I'd be happy with any solution that allows browser to not download ShouldNotBeDownloaded.

tabatkins commented 3 years ago

Right, Elika's suggestion would use ShouldNotBeDownloaded's 0 character for the ch size if something else has caused that font to download already, but would otherwise just use the fallback size. It's stateful, but accurate when there's a font being meaningfully used.

jfkthame commented 3 years ago

What about a slightly different case from Myles' example:

@font-face {
    font-family: CompositeFont;
    src: url(letters.ttf);
    unicode-range: U+20, U+41-5A, U+61-7A;
}
@font-face {
    font-family: CompositeFont;
    src: url(digits.ttf);
    unicode-range: U+30-39;
}
<div style="
    font-family: CompositeFont;
    border-top: 1ch solid;
">
    Some text that doesn't have the zero character in it
</div>

Does the use of the ch unit here cause digits.ttf to be downloaded? Should it?

litherum commented 3 years ago

We should consider "not downloading digits.ttf" as a requirement for any solution here.

tabatkins commented 3 years ago

No, it should again only use the 0 glyph size if it's already available. It'll never trigger a download on its own.

frivoal commented 2 years ago

So, how about:

jfkthame commented 2 years ago

The concern I have with the argument that the ch unit should never trigger a font download (but may use a glyph size from a downloaded font if available) is that it seems to open the door to mysterious, stateful, action-at-a-distance scenarios.

Given the example in https://github.com/w3c/csswg-drafts/issues/3135#issuecomment-960094958, suppose there's another element on the page that uses the same font-family: CompositeFont and does happen to contain a zero character; presumably that means ch will be derived from digits.ttf. But perhaps that element hasn't yet been reflowed (and hence loaded the digits.ttf resource), e.g. because it is hidden, but then it gets exposed as a result of some subsequent change.

Or the user types a zero into a similarly-styled editable field elsewhere on the page -- does that cause everything that was sized in ch units to suddenly shift?

css-meeting-bot commented 2 years ago

The CSS Working Group just discussed ch and font downloads.

The full IRC log of that discussion <emilio> topic: ch and font downloads
<emilio> github: https://github.com/w3c/csswg-drafts/issues/3135
<emilio> florian: we have multiple viewpoints represented, but are they enough?
<emilio> TabAtkins: initial issue is that currently ch uses the advance of the 0 glyph, which means that if your first font doesn't have a 0 glyph you go down the list and trigger a download for the next font on the list
<emilio> ... so I agree that that's unfortunate
<emilio> ... one objection is that if zero is defined _and_ being used, might as well use it
<emilio> ... this brings in potentially stateful behavior. Whether or not the ch uses it depends on whether the dom contains a 0, or a user types it, etc
<emilio> ... so options are: (1) no change, ch triggers downloads
<emilio> ... (2) always rely on first available font
<emilio> ... (3) look at 0 if there, and accept it's a stateful behavior
<emilio> chris: I think there's a (4), which is switch from the 0 glyph for .notdef
<emilio> TabAtkins: does .notdef has a reasonable char?
<emilio> chris: it's the tofu
<emilio> fantasai: we chose 0 because in monospace we want the size of the font, and in a proportional font digits are usually monospace
<dbaron> (or with font-variant-numeric: tabular-nums)
<emilio> ... so it fullfills all our these requirements
<emilio> fantasai: switching to .notdef would be a degradation for most uses of ch
<jfkthame_> fantasai++
<emilio> ... so I think it's a bad idea
<florian> q+
<emilio> ... relying on only first available font is problematic because often there's a stack where you're mixing latin/cjk fonts, so I don't think it's a good idea either
<emilio> ... I have no problem with (3) because I think it's going to be very unusual
<emilio> ... to have a font that doesn't have a 0, and some stack font has a zero and doesn't get loaded until later
<emilio> ... european digits are used in pretty much every writing system
<emilio> ... and chances are the font that has the zero is going to load
<emilio> ... so I think the concerns that jfkthame has about triggering a reflow when someone types will be rare
<astearns> ack fantasai
<astearns> ack florian
<emilio> ... and the other options degrade the ch units
<fantasai> s/rare/very rare/
<emilio> florian: I think where the fonts at the top of the stack doesn't have 0 is reasonably typical
<emilio> ... and you want the font with the actual zero
<emilio> ... stack with latin first, japanese there, without the digits seems really unusual
<emilio> ... one way you could workaround it is using the first avail font is that first avail font is dependent on the space glyph
<emilio> ... so you could remove the space from the first font down the list
<emilio> q+
<astearns> ack dbaron
<emilio> ... I don't think using the first avail font will buy us anything
<emilio> dbaron: I wonder if the argument that (3) is not a big deal is because it happens really rarely doesn't apply to (1)
<emilio> astearns: yeah, the lack of the glyph is really rare, and having a font that hasn't downloaded yet would be even more rare
<emilio> iank_: can you clarify?
<emilio> astearns: the most common case for lacking of 0 glyph is subsetting, and you do that so that you can use that font and the rest of the stack to render content
<emilio> ... so it's common for that font to be used by the content
<emilio> dbaron: other thought is about impl complexity
<emilio> ... and that depends on what the rules for (1) and (3) actually are
<emilio> ... I think (3) requires that if you _do_ download the font you update the ch units
<emilio> ... which also applies to (1) because ch units is what triggered the download
<emilio> ... so I think the only difference in implementation between (1) and (3) is whether the download is actually triggered
<astearns> ack emilio
<emilio> TabAtkins: that's correct
<fantasai> emilio: Even though 3 is very rare, I think I'd rather do 1
<iank_> don't we have the complexity that fonts can be partially downloaded in the future?
<fantasai> emilio: when it happens, it's just terrible, if you have anything sized with ch it shifts when you type a character, that's terrible
<fantasai> emilio: I'd much rather do 1, I think
<fantasai> emilio: 2 I wouldn't be opposed to, but I think fantasai's arguments are compelling
<dbaron> I agree that (1) (current behavior) is the correct thing.
<fantasai> TabAtkins: if we're trying to go for 1, I wouldn't be comfortable resolving without Myles around, since he was opposed
<fantasai> florian: this is not unique to ch, we have same problem with ex units
<emilio> ... if you want ex that depends on x height of the first avail font which is the first one that contains the space
<emilio> ... so if the first one doesn't contain the space you do need to trigger the download
<emilio> ... unless we want to redefine all font units to not depend on fonts...
<emilio> ... I think we can't escape this problem entirely anyways
<emilio> ... and giving up on font units doesn't sound like a good idea
<dbaron> dbaron: not the em unit, but ex, ch, ic, etc.
<emilio> ... so we need one more person to resolve on (1)
<emilio> iank_: isn't there a feature to allow fonts to be partially downloaded?
<astearns> q?
<emilio> astearns: yeah so that would make it more common not to have a 0
<emilio> chris: related, is there anything on CSS that assumes that fonts are completely downloaded?
<emilio> TabAtkins: yeah, css-font-loaded has a boolean loaded
<emilio> chris: yeah, we need to talk about that
<emilio> fantasai: tristate?
<emilio> dbaron: I think I have memories of working with jdagget on the font downloading code when ex units are used
<emilio> astearns: seems on this issue we're at an impasse?
<emilio> florian: it seems like consensus is towards (1)
<emilio> chris: though myles doesn't like it
<emilio> astearns: I think I agree with myles
<TabAtkins> s/font-loaded/font-loading/
<emilio> ... I want to avoid the case where someone has set up their fonts so that multiple downloads are triggered just to find a character where we could return another value
<emilio> ... and update when the download occurs
<emilio> florian: how's different with ex units and space?
<emilio> astearns: agreed that it's not specific to ch but ch is more common and this is more likely to happen
<emilio> jfkthame_: and arguably it can be avoided by changing the font to contain a 0
<emilio> astearns: I think this should be an exceedingly rare case but I think we should avoid the download
<emilio> jfkthame_: because of the rarity I think we should have the correct behavior. Having an emoji font at the beginning of the font list is not that rare but not getting the ch unit right there seems very unfortunate
<florian> q+
<TabAtkins> And thus, that rules out #2
<emilio> dbaron: one counterpoint here is that part of what makes this condition extra rare is the "and the font doesn't get downloaded for anything else". If the ch unit triggers the download, it's likely the first thing to download the font
<emilio> ... and it might be that the benefit that we get from downloading the font earlier is bigger than the issue of triggering an unlikely download
<emilio> astearns: say you have a font stacks that has a "my ampersand font", "my emoji font" (unused), and "my content font"
<emilio> ... and the emoji font does have a 0
<emilio> ... so you download the emoji font _and_ your ch unit doesn't match what your content actually uses
<fantasai> emilio: Point dbaron was making was if we do 3, then emoji gets used, and your ch unit changes
<emilio> dbaron: but if we do (3) then emoji gets used and your ch unit changes
<emilio> astearns: in the scenario of (3) the content font would use the right ch unit (from the content font, when it loads content), but then if you use emoji or a 0 your ch unit changes
<emilio> fantasai: I don't think that's going to be a common case
<emilio> astearns: yeah this are all really uncommon things
<emilio> ... but I agree with myles that prioritizing reducing downloads over layout changes is the right call
<emilio> fantasai: we can also make it up to the UA
<emilio> ... make triggering downloads not required
<fantasai> emilio: but this wouldn't change to half an em
<astearns> q?
<fantasai> emilio: it would switch to a font lower in the stack
<florian> q-
<emilio> florian (before ^): we already have the language "if not practical, use half an em"
<astearns> ack dbaron
<fantasai> emilio: I don't think that the cases that Alan and Myles argued for, I'm with dbaron that actually triggering the download for ch unit is more likely to help performance than cause problems
<fantasai> emilio: because by the time you get to layout the text, the font may already be there
<fantasai> emilio: otherwise you need to do more layout work, takes up all the ch units
<fantasai> emilio: and likely also trigger the download by the time you et to the content of the websiet
<fantasai> emilio: so I think doing 1 is the right thing
<fantasai> emilio: and also the more sensible thing
<emilio> fantasai: so... strawpoll?
<emilio> TabAtkins: pick up in two weeks with myles on the call
<emilio> ... myles was very strongly against (1)
<emilio> ... so don't want to resolve without him

See official minutes

r12a commented 2 years ago

Is there a summary of what came from the meeting discussion? (Given the length of the discussion multiplied by the number of people who will follow this thread, it seems that a summary could save a lot of reading and synthesis time. The alternative for those of us who read at about speaking speed is basically to sit through a simulation of the meeting.)

dbaron commented 2 years ago

I think the summary was:

r12a commented 2 years ago

Thanks @dbaron. That's really helpful.

jfkthame commented 2 years ago

I've created a small testcase at https://jfkthame.github.io/test/ch.html that demonstrates the sort of issues that can occur with option (3). Try it in current Safari to see the problem.

(Note that neither Chrome nor Firefox handle it correctly either, although their bugs are a bit different. As far as I can see, Chrome never uses the correct ch width, even when the relevant font is available; Firefox does try to use it, but fails to update things at the right time.)

I would argue that the correct rendering of this example is what Safari shows after a digit has been typed into the input field, but currently no browser gets this right on initial load. (Safari may get it right on subsequent page-loads, while the relevant font is cached; reload it in a new private-browsing window to see the problem again.)

css-meeting-bot commented 2 years ago

The CSS Working Group just discussed ch units shouldn't cause font downloads, and agreed to the following:

The full IRC log of that discussion <fantasai> Topic: ch units shouldn't cause font downloads
<fantasai> github: https://github.com/w3c/csswg-drafts/issues/3135
<astearns> zakim, start meeting
<Zakim> RRSAgent, make logs Public
<RRSAgent> I have made the request, Zakim
<Zakim> Meeting: Cascading Style Sheets (CSS) Working Group Teleconference
<fantasai> Scribenick: fantasai
<fantasai> astearns: We discussed this issue in NYC
<fantasai> astearns: whether ch units should cause font downloads
<fantasai> astearns: didn't resolve because Myles wasn't there
<fantasai> astearns: but general consensus was that we would leave spec unchanged, meaning ch units can cause font downloads
<fantasai> myles: Given the author's perspective here, they just want to make something e.g. 5ch wide, and the specific char isn't super important
<fantasai> myles: so if your font doesn't have the char, search down the list, but characters that are used are in the font
<fantasai> myles: in order to be exactly conformant you have to download things that are unnecessary
<fantasai> myles: They asked for ch, didn't say you have to match zero
<fantasai> myles: just said "make it 5 characters wide" so I think it's the wrong decision
<fantasai> dbaron: Idk how closely you read minutes from last time, we did talk through the various possibilities
<fantasai> dbaron: one of the issues was, if the ch unit doesn't trigger a font download, then you either end up in a situation where ch unit might change later
<fantasai> dbaron: or that it might be different whether or not downloaded a font
<fantasai> dbaron: other options all have problems, too
<fantasai> dbaron: so leaving as-is seemed like the least bad thing
<florian> +1 to david
<fantasai> +1
<fantasai> astearns: anyone else?
<fantasai> astearns: To close off, would like to resolve that we are making no change for this issue.
<fantasai> RESOLVED: ch units can cause a font download, no change to spec
<fantasai> astearns: Any other details discussed not in the spec that we should add in?
<fantasai> astearns: dbaron, you had a summary
<fantasai> dbaron: I have summarized discussion, leave to editors whether any non-normative changes needed to clarify
dbaron commented 2 years ago

I think the conclusion is really:

svgeesus commented 2 years ago

leave it to the editors as to whether any clarifying notes would be helpful.

Yes, need to go through your list looking for useful things to make clear in the spec.

In particular, this point would merit an example:

I think there was consensus that, whether or not ch units trigger font download, if the font is downloaded, then ch units get re-resolved if the newly-downloaded font is an earlier font. (This, in turn, means that the implementation complexity of triggering downloads is merely the triggering of downloads.)