using DigitalData.Core.Abstraction.Application.Repository; using EnvelopeGenerator.Application.Common.Configurations; using EnvelopeGenerator.Application.Exceptions; using EnvelopeGenerator.Application.Pdf.PSPDFKitModels; using EnvelopeGenerator.Domain.Constants; using EnvelopeGenerator.Domain.Entities; using GdPicture14; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using System.Drawing; namespace EnvelopeGenerator.Application.Pdf; /// /// /// public class BurnPdfCommand : IRequest { } /// /// /// public class BurnPdfCommandHandler : IRequestHandler { /// /// /// private readonly PDFBurnerParams _pdfBurnerParams; private readonly AnnotationManager _manager; private readonly IRepository _signRepo; private readonly ILogger _logger; /// /// /// /// /// /// /// public BurnPdfCommandHandler(IOptions pdfBurnerParams, AnnotationManager manager, IRepository signRepo, ILogger logger) { _pdfBurnerParams = pdfBurnerParams.Value; _manager = manager; _signRepo = signRepo; _logger = logger; } /// /// /// /// /// /// /// public byte[] BurnAnnotsToPDF(byte[] pSourceBuffer, List pInstantJSONList, int envelopeId) { // read the elements of envelope with their annotations var elements = _signRepo .Where(sig => sig.Document.EnvelopeId == envelopeId) .Include(sig => sig.Annotations) .ToList(); return elements.Any() ? BurnElementAnnotsToPDF(pSourceBuffer, elements) : BurnInstantJSONAnnotsToPDF(pSourceBuffer, pInstantJSONList); } /// /// /// /// /// /// public byte[] BurnElementAnnotsToPDF(byte[] pSourceBuffer, List elements) { // Add background using var doc = PdfEditor.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(); GdPictureStatus oResult; using var oSourceStream = new MemoryStream(pSourceBuffer); // Open PDF oResult = _manager.InitFromStream(oSourceStream); if (oResult != GdPictureStatus.OK) throw new BurnAnnotationException($"Could not open document for burning: [{oResult}]"); // Imported from background (add to configuration) var margin = 0.2; var inchFactor = 72; // Y offset of form fields var keys = new[] { "position", "city", "date" }; // add to configuration var unitYOffsets = 0.2; var yOffsetsOfFF = keys .Select((k, i) => new { Key = k, Value = unitYOffsets * i + 1 }) .ToDictionary(x => x.Key, x => x.Value); // Add annotations foreach (var element in elements) { var frameX = element.Left - 0.7 - margin; var frame = element.Annotations?.FirstOrDefault(a => a.Name == "frame"); var frameY = element.Top - 0.5 - margin; var frameYShift = (frame!.Y ?? default) - frameY * inchFactor; var frameXShift = (frame.X ?? default) - frameX * inchFactor; foreach (var annot in element.Annotations!) { if (!yOffsetsOfFF.TryGetValue(annot.Name, out var yOffsetofFF)) yOffsetofFF = 0; var y = frameY + yOffsetofFF; if (annot.Type == AnnotationType.PSPDFKit.FormField) { AddFormFieldValue( (annot.X ?? default) / inchFactor, y, (annot.Width ?? default) / inchFactor, (annot.Height ?? default) / inchFactor, element.Page, annot.Value ); } else if (annot.Type == AnnotationType.PSPDFKit.Image) { AddImageAnnotation( (annot.X ?? default) / inchFactor, annot.Name == "signature" ? ((annot.Y ?? default) - frameYShift) / inchFactor : y, (annot.Width ?? default) / inchFactor, (annot.Height ?? default) / inchFactor, element.Page, annot.Value ); } else if (annot.Type == AnnotationType.PSPDFKit.Ink) { AddInkAnnotation(element.Page, annot.Value); } } } // Save PDF using var oNewStream = new MemoryStream(); oResult = _manager.SaveDocumentToPDF(oNewStream); if (oResult != GdPictureStatus.OK) throw new BurnAnnotationException($"Could not save document to stream: [{oResult}]"); _manager.Close(); return oNewStream.ToArray(); } /// /// /// /// /// /// public byte[] BurnInstantJSONAnnotsToPDF(byte[] pSourceBuffer, List pInstantJSONList) { GdPictureStatus oResult; using var oSourceStream = new MemoryStream(pSourceBuffer); // Open PDF oResult = _manager.InitFromStream(oSourceStream); if (oResult != GdPictureStatus.OK) { throw new BurnAnnotationException($"Could not open document for burning: [{oResult}]"); } // Add annotation to PDF foreach (var oJSON in pInstantJSONList) { try { if (oJSON is string json) AddInstantJsonAnnotationToPdf(json); } catch (Exception ex) { _logger.LogWarning("Error in AddInstantJSONAnnotationToPDF - oJson: "); _logger.LogWarning(oJSON); throw new BurnAnnotationException("Adding Annotation failed", ex); } } oResult = _manager.BurnAnnotationsToPage(RemoveInitialAnnots: true, VectorMode: true); if (oResult != GdPictureStatus.OK) { throw new BurnAnnotationException($"Could not burn annotations to file: [{oResult}]"); } // Save PDF using var oNewStream = new MemoryStream(); oResult = _manager.SaveDocumentToPDF(oNewStream); if (oResult != GdPictureStatus.OK) { throw new BurnAnnotationException($"Could not save document to stream: [{oResult}]"); } _manager.Close(); return oNewStream.ToArray(); } private void AddInstantJsonAnnotationToPdf(string instantJson) { var annotationData = JsonConvert.DeserializeObject(instantJson); annotationData?.Annotations?.Reverse(); if (annotationData?.Annotations is null) return; foreach (var annotation in annotationData.Annotations) { _logger.LogDebug("Adding AnnotationID: {id}", annotation.Id); switch (annotation.Type) { case AnnotationType.PSPDFKit.Image: if (annotationData?.Attachments is not null) AddImageAnnotation(annotation, annotationData.Attachments); break; case AnnotationType.PSPDFKit.Ink: AddInkAnnotation(annotation); break; case AnnotationType.PSPDFKit.Widget: // Add form field values var formFieldValue = annotationData?.FormFieldValues? .FirstOrDefault(fv => fv.Name == annotation.Id); if (formFieldValue != null && !_pdfBurnerParams.IgnoredLabels.Contains(formFieldValue.Value)) { AddFormFieldValue(annotation, formFieldValue); } break; } } } private void AddFormFieldValue(double x, double y, double width, double height, int page, string value) { _manager.SelectPage(page); // Add the text annotation var ant = _manager.AddTextAnnot((float)x, (float)y, (float)width, (float)height, value); // Set the font properties ant.FontName = _pdfBurnerParams.FontName; ant.FontSize = _pdfBurnerParams.FontSize; ant.FontStyle = _pdfBurnerParams.FontStyle; _manager.SaveAnnotationsToPage(); } private void AddFormFieldValue(Annotation pAnnotation, FormFieldValue formFieldValue) { var ffIndex = _pdfBurnerParams.GetAnnotationIndex(pAnnotation.EgName); // Convert pixels to Inches var oBounds = pAnnotation.Bbox?.Select(ToInches).ToList(); if (oBounds is null || oBounds.Count < 4) return; double oX = oBounds[0]; double oY = oBounds[1] + _pdfBurnerParams.YOffset * ffIndex + _pdfBurnerParams.TopMargin; double oWidth = oBounds[2]; double oHeight = oBounds[3]; _manager.SelectPage(pAnnotation.PageIndex + 1); // Add the text annotation var ant = _manager.AddTextAnnot((float)oX, (float)oY, (float)oWidth, (float)oHeight, formFieldValue.Value); // Set the font properties ant.FontName = _pdfBurnerParams.FontName; ant.FontSize = _pdfBurnerParams.FontSize; ant.FontStyle = _pdfBurnerParams.FontStyle; _manager.SaveAnnotationsToPage(); } private void AddImageAnnotation(double x, double y, double width, double height, int page, string base64) { _manager.SelectPage(page); _manager.AddEmbeddedImageAnnotFromBase64(base64, (float)x, (float)y, (float)width, (float)height); } private void AddImageAnnotation(Annotation pAnnotation, Dictionary pAttachments) { var oAttachment = pAttachments .Where(a => a.Key == pAnnotation.ImageAttachmentId) .SingleOrDefault(); if (oAttachment.Value == null) return; // Convert pixels to Inches var oBounds = pAnnotation.Bbox?.Select(ToInches).ToList(); if (oBounds is null || oBounds.Count < 4) return; var oX = oBounds[0]; var oY = oBounds[1]; var oWidth = oBounds[2]; var oHeight = oBounds[3]; _manager.SelectPage(pAnnotation.PageIndex + 1); _manager.AddEmbeddedImageAnnotFromBase64(oAttachment.Value.Binary, (float)oX, (float)oY, (float)oWidth, (float)oHeight); } private void AddInkAnnotation(int page, string value) { throw new NotImplementedException(); } private void AddInkAnnotation(Annotation pAnnotation) { throw new NotImplementedException(); } #region Helpers private PointF ToPointF(List pPoints) { var oPoints = pPoints.Select(ToInches).ToList(); return new PointF(oPoints[0], oPoints[1]); } private double ToInches(double pValue) { return pValue / 72.0; } private float ToInches(float pValue) { return pValue / 72f; } #endregion /// /// /// /// /// /// /// public Task Handle(BurnPdfCommand request, CancellationToken cancellationToken) { throw new NotImplementedException(); } }