303 lines
11 KiB
C#
303 lines
11 KiB
C#
using DigitalData.Core.Abstraction.Application.Repository;
|
|
using DigitalData.Core.Exceptions;
|
|
using EnvelopeGenerator.Application.Common.Configurations;
|
|
using EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
|
|
using EnvelopeGenerator.Application.Common.Extensions;
|
|
using EnvelopeGenerator.Application.Exceptions;
|
|
using EnvelopeGenerator.Domain.Constants;
|
|
using EnvelopeGenerator.Domain.Entities;
|
|
using GdPicture14;
|
|
using MediatR;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace EnvelopeGenerator.Application.Pdf;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public record BurnPdfCommand(int? EnvelopeId = null, string? EnvelopeUuid = null) : IRequest<byte[]>
|
|
{
|
|
internal bool Debug { get; set; }
|
|
|
|
internal Envelope? Envelope { get; set; }
|
|
|
|
#if WINDOWS
|
|
internal byte[]? Report { get; set; }
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public static class BurnPdfCommandExtensions
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="envelopeId"></param>
|
|
/// <param name="cancel"></param>
|
|
/// <returns></returns>
|
|
public static Task<byte[]> BurnPdf(this ISender sender, int envelopeId, CancellationToken cancel = default)
|
|
=> sender.Send(new BurnPdfCommand(EnvelopeId: envelopeId), cancel);
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="envelopeUuid"></param>
|
|
/// <param name="cancel"></param>
|
|
/// <returns></returns>
|
|
public static Task<byte[]> BurnPdf(this ISender sender, string envelopeUuid, CancellationToken cancel = default)
|
|
=> sender.Send(new BurnPdfCommand(EnvelopeUuid: envelopeUuid), cancel);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class BurnPdfCommandHandler : IRequestHandler<BurnPdfCommand, byte[]>
|
|
{
|
|
private readonly PDFBurnerParams _options;
|
|
|
|
private readonly AnnotationManager _manager;
|
|
|
|
private readonly ILogger<BurnPdfCommandHandler> _logger;
|
|
|
|
private readonly IRepository<Envelope> _envRepo;
|
|
|
|
private readonly IRepository<Domain.Entities.DocumentStatus> _docStatusRepo;
|
|
|
|
private readonly IConfiguration _config;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="pdfBurnerParams"></param>
|
|
/// <param name="manager"></param>
|
|
/// <param name="logger"></param>
|
|
/// <param name="envRepo"></param>
|
|
/// <param name="docStatusRepo"></param>
|
|
/// <param name="config"></param>
|
|
public BurnPdfCommandHandler(IOptions<PDFBurnerParams> pdfBurnerParams, AnnotationManager manager, ILogger<BurnPdfCommandHandler> logger, IRepository<Envelope> envRepo, IRepository<Domain.Entities.DocumentStatus> docStatusRepo, IConfiguration config)
|
|
{
|
|
_options = pdfBurnerParams.Value;
|
|
_manager = manager;
|
|
_docStatusRepo = docStatusRepo;
|
|
_logger = logger;
|
|
_envRepo = envRepo;
|
|
_config = config;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancel"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
public async Task<byte[]> Handle(BurnPdfCommand request, CancellationToken cancel)
|
|
{
|
|
request.Debug = _config.GetValue<bool>("Debug");
|
|
var envQuery =
|
|
request.EnvelopeId is not null ? _envRepo.Where(env => env.Id == request.EnvelopeId) :
|
|
request.EnvelopeUuid is not null ? _envRepo.Where(env => env.Uuid == request.EnvelopeUuid) :
|
|
throw new BadRequestException("Request validation failed: Either Envelope Id or Envelope Uuid must be provided.");
|
|
|
|
request.Envelope = await envQuery
|
|
.Include(env => env.Documents!).ThenInclude(doc => doc.Elements!).ThenInclude(element => element.Annotations)
|
|
.FirstOrDefaultAsync(cancel)
|
|
?? throw new BadRequestException($"Envelope could not be found. Request details:\n" +
|
|
request.ToJson(Format.Json.ForDiagnostics));
|
|
|
|
var doc = request.Envelope.Documents?.FirstOrDefault()
|
|
?? throw new NotFoundException($"Document could not be located within the specified envelope. Request details:\n" +
|
|
request.ToJson(Format.Json.ForDiagnostics));
|
|
|
|
if (doc.ByteData is null)
|
|
throw new InvalidOperationException($"Document byte data is missing, indicating a potential data integrity issue. Request details:\n" +
|
|
request.ToJson(Format.Json.ForDiagnostics));
|
|
|
|
return doc.Elements?.SelectMany(e => e.Annotations ?? Enumerable.Empty<ElementAnnotation>()).Where(annot => annot is not null).Any() ?? false
|
|
? BurnElementAnnotsToPDF(doc.ByteData, doc.Elements)
|
|
: BurnInstantJSONAnnotsToPDF(doc.ByteData, await _docStatusRepo
|
|
.Where(status => status.EnvelopeId == request.Envelope.Id)
|
|
.Select(status => status.Value)
|
|
.ToListAsync(cancel));
|
|
}
|
|
|
|
private byte[] BurnElementAnnotsToPDF(byte[] pSourceBuffer, List<Signature> 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)
|
|
{
|
|
_manager.AddFormFieldValue(
|
|
(annot.X ?? default) / inchFactor,
|
|
y,
|
|
(annot.Width ?? default) / inchFactor,
|
|
(annot.Height ?? default) / inchFactor,
|
|
element.Page,
|
|
annot.Value,
|
|
_options
|
|
);
|
|
}
|
|
else if (annot.Type == AnnotationType.PSPDFKit.Image)
|
|
{
|
|
_manager.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)
|
|
{
|
|
_manager.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();
|
|
}
|
|
|
|
private byte[] BurnInstantJSONAnnotsToPDF(byte[] pSourceBuffer, List<string> 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<InstantData>(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)
|
|
_manager.AddImageAnnotation(annotation, annotationData.Attachments);
|
|
break;
|
|
|
|
case AnnotationType.PSPDFKit.Ink:
|
|
_manager.AddInkAnnotation(annotation);
|
|
break;
|
|
|
|
case AnnotationType.PSPDFKit.Widget:
|
|
// Add form field values
|
|
var formFieldValue = annotationData?.FormFieldValues?
|
|
.FirstOrDefault(fv => fv.Name == annotation.Id);
|
|
|
|
if (formFieldValue != null && !_options.IgnoredLabels.Contains(formFieldValue.Value))
|
|
{
|
|
_manager.AddFormFieldValue(annotation, formFieldValue, _options);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} |