201205PX Added code for SVG path processing (Parsing and generating path strings)
This commit is contained in:
parent
0cbe0545b1
commit
5e683475a6
|
@ -200,6 +200,7 @@
|
|||
<Compile Include="core\MvInvitationProcess.cs" />
|
||||
<Compile Include="model\Mv.cs" />
|
||||
<Compile Include="model\svg\SvgFile.cs" />
|
||||
<Compile Include="model\svg\SvgPath.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
|
|
|
@ -5,32 +5,72 @@ using System.Xml;
|
|||
using System.Xml.Schema;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
//Some documentation was copied from https://www.w3.org/TR/SVG/eltindex.html (and subpages)
|
||||
namespace dezentrale.model.svg
|
||||
{
|
||||
|
||||
public class SvgAnchor : SvgElement
|
||||
{
|
||||
[XmlAttribute] public string href { get; set; } = null;
|
||||
}
|
||||
/** \brief The location of the referenced object, expressed as an URL reference.
|
||||
* Refer to the common handling defined for URL reference attributes.*/
|
||||
[XmlAttribute] public string href { get; set; } = null;
|
||||
|
||||
/** \brief This attribute should be used when there are multiple possible targets
|
||||
* for the ending resource, such as when the parent document is embedded
|
||||
* within an HTML or XHTML document, or is viewed with a tabbed browser.
|
||||
* This attribute specifies the name of the browsing context (e.g., a
|
||||
* browser tab or an SVG, HTML, or XHTML iframe or object element) into
|
||||
* which a document is to be opened when the link is activated */
|
||||
[XmlAttribute] public string target { get; set; } = null;
|
||||
[XmlAttribute] public string download { get; set; } = null;
|
||||
[XmlAttribute] public string ping { get; set; } = null;
|
||||
[XmlAttribute] public string rel { get; set; } = null;
|
||||
[XmlAttribute] public string hreflang { get; set; } = null;
|
||||
[XmlAttribute] public string type { get; set; } = null;
|
||||
[XmlAttribute] public string referrerPolicy { get; set; } = null;
|
||||
}
|
||||
public class SvgCircle : SvgElement
|
||||
{
|
||||
[XmlAttribute] public string cx { get; set; } = null;
|
||||
[XmlAttribute] public string cy { get; set; } = null;
|
||||
[XmlAttribute] public string r { get; set; } = null;
|
||||
}
|
||||
public class SvgDefs : SvgElement
|
||||
{
|
||||
}
|
||||
public class SvgEllipse : SvgElement
|
||||
{
|
||||
[XmlAttribute] public string cx { get; set; } = null;
|
||||
[XmlAttribute] public string cy { get; set; } = null;
|
||||
[XmlAttribute] public string rx { get; set; } = null;
|
||||
[XmlAttribute] public string ry { get; set; } = null;
|
||||
}
|
||||
public class SvgGroup : SvgElement
|
||||
{
|
||||
}
|
||||
public class SvgPath : SvgElement
|
||||
public class SvgLine : SvgElement
|
||||
{
|
||||
[XmlAttribute] public string d { get; set; } = null;
|
||||
[XmlAttribute] public string x1 { get; set; } = null;
|
||||
[XmlAttribute] public string y1 { get; set; } = null;
|
||||
[XmlAttribute] public string x2 { get; set; } = null;
|
||||
[XmlAttribute] public string y2 { get; set; } = null;
|
||||
}
|
||||
|
||||
//helper methods for building the path ;-)
|
||||
public class SvgPolyline : SvgElement
|
||||
{
|
||||
[XmlText] public string Points { get; set; } = null;
|
||||
}
|
||||
public class SvgPolygon : SvgElement
|
||||
{
|
||||
[XmlText] public string Points { get; set; } = null;
|
||||
}
|
||||
public class SvgRect : SvgElement
|
||||
{
|
||||
[XmlAttribute] public string x { get; set; } = null;
|
||||
[XmlAttribute] public string y { get; set; } = null;
|
||||
[XmlAttribute] public string width { get; set; } = null;
|
||||
[XmlAttribute] public string x { get; set; } = null;
|
||||
[XmlAttribute] public string y { get; set; } = null;
|
||||
[XmlAttribute] public string width { get; set; } = null;
|
||||
[XmlAttribute] public string height { get; set; } = null;
|
||||
[XmlAttribute] public string rx { get; set; } = null;
|
||||
[XmlAttribute] public string ry { get; set; } = null;
|
||||
}
|
||||
public class SvgText : SvgElement
|
||||
{
|
||||
|
@ -39,9 +79,8 @@ namespace dezentrale.model.svg
|
|||
|
||||
public class SvgUse : SvgElement
|
||||
{
|
||||
[XmlAttribute( AttributeName = "href",
|
||||
Namespace = "http://www.w3.org/1999/xlink",
|
||||
Form = XmlSchemaForm.Qualified)]
|
||||
//TODO: Is there a better way, e.g. referencing to "xlink" instead of repeating the namespace URL?
|
||||
[XmlAttribute( AttributeName = "href", Namespace = "http://www.w3.org/1999/xlink", Form = XmlSchemaForm.Qualified)]
|
||||
public string href { get; set; } = null;
|
||||
}
|
||||
|
||||
|
@ -55,13 +94,19 @@ namespace dezentrale.model.svg
|
|||
[XmlAttribute("stroke-width")] public string stroke_width { get; set; } = null;
|
||||
[XmlAttribute("stroke-linejoin")] public string stroke_linejoin { get; set; } = null;
|
||||
|
||||
[XmlElement("a", typeof(SvgAnchor))]
|
||||
[XmlElement("defs", typeof(SvgDefs))] //It would be better to have defs as a separate svg section
|
||||
[XmlElement("g", typeof(SvgGroup))]
|
||||
[XmlElement("path", typeof(SvgPath))]
|
||||
[XmlElement("rect", typeof(SvgRect))]
|
||||
[XmlElement("text", typeof(SvgText))]
|
||||
[XmlElement("use", typeof(SvgUse))]
|
||||
//Todo: is it more efficient to sort these annotations by statistical probability?
|
||||
[XmlElement("a", typeof(SvgAnchor))]
|
||||
[XmlElement("circle", typeof(SvgCircle))]
|
||||
[XmlElement("defs", typeof(SvgDefs))] //It would be better to have defs as a separate svg section
|
||||
[XmlElement("ellipse", typeof(SvgEllipse))]
|
||||
[XmlElement("g", typeof(SvgGroup))]
|
||||
[XmlElement("line", typeof(SvgLine))]
|
||||
[XmlElement("path", typeof(SvgPath))]
|
||||
[XmlElement("polyline", typeof(SvgPolyline))]
|
||||
[XmlElement("polygon", typeof(SvgPolygon))]
|
||||
[XmlElement("rect", typeof(SvgRect))]
|
||||
[XmlElement("text", typeof(SvgText))]
|
||||
[XmlElement("use", typeof(SvgUse))]
|
||||
public List<SvgElement> Elements { get; set; } = new List<SvgElement>();
|
||||
}
|
||||
|
||||
|
@ -100,7 +145,7 @@ namespace dezentrale.model.svg
|
|||
else
|
||||
ser = new XmlSerializer(this.GetType());*/
|
||||
|
||||
XmlSerializer ser = new XmlSerializer(this.GetType(), "http://www.w3.org/2000/svg");
|
||||
XmlSerializer ser = new XmlSerializer(this.GetType(), "http://www.w3.org/2000/svg");
|
||||
|
||||
/*XmlWriterSettings settings = new XmlWriterSettings()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
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()
|
||||
{
|
||||
|
||||
}
|
||||
/** Tries to transform all contained path commands to absolute coordinate values */
|
||||
public void MakeRelative()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void AddCommand(SvgPathCommand cmd) { CommandList.Add(cmd); }
|
||||
|
||||
|
||||
/**\short translates all path coordinates by (dx, dy) */
|
||||
public void Translate(double dx, double dy)
|
||||
{
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
foreach (SvgPathCommand cmd in CommandList)
|
||||
{
|
||||
if(!cmd.Relative)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
/**\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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue