Repository navigation
corner-shape: squircle renders Apple continuous, not CSS superellipse(2): make corner-shape spec-correct and add -ios-corner-shape: continuous #11452
Description
Summary
corner-shape: squircle (added in #11365) maps to kCACornerCurveContinuous on iOS. I wrote that mapping believing Apple's continuous corner and the CSS squircle are the same curve. They are not, and the difference is large. Proposal: make corner-shape follow the CSS Borders Level 4 definitions, and expose Apple's continuous corner through a separate iOS-only property, -ios-corner-shape. Target: 9.2.
What is wrong
Measured on the iOS simulator against a real CALayer with cornerCurve = .continuous (200×100 layer, r=20, 4× raster diff):
Shape at the same border-radius |
Max gap from Apple continuous | Pixels differing |
|---|---|---|
Circle (CSS round, kCACornerCurveCircular) |
0.019 r (~0.3 pt at r=16) | 756 |
CSS squircle = superellipse(2) |
0.187 r (~3 pt at r=16) | 4368 |
Apple's continuous corner is essentially CSS round with the curvature eased in over a 1.528665 r run-up (CALayer.cornerCurveExpansionFactor(.continuous)). The CSS squircle is a much squarer corner. No superellipse(K) at any radius gets closer to continuous than a plain circle does, so the two cannot be reconciled by picking a different K.
Consequences today:
- A web developer reading
corner-shape: squircleexpects the Chrome/Safari shape and gets something visually indistinguishable fromround. - Non-uniform radii, borders and shadows fall back to circular arcs (documented limitation of feat(core): corner-shape css property for continuous corners on iOS #11365). fix(ios): honor corner-shape: squircle for non-uniform border radii #11451 tries to close that gap and correctly draws the CSS
superellipse(2), which means that after it merges, uniform and non-uniform squircle views would disagree by ~0.19 r, worse than the circular fallback. CALayerCornerCurveonly hascircularandcontinuous. There is no native way to draw the CSS shape, so the two curves need different rendering strategies anyway.
Proposal
corner-shapefollows the spec.round=kCACornerCurveCircular(unchanged; it is exactlysuperellipse(1)).squircle=superellipse(2), drawn as aCAShapeLayermask path on every branch, including the uniform-radius fast path, since Core Animation cannot draw it. Each corner is two cubic Béziers (one per half-corner, mirrored across the diagonal): in a unit corner box from(0, 1)to the diagonal point(h, h)withh = 0.5^(1/4) = 0.840896, control points(0.38905, 1)and(h − 0.1678, h + 0.1678). Max deviation from the exact curve is 1.65e-4 r. This is how Blink renders it, andsuperellipse(K)can be added later on the same path by refitting the three constants per K.- Android can implement the same path with
Path.cubicTo.
-ios-corner-shape: continuous(iOS-only property, ignored elsewhere) maps tokCACornerCurveContinuouson the uniform fast path, so it stays free. For non-uniform radii, borders and shadows it draws Apple's curve, which is three cubic Béziers per corner. Withd1/d2the edge vectors from the vertex (length r each), every point isv + a·d1 + b·d2:This is byte-for-byte whatline to (1.528665, 0) cubic cp (1.08849, 0) cp (0.868407, 0) to (0.631494, 0.074911) cubic cp (0.372824, 0.169060) cp (0.169060, 0.372824) to (0.074911, 0.631494) cubic cp (0, 0.868407) cp (0, 1.08849) to (0, 1.528665)UIBezierPath(roundedRect:byRoundingCorners:cornerRadii:)emits and renders pixel-identical to the layer. The corner consumescornerCurveExpansionFactor(.continuous) × rof each edge, so the radius cap needs that factor. When the non-zero radii are equal,maskedCorners+cornerRadius+cornerCurverenders a subset of corners natively with no mask at all.- If both are set,
-ios-corner-shapewins on iOS (platform override), otherwisecorner-shapeapplies.
Breaking change
corner-shape: squircle was released recently and currently renders continuous. After this change it renders the (squarer) spec shape and goes through a mask. Anyone who adopted it for the iOS system look migrates to -ios-corner-shape: continuous. Doing this early keeps the blast radius small; the changelog should call it out explicitly.
Related
- feat(core): corner-shape css property for continuous corners on iOS #11365 added
corner-shapewith the continuous mapping. - fix(ios): honor corner-shape: squircle for non-uniform border radii #11451 implements the spec squircle for non-uniform radii and is deferred until this lands.
Activity