Skip to content

[css-values-4] How should atan2 behave with different length units? #7482

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

Closed
devongovett opened this issue Jul 9, 2022 · 10 comments
Closed
Labels
Closed as Question Answered Used when the issue is more of a question than a problem, and it's been answered. css-values-4 Current Work Needs Testcase (WPT)

Comments

@devongovett
Copy link
Contributor

The spec says that atan2 accepts two calculations, which must have the same type. However, the type is defined as only "length" for length values, and does not include the unit. In fact, the unit seems to be completely ignored.

For example, the following are equivalent despite having completely different units.

rotate: atan2(90px, 90px);
rotate: atan2(90px, 90vw);

In the WebKit implementation, however, the following are not equivalent. Presumably there is some unit conversion happening due to the units being compatible, but not sure if this is correct.

rotate: atan2(90px, 10px);
rotate: atan2(90px, 10in);

Further, when a calculation results in incompatible units, it's unclear what the behavior should be. For example:

rotate: atan2(90px, 10px + 10vw);

In WebKit, the second term seems to be ignored and result in the same value as rotate: atan2(90px, 10px). This seems like a bug, but I'm not sure what to expect here. Should 10vw be computed and converted into px? What if the terms were reversed? Should 10px be converted to vw then? Should the units be ignored, resulting in atan2(90, 20)? The spec doesn't say in which unit atan2 should be computed.

My question: what should the behavior be when the units of the arguments are different, but the types are the same? What should happen if a calculation cannot be simplified to a single number?

Perhaps a better question: why does this function accept units at all? Why not only allow calculations that resolve to a <number> similar to some of the other math functions? Since the units aren't actually used, it just seems confusing.

@devongovett
Copy link
Contributor Author

Same is true for percentages. If percentages are allowed, then what are they a percentage of? WebKit ignores this and treats atan2(90%, 10%) the same as atan2(90, 10). Normally, this is dependent on the property but if the type of the property doesn't usually allow percentages (like rotate), then it's unclear what should happen.

Most of this applies for the hypot function as well btw.

@cdoublev
Copy link
Collaborator

My question: what should the behavior be when the units of the arguments are different, but the types are the same? What should happen if a calculation cannot be simplified to a single number?

Math functions are simplified to the extent possible. See 10.10.1. Simplification:

  1. If root is an operator node that’s not one of the calc-operator nodes, and all of its calculation children are numeric values with enough information to compute the operation root represents, return the result of running root’s operation using its children, expressed in the result’s canonical unit.

There is not enough information to resolve percentages (except when replacing a <number> | <percentage>), viewport or font-relative units, therefore a math function that includes such values is not simplified at parse time but at computed value time.

Perhaps a better question: why does this function accept units at all? Why not only allow calculations that resolve to a similar to some of the other math functions? Since the units aren't actually used, it just seems confusing.

Units should be used.

I'm not a spec author so please do not take my words for granted. But I went through the implementation of math expressions, so I hope it helps.

@cdoublev
Copy link
Collaborator

cdoublev commented Jul 10, 2022

The result from the atan2 operations in these tests are correct because their terms always have the same unit. The atan2(1cm, 1mm) CSS value should resolve to ~84deg, not 135deg. Theoretically, these test cases can be simplified to calc(135deg), (because both terms have the same unit) but there are some other cases of math expressions that can be simplified at parse time but are not. I cannot tell you why this case, atan2() with same units, is not simplified.

If a math function cannot be simplified to a numeric value at parse time, it is serialized as is. Then at computed value time, 1vw is canonicalized to whatever px value it corresponds, and atan2(1vw, -1vw) is resolved to a numeric value.

I agree that some clarification on the processing (parsing, simplification, type checking) of a math function would have helped me a lot. :)

@devongovett
Copy link
Contributor Author

That is one interpretation. It is not the interpretation that WebKit implements, and there are no tests in WPT that show this to be the correct one.

Additionally it leaves the question of what percentages are resolved against in properties that normally don't support them (eg rotate). I guess only the ratio between the arguments matters, but it's a little weird when the percentages don't actually resolve to anything.

@cdoublev
Copy link
Collaborator

cdoublev commented Jul 10, 2022

That is one interpretation.

Are you referring to the resolved value I expect for atan2(1cm, 1mm)? I could walk through and quote the steps in the spec to get to this result but it is best to let a CSSWG member answers you, yes.

Regarding <percentage> in atan2() to replace a value that should match <angle>, I believe that resolving the type of the function will fail:

<percentage>: if, in the context in which the math function containing this calculation is placed, <percentage>s are resolved relative to another type of value (such as in width, where <percentage> is resolved against a <length>), and that other type is not <number>, the type is determined as the other type. Otherwise, the type is «[ "percent" → 1 ]».

[...]

For each of the above, if the type is failure, the math function is invalid.

EDIT: he, no sorry, it will successfully resolve to «[ "percent" → 1 ]» but this type will fail to match <angle>.

@devongovett
Copy link
Contributor Author

atan2(1cm, 1mm) have compatible units. As I noted in my initial issue, WebKit (the only available browser implementation) seems to perform unit conversion on these. It seems to treat all non-absolute length units the same as px (i.e., as if the unit is not specified at all). Feel free to play around with it here: https://codepen.io/devongovett/pen/GRxqNYj

