@@ -116,5 +116,268 @@ SegmentInfo IPathInternals.PointAlongPath(float distance)
116116
117117 /// <inheritdoc/>
118118 IReadOnlyList < InternalPath > IInternalPathOwner . GetRingsAsInternalPath ( ) => new [ ] { this . InnerPath } ;
119+
120+ /// <summary>
121+ /// Converts an SVG path string into an <see cref="IPath"/>.
122+ /// </summary>
123+ /// <param name="svgPath">The string containing the SVG path data.</param>
124+ /// <param name="value">
125+ /// When this method returns, contains the logic path converted from the given SVG path string; otherwise, <see langword="null"/>.
126+ /// This parameter is passed uninitialized.
127+ /// </param>
128+ /// <returns><see langword="true"/> if the input value can be parsed and converted; otherwise, <see langword="false"/>.</returns>
129+ public static bool TryParseSvgPath ( string svgPath , out IPath value )
130+ => TryParseSvgPath ( svgPath . AsSpan ( ) , out value ) ;
131+
132+ /// <summary>
133+ /// Converts an SVG path string into an <see cref="IPath"/>.
134+ /// </summary>
135+ /// <param name="svgPath">The string containing the SVG path data.</param>
136+ /// <param name="value">
137+ /// When this method returns, contains the logic path converted from the given SVG path string; otherwise, <see langword="null"/>.
138+ /// This parameter is passed uninitialized.
139+ /// </param>
140+ /// <returns><see langword="true"/> if the input value can be parsed and converted; otherwise, <see langword="false"/>.</returns>
141+ public static bool TryParseSvgPath ( ReadOnlySpan < char > svgPath , out IPath value )
142+ {
143+ value = null ;
144+
145+ var builder = new PathBuilder ( ) ;
146+
147+ PointF first = PointF . Empty ;
148+ PointF c = PointF . Empty ;
149+ PointF lastc = PointF . Empty ;
150+ PointF point1 ;
151+ PointF point2 ;
152+ PointF point3 ;
153+
154+ char op = '\0 ' ;
155+ char previousOp = '\0 ' ;
156+ bool relative = false ;
157+ while ( true )
158+ {
159+ svgPath = svgPath . TrimStart ( ) ;
160+ if ( svgPath . Length == 0 )
161+ {
162+ break ;
163+ }
164+
165+ char ch = svgPath [ 0 ] ;
166+ if ( char . IsDigit ( ch ) || ch == '-' || ch == '+' || ch == '.' )
167+ {
168+ // Are we are the end of the string or we are at the end of the path?
169+ if ( svgPath . Length == 0 || op == 'Z' )
170+ {
171+ return false ;
172+ }
173+ }
174+ else if ( IsSeparator ( ch ) )
175+ {
176+ svgPath = TrimSeparator ( svgPath ) ;
177+ }
178+ else
179+ {
180+ op = ch ;
181+ relative = false ;
182+ if ( char . IsLower ( op ) )
183+ {
184+ op = char . ToUpper ( op ) ;
185+ relative = true ;
186+ }
187+
188+ svgPath = TrimSeparator ( svgPath . Slice ( 1 ) ) ;
189+ }
190+
191+ switch ( op )
192+ {
193+ case 'M' :
194+ svgPath = FindPoint ( svgPath , out point1 , relative , c ) ;
195+ builder . MoveTo ( point1 ) ;
196+ previousOp = '\0 ' ;
197+ op = 'L' ;
198+ c = point1 ;
199+ break ;
200+ case 'L' :
201+ svgPath = FindPoint ( svgPath , out point1 , relative , c ) ;
202+ builder . LineTo ( point1 ) ;
203+ c = point1 ;
204+ break ;
205+ case 'H' :
206+ svgPath = FindScaler ( svgPath , out float x ) ;
207+ if ( relative )
208+ {
209+ x += c . X ;
210+ }
211+
212+ builder . LineTo ( x , c . Y ) ;
213+ c . X = x ;
214+ break ;
215+ case 'V' :
216+ svgPath = FindScaler ( svgPath , out float y ) ;
217+ if ( relative )
218+ {
219+ y += c . Y ;
220+ }
221+
222+ builder . LineTo ( c . X , y ) ;
223+ c . Y = y ;
224+ break ;
225+ case 'C' :
226+ svgPath = FindPoint ( svgPath , out point1 , relative , c ) ;
227+ svgPath = FindPoint ( svgPath , out point2 , relative , c ) ;
228+ svgPath = FindPoint ( svgPath , out point3 , relative , c ) ;
229+ builder . CubicBezierTo ( point1 , point2 , point3 ) ;
230+ lastc = point2 ;
231+ c = point3 ;
232+ break ;
233+ case 'S' :
234+ svgPath = FindPoint ( svgPath , out point2 , relative , c ) ;
235+ svgPath = FindPoint ( svgPath , out point3 , relative , c ) ;
236+ point1 = c ;
237+ if ( previousOp is 'C' or 'S' )
238+ {
239+ point1 . X -= lastc . X - c . X ;
240+ point1 . Y -= lastc . Y - c . Y ;
241+ }
242+
243+ builder . CubicBezierTo ( point1 , point2 , point3 ) ;
244+ lastc = point2 ;
245+ c = point3 ;
246+ break ;
247+ case 'Q' : // Quadratic Bezier Curve
248+ svgPath = FindPoint ( svgPath , out point1 , relative , c ) ;
249+ svgPath = FindPoint ( svgPath , out point2 , relative , c ) ;
250+ builder . QuadraticBezierTo ( point1 , point2 ) ;
251+ lastc = point1 ;
252+ c = point2 ;
253+ break ;
254+ case 'T' :
255+ svgPath = FindPoint ( svgPath , out point2 , relative , c ) ;
256+ point1 = c ;
257+ if ( previousOp is 'Q' or 'T' )
258+ {
259+ point1 . X -= lastc . X - c . X ;
260+ point1 . Y -= lastc . Y - c . Y ;
261+ }
262+
263+ builder . QuadraticBezierTo ( point1 , point2 ) ;
264+ lastc = point1 ;
265+ c = point2 ;
266+ break ;
267+ case 'A' :
268+ svgPath = FindScaler ( svgPath , out float radiiX ) ;
269+ svgPath = TrimSeparator ( svgPath ) ;
270+ svgPath = FindScaler ( svgPath , out float radiiY ) ;
271+ svgPath = TrimSeparator ( svgPath ) ;
272+ svgPath = FindScaler ( svgPath , out float angle ) ;
273+ svgPath = TrimSeparator ( svgPath ) ;
274+ svgPath = FindScaler ( svgPath , out float largeArc ) ;
275+ svgPath = TrimSeparator ( svgPath ) ;
276+ svgPath = FindScaler ( svgPath , out float sweep ) ;
277+
278+ svgPath = FindPoint ( svgPath , out PointF point , relative , c ) ;
279+ if ( svgPath . Length > 0 )
280+ {
281+ builder . ArcTo ( radiiX , radiiY , angle , largeArc == 1 , sweep == 1 , point ) ;
282+ c = point ;
283+ }
284+
285+ break ;
286+ case 'Z' :
287+ builder . CloseFigure ( ) ;
288+ c = first ;
289+ break ;
290+ case '~' :
291+ svgPath = FindPoint ( svgPath , out point1 , relative , c ) ;
292+ svgPath = FindPoint ( svgPath , out point2 , relative , c ) ;
293+ builder . MoveTo ( point1 ) ;
294+ builder . LineTo ( point2 ) ;
295+ break ;
296+ default :
297+ return false ;
298+ }
299+
300+ if ( previousOp == 0 )
301+ {
302+ first = c ;
303+ }
304+
305+ previousOp = op ;
306+ }
307+
308+ value = builder . Build ( ) ;
309+ return true ;
310+
311+ static bool IsSeparator ( char ch )
312+ => char . IsWhiteSpace ( ch ) || ch == ',' ;
313+
314+ static ReadOnlySpan < char > TrimSeparator ( ReadOnlySpan < char > data )
315+ {
316+ if ( data . Length == 0 )
317+ {
318+ return data ;
319+ }
320+
321+ int idx = 0 ;
322+ for ( ; idx < data . Length ; idx ++ )
323+ {
324+ if ( ! IsSeparator ( data [ idx ] ) )
325+ {
326+ break ;
327+ }
328+ }
329+
330+ return data . Slice ( idx ) ;
331+ }
332+
333+ static ReadOnlySpan < char > FindPoint ( ReadOnlySpan < char > str , out PointF value , bool isRelative , PointF relative )
334+ {
335+ str = FindScaler ( str , out float x ) ;
336+ str = FindScaler ( str , out float y ) ;
337+ if ( isRelative )
338+ {
339+ x += relative . X ;
340+ y += relative . Y ;
341+ }
342+
343+ value = new PointF ( x , y ) ;
344+ return str ;
345+ }
346+
347+ static ReadOnlySpan < char > FindScaler ( ReadOnlySpan < char > str , out float scaler )
348+ {
349+ str = TrimSeparator ( str ) ;
350+ scaler = 0 ;
351+
352+ for ( int i = 0 ; i < str . Length ; i ++ )
353+ {
354+ if ( IsSeparator ( str [ i ] ) || i == str . Length )
355+ {
356+ scaler = ParseFloat ( str . Slice ( 0 , i ) ) ;
357+ return str . Slice ( i ) ;
358+ }
359+ }
360+
361+ if ( str . Length > 0 )
362+ {
363+ scaler = ParseFloat ( str ) ;
364+ }
365+
366+ return ReadOnlySpan < char > . Empty ;
367+ }
368+
369+ #if ! NETCOREAPP2_1_OR_GREATER
370+ static unsafe float ParseFloat ( ReadOnlySpan < char > str )
371+ {
372+ fixed ( char * p = str )
373+ {
374+ return float . Parse ( new string ( p , 0 , str . Length ) ) ;
375+ }
376+ }
377+ #else
378+ static float ParseFloat ( ReadOnlySpan < char > str )
379+ => float . Parse ( str ) ;
380+ #endif
381+ }
119382 }
120383}
0 commit comments