import Animated , { interpolate } from "react-native-reanimated" ; import parseSVG from "parse-svg-path" ; import absSVG from "abs-svg-path" ; import normalizeSVG from "normalize-svg-path" ; import type { Vector } from "./Vectors" ; import { cartesian2Polar } from "./Coordinates" ; import { cubicBezierYForX } from "./Math" ; type SVGCloseCommand = [ "Z" ] ; type SVGMoveCommand = [ "M" , number , number ] ; type SVGCurveCommand = [ "C" , number , number , number , number , number , number ] ; type SVGNormalizedCommands = [ SVGMoveCommand , ... ( SVGCurveCommand | SVGCloseCommand ) [ ] ] ; interface Curve { to : Vector ; c1 : Vector ; c2 : Vector ; } export type Path = { move : Vector ; curves : Curve [ ] ; close : boolean ; } ; summary worklet export const serialize = ( path ) => { "worklet" ; return `M${ path . move . x },${ path . move . y } ${ path . curves . map ( ( c ) => `C${ c . c1 . x },${ c . c1 . y } ${ c . c2 . x },${ c . c2 . y } ${ c . to . x },${ c . to . y }` ) . join ( " " ) }${ path . close ? "Z" : "" }` ; } ; description summary export const parse = ( d ) : Path => { const segments = normalizeSVG ( absSVG ( parseSVG ( d ) ) ) ; const path = createPath ( { x : segments [ 0 ] [ 1 ] , y : segments [ 0 ] [ 2 ] } ) ; segments . forEach ( ( segment ) => { if ( segment [ 0 ] === "Z" ) { close ( path ) ; } else if ( segment [ 0 ] === "C" ) { addCurve ( path , { c1 : { x : segment [ 1 ] , y : segment [ 2 ] , } , c2 : { x : segment [ 3 ] , y : segment [ 4 ] , } , to : { x : segment [ 5 ] , y : segment [ 6 ] , } , } ) ; } } ) ; return path ; } ; summary worklet export const interpolatePath = ( value , inputRange , outputRange , extrapolate = Animated . Extrapolate . CLAMP ) => { "worklet" ; const path = { move : { x : interpolate ( value , inputRange , outputRange . map ( ( p ) => p . move . x ) , extrapolate ) , y : interpolate ( value , inputRange , outputRange . map ( ( p ) => p . move . y ) , extrapolate ) , } , curves : outputRange [ 0 ] . curves . map ( ( _ , index ) => ( { c1 : { x : interpolate ( value , inputRange , outputRange . map ( ( p ) => p . curves [ index ] . c1 . x ) , extrapolate ) , y : interpolate ( value , inputRange , outputRange . map ( ( p ) => p . curves [ index ] . c1 . y ) , extrapolate ) , } , c2 : { x : interpolate ( value , inputRange , outputRange . map ( ( p ) => p . curves [ index ] . c2 . x ) , extrapolate ) , y : interpolate ( value , inputRange , outputRange . map ( ( p ) => p . curves [ index ] . c2 . y ) , extrapolate ) , } , to : { x : interpolate ( value , inputRange , outputRange . map ( ( p ) => p . curves [ index ] . to . x ) , extrapolate ) , y : interpolate ( value , inputRange , outputRange . map ( ( p ) => p . curves [ index ] . to . y ) , extrapolate ) , } , } ) ) , close : outputRange [ 0 ] . close , } ; return serialize ( path ) ; } ; summary worklet export const mixPath = ( value , p1 , p2 , extrapolate = Animated . Extrapolate . CLAMP ) => { "worklet" ; return interpolatePath ( value , [ 0 , 1 ] , [ p1 , p2 ] , extrapolate ) ; } ; summary worklet export const createPath = ( move ) : Path => { "worklet" ; return { move , curves : [ ] , close : false , } ; } ; summary worklet export const addArc = ( path , corner , to ) => { "worklet" ; const last = path . curves [ path . curves . length - 1 ] ; const from = last ? last . to : path . move ; const arc = 9 / 16 ; path . curves . push ( { c1 : { x : ( corner . x - from . x ) * arc + from . x , y : ( corner . y - from . y ) * arc + from . y , } , c2 : { x : ( corner . x - to . x ) * arc + to . x , y : ( corner . y - to . y ) * arc + to . y , } , to , } ) ; } ; summary worklet export const addCurve = ( path , c ) => { "worklet" ; path . curves . push ( { c1 : c . c1 , c2 : c . c2 , to : c . to , } ) ; } ; summary worklet export const addLine = ( path , to ) => { "worklet" ; const last = path . curves [ path . curves . length - 1 ] ; const from = last ? last . to : path . move ; path . curves . push ( { c1 : from , c2 : to , to , } ) ; } ; summary worklet export const addQuadraticCurve = ( path , cp , to ) => { "worklet" ; const last = path . curves [ path . curves . length - 1 ] ; const from = last ? last . to : path . move ; path . curves . push ( { c1 : { x : from . x / 3 + ( 2 / 3 ) * cp . x , y : from . y / 3 + ( 2 / 3 ) * cp . y , } , c2 : { x : to . x / 3 + ( 2 / 3 ) * cp . x , y : to . y / 3 + ( 2 / 3 ) * cp . y , } , to , } ) ; } ; summary worklet export const close = ( path ) => { "worklet" ; path . close = true ; } ; interface SelectedCurve { from : Vector ; curve : Curve ; } interface NullableSelectedCurve { from : Vector ; curve : Curve | null ; } worklet const curveIsFound = ( c ) : c is SelectedCurve => { "worklet" ; return c . curve !== null ; } ; summary worklet export const selectCurve = ( path , x ) : SelectedCurve | null => { "worklet" ; const result = { from : path . move , curve : null , } ; for ( let i = 0 ; i < path . curves . length ; i ++ ) { const c = path . curves [ i ] ; const contains = result . from . x > c . to . x ? x >= c . to . x && x <= result . from . x : x >= result . from . x && x <= c . to . x ; if ( contains ) { result . curve = c ; break ; } result . from = c . to ; } if ( ! curveIsFound ( result ) ) { return null ; } return result ; } ; summary example worklet export const getYForX = ( path , x , precision = 2 ) => { "worklet" ; const c = selectCurve ( path , x ) ; if ( c === null ) { return null ; } return cubicBezierYForX ( x , c . from , c . curve . c1 , c . curve . c2 , c . curve . to , precision ) ; } ; const controlPoint = ( current , previous , next , reverse , smoothing ) => { "worklet" ; const p = previous || current ; const n = next || current ; const lengthX = n . x - p . x ; const lengthY = n . y - p . y ; const o = cartesian2Polar ( { x : lengthX , y : lengthY } ) ; const angle = o . theta + ( reverse ? Math . PI : 0 ) ; const length = o . radius * smoothing ; const x = current . x + Math . cos ( angle ) * length ; const y = current . y + Math . sin ( angle ) * length ; return { x , y } ; } ; const exhaustiveCheck = ( a ) : never => { throw new Error ( `Unexhaustive handling for ${ a }` ) ; } ; summary worklet export const curveLines = ( points , smoothing , strategy ) => { "worklet" ; const path = createPath ( points [ 0 ] ) ; for ( let i = 0 ; i < points . length ; i ++ ) { if ( i === 0 ) { continue ; } const point = points [ i ] ; const next = points [ i + 1 ] ; const prev = points [ i - 1 ] ; const cps = controlPoint ( prev , points [ i - 2 ] , point , false , smoothing ) ; const cpe = controlPoint ( point , prev , next , true , smoothing ) ; switch ( strategy ) { case "simple" : const cp = { x : ( cps . x + cpe . x ) / 2 , y : ( cps . y + cpe . y ) / 2 , } ; addQuadraticCurve ( path , cp , point ) ; break ; case "bezier" : const p0 = points [ i - 2 ] || prev ; const p1 = points [ i - 1 ] ; const cp1x = ( 2 * p0 . x + p1 . x ) / 3 ; const cp1y = ( 2 * p0 . y + p1 . y ) / 3 ; const cp2x = ( p0 . x + 2 * p1 . x ) / 3 ; const cp2y = ( p0 . y + 2 * p1 . y ) / 3 ; const cp3x = ( p0 . x + 4 * p1 . x + point . x ) / 6 ; const cp3y = ( p0 . y + 4 * p1 . y + point . y ) / 6 ; path . curves . push ( { c1 : { x : cp1x , y : cp1y } , c2 : { x : cp2x , y : cp2y } , to : { x : cp3x , y : cp3y } , } ) ; if ( i === points . length - 1 ) { path . curves . push ( { to : points [ points . length - 1 ] , c1 : points [ points . length - 1 ] , c2 : points [ points . length - 1 ] , } ) ; } break ; case "complex" : path . curves . push ( { to : point , c1 : cps , c2 : cpe , } ) ; break ; default : exhaustiveCheck ( strategy ) ; } } return path ; } ;