using FastReport.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Linq;
using System.Text;
namespace FastReport
{
///
/// Represents a poly line object.
///
///
/// Use the Border.Width, Border.Style and Border.Color properties to set
/// the line width, style and color.
///
public partial class PolyLineObject : ReportComponentBase
{
#region Protected Internal Fields
///
/// do not set this value, internal use only
///
protected internal PolygonSelectionMode polygonSelectionMode;
#endregion Protected Internal Fields
#region Private Fields
private PointF center;
private PolyPointCollection pointsCollection;
private FloatCollection dashPattern;
#endregion Private Fields
#region Public Properties
///
/// Gets or sets collection of values for custom dash pattern.
///
///
/// Each element should be a non-zero positive number.
/// If the number is negative or zero, that number is replaced by one.
///
[Category("Appearance")]
public FloatCollection DashPattern
{
get { return dashPattern; }
set { dashPattern = value; }
}
///
/// Return points collection.
/// You can modify the collection for change this object.
///
public PolyPointCollection Points
{
get
{
return pointsCollection;
}
}
///
/// Returns origin of coordinates relative to the top left corner
///
[Browsable(false)]
public float CenterX { get { return center.X; } set { center.X = value; } }
///
/// Returns origin of coordinates relative to the top left corner
///
[Browsable(false)]
public float CenterY { get { return center.Y; } set { center.Y = value; } }
///
/// Return points array of line
/// deprecated
///
[Browsable(false)]
[Obsolete]
public PointF[] PointsArray
{
get
{
List result = new List();
foreach (PolyPoint point in pointsCollection)
{
result.Add(new PointF(point.X, point.Y));
}
return result.ToArray();
}
}
///
/// Return point types array. 0 - Start of line, 1 - Keep on line
/// deprecated
///
[Browsable(false)]
[Obsolete]
public byte[] PointTypesArray
{
get
{
List result = new List();
result.Add(0);
for (int i = 1; i < pointsCollection.Count; i++)
result.Add(1);
return result.ToArray();
}
}
#endregion Public Properties
#region Public Constructors
///
/// Initializes a new instance of the class with default settings.
///
public PolyLineObject()
{
FlagSimpleBorder = true;
FlagUseFill = false;
pointsCollection = new PolyPointCollection();
center = PointF.Empty;
dashPattern = new FloatCollection();
InitDesign();
}
#endregion Public Constructors
#region Public Methods
///
public override void Assign(Base source)
{
base.Assign(source);
PolyLineObject src = source as PolyLineObject;
pointsCollection = src.pointsCollection.Clone();
center = src.center;
DashPattern.Assign(src.DashPattern);
//recalculateBounds();
}
///
public override void Deserialize(FRReader reader)
{
base.Deserialize(reader);
pointsCollection.Clear();
if (reader.HasProperty("PolyPoints"))
{
string polyPoints = reader.ReadStr("PolyPoints");
foreach (string str in polyPoints.Split('|'))
{
string[] point = str.Split('\\');
if (point.Length == 3)
{
float f1 = float.Parse(point[0].Replace(',', '.'), CultureInfo.InvariantCulture);
float f2 = float.Parse(point[1].Replace(',', '.'), CultureInfo.InvariantCulture);
pointsCollection.Add(new PolyPoint(f1, f2));
}
}
}
else if (reader.HasProperty("PolyPoints_v2"))
{
string polyPoints = reader.ReadStr("PolyPoints_v2");
foreach (string str in polyPoints.Split('|'))
{
PolyPoint point = new PolyPoint();
point.Deserialize(str);
pointsCollection.Add(point);
}
}
if (reader.HasProperty("CenterX"))
center.X = reader.ReadFloat("CenterX");
if (reader.HasProperty("CenterY"))
center.Y = reader.ReadFloat("CenterY");
//recalculateBounds();
}
///
public override void Draw(FRPaintEventArgs e)
{
switch (pointsCollection.Count)
{
case 0:
case 1:
IGraphics g = e.Graphics;
float x = AbsLeft + CenterX;
float y = AbsTop + CenterY;
if (pointsCollection.Count == 1)
{
x += pointsCollection[0].X;
y += pointsCollection[0].Y;
}
g.DrawLine(Pens.Black, x * e.ScaleX - 6, y * e.ScaleY, x * e.ScaleX + 6, y * e.ScaleY);
g.DrawLine(Pens.Black, x * e.ScaleX, y * e.ScaleY - 6, x * e.ScaleX, y * e.ScaleY + 6);
break;
default:
DoDrawPoly(e);
DrawDesign0(e);
break;
}
DrawDesign1(e);
}
///
/// Calculate GraphicsPath for draw to page
///
/// Pen for lines
/// Left boundary
/// Top boundary
/// Right boundary
/// Bottom boundary
/// scale by width
/// scale by height
/// Always returns a non-empty path
public GraphicsPath GetPath(Pen pen, float left, float top, float right, float bottom, float scaleX, float scaleY)
{
if (pointsCollection.Count == 0)
{
GraphicsPath result = new GraphicsPath();
result.AddLine(left * scaleX, top * scaleX, (right + 1) * scaleX, (bottom + 1) * scaleX);
return result;
}
else if (pointsCollection.Count == 1)
{
GraphicsPath result = new GraphicsPath();
left = left + CenterX + pointsCollection[0].X;
top = top + CenterY + pointsCollection[0].Y;
result.AddLine(left * scaleX, top * scaleX, (left + 1) * scaleX, (top + 1) * scaleX);
return result;
}
List aPoints = new List();
List pointTypes = new List();
PolyPoint prev = null;
PolyPoint point = pointsCollection[0];
aPoints.Add(new PointF((point.X + left + center.X) * scaleX, (point.Y + top + center.Y) * scaleY));
pointTypes.Add(0);
int count = pointsCollection.Count;
if (this is PolygonObject)
{
count++;
}
for (int i = 1; i < count; i++)
{
prev = point;
point = pointsCollection[i];
//is bezier?
if (prev.RightCurve != null || point.LeftCurve != null)
{
if (prev.RightCurve != null)
{
aPoints.Add(new PointF((prev.X + left + center.X + prev.RightCurve.X) * scaleX, (prev.Y + top + center.Y + prev.RightCurve.Y) * scaleY));
pointTypes.Add(3);
}
else
{
PolyPoint pseudo = GetPseudoPoint(prev, point);
aPoints.Add(new PointF((pseudo.X + left + center.X) * scaleX, (pseudo.Y + top + center.Y) * scaleY));
pointTypes.Add(3);
}
if (point.LeftCurve != null)
{
aPoints.Add(new PointF((point.X + left + center.X + point.LeftCurve.X) * scaleX, (point.Y + top + center.Y + point.LeftCurve.Y) * scaleY));
pointTypes.Add(3);
}
else
{
PolyPoint pseudo = GetPseudoPoint(point, prev);
aPoints.Add(new PointF((pseudo.X + left + center.X) * scaleX, (pseudo.Y + top + center.Y) * scaleY));
pointTypes.Add(3);
}
aPoints.Add(new PointF((point.X + left + center.X) * scaleX, (point.Y + top + center.Y) * scaleY));
pointTypes.Add(3);
}
else
{
aPoints.Add(new PointF((point.X + left + center.X) * scaleX, (point.Y + top + center.Y) * scaleY));
pointTypes.Add(1);
}
}
return new GraphicsPath(aPoints.ToArray(), pointTypes.ToArray());
}
///
/// Recalculate position and size of element
///
public void RecalculateBounds()
{
if (pointsCollection.Count > 0)
{
// init
PolyPoint prev = null;
PolyPoint point = pointsCollection[0];
float left = point.X;
float top = point.Y;
float right = point.X;
float bottom = point.Y;
int count = pointsCollection.Count;
if (this is PolygonObject)
count++;
// stage 1 calculate min bounds
foreach (PolyPoint pnt in pointsCollection)
{
if (pnt.X < left)
left = pnt.X;
else if (pnt.X > right)
right = pnt.X;
if (pnt.Y < top)
top = pnt.Y;
else if (pnt.Y > bottom)
bottom = pnt.Y;
}
// stage 2 check if one of bezier way point is outside
for (int i = 1; i < count; i++)
{
prev = point;
point = pointsCollection[i];
bool haveToCalculate = false;
PolyPoint p_1 = null;
PolyPoint p_2 = null;
if (prev.RightCurve != null)
{
p_1 = new PolyPoint(prev.X + prev.RightCurve.X, prev.Y + prev.RightCurve.Y);
if (p_1.X < left)
haveToCalculate = true;
else if (p_1.X > right)
haveToCalculate = true;
if (p_1.Y < top)
haveToCalculate = true;
else if (p_1.Y > bottom)
haveToCalculate = true;
}
if (point.LeftCurve != null)
{
p_2 = new PolyPoint(point.X + point.LeftCurve.X, point.Y + point.LeftCurve.Y);
if (p_2.X < left)
haveToCalculate = true;
else if (p_2.X > right)
haveToCalculate = true;
if (p_2.Y < top)
haveToCalculate = true;
else if (p_2.Y > bottom)
haveToCalculate = true;
}
if (haveToCalculate)
{
if (p_1 == null)
p_1 = GetPseudoPoint(prev, point);
if (p_2 == null)
p_2 = GetPseudoPoint(point, prev);
// now calculate extrema
// x
float delta = RecalculateBounds_Delta(prev.X, p_1.X, p_2.X, point.X);
if (delta > 0)
{
delta = (float)Math.Sqrt(delta);
float t_1 = RecalculateBounds_Solve(prev.X, p_1.X, p_2.X, point.X, -delta);
if (0 < t_1 && t_1 < 1)
{
float x = RecalculateBounds_Value(prev.X, p_1.X, p_2.X, point.X, t_1);
if (x < left)
left = x;
else if (x > right)
right = x;
}
float t_2 = RecalculateBounds_Solve(prev.X, p_1.X, p_2.X, point.X, delta);
if (0 < t_2 && t_2 < 1)
{
float x = RecalculateBounds_Value(prev.X, p_1.X, p_2.X, point.X, t_2);
if (x < left)
left = x;
else if (x > right)
right = x;
}
}
// y
delta = RecalculateBounds_Delta(prev.Y, p_1.Y, p_2.Y, point.Y);
if (delta > 0)
{
delta = (float)Math.Sqrt(delta);
float t_1 = RecalculateBounds_Solve(prev.Y, p_1.Y, p_2.Y, point.Y, -delta);
if (0 < t_1 && t_1 < 1)
{
float y = RecalculateBounds_Value(prev.Y, p_1.Y, p_2.Y, point.Y, t_1);
if (y < top)
top = y;
else if (y > bottom)
bottom = y;
}
float t_2 = RecalculateBounds_Solve(prev.Y, p_1.Y, p_2.Y, point.Y, delta);
if (0 < t_2 && t_2 < 1)
{
float y = RecalculateBounds_Value(prev.Y, p_1.Y, p_2.Y, point.Y, t_2);
if (y < top)
top = y;
else if (y > bottom)
bottom = y;
}
}
}
}
// update
float centerX = center.X;
float centerY = center.Y;
center.X = -left;
center.Y = -top;
base.Left += left + centerX;
base.Top += top + centerY;
base.Height = bottom - top;
base.Width = right - left;
}
else
{
CenterX = 0;
CenterY = 0;
base.Width = 5;
base.Height = 5;
}
}
private float RecalculateBounds_Delta(float p_0, float p_1, float p_2, float p_3)
{
return p_1 * p_1 - p_0 * p_2 - p_1 * p_2 + p_2 * p_2 + p_0 * p_3 - p_1 * p_3;
}
private float RecalculateBounds_Solve(float p_0, float p_1, float p_2, float p_3, float deltaSqrt)
{
return (p_0 - 2 * p_1 + p_2 + deltaSqrt) / (p_0 - 3 * p_1 + 3 * p_2 - p_3);
}
private float RecalculateBounds_Value(float p_0, float p_1, float p_2, float p_3, float t)
{
float t1 = 1 - t;
return p_0 * t1 * t1 * t1 + 3 * p_1 * t1 * t1 * t + 3 * p_2 * t1 * t * t + p_3 * t * t * t;
}
///
public override void Serialize(FRWriter writer)
{
Border.SimpleBorder = true;
base.Serialize(writer);
PolyLineObject c = writer.DiffObject as PolyLineObject;
StringBuilder sb = new StringBuilder(pointsCollection.Count * 10);
foreach (PolyPoint point in pointsCollection)
{
point.Serialize(sb);
sb.Append("|");
}
if (sb.Length > 0)
sb.Length--;
writer.WriteStr("PolyPoints_v2", sb.ToString());
writer.WriteFloat("CenterX", center.X);
writer.WriteFloat("CenterY", center.Y);
if (DashPattern?.Count > 0)
writer.WriteValue("DashPattern", DashPattern);
}
public void SetPolyLine(PointF[] newPoints)
{
pointsCollection.Clear();
if (newPoints != null)
{
CenterX = 0;
CenterY = 0;
foreach (PointF point in newPoints)
{
pointsCollection.Add(new PolyPoint(point.X, point.Y));
}
}
float l = Left;
float t = Top;
RecalculateBounds();
Left = l;
Top = t;
}
#endregion Public Methods
#region Internal Methods
internal void DoDrawPoly(FRPaintEventArgs e)
{
IGraphics g = e.Graphics;
Report report = Report;
if (report != null && report.SmoothGraphics)
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.AntiAlias;
}
drawPoly(e);
if (report != null && report.SmoothGraphics)
{
g.InterpolationMode = InterpolationMode.Default;
g.SmoothingMode = SmoothingMode.Default;
}
}
#endregion Internal Methods
#region Protected Methods
///
/// Add point to end of polyline, need to recalculate bounds after add
/// First point must have zero coordinate and zero type.
/// Recalculate bounds.
/// Method is slow do not use this.
///
/// local x - relative to left-top point
/// local y - relative to left-top point
/// depreceted
protected PolyPoint addPoint(float localX, float localY, byte pointType)
{
PolyPoint result;
pointsCollection.Add(result = new PolyPoint(localX, localY));
RecalculateBounds();
return result;
}
///
/// Delete point from polyline by index.
/// Recalculate bounds.
/// Method is slow do not use this.
///
/// Index of point in polyline
protected void deletePoint(int index)
{
pointsCollection.Remove(index);
RecalculateBounds();
}
///
/// Draw polyline path to graphics
///
/// Event arguments
protected virtual void drawPoly(FRPaintEventArgs e)
{
Pen pen;
if (polygonSelectionMode == PolygonSelectionMode.MoveAndScale)
pen = e.Cache.GetPen(Border.Color, Border.Width * e.ScaleX, Border.DashStyle);
else pen = e.Cache.GetPen(Border.Color, 1, DashStyle.Solid);
DrawUtils.SetPenDashPatternOrStyle(DashPattern, pen, Border);
using (GraphicsPath path = GetPath(pen, AbsLeft, AbsTop, AbsRight, AbsBottom, e.ScaleX, e.ScaleY))
e.Graphics.DrawPath(pen, path);
}
///
/// Insert point to desired place of polyline
/// recalculateBounds();
/// Method is slow do not use this
///
/// Index of place from zero to count
/// local x - relative to left-top point
/// local y - relative to left-top point
/// deprecated
protected PolyPoint insertPoint(int index, float localX, float localY, byte pointType)
{
PolyPoint result;
pointsCollection.Insert(index, result = new PolyPoint(localX, localY));
RecalculateBounds();
return result;
}
#endregion Protected Methods
#region Private Methods
private float getDistance(float px, float py, float px0, float py0, float px1, float py1, out int index)
{
float vx = px1 - px0;
float vy = py1 - py0;
float wx = px - px0;
float wy = py - py0;
float c1 = vx * wx + vy * wy;
if (c1 <= 0) { index = -1; return (px0 - px) * (px0 - px) + (py0 - py) * (py0 - py); }
float c2 = vx * vx + vy * vy;
if (c2 <= c1) { index = 1; return (px1 - px) * (px1 - px) + (py1 - py) * (py1 - py); }
float b = c1 / c2;
index = 0;
float bx = px0 + vx * b;
float by = py0 + vy * b;
return (bx - px) * (bx - px) + (by - py) * (by - py);
}
private PolyPoint GetPseudoPoint(PolyPoint start, PolyPoint end)
{
float vecX = end.X - start.X;
float vecY = end.Y - start.Y;
float distance = (float)Math.Sqrt(vecX * vecX + vecY * vecY);
vecX = vecX / 3;
vecY = vecY / 3;
return new PolyPoint(start.X + vecX, start.Y + vecY);
}
#endregion Private Methods
#region Protected Internal Enums
protected internal enum PolygonSelectionMode : int
{
MoveAndScale,
Normal,
AddToLine,
AddBezier,
Delete
}
#endregion Protected Internal Enums
#region Public Classes
///
/// Represent a point for polygon object
///
public class PolyPoint
{
#region Private Fields
private static readonly NumberFormatInfo invariant;
private PolyPoint left;
private PolyPoint right;
private float x;
private float y;
#endregion Private Fields
#region Public Properties
public PolyPoint LeftCurve
{
get { return left; }
set { left = value; }
}
public PolyPoint RightCurve
{
get { return right; }
set { right = value; }
}
public float X
{
get { return x; }
set { x = value; }
}
public float Y
{
get { return y; }
set { y = value; }
}
#endregion Public Properties
#region Public Constructors
static PolyPoint()
{
invariant = new NumberFormatInfo();
invariant.NumberGroupSeparator = String.Empty;
invariant.NumberDecimalSeparator = ".";
}
public PolyPoint()
{
}
public PolyPoint(float x, float y)
{
this.x = x;
this.y = y;
}
#endregion Public Constructors
#region Public Methods
public void Deserialize(string s)
{
string[] strs = s.Split('/');
int index = 0;
Deserialize(strs, ref index);
if (index < strs.Length)
{
if (strs[index] == "L")
{
index++;
LeftCurve = new PolyPoint();
LeftCurve.Deserialize(strs, ref index);
}
if (index < strs.Length)
{
if (strs[index] == "R")
{
index++;
RightCurve = new PolyPoint();
RightCurve.Deserialize(strs, ref index);
}
}
}
}
public bool Near(PolyPoint p)
{
return (p != null) && (Math.Abs(x - p.x) < 0.0001) && (Math.Abs(y - p.y) < 0.0001);
}
public void ScaleX(float scale)
{
x *= scale;
if (LeftCurve != null)
LeftCurve.X *= scale;
if (RightCurve != null)
RightCurve.X *= scale;
}
public void ScaleY(float scale)
{
y *= scale;
if (LeftCurve != null)
LeftCurve.Y *= scale;
if (RightCurve != null)
RightCurve.Y *= scale;
}
public void Serialize(StringBuilder sb)
{
sb.Append(Round(x)).Append("/").Append(Round(y));
if (LeftCurve != null)
{
sb.Append("/L/").Append(Round(LeftCurve.X)).Append("/").Append(Round(LeftCurve.Y));
}
if (RightCurve != null)
{
sb.Append("/R/").Append(Round(RightCurve.X)).Append("/").Append(Round(RightCurve.Y));
}
}
public override string ToString()
{
return "(" + Round(x) + ";" + Round(y) + ")";
}
#endregion Public Methods
#region Private Methods
private void Deserialize(string[] strs, ref int index)
{
for (int i = 0; i < 2 && index < strs.Length; i++)
{
switch (i)
{
case 0:
Single.TryParse(strs[index], NumberStyles.Float, invariant, out x);
break;
case 1:
Single.TryParse(strs[index], NumberStyles.Float, invariant, out y);
break;
}
index++;
}
}
private string Round(float value)
{
return Convert.ToString(Math.Round(value, 4), invariant);
}
internal PolyPoint Clone()
{
PolyPoint result = new PolyPoint(x, y);
if (LeftCurve != null)
result.LeftCurve = LeftCurve.Clone();
if (RightCurve != null)
result.RightCurve = RightCurve.Clone();
return result;
}
#endregion Private Methods
}
public class PolyPointCollection : IEnumerable
{
#region Private Fields
private List points;
#endregion Private Fields
#region Public Indexers
public PolyPoint this[int index]
{
get
{
index = NormalizeIndex(index);
return points[index];
}
set
{
index = NormalizeIndex(index);
points[index] = value;
}
}
#endregion Public Indexers
#region Public Properties
public int Count
{
get
{
return points.Count;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
#endregion Public Properties
#region Public Constructors
public PolyPointCollection()
{
points = new List();
}
#endregion Public Constructors
#region Public Methods
public void Add(PolyPoint item)
{
points.Add(item);
}
public void Clear()
{
points.Clear();
}
public PolyPointCollection Clone()
{
PolyPointCollection result = new PolyPointCollection();
result.points = new List();
foreach (PolyPoint point in points)
{
result.points.Add(point.Clone());
}
return result;
}
public IEnumerator GetEnumerator()
{
return points.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return points.GetEnumerator();
}
public int IndexOf(PolyPoint currentPoint)
{
return points.IndexOf(currentPoint);
}
public void Insert(int index, PolyPoint item)
{
int count = points.Count;
if (count > 0)
{
while (index < 0)
index += count;
while (index > count)
index -= count;
}
points.Insert(index, item);
}
public void Remove(int index)
{
index = NormalizeIndex(index);
points.RemoveAt(index);
}
#endregion Public Methods
#region Private Methods
private int NormalizeIndex(int index)
{
int count = points.Count;
if (count == 0)
return 0;
if (index >= 0 && index < count)
return index;
while (index < 0)
index += count;
while (index >= count)
index -= count;
return index;
}
#endregion Private Methods
}
#endregion Public Classes
}
}