dmdb/model/svg/SvgPath.cs

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();
}
}
}