TimeSheet.cs
  1. //
  2. // This code is part of Document Solutions for PDF demos.
  3. // Copyright (c) MESCIUS inc. All rights reserved.
  4. //
  5. using System;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Drawing;
  9. using System.Collections.Generic;
  10. using GrapeCity.Documents.Pdf;
  11. using GrapeCity.Documents.Pdf.AcroForms;
  12. using GrapeCity.Documents.Text;
  13. using GrapeCity.Documents.Common;
  14. using GrapeCity.Documents.Drawing;
  15. using System.Security.Cryptography.X509Certificates;
  16. using GCTEXT = GrapeCity.Documents.Text;
  17. using GCDRAW = GrapeCity.Documents.Drawing;
  18.  
  19. namespace DsPdfWeb.Demos
  20. {
  21. // This sample implements a scenario involving generating, filling and signing a time sheet:
  22. // - The first step is to generate a time sheet form (AcroForm PDF).
  23. // The form contains fields for employee info, working times for a week,
  24. // and employee's and supervisor's signatures.
  25. // - The next step in a real app would involve employees filling and signing the form.
  26. // In this sample, we use some randomly generated data to fill the form on behalf
  27. // of an employee.
  28. // - We then flatten the filled form - convert the text fields filled by the employee
  29. // to regular text.
  30. // - Finally, we digitally sign the flattened document on behalf of the employee's
  31. // supervisor, and save it.
  32. //
  33. // See also TimeSheetIncremental - it is essentially the same code, but uses
  34. // incremental update to digitally sign the document by both employee and supervisor.
  35. public class TimeSheet
  36. {
  37. // Font collection to hold the fonts we need:
  38. private FontCollection _fc = new FontCollection();
  39. // The text layout used to render input fields when flattening the document:
  40. private TextLayout _inputTl = new TextLayout(72);
  41. // The text format used for input fields:
  42. private TextFormat _inputTf = new TextFormat();
  43. private GCTEXT.Font _inputFont = FontCollection.SystemFonts.FindFamilyName("Segoe UI", true);
  44. private float _inputFontSize = 12;
  45. // Input fields margin:
  46. private float _inputMargin = 5;
  47. // Space for employee's signature:
  48. private RectangleF _empSignRect;
  49. // This will hold the list of images so we can dispose them after saving the document:
  50. private List<IDisposable> _disposables = new List<IDisposable>();
  51.  
  52. // Main entry point of this sample:
  53. public int CreatePDF(Stream stream)
  54. {
  55. // Set up a font collection with the fonts we need:
  56. _fc.RegisterDirectory(Path.Combine("Resources", "Fonts"));
  57. // Set that font collection on input fields' text layout
  58. // (we will also set it on all text layouts that we'll use):
  59. _inputTl.FontCollection = _fc;
  60. // Set up layout and formatting for input fields:
  61. _inputTl.ParagraphAlignment = ParagraphAlignment.Center;
  62. _inputTf.Font = _inputFont;
  63. _inputTf.FontSize = _inputFontSize;
  64.  
  65. // Create the time sheet input form
  66. // (in a real-life scenario, we probably would only create it once,
  67. // and then re-use the form PDF):
  68. var doc = MakeTimeSheetForm();
  69.  
  70. // At this point, 'doc' is an empty AcroForm.
  71. // In a real-life app it would be distributed to employees
  72. // for them to fill and send back.
  73. FillEmployeeData(doc);
  74.  
  75. //
  76. // At this point the form is filled with employee's data.
  77. //
  78.  
  79. // Supervisor data (in a real app, these would probably be fetched from a db):
  80. var supName = "Jane Donahue";
  81. var supSignDate = Common.Util.TimeNow().ToShortDateString();
  82. SetFieldValue(doc, _Names.EmpSuper, supName);
  83. SetFieldValue(doc, _Names.SupSignDate, supSignDate);
  84.  
  85. // The next step is to 'flatten' the form: we loop over document AcroForm's fields,
  86. // drawing their current values in place, and then remove the fields.
  87. // This produces a PDF with text fields' values as part of the regular (non-editable) content:
  88. FlattenDoc(doc);
  89.  
  90. // Now we digitally sign the flattened document on behalf of the 'manager':
  91. var pfxPath = Path.Combine("Resources", "Misc", "DsPdfTest.pfx");
  92. var cert = new X509Certificate2(File.ReadAllBytes(pfxPath), "qq",
  93. X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
  94. var sp = new SignatureProperties()
  95. {
  96. SignatureBuilder = new Pkcs7SignatureBuilder()
  97. {
  98. CertificateChain = new X509Certificate2[] { cert }
  99. },
  100. Location = "DsPdfWeb - TimeSheet sample",
  101. SignerName = supName,
  102. SigningDateTime = Common.Util.TimeNow(),
  103. };
  104.  
  105. // Connect the signature field and signature props:
  106. SignatureField supSign = doc.AcroForm.Fields.First(f_ => f_.Name == _Names.SupSign) as SignatureField;
  107. sp.SignatureField = supSign;
  108. supSign.Widget.ButtonAppearance.Caption = supName;
  109. // Some browser PDF viewers do not show form fields, so we render a placeholder:
  110. supSign.Widget.Page.Graphics.DrawString("digitally signed", new TextFormat() { FontName = "Segoe UI", FontSize = 9 }, supSign.Widget.Rect);
  111.  
  112. // Done, now save the document with supervisor signature:
  113. doc.Sign(sp, stream);
  114. // Dispose images only after the document is saved:
  115. _disposables.ForEach(d_ => d_.Dispose());
  116. return doc.Pages.Count;
  117. }
  118.  
  119. // Replaces any text fields in the document with regular text:
  120. private void FlattenDoc(GcPdfDocument doc)
  121. {
  122. foreach (var f in doc.AcroForm.Fields)
  123. {
  124. if (f is TextField fld)
  125. {
  126. var w = fld.Widget;
  127. var g = w.Page.Graphics;
  128. _inputTl.Clear();
  129. _inputTl.Append(fld.Value, _inputTf);
  130. _inputTl.MaxHeight = w.Rect.Height;
  131. _inputTl.PerformLayout(true);
  132. g.DrawTextLayout(_inputTl, w.Rect.Location);
  133. }
  134. }
  135. for (int i = doc.AcroForm.Fields.Count - 1; i >= 0; --i)
  136. if (doc.AcroForm.Fields[i] is TextField)
  137. doc.AcroForm.Fields.RemoveAt(i);
  138. }
  139.  
  140. // Data field names:
  141. static class _Names
  142. {
  143. public static readonly string[] Dows = new string[]
  144. {
  145. "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
  146. };
  147. public const string EmpName = "empName";
  148. public const string EmpTitle = "empTitle";
  149. public const string EmpNum = "empNum";
  150. public const string EmpStatus = "empStatus";
  151. public const string EmpDep = "empDep";
  152. public const string EmpSuper = "empSuper";
  153. public static Dictionary<string, string[]> DtNames = new Dictionary<string, string[]>()
  154. {
  155. {"Sun", new string[] { "dtSun", "tSunStart", "tSunEnd", "tSunReg", "tSunOvr", "tSunTotal" } },
  156. {"Mon", new string[] { "dtMon", "tMonStart", "tMonEnd", "tMonReg", "tMonOvr", "tMonTotal" } },
  157. {"Tue", new string[] { "dtTue", "tTueStart", "tTueEnd", "tTueReg", "tTueOvr", "tTueTotal" } },
  158. {"Wed", new string[] { "dtWed", "tWedStart", "tWedEnd", "tWedReg", "tWedOvr", "tWedTotal" } },
  159. {"Thu", new string[] { "dtThu", "tThuStart", "tThuEnd", "tThuReg", "tThuOvr", "tThuTotal" } },
  160. {"Fri", new string[] { "dtFri", "tFriStart", "tFriEnd", "tFriReg", "tFriOvr", "tFriTotal" } },
  161. {"Sat", new string[] { "dtSat", "tSatStart", "tSatEnd", "tSatReg", "tSatOvr", "tSatTotal" } },
  162. };
  163. public const string TotalReg = "totReg";
  164. public const string TotalOvr = "totOvr";
  165. public const string TotalHours = "totHours";
  166. public const string EmpSign = "empSign";
  167. public const string EmpSignDate = "empSignDate";
  168. public const string SupSign = "supSign";
  169. public const string SupSignDate = "supSignDate";
  170. }
  171.  
  172. // Creates the Time Sheet form:
  173. private GcPdfDocument MakeTimeSheetForm()
  174. {
  175. const float marginH = 72, marginV = 48;
  176. var doc = new GcPdfDocument();
  177. var page = doc.NewPage();
  178. var g = page.Graphics;
  179. var ip = new PointF(marginH, marginV);
  180.  
  181. var tl = new TextLayout(g.Resolution) { FontCollection = _fc };
  182.  
  183. tl.Append("TIME SHEET", new TextFormat() { FontName = "Segoe UI", FontSize = 18 });
  184. tl.PerformLayout(true);
  185. g.DrawTextLayout(tl, ip);
  186. ip.Y += tl.ContentHeight + 15;
  187.  
  188. var logo = GCDRAW.Image.FromFile(Path.Combine("Resources", "ImagesBis", "AcmeLogo-vertical-250px.png"));
  189. _disposables.Add(logo);
  190. var s = new SizeF(250f * 0.75f, 64f * 0.75f);
  191. g.DrawImage(logo, new RectangleF(ip, s), null, ImageAlign.Default);
  192. ip.Y += s.Height + 5;
  193.  
  194. tl.Clear();
  195. tl.Append("Where Business meets Technology",
  196. new TextFormat() { FontName = "Segoe UI", FontItalic = true, FontSize = 10 });
  197. tl.PerformLayout(true);
  198. g.DrawTextLayout(tl, ip);
  199. ip.Y += tl.ContentHeight + 15;
  200.  
  201. tl.Clear();
  202. tl.Append("1901, Halford Avenue,\r\nSanta Clara, California – 95051-2553,\r\nUnited States",
  203. new TextFormat() { FontName = "Segoe UI", FontSize = 9 });
  204. tl.MaxWidth = page.Size.Width - marginH * 2;
  205. tl.TextAlignment = TextAlignment.Trailing;
  206. tl.PerformLayout(true);
  207. g.DrawTextLayout(tl, ip);
  208. ip.Y += tl.ContentHeight + 25;
  209.  
  210. var pen = new GCDRAW.Pen(Color.Gray, 0.5f);
  211.  
  212. var colw = (page.Size.Width - marginH * 2) / 2;
  213. var fields1 = DrawTable(ip,
  214. new float[] { colw, colw },
  215. new float[] { 30, 30, 30 },
  216. g, pen);
  217.  
  218. var tf = new TextFormat() { FontName = "Segoe UI", FontSize = 9 };
  219. tl.ParagraphAlignment = ParagraphAlignment.Center;
  220. tl.TextAlignment = TextAlignment.Leading;
  221. tl.MarginLeft = tl.MarginRight = tl.MarginTop = tl.MarginBottom = 4;
  222.  
  223. // t_ - caption
  224. // b_ - bounds
  225. // f_ - field name, null means no field
  226. Action<string, RectangleF, string> drawField = (t_, b_, f_) =>
  227. {
  228. float tWidth;
  229. if (!string.IsNullOrEmpty(t_))
  230. {
  231. tl.Clear();
  232. tl.MaxHeight = b_.Height;
  233. tl.MaxWidth = b_.Width;
  234. tl.Append(t_, tf);
  235. tl.PerformLayout(true);
  236. g.DrawTextLayout(tl, b_.Location);
  237. tWidth = tl.ContentRectangle.Right;
  238. }
  239. else
  240. tWidth = 0;
  241. if (!string.IsNullOrEmpty(f_))
  242. {
  243. var fld = new TextField() { Name = f_ };
  244. fld.Widget.Page = page;
  245. fld.Widget.Rect = new RectangleF(
  246. b_.X + tWidth + _inputMargin, b_.Y + _inputMargin,
  247. b_.Width - tWidth - _inputMargin * 2, b_.Height - _inputMargin * 2);
  248. fld.Widget.DefaultAppearance.Font = _inputFont;
  249. fld.Widget.DefaultAppearance.FontSize = _inputFontSize;
  250. fld.Widget.Border.Color = Color.LightSlateGray;
  251. fld.Widget.Border.Width = 0.5f;
  252. doc.AcroForm.Fields.Add(fld);
  253. }
  254. };
  255.  
  256. drawField("EMPLOYEE NAME: ", fields1[0, 0], _Names.EmpName);
  257. drawField("TITLE: ", fields1[1, 0], _Names.EmpTitle);
  258. drawField("EMPLOYEE NUMBER: ", fields1[0, 1], _Names.EmpNum);
  259. drawField("STATUS: ", fields1[1, 1], _Names.EmpStatus);
  260. drawField("DEPARTMENT: ", fields1[0, 2], _Names.EmpDep);
  261. drawField("SUPERVISOR: ", fields1[1, 2], _Names.EmpSuper);
  262.  
  263. ip.Y = fields1[0, 2].Bottom;
  264.  
  265. float col0 = 100;
  266. colw = (page.Size.Width - marginH * 2 - col0) / 5;
  267. float rowh = 25;
  268. var fields2 = DrawTable(ip,
  269. new float[] { col0, colw, colw, colw, colw, colw },
  270. new float[] { 50, rowh, rowh, rowh, rowh, rowh, rowh, rowh, rowh },
  271. g, pen);
  272.  
  273. tl.ParagraphAlignment = ParagraphAlignment.Far;
  274. drawField("DATE", fields2[0, 0], null);
  275. drawField("START TIME", fields2[1, 0], null);
  276. drawField("END TIME", fields2[2, 0], null);
  277. drawField("REGULAR HOURS", fields2[3, 0], null);
  278. drawField("OVERTIME HOURS", fields2[4, 0], null);
  279. tf.FontBold = true;
  280. drawField("TOTAL HOURS", fields2[5, 0], null);
  281. tf.FontBold = false;
  282. tl.ParagraphAlignment = ParagraphAlignment.Center;
  283. tf.ForeColor = Color.Gray;
  284. for (int i = 0; i < 7; ++i)
  285. drawField(_Names.Dows[i], fields2[0, i + 1], _Names.DtNames[_Names.Dows[i]][0]);
  286. // Vertically align date fields (compensate for different DOW widths):
  287. var dowFields = doc.AcroForm.Fields.TakeLast(7);
  288. var minW = dowFields.Min(f_ => ((TextField)f_).Widget.Rect.Width);
  289. dowFields.ToList().ForEach(f_ =>
  290. {
  291. var r_ = ((TextField)f_).Widget.Rect;
  292. r_.Offset(r_.Width - minW, 0);
  293. r_.Width = minW;
  294. ((TextField)f_).Widget.Rect = r_;
  295. });
  296.  
  297. tf.ForeColor = Color.Black;
  298. for (int row = 1; row <= 7; ++row)
  299. for (int col = 1; col <= 5; ++col)
  300. drawField(null, fields2[col, row], _Names.DtNames[_Names.Dows[row - 1]][col]);
  301.  
  302. tf.FontBold = true;
  303. drawField("WEEKLY TOTALS", fields2[0, 8], null);
  304. tf.FontBold = false;
  305.  
  306. drawField(null, fields2[3, 8], _Names.TotalReg);
  307. drawField(null, fields2[4, 8], _Names.TotalOvr);
  308. drawField(null, fields2[5, 8], _Names.TotalHours);
  309.  
  310. ip.Y = fields2[0, 8].Bottom;
  311.  
  312. col0 = 72 * 4;
  313. colw = page.Size.Width - marginH * 2 - col0;
  314. var fields3 = DrawTable(ip,
  315. new float[] { col0, colw },
  316. new float[] { rowh + 10, rowh, rowh },
  317. g, pen);
  318.  
  319. drawField("EMPLOYEE SIGNATURE: ", fields3[0, 1], null);
  320. var r = fields3[0, 1];
  321. _empSignRect = new RectangleF(r.X + r.Width / 2, r.Y, r.Width / 2 - _inputMargin * 2, r.Height);
  322. // For a digital employee signature, uncomment this code:
  323. /*
  324. var sf = new SignatureField();
  325. sf.Name = _Names.EmpSign;
  326. sf.Widget.Rect = new RectangleF(r.X + r.Width / 2, r.Y + _inputMargin, r.Width / 2 - _inputMargin * 2, r.Height - _inputMargin * 2);
  327. sf.Widget.Page = page;
  328. sf.Widget.BackColor = Color.LightSeaGreen;
  329. doc.AcroForm.Fields.Add(sf);
  330. */
  331. drawField("DATE: ", fields3[1, 1], _Names.EmpSignDate);
  332.  
  333. drawField("SUPERVISOR SIGNATURE: ", fields3[0, 2], null);
  334. // Supervisor signature:
  335. r = fields3[0, 2];
  336. var sf = new SignatureField();
  337. sf.Name = _Names.SupSign;
  338. sf.Widget.Rect = new RectangleF(r.X + r.Width / 2, r.Y + _inputMargin, r.Width / 2 - _inputMargin * 2, r.Height - _inputMargin * 2);
  339. sf.Widget.Page = page;
  340. sf.Widget.BackColor = Color.LightYellow;
  341. doc.AcroForm.Fields.Add(sf);
  342. drawField("DATE: ", fields3[1, 2], _Names.SupSignDate);
  343.  
  344. // Done:
  345. return doc;
  346. }
  347.  
  348. // Simple table drawing method. Returns the array of table cell rectangles.
  349. private RectangleF[,] DrawTable(PointF loc, float[] widths, float[] heights, GcGraphics g, GCDRAW.Pen p)
  350. {
  351. if (widths.Length == 0 || heights.Length == 0)
  352. throw new Exception("Table must have some columns and rows.");
  353.  
  354. RectangleF[,] cells = new RectangleF[widths.Length, heights.Length];
  355. var r = new RectangleF(loc, new SizeF(widths.Sum(), heights.Sum()));
  356. // Draw left borders (except for 1st one):
  357. float x = loc.X;
  358. for (int i = 0; i < widths.Length; ++i)
  359. {
  360. for (int j = 0; j < heights.Length; ++j)
  361. {
  362. cells[i, j].X = x;
  363. cells[i, j].Width = widths[i];
  364. }
  365. if (i > 0)
  366. g.DrawLine(x, r.Top, x, r.Bottom, p);
  367. x += widths[i];
  368. }
  369. // Draw top borders (except for 1st one):
  370. float y = loc.Y;
  371. for (int j = 0; j < heights.Length; ++j)
  372. {
  373. for (int i = 0; i < widths.Length; ++i)
  374. {
  375. cells[i, j].Y = y;
  376. cells[i, j].Height = heights[j];
  377. }
  378. if (j > 0)
  379. g.DrawLine(r.Left, y, r.Right, y, p);
  380. y += heights[j];
  381. }
  382. // Draw outer border:
  383. g.DrawRectangle(r, p);
  384. //
  385. return cells;
  386. }
  387.  
  388. // Fill in employee info and working hours with sample data:
  389. private void FillEmployeeData(GcPdfDocument doc)
  390. {
  391. // For the purposes of this sample, we fill the form with random data:
  392. SetFieldValue(doc, _Names.EmpName, "Jaime Smith");
  393. SetFieldValue(doc, _Names.EmpNum, "12345");
  394. SetFieldValue(doc, _Names.EmpDep, "Research & Development");
  395. SetFieldValue(doc, _Names.EmpTitle, "Senior Developer");
  396. SetFieldValue(doc, _Names.EmpStatus, "Full Time");
  397. var rand = new Random((int)Common.Util.TimeNow().Ticks);
  398. DateTime workday = Common.Util.TimeNow().AddDays(-15);
  399. while (workday.DayOfWeek != DayOfWeek.Sunday)
  400. workday = workday.AddDays(1);
  401. TimeSpan wkTot = TimeSpan.Zero, wkReg = TimeSpan.Zero, wkOvr = TimeSpan.Zero;
  402. for (int i = 0; i < 7; ++i)
  403. {
  404. // Start time:
  405. var start = new DateTime(workday.Year, workday.Month, workday.Day, rand.Next(6, 12), rand.Next(0, 59), 0);
  406. SetFieldValue(doc, _Names.DtNames[_Names.Dows[i]][0], start.ToShortDateString());
  407. SetFieldValue(doc, _Names.DtNames[_Names.Dows[i]][1], start.ToShortTimeString());
  408. // End time:
  409. var end = start.AddHours(rand.Next(8, 14)).AddMinutes(rand.Next(0, 59));
  410. SetFieldValue(doc, _Names.DtNames[_Names.Dows[i]][2], end.ToShortTimeString());
  411. var tot = end - start;
  412. var reg = TimeSpan.FromHours((start.DayOfWeek != DayOfWeek.Saturday && start.DayOfWeek != DayOfWeek.Sunday) ? 8 : 0);
  413. var ovr = tot.Subtract(reg);
  414. SetFieldValue(doc, _Names.DtNames[_Names.Dows[i]][3], reg.ToString(@"hh\:mm"));
  415. SetFieldValue(doc, _Names.DtNames[_Names.Dows[i]][4], ovr.ToString(@"hh\:mm"));
  416. SetFieldValue(doc, _Names.DtNames[_Names.Dows[i]][5], tot.ToString(@"hh\:mm"));
  417. wkTot += tot;
  418. wkOvr += ovr;
  419. wkReg += reg;
  420. //
  421. workday = workday.AddDays(1);
  422. }
  423. SetFieldValue(doc, _Names.TotalReg, wkReg.TotalHours.ToString("F"));
  424. SetFieldValue(doc, _Names.TotalOvr, wkOvr.TotalHours.ToString("F"));
  425. SetFieldValue(doc, _Names.TotalHours, wkTot.TotalHours.ToString("F"));
  426. SetFieldValue(doc, _Names.EmpSignDate, workday.ToShortDateString());
  427.  
  428. // 'Sign' the time sheet on behalf of the employee by drawing an image representing the signature
  429. // (see TimeSheetIncremental for digitally signing by both employee and supervisor):
  430. var empSignImage = GCDRAW.Image.FromFile(Path.Combine("Resources", "ImagesBis", "signature.png"));
  431. _disposables.Add(empSignImage);
  432. var ia = new ImageAlign(ImageAlignHorz.Center, ImageAlignVert.Center, true, true, true, false, false)
  433. { KeepAspectRatio = true };
  434. doc.Pages[0].Graphics.DrawImage(empSignImage, _empSignRect, null, ia);
  435. }
  436.  
  437. // Sets the value of a field with the specified name:
  438. private void SetFieldValue(GcPdfDocument doc, string name, string value)
  439. {
  440. var fld = doc.AcroForm.Fields.First(f_ => f_.Name == name);
  441. if (fld != null)
  442. fld.Value = value;
  443. }
  444. }
  445. }
  446.