This behavior seems inconsistent, so I am looking for clarification from the spec authors on the correct intent. Then, we can work on adding new Web Platform Tests to handle these cases.

Regarding in atan2() to replace a value that should match , I believe that resolving the type of the function will fail

This part of the spec is about resolving the type of the result of a math function, specifically calc(). For atan2 specifically, it says:

A and B can resolve to any <number>, <dimension>, or <percentage>, but must have the same type

I believe this means that percentages are valid in any use of atan2, even if the property doesn't support them. Same holds true for all units. <dimension> allows lengths, times, frequencies, etc., so even if the property where atan2 is used only supports lengths, the arguments to atan2 can accept times, frequencies, percentages, etc. This is weird, but it could work since only the ratio between the arguments is important, assuming it's possible to convert the units to match. Percentages are the odd case because in some cases, the percentage won't really be resolved against anything, whereas other units would.

@Loirooriol
Copy link
Contributor

Should 10vw be computed and converted into px? What if the terms were reversed? Should 10px be converted to vw then?

It doesn't matter. atan2(Y, X) only depends on Y / X and the sign of X. So let's say 1vw = k * 1px, where k > 0.

Then atan2(90px, 90vw) can use 90px / (k * 90px) = 1 / k and sign(k * 90px) = 1.
Or it can use (90vw / k) / 90vw = 1 / k and sign(90vw) = 1. No difference.

If percentages are allowed, then what are they a percentage of?

It depends. Percentages may have the type «[ "percent" → 1 ]» e.g. in opacity. Then you just use the ratio between the numerical parts of the percentages, and the sign.

But in properties like width, where a percentage resolves into a length, the type is «[ "length" → 1 ]». So what you do is resolve the percentage as specified in the property (in width, against the width of the containing block), and then just do the division with the resulting length.

What should happen if a calculation cannot be simplified to a single number?

See https://drafts.csswg.org/css-values-4/#calc-computed-value. Maybe not at specified value time, maybe not at computed value time either, but: "with used value time information, a math function always simplifies down to a single numeric value."

why does this function accept units at all?

Because it's more flexible and it's well-defined. Math functions that can accept dimensions do so: min, max, clamp, round, mod, rem, hypot, abs, sign.

@devongovett
Copy link
Contributor Author

Thanks. So it seems the webkit implementation is buggy then, and it would be good to add some new tests to WPT where different units are used between the arguments.

@tabatkins
Copy link
Member

tabatkins commented Jul 11, 2022

For example, the following are equivalent despite having completely different units.

Those two are absolutely not equivalent (unless the viewport is 100px wide), and if an implementation is treating them as having the same value it's buggy, just at a basic definitional level. Looking at all the rest of your example, phew, yeah the WK implementation appears to be completely wrong when working with dimensions; that behavior is nonsensical.

My question: what should the behavior be when the units of the arguments are different, but the types are the same? What should happen if a calculation cannot be simplified to a single number?

You resolve them as far as you can, given the value stage you're at. If you can't fully resolve the two arguments to the same canonical unit, then the function can't resolve yet, and will have to wait until later in the value pipeline. See https://drafts.csswg.org/css-values/#calc-simplification and https://drafts.csswg.org/css-values/#calc-computed-value for details - at computed value time we resolve everything as much we can, including resolving functions to their answers if possible, but if it's not yet possible then we wait for used-value time, when it's guaranteed to be possible.

For example, atan2(90px, 90vw) is resolvable at computed-value time, since the viewport units are resolvable to px at computed-value time. Both arguments get converted to the canonical length unit - px, then the function is resolved accordingly.

atan2(90px, 50%), tho, might not be resolveable at computed-value time, depending on what property it's used in. For 'width', for example, %s don't resolve at computed-value time; they need to wait for used-value time so they can resolve against layout information. So width: calc(100px * atan2(90px, 50%) / 360deg); won't simplify at all at computed-value time (every unit is either in the canonical unit already, or can't be resolved), and then at used-value time will be able to simplify to a value.

Perhaps a better question: why does this function accept units at all? Why not only allow calculations that resolve to a similar to some of the other math functions? Since the units aren't actually used, it just seems confusing.

The units are used, WebKit's impl is just completely broken. atan2() takes a coordinate pair, and it's reasonable to specify a coordinate pair with dimensions, when lengths are relevant to what you're doing. For example, knowing the angle formed by moving 90px up and 90vw sideways is meaningful!

So it seems the webkit implementation is buggy then, and it would be good to add some new tests to WPT where different units are used between the arguments.

Yeah, I wouldn't have imagined an implementation like this would have passed review in the first place, so we definitely need some tests to make sure they're definitely failing.

@tabatkins tabatkins added css-values-4 Current Work Needs Testcase (WPT) Closed as Question Answered Used when the issue is more of a question than a problem, and it's been answered. labels Jul 11, 2022
@devongovett
Copy link
Contributor Author

Thanks. I filed this WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=242629

And sorry for my confusion here. I was really scratching my head!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Closed as Question Answered Used when the issue is more of a question than a problem, and it's been answered. css-values-4 Current Work Needs Testcase (WPT)
Projects
None yet
Development

No branches or pull requests

4 participants