TimeSheetIncremental.vb
''
'' このコードは、DioDocs for PDF のサンプルの一部として提供されています。
'' © MESCIUS 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 GrapeCity.Documents.Pdf.Security
Imports System.Security.Cryptography.X509Certificates
Imports GCTEXT = GrapeCity.Documents.Text
Imports GCDRAW = GrapeCity.Documents.Drawing
'' このサンプルは、TimeSheet とほぼ同じですが、大きな違いが1つあります。
'' 他のサンプルとは異なり、このフォームでは、埋め込まれたフォームは従業員に
'' よってデジタル署名され、署名付き PDF は増分更新を使用してスーパバイザに
'' よって再度署名されます
'' (最初の署名の有効性を保持しながら署名済みの PDF に署名する唯一の方法です)。
''
'' 注意:このサンプルをダウンロードし、自分のシステムでローカルに実行する場合は、
'' 有効なライセンスが必要です。ライセンスなしの DsPdf では、ライセンスなしで
'' あることを示すバナーが挿入されることにより、従業員の署名が無効になります。
Public Class TimeSheetIncremental
'' 必要なフォントを保持するフォントコレクションです。
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 _logo As GCDRAW.Image
'' このサンプルのメインエントリポイント。
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 です。
'' 実際のアプリでは、従業員に配布して記入して返送します。
Using empSignedStream = FillEmployeeData(doc)
'' この時点で、 'empSignedStream' は従業員のデータで満たされ、署名されたフォームが含まれています。
'' 従業員が署名したドキュメントを読み込みます。
doc.Load(empSignedStream)
'' スーパーバイザーデータを入力します。
Dim supName = "Jane Donahue"
Dim supSignDate = Util.TimeNow().ToShortDateString()
SetFieldValue(doc, _Names.EmpSuper, supName)
SetFieldValue(doc, _Names.SupSignDate, supSignDate)
'' スーパーバイザーに代わってドキュメントに署名します。
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},
.HashAlgorithm = OID.HashAlgorithms.SHA512
},
.Location = "DsPdfWeb - TimeSheet Incremental",
.SignerName = supName,
.SigningDateTime = Util.TimeNow(),
.SignatureField = DirectCast(doc.AcroForm.Fields.First(Function(f_) f_.Name = _Names.SupSign), SignatureField)
}
'' ドキュメントを変更すると従業員の署名が無効になるため、以下を行うことはできません。
'' supSign.Widget.ButtonAppearance.Caption = supName;
''
'' 終了し、スーパーバイザー署名で文書を保存します。
'' 注意:従業員の署名を無効にしないために、ここで増分更新を使用しなければなりません
'' (Sign() メソッドではデフォルトで true です)。
doc.Sign(sp, stream)
End Using
_logo.Dispose()
Return doc.Pages.Count
End Function
'' 'excludeFields' にリストされているフィールドを除いて、
'' ドキュメント内のテキストフィールドを通常のテキストに置き換えます。
Private Sub FlattenDoc(ByVal doc As GcPdfDocument, ParamArray excludeFields As String())
For Each f In doc.AcroForm.Fields
If TypeOf f Is TextField AndAlso Not excludeFields.Contains(f.Name) 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 AndAlso Not excludeFields.Contains(doc.AcroForm.Fields(i).Name) 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
_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)
Dim sfEmp = New SignatureField() With {.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)
drawField("DATE: ", fields3(1, 1), _Names.EmpSignDate)
drawField("SUPERVISOR SIGNATURE: ", fields3(0, 2), Nothing)
r = fields3(0, 2)
Dim sfSup = New SignatureField() With {.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)
'' PDF ドキュメントを保存します。
Return cells
End Function
'' 従業員情報と勤務時間にサンプルデータを入力します。
Private Function FillEmployeeData(ByVal doc As GcPdfDocument) As Stream
'' このサンプルでは、フォームにランダムなデータを入力します。
Dim empName = "Jaime Smith"
SetFieldValue(doc, _Names.EmpName, empName)
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 = Util.TimeNow().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())
'' '従業員'に代わってドキュメントに署名します。
Dim pfxPath = Path.Combine("Resources", "Misc", "JohnDoe.pfx")
Dim cert = New X509Certificate2(File.ReadAllBytes(pfxPath), "secret",
X509KeyStorageFlags.MachineKeySet Or X509KeyStorageFlags.PersistKeySet Or X509KeyStorageFlags.Exportable)
Dim sp = New SignatureProperties() With {
.SignatureBuilder = New Pkcs7SignatureBuilder() With {
.CertificateChain = New X509Certificate2() {cert}
},
.DocumentAccessPermissions = AccessPermissions.FormFillingAndAnnotations,
.Reason = "I confirm time sheet is correct.",
.Location = "TimeSheetIncremental sample",
.SignerName = empName,
.SigningDateTime = Util.TimeNow()
}
'' 署名フィールドと署名プロパティを結びつけます。
Dim empSign = DirectCast(doc.AcroForm.Fields.First(Function(f_) f_.Name = _Names.EmpSign), SignatureField)
sp.SignatureField = empSign
empSign.Widget.ButtonAppearance.Caption = empName
'' 一部のブラウザの PDF ビューアではフォームフィールドが表示されないため、プレースホルダを描画します。
empSign.Widget.Page.Graphics.DrawString("digitally signed", New TextFormat() With {.FontName = "Segoe UI", .FontSize = 9}, empSign.Widget.Rect)
'' フォームを「平坦化」します。ドキュメントの AcroForm のフィールドをループし、
'' 現在の値を所定の位置に描画してからフィールドを削除します。
'' これにより、テキストフィールドの値を通常の(編集不可能な)コンテンツの一部と
'' して PDF が生成されます(スーパーバイザが入力したフィールドを残します)。
FlattenDoc(doc, _Names.EmpSuper, _Names.SupSignDate)
'' 終了し、従業員の署名でドキュメントを保存します。
Dim ms = New MemoryStream()
'' ここでは増分更新を使用しないことに注意してください(3番目のパラメータは false です)。
'' これはまだ必要ではありません(後でスーパーバイザが署名する際に必要となります)。
doc.Sign(sp, ms, False)
Return ms
End Function
'' 指定された名前のフィールドの値を設定します。
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