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
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: squircle expects the Chrome/Safari shape and gets something visually indistinguishable from round.
CALayerCornerCurve only has circular and continuous. There is no native way to draw the CSS shape, so the two curves need different rendering strategies anyway.
Proposal
corner-shape follows the spec.
round = kCACornerCurveCircular (unchanged; it is exactly superellipse(1)).
squircle = superellipse(2), drawn as a CAShapeLayer mask 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) with h = 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, and superellipse(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 to kCACornerCurveContinuous on 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. With d1/d2 the edge vectors from the vertex (length r each), every point is v + a·d1 + b·d2:
line 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)
This is byte-for-byte what UIBezierPath(roundedRect:byRoundingCorners:cornerRadii:) emits and renders pixel-identical to the layer. The corner consumes cornerCurveExpansionFactor(.continuous) × r of each edge, so the radius cap needs that factor. When the non-zero radii are equal, maskedCorners + cornerRadius + cornerCurve renders a subset of corners natively with no mask at all.
If both are set, -ios-corner-shape wins on iOS (platform override), otherwise corner-shape applies.
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.
Summary
corner-shape: squircle(added in #11365) maps tokCACornerCurveContinuouson iOS. I wrote that mapping believing Apple's continuous corner and the CSSsquircleare the same curve. They are not, and the difference is large. Proposal: makecorner-shapefollow 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
CALayerwithcornerCurve = .continuous(200×100 layer, r=20, 4× raster diff):border-radiusround,kCACornerCurveCircular)squircle=superellipse(2)Apple's continuous corner is essentially CSS
roundwith the curvature eased in over a1.528665 rrun-up (CALayer.cornerCurveExpansionFactor(.continuous)). The CSS squircle is a much squarer corner. Nosuperellipse(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:
corner-shape: squircleexpects the Chrome/Safari shape and gets something visually indistinguishable fromround.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.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: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.-ios-corner-shapewins on iOS (platform override), otherwisecorner-shapeapplies.Breaking change
corner-shape: squirclewas 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
corner-shapewith the continuous mapping.