w3c / csswg-drafts

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

[css-fonts-4][css-nesting] Nesting of @supports inside @font-face and font technology feature queries #6520

Closed LeaVerou closed 3 years ago

LeaVerou commented 3 years ago

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 appropriate font-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:

a {
    color: red;

    @supports (foo: bar) {
        color: green;
    }
}

What if this was allowed in @font-face as well? That way, authors could do something like:

@font-face {                                                                                                                                        
    font-family: Foo;
    src: url("foo.woff2") format("woff2");

    @supports font-technology(variations) and font-technology(COLR) and font-technology(palettes) {                                                                                                                                         
        src: url("foo.woff2") format(woff2);
    }
}

or

@font-face {                                                                                                                                        
    font-family: Foo;
    src: url("foo.woff2") format("woff2");

    @supports font-technology(variations COLR palettes) {                                                                                                                                       
        src: url("foo.woff2") format(woff2);
    }
}

instead of:

@font-face {                                                                                                                                        
    font-family: Foo;
    src: url("foo.woff2") format("woff2");                                                                                                                                          
    src: url("foo.woff2") format(woff2 supports variations color(COLR) palettes);                                                               }

Benefits:

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. testcase

There are no implementations of the current syntax, so it may not be too late for the change.

Thoughts, @svgeesus @litherum @fantasai @tabatkins?

litherum commented 3 years ago

I added 2 comments to https://github.com/drott/csswg-drafts/commit/62cfd95a5f52604c952e4aa37be6e4d6386f88f5 asking for a note to implementors.

svgeesus commented 3 years ago

@drott I'm waiting for this commit to surface as a PR on conditional 4, so that I can then merge in Tab's proposal without a merge conflict

svgeesus commented 3 years ago

I'm waiting for this commit to surface as a PR on conditional 4, so that I can then merge in Tab's proposal without a merge conflict

Both now done.

svgeesus commented 3 years ago

It also might be worth saying that implementing this depends on implementing @else.

I added a pair of examples that shows how @else is a cleaner solution for conditional @font-face

fantasai commented 3 years ago

Posting what I think we should do here.

Considerations:

Proposal:

<font-format> = <format-string> <ident>*
<format-string> = format type keyword registered in css-fonts or any registered MIME type keyword under the `font/` hierarchy

src: <url> format(<font-format>), ...;
@supports font-format(<font-format>) { ... }
drott commented 3 years ago

Thanks for summarizing your remaining feedback.

  • Extend the format() function to take keywords after the format string. These keywords represent the various font tech features. This allows simple forking of the URL, and can ship first.

That's the thing I tried to ship and received the feedback from TAG that micro-syntax is to be avoided there. Even if we flatten the grammar here to a sequence of keywords, it's still a simple grammar. The TAG's recommendation was to consider using @supports, which we resolved on.

  • Extend @supports to add a font-format() function with identical syntax. This allows higher-level conditionals that can control more than just the download URL.

