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