Skip to content
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

[css-borders-4] Specify how borders are rendered for corner-shape #11610

Open
noamr opened this issue Jan 30, 2025 · 18 comments
Open

[css-borders-4] Specify how borders are rendered for corner-shape #11610

noamr opened this issue Jan 30, 2025 · 18 comments

Comments

@noamr
Copy link
Collaborator

noamr commented Jan 30, 2025

Follow-up on #10993

corner-shape cannot be naturally stroked in the same way as ordinary rounded corners or a stroke path, and some details need to be defined.

For example, how should stroking bevel/notch look like? Do the inner border collapse into each other, or stay confined within the corner?

@noamr noamr changed the title [css-borders-4] Specify restrictions on corner-shape for rendering edge cases [css-borders-4] Specify how borders are rendered for corner-shape Feb 14, 2025
@noamr noamr self-assigned this Feb 14, 2025
aarongable pushed a commit to chromium/chromium that referenced this issue Feb 18, 2025
This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <[email protected]>
Commit-Queue: Noam Rosenthal <[email protected]>
Reviewed-by: Fredrik Söderquist <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1421457}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Feb 18, 2025
This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <[email protected]>
Commit-Queue: Noam Rosenthal <[email protected]>
Reviewed-by: Fredrik Söderquist <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1421457}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Feb 18, 2025
This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <[email protected]>
Commit-Queue: Noam Rosenthal <[email protected]>
Reviewed-by: Fredrik Söderquist <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1421457}
@noamr
Copy link
Collaborator Author

noamr commented Feb 19, 2025

I created a codepen that renders corner-shape using @fserb's formula to approximate superellipses (more on that later), and shows how I think the borders should work:
https://codepen.io/noamr/pen/WbNQEvP?editors=1111

Basically, to achieve the effect where the thickness of the border is equal to the relevant border-width, while painting within the corner bounds, the outer radius as to be lower than the inner radius for curvatures more concave than round.

This becomes very apparent with notch:

Image

And also with bevel

Image

Or scoop:

Image

In the prototype, the outer radius is reduced in a number that's a function of the corresponding border and the curvature.
The offset would be an intercept for a parallel line to the tangent of the curve at a (border-width) distance.

