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 { /// /// 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. /// [Serializable] public class AySignature { private string mSignatureCode = ""; /// /// Raw signature code stored by client software as captured /// public string SignatureCode { get { return mSignatureCode; } set { if (mSignatureCode != value) { mSignatureCode = value; Parse(); } } } /// /// Signature polyline stroke line paths in x,y coordinate pairs. /// public string StrokePath { get; set; } /// /// Width of signature pade area in pixels /// Defined because different devices may require different /// sized signature areas /// public int Width { get; set; } /// /// Height of signature pade area in pixels /// Defined because different devices may require different /// sized signature areas /// public int Height { get; set; } /// /// Date time at client device at moment captured signature was started drawing by end user /// public DateTime ClientCapturedDateTime { get; set; } /// /// Date time at server at moment captured signature was saved /// public SmartDate HostCapturedDateTime { get; set; } /// /// Version of signature code schema in case new features or SignatureCode changes are required in future and /// public string Version { get; set; } /// /// Strokepath is not empty and contains at least two sets of points /// public bool HasSignature { get { if (string.IsNullOrWhiteSpace(StrokePath)) return false; int nOccurs = AyaBizUtils.StringOccurrences(StrokePath, ","); return (nOccurs > 1); } } /// /// 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. :) ) /// public bool IsALikelySignature { get { if (string.IsNullOrWhiteSpace(StrokePath)) return false; int nOccurs = AyaBizUtils.StringOccurrences(StrokePath, ","); return (nOccurs > 1); } } Bitmap bmSig = null; /// /// Converts strokepath to bitmap object /// same size as signature box used to sign /// in first place. /// public Bitmap SignatureBitmap { get { //case 1704 if (bmSig == null && HasSignature) GenerateBitmap(); return bmSig; } } /// /// SignatureBitmap converted to byte array which is /// a more friendly format for some uses /// public byte[] SignatureBitmapAsByteArray { get { //case 1704 if (!HasSignature) return null; MemoryStream ms = new MemoryStream(); SignatureBitmap.Save(ms, ImageFormat.Bmp); return ms.ToArray(); } } /// /// Constructor takes the signature code /// containing the dimensions, client datetime captured, /// version and signature poly line strokes /// and parses it out. /// /// public AySignature(string signatureCode) { Clear(); SignatureCode = signatureCode; if(SignatureCode!=null && !string.IsNullOrWhiteSpace(SignatureCode)) Parse(); } /// /// Constructor /// public AySignature() { Clear(); } /// /// Reset signature object to empty /// 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; } /// /// convert stroke path to bitmap /// 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 lPoints = new List(); 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