using Godot; using System; using System.Collections.Generic; namespace Rokojori { 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": { ProcessHorizontalTo( 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( command ); } break; } } void ProcessClose( SVGPathCommand command ) { instruction.sourceCommand = command; instruction.sourceCommandIndex = 0; instruction.type = SVGPathInstructionType.Close; instruction.startPoint = startPoint; instruction.endPoint = startPoint; currentPoint = startPoint; DispatchInstruction(); } void ProcessMoveTo( SVGPathCommand command ) { var relative = command.type == "m"; var parameters = command.paramaters; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i+= 2 ) { instruction.sourceCommandIndex = sourceCommandIndex ++; if ( i == 0 ) { ProcessMoveToStartPoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] ); } else { ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] ); } } } void ProcessLineTo( SVGPathCommand command ) { var relative = command.type == "l"; var parameters = command.paramaters; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i+= 2 ) { instruction.sourceCommandIndex = sourceCommandIndex ++; ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] ); } } void ProcessVerticalTo( SVGPathCommand command ) { var relative = command.type == "v"; var parameters = command.paramaters; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i++ ) { instruction.sourceCommandIndex = sourceCommandIndex ++; var x = relative ? 0 : currentPoint.X; var y = parameters[ i ]; ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, y ); } } void ProcessHorizontalTo( SVGPathCommand command ) { var relative = command.type == "h"; var parameters = command.paramaters; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i++ ) { instruction.sourceCommandIndex = sourceCommandIndex ++; var x = parameters[ i ]; var y = relative ? 0 : currentPoint.Y; ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, y ); } } void ProcessArcTo( SVGPathCommand command ) { var relative = command.type == "a"; var parameters = command.paramaters; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i+= 7 ) { instruction.sourceCommandIndex = sourceCommandIndex ++; ProcessArc( relative, parameters[ i ], parameters[ i + 1 ], parameters[ i + 2 ], parameters[ i + 3 ], parameters[ i + 4 ], parameters[ i + 5 ], parameters[ i + 6 ] ); } } 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; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i+= 6 ) { instruction.sourceCommandIndex = sourceCommandIndex ++; 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; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i+= 4 ) { instruction.sourceCommandIndex = sourceCommandIndex ++; 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; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i+= 4 ) { instruction.sourceCommandIndex = sourceCommandIndex ++; 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; var sourceCommandIndex = 0; instruction.sourceCommand = command; for ( int i = 0; i < parameters.Count; i+= 2 ) { instruction.sourceCommandIndex = sourceCommandIndex ++; 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 ProcessMoveToStartPoint( bool relative, SVGPathInstructionType type, float x, float y ) { instruction.type = type; instruction.endPoint.X = x; instruction.endPoint.Y = y; if ( relative ) { instruction.endPoint += currentPoint; } currentPoint = instruction.endPoint; instruction.startPoint = currentPoint; startPoint = currentPoint; 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(); } } }