338 lines
16 KiB
C#
338 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Xml.Serialization;
|
|
|
|
namespace dezentrale.model.svg
|
|
{
|
|
public class SvgPath : SvgElement
|
|
{
|
|
public abstract class SvgPathCommand
|
|
{
|
|
public class DoubleInst
|
|
{
|
|
//this is a bit ugly but I don't have a better solution yet:
|
|
//DoubleInst is a class that holds one double value in order to
|
|
//allow dynamic passing by references (e.g. building an array
|
|
//out of DoubleInst objectsand pass them to another method that
|
|
//can then change the values and can be agnostic to its semantic
|
|
|
|
public double Val { get; set; } = 0;
|
|
|
|
public DoubleInst(double newVal) { Val = newVal; }
|
|
public override string ToString() { return Convert.ToString(Val, System.Globalization.CultureInfo.InvariantCulture); }
|
|
}
|
|
public class SvgPathMoveTo : SvgPathCommand
|
|
{
|
|
public DoubleInst X { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Y { get; set; } = new DoubleInst(0);
|
|
public override string ToString() { return $"{(Relative ? 'm' : 'M')}{X},{Y}"; }
|
|
protected override bool ParseCoordinates(string input, int startIndex, out int nextIndex)
|
|
{
|
|
return ParseStatic(input, startIndex, out nextIndex, (new DoubleInst[] { X, Y }));
|
|
}
|
|
}
|
|
public class SvgPathClose : SvgPathCommand
|
|
{
|
|
public override string ToString() { return $"{(Relative ? 'z' : 'Z')}"; }
|
|
protected override bool ParseCoordinates(string input, int startIndex, out int nextIndex)
|
|
{
|
|
return ParseStatic(input, startIndex, out nextIndex, (new DoubleInst[] { }));
|
|
}
|
|
}
|
|
public class SvgPathLineTo : SvgPathCommand
|
|
{
|
|
public DoubleInst X { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Y { get; set; } = new DoubleInst(0);
|
|
public override string ToString() { return $"{(Relative ? 'l' : 'L')}{X},{Y}"; }
|
|
protected override bool ParseCoordinates(string input, int startIndex, out int nextIndex)
|
|
{
|
|
return ParseStatic(input, startIndex, out nextIndex, (new DoubleInst[] { X, Y }));
|
|
}
|
|
}
|
|
public class SvgPathHorizontalLineTo : SvgPathCommand
|
|
{
|
|
public DoubleInst X { get; set; } = new DoubleInst(0);
|
|
public override string ToString() { return $"{(Relative ? 'h' : 'H')}{X}"; }
|
|
protected override bool ParseCoordinates(string input, int startIndex, out int nextIndex)
|
|
{
|
|
return ParseStatic(input, startIndex, out nextIndex, (new DoubleInst[] { X }));
|
|
}
|
|
}
|
|
public class SvgPathVerticalLineTo : SvgPathCommand
|
|
{
|
|
public DoubleInst Y { get; set; } = new DoubleInst(0);
|
|
public override string ToString() { return $"{(Relative ? 'v' : 'V')}{Y}"; }
|
|
protected override bool ParseCoordinates(string input, int startIndex, out int nextIndex)
|
|
{
|
|
return ParseStatic(input, startIndex, out nextIndex, (new DoubleInst[] { Y }));
|
|
}
|
|
}
|
|
public class SvgPathCurveTo : SvgPathCommand
|
|
{
|
|
public DoubleInst X1 { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Y1 { get; set; } = new DoubleInst(0);
|
|
public DoubleInst X2 { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Y2 { get; set; } = new DoubleInst(0);
|
|
public DoubleInst X { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Y { get; set; } = new DoubleInst(0);
|
|
public override string ToString() { return $"{(Relative ? 'c' : 'C')}{X1},{Y1} {X2},{Y2} {X},{Y}"; }
|
|
protected override bool ParseCoordinates(string input, int startIndex, out int nextIndex)
|
|
{
|
|
return ParseStatic(input, startIndex, out nextIndex, (new DoubleInst[] { X1, Y1, X2, Y2, X, Y }));
|
|
}
|
|
}
|
|
public class SvgPathSmoothCurveTo : SvgPathCommand
|
|
{
|
|
public DoubleInst X2 { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Y2 { get; set; } = new DoubleInst(0);
|
|
public DoubleInst X { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Y { get; set; } = new DoubleInst(0);
|
|
public override string ToString() { return $"{(Relative ? 's' : 'S')}{X2},{Y2} {X},{Y}"; }
|
|
protected override bool ParseCoordinates(string input, int startIndex, out int nextIndex)
|
|
{
|
|
return ParseStatic(input, startIndex, out nextIndex, (new DoubleInst[] { X2, Y2, X, Y }));
|
|
}
|
|
}
|
|
public class SvgPathEllipticalArc : SvgPathCommand
|
|
{
|
|
public DoubleInst Rx { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Ry { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Phi { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Lf { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Sf { get; set; } = new DoubleInst(0);
|
|
public DoubleInst X { get; set; } = new DoubleInst(0);
|
|
public DoubleInst Y { get; set; } = new DoubleInst(0);
|
|
public override string ToString() { return $"{(Relative ? 'a' : 'A')}{Rx},{Ry},{Phi} {Lf} {Sf} {X},{Y}"; }
|
|
protected override bool ParseCoordinates(string input, int startIndex, out int nextIndex)
|
|
{
|
|
return ParseStatic(input, startIndex, out nextIndex, (new DoubleInst[] { Rx, Ry, Phi, Lf, Sf, X, Y }));
|
|
}
|
|
}
|
|
|
|
public abstract override string ToString();
|
|
protected abstract bool ParseCoordinates(string input, int startIndex, out int nextIndex);
|
|
|
|
public bool Relative { get; set; } = false;
|
|
|
|
private static bool ParseStatic(string input, int startIndex, out int nextIndex, DoubleInst[] c)
|
|
{
|
|
//Console.WriteLine($"ParseStatic(\"{input}\", {startIndex}, -1, ({c.Length} entries))");
|
|
if (c.Length < 1) nextIndex = startIndex;
|
|
else nextIndex = -1;
|
|
|
|
foreach(DoubleInst d in c)
|
|
{
|
|
//trim leading space and commata
|
|
for (; startIndex < input.Length; startIndex++)
|
|
{
|
|
string ch = input.Substring(startIndex, 1);
|
|
if (ch != " " && ch != ",") break;
|
|
}
|
|
|
|
string tmp = "";
|
|
if (tmp.Length == 0)
|
|
Console.WriteLine("pling");
|
|
//assemble numeric value into tmp string
|
|
for(; startIndex < input.Length; startIndex++)
|
|
{
|
|
string ch = input.Substring(startIndex, 1);
|
|
//Console.WriteLine($"char={ch}");
|
|
|
|
bool ok = false;
|
|
switch (ch)
|
|
{
|
|
case "-":
|
|
if (tmp.Length == 0) //minus only at the beginning of a number
|
|
//todo: or an exponent
|
|
{
|
|
Console.WriteLine("pling");
|
|
tmp += ch;
|
|
ok = true;
|
|
}
|
|
break;
|
|
case "0": case "1": case "2": case "3": case "4":
|
|
case "5": case "6": case "7": case "8": case "9":
|
|
case ".": case "e": case "E":
|
|
tmp += ch;
|
|
ok = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!ok) break;
|
|
}
|
|
if(tmp.Length != 0)
|
|
{
|
|
Console.WriteLine($"tmp = {tmp}");
|
|
d.Val = Convert.ToDouble(tmp, System.Globalization.CultureInfo.InvariantCulture);
|
|
nextIndex = startIndex;
|
|
} else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
public static SvgPathCommand GetCommand(string input, int startIndex, out int nextIndex, SvgPathCommand lastCommand = null)
|
|
{
|
|
nextIndex = -1;
|
|
Console.WriteLine($"GetCommand((input),{startIndex},{nextIndex},{(lastCommand != null ? lastCommand.GetType().ToString() : "null")}");
|
|
|
|
//Console.WriteLine($"GetCommand: input = \"{input}\"");
|
|
if (startIndex < 0 || startIndex >= input.Length || input == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
//trim leading space and commata
|
|
string ch;
|
|
for (; startIndex < input.Length; startIndex++)
|
|
{
|
|
ch = input.Substring(startIndex, 1);
|
|
if (ch != " " && ch != ",") break;
|
|
}
|
|
|
|
ch = input.Substring(startIndex, 1);
|
|
SvgPathCommand ret = null;
|
|
switch (ch)
|
|
{
|
|
case "0": case "1": case "2": case "3": case "4":
|
|
case "5": case "6": case "7": case "8": case "9": case ".": case "-":
|
|
//Console.WriteLine("Test");
|
|
if (lastCommand == null)
|
|
ret = new SvgPathLineTo();
|
|
else if (lastCommand.GetType() == typeof(SvgPathMoveTo))
|
|
{
|
|
ret = new SvgPathLineTo() { Relative = lastCommand.Relative };
|
|
} else
|
|
{
|
|
ret = (SvgPathCommand)Activator.CreateInstance(lastCommand.GetType());
|
|
ret.Relative = lastCommand.Relative;
|
|
}
|
|
startIndex--;
|
|
break;
|
|
case "m": ret = new SvgPathMoveTo() { Relative = true }; break;
|
|
case "M": ret = new SvgPathMoveTo() { Relative = false }; break;
|
|
case "z": ret = new SvgPathClose() { Relative = true }; break;
|
|
case "Z": ret = new SvgPathClose() { Relative = false }; break;
|
|
case "l": ret = new SvgPathLineTo() { Relative = true }; break;
|
|
case "L": ret = new SvgPathLineTo() { Relative = false }; break;
|
|
case "h": ret = new SvgPathHorizontalLineTo() { Relative = true }; break;
|
|
case "H": ret = new SvgPathHorizontalLineTo() { Relative = false }; break;
|
|
case "v": ret = new SvgPathVerticalLineTo() { Relative = true }; break;
|
|
case "V": ret = new SvgPathVerticalLineTo() { Relative = false }; break;
|
|
case "c": ret = new SvgPathCurveTo() { Relative = true }; break;
|
|
case "C": ret = new SvgPathCurveTo() { Relative = false }; break;
|
|
case "s": ret = new SvgPathSmoothCurveTo() { Relative = true }; break;
|
|
case "S": ret = new SvgPathSmoothCurveTo() { Relative = false }; break;
|
|
case "a": ret = new SvgPathEllipticalArc() { Relative = true }; break;
|
|
case "A": ret = new SvgPathEllipticalArc() { Relative = false }; break;
|
|
|
|
default: //invalid character
|
|
nextIndex = -1;
|
|
return null;
|
|
}
|
|
startIndex++;
|
|
if (ret == null) return ret;
|
|
|
|
if (!ret.ParseCoordinates(input, startIndex, out nextIndex))
|
|
{
|
|
ret = null;
|
|
nextIndex = -1;
|
|
} else
|
|
{
|
|
Console.WriteLine($"{ret.GetType()}: nextIndex = {nextIndex}");
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
[XmlIgnore] public List<SvgPathCommand> CommandList { get; set; } = new List<SvgPathCommand>();
|
|
[XmlAttribute]
|
|
public string d
|
|
{
|
|
get
|
|
{
|
|
if (CommandList.Count == 0) return null;
|
|
//Assemble path
|
|
string path = "";
|
|
foreach (SvgPathCommand cmd in CommandList)
|
|
{
|
|
path += cmd.ToString() + " ";
|
|
}
|
|
return path.Trim();
|
|
}
|
|
set
|
|
{
|
|
Console.WriteLine($"CommandList.set(): \"{value}\"");
|
|
CommandList.Clear();
|
|
if (value == null) return;
|
|
//parse path
|
|
int nextIndex = 0;
|
|
SvgPathCommand lastCommand = null;
|
|
while(nextIndex >= 0)
|
|
{
|
|
lastCommand = SvgPathCommand.GetCommand(value, nextIndex, out nextIndex, lastCommand);
|
|
if (lastCommand != null)
|
|
CommandList.Add(lastCommand);
|
|
else
|
|
break;
|
|
}
|
|
|
|
//foreach(SvgPathCommand c in CommandList)
|
|
{
|
|
//Console.WriteLine($"{c.GetType()}: \"{c}\"");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//helper methods for building the path ;-)
|
|
|
|
|
|
/** Tries to transform all contained path commands to absolute coordinate values */
|
|
public void MakeAbsolute()
|
|
{
|
|
#pragma warning disable CS0219 // suppress unused for now, until this method is implemented better
|
|
double x = 0;
|
|
double y = 0;
|
|
#pragma warning restore CS0219
|
|
foreach (SvgPathCommand c in CommandList)
|
|
{
|
|
|
|
}
|
|
throw new NotImplementedException();
|
|
}
|
|
/** Tries to transform all contained path commands to absolute coordinate values */
|
|
public void MakeRelative()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void AddCommand(SvgPathCommand cmd) { CommandList.Add(cmd); }
|
|
|
|
|
|
/**\short translates all path coordinates by (dx, dy) */
|
|
public void Translate(double dx, double dy)
|
|
{
|
|
#pragma warning disable CS0219 // suppress unused for now, until this method is implemented better
|
|
double x = 0;
|
|
double y = 0;
|
|
#pragma warning restore CS0219
|
|
foreach (SvgPathCommand cmd in CommandList)
|
|
{
|
|
if(!cmd.Relative)
|
|
{
|
|
//cmd.Translate(dx, dy);
|
|
}
|
|
}
|
|
throw new NotImplementedException();
|
|
}
|
|
/**\short scales all path coordinates around the center point (cx,cy) by the factors of (fx, fy) */
|
|
public void Scale(double cx, double cy, double fx, double fy)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|