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] Refactor contour path algorithm
Use vector math to better describe how to draw a border-align contour.
Also use the miter points (which are the axisAlignedCornerStart/End points)
to make sure shadows are rendered in a border-aligned manner.
Closes#13037
* Add note
* nit
* Fix notch
* Update css-borders-4/Overview.bs
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
* Update css-borders-4/Overview.bs
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
* Update css-borders-4/Overview.bs
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
* Update css-borders-4/Overview.bs
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
* Update css-borders-4/Overview.bs
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
* Update css-borders-4/Overview.bs
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
* Update css-borders-4/Overview.bs
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
* Update css-borders-4/Overview.bs
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
* nits
---------
Co-authored-by: Sebastian Zartner <sebastianzartner@gmail.com>
<figcaption>Borders are aligned to the curve, shadows and clip are aligned to the axis.</figcaption>
1481
-
</figure>
1482
-
1473
+
When rendering borders or outlines, the offset is aligned to the curve of the element's shape.
1474
+
When rendering 'box-shadow' or offsetting for 'overflow-clip-margin', the offset is also aligned to the same curve in the other direction, and the curve continues to the outer edge.
1483
1475
1484
1476
An [=/element=] |element|'s <dfn>outer contour</dfn> is the [=border contour path=] given |element| and |element|'s [=border edge=].
1485
1477
1486
-
An [=/element=] |element|'s <dfn>inner contour</dfn> is the [=border contour path=] given |element| and |element|'s [=padding edge=].
1478
+
An [=/element=] |element|'s <dfn>inner contour</dfn> is the [=border contour path=] given |element| and |element|'s [=border width=].
1487
1479
1488
1480
An [=/element=]'s [=border=] is rendered in the area between its [=outer contour=] and its [=inner contour=].
1489
1481
1490
1482
An [=/element=]'s [=outline=] follows the [=outer contour=] with the [=used value|used=] 'outline-width' and 'outline-offset'.
1491
1483
The precise way in which it is rendered is implementation-defined.
1492
1484
1493
1485
An [=/element=]'s [=overflow=] area is shaped by its [=inner contour=].
1494
-
An [=/element=]'s [=overflow clip edge=] is shaped by the [=border contour path=] given |element|, and |element|'s [=padding edge=], and |element|'s [=used value|used=] 'overflow-clip-margin'.
1495
-
1496
-
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'.
To compute an [=/element=] |element|'s <dfn>border contour path</dfn> given an [=edge=] |targetEdge| and an optional number |spread| (default 0):
1500
-
1. Let |outerLeft|, |outerTop|, |outerRight|, |outerBottom| be |element|'s [=unshaped edge|unshaped=][=border edge=], outset by |spread|.
1501
-
1. Let |topLeftHorizontalRadius|, |topLeftVericalRadius|, |topRightHorizontalRadius|, |topRightVerticalRadius|, |bottomRightHorizontalRadius|,
1502
-
|bottomRightVerticalRadius|, |bottomLeftHorizontalRadius|, and |bottomLeftVerticalRadius| be |element| [=border edge=]'s radii,
1503
-
scaled by |element|'s [=opposite corner scale factor=] and [=outset-adjusted border radius|outset-adjusted=].
1504
-
1. Let |topLeftShape|, |topRightShape|, |bottomRightShape|, and |bottomLeftShape| be |element|'s [=computed value|computed=] 'corner-*-shape' values.
1505
-
1. Let |targetLeft|, |targetTop|, |targetRight|, |targetBottom| [=unshaped edge|unshaped=] |targetEdge|.
1506
-
1. Let |path| be a new path [[SVG2]].
1507
-
1. [=Add corner to path=] given |path|,
1508
-
the [=rectangle=]<code>(|outerRight| - |topRightHorizontalRadius|, |outerTop|, |topRightHorizontalRadius|, |topRightVerticalRadius|)</code>, |targetEdge|,
1509
-
0, |targetTop| - |outerTop|, |outerRight| - |targetRight|, and |topRightShape|.
1510
-
1. [=Add corner to path=] given |path|,
1511
-
the [=rectangle=]<code>(|outerRight| - |bottomRightHorizontalRadius|, |outerBottom| - |bottomRightVerticalRadius|, |bottomRightHorizontalRadius|, |bottomRightVerticalRadius|)</code>, |targetEdge|,
1512
-
1, |outerRight| - |targetRight|, |outerBottom| - |targetBottom|, and |bottomRightShape|.
1513
-
1. [=Add corner to path=] given |path|,
1514
-
the [=rectangle=]<code>(|outerLeft|, |outerBottom| - |bottomLeftVerticalRadius|, |bottomLeftHorizontalRadius|, |bottomLeftVerticalRadius|)</code>, |targetEdge|,
1515
-
2, |outerBottom| - |targetBottom|, |targetLeft| - |outerLeft|, and |bottomLeftShape|.
1516
-
1. [=Add corner to path=] given |path|,
1517
-
the [=rectangle=]<code>(|outerLeft|, |outerTop|, |topLeftHorizontalRadius|, |topLeftVericalRadius|)</code>, |targetEdge|,
1518
-
3, |targetLeft| - |outerLeft|, |targetTop| - |outerTop|, and |topLeftShape|.
1519
-
1. Return |path|.
1486
+
An [=/element=]'s [=overflow clip edge=] is shaped by the [=border contour path=] given |element|, and an [=uniform inset from an outset=] given |element|'s [=used value|used=]'overflow-clip-margin'.
1487
+
1488
+
Each shadow of [=/element=]'s 'box-shadow' is shaped by the [=border contour path=] given |element|, and an [=uniform inset from an outset=] given the shadow's [=used value|used=]'box-shadow-spread'.
1489
+
1490
+
An <dfn>uniform inset from an outset</dfn> given a number |outset| is an [=edge=] whose value is <code>-|outset|</code> in all directions.
1491
+
1492
+
<h5 id=vector-math-helpers>Vector Math Helpers</h5>
1493
+
1494
+
A <dfn>two-dimensional vector</dfn> is a pair of numbers (x, y).
1495
+
<div algorithm="extend-point">
1496
+
A <dfn>point extended by vectors</dfn> given a {{DOMPointReadOnly}} |p| and a [=/list=] of [=two-dimensional vector=]s |vectors|:
1497
+
1. Let |x| be |p|'s {{DOMPointReadOnly/x}}.
1498
+
1. Let |y| be |p|'s {{DOMPointReadOnly/y}}.
1499
+
1. [=list/For each=] |v| in |vectors|:
1500
+
1. Increment |x| by |v|[0];
1501
+
1. Increment |y| by |v|[1];
1502
+
1. Return a new {{DOMPointReadOnly}} whose {{DOMPointReadOnly/x}} is |x| and whose {{DOMPointReadOnly/y}} is |y|.
1503
+
</div>
1520
1504
1521
-
To <dfn>add corner to path</dfn> given a path |path|, a rectangle |cornerRect|, a rectangle |trimRect|,
1522
-
and numbers |orientation|, |startThickness|, |endThickness|, |curvature|:
1523
-
1524
-
1. If |cornerRect| is empty, or if |curvature| is ∞:
1525
-
1. Let |innerQuad| be |trimRect|'s [=clockwise quad=] .
1526
-
1. Extend |path| by drawing a line to |innerQuad|[<code>(|orienation| + 1) % 4</code>].
1527
-
1. Return.
1528
-
1529
-
1. Let |cornerQuad| be |cornerRect|'s [=clockwise quad=].
1530
-
1. If |curvature| is -∞:
1531
-
1. Extend |path| by drawing a line from |cornerQuad|[0] to |cornerQuad|[3], trimmed by |trimRect|.
1532
-
1. Extend |path| by drawing a line from |cornerQuad|[3] to |cornerQuad|[2], trimmed by |trimRect|.
1533
-
1. Return.
1534
-
1535
-
1. Let |clampedNormalizedHalfCorner| be the [=normalized superellipse half corner=] given <code>clamp(|curvature|, -1, 1)</code>.
1536
-
1. Let |equivalentQuadraticControlPointX| be <code>|clampedNormalizedHalfCorner| * 2 - 0.5</code>.
1537
-
1. Let |curveStartPoint| be the [=aligned corner point=] given |cornerQuad|[|orienation|], the vector (|equivalentQuadraticControlPointX|, <code>1 - |equivalentQuadraticControlPointX|</code>), |startThickness|, and |orientation| + 1.
1538
-
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.
1539
-
1. Let |alignedCornerRect| be a [=rectangle=] that includes the points |curveStartPoint| and |curveEndPoint|.
1540
-
1. Let |projectionToCornerRect| be a [=transformation matrix=],
1541
-
translated by <code>(|alignedCornerRect|'s [=x coordinate=], |alignedCornerRect|'s [=y coordinate=])</code>,
1542
-
scaled by <code>(|alignedCornerRect|'s [=width dimension=], |alignedCornerRect|'s [=height dimension=])</code>,
1543
-
translated by <code>(0.5, 0.5)</code>,
1544
-
rotated by <code>90deg * orientation</code>,
1545
-
and translated by <code>(-0.5, -0.5)</code>.
1546
-
1547
-
1. Let |K| be <code>0.5<sup>abs(|curvature|)</sup></code>.
1548
-
1. For each |T| between 0 and 1:
1549
-
1. Let |A| be <code>|T|<sup>|K|</sup></code>.
1550
-
1. Let |B| be <code>1 - (1 - |T|)<sup>|K|</sup></code>.
1551
-
1. Let |normalizedPoint| be <code>(|A|, |B|)</code> if |curvature| is positive, otherwise <code>(|B|, |A|)</code>.
1552
-
1. Let |absolutePoint| be |normalizedPoint|, transformed by |projectionToCornerRect|.
1553
-
1. If |absolutePoint| is within |trimRect|, extend |path| through |absolutePoint|.
1554
-
1555
-
Note: User agents may approximate this algorithm, for instance, by using concatenated Bezier curves, to balance between performance and rendering accuracy.
1556
-
1557
-
To compute the <dfn>aligned corner point</dfn> given a point |originalPoint|, a two-component vector |offsetFromControlPoint|, a number |thickness|, and a number |orientation|:
1558
-
1. Let |length| be <code>hypot(|offsetFromControlPoint|.x, |offsetFromControlPoint|.y)</code>.
1559
-
1. Rotate |offsetFromControlPoint| by <code>90deg * |orientation|</code>, and scale by |thickness|.
1560
-
1. Translate |originalPoint| by <code>|offsetFromControlPoint|.x / |length|, |offsetFromControlPoint|.y / |length|</code>, and return the result.
1561
-
1562
-
The <dfn>clockwise quad</dfn> given a [=rectangle=] |rect|, is a [=quadrilateral=] with the points
To <dfn for="two-dimensional vector">scale</dfn> a [=two-dimensional vector=] |v| by a number |factor|, return <code>(|v|[0] ⋅ |factor|, |v|[1] ⋅ |factor|)</code>.
1567
1506
1507
+
<div algorithm="normalize-vector">
1508
+
To <dfn for="two-dimensional vector">normalize</dfn> a [=two-dimensional vector=] |v|:
The <dfn for="two-dimensional vector">perpendicular</dfn> of a [=two-dimensional vector=] |v| is <code>(-|v|[1], |v|[0])</code>.
1515
+
1516
+
The <dfn>vector between two points</dfn>, given {{DOMPoint}}s |a| and |b|, is <code>(|b|'s {{DOMPointReadOnly/x}} - |a|'s {{DOMPointReadOnly/x}}, |b|'s {{DOMPointReadOnly/y}} - |a|'s {{DOMPointReadOnly/y}})</code>.
1517
+
1518
+
<h5 id=contour-path>Computing a contoured path</h5>
1519
+
1520
+
This algorithm describes how to compute a path with shaped corners (a path that is either inset or outset from the original).
1521
+
To avoid the complexities of defining how superellipses intersect, the algorithm simplifies the process by specifying that each corner is "clipped out" of the path.
1522
+
The specific implementation details of this clipping operation are left to implementations.
1523
+
1524
+
<div algorithm="border-aligned-contour-path">
1525
+
To compute an [=/element=] |element|'s <dfn>border contour path</dfn> given numbers |topInset|, |rightInset|, |bottomInset|, |leftInset|:
1526
+
1. Let |borderRect| be |element|'s [=border box=].
1527
+
1. Let |unshapedTargetRect| be |borderRect|, inset by |topInset|, |rightInset|, |bottomInset|, |leftInset|.
1528
+
1529
+
Note: If this is a shadow or 'overflow-clip-margin', the insets would have negative values and |unshapedTargetRect| would become an outset of |borderRect|.
1530
+
1531
+
1. Let |path| be a path that contains |unshapedTargetRect|.
1532
+
1533
+
1. Let |scaleFactor| be the [=opposite corner scale factor=] given |element|.
1534
+
1535
+
1. Let |adjustedRadius| be the following steps given a property |P|, and numbers |insetX| and |insetY|:
1536
+
1. Let |radius| be |element|'s [=used value=] of |P|.
1537
+
1. If |insetX| and |insetY| are zero, return |radius|.
1538
+
1. If |insetX| or |insetY| are positive, then return <code>(|radius|'s [=width=] ⋅ |scaleFactor|, |radius|'s [=height=] ⋅ |scaleFactor|)</code>.
1539
+
1. Let |adjustedRadiusInOutsetCoordinates| be the [=outset-adjusted border radius=] given |borderRect|'s size, |radius|, and <code>(-|insetX|, -|insetY|)</code>.
To get the <dfn>border-aligned corner clip-out path</dfn> given a {{DOMPointReadOnly}} |originalCornerOuter|, a [=two-dimensional vector=] |vectorTowardsStart|, a [=two-dimensional vector=] |vectorTowardsEnd|, a [=superellipse parameter=] |curvature|, and numbers |startInset| and |endInset|:
1574
+
1. If |curvature| is ∞, then return an empty path.
1575
+
1. Let |clampedHalfCorner| be the [=normalized superellipse half corner=] given <code>clamp(|curvature|, -1, 1)</code>.
1576
+
1. Let |originalCornerStart| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |vectorTowardsStart| ».
1577
+
1. Let |originalCornerEnd| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |vectorTowardsEnd| ».
1578
+
1. Let |originalCornerCenter| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |vectorTowardsStart|, |vectorTowardsEnd| ».
1579
+
1. Let |extendStart| be a [=vector between two points|vector between=] |originalCornerStart| and |originalCornerCenter|, [=two-dimensional vector/normalize|normalized=] and [=two-dimensional vector/scale|scaled by=] |startInset|.
1580
+
1. Let |extendEnd| be a [=vector between two points|vector between=] |originalCornerEnd| and |originalCornerCenter|, [=two-dimensional vector/normalize|normalized=] and [=two-dimensional vector/scale|scaled by=] |endInset|.
1581
+
1. Let |clipStart| be |originalCornerStart|, [=point extended by vectors|extended by=] « |extendStart| ».
1582
+
1. Let |clipEnd| be |originalCornerEnd|, [=point extended by vectors|extended by=] « |extendEnd| ».
1583
+
1. Let |clipOuter| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |extendStart|, |extendEnd| ».
1584
+
1. Let |vectorFromStartToControlPoint| be the the [=two-dimensional vector=]<code>(2 ⋅ |clampedHalfCorner| - 0.5, 1.5 - 2 ⋅ |clampedHalfCorner|)</code>.
1585
+
1. Let |singlePixelVectorFromStartToControlPoint| be the |vectorFromStartToControlPoint|, [=two-dimensional vector/normalize|normalized=].
1586
+
1. Let <code>|strokeA|, |strokeB|</code> be the [=two-dimensional vector/perpendicular=] of |singlePixelVectorFromStartToControlPoint|.
1587
+
1. Let |offset1| be [=vector between two points|the vector between=] |originalCornerStart| and |outerCorner|, [=two-dimensional vector/normalize|normalized=] and [=two-dimensional vector/scale|scaled by=]<code>|startInset| ⋅ |strokeA|</code>.
1588
+
1. Let |offset2| be [=vector between two points|the vector between=] |outerCorner| and |originalCornerEnd|, [=two-dimensional vector/normalize|normalized=] and [=two-dimensional vector/scale|scaled by=]<code>|startInset| ⋅ |strokeB|</code>.
1589
+
1. Let |offset3| be [=vector between two points|the vector between=] |originalCornerEnd| and |originalCornerCenter|, [=two-dimensional vector/normalize|normalized=] and [=two-dimensional vector/scale|scaled by=]<code>|endInset| ⋅ |strokeB|</code>.
1590
+
1. Let |offset4| be [=vector between two points|the vector between=] |originalCornerCenter| and |originalCornerStart|, [=two-dimensional vector/normalize|normalized=] and [=two-dimensional vector/scale|scaled by=]<code>|endInset| ⋅ |strokeA|</code>.
1591
+
1. Let |adjustedCornerStart| be |originalCornerStart|, [=point extended by vectors|extended by=] « |offset1|, |offset2| ».
1592
+
1. Let |adjustedCornerEnd| be |originalCornerEnd|, [=point extended by vectors|extended by=] « |offset3|, |offset4| ».
1593
+
1. Let |adjustedCornerCenter| be |originalCornerCenter|, [=point extended by vectors|extended by=] « |offset4|, |offset1| ».
1594
+
1. Let |adjustedCornerOuter| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |offset2|, |offset3| ».
1595
+
1. Let |curveCenter| be |adjustedCornerOuter| if |curvature| is less than 0, |adjustedCornerCenter| otherwise.
1596
+
1. Let |mapPointToCorner| be the following steps: given numbers |x| and |y|:
1597
+
1. Let |v1| be [=vector between two points|the vector between=] |curveCenter| and |adjustedCornerEnd|, [=two-dimensional vector/scale|scaled by=] |x|.
1598
+
1. Let |v2| be [=vector between two points|the vector between=] |curveCenter| and |adjustedCornerStart|, [=two-dimensional vector/scale|scaled by=] |y|.
1599
+
1. Return the {{DOMPointReadOnly}} at <code>(|x|, |y|)</code>, [=point extended by vectors|extended by=] « |v1|, |v2| ».
1600
+
1601
+
1. Let |controlPoint| be the result of calling |mapPointToCorner| given |vectorFromStartToControlPoint|'s {{DOMPointReadOnly/y}} and <code>1 - |unitVectorFromStartToControlPoint|'s {{DOMPointReadOnly/x}}</code>.
1602
+
1. Let |axisAlignedCornerStart| be the intersection between the lines <code>(|adjustedCornerStart|, |controlPoint|)</code> and <code>(|clipStart|, |clipOuter|)</code>. If the lines are parallel, let it be <code>adjustedCornerStart</code>.
1603
+
1. Let |axisAlignedCornerEnd| be the intersection between the lines <code>(|adjustedCornerEnd|, |controlPoint|)</code> and <code>(|clipEnd|, |clipOuter|)</code>. If the lines are parallel, let it be <code>adjustedCornerEnd</code>.
1604
+
1605
+
Note: |axisAlignedCornerStart| and |axisAlignedCornerEnd| act as "miters" when rendering an outset.
1606
+
They are a straight extension of the curve's tangent, intersecting with the unshaped target rect.
1607
+
1608
+
1. Let |path| be a path, starting at |axisAlignedCornerStart|.
1609
+
1. If |curvature| is -∞:
1610
+
1. Extend |path| to |adjustedCornerStart|.
1611
+
1. Extend |path| to |adjustedCornerCenter|.
1612
+
1. Extend |path| to |adjustedCornerEnd|.
1613
+
1. Otherwise:
1614
+
1. Let |K| be <code>0.5<sup>-abs(|curvature|)</sup></code>.
1615
+
1. For every |T| between 0 and 1, in an [=implementation-approximated=] manner,
1616
+
extend |path| to the result of calling |mapPointToCorner| given <code>|T|<sup>|K|</sup></code> and <code>(1 - |T|)<sup>|K|</sup></code>.
1617
+
1618
+
1. Extend |path| to |axisAlignedCornerEnd|.
1619
+
1. Extend |path| to |clipOuter|.
1620
+
1. Return |path|.
1568
1621
</div>
1569
1622
1570
1623
<wpt>
@@ -1635,7 +1688,7 @@ To compute the <dfn>normalized superellipse half corner</dfn> given a [=superell
1635
1688
: Otherwise
1636
1689
::
1637
1690
1. Let |k| be <code>0.5<sup>abs(|s|)</sup></code>.
1638
-
1. Let |convexHalfCorner| be <code>0.5<sup>|k|</sup></code>.
1691
+
1. Let |convexHalfCorner| be <code>0.5<sup>1/|k|</sup></code>.
1639
1692
1. If |s| is less than 0, return <code>1 - |convexHalfCorner|</code>.
0 commit comments