(It still looks a bit off at around scoop though, so we're not done with the formula)

@jsnkuhn
Copy link

jsnkuhn commented Feb 19, 2025

I have a hand full of examples of partial bordered corner-shape like examples that might be worth taking a look at here:

#9072

and a couple of others from the giant use case thread:

#6980 (comment)
#6980 (comment)

@noamr
Copy link
Collaborator Author

noamr commented Feb 19, 2025

I have a hand full of examples of partial bordered corner-shape like examples that might be worth taking a look at here:

#9072

and a couple of others from the giant use case thread:

#6980 (comment) #6980 (comment)

Thanks, I wonder how these use case translate to the specifics of border rendering?
Some of these seem to be semi-rounded notches which we've been discussing on a different thread.

@jsnkuhn
Copy link

jsnkuhn commented Feb 20, 2025

In this example I notice that the thicker right border's width is maintained through the entirety of the top-right angled corner. The width of the drawn border does not transition from the border-top-width to the border-right-width like in the codepen. Or is there a way to accomplish this that I'm not seeing?

Image

@noamr
Copy link
Collaborator Author

noamr commented Feb 20, 2025

In this example I notice that the thicker right border's width is maintained through the entirety of the top-right angled corner. The width of the drawn border does not transition from the border-top-width to the border-right-width like in the codepen. Or is there a way to accomplish this that I'm not seeing?

Image

There is no current semantic for defining where the corner gets the border-width from, so it interpolates between the two sides... A corner like in the picture would only be available with border-shape ATM.

@smfr
Copy link
Contributor

smfr commented Feb 23, 2025

I've drawn up some diagrams of how I think bevel and scoop should be rendered. Note that with uneven border widths, you can't think of rendering the border as stroking a path; it has to be inner and outer paths computed separately.

Bevel

Image

The goal is to have the bevel section maintain the border width. Naively joining the points you'd use for round result in a thinner diagonal.

Proposed algorithm for computing the inner border edge:

  1. Take the points where the outer border bevel corners are
  2. From each, compute a line perpendicular to the the bevel, with a length given by the border width on that side (w1, w2)
  3. Compute a line that intersects the inner ends of the lines from step 2
  4. Compute where that line intersects the two inner border sides

Scoop

Image

The goal here again is to maintain the width of the scoop section, and also to have the inner scoop edge prescribe an ellipse around the outer box corner (this looks better than perpendicular joins at the inner edges).

Proposed algorithm for computing the inner border edge:

  1. Compute an ellipse whose center is the outer corner, whose x radius is the (border-radius width + border width on the horizontal side) and whose y radius is (border-radius height + border width on the vertical side)
  2. Compute the intersections of the relevant ellipse quadrant with the inner border edges

@noamr
Copy link
Collaborator Author

noamr commented Feb 23, 2025

The formula in the codepen is sort of what you wrote for bevel. It's equivalent to reducing the outer radius by border-width*sqrt(2)-1, for the width relevant to the side of the corner the path is at. A similar formula is applied to scoop and all the other superellipses so that's probably why it doesn't look great there.

@jsnkuhn
Copy link

jsnkuhn commented Feb 24, 2025

In this example I notice that the thicker right border's width is maintained through the entirety of the top-right angled corner. The width of the drawn border does not transition from the border-top-width to the border-right-width like in the codepen. Or is there a way to accomplish this that I'm not seeing?
Image

There is no current semantic for defining where the corner gets the border-width from, so it interpolates between the two sides... A corner like in the picture would only be available with border-shape ATM.

In practice what I'd be more likely to do than draw out the 2 more complicated paths (likely with a generator) for border-shape here is use a consistent 1px border then extend the right/left sides with drop-shadows (assuming drop-shadow works the way I expect).

corner-size/border-radius: 0 14px / 0 20px;
corner-shape: square bevel;
/* (or just `bevel` i guess because the 0 corner size will make the corner square anyway?) */
border-color: solid 1px #a1c3ca;
filter: drop-shadow(2px 0 #a1c3ca) drop-shadow(-2px 0 #a1c3ca);

@noamr
Copy link
Collaborator Author

noamr commented Feb 24, 2025

A focused discussion for the WG here is whether concave corner borders (scoop/notch) can be allowed to overlap each other. The alternative is to shrink the outer radius and create some kind of join.

I believe the latter is a reasonable constraints for corners, however some use cases will not be possible (a sharp "star" using scoops where the borders overlap). Note that this would be possible with border-shape.

This prototype attempts to solve the join problem to appear consistent with the existing rounded/square shapes.

@tabatkins
Copy link
Member

Hm, I notice in your prototype that the point on the border's outer edge that looks like it starts the corner stays constant while the border is convex, but moves slightly closer to the corner as it becomes concave. I presume this is what Simon was referring to.

@smfr, in your diagrams, could you indicate exactly what parts are intended to correspond to the specified radiuses? I presume it's the length of the outer dashed sides?

@noamr
Copy link
Collaborator Author

noamr commented Feb 24, 2025

Hm, I notice in your prototype that the point on the border's outer edge that looks like it starts the corner stays constant while the border is convex, but moves slightly closer to the corner as it becomes concave. I presume this is what Simon was referring to.

Yes, this is intentional. It creates an effect where scoop is a mirror image of round in the opposite corner, while staying within the bounds,

@smfr
Copy link
Contributor

smfr commented Feb 25, 2025

Amended diagrams (text slightly garbled by OmniGraffle export):

Image

Image

@noamr
Copy link
Collaborator Author

noamr commented Feb 25, 2025

@smfr I looked at the bevel formula and generalized it to any superellipse, by using the approximate tangent.
It basically works like a bevel, where the line goes to the control point of the arc equivalent to the superellipse (have the same value at t=0.5). I think it works the same for scoop.

It looks something like this:

  // a and b are the coordinates for the contol point of the approximate arc
  a = 0.5 ^ (1/k);
  b = 1 - a;
  slope = a / b;
  magnitude = hypot(a, b);
  norm_a = a / magnitude;
  norm_b = b / magnitude;

  // This compute the intercept of a line parallel to the tangent, at distance (1)
  outer_offset = norm_b + slope * (norm_a - 1);

  // outer_offset can now be multiplied by the stroke width to compute the join size (actual outer offset)

@noamr
Copy link
Collaborator Author

noamr commented Feb 25, 2025

Hm, I notice in your prototype that the point on the border's outer edge that looks like it starts the corner stays constant while the border is convex, but moves slightly closer to the corner as it becomes concave. I presume this is what Simon was referring to.

Update: I changed the algorithm in the prototype to modify the inner radius instead, but constrain the radius so that it doesn't overlap (effectively shrinking the outer radius when close to 50%).

i3roly pushed a commit to i3roly/firefox-dynasty that referenced this issue Feb 26, 2025
…only

Automatic update from web-platform-tests
Rendering of corner-shape: bevel

This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <[email protected]>
Commit-Queue: Noam Rosenthal <[email protected]>
Reviewed-by: Fredrik Söderquist <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1421457}

--

wpt-commits: cd546585e048de3c88ffdaa7f83e23f4a0dba932
wpt-pr: 50777
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Feb 27, 2025
…only

Automatic update from web-platform-tests
Rendering of corner-shape: bevel

This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <[email protected]>
Commit-Queue: Noam Rosenthal <[email protected]>
Reviewed-by: Fredrik Söderquist <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1421457}

--

wpt-commits: cd546585e048de3c88ffdaa7f83e23f4a0dba932
wpt-pr: 50777
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified that referenced this issue Mar 1, 2025
…only

Automatic update from web-platform-tests
Rendering of corner-shape: bevel

