Closed LeaVerou closed 3 years ago
This seems obvious but checking just in case: your proposal @LeaVerou is to allow @supports
inside any at-rule that has descriptors (so, not @charset
for example), not solely on @font-face
, right?
Currently it is for @font-face
. Obviously, it would be better to allow it in every at-rule, but I didn't want some unlikely combination of at-rule and @supports
to hold up this proposal, so I figured we could start small and expand as use cases arise.
CC @jfkthame
I'll investigate what that would entail from an implementation point of view (complexity, overhead) and comment once I have some findings.
One consideration from a readability point of view: If we introduce such a @supports
rule, we will have both mechanisms: a list of entries in the src:
descriptor (for example multiple url()
entries potentially then "legacy" format("...")
specifiers (with its own semantics of choosing the first compatible one from this list) and potentially multiple src:
descriptors conditional on @supports
blocks in the same @font-face
rule, which I personally do not find great for consistency or readability. In other words, we would change the primary paradigm for src:
selection to the @supports
syntax but would still need to keep compatibility with the current approach of traversing a list of entries.
- It skips the parsing weirdness for
src
that this microsyntax has introduced
FWIW, the "parsing weirdness" may be considered addressed or improved with the most recent change which references the CSS syntax spec for parsing a comma separated list of components.
How is the supports rule represented in the OM? Implementation-wise I think it'd be easier for us if it just disappeared, but...
Hi @drott!
One consideration from a readability point of view: If we introduce such a
@supports
rule, we will have both mechanisms: a list of entries in thesrc:
descriptor (for example multipleurl()
entries potentially then "legacy"format("...")
specifiers (with its own semantics of choosing the first compatible one from this list) and potentially multiplesrc:
descriptors conditional on@supports
blocks in the same@font-face
rule, which I personally do not find great for consistency or readability. In other words, we would change the primary paradigm forsrc:
selection to the@supports
syntax but would still need to keep compatibility with the current approach of traversing a list of entries.
Yeah, this was raised by @plinss as well. The counter-argument he agreed to was that @supports
came after format()
, so while we're stuck with format()
for legacy format selection, we shouldn't be actively developing two different feature detection mechanisms, one of which is entirely ad hoc and severely more limited.
FWIW, the "parsing weirdness" may be considered addressed or improved with the most recent change which references the CSS syntax spec for parsing a comma separated list of components.
I was referring to the fact that this descriptor's syntax needs prose and cannot be fully described by a grammar. I wasn't even aware that it used to be even weirder, whoa!
@emilio
How is the supports rule represented in the OM? Implementation-wise I think it'd be easier for us if it just disappeared, but...
I've wondered about this too, but I'm afraid making it disappear would be fairly inconsistent with every other @-rule, which would be even more confusing once Nesting is implemented.
While it would involve more duplication, would it be okay to just leverage @supports normally, with some new queries to ask about various font-feature support, and then just have @font-face as a child of the rule like normal?
The big argument against is if we think multiple independent queries affecting different bits would be common; you'd have a combinatorial explosion to achieve equivalent results.
Looking at the proposal itself - it feels slightly weird to have context-specific @supports (I presume this wouldn't allow arbitrary property-support queries, but only font-based ones?). If I'm wrong about that and this would just be an ordinary @supports, with a supporting proposal to add the font-feature queries, then this is less weird. (If I'm right, naming it @font-supports
or something would defuse my objection.)
I don't have a strong opinion on whether this should be treated as a parse-time syntax, disappearing from the tree in the OM, or kept around like normal @supports is. Parse-time is definitely simpler overall, but I understand how it feels new and odd.
Overall tho, I definitely support something like this over the growing weird microsyntax. The microsyntax was reasonable when it was just one thing, but it's getting out of hand.
While it would involve more duplication, would it be okay to just leverage @supports normally, with some new queries to ask about various font-feature support, and then just have @font-face as a child of the rule like normal?
The big argument against is if we think multiple independent queries affecting different bits would be common; you'd have a combinatorial explosion to achieve equivalent results.
I think that could be a good first step, and would enable authors to do what they want with some duplication, and would likely (?) be relatively easy to implement. However, we do eventually want to avoid the duplication, even if multiple independent queries aren't common.
Looking at the proposal itself - it feels slightly weird to have context-specific @supports (I presume this wouldn't allow arbitrary property-support queries, but only font-based ones?). If I'm wrong about that and this would just be an ordinary @supports, with a supporting proposal to add the font-feature queries, then this is less weird. (If I'm right, naming it
@font-supports
or something would defuse my objection.)
It is not context-specific. The whole idea is that this way authors can use it to feature detect in any context that suits them, not just to differentiate the src
, but possibly other aspects of their style.
Having looked at and discussed with my CSS colleagues, the implementation of @supports
inside the @font-face
at-rule would be a larger effort, in particular if the @supports
rules need to be represented in the CSSOM, which poses problems given the current internal representation. (In addition, we're blocked on some parser refactoring which would make this more difficult, but that's an internal issue).
While it would involve more duplication, would it be okay to just leverage @supports normally, with some new queries to ask about various font-feature support, and then just have @font-face as a child of the rule like normal?
I think that could be a good first step, and would enable authors to do what they want with some duplication, and would likely (?) be relatively easy to implement. However, we do eventually want to avoid the duplication, even if multiple independent queries aren't common.
Yes, I agree, adding something similar to a <supports-font-technology-fn>
to 2. Extensions to the @supports rule with syntax similar to what is in the current font spec proposal is immediately useful for the desired feature detection through CSS.supports()
JavaScript and would immediately work at the top level (not nested).
This would give us a path that's relatively straight-forward to implement and not blocked on nesting @supports
inside @font-face
, while it would still allow for that kind of nesting later and migrating away from unnecessary @font-face
repetition.
I think adding the @supports
criteria first, and implementing the nesting step later is a good path forwards.
Adding this to the agenda so we can resolve that these changes have CSS WG consensus.
In this branch I drafted an edit to css-conditional-4, carrying over the syntax from fonts-4's - Parsing the source descriptor, an excerpt from that draft (feel free to comment on the commit directly as well):
<supports-feature> = <supports-selector-fn> | <supports-font-technology-fn> | <supports-decl>
<supports-selector-fn> = selector( <complex-selector> )
<supports-font-technology-fn> = font-technology ( <font-technology> )
<font-technology> = [ features( <font-feature-technology> ) | variations
| color( <color-font-technology> ) | palettes | incremental ]
<font-feature-technology> = [ opentype | aat | graphite]
<color-font-technology> = [ COLRv0 | COLRv1 | SVG | sbix | CBDT ]
Considerations:
<font-technology>
in the <supports-font-technology-fn>
rule as required combinations of technologies can be expressed through and
and or
of the @supports
syntax, which I consider an improvement over the syntax in the fonts spec.I'd be very happy if in tomorrow's meeting we could attempt to resolve towards a direction of the syntax along those lines (feedback very welcome), in addition to the general aim to resolve adding the criteria to the @supports
rule.
I did not add a repetition operator # after
in the rule as required combinations of technologies can be expressed through and and or of the @supports syntax, which I consider an improvement over the syntax in the fonts spec.
I agree, more readable that way.
Just to be sure I understand, the first example in the@font-face
src
explainer would become (repetitive, but readable; most desirable option is last):
/* 1. prefer COLRv1, then SVG-in-OpenType, then COLRv0 */
@font-face {
font-family: jewel;
src: url(boring.ttf) format("woff2");
}
@supports font-technology(color(COLRv0)) {
@font-face {
font-family: jewel;
src: url(jewel-v0.woff2) format("woff2");
}
}
@supports font-technology(color(SVG)) {
@font-face {
font-family: jewel;
src: url(jewel-svg.woff2) format("woff2");
}
}
@supports font-technology(color(COLRv1)) {
@font-face {
font-family: jewel;
src: url(jewel-v1.woff2) format("woff2"),;
}
}
Yes, that's how I would express it as well. That is: for now, without nested @supports
.
@dbaron @fantasai thoughts? Since we are all editors of Conditional 3 I assume we are of Conditional 4 too, right? @astearns @atanassov or would that need a separate resolution?
Oh wait
The declaration being tested must always occur within parentheses, when it is the only thing in the expression. source
So that is like
@supports (font-technology(color(COLRv0))) {
and so on
Oh wait
The declaration being tested must always occur within parentheses, when it is the only thing in the expression. source
So that is like
@supports (font-technology(color(COLRv0))) { and so on
This applies to declaration tests, not selector()
or font-technology()
queries.
Nitpick, I don’t think we need nested function calls, not to mention color()
might look confusing because of the other color()
function. We could just prefix keywords with color-
and feature-
, which avoids the multiple nested parens.
We could just prefix keywords with
color-
andfeature-
, which avoids the multiple nested parens.
Updated the proposed change with that suggestion, thanks.
@svgeesus asked yesterday if we could use colons (font-technology(color: COLRv0)
). I think that's not idiomatic to CSS and it's weird to have key-value pairs for some things and not others, but I'd defer to @tabatkins on that one. If he's ok with colons, I’m ok with colons.
So, that would be
/* 1. prefer COLRv1, then SVG-in-OpenType, then COLRv0 */
@font-face {
font-family: jewel;
src: url(boring.ttf) format("woff2");
}
@supports font-technology(color-COLRv0) {
@font-face {
font-family: jewel;
src: url(jewel-v0.woff2) format("woff2");
}
}
@supports font-technology(color-SVG) {
@font-face {
font-family: jewel;
src: url(jewel-svg.woff2) format("woff2");
}
}
@supports font-technology(color-COLRv1) {
@font-face {
font-family: jewel;
src: url(jewel-v1.woff2) format("woff2"),;
}
}
it's weird to have key-value pairs for some things and not others
Its also weird to have key-value pairs multiple times for the same key
@supports font-technology(color: COLRv0) AND font-technology(color: COLRv1)
so on balance I prefer the hyphenated form proposed earlier.
One note of caution - we have to consider how authors will achieve progressive enhancement on new browsers, while continuing to make their page work on old browsers. So, if we do something like this, it's more likely authors will be saying something like
@font-face {
font-family: jewel;
font-weight: 400;
src: url(jewel-fallback.woff2) format("woff2");
}
@supports font-technology(color-COLRv1) {
@font-face {
font-family: jewel;
font-weight: 400;
src: url(jewel-v1.woff2) format("woff2");
}
}
(I added the font-weight
descriptor, because it's not unreasonable to do so, and it helps inform the below point.)
This behaves differently than the existing progressive enhancement behavior of the src
descriptor:
@font-face {
font-family: jewel;
src: url(jewel-fallback.woff2) format("woff2");
src: url(jewel-v1.woff2) format("woff2" supports(COLRv1)), url(jewel-fallback.woff2) format("woff2");
}
In the second example, if the browser doesn't understand the new syntax, ~or "COLRv1",~ the entire src
line falls back wholesale (Edit: and if the browser doesn't understand "COLRv1", just that item in the src
descriptor is ignored). However, in the first example, the browser would see both @font-face
declarations together.
If the second @font-face
declaration has exactly the same descriptors, it will shadow the first one, and all is good (Edit: all is not good, see below). But, if the second one has a typo, the author has suddenly made a family with 2 fonts, and it's possible of half or even all of their text erroneously gets rendered with the wrong font.
The CSS Working Group just discussed nesting of @supports inside @font-face
.
In the second example, if the browser doesn't understand the new syntax, or "COLRv1", the entire src line falls back wholesale.
No, that is actually the reason we went for that syntax - the second src (or the second comma-separated item, if you did it that way) is dropped but the first remains, so in older browsers you end up with
@font-face {
font-family: jewel;
src: url(jewel-fallback.woff2) format("woff2");
}
I don't really have an opinion on whether we do hyphenated values or use colon-separated syntax. While we don't quite have precedent for colons inside of a function, it's just a tiny step past paren-wrapped colon-separated values, so I'd be fine with it.
My only real complain is that font-technology()
is so long. Can we just do font()
?
It was brought up on today's call that we already have the typo problem, but I think that typo problem and this typo problem are different enough that they're worth distinguishing between.
If an author uses two font face blocks to create a family, their goal is to use both fonts and associate the fonts together, having them act as a single family. If the author makes a typo, the exact boundaries between the different faces in that family will be wrong, but they will still have achieved their general purpose - associate the fonts together and have them act as a single family.
In the situation in https://github.com/w3c/csswg-drafts/issues/6520#issuecomment-905691885, their goal is not to use both fonts or create a family; but instead to implement progressive enhancement on newer browsers. If they make a typo, they haven't achieved their general purpose; they've instead triggered a totally different kind of functionality, which can behave exactly the opposite of what they were intending to do in the first place.
Edit: I'm describing typos in the numeric descriptors: font-weight
, font-stretch
, and font-style
.
In addition to the potential for unexpected behavior if the "preferred" rule does not precisely shadow the fallback, Myles's example above (repeated here for ease of reference):
@font-face {
font-family: jewel;
font-weight: 400;
src: url(jewel-fallback.woff2) format("woff2");
}
@supports font-technology(color-COLRv1) {
@font-face {
font-family: jewel;
font-weight: 400;
src: url(jewel-v1.woff2) format("woff2");
}
}
also conceals a performance footgun.
Consider the case of a browser that does implement COLRv1, so it is expected to use the jewel-v1.woff2 font. That seems fine.
What is perhaps not obvious to the author is what happens if the content styled with font-family: jewel
includes a character that is not supported by jewel-v1.woff2
. Because the jewel
family contains two faces with identical style descriptors, when the browser finds a character missing from one of them, it will be expected to fall back to the other.
It's likely, of course, that the two jewel
faces have the same character coverage, and falling back from jewel-v1.woff2
to jewel-fallback.woff2
is pointless, but the browser will nevertheless have to download the fallback resource and check its character coverage.
This could be avoided by careful use of unicode-range
, so that any character that is not covered by the v1 font is known to be excluded from the fallback face as well, but this is an added burden for authors, and seems unlikely to be done with any consistency.
@svgeesus
No, that is actually the reason we went for that syntax
Yes, you're right, I've edited https://github.com/w3c/csswg-drafts/issues/6520#issuecomment-905691885 to strike out the incorrect text, and add some clarifying text.
@litherum Could you provide a full example of the problem you’re seeing, i.e. not just the @font-face
rules but the actual usage and the problem that occurs? It seems identical to me to the existing issue. In one case mistyping a descriptor gets you the wrong face, in the other case mistyping a descriptor gets you the fallback font, which is still part of the same family. 🤷🏼♀️
@jfkthame Interesting. Is that per spec or a browser bug? I could not actually find anything in css-fonts about this.
Do note that instead of unicode-range
(which would be a pain, I agree), one could also use not
in @supports
instead of having a bare @font-face
as the fallback.
I don't think what @jfkthame describes in https://github.com/w3c/csswg-drafts/issues/6520#issuecomment-905805384 is a bug at all. I think the design of @font-face
is that each @font-face
rule adds an entry to the library of font faces that can be used. So I think things that are designed to be modifications to a single face in that library probably belong inside of a single @font-face
rule, whereas things that are designed to be separate faces should be in separate @font-face
rules.
@jfkthame Interesting. Is that per spec or a browser bug? I could not actually find anything in css-fonts about this.
This falls naturally out of the font matching algorithm. Each @font-face
rule (with the same font-family
descriptor) adds an entry to the collection of faces that belong to the given family. Font matching will filter that collection according to the requested style properties, but if there are multiple faces in the family with the same weight/width/style, they'll all remain in the matched set (it's a "composite face"), and the browser has to use the "effective character map" (intersection of unicode-range
and the font's actual coverage) to choose among them.
Note that existing sites rely on this behavior. A somewhat common "copy-inhibitiing" scheme is to pseudo-randomly split the glyphs of a font across two separate resources, so that each resource has a scattering of glyphs that are unlikely to be much use as a standalone font. The site then uses two @font-face
rules to load both resources as the same font-family
, with the same style descriptors, and relies on the browser selecting the appropriate one on a character-by-character basis.
(I'm not endorsing this practice: I think it's not particularly effective, hurts performance, and breaks things like kerning, but I see it in the wild.)
Do note that instead of
unicode-range
(which would be a pain, I agree), one could also usenot
in@supports
instead of having a bare@font-face
as the fallback.
Yes, using @supports not ...
would avoid this. That gets a bit cumbersome in complex cases: e.g. if there are two or three color-font techologies being queried, as well as variations and a couple of shaping technologies, the @supports
rule wrapping the fallback that doesn't support any of these could become pretty unwieldy.
It also feels like a footgun because it differs from how @supports
normally interacts with graceful degradation. With typical CSS properties, the user can write the generic fallback first, then write the more-sophisticated version in an @supports
rule, expecting it to override the fallback if it's supported. The performance "gotcha" here arises because @font-face
rules do not override previously-defined ones for the same descriptors; instead, they accumulate.
That gets a bit cumbersome in complex cases: e.g. if there are two or three color-font techologies being queried, as well as variations and a couple of shaping technologies, the @supports rule wrapping the fallback that doesn't support any of these could become pretty unwieldy.
I don't completely disagree, but I don't think combinations of shaping engine choice and other capabilities are very likely when there are barely any aat and graphite web fonts used let alone color or variable versions of those. I consider the most likely scenarios are variations yes/no, and perhaps 1-3 color technologies as a fallback ladder.
It also feels like a footgun because it differs from how
@supports
normally interacts with graceful degradation. With typical CSS properties, the user can write the generic fallback first, then write the more-sophisticated version in an@supports
rule, expecting it to override the fallback if it's supported. The performance "gotcha" here arises because@font-face
rules do not override previously-defined ones for the same descriptors; instead, they accumulate.
This "performance gotcha" only arises when you really have different glyph sets and that is a surprise to you as the author or font provider who delivers that CSS and the fallback font would be loaded inadvertently to check for glyph coverage.
For language/script subset fallback unicode-range
is an established and widely used technique to control that.
For color- or variations-enhanced upgrades of a monochrome static font the glyph sets are (as you mentioned yourself) likely identical. If not, unicode-range
is used to guard control fall through. In most cases, CSS and fonts are delivered from the same provider who knows the fonts' coverage. For those reasons, overall, my impression is, an accidental fall-through performance gotcha does not seem a likely footgun. I would agree with @dbaron that this seems working as intended.
A solution that avoids this accumulation issue is @supports
inside @font-face
which we can spec already by adding the css-conditionals-4 font-technoloy()
function and allowing @supports
nesting inside @font-face
. Feasible for us from an implementation point of view short-term is @font-face
inside @supports
, nesting only later, but we are supportive of allowing and implementing that in general, it will just take more time. Would this perspective / progression be sufficient for addressing your concerns, @jfkthame ?
Another solution that avoids this accumlation issue is for example to modify the font matching algorithm based on an additional technology descriptor, or going back to some kind of technology-tagging in the src:
line. I think both those avenues would lead to micro-syntaxes external to @supports
.
This "performance gotcha" only arises when you really have different glyph sets
No, my point is the performance issue arises when the preferred and fallback fonts have the same glyph set (and precisely-enumerated unicode-range
descriptors have not been used). In this case, when a character isn't supported by the preferred font, the browser will have to load the fallback as well in order to check its glyph coverage, which will be purely wasted time and bandwidth.
Nesting @supports
inside @font-face
would make it easier (as an author) to structure things efficiently, but I am not sure about the implementation complexity issues at this stage. We should also think about how adding nested-@supports
at a later stage will work for authors who need to account for older UAs that don't support that. It'd be interesting to see an example of what the eventual CSS should look like for a page that wants to use nested @supports
inside @font-face
if available, falling back to @font-face
inside @supports
for earlier browsers.
(Edited to include font matching algorithm discussion)
No, my point is the performance issue arises when the preferred and fallback fonts have the same glyph set (and precisely-enumerated
unicode-range
descriptors have not been used). In this case, when a character isn't supported by the preferred font, the browser will have to load the fallback as well in order to check its glyph coverage, which will be purely wasted time and bandwidth.
I don't understand. If they have the same glyph set, and same CMAP coverage - why would a character then not be supported by the preferred font? The load for the fallback only needs to be triggered if the primary font doesn't have coverage and such was determined during font analysis or shaping, but if coverage was already found, no load is needed.
- If the matched face is defined via @font-face rules, user agents must use the procedure below to select a single font:
If the font resource has not been loaded and the range of characters defined by the unicode-range descriptor value includes the character in question, load the font.
After downloading, if the effective character map supports the character in question, select that font.
When the matched face is a composite face, user agents must use the procedure above on each of the faces in the composite face in reverse order of @font-face rule definition.
This is the spec text from the font matching algorithm how to resolve when at the end of matching multiple fonts are in the set of web fonts, which is the case if we have an accumulated set. This does not force the UA to download both in the set, but says that the last @font-face
defined (the preferred one), should be tried first.
In the case of color and variations upgrades of a conventional font with same glyph set, when would such a situation really be the case?
I don't understand. If they have the same glyph set, and same CMAP coverage - why would a character then not be supported by the preferred font? The load for the fallback only needs to be triggered if the primary font doesn't have coverage and such was determined during font analysis or shaping, but if coverage was already found, no load is needed.
The point is that for a character that turns out not to be supported (by either the primary or fallback fonts, as they have the same glyph set) the fallback nevertheless has to be loaded in order to determine its coverage.
So if you use the "obvious" pattern to implement progressive enhancement (or graceful degradation, to look at it from the other side):
@font-face { font-family: my-icons; src: url(my-icons-bw-fallback.woff2); }
@supports font-technology(color-COLRv0) {
@font-face { font-family: my-icons; src: url(my-icons-flat-colors.woff2); }
}
@supports font-technology(color-COLRv1) {
@font-face { font-family: my-icons; src: url(my-icons-gradient-colors.woff2); }
}
a browser that implements both COLRv0 and COLRv1 will see all three of the @font-face
rules.
Now, when the browser encounters a character X that is not supported by my-icons
, it will fail to find it in the COLRv1 resource; so it will load the COLRv0 font to check its coverage. Again, it'll fail to find the character, so it'll load the BW font; and again, fail to find the character. So it has loaded all three resources in order to check the character coverage of each, even though the author's intention was to just use the best supported version of the three.
To avoid this, the author has to construct more complex @supports
rules such that any given browser will only "see" the single preferred one of the @font-face
declarations, rather than all the forms it supports. Given, for example, five possible color-*
technologies, this could get quite unwieldy.
Do note that instead of
unicode-range
(which would be a pain, I agree), one could also usenot
in@supports
instead of having a bare@font-face
as the fallback.Yes, using
@supports not ...
would avoid this. That gets a bit cumbersome in complex cases: e.g. if there are two or three color-font techologies being queried, as well as variations and a couple of shaping technologies, the@supports
rule wrapping the fallback that doesn't support any of these could become pretty unwieldy.
One way to make this easier would be to define aliases like color-any
or color-COLR
(which encompasses both v0 and v1).
One way to make this easier would be to define aliases like color-any or color-COLR (which encompasses both v0 and v1).
Or (since we have the hyphen scheme) just color
and feature
; although I worry what that means when in the future we add, say, color-supercolor
or features-supertype
which are, naturally, not universally supported. Do they mean "supports at least one" or "supports all"?
After your explanations @jfkthame , I agree that the behaviour with @supports
in that sense would be different from what was in the fonts spec, where only the first, highest-capability-tagged resource of listed supported resources in the src:
line would be loaded for a glyph for which there is no coverage.
As you noted, using unicode-range:
for declaring the supported range does make this problem disappear altogether. In my opinion, high performance font loading strategies should and do already declare unicode-range, tools like Wakamai Fondue's beta version help in declaring it, Google Fonts does it for subsetting into script ranges. I find that an acceptable tool and established strategy for authors who care about font performance.
Also agree with @LeaVerou that catch-all aliases help with describing negative @supports
rules to exclude fonts from the set.
By the way I notice that the specification describes in detail how to handle a src
descriptor whose value is a comma-separated list with more than one list item (and most examples use that style) but is fairly silent about two other cases:
src
descriptors in the same @font-face
@font-face
with all the font-selecting descriptors identicalhat needs to be covered at the same level of detail.
As you noted, using
unicode-range:
for declaring the supported range does make this problem disappear altogether. In my opinion, high performance font loading strategies should and do already declare unicode-range, tools like Wakamai Fondue's beta version help in declaring it, Google Fonts does it for subsetting into script ranges. I find that an acceptable tool and established strategy for authors who care about font performance.
Yes, I'm aware of how Google Fonts (for example) uses unicode-range
to split fonts into script ranges. For a major service that can invest in tooling and expertise to do this, that's clearly beneficial for performance.
I'm still a bit concerned, though, about the case of an author who is, for example, just adding a custom icon, logo or in-house decorative/branding font to a site, and hand-writing the CSS to support it; this is the kind of scenario where it's easy to imagine authors writing code along the lines of the example above, without realizing that they're causing redundant resource loads.
The CSS spec even suggests that it's reasonable to use broad-range unicode-range
descriptors rather than enumerating the precise coverage of the font:
When the font is used, the effective character map is the intersection of the codepoints defined by 'unicode-range' with the font’s character map. This allows authors to define supported ranges in terms of broad ranges without worrying about the precise codepoint ranges supported by the underlying font.
So an author might quite reasonably write unicode-range: U+1f6??
for an icon font that includes a sparse subset of the entire 1F6xx block; but then if a symbol from that block not covered by the webfont is encountered, all the fallback versions will be fetched before finally resorting to the system's standard emoji font.
I think we should try to design the solution here such that authors who write their font-enrichment CSS in the "natural" way do not thereby create a hidden performance drag. Quite how best to do that isn't yet clear to me.
The CSS spec even suggests that it's reasonable to use broad-range unicode-range descriptors rather than enumerating the precise coverage of the font:
I'd be in favour of changing that recommendation.
In the discussion so far, I see these constraints - am I missing something, or are there differing views?
@supports
inside @font-face
so we seem to agree on a preference to have it at the top level. @support
+ a font-technology()
function is placed at the top level, we may run into the cascading / accumulation issue (with varying opinions on how severe that issue is). font-technology()
supports function, nothing per-se stops it from being used at the top-level. Options we've been pondering in an internal discussion:
@font-face
remotely similar to a C++ final
keyword - meaning that there shouldn't be further search for other segmented font faces in the same family. This would require people to use it, otherwise the same cascading problem remains. And due to the fact it needs to be actively used it becomes somewhat redundant with unicode-range
which addresses the same issue. Alternatively, but probably too intrusive, have a new @font-face-no-cascade
(name to be bikeshod) with a different matching algorithm / cascading behavior?url()
alias blob object to point to a url()
resource inside a top-level @supports
- then use that alias/reference in a src:
descriptor inside @font-face
s? Are there precedents for something like that?@supports
at the top level and despite the implementation hurdles opt for a nested approach, add prose to the new font-technology()
function that it resolves to false when used outside the nested scope? I apologize, but it's not entirely clear to me what this final
concept would bring that's not already done by omitting unicode-range
entirely.
The way I mean it, taking Jonathan's example and adding a hypothetical fallback: no-cascade;
@font-face { font-family: my-icons; src: url(my-icons-bw-fallback.woff2); fallback: no-cascade; }
@supports font-technology(color-COLRv0) {
@font-face { font-family: my-icons; src: url(my-icons-flat-colors.woff2); fallback: no-cascade; }
}
@supports font-technology(color-COLRv1) {
@font-face { font-family: my-icons; src: url(my-icons-gradient-colors.woff2); fallback: no-cascade; }
}
Without fallback: no-cascade;
the my-icons family would consist of three fonts that have accumulated into the my-icons font at identical attributes for weight, stretch, style. So when checking for a character that's not contained in the icon font, first my-icons COLRv1, then COLRv0, then bw have to be loaded, in that order. If there is a hyptohtetical fallback: no-cascade;
descriptor, we can spec that to mean: after checking the first one (in reverse order of CSS declarations), do not look further in this cascade and do not load any others.
The way I understand it: Omitting unicode-range
would usually lead to all of them being loaded and checked, until system fallback is used. Using unicode-range
with tight codepoint ranges means a behavior similar to the hypothetical fallback: no-cascade;
, meaning: if unicode-range
indicates there's not going to be coverage, do not load this font.
The typical use-case here is that there are multiple resources available, each based on different font technologies, and the author wants to use the single "best" one that is supported by the UA. The alternatives should be "hidden" so that the UA doesn't waste time (and bandwidth) on them.
Doing this entirely with @supports
becomes increasingly cumbersome as more technologies are considered, as each @supports
block is independently defined; to ensure that exactly one of the alternatives is chosen by any given UA, the conditions have to be carefully constructed to not only check for support of a given technology, but also for non-support of any "better" technology.
It would be more natural and ergonomic for authors if this could be done by chaining the @supports
blocks in an "if-elseif-elseif-else" way. Inventing some syntax here, we might have:
@supports font-technology(color-COLRv1) {
@font-face { font-family: my-icons; src: url(my-icons-gradient-colors.woff2); }
}
@else-supports font-technology(color-SVG) {
@font-face { font-family: my-icons; src: url(my-icons-svg.woff2); }
}
@else-supports font-technology(color-COLRv0) {
@font-face { font-family: my-icons; src: url(my-icons-flat-colors.woff2); }
}
@else {
@font-face { font-family: my-icons; src: url(my-icons-bw-fallback.woff2); }
}
I think a similar @else-
extension to conditional rules would also be useful for @media
. Is this something we could explore further?
Yes, the syntax you drafted resembles very closely the proposal that @tabatkins previously published here: https://tabatkins.github.io/specs/css-when-else/ which was briefly mentioned in the notes for the last CSS WG meeting.
Yes, perhaps if we explore this further we'll have a future ergonomics improvement for @supports
& font-technology()
. We could define the latter now and invest in @when + @else
- which might be a viable way forward.
Pursuing @else
would satisfy my concerns, too. @else
would have to come first, though, so we don't teach authors to do the wrong thing because the better tools aren't available yet.
If we have @else
, I don't see any reason why other at-rules couldn't be inside it, too. @font-face
doesn't need to be special.
My only remaining thought is general distaste for using different mechanisms to select truetype/opentype vs COLR/sbix.
Relevant issue for an if-elsif-else
block is https://github.com/w3c/csswg-drafts/issues/112
I agree that we need a syntax where:
Note that the existing, ugly, jammed-into-format syntax already has those properties; and I don't want to see it adopted anyway just for expediency because we take too long to add an if-elsif-else
construct. I also don't want us to adopt a solution "for now" that has the multiple download and fallback problems that @jfkthame has explained so well.
My only remaining thought is general distaste for using different mechanisms to select truetype/opentype vs COLR/sbix.
We will need to keep format
for legacy reasons but nothing prevents our adding a format test to the new syntax along with the font technology tests. Although the spec already requires treating truetype and opentype as synonyms so format
is mainly testing for new compression/deployment wrappers (woff, woff2, IFT, etc)
@litherum wrote:
@else would have to come first, though, so we don't teach authors to do the wrong thing because the better tools aren't available yet.
@svgeesus wrote:
I also don't want us to adopt a solution "for now" that has the multiple download and fallback problems that @jfkthame has explained so well.
If we add an @else
construct, and only after that define a font-technology()
function for feature detection and @supports
conditionals, we supposedly gain a form of educational advantage.
But if both are shipped in the future, whom are we preventing from still writing the less performance optimised version that @jfkthame explained where the font blobs accumulated into a family through which we need to fallback?
Aren't we overestimating this advantage of shipping a future @else
before the feature detection function, given that it will still be possible to spell out the less performant version (which I still consider a niche case given that font CSS is generated in the majority of use cases)? Aren't we undererstimating the uptake of a better @else
construct once it's available?
The CSS Working Group just discussed Nesting @supports inside @font-face / font tech queries
, and agreed to the following:
RESOLVED: Adopt if/else as next level of css-conditional
RESOLVED: Accept the PR
This is related to #633 and this part of css-fonts-4: https://drafts.csswg.org/css-fonts-4/#font-face-src-parsing
A bit late to the party, but I wondered: what if, instead of adding yet another microsyntax for feature detection, we use
@supports
with an appropriatefont-technology()
function (or whatever we want to name it)?We recently resolved to allow nesting of conditional rules inside regular rules, so that authors can do things like:
What if this was allowed in
@font-face
as well? That way, authors could do something like:or
instead of:
Benefits:
src
that this microsyntax has introducedCSS.supports()
API (current syntax also allows programmatic detectability but in a much clunkier wayformat()
is a list of keywords, with no hierarchy. Also, the syntax makes it unclear whether this is a feature query for the browser, or informing the browser what the font file supports.Downsides:
Unlike conditional rules in general, feature queries do not change during the lifetime of the page load, and thus this should not trigger re-interpretation of the
@font-face
rule or be more heavyweight in any other substantial way.If such syntax is used with today's browsers, they drop the
@supports
rule but keep the@font-face
rule, so it does appear forwards compatible. testcaseThere are no implementations of the current syntax, so it may not be too late for the change.
Thoughts, @svgeesus @litherum @fantasai @tabatkins?