It was my understanding (@svgeesus and @LeaVerou 's as well, as stated in #6635 "[css-fonts] Remove [supports #]?)]? from src: descriptor?") that we remove the format() syntax in the src: descriptor. It seems to me that that also better follows the intention of the TAG's advice on avoiding micro-syntax. Even if we reference the syntax for format() in src: from CSS conditionals grammar, the way I interpret the TAG's feedback is not not have complex syntax in a very particular place outside @supports.

  • Add @else to allow fallback rules to be easily made conditional on negation of the @supports query.

@when and @else were already added, per #112, https://drafts.csswg.org/css-conditional-4/#else-rule in daeb7c32425c69e63355dd2580deff41ccc4ec4c and 0a8f9f90f0095c17328dfccd9eacda77d66ba693, weren't they?

So now that we have the font-technology() function in for @supports, and we have @when in the spec, it looks to me like we're in a good spot.

I also don't see any alternative font container or compression formats that strictly restrict or bring their own font features other than SFNT / OpenType and WOFF2. In other words, everything's in an OpenType container these days, optionally WOFF(2) compressed, whether it's OpenType layout, AAT, graphite, color or non color, variable or not. But we do have the flexibility of adding format keywords in the font-technology() function as needed for alternatives if they arise.

LeaVerou commented 3 years ago

Please note that the TAG feedback was not "You must use @supports" but "Please exhaust the possibility of using existing mechanisms for feature support before introducing a new one" (src). The TAG does not enforce any particular design, that is not the point of a design review.

At this point, the issue has been discussed thoroughly in the CSS WG, and there are some notable practical advantages to an inline syntax. Furthermore, a list of keywords is not a microsyntax, otherwise half of CSS properties would be using microsyntaxes. The worst of all worlds is the current state of specs, with two different syntaxes for querying support for font technology, this is one we should move away from as soon as possible. Personally I think what @fantasai suggests is reasonable, assuming it's just a list of keywords and nothing else.

@fantasai with that simplification src won't need special parsing rules, right?

drott commented 3 years ago

I understood it was mainly a TAG recommendation / invitation to explore options. Since we arrived at a @supports based syntax + @when/@if I did not see a need for a somewhat redundant solution but I agree that there is a practical advantage to the format() syntax in src:, in particular until @when/@if arrives.

If a flat list of keywords is acceptable inside a format() , and the two specs refer to the same list, no objections from my side to harmonizing the two syntaxes/keyword-lists, defining a list of keywords in CSS fonts that both spec texts refer to and renaming font-technology() to font-format() in CSS conditionals 4 and. I can try to prepare draft PRs.

fantasai commented 3 years ago

@fantasai with that simplification src won't need special parsing rules, right?

Correct.

fantasai commented 3 years ago

Agenda+ to discuss https://github.com/w3c/csswg-drafts/issues/6520#issuecomment-934811808 and discuss schedule for publication/review/shipping.

svgeesus commented 3 years ago

Font tech features are dependent on the font format. We do have multiple formats which are effectively interchangeable wrappers right now, and for those the wrapper format and feature tech are independent queries, but this isn't true in consideration of all font formats past and future.

That isn't really true currently, which makes it a poor fit for format.

Agreed in the past there were

and of course we can't predict what may happen more than a few years into the future, but currently in font format terms everything is an sfnt which could be called either TrueType or OpenType. And (also stuffed into format) there may be compression/transfer tech on top (WOFF|WOFF2|IFT) but that does not alter the font technologies available, it just compresses or subsets them.

That said I don't object to renaming font-technology() to font-format() if that is what works and we agree on. But it makes things less clear.

LeaVerou commented 3 years ago

Btw if we accept @fantasai's proposal, then the format() function would need to accept these keywords even without a format string. We don't want people to have to write things like @supports format(woff color-COLRv1) when they really mean @supports format(color-COLRv1). Right now <font-format> is mandatory.

svgeesus commented 3 years ago

We would also need to test what happens with

@font-face{
  font-family: foo;
  src: url("foo.woff") format("color-COLRv1");
}

because I suspect with an omitted actual format, the font is assumed to be an unsupported format and is not loaded. As opposed to

@font-face{
  font-family: foo;
  src: url("foo.woff") format("woff color-COLRv1");
}

In general the whole format() thing is a buggy hack with huge backwards-compatible fragility; adding anything is highly constrained and we can't do good design in this space. I would be much happier with (as we originally resolved)

  1. dropping [supports <font-technology>#]? from @font-face src entirely
  2. using the conditional features in Conditional 4 instead, together with font-technology()
drott commented 3 years ago
@font-format {

@svgeesus , did you mean @font-face in those two examples?

svgeesus commented 3 years ago

Heh, yes. Corrected.

litherum commented 3 years ago

This issue is on the CSSWG agenda for today, and I'd like to participate, but I’m not feeling well (food poisoning?). Can we postpone it from the CSSWG agenda for 1 week?

LeaVerou commented 3 years ago

Resolved in today's meeting to discuss in breakout right before the regular meeting time next week. @drott could you please coordinate the logistics of said breakout?

drott commented 3 years ago

Yes, I'll send details and and invite to a Meet call to the private mailing list.

fantasai commented 3 years ago

@svgeesus From your example, format("color-COLRv1") and format("woff color-COLRv1") would both fail to load because neither string is a recognized font format.

svgeesus commented 3 years ago

Within the comma separated list, things are supposed to be space separated. Which is why

format(opentype supports incremental)

works in a backward compat way. As you say, older browsers treat the whole thing as one unknown format that happens to contain spaces.

litherum commented 3 years ago

@fantasai's proposal in https://github.com/w3c/csswg-drafts/issues/6520#issuecomment-934811808 makes sense to me.

Regarding the question of whether or not we can avoid changing the src descriptor, I think it comes down to how authors would implement fallback with @supports instead. Ideally, the same fallback code should be applied in both situations: 1) you're on an old browser that doesn't understand the new @supports syntax, and 2) You're on a new browser, but the browser doesn't actually support the font technology you're requesting. I think handling fallback for both these situations using the same code should be considered a requirement (otherwise it's too hard and authors will get it wrong).

A quick check shows that, in today's browsers (which are the first situation above),

@supports not font-format(truetype variations) {
    div {
        background: blue;
    }
}

does not cause <div>s to get blue backgrounds.

On the other hand,

@supports not ( font-format(truetype variations) ) {
    div {
        background: blue;
    }
}

does cause <div>s to get blue backgrounds. Maybe the solution is to change <supports-decl>'s definition from ( <declaration> ) to ( <declaration> | font-format(<font-format>) )? I'm not sure we can actually use this, though, because I think it was an intentional choice to make the thing between the parentheses a <declaration>.

If we can come up with a solution that satisfies these constraints, we can leave the src descriptor alone. However, if we can't, then I think we need to add expressiveness to the src descriptor because that's how authors would achieve correct fallback behavior.


Of course, another way to solve this problem is to wait to implement (but not necessarily spec) this fancy new @supports nesting stuff until after @else is implemented. That way, we would encourage authors to write code like

@supports font-format(truetype variations) {
    @font-face {
        /* variation font stuff */
    }
} @else {
    @font-face {
        /* fallback stuff */
    }
}

If these two features (@else, then @font-face nesting) were implemented in-order, we wouldn't need to modify the src descriptor. But, if they were implemented in reverse order, that would be bad unless we come up with some way of solving the problem described above (above the -----------).

LeaVerou commented 3 years ago

@litherum My understanding of the @supports grammar is that any negated condition needs to be in parens. If that's correct, that would explain why your first rule didn't work: because it's invalid syntax. Perhaps @tabatkins could confirm.

drott commented 3 years ago

I tend to agree with @LeaVerou here, with

<supports-condition> = not <supports-in-parens>
<supports-in-parens> = ( <supports-condition> ) …

(https://drafts.csswg.org/css-conditional-3/#at-supports)

only the second part of your example is valid syntax.

@supports not ( font-format(truetype variations) ) {
    div {
        background: blue;
    }
}

I am assuming the intent in your examples is to get blue background, or in other words: "use this block for fallback" is the intention.

It seems to me we we can conclude this syntax (with condition in parentheses) would fulfil your expectation of code a) working in old browsers without font-technology/font-format support, as well as b) in browsers that do understand the condition function.

css-meeting-bot commented 3 years ago

The CSS Working Group just discussed fonts, continued (beginning of log missing), and agreed to the following:

The full IRC log of that discussion <dbaron> topic: fonts, continued (beginning of log missing)
<dbaron> github: https://github.com/w3c/csswg-drafts/issues/6520
<lea> q?
<fantasai> astearns: jfkthame is suggesting to re-use format() not add new function
<fantasai> jfkthame: ...
<fantasai> chris: Because existing format function requires a font format
<fantasai> jfkthame: I don't see why not
<fantasai> jfkthame: would work just the same as not specifying format function at all
<fantasai> jfkthame: except now filtered by the tech keywords you just expressed
<lea> q+
<fantasai> drott: Currently state of format() is quite messy and not quite inteorp
<florian> q- later
<fantasai> drott: Some accept strings, some keywords, some both
<fantasai> drott: New function we have the potential of cleaning that up a bit
<drott> q-
<astearns> ack fantasai
<Zakim> fantasai, you wanted to ask about fallback in legacy browsers and to
<drott> q+
<fantasai> fantasai: One point about format() is it wouldn't invalidate src declaration in older browsers
<fantasai> fantasai: whereas a new function would
<fantasai> fantasai: As for jfkthame's proposal, I think it would work
<fantasai> fantasai: it would mean that the font-tech keywords would be in the same namespace as any format keywords, so you'd have to be careful to avoid name clashes
<fantasai> fantasai: but otherwise seems easily parsable
<drott> q-
<fantasai> astearns: One argument against format() is that then we can't use the same syntax in @supports, as context is lost has to be font-format()
<astearns> ack PeterCon
<fantasai> PeterCon: Might be edge case, but different tech ...
<fantasai> PeterCon: Variations technology is binary, font is either variable or not
<fantasai> PeterCon: feature capabilities are mutually exclusive
<TabAtkins> I think `@supports font-format()` works fine - the connection to @font-face's format() seems clear that you're qualifying it now that it's in a more generic context.
<fantasai> PeterCon: Font could have either AAT or Graphite, but impl only wants to use one or the other
<fantasai> PeterCon: If the font has both, perhaps author prefers one or other
<fantasai> PeterCon: So may want to specify which
<fantasai> PeterCon: In a fallback case, they might tolerate the second choice and not the first
<fantasai> PeterCon: Color tech is potentially complementary
<fantasai> PeterCon: Font might have both and use it for different glyphs
<fantasai> PeterCon: iN that case, might be happy to have both
<fantasai> PeterCon: An author potentially might say, I want to use this font but only use the ?? color list, not any of color-v0
<myles> q+
<fantasai> PeterCon: Idk if these are too edge case to worry about
<fantasai> drott: I don't think necessarily that we want to describe which part of font to use
<fantasai> drott: this is just syntax for ???
<fantasai> drott: I don't think we have tools for choosing the tech, that would be a separate thing
<fantasai> myles: ...
<drott> s/ ???/selection \/ download choice/
<fantasai> myles: It's not a subsetting feature
<chris> s/?? color/sbix color/
<astearns> ack lea
<fantasai> lea: It occurred to me that format wasn't originally for feature description, but to meta describe the URL
<fantasai> lea: This is a woff font, this is opentype
<fantasai> lea: [something about calling something woff but getting an opentype and whether it opens it or rejects]
<fantasai> myles: I think it's specced
<chris> format was added because we did not (at the time) have font MIME types
<fantasai> myles: If browser has already downloaded font, why should not use it?
<fantasai> lea: I thought it was defined to describe the resource
<chris> q?
<astearns> ack florian
<fantasai> lea: but maybe it was defined as feature detection?
<fantasai> fantasai: kinda was
<fantasai> florian: fantasai noted that putting both tech keyword would share namespace
<fantasai> florian: could have some syntax to divide, e.g. "with color-v1"
<fantasai> florian: Issue
<fantasai> florian: We say space-separated list is "and" not "or"
<fantasai> florian: fine, but might be two different meanings for "and"
<myles> [ ( woff | opentype | svg)? WITH [variations, color-sbix, opentype-features]# ]
<fantasai> florian: Do you support this and that?
<fantasai> florian: do you support both in the same file?
<fantasai> florian: slightly different question
<fantasai> florian: just to make sure this is forward proof might need to think about it
<astearns> q?
<astearns> ack myles
<fantasai> myles: Right now there's nothing in CSS that lets you say "use this part of the font, but not the other"
<fantasai> myles: I think that's good, because I don't think it's implementable
<fantasai> myles: I don't think we should add a feature that let's you ignore certain tables
<fantasai> fantasai: If we were to do so, I think it wouldn't be in src, should be its own descriptor
<drott> fantasai: if we would do that, it should be outside src descriptor
<fantasai> astearns: It sounds to me that we do have consensus to modify src descriptor to add font-tech detection somehow
<florian> Myles' sample syntax higher up is what I mean, except for the comas (and with an allowance for bikesheding the keyword WITH)
<fantasai> myles: Tab said something in IRC that made a lot of sense
<fantasai> myles: Could do jfkthame's idea for extending format()
<fantasai> myles: and have the same value syntax but different function name in @supports
<fantasai> fantasai: Yes, that was my proposal in the issue (using format() and font-format())
<florian> q?
<florian> q+
<fantasai> chris: Or we could use font-technology(), I'd prefer that
<fantasai> chris: but won't stand in the way
<lea> +1 to chris's point
<fantasai> chris: I think it's poor design to ???
<drott> s/???/throw it all in the format descriptor/
<fantasai> myles: I think if we're adding tech queries to @supports, should also allow format queries. So if we want distinct functions, then should have both in @supports
<drott> s/descriptor/function/
<fantasai> florian: Did anyone address fantasai's point about dropping the src descriptor if adding new function
<fantasai> florian: My understanding is if we extend format() will be OK, if add new function will throw out entire src declaration
<fantasai> myles: I'm not sure that distinction is true, thinking to how we parse src ...
<fantasai> myles: if you put format(keyword keyword) maybe it gets dropped entirely
<fantasai> florian: could put it all inside one string?
<fantasai> [no that's terrible]
<fantasai> myles: You do that by having two declarations
<fantasai> chris: Dropping one item in list is OK, dropping entire declaration is a bit of a problem
<fantasai> florian: If space-separated keywords in format(), do we drop the entire declaration or treat as unknown format?
<astearns> ack florian
<fantasai> chris: Unknown format
<fantasai> florian: probably should check implementations
<fantasai> lea: In my test seems like entire descriptor is dropped
<fantasai> florian: that's sad
<fantasai> lea: So I think we should use a new function
<fantasai> astearns: I think we can resolve that we add font-tech detection to src descriptor
<fantasai> astearns: is that the case? anyone arguing against and only wants @supports?
<lea> FYI to test I was using this: https://codepen.io/leaverou/pen/c02cfbe57388cca2399ee427c58e9f19 and checking what requests are sent to my localhost
<fantasai> RESOLVED: Modify src to allow for font-tech detection per URL
<fantasai> astearns: Next, can we decide on whether we're extending format() or adding new function?
<fantasai> lea: I just tested chrome
<fantasai> [fussing with the test]
<fantasai> florian: Another thing we can resolved, whichever form we add to add to src descriptor, we will expose that to @supports, possibly with font- prefix
<lea> Just tested Firefox, same
<fantasai> astearns: objections to that?
<fantasai> RESOLVED: Same syntax available in src, will be available also in @supports, possibly with a font- prefix
<drott> slight preference for font-technology
<fantasai> florian: ...
<lea> slight preference for font-tech as well
<fantasai> chris: Spec doesn't say, but fine to add that
<fantasai> astearns: I have a slight preference for a separate function, mainly because it makes the microsyntax we're adding slightly more clear
<fantasai> astearns: font-tech is not a format, and having two separate names for what you're specifying is very slightly better
<fantasai> myles: I'd like to hear from jfkthame
<florian> s/.../if we were to go for two functions, would both format and the new function be allowed in @support/
<drott> also solves migrating away from messy state of what format() is
<fantasai> jfkthame: I don't have a strong view one way or other, particularly if we don't get a forward-compat benefit from using format()
<drott> q+
<fantasai> jfkthame: To my mind these are very similar feature support queries, whether feature of a particular format or feature of a particular technology with the font
<astearns> ack drott
<fantasai> jfkthame: if people wnat to separate them, it's OK with me
<fantasai> drott: Do we remove the current ???
<fantasai> lea: yes
<fantasai> chris: Yes, that should all go
<fantasai> astearns: So we will add a font-technology() function with some amount of the keywords we've discussed
<fantasai> RESOLVED: remove "supports <font-technology>#"
<fantasai> florian: if we add format() to @supports, we have to add a font- prefix
<fantasai> florian: so shouldn't it be removed from font-technology() within src?
<fantasai> chris: Yes, let's be consistent
<fantasai> +1
<jfkthame> +1
<fantasai> lea: I thought one of the benefits was to be the same
<fantasai> astearns: but consistency is probably preferale
<fantasai> astearns: Any objections to technology()?
<florian> tech? capability?
<fantasai> fantasai: No, but I would prefer a shorter name if we can find one
<fantasai> lea: yes, please
<fantasai> RESOLVED: add technology() and open bikeshedding issue
<lea> given that Chrome needs to ship this soon, if we don't bikeshed soon, it's just de facto font-technology
<fantasai> RESOLVED: Add font-technology() and font-format() to @supports
<fantasai> with same syntax within the parentheses
<fantasai> drott: do we need both?
<florian> q+
<fantasai> astearns: prefer to add now, if ppl have objections can remove in the future, but seemed there were some use cases
<fantasai> florian: Point about "and" having two meanings, is it a concern?
<astearns> ack florian
<lea> what if we just add format-* keywords to font-technology()?
<fantasai> fantasai: You're "and"-ing over a single font file...
<fantasai> myles: The question is what if browser supports two different technologies, but not in the same font
<fantasai> florian: I guess you just choke on trying the use the download, which is wasteful but maybe not an issue
<fantasai> PeterCon: The example you gave was variable font with SVG table
<fantasai> PeterCon: likelihood of creating such a font is not great
<fantasai> myles: there's no way to describe variableness in SVG
<fantasai> Meeting closed.
astearns commented 3 years ago

Full minutes posted here: https://www.w3.org/2021/10/20-css-minutes.html

astearns commented 3 years ago

Since this issue thread is really long and pulls in a lot of related topics, please consider opening new focused issues based on the resolutions above and/or the spec edits that will be made soon. For instance, if we need to bikeshed the technology() term it would be better to have a new issue just for that. Or if the keywords edited in for the new function need changes, that would be excellent as a separate issue.

svgeesus commented 3 years ago

Edits made to CSS Conditional 4, CSS Fonts 4, and CSS Fonts 5.

svgeesus commented 3 years ago

Extensions to the @supports rule Parsing the src descriptor (4) and Parsing the src descriptor (5) Font formats Font technologies

LeaVerou commented 3 years ago

Extensions to the @supports rule Parsing the src descriptor (4) and Parsing the src descriptor (5) Font formats Font technologies

I thought we could get rid of the parsing weirdness around src now?

svgeesus commented 3 years ago

I thought we could get rid of the parsing weirdness around src now?

We got rid of the weirdness around format supports whatever. We still have a descriptor wchich is a comma-separated list of items, where each item is a url() followed by an optional format() and an optional technology() so we still need to describe how to work out the winning item.