This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <pdrchromium.org>
Commit-Queue: Noam Rosenthal <nrosenthalchromium.org>
Reviewed-by: Fredrik Söderquist <fsopera.com>
Cr-Commit-Position: refs/heads/main{#1421457}

--

wpt-commits: cd546585e048de3c88ffdaa7f83e23f4a0dba932
wpt-pr: 50777

UltraBlame original commit: 402b0e16cc0e1dafd266edb84ae86e3516159e06
gecko-dev-updater pushed a commit to marco-c/gecko-dev-comments-removed that referenced this issue Mar 1, 2025
…only

Automatic update from web-platform-tests
Rendering of corner-shape: bevel

This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <pdrchromium.org>
Commit-Queue: Noam Rosenthal <nrosenthalchromium.org>
Reviewed-by: Fredrik Söderquist <fsopera.com>
Cr-Commit-Position: refs/heads/main{#1421457}

--

wpt-commits: cd546585e048de3c88ffdaa7f83e23f4a0dba932
wpt-pr: 50777

UltraBlame original commit: 402b0e16cc0e1dafd266edb84ae86e3516159e06
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified-and-comments-removed that referenced this issue Mar 1, 2025
…only

Automatic update from web-platform-tests
Rendering of corner-shape: bevel

This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <pdrchromium.org>
Commit-Queue: Noam Rosenthal <nrosenthalchromium.org>
Reviewed-by: Fredrik Söderquist <fsopera.com>
Cr-Commit-Position: refs/heads/main{#1421457}

--

wpt-commits: cd546585e048de3c88ffdaa7f83e23f4a0dba932
wpt-pr: 50777

UltraBlame original commit: 402b0e16cc0e1dafd266edb84ae86e3516159e06
jamienicol pushed a commit to jamienicol/gecko that referenced this issue Mar 5, 2025
…only

Automatic update from web-platform-tests
Rendering of corner-shape: bevel

This sets the infrastructure for rendering corner-shape,
by storing 4-float "CornerCurvature" on a FloatRoundedRect.

Currently only the special-case for "bevel" is handled, with the idea
of iterating on the different aspects of rendering as we go
along.

To properly render bevel (and any other curvature that's lower than
round), drawing a straight line from the outer/inner sides
doesn't create the right effect, because the diagonal distance between
those lines doesn't match the border width.

To correct that, while maintaining rendering within the initial
corners, the outer corner is shrunk to the point where the resulting
diagonal quad has a width equal to the border-width (or interpolating
between two border-widths).

See open spec issue: w3c/csswg-drafts#11610

Removed existing hand-coded SVG tests, and generating the refs using
a canvas polyfill instead.

This polyfill can be amended later on as we polish the spec.

Bug: 394059604
Change-Id: I9fb76dd7dc557ed10c40adecbb9f68f672de1acc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6250197
Reviewed-by: Philip Rogers <[email protected]>
Commit-Queue: Noam Rosenthal <[email protected]>
Reviewed-by: Fredrik Söderquist <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1421457}

--

wpt-commits: cd546585e048de3c88ffdaa7f83e23f4a0dba932
wpt-pr: 50777
@noamr
Copy link
Collaborator Author

noamr commented Mar 11, 2025

After many iterations together with @smfr and @fserb, I think we have a good model:

  • Shaped borders do not create new constraint on the radius. Instead, they are clipped by the outer shape, and can overlap with each other when concave. This allows creating shapes like a scooped star:

Image

  • The border should have its specified interpolated thickness through entire length when possible. Same for shadows. This requires a formula similar to this one, but one that's applicable for arbitrary curvature. As a general rule, it should feel like two superellipse quadrants, with the same center, same curvature, and a different radius. It's basically what @smfr describes in [css-borders-4] Specify how borders are rendered for corner-shape #11610 (comment), but applied to all superellipses.
  • Dashed/dotted borders etc should be rendered side-after-side, which might create some overlaps. This would be consistent with the convex versions in case of animations.
  • Color joins should work similar to how they work for ordinary rounded corners - they should either cross diagonally between the border and padding edges, or try to slice the corner at its middle.

Would love to get a resolution on this soon!

Feel free to play with the different options on https://noamr.github.io/squircle-testbed/corner-shape.html
Note that it doesn't render color joins correctly, but everything else should be fine.

@tabatkins @fantasai @LeaVerou @yisibl

@tabatkins
Copy link
Member

That definitely feels pretty good to me when I play around with it a little, and your description matches how I think they should work. While we still need to define the diagonal-corner overlap avoidance rules (which your demo mostly avoids, by limiting the radiuses to half the half the side size), I like the shape of this chunk of the rules.

(By the way, there's a rendering bug if some of the corners are zero radius.)

@noamr noamr added the Agenda+ label Mar 20, 2025
@yisibl
Copy link
Contributor

yisibl commented Mar 20, 2025

@noamr Sorry for the late reply. This looks very nice, I see that there are two Mode options at the bottom, does the actual implementation of Blink use the bezier curve approximation as well?

@noamr
Copy link
Collaborator Author

noamr commented Mar 20, 2025

@noamr Sorry for the late reply. This looks very nice, I see that there are two Mode options at the bottom, does the actual implementation of Blink use the bezier curve approximation as well?

Currently in canary, blink uses the bezier curve approximation and the web platform tests use point-by-point superellipse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants