@@ -21,6 +21,7 @@ import androidx.compose.ui.geometry.Rect
2121import androidx.compose.ui.geometry.RoundRect
2222import org.jetbrains.skia.Matrix33
2323import org.jetbrains.skia.PathDirection
24+ import org.jetbrains.skia.PathBuilder
2425import org.jetbrains.skia.PathFillMode
2526import org.jetbrains.skia.PathOp
2627
@@ -49,6 +50,17 @@ internal class SkiaBackedPath(
4950) : Path {
5051 var internalPath = internalPath
5152 private set
53+ private var pathBuilder = PathBuilder (internalPath)
54+
55+ private inline fun mutatePath (block : PathBuilder .() -> Unit ) {
56+ pathBuilder.apply (block)
57+ internalPath = pathBuilder.snapshot()
58+ }
59+
60+ private fun replacePath (path : org.jetbrains.skia.Path ) {
61+ internalPath = path
62+ pathBuilder = PathBuilder (path)
63+ }
5264
5365 override var fillType: PathFillType
5466 get() {
@@ -60,53 +72,63 @@ internal class SkiaBackedPath(
6072 }
6173
6274 set(value) {
63- internalPath.fillMode =
75+ pathBuilder.setFillType(
6476 if (value == PathFillType .EvenOdd ) {
6577 PathFillMode .EVEN_ODD
6678 } else {
6779 PathFillMode .WINDING
6880 }
81+ )
82+ internalPath = pathBuilder.snapshot()
6983 }
7084
71- override fun moveTo (x : Float , y : Float ) {
72- internalPath. moveTo(x, y)
85+ override fun moveTo (x : Float , y : Float ) = mutatePath {
86+ moveTo(x, y)
7387 }
7488
75- override fun relativeMoveTo (dx : Float , dy : Float ) {
76- internalPath. rMoveTo(dx, dy)
89+ override fun relativeMoveTo (dx : Float , dy : Float ) = mutatePath {
90+ rMoveTo(dx, dy)
7791 }
7892
79- override fun lineTo (x : Float , y : Float ) {
80- internalPath. lineTo(x, y)
93+ override fun lineTo (x : Float , y : Float ) = mutatePath {
94+ lineTo(x, y)
8195 }
8296
83- override fun relativeLineTo (dx : Float , dy : Float ) {
84- internalPath. rLineTo(dx, dy)
97+ override fun relativeLineTo (dx : Float , dy : Float ) = mutatePath {
98+ rLineTo(dx, dy)
8599 }
86100
87- override fun quadraticBezierTo (x1 : Float , y1 : Float , x2 : Float , y2 : Float ) {
88- internalPath.quadTo(x1, y1, x2, y2)
89- }
90-
91- override fun quadraticTo (x1 : Float , y1 : Float , x2 : Float , y2 : Float ) {
92- internalPath.quadTo(x1, y1, x2, y2)
93- }
94-
95- override fun relativeQuadraticBezierTo (dx1 : Float , dy1 : Float , dx2 : Float , dy2 : Float ) {
96- internalPath.rQuadTo(dx1, dy1, dx2, dy2)
97- }
101+ @Deprecated(
102+ " Use quadraticTo() for consistency with cubicTo()" ,
103+ replaceWith = ReplaceWith (" quadraticTo(x1, y1, x2, y2)" ),
104+ level = DeprecationLevel .WARNING ,
105+ )
106+ override fun quadraticBezierTo (x1 : Float , y1 : Float , x2 : Float , y2 : Float ) =
107+ mutatePath { quadTo(x1, y1, x2, y2) }
108+
109+ override fun quadraticTo (x1 : Float , y1 : Float , x2 : Float , y2 : Float ) =
110+ mutatePath { quadTo(x1, y1, x2, y2) }
111+
112+ @Deprecated(
113+ " Use relativeQuadraticTo() for consistency with relativeCubicTo()" ,
114+ replaceWith = ReplaceWith (" relativeQuadraticTo(dx1, dy1, dx2, dy2)" ),
115+ level = DeprecationLevel .WARNING ,
116+ )
117+ override fun relativeQuadraticBezierTo (dx1 : Float , dy1 : Float , dx2 : Float , dy2 : Float ) =
118+ mutatePath { rQuadTo(dx1, dy1, dx2, dy2) }
98119
99120 override fun relativeQuadraticTo (dx1 : Float , dy1 : Float , dx2 : Float , dy2 : Float ) {
100- internalPath. rQuadTo(dx1, dy1, dx2, dy2)
121+ mutatePath { rQuadTo(dx1, dy1, dx2, dy2) }
101122 }
102123
103- override fun cubicTo (x1 : Float , y1 : Float , x2 : Float , y2 : Float , x3 : Float , y3 : Float ) {
104- internalPath.cubicTo(
105- x1, y1,
106- x2, y2,
107- x3, y3
108- )
109- }
124+ override fun cubicTo (x1 : Float , y1 : Float , x2 : Float , y2 : Float , x3 : Float , y3 : Float ) =
125+ mutatePath {
126+ cubicTo(
127+ x1, y1,
128+ x2, y2,
129+ x3, y3
130+ )
131+ }
110132
111133 override fun relativeCubicTo (
112134 dx1 : Float ,
@@ -115,8 +137,8 @@ internal class SkiaBackedPath(
115137 dy2 : Float ,
116138 dx3 : Float ,
117139 dy3 : Float
118- ) {
119- internalPath. rCubicTo(
140+ ) = mutatePath {
141+ rCubicTo(
120142 dx1, dy1,
121143 dx2, dy2,
122144 dx3, dy3
@@ -128,8 +150,8 @@ internal class SkiaBackedPath(
128150 startAngleDegrees : Float ,
129151 sweepAngleDegrees : Float ,
130152 forceMoveTo : Boolean
131- ) {
132- internalPath. arcTo(
153+ ) = mutatePath {
154+ arcTo(
133155 rect.left,
134156 rect.top,
135157 rect.right,
@@ -140,8 +162,13 @@ internal class SkiaBackedPath(
140162 )
141163 }
142164
143- override fun addRect (rect : Rect ) {
144- internalPath.addRect(
165+ @Deprecated(
166+ " Prefer usage of addRect() with a winding direction" ,
167+ replaceWith = ReplaceWith (" addRect(rect)" ),
168+ level = DeprecationLevel .HIDDEN ,
169+ )
170+ override fun addRect (rect : Rect ) = mutatePath {
171+ addRect(
145172 rect.left,
146173 rect.top,
147174 rect.right,
@@ -150,8 +177,8 @@ internal class SkiaBackedPath(
150177 )
151178 }
152179
153- override fun addRect (rect : Rect , direction : Path .Direction ) {
154- internalPath. addRect(
180+ override fun addRect (rect : Rect , direction : Path .Direction ) = mutatePath {
181+ addRect(
155182 rect.left,
156183 rect.top,
157184 rect.right,
@@ -160,8 +187,8 @@ internal class SkiaBackedPath(
160187 )
161188 }
162189
163- override fun addOval (oval : Rect ) {
164- internalPath. addOval(
190+ override fun addOval (oval : Rect ) = mutatePath {
191+ addOval(
165192 oval.left,
166193 oval.top,
167194 oval.right,
@@ -170,8 +197,8 @@ internal class SkiaBackedPath(
170197 )
171198 }
172199
173- override fun addOval (oval : Rect , direction : Path .Direction ) {
174- internalPath. addOval(
200+ override fun addOval (oval : Rect , direction : Path .Direction ) = mutatePath {
201+ addOval(
175202 oval.left,
176203 oval.top,
177204 oval.right,
@@ -180,8 +207,8 @@ internal class SkiaBackedPath(
180207 )
181208 }
182209
183- override fun addRoundRect (roundRect : RoundRect ) {
184- internalPath. addRRect(
210+ override fun addRoundRect (roundRect : RoundRect ) = mutatePath {
211+ addRRect(
185212 roundRect.left,
186213 roundRect.top,
187214 roundRect.right,
@@ -200,8 +227,8 @@ internal class SkiaBackedPath(
200227 )
201228 }
202229
203- override fun addRoundRect (roundRect : RoundRect , direction : Path .Direction ) {
204- internalPath. addRRect(
230+ override fun addRoundRect (roundRect : RoundRect , direction : Path .Direction ) = mutatePath {
231+ addRRect(
205232 roundRect.left,
206233 roundRect.top,
207234 roundRect.right,
@@ -224,42 +251,41 @@ internal class SkiaBackedPath(
224251 addArc(oval, degrees(startAngleRadians), degrees(sweepAngleRadians))
225252 }
226253
227- override fun addArc (oval : Rect , startAngleDegrees : Float , sweepAngleDegrees : Float ) {
228- internalPath. addArc(
254+ override fun addArc (oval : Rect , startAngleDegrees : Float , sweepAngleDegrees : Float ) = mutatePath {
255+ addArc(
229256 oval.left,
230257 oval.top,
231258 oval.right,
232259 oval.bottom,
233- startAngleDegrees, sweepAngleDegrees
260+ startAngleDegrees,
261+ sweepAngleDegrees
234262 )
235263 }
236264
237- override fun addPath (path : Path , offset : Offset ) {
238- internalPath.addPath(path.asSkiaPath(), offset.x, offset.y)
239- }
265+ override fun addPath (path : Path , offset : Offset ) =
266+ mutatePath { addPath(path.asSkiaPath(), offset.x, offset.y) }
240267
241- override fun close () {
242- internalPath. closePath()
268+ override fun close () = mutatePath {
269+ closePath()
243270 }
244271
245272 override fun reset () {
246- // preserve fillType to match the Android behavior
247- // see https://cs.android.com/android/_/android/platform/frameworks/base/+/d0f379c1976c600313f1f4c39f2587a649e3a4fc
248- val fillType = this .fillType
249- internalPath.reset()
250- this .fillType = fillType
251- }
252-
253- override fun rewind () {
254- internalPath.rewind()
273+ val fillMode = internalPath.fillMode
274+ pathBuilder.reset().setFillType(fillMode)
275+ internalPath = pathBuilder.snapshot()
255276 }
256277
257278 override fun translate (offset : Offset ) {
258- internalPath.transform(Matrix33 .makeTranslate(offset.x, offset.y))
279+ pathBuilder = PathBuilder (internalPath.fillMode)
280+ .addPath(internalPath, offset.x, offset.y)
281+ internalPath = pathBuilder.snapshot()
259282 }
260283
261284 override fun transform (matrix : Matrix ) {
262- internalPath.transform(Matrix33 .makeTranslate(0f , 0f ).apply { setFrom(matrix) })
285+ val skiaMatrix = Matrix33 .makeTranslate(0f , 0f ).apply { setFrom(matrix) }
286+ pathBuilder = PathBuilder (internalPath.fillMode)
287+ .addPath(internalPath, skiaMatrix)
288+ internalPath = pathBuilder.snapshot()
263289 }
264290
265291 override fun getBounds (): Rect {
@@ -283,7 +309,9 @@ internal class SkiaBackedPath(
283309 operation.toSkiaOperation()
284310 )
285311
286- internalPath = path ? : internalPath
312+ if (path != null ) {
313+ replacePath(path)
314+ }
287315 return path != null
288316 }
289317
@@ -304,4 +332,4 @@ internal class SkiaBackedPath(
304332private fun Path.Direction.toSkiaPathDirection () = when (this ) {
305333 Path .Direction .CounterClockwise -> PathDirection .COUNTER_CLOCKWISE
306334 Path .Direction .Clockwise -> PathDirection .CLOCKWISE
307- }
335+ }
0 commit comments