367 lines
12 KiB
C#
367 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Drawing.Imaging;
|
|
using CSLA;
|
|
using System.IO;
|
|
|
|
namespace GZTW.AyaNova.BLL
|
|
{
|
|
/// <summary>
|
|
/// AyaNova captures and stores signatures as vector data,
|
|
/// this class is used to parse the captured signature data
|
|
/// for various admin info stored with it as well as generate
|
|
/// an image for use in reporting or displaying in UI.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class AySignature
|
|
{
|
|
|
|
private string mSignatureCode = "";
|
|
|
|
/// <summary>
|
|
/// Raw signature code stored by client software as captured
|
|
/// </summary>
|
|
public string SignatureCode {
|
|
get
|
|
{
|
|
return mSignatureCode;
|
|
|
|
}
|
|
set
|
|
{
|
|
if (mSignatureCode != value)
|
|
{
|
|
mSignatureCode = value;
|
|
Parse();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signature polyline stroke line paths in x,y coordinate pairs.
|
|
/// </summary>
|
|
public string StrokePath { get; set; }
|
|
|
|
/// <summary>
|
|
/// Width of signature pade area in pixels
|
|
/// Defined because different devices may require different
|
|
/// sized signature areas
|
|
/// </summary>
|
|
public int Width { get; set; }
|
|
|
|
/// <summary>
|
|
/// Height of signature pade area in pixels
|
|
/// Defined because different devices may require different
|
|
/// sized signature areas
|
|
/// </summary>
|
|
public int Height { get; set; }
|
|
|
|
/// <summary>
|
|
/// Date time at client device at moment captured signature was started drawing by end user
|
|
/// </summary>
|
|
public DateTime ClientCapturedDateTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Date time at server at moment captured signature was saved
|
|
/// </summary>
|
|
public SmartDate HostCapturedDateTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Version of signature code schema in case new features or SignatureCode changes are required in future and
|
|
/// </summary>
|
|
public string Version { get; set; }
|
|
|
|
/// <summary>
|
|
/// Strokepath is not empty and contains at least two sets of points
|
|
/// </summary>
|
|
public bool HasSignature
|
|
{
|
|
get
|
|
{
|
|
if (string.IsNullOrWhiteSpace(StrokePath)) return false;
|
|
int nOccurs = AyaBizUtils.StringOccurrences(StrokePath, ",");
|
|
return (nOccurs > 1);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Strokepath is not empty
|
|
/// (This is obviously not definitive, but a strong indication the user drew more than a dot or a short line,
|
|
/// Of course nothing can certify it's a valid legal signature other than a signature expert or witnesses. :) )
|
|
/// </summary>
|
|
public bool IsALikelySignature
|
|
{
|
|
get
|
|
{
|
|
if (string.IsNullOrWhiteSpace(StrokePath)) return false;
|
|
int nOccurs = AyaBizUtils.StringOccurrences(StrokePath, ",");
|
|
return (nOccurs > 1);
|
|
}
|
|
}
|
|
|
|
Bitmap bmSig = null;
|
|
|
|
/// <summary>
|
|
/// Converts strokepath to bitmap object
|
|
/// same size as signature box used to sign
|
|
/// in first place.
|
|
/// </summary>
|
|
public Bitmap SignatureBitmap
|
|
{
|
|
get
|
|
{
|
|
//case 1704
|
|
if (bmSig == null && HasSignature)
|
|
GenerateBitmap();
|
|
return bmSig;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// SignatureBitmap converted to byte array which is
|
|
/// a more friendly format for some uses
|
|
/// </summary>
|
|
public byte[] SignatureBitmapAsByteArray
|
|
{
|
|
get
|
|
{
|
|
//case 1704
|
|
if (!HasSignature) return null;
|
|
MemoryStream ms = new MemoryStream();
|
|
SignatureBitmap.Save(ms, ImageFormat.Bmp);
|
|
return ms.ToArray();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Constructor takes the signature code
|
|
/// containing the dimensions, client datetime captured,
|
|
/// version and signature poly line strokes
|
|
/// and parses it out.
|
|
/// </summary>
|
|
/// <param name="signatureCode"></param>
|
|
public AySignature(string signatureCode)
|
|
{
|
|
Clear();
|
|
SignatureCode = signatureCode;
|
|
if(SignatureCode!=null && !string.IsNullOrWhiteSpace(SignatureCode))
|
|
Parse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
public AySignature()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset signature object to empty
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
//defaults in case signature blank as it often will be for most people
|
|
SignatureCode = "";
|
|
Version = "1";
|
|
Width = 300;
|
|
Height = 100;
|
|
ClientCapturedDateTime = DateTime.MinValue;
|
|
StrokePath = "";
|
|
HostCapturedDateTime = new SmartDate();
|
|
//case 1626
|
|
if (bmSig != null)
|
|
{
|
|
bmSig.Dispose();
|
|
bmSig = null;
|
|
}
|
|
}
|
|
|
|
private void Parse()
|
|
{
|
|
Parse2();
|
|
return;
|
|
//Match m = AyaBizUtils.rxSignatureParser.Match(SignatureCode);
|
|
|
|
//if (m.Groups.Count < 10) return;
|
|
|
|
//Version = m.Groups["version"].Value;
|
|
//Height = int.Parse(m.Groups["height"].Value);
|
|
//Width = int.Parse(m.Groups["width"].Value);
|
|
//ClientCapturedDateTime = new DateTime(
|
|
// int.Parse(m.Groups["year"].Value),
|
|
// int.Parse(m.Groups["month"].Value),
|
|
// int.Parse(m.Groups["day"].Value),
|
|
// int.Parse(m.Groups["hour"].Value),
|
|
// int.Parse(m.Groups["minute"].Value),
|
|
// int.Parse(m.Groups["second"].Value)
|
|
// );
|
|
|
|
//StrokePath = m.Groups["strokes"].Value.TrimStart('X').Trim();
|
|
////case 1978
|
|
//StrokePath = StrokePath.Replace("NaN", "0");
|
|
}
|
|
|
|
|
|
//case
|
|
private void Parse2()
|
|
{
|
|
if (String.IsNullOrWhiteSpace(SignatureCode)) return;
|
|
|
|
Version = "1";//it's always 1
|
|
int nWidthStart = SignatureCode.LastIndexOf("width=");
|
|
int nHeightStart = SignatureCode.LastIndexOf("height=");
|
|
int nCapturedStart = SignatureCode.LastIndexOf("captured=");
|
|
int nCloseBracketStart = SignatureCode.LastIndexOf("}");
|
|
|
|
string sWidthValue = SignatureCode.Substring(nWidthStart + 6, nHeightStart - (nWidthStart + 6));
|
|
string sHeightValue = SignatureCode.Substring(nHeightStart + 7, nCapturedStart - (nHeightStart + 7));
|
|
string sCapturedValue = SignatureCode.Substring(nCapturedStart + 9, nCloseBracketStart - (nCapturedStart + 9));
|
|
string sStrokes = SignatureCode.Substring(nCloseBracketStart + 1).TrimStart('X').Trim().Replace("NaN", "0");
|
|
|
|
//in case there are commas in the decimal separator
|
|
sHeightValue=sHeightValue.Replace(',', '.');
|
|
sWidthValue = sWidthValue.Replace(',', '.');
|
|
|
|
|
|
Height = ParseInt(sHeightValue);
|
|
Width = ParseInt(sWidthValue);
|
|
StrokePath = sStrokes;
|
|
|
|
string[] cap = sCapturedValue.Split(':');
|
|
|
|
ClientCapturedDateTime = new DateTime(
|
|
int.Parse(cap[0]),//year
|
|
int.Parse(cap[1]),//month
|
|
int.Parse(cap[2]),//day
|
|
int.Parse(cap[3]),//hr
|
|
int.Parse(cap[4]),//min
|
|
int.Parse(cap[5])//sec
|
|
);
|
|
|
|
|
|
//{version=1 width=300.4 height=100.4 captured=2017:2:1:12:27:37}X 62,43 62,45 62,46 63,47 64,49 66,50 66,50 68,50 70,51 75,51 83,51 95,49 110,43 144,26 151,22 156,19 158,18 160,17 160,18 160,22 160,29 162,34 162,39 163,43 166,47 167,51 168,53 171,54 174,57 176,57 179,58 183,59 188,59 194,59 202,59 207,58 214,58 218,58 220,58 222,58 223,59 226,59 232,58 240,57 246,53 252,49 258,45 260,41 262,37 263,37 263,35 263,37 263,38 263,39 263,41 263,42 263,43 263,45 263,45
|
|
}
|
|
|
|
private int ParseInt(string s, bool alwaysRoundDown = false)
|
|
{
|
|
//converts null/empty strings to zero
|
|
if (string.IsNullOrEmpty(s)) return 0;
|
|
|
|
if (!s.Contains(".")) return int.Parse(s);
|
|
|
|
string[] parts = s.Split('.');
|
|
int i = int.Parse(parts[0]);
|
|
if (alwaysRoundDown || parts.Length == 1) return i;
|
|
|
|
String afterPoint = parts[1];
|
|
if (afterPoint.Length > 1)
|
|
afterPoint = afterPoint.Substring(0, 1);
|
|
|
|
int digitAfterPoint = int.Parse(afterPoint);
|
|
return (digitAfterPoint < 5) ? i : i + 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// convert stroke path to bitmap
|
|
/// </summary>
|
|
private void GenerateBitmap()
|
|
{
|
|
//case 1704
|
|
if (!HasSignature) return;
|
|
|
|
bmSig = new Bitmap(Width, Height);
|
|
Graphics g = Graphics.FromImage(bmSig);
|
|
|
|
//Make the bitmap all white before drawing the signature on it
|
|
Brush bWhite = new SolidBrush(Color.White);
|
|
g.FillRectangle(bWhite, 0, 0, Width, Height);
|
|
|
|
//Pen for drawing sample
|
|
Pen penDraw = new Pen(Color.Black, 3);
|
|
|
|
// GraphicsPath pth = new GraphicsPath();
|
|
List<Point> lPoints = new List<Point>();
|
|
|
|
string[] sLines = StrokePath.Split('X');
|
|
|
|
foreach (string sLine in sLines)
|
|
{
|
|
string[] sPoints = sLine.Trim().Split(' ');
|
|
|
|
if (sPoints.Length > 0)
|
|
{
|
|
lPoints.Clear();
|
|
GraphicsPath pth = new GraphicsPath();
|
|
|
|
foreach (string s in sPoints)
|
|
{
|
|
lPoints.Add(StringToPoint(s));
|
|
}
|
|
|
|
pth.AddLines(lPoints.ToArray());
|
|
|
|
g.DrawPath(penDraw, pth);
|
|
pth.Dispose();
|
|
pth = null;
|
|
|
|
}
|
|
}
|
|
|
|
if (g != null)
|
|
g.Dispose();
|
|
}
|
|
|
|
private static Point StringToPoint(string s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s))
|
|
return new Point(0, 0);
|
|
|
|
if (!s.Contains(','))
|
|
return new Point(0, 0);
|
|
|
|
string[] spoint = s.Split(',');
|
|
|
|
//case 1939 - can't treat the input in the parse as integers as they can apparently contain decimals now
|
|
//I suspect that the conversion with decimal is likely slower than checking to see if it has a decimal first so
|
|
//I'm breaking this out into two separate streams for performance
|
|
if (s.Contains('.'))
|
|
{
|
|
// if it contains a decimal then convert differently
|
|
return new Point(
|
|
Convert.ToInt32(Math.Round(Convert.ToDouble(spoint[0]))),
|
|
Convert.ToInt32(Math.Round(Convert.ToDouble(spoint[1])))
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//if it doesn't contain a decimal then use the original method
|
|
//case 1978 wrap this in a try catch block due to one customer having NaN values
|
|
//in their signature stroke path string.
|
|
try
|
|
{
|
|
return new Point(int.Parse(spoint[0]), int.Parse(spoint[1]));
|
|
}
|
|
catch (System.FormatException )
|
|
{
|
|
return new Point(0, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}//End of class
|
|
|
|
|
|
}//End of namespace
|