Skip to content

Commit a3a4c3c

Browse files
authored
[css-borders-4] Tighten and improve corner-shape algorithm (#12313)
This reduces a lot of steps, and uses a couple of helper algorithms. Also fixed a few minor bugs, and removed the "canonical superellipse formula" which is no longer needed.
1 parent 53ae2ac commit a3a4c3c

File tree

1 file changed

+72
-124
lines changed

1 file changed

+72
-124
lines changed

css-borders-4/Overview.bs

Lines changed: 72 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -609,33 +609,18 @@ The 'corner-*-shape' Shorthands And Longhands</h4>
609609
<h4 id=corner-shape-rendering>
610610
Rendering 'corner-shape'</h4>
611611

612+
When rendering elements with shaped corners, the element's path needs to be offset,
613+
based on [=border=], [=outline=], 'box-shadow', 'overflow-clip-margin' and more.
612614

615+
When rendering borders or outlines, the offset is aligned to the curve of the element's shape,
616+
while when rendering 'box-shadow' or offsetting for 'overflow-clip-margin', the offset is aligned to the axis.
613617

614-
The <dfn export>canonical superellipse formula</dfn> can be described in Cartesian coordinates, as follows,
615-
where <code>s</code> is the [=superellipse parameter=]:
616-
617-
<pre>
618-
k = 2<sup>abs(|s|)</sup>
619-
x<sup>k</sup> + y<sup>k</sup> = 1
620-
</pre>
621-
622-
The resulting |x| and |y| are later projected to CSS coordinates by scaling based on the 'border-radius' properties,
623-
inversed if the [=superellipse parameter=] is negative. This creates symmetry between convex and concave shapes of the same absolute
624-
[=superellipse parameter=].
625-
626-
627-
628-
629-
Since stroking a superellipse accurately may be computationally intensive,
630-
user agents may approximate the path using bezier curves,
631-
as well as account for sharp edges and overlaps.
632-
633-
<figure>
634-
<img src="images/corner-shape-adjusting.svg"
635-
style="background: white;"
636-
alt="Adjusting corner shapes">
637-
<figcaption>Borders are aligned to the curve, shadows and clip are aligned to the axis.</figcaption>
638-
</figure>
618+
<figure>
619+
<img src="images/corner-shape-adjusting.svg"
620+
style="background: white;"
621+
alt="Adjusting corner shapes">
622+
<figcaption>Borders are aligned to the curve, shadows and clip are aligned to the axis.</figcaption>
623+
</figure>
639624

640625

641626
An [=/element=] |element|'s <dfn>outer contour</dfn> is the [=border contour path=] given |element| and |element|'s [=border edge=].
@@ -653,117 +638,80 @@ An [=/element=]'s [=overflow clip edge=] is shaped by the [=border contour path=
653638
Each shadow of [=/element=]'s 'box shadow' is shaped by the [=border contour path=] given |element|, and |element|'s [=border edge=], and the shadow's [=used value|used=] 'box-shadow-spread'.
654639

655640
<div algorithm="adjust-border-inner-path-for-corner-shape">
656-
To compute an [=/element=] |element|'s <dfn>border contour path</dfn> given an an [=edge=] |targetEdge| and an optional number |spread| (default 0):
641+
To compute an [=/element=] |element|'s <dfn>border contour path</dfn> given an [=edge=] |targetEdge| and an optional number |spread| (default 0):
657642
1. Let |outerLeft|, |outerTop|, |outerRight|, |outerBottom| be |element|'s [=unshaped edge|unshaped=] [=border edge=].
658643
1. Let |topLeftHorizontalRadius|, |topLeftVericalRadius|, |topRightHorizontalRadius|, |topRightVerticalRadius|, |bottomRightHorizontalRadius|,
659644
|bottomRightVerticalRadius|, |bottomLeftHorizontalRadius|, and |bottomLeftVerticalRadius| be |element| [=border edge=]'s radii,
660645
scaled by |element|'s [=opposite corner scale factor=].
661646
1. Let |topLeftShape|, |topRightShape|, |bottomRightShape|, and |bottomLeftShape| be |element|'s [=computed value|computed=] 'corner-*-shape' values.
662647
1. Let |targetLeft|, |targetTop|, |targetRight|, |targetBottom| [=unshaped edge|unshaped=] |targetEdge|.
663648
1. Let |path| be a new path [[SVG2]].
664-
1. Compute a [=corner path=] given
665-
the [=rectangle=] <code>(|outerRight| - |topRightHorizontalRadius|, |outerTop|, |topRightHorizontalRadius|, |topRightVerticalRadius|)</code>,
666-
0, |targetTop| - |outerTop|, |outerRight| - |targetRight|, and |topRightShape|,
667-
and append it to |path|.
668-
1. Compute a [=corner path=] given
669-
the rectangle <code>(|outerRight| - |bottomRightHorizontalRadius|, |outerBottom| - |bottomRightVerticalRadius|, |bottomRightHorizontalRadius|, |bottomRightVerticalRadius|)</code>, |targetEdge|,
670-
1, |outerRight| - |targetRight|, |outerBottom| - |targetBottom|, and |bottomRightShape|,
671-
and append it to |path|.
672-
1. Compute a [=corner path=] given
673-
the rectangle <code>(|outerLeft|, |outerBottom| - |bottomLeftVerticalRadius|, |bottomLeftHorizontalRadius|, |bottomLeftVerticalRadius|)</code>, |targetEdge|,
674-
2, |outerBottom| - |targetBottom|, |targetLeft| - |outerLeft|, and |bottomLeftShape|,
675-
and append it to |path|.
676-
1. Compute a [=corner path=] given
677-
the rectangle <code>(|outerLeft|, |outerTop|, |topLeftHorizontalRadius|, |topLeftVericalRadius|)</code>, |targetEdge|,
678-
3, |targetLeft| - |outerLeft|, |targetTop| - |outerTop|, and |topLeftShape|,
679-
and append it to |path|.
649+
1. [=Add corner to path=] given |path|,
650+
the [=rectangle=] <code>(|outerRight| - |topRightHorizontalRadius|, |outerTop|, |topRightHorizontalRadius|, |topRightVerticalRadius|)</code>, |targetEdge|,
651+
0, |targetTop| - |outerTop|, |outerRight| - |targetRight|, and |topRightShape|.
652+
1. [=Add corner to path=] given |path|,
653+
the [=rectangle=] <code>(|outerRight| - |bottomRightHorizontalRadius|, |outerBottom| - |bottomRightVerticalRadius|, |bottomRightHorizontalRadius|, |bottomRightVerticalRadius|)</code>, |targetEdge|,
654+
1, |outerRight| - |targetRight|, |outerBottom| - |targetBottom|, and |bottomRightShape|.
655+
1. [=Add corner to path=] given |path|,
656+
the [=rectangle=] <code>(|outerLeft|, |outerBottom| - |bottomLeftVerticalRadius|, |bottomLeftHorizontalRadius|, |bottomLeftVerticalRadius|)</code>, |targetEdge|,
657+
2, |outerBottom| - |targetBottom|, |targetLeft| - |outerLeft|, and |bottomLeftShape|.
658+
1. [=Add corner to path=] given |path|,
659+
the [=rectangle=] <code>(|outerLeft|, |outerTop|, |topLeftHorizontalRadius|, |topLeftVericalRadius|)</code>, |targetEdge|,
660+
3, |targetLeft| - |outerLeft|, |targetTop| - |outerTop|, and |topLeftShape|.
680661
1. If |spread| is not 0, then:
681-
1. Scale |path| by <code>1 + (|spread| * 2) / (|targetRect|'s [=width dimension|width=]), 1 + (|spread| * 2) / (|targetEdge|'s [=height dimension|height=])</code>.
662+
1. Scale |path| by <code>1 + (|spread| * 2) / (|targetEdge|'s [=width dimension|width=]), 1 + (|spread| * 2) / (|targetEdge|'s [=height dimension|height=])</code>.
682663
1. Translate |path| by <code>-|spread|, -|spread|</code>.
683664

684665
Note: this creates an effect where the resulting path has the same shape as the original path, but scaled to fit the given spread.
685666
1. Return |path|.
686667

687-
To compute the <dfn>corner path</dfn> given a rectangle |cornerRect|, a rectangle |trimRect|, and numbers |startThickness|, |endThickness|, |orientation|, and |curvature|:
688-
1. Assert: |orientation| is 0, 1, 2, or 3.
689-
1. If |curvature| is less than zero, then:
690-
1. Set |curvature| to <code>-|curvature|</code>.
691-
1. Swap between |startThickness| and |endThickness|.
692-
1. Set |orientation| to (|orientation| + 2) % 4.
693-
1. Let |cornerPath| be a path that begins at <code>(0, 1)</code>.
694-
1. Switch on |curvature|:
695-
<dl class=switch>
696-
: 0
697-
:: Extend |cornerPath| by adding a straight line to <code>(1, 0)</code>.
698-
699-
: &infin;
700-
::
701-
1. Extend |cornerPath| by adding a straight line to <code>(1, 1)</code>.
702-
1. Extend |cornerPath| by adding a straight line to <code>(1, 0)</code>.
703-
704-
: Otherwise
705-
::
706-
1. Let |K| be <code>0.5<sup>|curvature|</sup></code>.
707-
1. For each |T| between 0 and 1, extend |cornerPath| through <code>(|T|<sup>|K|</sup>, (1−|T|)<sup>|K|</sup>)</code>.
708-
709-
User agents may approximate this path, for instance, by using concatenated Bezier curves, to balance between performance and rendering accuracy.
710-
</dl>
711-
712-
1. Let (|x|, |y|, |width|, |height|) be |targetRect|.
713-
1. Let |clockwiseRectQuad| be « (|x|, |y|), (|x| + |width|, |y|), (|x| + |width|, |y| + |height|), (|x|, |y| + height|) ».
714-
1. Let |curveStartPoint| be |clockwiseRectQuad|[|orientation|].
715-
1. Let |curveEndPoint| be |clockwiseRectQuad|[(|orientation| + 2) % 4].
716-
1. If either |startThickness| or |endThickness| is greater than 0, then:
717-
718-
Note: the following substeps compute a new |curveStartPoint| and |curveEndPoint|, based on the thickness and |curvature|.
719-
The start and end points are offset inwards, perpendicular to the direction of the curve, with the corresponding |startThickness| or |endThickness|.
720-
721-
1. Let |tangentUnitVector| be <code>(1, 0)</code>.
722-
723-
Note: |tangentUnitVector| is a unit vector (length of 1 pixel) that points along a curve with both positive X and Y components
724-
(like a top-right corner) and reflects the given |curvature|. This base vector can then be rotated to align with the specific corner's orientation
725-
and scaled to match the required border thickness.
726-
For round curvatures, or for hyperellipses (|curvature| greater than 1), the tangent is a horizontal line to the right.
727-
728-
<figure>
729-
<img src="images/corner-shape-target-unit-vector-round.svg"
730-
style="background: white; padding: 8px;"
731-
alt="Tangent unit vector with round (s=1)">
732-
<figcaption>When the 'corner-shape' is ''corner-shape/round'' or more convex (<code>>= 1</code>), the unit vector is <code>1, 0</code>.
733-
</figcaption>
734-
</figure>
735-
736-
1. If |curvature| is less than 1:
737-
1. Let |halfCorner| be the [=normalized superellipse half corner=] given |curvature|.
738-
1. Let |offsetX| be <code>max(0, (|halfCorner| - 1) * 2 + &Sqrt;2)</code>.
739-
1. Let |offsetY| be <code>max(0, &Sqrt;2 - |halfCorner| * 2)</code>.
740-
741-
Note: This formula defines the tangent of a quadratic Bezier curve that's equivalent to a superellipse quadrant.
742-
Notably, convex hypoellipses (superellipses with a [=superellipse parameter|parameter=] between 0 and 1) can be very precisely represented by quadratic curves.
743-
744-
1. Let |length| be <code>hypot(|offsetX|, |offsetY|)</code>.
745-
1. Set |tangentUnitVector| to <code>(|offsetX| / |length|, |offsetY| / |length|)</code>.
746-
747-
At this point |curvature| is guaranteed to be convex (>=1), so ther resulting |tangentUnitVector| would be in the range between <code>(1, 0)</code> and <code>(&Sqrt;2/2, &Sqrt;2/2)</code>.
748-
749-
<figure>
750-
<img src="images/corner-shape-target-unit-vector-bevel.svg"
751-
style="background: white; padding: 8px;"
752-
alt="Tangent unit vector with bevel (s=0)">
753-
<figcaption>When the 'corner-shape' is ''corner-shape/bevel'' (<code>0</code>), the unit vector is <code>&Sqrt;2/2, &Sqrt;2/2</code>.
754-
</figcaption>
755-
</figure>
756-
757-
1. Let |startOffset| be |tangentUnitVector|, scaled by |startThickness| and rotated <code>90° * ((|orientation| + 1) % 4)</code> clockwise.
758-
1. Let |endOffset| be |tangentUnitVector|, scaled by |endThickness| and rotated by <code>90° * ((|orientation| + 2) % 4)</code> clockwise.
759-
1. Translate |curveStartPoint| by |startOffset|.
760-
1. Translate |curveEndPoint| by |endOffset|.
761-
1. Set |cornerRect| to a rectangle that contains |curveStartPoint| and |curveEndPoint|.
762-
1. Rotate |cornerPath| by <code>90° * |orientation|</code>, with <code>(0.5, 0.5)</code> as the origin, as described [=transformation matrix|here=].
763-
1. Scale |cornerPath| by <code>|cornerRect|'s [=width dimension|width=], |cornerRect|'s [=width dimension|height=]</code>.
764-
1. translate |cornerPath| by<code> |cornerRect|'s [=x coordinate|x=], |cornerRect|'s [=y coordinate|y=]</code>.
765-
1. Trim |cornerPath| to |trimRect|.
766-
1. Return |cornerPath|.
668+
To <dfn>add corner to path</dfn> given a path |path|, a rectangle |cornerRect|, a rectangle |trimRect|,
669+
and numbers |orientation|, |startThickness|, |endThickness|, |curvature|:
670+
671+
1. If |cornerRect| is empty, or if |curvature| is &infin;:
672+
1. Let |innerQuad| be |trimRect|'s [=clockwise quad=] .
673+
1. Extend |path| by drawing a line to |innerQuad|[<code>(|orienation| + 1) % 4</code>].
674+
1. Return.
675+
676+
1. Let |cornerQuad| be |cornerRect|'s [=clockwise quad=].
677+
1. If |curvature| is -&infin;:
678+
1. Extend |path| by drawing a line from |cornerQuad|[0] to |cornerQuad|[3], trimmed by |trimRect|.
679+
1. Extend |path| by drawing a line from |cornerQuad|[3] to |cornerQuad|[2], trimmed by |trimRect|.
680+
1. Return.
681+
682+
1. Let |clampedNormalizedHalfCorner| be the [=normalized superellipse half corner=] given <code>clamp(|curvature|, -1, 1)</code>.
683+
1. Let |equivalentQuadraticControlPointX| be <code>|clampedNormalizedHalfCorner| * 2 - 0.5</code>.
684+
1. Let |curveStartPoint| be the [=aligned corner point=] given |cornerQuad|[|orienation|], the vector (|equivalentQuadraticControlPointX|, <code>1 - |equivalentQuadraticControlPointX|</code>), |startThickness|, and |orientation| + 1.
685+
1. Let |curveEndPoint| by the [=aligned corner point=] given |cornerQuad|[(|orientation| + 2) % 4], the vector (<code>|equivalentQuadraticControlPointX| - 1</code>, <code>-|equivalentQuadraticControlPointX|</code>), |endThickness|, and |orientation| + 3.
686+
1. Let |alignedCornerRect| be a [=rectangle=] that includes the points |curveStartPoint| and |curveEndPoint|.
687+
1. Let |projectionToCornerRect| be a [=transformation matrix=],
688+
translated by <code>(|alignedCornerRect|'s [=x coordinate=], |alignedCornerRect|'s [=y coordinate=])</code>,
689+
scaled by <code>(|alignedCornerRect|'s [=width dimension=], |alignedCornerRect|'s [=height dimension=])</code>,
690+
translated by <code>(0.5, 0.5)</code>,
691+
rotated by <code>90deg * orientation</code>,
692+
and translated by <code>(-0.5, -0.5)</code>.
693+
694+
1. Let |K| be <code>0.5<sup>abs(|curvature|)</sup></code>.
695+
1. For each |T| between 0 and 1:
696+
1. Let |A| be <code>|T|<sup>|K|</sup></code>.
697+
1. Let |B| be <code>1 - (1 - |T|)<sup>|K|</sup></code>.
698+
1. Let |normalizedPoint| be <code>(|A|, |B|)</code> if |curvature| is positive, otherwise <code>(|B|, |A|)</code>.
699+
1. Let |absolutePoint| be |normalizedPoint|, transformed by |projectionToCornerRect|.
700+
1. If |absolutePoint| is within |trimRect|, extend |path| through |absolutePoint|.
701+
702+
Note: User agents may approximate this algorithm, for instance, by using concatenated Bezier curves, to balance between performance and rendering accuracy.
703+
704+
To compute the <dfn>aligned corner point</dfn> given a point |originalPoint|, a two-component vector |offsetFromControlPoint|, a number |thickness|, and a number |orientation|:
705+
1. Let |length| be <code>hypot(|offsetFromControlPoint|.x, |offsetFromControlPoint|.y)</code>.
706+
1. Rotate |offsetFromControlPoint| by <code>90deg * |orientation|</code>, and scale by |thickness|.
707+
1. Translate |originalPoint| by <code>|offsetFromControlPoint|.x / |length|, |offsetFromControlPoint|.y / |length|</code>, and return the result.
708+
709+
The <dfn>clockwise quad</dfn> given a [=rectangle=] |rect|, is a [=quadrilateral=] with the points
710+
(|rect|'s [=x coordinate=], |rect|'s [=y coordinate=]),
711+
(|rect|'s [=x coordinate=] + |rect|'s [=width dimension=], |rect|'s [=y coordinate=]),
712+
(|rect|'s [=x coordinate=] + |rect|'s [=width dimension=], |rect|'s [=y coordinate=] + |rect|'s [=height dimension=]),
713+
(|rect|'s [=x coordinate=], |rect|'s [=y coordinate=] + |rect|'s [=height dimension=]).
714+
767715
</div>
768716

769717
<h4 id=corner-shape-constrain-radii>
@@ -829,7 +777,7 @@ To compute the <dfn>normalized superellipse half corner</dfn> given a [=superell
829777
::
830778
1. Let |k| be <code>0.5<sup>abs(|s|)</sup></code>.
831779
1. Let |convexHalfCorner| be <code>0.5<sup>|k|</sup></code>.
832-
1. If |param| is less than 0, return <code>1 - |convexHalfCorner|</code>.
780+
1. If |s| is less than 0, return <code>1 - |convexHalfCorner|</code>.
833781
1. Return |convexHalfCorner|.
834782
</dl>
835783
</div>

0 commit comments

Comments
 (0)