w3c / csswg-drafts

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

[css-values-4] Resolve `<percentage>` representing `<number>` or `<angle>` at parse time #9395

Open cdoublev opened 1 year ago

cdoublev commented 1 year ago

Context

There appears to be almost perfect browser interop and consistency between specs for serializing a <percentage> with a <number> as a component of a specified value:

The exception is in filter as an argument of some <filter-function>s, because Filter Effects does not require this.


Problem 1

In scale, which is defined with [<number> | <percentage>]{1,3}, the 3rd value must be omitted if it is 1, and the 2nd value if it is the same as the 1st value.

However it is not clear whether 0% 0 100% should serialize with 0% or something else, because it is not clearly defined when <percentage> should be resolved.

Serializing 0 requires to resolve the 1st and 3rd values before trying to omit the 2nd value. Serialization is not "atomic".

Problem 2

CSS Values requires to try resolving a <percentage> in a math function (emphasize added):

If root is a percentage that will be resolved against another value, and there is enough information available to resolve it, do so, and express the resulting numeric value in the appropriate canonical unit.

However a <number> has no canonical unit, and <percentage> and <number> cannot be combined, therefore a percentage will always be simplified down to a single value. It does not need to be resolved to a <number>, except for sRGB color functions.

<angle> is the only other numeric type that can be mapped from a <percentage> at parse time. The only context <angle-percentage> values appear is <conic-gradient()> and browsers do not resolve them.


Suggestions

  1. either define explicitly to resolve <percentage> to <number> at parse time (when there is enough information available) or status quo but provide clarifications/examples to serialize scale without optional values and serialize <percentage> with <number> in the related <filter-function>s
  2. fix the above requirement to simplify a math function:
- If `root` is a percentage that will be resolved against another value, and there is enough information available to resolve it, do so, and express the resulting numeric value in the appropriate canonical unit.
+ If `root` is a percentage that will be resolved against another dimension value, and there is enough information available to resolve it, do so, and express the resulting numeric value in the appropriate canonical unit.
cdoublev commented 1 year ago

Related: #5642. Because if you resolve <percentage> to <number> before/while serializing, I think you open the door to resolving other static values.

tabatkins commented 12 months ago

However a <number> has no canonical unit

Okay, technically I don't define that numbers have a canonical unit, because they don't have a unit at all (or they all use the null unit). But this is English, not C++; I think it's clear enough from context what "expressed in its canonical unit" means for numbers (aka, no-op).

<percentage> and <number> cannot be combined

While percentages and numbers cannot be combined, as in added/subtracted, you can absolutely have a percentage that resolves against a number.


I don't have a particular opinion on whether we should specify that %s are eagerly simplified at parse time when resolved against numbers. I'd be fine with that, or with trying to get impls to align on only simplifying at computed-value and used-value time (except for the color function exception we've already talked about).

Agenda+ to decide this.

css-meeting-bot commented 10 months ago

The CSS Working Group just discussed [css-values-4] Resolve `<percentage>` representing `<number>` or `<angle>` at parse time, and agreed to the following:

The full IRC log of that discussion <dael> TabAtkins: Pointed out that while the spec says we simplify trees at computed and used time. There's significant browser interop at resolving percents of numbers at parse time. Calc(50%) in opacity and you read back immediately you get .5
<dael> TabAtkins: I don't have much of an opinion. I'm fine leaving spec as-is or with modifying spec to say % resolved against numbers are resolved at parse time. Either is okay with me, so question of what do browsers want to do
<dael> fantasai: Another relevent question is would authors have an opinion on it
<dael> TabAtkins: I've never heard anyone have an opinion. I haven't looked deeply, but haven't spotted it
<dael> fantasai: I think tend to keep specified values as specified. Ex a lot of color stuff could be at parse time, but we don't. Same with bg position stuff
<dael> astearns: If we decide we want that behavior is it likely that all the browsers will change their current code to adapt?
<dael> TabAtkins: Given I'm not seeing anyone say anything, I suggest we resolve to match browser behavior
<dael> florian: Only thoght against that direction is the more you preserve the easier it is for browser tools that work off OM. But not sure if we've followed that enough for it to be practical
<dael> TabAtkins: Agree. And if you want high quality you have to do it like dev tools
<dael> astearns: And there is now a dev tools API
<dael> TabAtkins: It is very important to get access to the invalid stuff and you cannot get it from the OM. I'm not super symathetic to needing values for an editor concern. Author surprise would be different, but I don't see that.
<dael> fantasai: Situation is we're kind of inconsistent where some things can resolve and some can't?
<dael> astearns: For things that do resolve against a number, only exception is filter arg.
<dael> florian: That suggests it's reasonable
<dael> TabAtkins: Can drop some tests and highlight that filter effect needs a change
<dael> fantasai: Just put a warning where you write the thing that it's not always going to resolve like line-height
<florian> s/That suggests it's reasonable/Anyway, I think Tab's suggestion is reasonable.
<dael> astearns: An example of something that looks like it would work but doesn't would be reasonable to put in spec
<dael> TabAtkins: I think line-height is most reasonable thing. Good idea
<dael> astearns: Prop: % that are resolved against numbers do that at parse time
<dael> astearns: And we'll have an example of something that doesn't quite fit the pattern and we'll add wpt for this
<dael> astearns: Obj?
<dael> RESOLVED: % that are resolved against numbers do that at parse time
cdoublev commented 10 months ago

Can you please provide an answer for <percentage> resolved to <angle>? The question would also apply for any dimension that can be represented as a percentage, but there is no existing case.

For example, should the specified value of conic-gradient(blue 50%, yellow) be conic-gradient(blue 180deg, yellow)? Should the specified value of conic-gradient(blue calc(25% + 90deg), yellow) be conic-gradient(blue calc(180deg), yellow)?

Note: browsers do not seem to support combining <angle> and <percentage> in math functions at the moment.


Is it fine to assume that <percentage> is resolved before simplifying a calculation tree? This would mean that its step 1.1 (resolve <percentage>) is not required.


Fwiw, if they cannot be preserved within specified values, I would have preferred percentages to be resolved at serialization time vs. parse time wherever interop requires it, which I assume would imply that 25% + 90deg would not be simplified, and omitting default number values resolved from percentages to be clarified wherever required.

tabatkins commented 10 months ago

Can you please provide an answer for resolved to ?

The spec already defines how percentages relative to another value resolve (step 1.1 of the simplification algo). So, yes, if you used calc(50%) in conic-gradient(), it would simplify into calc(180deg) at computed-value time.

Whether a naked (non-calc()-wrapped) percentage turns into an angle is defined by the context it's in (tho we often forget to explicitly define this stuff). Usually we absolutize values and resolve percentages if possible, at computed-value time. It does look like conic-gradient() doesn't define this, tho.

cdoublev commented 10 months ago

So <percentage> representing <number> is resolved at parse time, but <percentage> representing <angle> remains resolved at computed value time. Assuming 1% is always equal to 3.6deg, it seems inconsistent but... ok!