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 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 Dim frameX = (element.Left - 0.7 - margin) Dim frame = element.Annotations.FirstOrDefault(Function(a) a.Name = "frame") Dim frameY = element.Top - 0.5 - margin Dim frameYShift = frame.Y - frameY * inchFactor Dim frameXShift = frame.X - frameX * inchFactor For Each annot In element.Annotations Dim yOffsetofFF As Double = If(yOffsetsOfFF.TryGetValue(annot.Name, yOffsetofFF), yOffsetofFF, 0) 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