diff --git a/css-borders-4/Overview.bs b/css-borders-4/Overview.bs index 2a4c5fa8b75..4ea51e10b65 100644 --- a/css-borders-4/Overview.bs +++ b/css-borders-4/Overview.bs @@ -1459,20 +1459,12 @@ Rendering 'corner-shape' When rendering elements with shaped corners, the element's path needs to be offset, based on [=border=], [=outline=], 'box-shadow', 'overflow-clip-margin' and more. -When rendering borders or outlines, the offset is aligned to the curve of the element's shape, -while when rendering 'box-shadow' or offsetting for 'overflow-clip-margin', the offset is aligned to the axis. - -
- Adjusting corner shapes -
Borders are aligned to the curve, shadows and clip are aligned to the axis.
-
- +When rendering borders or outlines, the offset is aligned to the curve of the element's shape. +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. An [=/element=] |element|'s outer contour is the [=border contour path=] given |element| and |element|'s [=border edge=]. -An [=/element=] |element|'s inner contour is the [=border contour path=] given |element| and |element|'s [=padding edge=]. +An [=/element=] |element|'s inner contour is the [=border contour path=] given |element| and |element|'s [=border width=]. An [=/element=]'s [=border=] is rendered in the area between its [=outer contour=] and its [=inner contour=]. @@ -1480,80 +1472,141 @@ An [=/element=]'s [=outline=] follows the [=outer contour=] with the [=used valu The precise way in which it is rendered is implementation-defined. An [=/element=]'s [=overflow=] area is shaped by its [=inner contour=]. -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'. - -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 border contour path given an [=edge=] |targetEdge| and an optional number |spread| (default 0): - 1. Let |outerLeft|, |outerTop|, |outerRight|, |outerBottom| be |element|'s [=unshaped edge|unshaped=] [=border edge=], outset by |spread|. - 1. Let |topLeftHorizontalRadius|, |topLeftVericalRadius|, |topRightHorizontalRadius|, |topRightVerticalRadius|, |bottomRightHorizontalRadius|, - |bottomRightVerticalRadius|, |bottomLeftHorizontalRadius|, and |bottomLeftVerticalRadius| be |element| [=border edge=]'s radii, - scaled by |element|'s [=opposite corner scale factor=] and [=outset-adjusted border radius|outset-adjusted=]. - 1. Let |topLeftShape|, |topRightShape|, |bottomRightShape|, and |bottomLeftShape| be |element|'s [=computed value|computed=] 'corner-*-shape' values. - 1. Let |targetLeft|, |targetTop|, |targetRight|, |targetBottom| [=unshaped edge|unshaped=] |targetEdge|. - 1. Let |path| be a new path [[SVG2]]. - 1. [=Add corner to path=] given |path|, - the [=rectangle=] (|outerRight| - |topRightHorizontalRadius|, |outerTop|, |topRightHorizontalRadius|, |topRightVerticalRadius|), |targetEdge|, - 0, |targetTop| - |outerTop|, |outerRight| - |targetRight|, and |topRightShape|. - 1. [=Add corner to path=] given |path|, - the [=rectangle=] (|outerRight| - |bottomRightHorizontalRadius|, |outerBottom| - |bottomRightVerticalRadius|, |bottomRightHorizontalRadius|, |bottomRightVerticalRadius|), |targetEdge|, - 1, |outerRight| - |targetRight|, |outerBottom| - |targetBottom|, and |bottomRightShape|. - 1. [=Add corner to path=] given |path|, - the [=rectangle=] (|outerLeft|, |outerBottom| - |bottomLeftVerticalRadius|, |bottomLeftHorizontalRadius|, |bottomLeftVerticalRadius|), |targetEdge|, - 2, |outerBottom| - |targetBottom|, |targetLeft| - |outerLeft|, and |bottomLeftShape|. - 1. [=Add corner to path=] given |path|, - the [=rectangle=] (|outerLeft|, |outerTop|, |topLeftHorizontalRadius|, |topLeftVericalRadius|), |targetEdge|, - 3, |targetLeft| - |outerLeft|, |targetTop| - |outerTop|, and |topLeftShape|. - 1. Return |path|. +An [=/element=]'s [=overflow clip edge=] is shaped by the [=border contour path=] given |element|, and an [=inset from a uniform outset=] given |element|'s [=used value|used=] 'overflow-clip-margin'. + +Each shadow of [=/element=]'s 'box shadow' is shaped by the [=border contour path=] given |element|, and an [=inset from a uniform outset=] given the shadow's [=used value|used=] 'box-shadow-spread'. + +An inset from a uniform outset given a number |outset| is an [=edge=] whose value is -|outset| in all directions. + +
Vector Math Helpers
+ +A two-dimensional vector is a pair of numbers (, y). +
+A point extended by vectors given a {{DOMPointReadOnly}} |p| and a [=/list=] of [=two-dimensional vector=]s |vectors|: + 1. Let |x| by |p|'s {{DOMPointReadOnly/x}}. + 1. Let |y| by |p|'s {{DOMPointReadOnly/y}}. + 1. [=list/For each=] |v| in |vectors|: + 1. Increment |x| by |v|[0]; + 1. Increment |y| by |v|[1]; + 1. Return a new {{DOMPointReadOnly}} whose {{DOMPointReadOnly/x}} is |x| and whose {{DOMPointReadOnly/y}} is |y|. +
+ +To scale a [=two-dimensional vector=] |v| by a number |factor|, return (|v|[0] ⋅ |factor|, |v|[1] ⋅ |factor|). + +
+To normalize a [=two-dimensional vector=] |v|: + 1. Let |l| be hypot(|v|[0], |v|[1]). + 1. If |l| is 0, return |v|. + 1. Return (|v|[0] / |l|, |v|[1] / |l|). +
-To add corner to path given a path |path|, a rectangle |cornerRect|, a rectangle |trimRect|, - and numbers |orientation|, |startThickness|, |endThickness|, |curvature|: - - 1. If |cornerRect| is empty, or if |curvature| is ∞: - 1. Let |innerQuad| be |trimRect|'s [=clockwise quad=] . - 1. Extend |path| by drawing a line to |innerQuad|[(|orienation| + 1) % 4]. - 1. Return. - - 1. Let |cornerQuad| be |cornerRect|'s [=clockwise quad=]. - 1. If |curvature| is -∞: - 1. Extend |path| by drawing a line from |cornerQuad|[0] to |cornerQuad|[3], trimmed by |trimRect|. - 1. Extend |path| by drawing a line from |cornerQuad|[3] to |cornerQuad|[2], trimmed by |trimRect|. - 1. Return. - - 1. Let |clampedNormalizedHalfCorner| be the [=normalized superellipse half corner=] given clamp(|curvature|, -1, 1). - 1. Let |equivalentQuadraticControlPointX| be |clampedNormalizedHalfCorner| * 2 - 0.5. - 1. Let |curveStartPoint| be the [=aligned corner point=] given |cornerQuad|[|orienation|], the vector (|equivalentQuadraticControlPointX|, 1 - |equivalentQuadraticControlPointX|), |startThickness|, and |orientation| + 1. - 1. Let |curveEndPoint| by the [=aligned corner point=] given |cornerQuad|[(|orientation| + 2) % 4], the vector (|equivalentQuadraticControlPointX| - 1, -|equivalentQuadraticControlPointX|), |endThickness|, and |orientation| + 3. - 1. Let |alignedCornerRect| be a [=rectangle=] that includes the points |curveStartPoint| and |curveEndPoint|. - 1. Let |projectionToCornerRect| be a [=transformation matrix=], - translated by (|alignedCornerRect|'s [=x coordinate=], |alignedCornerRect|'s [=y coordinate=]), - scaled by (|alignedCornerRect|'s [=width dimension=], |alignedCornerRect|'s [=height dimension=]), - translated by (0.5, 0.5), - rotated by 90deg * orientation, - and translated by (-0.5, -0.5). - - 1. Let |K| be 0.5abs(|curvature|). - 1. For each |T| between 0 and 1: - 1. Let |A| be |T||K|. - 1. Let |B| be 1 - (1 - |T|)|K|. - 1. Let |normalizedPoint| be (|A|, |B|) if |curvature| is positive, otherwise (|B|, |A|). - 1. Let |absolutePoint| be |normalizedPoint|, transformed by |projectionToCornerRect|. - 1. If |absolutePoint| is within |trimRect|, extend |path| through |absolutePoint|. - - Note: User agents may approximate this algorithm, for instance, by using concatenated Bezier curves, to balance between performance and rendering accuracy. - -To compute the aligned corner point given a point |originalPoint|, a two-component vector |offsetFromControlPoint|, a number |thickness|, and a number |orientation|: - 1. Let |length| be hypot(|offsetFromControlPoint|.x, |offsetFromControlPoint|.y). - 1. Rotate |offsetFromControlPoint| by 90deg * |orientation|, and scale by |thickness|. - 1. Translate |originalPoint| by |offsetFromControlPoint|.x / |length|, |offsetFromControlPoint|.y / |length|, and return the result. - -The clockwise quad given a [=rectangle=] |rect|, is a [=quadrilateral=] with the points - (|rect|'s [=x coordinate=], |rect|'s [=y coordinate=]), - (|rect|'s [=x coordinate=] + |rect|'s [=width dimension=], |rect|'s [=y coordinate=]), - (|rect|'s [=x coordinate=] + |rect|'s [=width dimension=], |rect|'s [=y coordinate=] + |rect|'s [=height dimension=]), - (|rect|'s [=x coordinate=], |rect|'s [=y coordinate=] + |rect|'s [=height dimension=]). +The perpendicular of a [=two-dimensional vector=] |v| is (-|v|[1], |v|[0]). + +The vector between two points, given {{DOMPoint}}s |a| and |b|, is (|b|'s {{DOMPointReadOnly/x}} - |a|'s {{DOMPointReadOnly/x}}, |b|'s {{DOMPointReadOnly/y}} - |a|'s {{DOMPointReadOnly/y}}). + +
Computing a contoured path
+ +This algorithm describes how to compute a path with shaped corners (a path that is either inset or outset from the original). +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. +The specific implementation details of this clipping operation are left to implementations. + +
+To compute an [=/element=] |element|'s border contour path given numbers |topInset|, |rightInset|, |bottomInset|, |leftInset|: + 1. Let |borderRect| be |element|'s [=border box=]. + 1. Let |unshapedTargetRect| be |borderRect|, inset by |topInset|, |rightInset|, |bottomInset|, |leftInset|. + + Note: If this is a shadow or 'overflow-clip-margin', the insets would have negative values and |unshapedTargetRect| would become an outset of |borderRect|. + + 1. Let |path| be a path that contains |unshapedTargetRect|. + + 1. Let |scaleFactor| be the [=opposite corner scale factor=] given |element|. + + 1. Let |adjustedRadius| be the following steps given a property |P|, and numbers |insetX| and |insetY|: + 1. Let |radius| be |element|'s [=used value=] of |P|. + 1. If |insetX| and |insetY| are zero, return |radius|. + 1. If |insetX| or |insetY| are positive, then return (|radius|'s [=width=] ⋅ |scaleFactor|, |radius|'s [=height=] ⋅ |scaleFactor|). + 1. Let |adjsutedRadiusInOutsetCoordinates| be the [=outset-adjusted border radius=] given |borderRect|'s size, |radius|, and (-|insetX|, -|insetY|). + 1. Return (|adjsutedRadiusInOutsetCoordinates|'s [=width=] - |insetX|, |adjsutedRadiusInOutsetCoordinates|'s [=height=] - |insetY|). + + 1. Let |adjustedTopRightRadius| be the |adjustedRadius| given 'border-top-right-radius', |insetRight|, and |insetTop|. + 1. Let |adjustedBottomRightRadius| be the |adjustedRadius| given 'border-bottom-right-radius', |insetRight|, and |insetBottom|. + 1. Let |adjustedBottomLeftRadius| be the |adjustedRadius| given 'border-bottom-left-radius', |insetLeft|, and |insetBottom|. + 1. Let |adjustedTopLeftRadius| be the |adjustedRadius| given 'border-top-left-radius', |insetLeft|, and |insetTop|. + + 1. Clip out from |path|, the [=border-aligned corner clip-out path=] given + |borderRect|'s right, |borderRect|'s top, + (-|adjustedTopRightRadius|[0], 0), (0, |adjustedTopRightRadius|[1]), + |element|'s [=computed value|computed=] 'corner-top-right-shape', + |topInset|, and |rightInset|. + + 1. Clip out from |path|, the [=border-aligned corner clip-out path=] given + |borderRect|'s right, |borderRect|'s bottom, + (0, -|adjustedBottomRightRadius|[1]), (-|adjustedTopRightRadius|[0], 0), + |element|'s [=computed value|computed=] 'corner-bottom-right-shape', + |rightInset|, and |bottomInset|. + + 1. Clip out from |path|, the [=border-aligned corner clip-out path=] given + |borderRect|'s left, |borderRect|'s bottom, + (|adjustedBottomLeftRadius|[0], 0), (0, -|adjustedBottomLeftRadius|[1]), + |element|'s [=computed value|computed=] 'corner-bottom-left-shape', + |bottomInset|, and |leftInset|. + + 1. Clip out from |path|, the [=border-aligned corner clip-out path=] given + |borderRect|'s left, |borderRect|'s top, + (0, |adjustedTopLeftRadius|[1]), (|adjustedTopLeftRadius|[0], 0), + |element|'s [=computed value|computed=] 'corner-top-left-shape', + |leftInset|, and |topInset|. + 1. Return |path|. + +To get the border-aligned corner clip-out path given a {{DOMPointReadOnly}} |originalCornerOuter|, a [=two-dimensional vector=] |vectorTowardsStart|, a [=two-dimensional vector=] |vectorTowardsEnd|, a [=superellipse parameter=] |curvature|, and numbers |startInset| and |endInset|: + 1. If |curvature| is ∞, then return an empty path. + 1. Let |clampedHalfCorner| be the [=normalized superellipse half corner=] given clamp(|curvature|, -1, 1). + 1. Let |originalCornerStart| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |vectorTowardsStart| ». + 1. Let |originalCornerEnd| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |vectorTowardsEnd| ». + 1. Let |originalCornerCenter| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |vectorTowardsStart|, |vectorTowardsEnd| ». + 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|. + 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|. + 1. Let |clipStart| be |originalCornerStart|, [=point extended by vectors|extended by=] « |extendStart| ». + 1. Let |clipEnd| be |originalCornerEnd|, [=point extended by vectors|extended by=] « |extendEnd| ». + 1. Let |clipOuter| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |extendStart|, |extendEnd| ». + 1. Let |vectorFromStartToControlPoint| be the the [=two-dimensional vector=] (2 ⋅ |clampedHalfCorner| - 0.5, 1.5 - 2 ⋅ |clampedHalfCorner|). + 1. Let |singlePixelVectorFromStartToControlPoint| be the |vectorFromStartToControlPoint|, [=two-dimensional vector/normalize|normalized=]. + 1. Let |strokeA|, |strokeB| be the [=two-dimensional vector/perpendicular=] of |singlePixelVectorFromStartToControlPoint|. + 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=] |startInset| ⋅ |strokeA|. + 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=] |startInset| ⋅ |strokeB|. + 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=] |endInset| ⋅ |strokeB|. + 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=] |endInset| ⋅ |strokeA|. + 1. Let |adjustedCornerStart| be |originalCornerStart|, [=point extended by vectors|extended by=] « |offset1|, |offset2| ». + 1. Let |adjustedCornerEnd| be |originalCornerEnd|, [=point extended by vectors|extended by=] « |offset3|, |offset4| ». + 1. Let |adjustedCornerCenter| be |originalCornerCenter|, [=point extended by vectors|extended by=] « |offset4|, |offset1| ». + 1. Let |adjustedCornerOuter| be |originalCornerOuter|, [=point extended by vectors|extended by=] « |offset2|, |offset3| ». + 1. Let |curveCenter| be |adjustedCornerOuter| if |curvature| is less than 0, |adjustedCornerCenter| otherwise. + 1. Let |mapPointToCorner| be the following steps: given numbers |x| and |y|: + 1. Let |v1| be [=vector between two points|the vector between=] |curveCenter| and |adjustedCornerEnd|, [=two-dimensional vector/scale|scaled by=] |x|. + 1. Let |v2| be [=vector between two points|the vector between=] |curveCenter| and |adjustedCornerStart|, [=two-dimensional vector/scale|scaled by=] |y|. + 1. Return the {{DOMPointReadOnly}} at (|x|, |y|), [=point extended by vectors|extended by=] « |v1|, |v2| ». + + 1. Let |controlPoint| be the result of calling |mapPointToCorner| given |unitVectorFromStartToControlPoint|'s {{DOMPointReadOnly/y}} and 1 - |unitVectorFromStartToControlPoint|'s {{DOMPointReadOnly/x}}. + 1. Let |axisAlignedCornerStart| be the intersection between the lines (|adjustedCornerStart|, |controlPoint|) and (|clipStart|, |clipOuter|). If the lines are parallel, let it be adjustedCornerStart. + 1. Let |axisAlignedCornerEnd| be the intersection between the lines (|adjustedCornerEnd|, |controlPoint|) and (|clipEnd|, |clipOuter|). If the lines are parallel, let it be adjustedCornerEnd. + + Note: |axisAlignedCornerStart| and |axisAlignedCornerEnd| act as "miters" when rendering an outset. + They are a straight extension of the curve's tangent, intersecting with the unshaped target rect. + + 1. Let |path| be a path, starting at |axisAlignedCornerStart|. + 1. If |curvature| is -∞: + 1. Extend |path| to |adjustedCornerStart|. + 1. Extend |path| to |adjustedCornerCenter|. + 1. Extend |path| to |adjustedCornerEnd|. + 1. Otherwise: + 1. Let |K| be 0.5-abs(|curvature|). + 1. For every |T| between 0 and 1, in an [=implementation-approximated=] manner, + extend |path| to the result of calling |mapPointToCorner| given |T||K| and (1 - |T|)|K|. + + 1. Extend |path| to |axisAlignedCornerEnd|. + 1. Extend |path| to |clipOuter|. + 1. Return |path|.
@@ -1624,7 +1677,7 @@ To compute the normalized superellipse half corner given a [=superell : Otherwise :: 1. Let |k| be 0.5abs(|s|). - 1. Let |convexHalfCorner| be 0.5|k|. + 1. Let |convexHalfCorner| be 0.51/|k|. 1. If |s| is less than 0, return 1 - |convexHalfCorner|. 1. Return |convexHalfCorner|.