201205PX Added code for SVG path processing (Parsing and generating path strings)

This commit is contained in:
phantomix 2020-12-05 04:12:19 +01:00
parent 0cbe0545b1
commit 5e683475a6
3 changed files with 392 additions and 20 deletions

View File

@ -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" />

View File

@ -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()
{

326
model/svg/SvgPath.cs Normal file
View File

@ -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)
{
}
}
}