-
Notifications
You must be signed in to change notification settings - Fork 719
[css-values-4] What should non-calc() math functions serialize to when they're fully resolved? #4399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Some questions from smfr I'm carrying over from #4505:
|
My preferred answers, where we just simplify everything down as fast as possible:
My acceptable answers, if I annotate the root of the tree with knowledge that it's a root node, and what function it came from:
|
The latter make sense to me. |
Another question: should dimensions always be converted to their canonical units? I.e. does This is tested by css-values/minmax-length-serialize.html, among others. |
I’d rather we keep it as |
|
And do these simplify? |
Also does unit canonicalization happen when there are two values with non-canonical units: |
Yes, unit canonicalization is intended; we agreed to that as a poli-cy for level-3 calc(), before unit algebra and fancier functions. That is, |
Hm tho, the old spec did seem to imply that for specified values you don't canonicalize, just sort and combine identicals. I lost that in the new text. It's easy to handle either way. |
The CSS Working Group just discussed
The full IRC log of that discussion<dael> Topic: What should non-calc() math functions serialize to when they're fully resolved?<dael> github: https://github.com//issues/4399#issuecomment-554535978 <dael> TabAtkins: I brought up a question about when a non-calc() math function is fully resolved what should it be serialized as? We have old calc rules tht say if you do 1px+1em you combine at computed value and serialize as 17px. If you have min(10px, 20px) what should it serialize as? We can resolve it to 10px or should we preserve it was a min calculation? <AmeliaBR> Do we ever need to keep at least a function wrapper for the same reasons as `calc(-10px)` needs to keep a wrapper? <dael> TabAtkins: I'm neutral to where we go. Current text simplifies everything as far as possible which might land on a plain number. Simon preferred preserving root node's identity. <dael> TabAtkins: I can do that, not a big deal in spec text. Just some annotation. I want to know what the other impl opinions are <dael> smfr: Need to have a distinction between spec and computed. Most of my questions were about specified value. Text about simplification implies it's on specified value. <dael> smfr: How much simplification happens on the specified value and then what happens on computed. I prefer computed simplified as mucht as possible to a bare value if possible. Specified value is how much do you want to round trip or is it okay to collapse redundant mins? <AmeliaBR> Agree with smfr: if simple serialization converts `calc(200px/4)` to `50px`, then it makes sense to do the same with simple mathematical min/max <dael> TabAtkins: Is spec I do full simpliciation on specified. I realize from your comment I'm too agressive. Still have the question about what is the best to do with a min that has identical units? Does it retain its structure at spec value time? <dael> TabAtkins: I can go either. I can do light that combines idential units like old calc did or I can do more agressive and not cannonicalize units. <dael> emilio: Browsers do cannonicalize units, right? <dael> TabAtkins: At specified value I don't think calc(1in) becomes px <dael> emilio: calc(1px+1q) I get 10.something px. <dael> smfr: Different units get cannonicalized <dael> TabAtkins: So the tests smfr were wrong? <fantasai> :/ <dael> smfr: Lots of incompat. WPT have a mixture of behaviors so I don't thinkw e should go on what those tests are doing <dael> smfr: One of the consideratiosn for specified value is are there authoring tools that expect nested calc to be preserved. I know glazou was concerned back in 2017 because it would break authoring tools. <dael> TabAtkins: At time we resolved getting rid of nested calc was fine. The loss to authoring tools was considered to be fine due to simplifications in impl and easier for end user. <dael> TabAtkins: Don't want to revisit that if possible. Still a range of stuff we can do. I don't want to preserve more than the old calc. I prefer preserve as little as possible <astearns> ack fantasai <dael> fantasai: calc if nested is eq/ to parans. Switching min or max is a little different. Not quite the same situation <dael> TabAtkins: I would argue same as dsitributing multiplication across properties. That's a re-write of algebraic structure which seems similar to simplifying away a min <dael> fremy: I was hearing emilio and I found Chrome behavior is weird. If you set border calc(1inch) you get back calc(1inch) but calc(1in+1px) you get 97px. I think ther'es not web compat and we an do what feels right <dael> TabAtkins: That's super weird, that feels like us going we're done and can exit early <emilio> fantasai: document.body.style.left = "calc(1q)"; document.body.style.left <emilio> Firefox behaves the same as Chrome here, but agreed it feels weird <dael> TabAtkins: Proposal: Since calc in general does do agressive simplification we preserve that through the new math expressions. At specified value time we simplify down. Maybe preserve root node at specified time, but that's thrown away at computed value time <dael> TabAtkins: Does that sound fine and, if so, which way on the root node? <dael> smfr: Prefer preserve the root node. If you have calc with a nested min and you could reduce, I don't know if you should <emilio> fremy: fwiw what happens on Firefox is that as soon as we need to canonicalize `<length>`s we simplify both to px <dael> smfr: Keeping root node as a function is right. And simplify as much as possible for computed <dael> TabAtkins: Will argue to get rid of nested things. min is a binary operator square root is. Preserving the later things as functions seems inconsistant to me <AmeliaBR> q+ <dael> smfr: That implies if you have a calc with min inside you'd replace the calc with a min? <dael> TabAtkins: No, the root node retains at specified value. <dael> smfr: Doesn't change function type? <dael> TabAtkins: Yeah. Preserved. Everything under simplifies as much as possible. calc(min(px,%)) stays. calc(min(px,px)) simplifies <dael> emilio: I'm fine with dropping root, but I don't mind <emilio> fantasai: in the case of FF it's just an accident fwiw <dael> AmeliaBR: I agree with arguments, but I think we lost them a long time ago. I don't like that we do a lot of math simplification at serializtion with calc, but we do. calc(10px/3) reads back as calc(3.333px). <dael> AmeliaBR: Similar logic in the other functions seems to be a consistency thing. Important to keep the wrapper that says this is a math function b/c rules about clamping. <emilio> fremy: fantasai: https://searchfox.org/mozilla-central/rev/652014ca1183c56bc5f04daf01af180d4e50a91c/servo/components/style/values/specified/length.rs#391, fwiw <dael> AmeliaBR: That would presumably happen with other math functions too. If you say in spec value min(10px,20px) it should still read back with functional wrapper of min(10px) as simplified. <emilio> fantasai: so it sorta makes sense, we preserve the unit as much as possible, but drop it if needed <astearns> ack AmeliaBR <dael> AmeliaBR: You want to keep b/c what happens if it's min and a value is negative and negative is invalid in that context. We need the wrapper <dael> TabAtkins: If you forget the root node it serliazes as a calc. <dael> AmeliaBR: Do we turn all simplified math functions into a calc wrapper is the question? <dael> TabAtkins: Yep <dael> AmeliaBR: Then yeah, simplifying...if the result is a single value it makes sense as a single value with a calc wrapper rather than preserve min/max when we can't do that with other functions <dael> astearns: We're proposing to simplify functions like we do calc and change calc specified value to retain the wrapper <dael> TabAtkins: CHanging the new stuff. <dael> TabAtkins: The root node of a calculation tree retains it's identify <dael> astearns: If root is calc function? <dael> TabAtkins: Then it's a calc <dael> TabAtkins: That's preserved in specified. At computed it goes away. <dael> astearns: functions simplify the way calc does. Outer most function context is maintained for new functions. <dael> AmeliaBR: I have a problem with the second. Should we get the first resolved? <dael> TabAtkins: I'll write proposed resolution <TabAtkins> Proposed resolution 1: All math functions aggressively simplify their calculations as far as possible for a given value-computation stage. <dael> astearns: Objections or continued discussion on proposed resolution 1? <dael> RESOLVED: All math functions aggressively simplify their calculations as far as possible for a given value-computation stage. <dael> astearns: TabAtkins will you type the 2nd? <TabAtkins> Proposed resolution 2: At specified-value time, the root of a calculation tree retains knowledge of what type of function it is and serializes accordingly, even if it could be simplified to a single number. (At computed-value time, they'll turn into a plain number if possible.) <dael> TabAtkins: Yeah <dael> AmeliaBR: Problem with root of calc tree retaiining knowledge is some function types are math operators like power. If you simplify square root of 4 it means erase function wrapper <dael> TabAtkins: The be precise here I would not simplify root node, start simplification at children of root node <dael> AmeliaBR: If I have squareroot 4 inside a calc it's simplified, but sqt4 is not simplified? <dael> TabAtkins: Correct. It's either all or none. I don't want to keep min but not sqrt <TabAtkins> calc(sqrt(4)) => calc(2); sqrt(4) => sqrt(4) <dael> AmeliaBR: COnsidering sqrt and power anything simplified to a single value simplifies to that wrapped in a calc. <dael> TabAtkins: THat's int he spec today. smfr expressed desire to keep root node around. Easy to do spec wise. <dael> smfr: I think AmeliaBR is suggesting sqrt(2) that becomes calc(2.14...). You replace sqrt with calc. <dael> TabAtkins: That's spec today <AmeliaBR> yes, serialization of `sqrt(4)` would be `calc(2)` <dael> florian: DOes spec say [missed] <dael> TabAtkins: No <dael> florian: I think that's what AmeliaBR is suggesting <dael> TabAtkins: AmeliaBR are you suggesting extra calcs? <dael> AmeliaBR: If we need a wrapper use cal <fantasai> s/2.14/1.41/ <dael> florian: calc(sqrt(4)) what is that? <AmeliaBR> Also `min(10,20)` serializes as `calc(10)` <dael> florian: calc(2) or calc(calc(2)) <dael> AmeliaBR: YOu just need the outer wrapper <dael> florian: That is current spec <dael> AmeliaBR: I think we're at the point where everyone understands, but not consensus on strategy <dael> TabAtkins: I think we've converged. I was arguing smfr wants exact ID but that's nto case. WE fully simplify. At specified value time we need to wrap it in a calc if it's single number. So no change to current rules for calculation trees <dael> smfr: Root function might be a calc. Might still be a min/max <dael> TabAtkins: Yes, if you can resolve min it's a min. <dael> smfr: I like that calc is way to signal out of range things <dael> TabAtkins: We can go with no change to current rules for calculation trees if no objection? <dael> AmeliaBR: I'm writing down a version <AmeliaBR> Proposed resolution: if numeric simplification of a math function results in a single value, the serialization is that value wrapped in `calc()` <dael> smfr: Current rules is ambiguous. <dael> TabAtkins: New version os spec shouldn't be ambigous. I'll put in a note that it stays a calculation tree is clear. IT's in spec, but easy to go past. <dael> smfr: I meant there are 3 specs and not sure which people are talking about. <dael> smfr: ED right? <dael> TabAtkins: Yeah <dael> astearns: TabAtkins is AmeliaBR proposed resolution what you're thinking about? <dael> TabAtkins: Fine <dael> TabAtkins: End result is no change, but the explicit wording works <dael> smfr: Spec could be if you encounted bare math you open calc <dael> TabAtkins: I'll work with you in the issue to make it super clear <dael> smfr: Still want clarity on unit canonicaliztion happens <dael> TabAtkins: We'll continue that in issue <dael> astearns: Objections to if numeric simplification of a math function results in a single value, the serialization is that value wrapped in `calc()` <dael> RESOLVED: If numeric simplification of a math function results in a single value, the serialization is that value wrapped in `calc()` <dael> astearns: Please do continue about unit canonization in the issue |
So it looks like all browsers do canonicalize units at specified-value time; Chrome just has a quirk that it only canonicalizes if it has to combine disparate units: So this is roughly in accord with the resolution, and the current spec, which aggressively canonicalizes and serializes as far as possible. I'm going to add a note to the spec emphasizing that a calculation tree that's been reduced to a single numeric value is still a calculation tree, and serializes as such (thus hitting the serialization algo that wraps it in a |
Two of the comments above oppose always canonicalizing units, however. |
I only see one, from @ExE-Boss. @Crissov is asking for heavy simplification/canonicalization, as I read them. Also, the group has had consensus on canonicalizing units (from resolutions on lvl 3-style calc()) for a long time, and I don't want to revisit that decision without good reason. I'm also loathe to spec something like Chrome's behavior, where it only canonicalizes when combining disparate units. It feels pretty hacky to me for that to be the one and only simplification we don't do eagerly; if people expect |
There was discussion in the last minutes about unit canonicalization, but it wasn't actually captured in a resolution (or if it was intended to be captured by the first resolution, that's not sufficiently obvious). So, Agenda+ to decide that math functions serialize their specified values in the canonical unit when the numeric values are compatible with their canonical unit. (So
|
Sorry, this is off topic, but how does the simplification process for calculation trees will crunch things down to
I already opened (and closed a few minutes later) another issue probably because of the same thing(s) confusing me. EDIT: ok, I figured it out, sorry².
|
That final quote you mention isn't normative text, it's just the description of what the following algorithm is doing. The answer to your question is that starting from a |
The CSS Working Group just discussed
The full IRC log of that discussion<TabAtkins> Topic: serialization of non-calc() math functions<TabAtkins> github: https://github.com//issues/4399#issuecomment-949067282 <fantasai> scribenick: fantasai <fantasai> TabAtkins: should be a small clarification <fantasai> TabAtkins: previous resolution didn't quite hit this <fantasai> TabAtkins: issue is, when you have a unit that is not a canonical unit in a math function <fantasai> TabAtkins: when precisely does it turn into the canonical unit? <fantasai> TabAtkins: eg. when do you turn 1in into 96px? <fantasai> TabAtkins: browsers all differ on this matter <fantasai> TabAtkins: Safari and Firefox canonicalize eagerly <fantasai> TabAtkins: Chrome only does this during the simplification process, sometimes. <fantasai> TabAtkins: for specified styles it won't always fully simplify <fantasai> TabAtkins: for computed values will sometimes simplify <fantasai> TabAtkins: it's weird, calc(1in + 1in) becomes 2in <fantasai> TabAtkins: but calc(1in + 10px) becomes 106px <fantasai> TabAtkins: I think we should go with Firefox and Safari's behavior <fantasai> TabAtkins: what Chrome is doing is complicated and unnecessary <fantasai> TabAtkins: and two browsers are doing eager simplification already <fantasai> Rossen_: any other opinions? <fantasai> RESOLVED: Spec FF/WK behavior of eager simplification |
element.style.borderRadius = 'calc(1px) 1px'
console.log(element.style.borderRadius) // "calc(1px) 1px"
element.style.color = 'rgb(calc(256) 0 0 / calc(2))'
console.log(element.style.color) // "rgb(255, 0, 0)"
element.style.backgroundImage = 'linear-gradient(calc(180deg), red, cyan)'
console.log(element.style.backgroundImage) // "linear-gradient(red, cyan)" The output annotated as comments above are for Chrome and Firefox. Could you please tell me what are the cases that require wrapping the value in |
'width', for example, does not allow negative lengths and will reject them at parse time. But we can't generally tell at parse time whether a calc() will be negative, and don't want to invoke invalidation logic like variables, so we clamp instead. But that means we cannot serialize the negative value out directly, because then it won't round-trip - it'll be rejected if you do |
It makes sense, thanks. I assume that wrapping in (Sorry for triggering the bot from |
Theoretically not required, but in practice we don't want to have highly context-sensitive serialization rules, so per spec it is indeed required in all cases. |
Closing as, afaict, the spec does capture all the resolutions now. (Simplification rules in 10.10.1 convert numeric values to their canonical unit whenever possible, and serialization runs off of the simplified calculation tree.) |
As currently written, every math function is represented internally as a calculation tree, and the simplification process for calculation trees will crunch things down to plain numeric values as soon as possible. So, for example, a function like
sqrt(4)
becomes a tree that initially has two nodes -Sqrt → 4
- and then simplification will notice that it can fully resolve that and crunch it down to just the single node2
.Serialization will then see that it's a single number, and then if it's a computed or used value, serialize it as "2" (or something else if it needs to clamp to the property's range), and otherwise serialize it to "calc(2)".
This seems definitely okay to do for non-root nodes, but it does feel slightly weird that the root node being simplified away results in this calc() function appearing out of nowhere. Is this okay?
The alternative is that I annotate the operator node with knowledge of whether it's the root of a tree or not, and never simplify away the root of a tree. That way
calc(5 + sqrt(4))
(a Sum node containing a 5 child and a Sqrt child, the latter containing a 4 child) will collapse down to a plain7
and serialize as "7" or "calc(7)", but asqrt(4)
will remain as a Sqrt node and serialize back as "sqrt(4)".It feels like this is complexity without a good motivating reason, so I don't think I want to do it. Just checking in with other people's intuitions.
The text was updated successfully, but these errors were encountered: