react-native-smooth-clip-view 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +86 -21
  2. package/android/CMakeLists.txt +7 -0
  3. package/android/build.gradle +14 -2
  4. package/android/proguard-rules.pro +3 -3
  5. package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
  6. package/android/src/main/cpp/SmoothClipBindings.cpp +896 -6
  7. package/android/src/main/cpp/SmoothClipRegistry.cpp +1067 -46
  8. package/android/src/main/cpp/SmoothClipTrace.h +25 -0
  9. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +265 -0
  10. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +21 -0
  11. package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +283 -0
  12. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +385 -24
  13. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
  14. package/cpp/SmoothClipAnimationCurve.h +151 -122
  15. package/cpp/SmoothClipRegistry.h +102 -4
  16. package/cpp/SmoothClipSharedGeometry.h +146 -7
  17. package/cpp/SmoothClipVelocityTracker.h +13 -9
  18. package/ios/SmoothClipGeometry.h +184 -6
  19. package/ios/SmoothClipRegistry.mm +1033 -25
  20. package/ios/SmoothClipRegistrySnapshot.h +13 -0
  21. package/ios/SmoothClipTurboModule.cpp +857 -2
  22. package/ios/SmoothClipTurboModule.h +310 -0
  23. package/ios/SmoothClipView.mm +714 -183
  24. package/ios/SmoothClipViewRegistry.h +1 -0
  25. package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  26. package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  27. package/lib/module/NativeSmoothClipModule.js.map +1 -1
  28. package/lib/module/SmoothClipView.ios.js +22 -6
  29. package/lib/module/SmoothClipView.ios.js.map +1 -1
  30. package/lib/module/SmoothClipView.js +1 -46
  31. package/lib/module/SmoothClipView.js.map +1 -1
  32. package/lib/module/SmoothClipViewNativeComponent.ts +27 -1
  33. package/lib/module/capabilities.js +4 -0
  34. package/lib/module/capabilities.js.map +1 -0
  35. package/lib/module/capabilities.native.js +36 -0
  36. package/lib/module/capabilities.native.js.map +1 -0
  37. package/lib/module/capabilityTypes.js +11 -0
  38. package/lib/module/capabilityTypes.js.map +1 -0
  39. package/lib/module/driverState.js +0 -28
  40. package/lib/module/driverState.js.map +1 -1
  41. package/lib/module/drivers.js +1 -281
  42. package/lib/module/drivers.js.map +1 -1
  43. package/lib/module/drivers.native.js +340 -75
  44. package/lib/module/drivers.native.js.map +1 -1
  45. package/lib/module/geometry.js +147 -17
  46. package/lib/module/geometry.js.map +1 -1
  47. package/lib/module/groupDriverTypes.js +4 -0
  48. package/lib/module/groupDriverTypes.js.map +1 -0
  49. package/lib/module/groupDrivers.android.js +4 -0
  50. package/lib/module/groupDrivers.android.js.map +1 -0
  51. package/lib/module/groupDrivers.ios.js +4 -0
  52. package/lib/module/groupDrivers.ios.js.map +1 -0
  53. package/lib/module/groupDrivers.js +4 -0
  54. package/lib/module/groupDrivers.js.map +1 -0
  55. package/lib/module/groupDrivers.native.js +799 -0
  56. package/lib/module/groupDrivers.native.js.map +1 -0
  57. package/lib/module/index.js +3 -2
  58. package/lib/module/index.js.map +1 -1
  59. package/lib/module/presentationProtocol.js +15 -0
  60. package/lib/module/presentationProtocol.js.map +1 -0
  61. package/lib/typescript/src/NativeSmoothClipModule.d.ts +25 -0
  62. package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
  63. package/lib/typescript/src/SmoothClipView.d.ts +2 -10
  64. package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
  65. package/lib/typescript/src/SmoothClipView.ios.d.ts +133 -2
  66. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
  67. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +8 -0
  68. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  69. package/lib/typescript/src/capabilities.d.ts +3 -0
  70. package/lib/typescript/src/capabilities.d.ts.map +1 -0
  71. package/lib/typescript/src/capabilities.native.d.ts +3 -0
  72. package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
  73. package/lib/typescript/src/capabilityTypes.d.ts +10 -0
  74. package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
  75. package/lib/typescript/src/driverState.d.ts +1 -6
  76. package/lib/typescript/src/driverState.d.ts.map +1 -1
  77. package/lib/typescript/src/driverTypes.d.ts +39 -11
  78. package/lib/typescript/src/driverTypes.d.ts.map +1 -1
  79. package/lib/typescript/src/drivers.d.ts +2 -4
  80. package/lib/typescript/src/drivers.d.ts.map +1 -1
  81. package/lib/typescript/src/drivers.native.d.ts.map +1 -1
  82. package/lib/typescript/src/geometry.d.ts +36 -2
  83. package/lib/typescript/src/geometry.d.ts.map +1 -1
  84. package/lib/typescript/src/groupDriverTypes.d.ts +66 -0
  85. package/lib/typescript/src/groupDriverTypes.d.ts.map +1 -0
  86. package/lib/typescript/src/groupDrivers.android.d.ts +2 -0
  87. package/lib/typescript/src/groupDrivers.android.d.ts.map +1 -0
  88. package/lib/typescript/src/groupDrivers.d.ts +2 -0
  89. package/lib/typescript/src/groupDrivers.d.ts.map +1 -0
  90. package/lib/typescript/src/groupDrivers.ios.d.ts +2 -0
  91. package/lib/typescript/src/groupDrivers.ios.d.ts.map +1 -0
  92. package/lib/typescript/src/groupDrivers.native.d.ts +4 -0
  93. package/lib/typescript/src/groupDrivers.native.d.ts.map +1 -0
  94. package/lib/typescript/src/index.d.ts +6 -3
  95. package/lib/typescript/src/index.d.ts.map +1 -1
  96. package/lib/typescript/src/presentationProtocol.d.ts +4 -0
  97. package/lib/typescript/src/presentationProtocol.d.ts.map +1 -0
  98. package/package.json +10 -11
  99. package/src/NativeSmoothClipModule.ts +275 -0
  100. package/src/SmoothClipView.ios.tsx +30 -11
  101. package/src/SmoothClipView.tsx +2 -76
  102. package/src/SmoothClipViewNativeComponent.ts +27 -1
  103. package/src/capabilities.native.ts +69 -0
  104. package/src/capabilities.ts +2 -0
  105. package/src/capabilityTypes.ts +17 -0
  106. package/src/driverState.ts +1 -41
  107. package/src/driverTypes.ts +56 -11
  108. package/src/drivers.native.ts +921 -151
  109. package/src/drivers.ts +2 -423
  110. package/src/geometry.ts +231 -27
  111. package/src/groupDriverTypes.ts +117 -0
  112. package/src/groupDrivers.android.ts +1 -0
  113. package/src/groupDrivers.ios.ts +1 -0
  114. package/src/groupDrivers.native.ts +1189 -0
  115. package/src/groupDrivers.ts +1 -0
  116. package/src/index.ts +30 -2
  117. package/src/presentationProtocol.ts +28 -0
  118. package/lib/module/SmoothClipView.web.js +0 -54
  119. package/lib/module/SmoothClipView.web.js.map +0 -1
  120. package/lib/module/index.web.js +0 -8
  121. package/lib/module/index.web.js.map +0 -1
  122. package/lib/module/webClipPath.js +0 -25
  123. package/lib/module/webClipPath.js.map +0 -1
  124. package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
  125. package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
  126. package/lib/typescript/src/index.web.d.ts +0 -9
  127. package/lib/typescript/src/index.web.d.ts.map +0 -1
  128. package/lib/typescript/src/webClipPath.d.ts +0 -7
  129. package/lib/typescript/src/webClipPath.d.ts.map +0 -1
  130. package/src/SmoothClipView.web.tsx +0 -66
  131. package/src/index.web.ts +0 -23
  132. package/src/webClipPath.ts +0 -30
@@ -1,5 +1,7 @@
1
1
  #pragma once
2
2
 
3
+ #include "SmoothClipRegistry.h"
4
+
3
5
  #include <algorithm>
4
6
  #include <cmath>
5
7
 
@@ -11,23 +13,110 @@ struct NormalizedClip {
11
13
  double right = 0;
12
14
  double bottom = 0;
13
15
  double radius = 0;
16
+ double topLeftRadius = 0;
17
+ double topRightRadius = 0;
18
+ double bottomRightRadius = 0;
19
+ double bottomLeftRadius = 0;
20
+ ClipCurve curve = ClipCurve::Circular;
14
21
  };
15
22
 
16
- // Shared clip normalization emitting edges (Android's Outline consumes
17
- // edges). Mirrors ios/SmoothClipGeometry.h (SmoothClipNormalizeGeometry) and
18
- // ClipGeometryNormalizer.kt (normalizeClipGeometryPx); a change here must
19
- // land in all three.
20
- inline bool SmoothClipNormalize(
23
+ inline double SmoothClipResolvedRadius(double overrideValue, double radius) {
24
+ return std::isfinite(overrideValue) ? overrideValue : radius;
25
+ }
26
+
27
+ /**
28
+ * Returns true only when host normalization is the identity for this geometry.
29
+ *
30
+ * Timing/keyframe renderers interpolate already-normalized native values on
31
+ * iOS, while Android interpolates presentation scalars and normalizes each
32
+ * delivered frame. Those operations commute exactly when every anchor stays
33
+ * inside one host and the CSS corner-overlap rule never activates. Because
34
+ * each condition below is linear, it also holds for every convex interpolation
35
+ * between accepted anchors.
36
+ *
37
+ * Static clipping deliberately does not use this gate: out-of-bounds geometry
38
+ * remains valid and is still clipped by SmoothClipNormalizeV2.
39
+ */
40
+ inline bool SmoothClipGeometryNormalizationIsIdentity(
41
+ const Geometry &geometry,
42
+ double hostWidth,
43
+ double hostHeight) {
44
+ const double topLeft = SmoothClipResolvedRadius(
45
+ geometry.topLeftRadius, geometry.radius);
46
+ const double topRight = SmoothClipResolvedRadius(
47
+ geometry.topRightRadius, geometry.radius);
48
+ const double bottomRight = SmoothClipResolvedRadius(
49
+ geometry.bottomRightRadius, geometry.radius);
50
+ const double bottomLeft = SmoothClipResolvedRadius(
51
+ geometry.bottomLeftRadius, geometry.radius);
52
+ const double right = geometry.x + geometry.width;
53
+ const double bottom = geometry.y + geometry.height;
54
+ const double topRadii = topLeft + topRight;
55
+ const double bottomRadii = bottomLeft + bottomRight;
56
+ const double leftRadii = topLeft + bottomLeft;
57
+ const double rightRadii = topRight + bottomRight;
58
+ const double values[] = {
59
+ geometry.x,
60
+ geometry.y,
61
+ geometry.width,
62
+ geometry.height,
63
+ geometry.radius,
64
+ topLeft,
65
+ topRight,
66
+ bottomRight,
67
+ bottomLeft,
68
+ right,
69
+ bottom,
70
+ topRadii,
71
+ bottomRadii,
72
+ leftRadii,
73
+ rightRadii,
74
+ hostWidth,
75
+ hostHeight,
76
+ };
77
+ for (const double value : values) {
78
+ if (!std::isfinite(value)) return false;
79
+ }
80
+ return hostWidth >= 0 && hostHeight >= 0 &&
81
+ geometry.x >= 0 && geometry.y >= 0 &&
82
+ geometry.width >= 0 && geometry.height >= 0 &&
83
+ right <= hostWidth && bottom <= hostHeight &&
84
+ topLeft >= 0 && topRight >= 0 &&
85
+ bottomRight >= 0 && bottomLeft >= 0 &&
86
+ topRadii <= geometry.width &&
87
+ bottomRadii <= geometry.width &&
88
+ leftRadii <= geometry.height &&
89
+ rightRadii <= geometry.height;
90
+ }
91
+
92
+ inline bool SmoothClipNormalizeV2(
21
93
  double x,
22
94
  double y,
23
95
  double width,
24
96
  double height,
25
97
  double radius,
98
+ double topLeftRadius,
99
+ double topRightRadius,
100
+ double bottomRightRadius,
101
+ double bottomLeftRadius,
102
+ ClipCurve curve,
26
103
  double hostWidth,
27
104
  double hostHeight,
28
105
  NormalizedClip &out) {
106
+ const double resolvedTopLeft =
107
+ SmoothClipResolvedRadius(topLeftRadius, radius);
108
+ const double resolvedTopRight =
109
+ SmoothClipResolvedRadius(topRightRadius, radius);
110
+ const double resolvedBottomRight =
111
+ SmoothClipResolvedRadius(bottomRightRadius, radius);
112
+ const double resolvedBottomLeft =
113
+ SmoothClipResolvedRadius(bottomLeftRadius, radius);
29
114
  if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) ||
30
115
  !std::isfinite(height) || !std::isfinite(radius) ||
116
+ !std::isfinite(resolvedTopLeft) ||
117
+ !std::isfinite(resolvedTopRight) ||
118
+ !std::isfinite(resolvedBottomRight) ||
119
+ !std::isfinite(resolvedBottomLeft) ||
31
120
  !std::isfinite(hostWidth) || !std::isfinite(hostHeight)) {
32
121
  return false;
33
122
  }
@@ -42,9 +131,59 @@ inline bool SmoothClipNormalize(
42
131
  out.bottom = std::min(boundedHostHeight, std::max(0.0, requestedBottom));
43
132
  const double visibleWidth = std::max(0.0, out.right - out.left);
44
133
  const double visibleHeight = std::max(0.0, out.bottom - out.top);
45
- out.radius = std::min(
46
- std::max(0.0, radius), std::min(visibleWidth, visibleHeight) / 2.0);
134
+
135
+ double topLeft = std::max(0.0, resolvedTopLeft);
136
+ double topRight = std::max(0.0, resolvedTopRight);
137
+ double bottomRight = std::max(0.0, resolvedBottomRight);
138
+ double bottomLeft = std::max(0.0, resolvedBottomLeft);
139
+ double scale = 1.0;
140
+ const auto includeLimit = [&scale](double available, double requested) {
141
+ if (requested > 0) scale = std::min(scale, available / requested);
142
+ };
143
+ includeLimit(visibleWidth, topLeft + topRight);
144
+ includeLimit(visibleWidth, bottomLeft + bottomRight);
145
+ includeLimit(visibleHeight, topLeft + bottomLeft);
146
+ includeLimit(visibleHeight, topRight + bottomRight);
147
+ scale = std::max(0.0, std::min(1.0, scale));
148
+ out.topLeftRadius = topLeft * scale;
149
+ out.topRightRadius = topRight * scale;
150
+ out.bottomRightRadius = bottomRight * scale;
151
+ out.bottomLeftRadius = bottomLeft * scale;
152
+ const bool uniform = out.topLeftRadius == out.topRightRadius &&
153
+ out.topLeftRadius == out.bottomRightRadius &&
154
+ out.topLeftRadius == out.bottomLeftRadius;
155
+ out.radius = uniform ? out.topLeftRadius : 0.0;
156
+ out.curve = curve;
47
157
  return true;
48
158
  }
49
159
 
160
+ // Shared clip normalization emitting edges (Android's Outline consumes
161
+ // edges). Mirrors ios/SmoothClipGeometry.h (SmoothClipNormalizeGeometry) and
162
+ // ClipGeometryNormalizer.kt (normalizeClipGeometryPx); a change here must
163
+ // land in all three.
164
+ inline bool SmoothClipNormalize(
165
+ double x,
166
+ double y,
167
+ double width,
168
+ double height,
169
+ double radius,
170
+ double hostWidth,
171
+ double hostHeight,
172
+ NormalizedClip &out) {
173
+ return SmoothClipNormalizeV2(
174
+ x,
175
+ y,
176
+ width,
177
+ height,
178
+ radius,
179
+ radius,
180
+ radius,
181
+ radius,
182
+ radius,
183
+ ClipCurve::Circular,
184
+ hostWidth,
185
+ hostHeight,
186
+ out);
187
+ }
188
+
50
189
  } // namespace smoothclip
@@ -6,8 +6,9 @@
6
6
  namespace smoothclip {
7
7
 
8
8
  // The two most recent interactive samples used by `initialVelocity: 'inherit'`.
9
- // Channels are the seven presentation scalars (x, y, width, height, radius,
10
- // contentTranslateX, contentTranslateY) but the tracker is value-agnostic:
9
+ // Channels are the eleven continuous V2 presentation scalars. Scale is stored
10
+ // so histories remain coherent but excluded from velocity projection; each of
11
+ // the four radius channels contributes one quarter of the legacy radius weight.
11
12
  // iOS records normalized (host-clamped) geometry, Android raw DIP — the
12
13
  // projection only needs both samples and the target in the same space.
13
14
  // Shared by ios/SmoothClipView.mm and android/.../SmoothClipRegistry.cpp; a
@@ -15,8 +16,8 @@ namespace smoothclip {
15
16
  struct VelocitySampleHistory {
16
17
  bool hasPrevious = false;
17
18
  bool hasLatest = false;
18
- std::array<double, 7> previous{};
19
- std::array<double, 7> latest{};
19
+ std::array<double, 11> previous{};
20
+ std::array<double, 11> latest{};
20
21
  double previousTimeS = 0;
21
22
  double latestTimeS = 0;
22
23
  };
@@ -40,7 +41,7 @@ inline constexpr double kVelocityFullCreditS = 1.0 / 60.0;
40
41
 
41
42
  inline void recordVelocitySample(
42
43
  VelocitySampleHistory &history,
43
- const std::array<double, 7> &channels,
44
+ const std::array<double, 11> &channels,
44
45
  double nowS) {
45
46
  if (history.hasLatest && channels == history.latest) {
46
47
  // Identical re-record (a `from` seed equal to the last interactive
@@ -97,7 +98,7 @@ inline double velocityStalenessCredit(double holdS) {
97
98
  // Android seeds each integrator channel with scalar·displacement.
98
99
  inline double inheritedVelocity(
99
100
  const VelocitySampleHistory &history,
100
- const std::array<double, 7> &target,
101
+ const std::array<double, 11> &target,
101
102
  double nowS) {
102
103
  if (!history.hasPrevious || !history.hasLatest) return 0;
103
104
  const double elapsed = history.latestTimeS - history.previousTimeS;
@@ -107,11 +108,14 @@ inline double inheritedVelocity(
107
108
  }
108
109
  double numerator = 0;
109
110
  double denominator = 0;
110
- for (int index = 0; index < 7; index += 1) {
111
+ for (int index = 0; index < 11; index += 1) {
112
+ const double weight = index >= 4 && index <= 7
113
+ ? 0.25
114
+ : (index == 10 ? 0.0 : 1.0);
111
115
  const double sampleDelta = history.latest[index] - history.previous[index];
112
116
  const double destinationDelta = target[index] - history.latest[index];
113
- numerator += sampleDelta * destinationDelta;
114
- denominator += destinationDelta * destinationDelta;
117
+ numerator += weight * sampleDelta * destinationDelta;
118
+ denominator += weight * destinationDelta * destinationDelta;
115
119
  }
116
120
  if (denominator <= 1e-12) return 0;
117
121
  const double result = numerator / elapsed / denominator * credit;
@@ -2,23 +2,77 @@
2
2
 
3
3
  #include <math.h>
4
4
 
5
+ typedef struct {
6
+ CGFloat topLeft;
7
+ CGFloat topRight;
8
+ CGFloat bottomRight;
9
+ CGFloat bottomLeft;
10
+ } SmoothClipCornerRadii;
11
+
12
+ typedef NS_ENUM(NSInteger, SmoothClipCornerCurve) {
13
+ SmoothClipCornerCurveCircular = 0,
14
+ SmoothClipCornerCurveContinuous = 1,
15
+ };
16
+
5
17
  typedef struct {
6
18
  CGRect rect;
19
+ // Retained for the V1 uniform-radius contract. V2 callers should consume
20
+ // `radii`; this field is zero when the normalized radii are unequal.
7
21
  CGFloat radius;
22
+ SmoothClipCornerRadii radii;
23
+ SmoothClipCornerCurve curve;
8
24
  } SmoothNormalizedClipGeometry;
9
25
 
10
- /** Mirrors the Android and JavaScript geometry contract without allocating. */
11
- NS_INLINE BOOL SmoothClipNormalizeGeometry(
26
+ NS_INLINE BOOL SmoothClipCornerRadiiEqual(
27
+ SmoothClipCornerRadii first,
28
+ SmoothClipCornerRadii second) {
29
+ return first.topLeft == second.topLeft &&
30
+ first.topRight == second.topRight &&
31
+ first.bottomRight == second.bottomRight &&
32
+ first.bottomLeft == second.bottomLeft;
33
+ }
34
+
35
+ NS_INLINE BOOL SmoothClipCornerRadiiAreUniform(
36
+ SmoothClipCornerRadii radii) {
37
+ return radii.topLeft == radii.topRight &&
38
+ radii.topLeft == radii.bottomRight &&
39
+ radii.topLeft == radii.bottomLeft;
40
+ }
41
+
42
+ NS_INLINE CGFloat SmoothClipSafeOverlapFactor(
43
+ CGFloat extent,
44
+ CGFloat firstRadius,
45
+ CGFloat secondRadius) {
46
+ const CGFloat sum = firstRadius + secondRadius;
47
+ return sum > 0 ? extent / sum : 1;
48
+ }
49
+
50
+ /**
51
+ * Normalizes the V2 rectangle and four radii without allocating.
52
+ *
53
+ * The proportional overlap rule matches CSS border radii and the shared
54
+ * Android/C++ contract: all corners use one scale factor, preserving their
55
+ * relative shape while no opposing pair exceeds its edge.
56
+ */
57
+ NS_INLINE BOOL SmoothClipNormalizeGeometryV2(
12
58
  CGFloat x,
13
59
  CGFloat y,
14
60
  CGFloat width,
15
61
  CGFloat height,
16
- CGFloat radius,
62
+ CGFloat topLeftRadius,
63
+ CGFloat topRightRadius,
64
+ CGFloat bottomRightRadius,
65
+ CGFloat bottomLeftRadius,
66
+ NSInteger curveCode,
17
67
  CGSize hostSize,
18
68
  SmoothNormalizedClipGeometry *result) {
19
69
  if (!isfinite(x) || !isfinite(y) || !isfinite(width) ||
20
- !isfinite(height) || !isfinite(radius) ||
21
- !isfinite(hostSize.width) || !isfinite(hostSize.height)) {
70
+ !isfinite(height) || !isfinite(topLeftRadius) ||
71
+ !isfinite(topRightRadius) || !isfinite(bottomRightRadius) ||
72
+ !isfinite(bottomLeftRadius) || !isfinite(hostSize.width) ||
73
+ !isfinite(hostSize.height) ||
74
+ (curveCode != SmoothClipCornerCurveCircular &&
75
+ curveCode != SmoothClipCornerCurveContinuous)) {
22
76
  return NO;
23
77
  }
24
78
 
@@ -35,7 +89,131 @@ NS_INLINE BOOL SmoothClipNormalizeGeometry(
35
89
  const CGFloat visibleWidth = MAX(0, right - left);
36
90
  const CGFloat visibleHeight = MAX(0, bottom - top);
37
91
 
92
+ SmoothClipCornerRadii radii = {
93
+ MAX(0, topLeftRadius),
94
+ MAX(0, topRightRadius),
95
+ MAX(0, bottomRightRadius),
96
+ MAX(0, bottomLeftRadius),
97
+ };
98
+ const CGFloat overlapScale = MIN(
99
+ 1,
100
+ MIN(
101
+ MIN(
102
+ SmoothClipSafeOverlapFactor(
103
+ visibleWidth, radii.topLeft, radii.topRight),
104
+ SmoothClipSafeOverlapFactor(
105
+ visibleWidth, radii.bottomLeft, radii.bottomRight)),
106
+ MIN(
107
+ SmoothClipSafeOverlapFactor(
108
+ visibleHeight, radii.topLeft, radii.bottomLeft),
109
+ SmoothClipSafeOverlapFactor(
110
+ visibleHeight, radii.topRight, radii.bottomRight))));
111
+ radii.topLeft *= overlapScale;
112
+ radii.topRight *= overlapScale;
113
+ radii.bottomRight *= overlapScale;
114
+ radii.bottomLeft *= overlapScale;
115
+
38
116
  result->rect = CGRectMake(left, top, visibleWidth, visibleHeight);
39
- result->radius = MIN(MAX(0, radius), MIN(visibleWidth, visibleHeight) / 2.0);
117
+ result->radii = radii;
118
+ result->radius = SmoothClipCornerRadiiAreUniform(radii)
119
+ ? radii.topLeft
120
+ : 0;
121
+ result->curve = (SmoothClipCornerCurve)curveCode;
40
122
  return YES;
41
123
  }
124
+
125
+ /** Mirrors the Android and JavaScript geometry contract without allocating. */
126
+ NS_INLINE BOOL SmoothClipNormalizeGeometry(
127
+ CGFloat x,
128
+ CGFloat y,
129
+ CGFloat width,
130
+ CGFloat height,
131
+ CGFloat radius,
132
+ CGSize hostSize,
133
+ SmoothNormalizedClipGeometry *result) {
134
+ return SmoothClipNormalizeGeometryV2(
135
+ x,
136
+ y,
137
+ width,
138
+ height,
139
+ radius,
140
+ radius,
141
+ radius,
142
+ radius,
143
+ SmoothClipCornerCurveCircular,
144
+ hostSize,
145
+ result);
146
+ }
147
+
148
+ /**
149
+ * Creates the stable nine-segment rounded-rectangle topology used by the
150
+ * unequal-corner CAShapeLayer mask. Every corner is one cubic, including a
151
+ * zero-radius corner, so Core Animation can interpolate paths without a
152
+ * topology change.
153
+ */
154
+ NS_INLINE CGPathRef SmoothClipCreateRoundedRectPath(
155
+ CGRect rect,
156
+ SmoothClipCornerRadii radii,
157
+ SmoothClipCornerCurve curve) CF_RETURNS_RETAINED {
158
+ const CGFloat minX = CGRectGetMinX(rect);
159
+ const CGFloat minY = CGRectGetMinY(rect);
160
+ const CGFloat maxX = CGRectGetMaxX(rect);
161
+ const CGFloat maxY = CGRectGetMaxY(rect);
162
+ // Circular is the exact cubic-circle coefficient. The portable continuous
163
+ // fallback uses kappa=1 so each cubic joins its straight edges with zero
164
+ // curvature, matching the portable Android path. Hit testing derives
165
+ // from this rendered path rather than maintaining a second curve model.
166
+ // Uniform
167
+ // continuous shapes use CALayer.cornerCurve for Apple's platform-native
168
+ // compositor shape and are intentionally not pixel-identical cross-platform.
169
+ const CGFloat controlFactor =
170
+ curve == SmoothClipCornerCurveContinuous
171
+ ? 1.0
172
+ : 0.5522847498307936;
173
+
174
+ CGMutablePathRef path = CGPathCreateMutable();
175
+ CGPathMoveToPoint(path, NULL, minX + radii.topLeft, minY);
176
+ CGPathAddLineToPoint(path, NULL, maxX - radii.topRight, minY);
177
+ CGPathAddCurveToPoint(
178
+ path,
179
+ NULL,
180
+ maxX - radii.topRight + radii.topRight * controlFactor,
181
+ minY,
182
+ maxX,
183
+ minY + radii.topRight - radii.topRight * controlFactor,
184
+ maxX,
185
+ minY + radii.topRight);
186
+ CGPathAddLineToPoint(path, NULL, maxX, maxY - radii.bottomRight);
187
+ CGPathAddCurveToPoint(
188
+ path,
189
+ NULL,
190
+ maxX,
191
+ maxY - radii.bottomRight + radii.bottomRight * controlFactor,
192
+ maxX - radii.bottomRight +
193
+ radii.bottomRight * controlFactor,
194
+ maxY,
195
+ maxX - radii.bottomRight,
196
+ maxY);
197
+ CGPathAddLineToPoint(path, NULL, minX + radii.bottomLeft, maxY);
198
+ CGPathAddCurveToPoint(
199
+ path,
200
+ NULL,
201
+ minX + radii.bottomLeft - radii.bottomLeft * controlFactor,
202
+ maxY,
203
+ minX,
204
+ maxY - radii.bottomLeft + radii.bottomLeft * controlFactor,
205
+ minX,
206
+ maxY - radii.bottomLeft);
207
+ CGPathAddLineToPoint(path, NULL, minX, minY + radii.topLeft);
208
+ CGPathAddCurveToPoint(
209
+ path,
210
+ NULL,
211
+ minX,
212
+ minY + radii.topLeft - radii.topLeft * controlFactor,
213
+ minX + radii.topLeft - radii.topLeft * controlFactor,
214
+ minY,
215
+ minX + radii.topLeft,
216
+ minY);
217
+ CGPathCloseSubpath(path);
218
+ return path;
219
+ }