Commit d151315a authored by Tebjan Halm's avatar Tebjan Halm
Browse files

Merge pull request #75 from bcbarnes-gmx/Devel

fixed up the support for markers so that they draw properly, and added support for "preserveAspectRatio" except for the 'slice' and 'defer' options.
parents b3c305c8 3c1dd60e
using System;
using Svg.DataTypes;
using System.ComponentModel;
namespace Svg
......@@ -6,9 +6,10 @@ namespace Svg
/// <summary>
/// Description of SvgAspectRatio.
/// </summary>
[TypeConverter(typeof(SvgPreserveAspectRatioConverter))]
public class SvgAspectRatio
{
public SvgAspectRatio()
public SvgAspectRatio() : this(SvgPreserveAspectRatio.none)
{
}
......@@ -21,6 +22,7 @@ namespace Svg
{
this.Align = align;
this.Slice = slice;
this.Defer = false;
}
public SvgPreserveAspectRatio Align
......@@ -35,6 +37,12 @@ namespace Svg
set;
}
public bool Defer
{
get;
set;
}
public override string ToString()
{
return TypeDescriptor.GetConverter(typeof(SvgPreserveAspectRatio)).ConvertToString(this.Align) + (Slice ? " slice" : "");
......@@ -42,18 +50,17 @@ namespace Svg
}
[TypeConverter(typeof(SvgPreserverAspectRatioConverter))]
public enum SvgPreserveAspectRatio
{
XMidYMid, //default
None,
XMinYMin,
XMidYMin,
XMaxYMin,
XMinYMid,
XMaxYMid,
XMinYMax,
XMidYMax,
XMaxYMax
xMidYMid, //default
none,
xMinYMin,
xMidYMin,
xMaxYMin,
xMinYMid,
xMaxYMid,
xMinYMax,
xMidYMax,
xMaxYMax
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Svg.DataTypes
{
//implementaton for preserve aspect ratio
public sealed class SvgPreserveAspectRatioConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value == null)
{
return new SvgAspectRatio();
}
if (!(value is string))
{
throw new ArgumentOutOfRangeException("value must be a string.");
}
SvgPreserveAspectRatio eAlign = SvgPreserveAspectRatio.none;
bool bDefer = false;
bool bSlice = false;
string[] sParts = (value as string).Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
int nAlignIndex = 0;
if (sParts[0].Equals("defer"))
{
bDefer = true;
nAlignIndex++;
if(sParts.Length < 2)
throw new ArgumentOutOfRangeException("value is not a member of SvgPreserveAspectRatio");
}
if (!Enum.TryParse<SvgPreserveAspectRatio>(sParts[nAlignIndex], out eAlign))
throw new ArgumentOutOfRangeException("value is not a member of SvgPreserveAspectRatio");
nAlignIndex++;
if (sParts.Length > nAlignIndex)
{
switch (sParts[nAlignIndex])
{
case "meet":
break;
case "slice":
bSlice = true;
break;
default:
throw new ArgumentOutOfRangeException("value is not a member of SvgPreserveAspectRatio");
}
}
nAlignIndex++;
if(sParts.Length > nAlignIndex)
throw new ArgumentOutOfRangeException("value is not a member of SvgPreserveAspectRatio");
SvgAspectRatio pRet = new SvgAspectRatio(eAlign);
pRet.Slice = bSlice;
pRet.Defer = bDefer;
return (pRet);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
using System.ComponentModel;
namespace Svg.DataTypes
{
[TypeConverter(typeof(SvgMarkerUnitsConverter))]
public enum SvgMarkerUnits
{
strokeWidth,
userSpaceOnUse
}
}
using System;
using System.Collections.Generic;
using System.Text;
using Svg.DataTypes;
using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Globalization;
namespace Svg
{
/// <summary>
/// Represents an orientation in an Scalable Vector Graphics document.
/// </summary>
[TypeConverter(typeof(SvgOrientConverter))]
public class SvgOrient
{
private bool _isAuto = true;
......
using System;
using System.ComponentModel;
using System.Globalization;
namespace Svg.DataTypes
{
public sealed class SvgOrientConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value == null)
{
return new SvgUnit(SvgUnitType.User, 0.0f);
}
if (!(value is string))
{
throw new ArgumentOutOfRangeException("value must be a string.");
}
switch (value.ToString())
{
case "auto":
return (new SvgOrient());
default:
float fTmp = float.MinValue;
if(!float.TryParse(value.ToString(), out fTmp))
throw new ArgumentOutOfRangeException("value must be a valid float.");
return (new SvgOrient(fTmp));
}
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
......@@ -103,7 +103,7 @@ namespace Svg
[SvgAttribute("preserveAspectRatio")]
public SvgAspectRatio AspectRatio
{
get {return this.Attributes.GetAttribute<SvgAspectRatio>("preserveAspectRatio"); }
get { return this.Attributes.GetAttribute<SvgAspectRatio>("preserveAspectRatio"); }
set { this.Attributes["preserveAspectRatio"] = value; }
}
......@@ -117,10 +117,60 @@ namespace Svg
if (!this.ViewBox.Equals(SvgViewBox.Empty))
{
renderer.TranslateTransform(_x, _y, MatrixOrder.Append);
renderer.TranslateTransform(-this.ViewBox.MinX, -this.ViewBox.MinY, MatrixOrder.Append);
float fScaleX = this.Width.ToDeviceValue() / this.ViewBox.Width;
float fScaleY = this.Height.ToDeviceValue() / this.ViewBox.Height;
float fMinX = -this.ViewBox.MinX;
float fMinY = -this.ViewBox.MinY;
if (AspectRatio.Align != SvgPreserveAspectRatio.none)
{
fScaleX = Math.Min(fScaleX, fScaleY);
fScaleY = Math.Min(fScaleX, fScaleY);
float fViewMidX = (this.ViewBox.Width / 2) * fScaleX;
float fViewMidY = (this.ViewBox.Height / 2) * fScaleY;
float fMidX = this.Width.ToDeviceValue() / 2;
float fMidY = this.Height.ToDeviceValue() / 2;
switch (AspectRatio.Align)
{
case SvgPreserveAspectRatio.xMinYMin:
break;
case SvgPreserveAspectRatio.xMidYMin:
fMinX += (fMidX - fViewMidX) / fScaleX;
break;
case SvgPreserveAspectRatio.xMaxYMin:
fMinX += this.ViewBox.Width - this.Width.ToDeviceValue();
break;
case SvgPreserveAspectRatio.xMinYMid:
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMidYMid:
fMinX += (fMidX - fViewMidX) / fScaleX;
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMaxYMid:
fMinX += this.ViewBox.Width - this.Width.ToDeviceValue();
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMinYMax:
fMinY += this.ViewBox.Height - this.Height.ToDeviceValue();
break;
case SvgPreserveAspectRatio.xMidYMax:
fMinX += (fMidX - fViewMidX) / fScaleX;
fMinY += this.ViewBox.Height - this.Height.ToDeviceValue();
break;
case SvgPreserveAspectRatio.xMaxYMax:
fMinX += this.ViewBox.Width - this.Width.ToDeviceValue();
fMinY += this.ViewBox.Height - this.Height.ToDeviceValue();
break;
default:
break;
}
}
renderer.ScaleTransform(this.Width.ToDeviceValue() / this.ViewBox.Width, this.Height.ToDeviceValue() / this.ViewBox.Height, MatrixOrder.Append);
renderer.TranslateTransform(_x, _y, MatrixOrder.Append);
renderer.TranslateTransform(fMinX, fMinY, MatrixOrder.Append);
renderer.ScaleTransform(fScaleX, fScaleY, MatrixOrder.Append);
}
}
......@@ -162,7 +212,7 @@ namespace Svg
this.Height = new SvgUnit(SvgUnitType.Percentage, 100.0f);
this.Width = new SvgUnit(SvgUnitType.Percentage, 100.0f);
this.ViewBox = SvgViewBox.Empty;
this.AspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.None);
this.AspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid);
}
......
using System;
using Svg.DataTypes;
using System;
using System.ComponentModel;
using System.Globalization;
......@@ -108,11 +109,6 @@ namespace Svg
{
}
//implementaton for preserve aspect ratio
public sealed class SvgPreserverAspectRatioConverter : EnumBaseConverter<SvgPreserveAspectRatio>
{
}
public sealed class SvgStrokeLineCapConverter : EnumBaseConverter<SvgStrokeLineCap>
{
}
......@@ -121,4 +117,7 @@ namespace Svg
{
}
public sealed class SvgMarkerUnitsConverter : EnumBaseConverter<SvgMarkerUnits>
{
}
}
......@@ -6,6 +6,8 @@ using System.Web;
using System.Xml;
using System.Xml.Serialization;
using System.Drawing.Drawing2D;
using System.Drawing;
using Svg.DataTypes;
namespace Svg
{
......@@ -61,6 +63,35 @@ namespace Svg
}
[SvgAttribute("markerWidth")]
public virtual SvgUnit MarkerWidth
{
get { return this.Attributes.GetAttribute<SvgUnit>("markerWidth"); }
set { this.Attributes["markerWidth"] = value; }
}
[SvgAttribute("markerHeight")]
public virtual SvgUnit MarkerHeight
{
get { return this.Attributes.GetAttribute<SvgUnit>("markerHeight"); }
set { this.Attributes["markerHeight"] = value; }
}
[SvgAttribute("markerUnits")]
public virtual SvgMarkerUnits MarkerUnits
{
get { return this.Attributes.GetAttribute<SvgMarkerUnits>("markerUnits"); }
set { this.Attributes["markerUnits"] = value; }
}
public SvgMarker()
{
MarkerUnits = SvgMarkerUnits.strokeWidth;
MarkerHeight = 3;
MarkerWidth = 3;
Overflow = SvgOverflow.hidden;
}
public override System.Drawing.Drawing2D.GraphicsPath Path
{
get
......@@ -87,19 +118,6 @@ namespace Svg
}
}
//protected internal override void RenderStroke(SvgRenderer renderer)
//{
// this.PushTransforms(renderer);
// SvgElement parent = element._parent;
// element._parent = this;
// element.RenderElement(renderer);
// element._parent = parent;
// this.PopTransforms(renderer);
//}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgMarker>();
......@@ -117,5 +135,148 @@ namespace Svg
return newObj;
}
/// <summary>
/// Render this marker using the slope of the given line segment
/// </summary>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint1"></param>
/// <param name="pMarkerPoint2"></param>
public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2)
{
float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
float fAngle1 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
RenderPart2(fAngle1, pRenderer, pOwner, pRefPoint);
}
/// <summary>
/// Render this marker using the average of the slopes of the two given line segments
/// </summary>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint1"></param>
/// <param name="pMarkerPoint2"></param>
/// <param name="pMarkerPoint3"></param>
public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3)
{
float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
float fAngle1 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
xDiff = pMarkerPoint3.X - pMarkerPoint2.X;
yDiff = pMarkerPoint3.Y - pMarkerPoint2.Y;
float fAngle2 = (float)(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI);
RenderPart2((fAngle1 + fAngle2) / 2, pRenderer, pOwner, pRefPoint);
}
/// <summary>
/// Common code for rendering a marker once the orientation angle has been calculated
/// </summary>
/// <param name="fAngle"></param>
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint"></param>
private void RenderPart2(float fAngle, SvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint)
{
Pen pRenderPen = CreatePen(pOwner);
GraphicsPath markerPath = GetClone(pOwner);
Matrix transMatrix = new Matrix();
transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y);
if (Orient.IsAuto)
transMatrix.Rotate(fAngle);
else
transMatrix.Rotate(Orient.Angle);
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
transMatrix.Translate(AdjustForViewBoxWidth(-RefX * pOwner.StrokeWidth), AdjustForViewBoxHeight(-RefY * pOwner.StrokeWidth));
break;
case SvgMarkerUnits.userSpaceOnUse:
transMatrix.Translate(-RefX, -RefY);
break;
}
markerPath.Transform(transMatrix);
pRenderer.DrawPath(pRenderPen, markerPath);
SvgPaintServer pFill = Fill;
SvgFillRule pFillRule = FillRule; // TODO: What do we use the fill rule for?
float fOpacity = FillOpacity;
if (pFill != null)
{
Brush pBrush = pFill.GetBrush(this, fOpacity);
pRenderer.FillPath(pBrush, markerPath);
pBrush.Dispose();
}
pRenderPen.Dispose();
markerPath.Dispose();
transMatrix.Dispose();
}
/// <summary>
/// Create a pen that can be used to render this marker
/// </summary>
/// <param name="pStroke"></param>
/// <returns></returns>
private Pen CreatePen(SvgPath pPath)
{
Brush pBrush = pPath.Stroke.GetBrush(this, Opacity);
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
return (new Pen(pBrush, StrokeWidth * pPath.StrokeWidth));
case SvgMarkerUnits.userSpaceOnUse:
return (new Pen(pBrush, StrokeWidth));
}
return (new Pen(pBrush, StrokeWidth));
}
/// <summary>
/// Get a clone of the current path, scaled for the stroke with
/// </summary>
/// <returns></returns>
private GraphicsPath GetClone(SvgPath pPath)
{
GraphicsPath pRet = Path.Clone() as GraphicsPath;
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
Matrix transMatrix = new Matrix();
transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth));
pRet.Transform(transMatrix);
break;
case SvgMarkerUnits.userSpaceOnUse:
break;
}
return (pRet);
}
/// <summary>
/// Adjust the given value to account for the width of the viewbox in the viewport
/// </summary>
/// <param name="fWidth"></param>
/// <returns></returns>
private float AdjustForViewBoxWidth(float fWidth)
{
// TODO: We know this isn't correct
return (fWidth / ViewBox.Width);
}
/// <summary>
/// Adjust the given value to account for the height of the viewbox in the viewport
/// </summary>
/// <param name="fWidth"></param>
/// <returns></returns>
private float AdjustForViewBoxHeight(float fHeight)
{
// TODO: We know this isn't correct
return (fHeight / ViewBox.Height);
}
}
}
\ No newline at end of file
......@@ -56,6 +56,17 @@ namespace Svg
}
/// <summary>
/// Gets or sets the marker (start cap) of the path.
/// </summary>
[SvgAttribute("marker-mid")]
public Uri MarkerMid
{
get { return this.Attributes.GetAttribute<Uri>("marker-mid"); }
set { this.Attributes["marker-mid"] = value; }
}
/// <summary>
/// Gets or sets the marker (start cap) of the path.
/// </summary>
......@@ -135,7 +146,7 @@ namespace Svg
if (this.Stroke != null)
{
float strokeWidth = this.StrokeWidth.ToDeviceValue(this);
using (var pen = new Pen(this.Stroke.GetBrush(this, this.StrokeOpacity), strokeWidth))
using (Pen pen = new Pen(this.Stroke.GetBrush(this, this.StrokeOpacity), strokeWidth))
{
if (this.StrokeDashArray != null && this.StrokeDashArray.Count > 0)
{
......@@ -143,32 +154,29 @@ namespace Svg
pen.DashPattern = this.StrokeDashArray.ConvertAll(u => u.Value / ((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray();
}
//hardcoded transformation matrix. I am not sure why this is not in proportion or rotated correctly (something to do with how the endcaps are determined in GDI)
var transMatrix = new Matrix();
transMatrix.Rotate(-90f);
transMatrix.Scale(.6f, .6f);
renderer.DrawPath(pen, this.Path);
if (this.MarkerStart != null)
{
var marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerStart.ToString());
var markerPath = marker.Path.Clone() as GraphicsPath;
markerPath.Transform(transMatrix);
pen.CustomStartCap = new CustomLineCap(markerPath, null);
SvgMarker marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerStart.ToString());
marker.RenderMarker(renderer, this, Path.PathPoints[0], Path.PathPoints[0], Path.PathPoints[1]);
}
if (this.MarkerEnd != null)
if (this.MarkerMid != null)
{
var marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerEnd.ToString());
var markerPath = marker.Path.Clone() as GraphicsPath;
markerPath.Transform(transMatrix);
pen.CustomEndCap = new CustomLineCap(markerPath, null);
SvgMarker marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerMid.ToString());
for (int i = 1; i <= Path.PathPoints.Length - 2; i++)
marker.RenderMarker(renderer, this, Path.PathPoints[i], Path.PathPoints[i - 1], Path.PathPoints[i], Path.PathPoints[i + 1]);
}
renderer.DrawPath(pen, this.Path);
if (this.MarkerEnd != null)
{
SvgMarker marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerEnd.ToString());
marker.RenderMarker(renderer, this, Path.PathPoints[Path.PathPoints.Length - 1], Path.PathPoints[Path.PathPoints.Length - 2], Path.PathPoints[Path.PathPoints.Length - 1]);
}
}
}
}
public override SvgElement DeepCopy()
{
......@@ -186,9 +194,5 @@ namespace Svg
return newObj;
}
}
}
\ No newline at end of file
......@@ -99,6 +99,8 @@
<Compile Include="Clipping and Masking\SvgClipRule.cs" />
<Compile Include="Clipping and Masking\SvgClipPath.cs" />
<Compile Include="Clipping and Masking\SvgMask.cs" />
<Compile Include="DataTypes\SvgAspectRatioConverter.cs" />
<Compile Include="DataTypes\SvgMarkerUnits.cs" />
<Compile Include="DataTypes\SvgOrient.cs" />
<Compile Include="DataTypes\ISvgViewPort.cs" />
<Compile Include="DataTypes\SvgAspectRatio.cs" />
......@@ -106,6 +108,7 @@
<Compile Include="DataTypes\SvgElementStyle.cs" />
<Compile Include="DataTypes\SvgCoordinateUnits.cs" />
<Compile Include="DataTypes\SvgFontWeight.cs" />
<Compile Include="DataTypes\SvgOrientConverter.cs" />
<Compile Include="DataTypes\SvgOverflow.cs" />
<Compile Include="DataTypes\SvgUnitCollection.cs" />
<Compile Include="DataTypes\SvgViewBox.cs" />
......
......@@ -26,7 +26,7 @@ namespace Svg
/// </summary>
public SvgDocument()
{
Ppi = 96;
Ppi = PointsPerInch;
}
/// <summary>
......
......@@ -72,6 +72,16 @@ namespace Svg
this._innerGraphics.DrawPath(pen, path);
}
public void RotateTransform(float fAngle, MatrixOrder order)
{
this._innerGraphics.RotateTransform(fAngle, order);
}
public void RotateTransform(float fAngle)
{
this.RotateTransform(fAngle, MatrixOrder.Append);
}
public void TranslateTransform(float dx, float dy, MatrixOrder order)
{
this._innerGraphics.TranslateTransform(dx, dy, order);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment