TimeSheet.vb
''
'' このコードは、DioDocs for PDF のサンプルの一部として提供されています。
'' Copyright (c) GrapeCity inc. All rights reserved.
''
Imports System.IO
Imports System.Drawing
Imports GrapeCity.Documents.Pdf
Imports GrapeCity.Documents.Pdf.AcroForms
Imports GrapeCity.Documents.Text
Imports GrapeCity.Documents.Common
Imports GrapeCity.Documents.Drawing
Imports System.Security.Cryptography.X509Certificates
Imports GCTEXT = GrapeCity.Documents.Text
Imports GCDRAW = GrapeCity.Documents.Drawing
'' このサンプルでは、タイムシートの生成、記入、署名に関するシナリオを実装しています。
'' - 最初のステップは、タイムシートフォーム(AcroForm PDF)を生成することです。
'' このフォームには、従業員情報、1週間の作業時間、従業員とスーパーバイザの署名の
'' フィールドが含まれています。
'' - 実際のアプリにおける次のステップは、従業員が記入してフォームに署名することです。
'' このサンプルでは、従業員にによってフォームを埋める代わりに、ランダムに生成された
'' データを使用します。
'' - 次に、入力されたフォームを平坦化します。従業員が入力したテキストフィールドを通常の
'' テキストに変換します。
'' - 最後に、従業員の上司に代わって平準化された文書にデジタル署名して保存します。
''
'' TimeSheetIncremental も参照してください。これは本質的に同じコードですが、増分更新を
'' 使用して、従業員とスーパーバイザの両方で文書にデジタル署名します。
Public Class TimeSheet
'' 必要なフォントを保持するフォントコレクションです。
Private _fc As FontCollection = New FontCollection()
'' ドキュメントを平坦化するときに入力フィールドを描画するために使用されるテキストレイアウトです。
Private _inputTl As TextLayout = New TextLayout(72)
'' 入力フィールドに使用されるテキスト書式です。
Private _inputTf As TextFormat = New TextFormat()
Private _inputFont As GCTEXT.Font = FontCollection.SystemFonts.FindFamilyName("Segoe UI", True)
Private _inputFontSize As Single = 12
'' 入力フィールドのマージンです。
Private _inputMargin As Single = 5
'' 従業員の署名のためのスペースです。
Private _empSignRect As RectangleF
'' これにより、画像のリストが保持され、ドキュメントを保存した後に破棄することができます。
Private _disposables As List(Of IDisposable) = New List(Of IDisposable)
'' このサンプルのメインエントリポイント。
Function CreatePDF(ByVal stream As Stream) As Integer
'' 必要なフォントでフォントコレクションを設定します。
_fc.RegisterDirectory(Path.Combine("Resources", "Fonts"))
'' 入力フィールドのテキストレイアウトにそのフォントコレクションを設定します
'' (使用するすべてのテキストレイアウトでも設定します)。
_inputTl.FontCollection = _fc
'' 入力フィールドのレイアウトと書式を設定します。
_inputTl.ParagraphAlignment = ParagraphAlignment.Center
_inputTf.Font = _inputFont
_inputTf.FontSize = _inputFontSize
'' タイムシート入力フォームを作成します
'' (現実のシナリオでは、PDF フォームを一度しか作成せずに、
'' それを再利用することになります)。
Dim doc = MakeTimeSheetForm()
'' この時点で、 'doc' は空の AcroForm です。
'' 実際のアプリでは、従業員に配布して記入して返送します。
FillEmployeeData(doc)
''
'' この時点で、フォームは従業員のデータで埋められます。
''
''
'' スーパバイザデータです(実際のアプリケーションでは、おそらくDBからフェッチされます)。
Dim supName = "Jane Donahue"
Dim supSignDate = DateTime.Now.ToShortDateString()
SetFieldValue(doc, _Names.EmpSuper, supName)
SetFieldValue(doc, _Names.SupSignDate, supSignDate)
'' 次のステップは、フォームを「平坦化」することです。文書の AcroForm の
'' フィールドをループし、現在の値をその場で描画してからフィールドを削除します。
'' これにより、テキストフィールドの値を通常の(編集不可能な)コンテンツの
'' 一部として含む PDF が生成されます。
FlattenDoc(doc)
'' これで、「上司」に代わってフラット化されたドキュメントにデジタルで署名します。
Dim pfxPath = Path.Combine("Resources", "Misc", "GcPdfTest.pfx")
Dim cert = New X509Certificate2(File.ReadAllBytes(pfxPath), "qq",
X509KeyStorageFlags.MachineKeySet Or X509KeyStorageFlags.PersistKeySet Or X509KeyStorageFlags.Exportable)
Dim sp = New SignatureProperties() With {
.SignatureBuilder = New Pkcs7SignatureBuilder() With {
.CertificateChain = New X509Certificate2() {cert}
},
.Location = "GcPdfWeb - TimeSheet sample",
.SignerName = supName
}
'' 署名フィールドと署名プロパティを結びつけます。
Dim supSign = DirectCast(doc.AcroForm.Fields.First(Function(f_) f_.Name = _Names.SupSign), SignatureField)
sp.SignatureField = supSign
supSign.Widget.ButtonAppearance.Caption = supName
'' 一部のブラウザの PDF ビューアではフォームフィールドが表示されないため、プレースホルダを描画します。
supSign.Widget.Page.Graphics.DrawString("digitally signed", New TextFormat() With {.FontName = "Segoe UI", .FontSize = 9}, supSign.Widget.Rect)
'' 終了し、スーパーバイザの署名でドキュメントを保存します。
doc.Sign(sp, stream)
'' ドキュメントを保存した後にのみ画像を廃棄します。
_disposables.ForEach(Sub(d_) d_.Dispose())
Return doc.Pages.Count
End Function
'' ドキュメント内のテキストフィールドを通常のテキストに置き換えます。
Private Sub FlattenDoc(ByVal doc As GcPdfDocument)
For Each f In doc.AcroForm.Fields
If (TypeOf f Is TextField) Then
Dim fld = DirectCast(f, TextField)
Dim w = fld.Widget
Dim g = w.Page.Graphics
_inputTl.Clear()
_inputTl.Append(fld.Value, _inputTf)
_inputTl.MaxHeight = w.Rect.Height
_inputTl.PerformLayout(True)
g.DrawTextLayout(_inputTl, w.Rect.Location)
End If
Next
For i = doc.AcroForm.Fields.Count - 1 To 0 Step -1
If TypeOf doc.AcroForm.Fields(i) Is TextField Then
doc.AcroForm.Fields.RemoveAt(i)
End If
Next
End Sub
'' データフィールド名です。
Private Structure _Names
Shared ReadOnly Dows As String() = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
}
Const EmpName = "empName"
Const EmpTitle = "empTitle"
Const EmpNum = "empNum"
Const EmpStatus = "empStatus"
Const EmpDep = "empDep"
Const EmpSuper = "empSuper"
Shared ReadOnly DtNames = New Dictionary(Of String, String()) From {
{"Sun", New String() {"dtSun", "tSunStart", "tSunEnd", "tSunReg", "tSunOvr", "tSunTotal"}},
{"Mon", New String() {"dtMon", "tMonStart", "tMonEnd", "tMonReg", "tMonOvr", "tMonTotal"}},
{"Tue", New String() {"dtTue", "tTueStart", "tTueEnd", "tTueReg", "tTueOvr", "tTueTotal"}},
{"Wed", New String() {"dtWed", "tWedStart", "tWedEnd", "tWedReg", "tWedOvr", "tWedTotal"}},
{"Thu", New String() {"dtThu", "tThuStart", "tThuEnd", "tThuReg", "tThuOvr", "tThuTotal"}},
{"Fri", New String() {"dtFri", "tFriStart", "tFriEnd", "tFriReg", "tFriOvr", "tFriTotal"}},
{"Sat", New String() {"dtSat", "tSatStart", "tSatEnd", "tSatReg", "tSatOvr", "tSatTotal"}}
}
Const TotalReg = "totReg"
Const TotalOvr = "totOvr"
Const TotalHours = "totHours"
Const EmpSign = "empSign"
Const EmpSignDate = "empSignDate"
Const SupSign = "supSign"
Const SupSignDate = "supSignDate"
End Structure
'' タイムシートフォームを作成します。
Private Function MakeTimeSheetForm() As GcPdfDocument
Const marginH = 72.0F, marginV = 48.0F
Dim doc = New GcPdfDocument()
Dim page = doc.NewPage()
Dim g = page.Graphics
Dim ip = New PointF(marginH, marginV)
Dim tl = New TextLayout(g.Resolution) With {.FontCollection = _fc}
tl.Append("TIME SHEET", New TextFormat() With {.FontName = "Segoe UI", .FontSize = 18})
tl.PerformLayout(True)
g.DrawTextLayout(tl, ip)
ip.Y += tl.ContentHeight + 15
Dim logo = GCDRAW.Image.FromFile(Path.Combine("Resources", "ImagesBis", "AcmeLogo-vertical-250px.png"))
Dim s = New SizeF(250.0F * 0.75F, 64.0F * 0.75F)
g.DrawImage(logo, New RectangleF(ip, s), Nothing, ImageAlign.Default)
ip.Y += s.Height + 5
tl.Clear()
tl.Append("Where Business meets Technology",
New TextFormat() With {.FontName = "Segoe UI", .FontItalic = True, .FontSize = 10})
tl.PerformLayout(True)
g.DrawTextLayout(tl, ip)
ip.Y += tl.ContentHeight + 15
tl.Clear()
tl.Append($"1901, Halford Avenue,{vbCrLf}Santa Clara, California – 95051-2553,{vbCrLf}United States",
New TextFormat() With {.FontName = "Segoe UI", .FontSize = 9})
tl.MaxWidth = page.Size.Width - marginH * 2
tl.TextAlignment = TextAlignment.Trailing
tl.PerformLayout(True)
g.DrawTextLayout(tl, ip)
ip.Y += tl.ContentHeight + 25
Dim pen = New GCDRAW.Pen(Color.Gray, 0.5F)
Dim colw = (page.Size.Width - marginH * 2) / 2
Dim fields1 = DrawTable(ip,
New Single() {colw, colw},
New Single() {30, 30, 30},
g, pen)
Dim tf = New TextFormat() With {.FontName = "Segoe UI", .FontSize = 9}
With tl
.ParagraphAlignment = ParagraphAlignment.Center
.TextAlignment = TextAlignment.Leading
.MarginLeft = 4
.MarginRight = 4
.MarginTop = 4
.MarginBottom = 4
End With
'' t_ - キャプション
'' b_ - 範囲
'' f_ - フィールド名、null はフィールドがないことを意味します
Dim drawField As Action(Of String, RectangleF, String) =
Sub(t_, b_, f_)
Dim tWidth As Single
If Not String.IsNullOrEmpty(t_) Then
tl.Clear()
tl.MaxHeight = b_.Height
tl.MaxWidth = b_.Width
tl.Append(t_, tf)
tl.PerformLayout(True)
g.DrawTextLayout(tl, b_.Location)
tWidth = tl.ContentRectangle.Right
Else
tWidth = 0
End If
If Not String.IsNullOrEmpty(f_) Then
Dim fld = New TextField() With {.Name = f_}
fld.Widget.Page = page
fld.Widget.Rect = New RectangleF(
b_.X + tWidth + _inputMargin, b_.Y + _inputMargin,
b_.Width - tWidth - _inputMargin * 2, b_.Height - _inputMargin * 2)
fld.Widget.DefaultAppearance.Font = _inputFont
fld.Widget.DefaultAppearance.FontSize = _inputFontSize
fld.Widget.Border.Color = Color.LightSlateGray
fld.Widget.Border.Width = 0.5F
doc.AcroForm.Fields.Add(fld)
End If
End Sub
drawField("EMPLOYEE NAME: ", fields1(0, 0), _Names.EmpName)
drawField("TITLE: ", fields1(1, 0), _Names.EmpTitle)
drawField("EMPLOYEE NUMBER: ", fields1(0, 1), _Names.EmpNum)
drawField("STATUS: ", fields1(1, 1), _Names.EmpStatus)
drawField("DEPARTMENT: ", fields1(0, 2), _Names.EmpDep)
drawField("SUPERVISOR: ", fields1(1, 2), _Names.EmpSuper)
ip.Y = fields1(0, 2).Bottom
Dim col0 = 100.0F
colw = (page.Size.Width - marginH * 2 - col0) / 5
Dim rowh = 25.0F
Dim fields2 = DrawTable(ip,
New Single() {col0, colw, colw, colw, colw, colw},
New Single() {50, rowh, rowh, rowh, rowh, rowh, rowh, rowh, rowh},
g, pen)
tl.ParagraphAlignment = ParagraphAlignment.Far
drawField("DATE", fields2(0, 0), Nothing)
drawField("START TIME", fields2(1, 0), Nothing)
drawField("END TIME", fields2(2, 0), Nothing)
drawField("REGULAR HOURS", fields2(3, 0), Nothing)
drawField("OVERTIME HOURS", fields2(4, 0), Nothing)
tf.FontBold = True
drawField("TOTAL HOURS", fields2(5, 0), Nothing)
tf.FontBold = False
tl.ParagraphAlignment = ParagraphAlignment.Center
tf.ForeColor = Color.Gray
For i = 0 To 6
drawField(_Names.Dows(i), fields2(0, i + 1), _Names.DtNames(_Names.Dows(i))(0))
Next
'' 日付フィールドを垂直に配置します(異なるDOW幅を補正します)。
Dim dowFields = doc.AcroForm.Fields.TakeLast(7)
Dim minW = dowFields.Min(Function(f_) CType(f_, TextField).Widget.Rect.Width)
dowFields.ToList().ForEach(
Sub(f_)
Dim r_ = CType(f_, TextField).Widget.Rect
r_.Offset(r_.Width - minW, 0)
r_.Width = minW
CType(f_, TextField).Widget.Rect = r_
End Sub
)
tf.ForeColor = Color.Black
For row = 1 To 7
For col = 1 To 5
drawField(Nothing, fields2(col, row), _Names.DtNames(_Names.Dows(row - 1))(col))
Next
Next
tf.FontBold = True
drawField("WEEKLY TOTALS", fields2(0, 8), Nothing)
tf.FontBold = False
drawField(Nothing, fields2(3, 8), _Names.TotalReg)
drawField(Nothing, fields2(4, 8), _Names.TotalOvr)
drawField(Nothing, fields2(5, 8), _Names.TotalHours)
ip.Y = fields2(0, 8).Bottom
col0 = 72 * 4
colw = page.Size.Width - marginH * 2 - col0
Dim fields3 = DrawTable(ip,
New Single() {col0, colw},
New Single() {rowh + 10, rowh, rowh},
g, pen)
drawField("EMPLOYEE SIGNATURE: ", fields3(0, 1), Nothing)
Dim r = fields3(0, 1)
_empSignRect = New RectangleF(r.X + r.Width / 2, r.Y, r.Width / 2 - _inputMargin * 2, r.Height)
#If False Then
'' デジタル従業員の署名については、このコードのコメントを外してください。
Dim sfEmp = New SignatureField()
sfEmp.Name = _Names.EmpSign
sfEmp.Widget.Rect = New RectangleF(r.X + r.Width / 2, r.Y + _inputMargin, r.Width / 2 - _inputMargin * 2, r.Height - _inputMargin * 2)
sfEmp.Widget.Page = page
sfEmp.Widget.BackColor = Color.LightSeaGreen
doc.AcroForm.Fields.Add(sfEmp)
#End If
drawField("DATE: ", fields3(1, 1), _Names.EmpSignDate)
drawField("SUPERVISOR SIGNATURE: ", fields3(0, 2), Nothing)
r = fields3(0, 2)
Dim sfSup = New SignatureField()
sfSup.Name = _Names.SupSign
sfSup.Widget.Rect = New RectangleF(r.X + r.Width / 2, r.Y + _inputMargin, r.Width / 2 - _inputMargin * 2, r.Height - _inputMargin * 2)
sfSup.Widget.Page = page
sfSup.Widget.BackColor = Color.LightYellow
doc.AcroForm.Fields.Add(sfSup)
drawField("DATE: ", fields3(1, 2), _Names.SupSignDate)
'' PDF ドキュメントを保存します。
Return doc
End Function
'' シンプルな表の描画メソッドです。表のセルの矩形の配列を返します。
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(,)
If widths.Length = 0 OrElse heights.Length = 0 Then
Throw New Exception("Table must have some columns and rows.")
End If
Dim cells(widths.Length, heights.Length) As RectangleF
Dim r = New RectangleF(loc, New SizeF(widths.Sum(), heights.Sum()))
'' 左の枠線を描画します(1番目を除く)。
Dim x = loc.X
For i = 0 To widths.Length - 1
For j = 0 To heights.Length - 1
cells(i, j).X = x
cells(i, j).Width = widths(i)
Next
If (i > 0) Then
g.DrawLine(x, r.Top, x, r.Bottom, p)
End If
x += widths(i)
Next
'' 上の枠線を描画します(1番目を除く)。
Dim y = loc.Y
For j = 0 To heights.Length - 1
For i = 0 To widths.Length - 1
cells(i, j).Y = y
cells(i, j).Height = heights(j)
Next
If (j > 0) Then
g.DrawLine(r.Left, y, r.Right, y, p)
End If
y += heights(j)
Next
'' 外枠を描画します。
g.DrawRectangle(r, p)
''
Return cells
End Function
'' 従業員情報と勤務時間にサンプルデータを入力します。
Private Sub FillEmployeeData(ByVal doc As GcPdfDocument)
'' このサンプルでは、フォームにランダムなデータを入力します。
SetFieldValue(doc, _Names.EmpName, "Jaime Smith")
SetFieldValue(doc, _Names.EmpNum, "12345")
SetFieldValue(doc, _Names.EmpDep, "Research & Development")
SetFieldValue(doc, _Names.EmpTitle, "Senior Developer")
SetFieldValue(doc, _Names.EmpStatus, "Full Time")
Dim rand = Util.NewRandom()
Dim workday = DateTime.Now.AddDays(-15)
While workday.DayOfWeek <> DayOfWeek.Sunday
workday = workday.AddDays(1)
End While
Dim wkTot = TimeSpan.Zero, wkReg = TimeSpan.Zero, wkOvr = TimeSpan.Zero
For i = 0 To 6
'' 開始時刻。
Dim start = New DateTime(workday.Year, workday.Month, workday.Day, rand.Next(6, 12), rand.Next(0, 59), 0)
SetFieldValue(doc, _Names.DtNames(_Names.Dows(i))(0), start.ToShortDateString())
SetFieldValue(doc, _Names.DtNames(_Names.Dows(i))(1), start.ToShortTimeString())
'' 終了時刻。
Dim endd = start.AddHours(rand.Next(8, 14)).AddMinutes(rand.Next(0, 59))
SetFieldValue(doc, _Names.DtNames(_Names.Dows(i))(2), endd.ToShortTimeString())
Dim tot = endd - start
Dim reg = TimeSpan.FromHours(If(start.DayOfWeek <> DayOfWeek.Saturday AndAlso start.DayOfWeek <> DayOfWeek.Sunday, 8, 0))
Dim ovr = tot.Subtract(reg)
SetFieldValue(doc, _Names.DtNames(_Names.Dows(i))(3), reg.ToString("hh\:mm"))
SetFieldValue(doc, _Names.DtNames(_Names.Dows(i))(4), ovr.ToString("hh\:mm"))
SetFieldValue(doc, _Names.DtNames(_Names.Dows(i))(5), tot.ToString("hh\:mm"))
wkTot += tot
wkOvr += ovr
wkReg += reg
''
workday = workday.AddDays(1)
Next
SetFieldValue(doc, _Names.TotalReg, wkReg.TotalHours.ToString("F"))
SetFieldValue(doc, _Names.TotalOvr, wkOvr.TotalHours.ToString("F"))
SetFieldValue(doc, _Names.TotalHours, wkTot.TotalHours.ToString("F"))
SetFieldValue(doc, _Names.EmpSignDate, workday.ToShortDateString())
'' 署名を表わすイメージを描くことによって、従業員に代わってタイムシートに「署名」します。
'' (従業員とスーパーバイザの両方によるデジタル署名については、TimeSheetIncremental を参照してください)
Dim empSignImage = GCDRAW.Image.FromFile(Path.Combine("Resources", "ImagesBis", "signature.png"))
Dim ia = New ImageAlign(ImageAlignHorz.Center, ImageAlignVert.Center, True, True, True, False, False) With {.KeepAspectRatio = True}
doc.Pages(0).Graphics.DrawImage(empSignImage, _empSignRect, Nothing, ia)
End Sub
'' 指定された名前のフィールドの値を設定します。
Private Sub SetFieldValue(ByVal doc As GcPdfDocument, ByVal name As String, ByVal value As String)
Dim fld = doc.AcroForm.Fields.First(Function(f_) f_.Name = name)
If fld IsNot Nothing Then
fld.Value = value
End If
End Sub
End Class