You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[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.
@@ -609,33 +609,18 @@ The 'corner-*-shape' Shorthands And Longhands
609
609
610
610
Rendering 'corner-shape'
611
611
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.
612
614
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.
613
617
614
-
The canonical superellipse formula can be described in Cartesian coordinates, as follows,
615
-
where s is the [=superellipse parameter=]:
616
-
617
-
618
-
k = 2abs(|s|)
619
-
xk + yk = 1
620
-
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
-
634
-
635
-
style="background: white;"
636
-
alt="Adjusting corner shapes">
637
-
Borders are aligned to the curve, shadows and clip are aligned to the axis.
638
-
618
+
619
+
620
+
style="background: white;"
621
+
alt="Adjusting corner shapes">
622
+
Borders are aligned to the curve, shadows and clip are aligned to the axis.
623
+
639
624
640
625
641
626
An [=/element=] |element|'s outer contour 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=
653
638
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'.
654
639
655
640
656
-
To compute an [=/element=] |element|'s border contour path given an an [=edge=] |targetEdge| and an optional number |spread| (default 0):
641
+
To compute an [=/element=] |element|'s border contour path given an [=edge=] |targetEdge| and an optional number |spread| (default 0):
657
642
1. Let |outerLeft|, |outerTop|, |outerRight|, |outerBottom| be |element|'s [=unshaped edge|unshaped=][=border edge=].
658
643
1. Let |topLeftHorizontalRadius|, |topLeftVericalRadius|, |topRightHorizontalRadius|, |topRightVerticalRadius|, |bottomRightHorizontalRadius|,
659
644
|bottomRightVerticalRadius|, |bottomLeftHorizontalRadius|, and |bottomLeftVerticalRadius| be |element| [=border edge=]'s radii,
660
645
scaled by |element|'s [=opposite corner scale factor=].
661
646
1. Let |topLeftShape|, |topRightShape|, |bottomRightShape|, and |bottomLeftShape| be |element|'s [=computed value|computed=] 'corner-*-shape' values.
662
647
1. Let |targetLeft|, |targetTop|, |targetRight|, |targetBottom| [=unshaped edge|unshaped=] |targetEdge|.
663
648
1. Let |path| be a new path [[SVG2]].
664
-
1. Compute a [=corner path=] given
665
-
the [=rectangle=](|outerRight| - |topRightHorizontalRadius|, |outerTop|, |topRightHorizontalRadius|, |topRightVerticalRadius|),
666
-
0, |targetTop| - |outerTop|, |outerRight| - |targetRight|, and |topRightShape|,
Note: this creates an effect where the resulting path has the same shape as the original path, but scaled to fit the given spread.
685
666
1. Return |path|.
686
667
687
-
To compute the corner path 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 -|curvature|.
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 (0, 1).
694
-
1. Switch on |curvature|:
695
-
696
-
: 0
697
-
:: Extend |cornerPath| by adding a straight line to (1, 0).
698
-
699
-
: ∞
700
-
::
701
-
1. Extend |cornerPath| by adding a straight line to (1, 1).
702
-
1. Extend |cornerPath| by adding a straight line to (1, 0).
703
-
704
-
: Otherwise
705
-
::
706
-
1. Let |K| be 0.5|curvature|.
707
-
1. For each |T| between 0 and 1, extend |cornerPath| through (|T||K|, (1−|T|)|K|).
708
-
709
-
User agents may approximate this path, for instance, by using concatenated Bezier curves, to balance between performance and rendering accuracy.
710
-
711
-
712
-
1. Let (|x|, |y|, |width|, |height|) be |targetRect|.
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 (1, 0).
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
-
729
-
730
-
style="background: white; padding: 8px;"
731
-
alt="Tangent unit vector with round (s=1)">
732
-
When the 'corner-shape' is ''corner-shape/round'' or more convex (>= 1), the unit vector is 1, 0.
733
-
734
-
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 max(0, (|halfCorner| - 1) * 2 + √2).
739
-
1. Let |offsetY| be max(0, √2 - |halfCorner| * 2).
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 hypot(|offsetX|, |offsetY|).
745
-
1. Set |tangentUnitVector| to (|offsetX| / |length|, |offsetY| / |length|).
746
-
747
-
At this point |curvature| is guaranteed to be convex (>=1), so ther resulting |tangentUnitVector| would be in the range between (1, 0) and (√2/2, √2/2).
748
-
749
-
750
-
751
-
style="background: white; padding: 8px;"
752
-
alt="Tangent unit vector with bevel (s=0)">
753
-
When the 'corner-shape' is ''corner-shape/bevel'' (0), the unit vector is √2/2, √2/2.
754
-
755
-
756
-
757
-
1. Let |startOffset| be |tangentUnitVector|, scaled by |startThickness| and rotated 90° * ((|orientation| + 1) % 4) clockwise.
758
-
1. Let |endOffset| be |tangentUnitVector|, scaled by |endThickness| and rotated by 90° * ((|orientation| + 2) % 4) 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 90° * |orientation|, with (0.5, 0.5) as the origin, as described [=transformation matrix|here=].
763
-
1. Scale |cornerPath| by |cornerRect|'s [=width dimension|width=], |cornerRect|'s [=width dimension|height=].
764
-
1. translate |cornerPath| by |cornerRect|'s [=x coordinate|x=], |cornerRect|'s [=y coordinate|y=].
765
-
1. Trim |cornerPath| to |trimRect|.
766
-
1. Return |cornerPath|.
668
+
To add corner to path 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 ∞:
672
+
1. Let |innerQuad| be |trimRect|'s [=clockwise quad=] .
673
+
1. Extend |path| by drawing a line to |innerQuad|[(|orienation| + 1) % 4].
674
+
1. Return.
675
+
676
+
1. Let |cornerQuad| be |cornerRect|'s [=clockwise quad=].
677
+
1. If |curvature| is -∞:
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 clamp(|curvature|, -1, 1).
683
+
1. Let |equivalentQuadraticControlPointX| be |clampedNormalizedHalfCorner| * 2 - 0.5.
684
+
1. Let |curveStartPoint| be the [=aligned corner point=] given |cornerQuad|[|orienation|], the vector (|equivalentQuadraticControlPointX|, 1 - |equivalentQuadraticControlPointX|), |startThickness|, and |orientation| + 1.
685
+
1. Let |curveEndPoint| by the [=aligned corner point=] given |cornerQuad|[(|orientation| + 2) % 4], the vector (|equivalentQuadraticControlPointX| - 1, -|equivalentQuadraticControlPointX|), |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 (|alignedCornerRect|'s [=x coordinate=], |alignedCornerRect|'s [=y coordinate=]),
689
+
scaled by (|alignedCornerRect|'s [=width dimension=], |alignedCornerRect|'s [=height dimension=]),
690
+
translated by (0.5, 0.5),
691
+
rotated by 90deg * orientation,
692
+
and translated by (-0.5, -0.5).
693
+
694
+
1. Let |K| be 0.5abs(|curvature|).
695
+
1. For each |T| between 0 and 1:
696
+
1. Let |A| be |T||K|.
697
+
1. Let |B| be 1 - (1 - |T|)|K|.
698
+
1. Let |normalizedPoint| be (|A|, |B|) if |curvature| is positive, otherwise (|B|, |A|).
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 aligned corner point given a point |originalPoint|, a two-component vector |offsetFromControlPoint|, a number |thickness|, and a number |orientation|:
705
+
1. Let |length| be hypot(|offsetFromControlPoint|.x, |offsetFromControlPoint|.y).
706
+
1. Rotate |offsetFromControlPoint| by 90deg * |orientation|, and scale by |thickness|.
707
+
1. Translate |originalPoint| by |offsetFromControlPoint|.x / |length|, |offsetFromControlPoint|.y / |length|, and return the result.
708
+
709
+
The clockwise quad given a [=rectangle=] |rect|, is a [=quadrilateral=] with the points
0 commit comments