using Godot; using System; using System.Collections.Generic; namespace Rokojori { public enum SVGPathInstructionType { MoveTo, LineTo, QuadraticBezierTo, CubicBezierTo, ArcTo, Close } public class SVGPathInstruction { public SVGPathInstructionType type = SVGPathInstructionType.MoveTo; public Vector2 startPoint = Vector2.Zero; public Vector2 controlPoint1 = Vector2.Zero; public Vector2 controlPoint2 = Vector2.Zero; public Vector2 endPoint = Vector2.Zero; public Vector2 radius = Vector2.Zero; public float angle = 0; public bool largeArcFlag = false; public bool sweepFlag = false; public override string ToString() { return GetInfo(); } public string GetInfo() { var infos = new List(); infos.Add( type ); if ( SVGPathInstructionType.MoveTo == type ) { infos.Add( endPoint ); } else if ( SVGPathInstructionType.LineTo == type ) { infos.Add( startPoint ); infos.Add( endPoint ); } else if ( SVGPathInstructionType.QuadraticBezierTo == type ) { infos.Add( startPoint ); infos.Add( controlPoint1 ); infos.Add( endPoint ); } else if ( SVGPathInstructionType.CubicBezierTo == type ) { infos.Add( startPoint ); infos.Add( controlPoint1 ); infos.Add( controlPoint2 ); infos.Add( endPoint ); } else if ( SVGPathInstructionType.ArcTo == type ) { infos.Add( startPoint ); infos.Add( radius ); infos.Add( angle ); infos.Add( largeArcFlag ); infos.Add( sweepFlag ); infos.Add( endPoint ); } return RJLog.GetLogString( infos.ToArray() ); } } public class SVGPathExtractor { Vector2 currentPoint = new Vector2(); Vector2 lastControlPoint = new Vector2(); Vector2 startPoint = new Vector2(); SVGPathInstruction instruction = new SVGPathInstruction(); public readonly EventSlot onInstruction = new EventSlot(); void DispatchInstruction() { onInstruction.DispatchEvent( instruction ); } public void Process( List commands ) { commands.ForEach( c => ProcessNext( c ) ); } public void ProcessNext( SVGPathCommand command ) { switch ( command.type) { case "m": case "M": { ProcessMoveTo( command ); } break; case "l": case "L": { ProcessLineTo( command ); } break; case "v": case "V": { ProcessVerticalTo( command ); } break; case "h": case "H": { ProcessHorizonatlTo( command ); } break; case "c": case "C": { ProcessCubicTo( command ); } break; case "q": case "Q": { ProcessQuadraticTo( command ); } break; case "a": case "A": { ProcessArcTo( command ); } break; case "z": case "Z": { ProcessClose(); } break; } } void ProcessClose() { instruction.type = SVGPathInstructionType.Close; instruction.startPoint = currentPoint; instruction.endPoint = startPoint; } void ProcessMoveTo( SVGPathCommand command ) { var relative = command.type == "m"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i+= 2 ) { ProcessToEndpoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] ); if ( i == 0 ) { startPoint = currentPoint; } } } void ProcessLineTo( SVGPathCommand command ) { var relative = command.type == "l"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i+= 2 ) { ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] ); } } void ProcessVerticalTo( SVGPathCommand command ) { var relative = command.type == "v"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i++ ) { var x = relative ? 0 : currentPoint.X; ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, parameters[ i ] ); } } void ProcessHorizonatlTo( SVGPathCommand command ) { var relative = command.type == "v"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i++ ) { var y = relative ? 0 : currentPoint.Y; ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], y ); } } void ProcessArcTo( SVGPathCommand command ) { var relative = command.type == "a"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i+= 7 ) { ProcessArc( relative, parameters[ i ], parameters[ i + 1 ], parameters[ i + 2 ], parameters[ i + 3 ], parameters[ i + 4 ], parameters[ i + 5 ], parameters[ i + 7 ] ); } } void ProcessArc( bool relative, float rx, float ry, float angle, float largeArc, float sweepArc, float x, float y ) { instruction.type = SVGPathInstructionType.ArcTo; instruction.startPoint = currentPoint; instruction.radius.X = rx; instruction.radius.Y = ry; instruction.angle = angle; instruction.largeArcFlag = largeArc > 0; instruction.sweepFlag = sweepArc > 0; instruction.endPoint.X = x; instruction.endPoint.Y = y; if ( relative ) { instruction.endPoint += currentPoint; } currentPoint = instruction.endPoint; DispatchInstruction(); } void ProcessCubicTo( SVGPathCommand command ) { var relative = command.type == "c"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i+= 6 ) { ProcessCubic( relative, parameters[ i ], parameters[ i + 1 ], parameters[ i + 2 ], parameters[ i + 3 ], parameters[ i + 4 ], parameters[ i + 5 ] ); } } void ProcessSCubicTo( SVGPathCommand command ) { var relative = command.type == "s"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i+= 4 ) { var cpDiff = currentPoint - lastControlPoint; var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); ProcessCubic( relative, cpNext.X, cpNext.Y, parameters[ i ], parameters[ i + 1 ], parameters[ i + 2 ], parameters[ i + 3 ] ); } } void ProcessCubic( bool relative, float cx1, float cy1, float cx2, float cy2, float x, float y ) { instruction.type = SVGPathInstructionType.CubicBezierTo; instruction.startPoint = currentPoint; instruction.controlPoint1.X = cx1; instruction.controlPoint1.Y = cy1; instruction.controlPoint2.X = cx2; instruction.controlPoint2.Y = cy2; instruction.endPoint.X = x; instruction.endPoint.Y = y; lastControlPoint = instruction.controlPoint2; if ( relative ) { instruction.controlPoint1 += currentPoint; instruction.controlPoint2 += currentPoint; instruction.endPoint += currentPoint; } currentPoint = instruction.endPoint; DispatchInstruction(); } void ProcessQuadraticTo( SVGPathCommand command ) { var relative = command.type == "q"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i+= 4 ) { ProcessQuadratic( relative, parameters[ i ], parameters[ i + 1 ], parameters[ i + 2 ], parameters[ i + 3 ] ); } } void ProcessTQuadraticTo( SVGPathCommand command ) { var relative = command.type == "t"; var parameters = command.paramaters; for ( int i = 0; i < parameters.Count; i+= 2 ) { var cpDiff = currentPoint - lastControlPoint; var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); ProcessQuadratic( relative, cpNext.X, cpNext.Y, parameters[ i ], parameters[ i + 1 ] ); } } void ProcessQuadratic( bool relative, float cx1, float cy1, float x, float y ) { instruction.type = SVGPathInstructionType.QuadraticBezierTo; instruction.startPoint = currentPoint; instruction.controlPoint1.X = cx1; instruction.controlPoint1.Y = cy1; instruction.endPoint.X = x; instruction.endPoint.Y = y; lastControlPoint = instruction.controlPoint1; if ( relative ) { instruction.controlPoint1 += currentPoint; instruction.endPoint += currentPoint; } currentPoint = instruction.endPoint; DispatchInstruction(); } void ProcessToEndpoint( bool relative, SVGPathInstructionType type, float x, float y ) { instruction.type = type; instruction.startPoint = currentPoint; instruction.endPoint.X = x; instruction.endPoint.Y = y; if ( relative ) { instruction.endPoint += currentPoint; } currentPoint = instruction.endPoint; DispatchInstruction(); } } }