Refactored annotation processing in PDFBurner.vb to handle null elements and annotations safely, preventing possible errors and improving code clarity. Updated GetSignedDate in ReceiverModel.vb to ensure consistent DateTime return values, handle DBNull and type conversions, and improve error handling.
468 lines
19 KiB
VB.net
468 lines
19 KiB
VB.net
Imports System.Collections.Immutable
|
|
Imports System.Drawing
|
|
Imports System.IO
|
|
Imports DevExpress.DataProcessing
|
|
Imports DigitalData.Core.Abstraction.Application.Repository
|
|
Imports DigitalData.Core.Abstractions
|
|
Imports DigitalData.Modules.Base
|
|
Imports DigitalData.Modules.Logging
|
|
Imports EnvelopeGenerator.CommonServices.Jobs.FinalizeDocument.FinalizeDocumentExceptions
|
|
Imports EnvelopeGenerator.Domain.Entities
|
|
Imports EnvelopeGenerator.PdfEditor
|
|
Imports GdPicture14
|
|
Imports Microsoft.EntityFrameworkCore
|
|
Imports Newtonsoft.Json
|
|
|
|
Namespace Jobs.FinalizeDocument
|
|
Public Class PDFBurner
|
|
Inherits BaseClass
|
|
|
|
Private ReadOnly Manager As AnnotationManager
|
|
Private ReadOnly LicenseManager As LicenseManager
|
|
|
|
Private Property _pdfBurnerParams As PDFBurnerParams
|
|
|
|
Public Sub New(pLogConfig As LogConfig, pGDPictureLicenseKey As String, pdfBurnerParams As PDFBurnerParams)
|
|
MyBase.New(pLogConfig)
|
|
|
|
LicenseManager = New LicenseManager()
|
|
LicenseManager.RegisterKEY(pGDPictureLicenseKey)
|
|
|
|
Manager = New AnnotationManager()
|
|
|
|
_pdfBurnerParams = pdfBurnerParams
|
|
End Sub
|
|
|
|
#Region "Burn PDF"
|
|
Public Function BurnAnnotsToPDF(pSourceBuffer As Byte(), pInstantJSONList As List(Of String), envelopeId As Integer) As Byte()
|
|
'read the elements of envelope with their annotations
|
|
Using scope = Factory.Shared.ScopeFactory.CreateScope()
|
|
|
|
Dim envRepo = scope.ServiceProvider.Repository(Of Envelope)()
|
|
Dim envelope = envRepo.Where(Function(env) env.Id = envelopeId).FirstOrDefault()
|
|
If envelope Is Nothing Then
|
|
Throw New BurnAnnotationException($"Envelope with Id {envelopeId} not found.")
|
|
ElseIf envelope.ReadOnly Then
|
|
Return pSourceBuffer
|
|
End If
|
|
|
|
Dim sigRepo = scope.ServiceProvider.Repository(Of Signature)()
|
|
Dim elements = sigRepo _
|
|
.Where(Function(sig) sig.Document.EnvelopeId = envelopeId) _
|
|
.Include(Function(sig) sig.Annotations) _
|
|
.ToList()
|
|
|
|
Return If(elements.Any(),
|
|
BurnElementAnnotsToPDF(pSourceBuffer, elements),
|
|
BurnInstantJSONAnnotsToPDF(pSourceBuffer, pInstantJSONList))
|
|
End Using
|
|
End Function
|
|
|
|
Public Function BurnElementAnnotsToPDF(pSourceBuffer As Byte(), elements As List(Of Signature)) As Byte()
|
|
' Add background
|
|
Using doc As Pdf(Of MemoryStream, MemoryStream) = Pdf.FromMemory(pSourceBuffer)
|
|
'TODO: take the length from the largest y
|
|
pSourceBuffer = doc.Background(elements, 1.9500000000000002 * 0.93, 2.52 * 0.67).ExportStream().ToArray()
|
|
|
|
Dim oResult As GdPictureStatus
|
|
Using oSourceStream As New MemoryStream(pSourceBuffer)
|
|
' Open PDF
|
|
oResult = Manager.InitFromStream(oSourceStream)
|
|
If oResult <> GdPictureStatus.OK Then
|
|
Throw New BurnAnnotationException($"Could not open document for burning: [{oResult}]")
|
|
End If
|
|
|
|
' Imported from background (add to configuration)
|
|
Dim margin As Double = 0.2
|
|
Dim inchFactor As Double = 72
|
|
|
|
' Y offset of form fields
|
|
Dim keys = {"position", "city", "date"} ' add to configuration
|
|
Dim unitYOffsets = 0.2
|
|
Dim yOffsetsOfFF = keys.
|
|
Select(Function(k, i) New With {Key .Key = k, Key .Value = unitYOffsets * i + 1}).
|
|
ToDictionary(Function(x) x.Key, Function(x) x.Value)
|
|
|
|
'Add annotations
|
|
For Each element In elements
|
|
If element Is Nothing Then
|
|
Continue For
|
|
End If
|
|
|
|
Dim elementAnnotations = If(element.Annotations, Enumerable.Empty(Of ElementAnnotation)())
|
|
If Not elementAnnotations.Any() Then
|
|
Continue For
|
|
End If
|
|
|
|
Dim frameX = (element.Left - 0.7 - margin)
|
|
|
|
Dim frame = elementAnnotations.FirstOrDefault(Function(a) a.Name = "frame")
|
|
Dim frameY = element.Top - 0.5 - margin
|
|
Dim frameYShift As Double = 0
|
|
Dim frameXShift As Double = 0
|
|
|
|
If frame IsNot Nothing Then
|
|
frameYShift = frame.Y - frameY * inchFactor
|
|
frameXShift = frame.X - frameX * inchFactor
|
|
End If
|
|
|
|
For Each annot In elementAnnotations
|
|
If annot Is Nothing Then
|
|
Continue For
|
|
End If
|
|
|
|
Dim yOffsetofFF As Double = 0
|
|
If Not String.IsNullOrEmpty(annot.Name) Then
|
|
yOffsetsOfFF.TryGetValue(annot.Name, yOffsetofFF)
|
|
End If
|
|
|
|
Dim y = frameY + yOffsetofFF
|
|
|
|
If annot.Type = AnnotationType.FormField Then
|
|
AddFormFieldValue(annot.X / inchFactor, y, annot.Width / inchFactor, annot.Height / inchFactor, element.Page, annot.Value)
|
|
ElseIf annot.Type = AnnotationType.Image Then
|
|
AddImageAnnotation(
|
|
annot.X / inchFactor,
|
|
If(annot.Name = "signature", (annot.Y - frameYShift) / inchFactor, y),
|
|
annot.Width / inchFactor,
|
|
annot.Height / inchFactor,
|
|
element.Page,
|
|
annot.Value
|
|
)
|
|
ElseIf annot.Type = AnnotationType.Ink Then
|
|
AddInkAnnotation(element.Page, annot.Value)
|
|
End If
|
|
Next
|
|
Next
|
|
|
|
'Save PDF
|
|
Using oNewStream As New MemoryStream()
|
|
oResult = Manager.SaveDocumentToPDF(oNewStream)
|
|
If oResult <> GdPictureStatus.OK Then
|
|
Throw New BurnAnnotationException($"Could not save document to stream: [{oResult}]")
|
|
End If
|
|
|
|
Manager.Close()
|
|
|
|
Return oNewStream.ToArray()
|
|
End Using
|
|
End Using
|
|
End Using
|
|
End Function
|
|
|
|
Public Function BurnInstantJSONAnnotsToPDF(pSourceBuffer As Byte(), pInstantJSONList As List(Of String)) As Byte()
|
|
Dim oResult As GdPictureStatus
|
|
Using oSourceStream As New MemoryStream(pSourceBuffer)
|
|
' Open PDF
|
|
oResult = Manager.InitFromStream(oSourceStream)
|
|
If oResult <> GdPictureStatus.OK Then
|
|
Throw New BurnAnnotationException($"Could not open document for burning: [{oResult}]")
|
|
End If
|
|
|
|
' Add annotation to PDF
|
|
For Each oJSON In pInstantJSONList
|
|
Try
|
|
AddInstantJSONAnnotationToPDF(oJSON)
|
|
Catch ex As Exception
|
|
Logger.Warn($"Error in AddInstantJSONAnnotationToPDF - oJson: ")
|
|
Logger.Warn(oJSON)
|
|
Throw New BurnAnnotationException($"Adding Annotation failed", ex)
|
|
End Try
|
|
Next
|
|
oResult = Manager.BurnAnnotationsToPage(RemoveInitialAnnots:=True, VectorMode:=True)
|
|
If oResult <> GdPictureStatus.OK Then
|
|
Throw New BurnAnnotationException($"Could not burn annotations to file: [{oResult}]")
|
|
End If
|
|
|
|
'Save PDF
|
|
Using oNewStream As New MemoryStream()
|
|
oResult = Manager.SaveDocumentToPDF(oNewStream)
|
|
If oResult <> GdPictureStatus.OK Then
|
|
Throw New BurnAnnotationException($"Could not save document to stream: [{oResult}]")
|
|
End If
|
|
|
|
Manager.Close()
|
|
|
|
Return oNewStream.ToArray()
|
|
End Using
|
|
End Using
|
|
End Function
|
|
#End Region
|
|
|
|
#Region "Add Value"
|
|
Private Sub AddInstantJSONAnnotationToPDF(pInstantJSON As String)
|
|
Dim oAnnotationData = JsonConvert.DeserializeObject(Of AnnotationData)(pInstantJSON)
|
|
|
|
oAnnotationData.annotations.Reverse()
|
|
|
|
For Each oAnnotation In oAnnotationData.annotations
|
|
Logger.Debug("Adding AnnotationID: " + oAnnotation.id)
|
|
|
|
Select Case oAnnotation.type
|
|
Case AnnotationType.Image
|
|
AddImageAnnotation(oAnnotation, oAnnotationData.attachments)
|
|
Exit Select
|
|
Case AnnotationType.Ink
|
|
AddInkAnnotation(oAnnotation)
|
|
Exit Select
|
|
Case AnnotationType.Widget
|
|
'Add form field values
|
|
Dim formFieldValue = oAnnotationData.formFieldValues.FirstOrDefault(Function(fv) fv.name = oAnnotation.id)
|
|
If formFieldValue IsNot Nothing AndAlso Not _pdfBurnerParams.IgnoredLabels.Contains(formFieldValue.value) Then
|
|
AddFormFieldValue(oAnnotation, formFieldValue)
|
|
End If
|
|
Exit Select
|
|
End Select
|
|
Next
|
|
|
|
End Sub
|
|
|
|
Private Sub AddImageAnnotation(x As Double, y As Double, width As Double, height As Double, page As Integer, base64 As String)
|
|
Manager.SelectPage(page)
|
|
Manager.AddEmbeddedImageAnnotFromBase64(base64, x, y, width, height)
|
|
End Sub
|
|
|
|
Private Sub AddImageAnnotation(pAnnotation As Annotation, pAttachments As Dictionary(Of String, Attachment))
|
|
Dim oAttachment = pAttachments.Where(Function(a) a.Key = pAnnotation.imageAttachmentId).
|
|
SingleOrDefault()
|
|
|
|
' Convert pixels to Inches
|
|
Dim oBounds = pAnnotation.bbox.Select(AddressOf ToInches).ToList()
|
|
|
|
Dim oX = oBounds.Item(0)
|
|
Dim oY = oBounds.Item(1)
|
|
Dim oWidth = oBounds.Item(2)
|
|
Dim oHeight = oBounds.Item(3)
|
|
|
|
Manager.SelectPage(pAnnotation.pageIndex + 1)
|
|
Manager.AddEmbeddedImageAnnotFromBase64(oAttachment.Value.binary, oX, oY, oWidth, oHeight)
|
|
End Sub
|
|
|
|
Private Sub AddInkAnnotation(page As Integer, value As String)
|
|
|
|
Dim ink = JsonConvert.DeserializeObject(Of Ink)(value)
|
|
|
|
Dim oSegments = ink.lines.points
|
|
Dim oColor = ColorTranslator.FromHtml(ink.strokeColor)
|
|
Manager.SelectPage(page)
|
|
|
|
For Each oSegment As List(Of List(Of Single)) In oSegments
|
|
Dim oPoints = oSegment.
|
|
Select(AddressOf ToPointF).
|
|
ToArray()
|
|
|
|
Manager.AddFreeHandAnnot(oColor, oPoints)
|
|
Next
|
|
End Sub
|
|
|
|
Private Sub AddInkAnnotation(pAnnotation As Annotation)
|
|
Dim oSegments = pAnnotation.lines.points
|
|
Dim oColor = ColorTranslator.FromHtml(pAnnotation.strokeColor)
|
|
Manager.SelectPage(pAnnotation.pageIndex + 1)
|
|
|
|
For Each oSegment As List(Of List(Of Single)) In oSegments
|
|
Dim oPoints = oSegment.
|
|
Select(AddressOf ToPointF).
|
|
ToArray()
|
|
|
|
Manager.AddFreeHandAnnot(oColor, oPoints)
|
|
Next
|
|
End Sub
|
|
|
|
Private Sub AddFormFieldValue(x As Double, y As Double, width As Double, height As Double, page As Integer, value As String)
|
|
Manager.SelectPage(page)
|
|
|
|
' Add the text annotation
|
|
Dim ant = Manager.AddTextAnnot(x, y, width, height, value)
|
|
|
|
' Set the font properties
|
|
ant.FontName = _pdfBurnerParams.FontName
|
|
ant.FontSize = _pdfBurnerParams.FontSize
|
|
ant.FontStyle = _pdfBurnerParams.FontStyle
|
|
Manager.SaveAnnotationsToPage()
|
|
End Sub
|
|
|
|
Private Sub AddFormFieldValue(pAnnotation As Annotation, formFieldValue As FormFieldValue)
|
|
Dim ffIndex As Integer = EGName.Index(pAnnotation.egName)
|
|
|
|
' Convert pixels to Inches
|
|
Dim oBounds = pAnnotation.bbox.Select(AddressOf ToInches).ToList()
|
|
|
|
Dim oX = oBounds.Item(0)
|
|
Dim oY = oBounds.Item(1) + _pdfBurnerParams.YOffset * ffIndex + _pdfBurnerParams.TopMargin
|
|
Dim oWidth = oBounds.Item(2)
|
|
Dim oHeight = oBounds.Item(3)
|
|
|
|
Manager.SelectPage(pAnnotation.pageIndex + 1)
|
|
' Add the text annotation
|
|
Dim ant = Manager.AddTextAnnot(oX, oY, oWidth, oHeight, formFieldValue.value)
|
|
|
|
' Set the font properties
|
|
ant.FontName = _pdfBurnerParams.FontName
|
|
ant.FontSize = _pdfBurnerParams.FontSize
|
|
ant.FontStyle = _pdfBurnerParams.FontStyle
|
|
Manager.SaveAnnotationsToPage()
|
|
End Sub
|
|
#End Region
|
|
|
|
#Region "Helpers"
|
|
Private Function ToPointF(pPoints As List(Of Single)) As PointF
|
|
Dim oPoints = pPoints.Select(AddressOf ToInches).ToList()
|
|
Return New PointF(oPoints.Item(0), oPoints.Item(1))
|
|
End Function
|
|
|
|
Private Function ToInches(pValue As Double) As Double
|
|
Return pValue / 72
|
|
End Function
|
|
|
|
Private Function ToInches(pValue As Single) As Single
|
|
Return pValue / 72
|
|
End Function
|
|
#End Region
|
|
|
|
#Region "Model"
|
|
Friend Class AnnotationType
|
|
Public Const Image As String = "pspdfkit/image"
|
|
Public Const Ink As String = "pspdfkit/ink"
|
|
Public Const Widget As String = "pspdfkit/widget"
|
|
Public Const FormField As String = "pspdfkit/form-field-value"
|
|
End Class
|
|
|
|
Friend Class AnnotationData
|
|
Public Property annotations As List(Of Annotation)
|
|
|
|
Public ReadOnly Property AnnotationsByReceiver As IEnumerable(Of List(Of Annotation))
|
|
Get
|
|
Return annotations _
|
|
.Where(Function(annot) annot.hasStructuredID).ToList() _
|
|
.GroupBy(Function(a) a.receiverId) _
|
|
.Select(Function(g) g.ToList())
|
|
End Get
|
|
End Property
|
|
|
|
Public ReadOnly Property UnstructuredAnnotations As IEnumerable(Of List(Of Annotation))
|
|
Get
|
|
Return annotations _
|
|
.Where(Function(annot) Not annot.hasStructuredID).ToList() _
|
|
.GroupBy(Function(a) a.receiverId) _
|
|
.Select(Function(g) g.ToList())
|
|
End Get
|
|
End Property
|
|
|
|
Public Property attachments As Dictionary(Of String, Attachment)
|
|
Public Property formFieldValues As List(Of FormFieldValue)
|
|
End Class
|
|
|
|
Friend Class Annotation
|
|
|
|
Private _id As String = Nothing
|
|
|
|
Public envelopeId As Integer = Nothing
|
|
|
|
Public receiverId As Integer = Nothing
|
|
|
|
Public index As Integer = Nothing
|
|
|
|
Public egName As String = PDFBurner.EGName.NoName
|
|
|
|
Public hasStructuredID As Boolean = False
|
|
|
|
Public ReadOnly Property isLabel As Boolean
|
|
Get
|
|
If String.IsNullOrEmpty(egName) Then
|
|
Return False
|
|
End If
|
|
|
|
Dim parts As String() = egName.Split("_"c)
|
|
Return parts.Length > 1 AndAlso parts(1) = "label"
|
|
End Get
|
|
End Property
|
|
|
|
Public Property id As String
|
|
Get
|
|
Return _id
|
|
End Get
|
|
Set(value As String)
|
|
_id = value
|
|
|
|
If String.IsNullOrWhiteSpace(_id) Then
|
|
Throw New BurnAnnotationException("The identifier of annotation is null or empty.")
|
|
End If
|
|
|
|
Dim parts As String() = value.Split("#"c)
|
|
|
|
If (parts.Length <> 4) Then
|
|
Return
|
|
'Throw New BurnAnnotationException($"The identifier of annotation has more or less than 4 sub-part. Id: {_id}")
|
|
End If
|
|
|
|
If Not Integer.TryParse(parts(0), envelopeId) Then
|
|
Throw New BurnAnnotationException($"The envelope ID of annotation is not integer. Id: {_id}")
|
|
End If
|
|
|
|
If Not Integer.TryParse(parts(1), receiverId) Then
|
|
Throw New BurnAnnotationException($"The receiver ID of annotation is not integer. Id: {_id}")
|
|
End If
|
|
|
|
If Not Integer.TryParse(parts(2), index) Then
|
|
Throw New BurnAnnotationException($"The index of annotation is not integer. Id: {_id}")
|
|
End If
|
|
|
|
egName = parts(3)
|
|
|
|
hasStructuredID = True
|
|
End Set
|
|
End Property
|
|
|
|
Public Property bbox As List(Of Double)
|
|
|
|
Public Property type As String
|
|
|
|
Public Property isSignature As Boolean
|
|
|
|
Public Property imageAttachmentId As String
|
|
|
|
Public Property lines As Lines
|
|
|
|
Public Property pageIndex As Integer
|
|
|
|
Public Property strokeColor As String
|
|
End Class
|
|
|
|
Friend Class Ink
|
|
Public Property lines As Lines
|
|
|
|
Public Property strokeColor As String
|
|
End Class
|
|
|
|
Public Class EGName
|
|
Public Shared ReadOnly NoName As String = Guid.NewGuid().ToString()
|
|
|
|
Public Shared ReadOnly Seal As String = "signature"
|
|
|
|
Public Shared ReadOnly Index As ImmutableDictionary(Of String, Integer) =
|
|
New Dictionary(Of String, Integer) From {
|
|
{NoName, 0},
|
|
{Seal, 0},
|
|
{"position", 1},
|
|
{"city", 2},
|
|
{"date", 3}
|
|
}.ToImmutableDictionary()
|
|
End Class
|
|
|
|
Friend Class Lines
|
|
Public Property points As List(Of List(Of List(Of Single)))
|
|
End Class
|
|
|
|
Friend Class Attachment
|
|
Public Property binary As String
|
|
Public Property contentType As String
|
|
End Class
|
|
|
|
Friend Class FormFieldValue
|
|
Public Property name As String
|
|
Public Property value As String
|
|
End Class
|
|
#End Region
|
|
End Class
|
|
End Namespace |