- Programmiersprache von VSC zu C# geändert - Framework von .NET Framework zu .NET geändert
446 lines
15 KiB
C#
446 lines
15 KiB
C#
using System.Collections.Immutable;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using DigitalData.Modules.Base;
|
|
using DigitalData.Modules.Logging;
|
|
using EnvelopeGenerator.Domain.Entities;
|
|
using EnvelopeGenerator.Infrastructure;
|
|
using EnvelopeGenerator.PdfEditor;
|
|
using EnvelopeGenerator.ServiceHost.Exceptions;
|
|
using GdPicture14;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
|
|
|
public class PDFBurner : BaseClass
|
|
{
|
|
private readonly AnnotationManager _manager;
|
|
private readonly LicenseManager _licenseManager;
|
|
private readonly DbContextOptions<EGDbContext> _dbContextOptions;
|
|
|
|
private readonly PDFBurnerParams _pdfBurnerParams;
|
|
|
|
public PDFBurner(LogConfig logConfig, string gdPictureLicenseKey, PDFBurnerParams pdfBurnerParams, string connectionString) : base(logConfig)
|
|
{
|
|
_licenseManager = new LicenseManager();
|
|
_licenseManager.RegisterKEY(gdPictureLicenseKey);
|
|
|
|
_manager = new AnnotationManager();
|
|
|
|
_pdfBurnerParams = pdfBurnerParams;
|
|
_dbContextOptions = new DbContextOptionsBuilder<EGDbContext>()
|
|
.UseSqlServer(connectionString)
|
|
.EnableDetailedErrors()
|
|
.EnableSensitiveDataLogging()
|
|
.Options;
|
|
}
|
|
|
|
public byte[] BurnAnnotsToPDF(byte[] sourceBuffer, List<string> instantJsonList, int envelopeId)
|
|
{
|
|
using var context = new EGDbContext(_dbContextOptions, Options.Create(new DbTriggerParams()));
|
|
|
|
var envelope = context.Envelopes.FirstOrDefault(env => env.Id == envelopeId);
|
|
if (envelope is null)
|
|
{
|
|
throw new BurnAnnotationException($"Envelope with Id {envelopeId} not found.");
|
|
}
|
|
|
|
if (envelope.ReadOnly)
|
|
{
|
|
return sourceBuffer;
|
|
}
|
|
|
|
var elements = context.DocumentReceiverElements
|
|
.Where(sig => sig.Document.EnvelopeId == envelopeId)
|
|
.Include(sig => sig.Annotations)
|
|
.ToList();
|
|
|
|
return elements.Any()
|
|
? BurnElementAnnotsToPDF(sourceBuffer, elements)
|
|
: BurnInstantJSONAnnotsToPDF(sourceBuffer, instantJsonList);
|
|
}
|
|
|
|
public byte[] BurnElementAnnotsToPDF(byte[] sourceBuffer, List<Signature> elements)
|
|
{
|
|
using (var doc = Pdf.FromMemory(sourceBuffer))
|
|
{
|
|
sourceBuffer = doc.Background(elements, 1.9500000000000002 * 0.93, 2.52 * 0.67).ExportStream().ToArray();
|
|
|
|
using var sourceStream = new MemoryStream(sourceBuffer);
|
|
var result = _manager.InitFromStream(sourceStream);
|
|
if (result != GdPictureStatus.OK)
|
|
{
|
|
throw new BurnAnnotationException($"Could not open document for burning: [{result}]");
|
|
}
|
|
|
|
var margin = 0.2;
|
|
var inchFactor = 72d;
|
|
|
|
var keys = new[] { "position", "city", "date" };
|
|
var unitYOffsets = 0.2;
|
|
var yOffsetsOfFF = keys
|
|
.Select((k, i) => new { Key = k, Value = unitYOffsets * i + 1 })
|
|
.ToDictionary(x => x.Key, x => x.Value);
|
|
|
|
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 ?? 0) - frameY * inchFactor;
|
|
var frameXShift = (frame?.X ?? 0) - frameX * inchFactor;
|
|
|
|
foreach (var annot in element.Annotations)
|
|
{
|
|
var yOffsetOfFF = yOffsetsOfFF.TryGetValue(annot.Name, out var offset) ? offset : 0;
|
|
var y = frameY + yOffsetOfFF;
|
|
|
|
if (annot.Type == AnnotationType.FormField)
|
|
{
|
|
AddFormFieldValue(annot.X / inchFactor, y, annot.Width / inchFactor, annot.Height / inchFactor, element.Page, annot.Value);
|
|
}
|
|
else if (annot.Type == AnnotationType.Image)
|
|
{
|
|
AddImageAnnotation(
|
|
annot.X / inchFactor,
|
|
annot.Name == "signature" ? (annot.Y - frameYShift) / inchFactor : y,
|
|
annot.Width / inchFactor,
|
|
annot.Height / inchFactor,
|
|
element.Page,
|
|
annot.Value);
|
|
}
|
|
else if (annot.Type == AnnotationType.Ink)
|
|
{
|
|
AddInkAnnotation(element.Page, annot.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
using var newStream = new MemoryStream();
|
|
result = _manager.SaveDocumentToPDF(newStream);
|
|
if (result != GdPictureStatus.OK)
|
|
{
|
|
throw new BurnAnnotationException($"Could not save document to stream: [{result}]");
|
|
}
|
|
|
|
_manager.Close();
|
|
|
|
return newStream.ToArray();
|
|
}
|
|
}
|
|
|
|
public byte[] BurnInstantJSONAnnotsToPDF(byte[] sourceBuffer, List<string> instantJsonList)
|
|
{
|
|
using var sourceStream = new MemoryStream(sourceBuffer);
|
|
var result = _manager.InitFromStream(sourceStream);
|
|
if (result != GdPictureStatus.OK)
|
|
{
|
|
throw new BurnAnnotationException($"Could not open document for burning: [{result}]");
|
|
}
|
|
|
|
foreach (var json in instantJsonList)
|
|
{
|
|
try
|
|
{
|
|
AddInstantJSONAnnotationToPDF(json);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Warn("Error in AddInstantJSONAnnotationToPDF - oJson: ");
|
|
Logger.Warn(json);
|
|
throw new BurnAnnotationException("Adding Annotation failed", ex);
|
|
}
|
|
}
|
|
|
|
result = _manager.BurnAnnotationsToPage(RemoveInitialAnnots: true, VectorMode: true);
|
|
if (result != GdPictureStatus.OK)
|
|
{
|
|
throw new BurnAnnotationException($"Could not burn annotations to file: [{result}]");
|
|
}
|
|
|
|
using var newStream = new MemoryStream();
|
|
result = _manager.SaveDocumentToPDF(newStream);
|
|
if (result != GdPictureStatus.OK)
|
|
{
|
|
throw new BurnAnnotationException($"Could not save document to stream: [{result}]");
|
|
}
|
|
|
|
_manager.Close();
|
|
|
|
return newStream.ToArray();
|
|
}
|
|
|
|
private void AddInstantJSONAnnotationToPDF(string instantJson)
|
|
{
|
|
var annotationData = JsonConvert.DeserializeObject<AnnotationData>(instantJson);
|
|
if (annotationData is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
annotationData.annotations.Reverse();
|
|
|
|
foreach (var annotation in annotationData.annotations)
|
|
{
|
|
Logger.Debug("Adding AnnotationID: " + annotation.id);
|
|
|
|
switch (annotation.type)
|
|
{
|
|
case AnnotationType.Image:
|
|
AddImageAnnotation(annotation, annotationData.attachments);
|
|
break;
|
|
case AnnotationType.Ink:
|
|
AddInkAnnotation(annotation);
|
|
break;
|
|
case AnnotationType.Widget:
|
|
var formFieldValue = annotationData.formFieldValues.FirstOrDefault(fv => fv.name == annotation.id);
|
|
if (formFieldValue is not null && !_pdfBurnerParams.IgnoredLabels.Contains(formFieldValue.value))
|
|
{
|
|
AddFormFieldValue(annotation, formFieldValue);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 annotation, Dictionary<string, Attachment> attachments)
|
|
{
|
|
var attachment = attachments.Where(a => a.Key == annotation.imageAttachmentId).SingleOrDefault();
|
|
|
|
var bounds = annotation.bbox.Select(ToInches).ToList();
|
|
|
|
var x = bounds[0];
|
|
var y = bounds[1];
|
|
var width = bounds[2];
|
|
var height = bounds[3];
|
|
|
|
_manager.SelectPage(annotation.pageIndex + 1);
|
|
_manager.AddEmbeddedImageAnnotFromBase64(attachment.Value.binary, (float) x, (float) y, (float) width, (float) height);
|
|
}
|
|
|
|
private void AddInkAnnotation(int page, string value)
|
|
{
|
|
var ink = JsonConvert.DeserializeObject<Ink>(value);
|
|
if (ink is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var segments = ink.lines.points;
|
|
var color = ColorTranslator.FromHtml(ink.strokeColor);
|
|
_manager.SelectPage(page);
|
|
|
|
foreach (var segment in segments)
|
|
{
|
|
var points = segment.Select(ToPointF).ToArray();
|
|
_manager.AddFreeHandAnnot(color, points);
|
|
}
|
|
}
|
|
|
|
private void AddInkAnnotation(Annotation annotation)
|
|
{
|
|
var segments = annotation.lines.points;
|
|
var color = ColorTranslator.FromHtml(annotation.strokeColor);
|
|
_manager.SelectPage(annotation.pageIndex + 1);
|
|
|
|
foreach (var segment in segments)
|
|
{
|
|
var points = segment.Select(ToPointF).ToArray();
|
|
_manager.AddFreeHandAnnot(color, points);
|
|
}
|
|
}
|
|
|
|
private void AddFormFieldValue(double x, double y, double width, double height, int page, string value)
|
|
{
|
|
_manager.SelectPage(page);
|
|
|
|
var annot = _manager.AddTextAnnot((float) x, (float) y, (float) width, (float) height, value);
|
|
annot.FontName = _pdfBurnerParams.FontName;
|
|
annot.FontSize = _pdfBurnerParams.FontSize;
|
|
annot.FontStyle = _pdfBurnerParams.FontStyle;
|
|
_manager.SaveAnnotationsToPage();
|
|
}
|
|
|
|
private void AddFormFieldValue(Annotation annotation, FormFieldValue formFieldValue)
|
|
{
|
|
var ffIndex = EGName.Index[annotation.egName];
|
|
|
|
var bounds = annotation.bbox.Select(ToInches).ToList();
|
|
|
|
var x = bounds[0];
|
|
var y = bounds[1] + _pdfBurnerParams.YOffset * ffIndex + _pdfBurnerParams.TopMargin;
|
|
var width = bounds[2];
|
|
var height = bounds[3];
|
|
|
|
_manager.SelectPage(annotation.pageIndex + 1);
|
|
var annot = _manager.AddTextAnnot((float) x, (float) y, (float) width, (float) height, formFieldValue.value);
|
|
annot.FontName = _pdfBurnerParams.FontName;
|
|
annot.FontSize = _pdfBurnerParams.FontSize;
|
|
annot.FontStyle = _pdfBurnerParams.FontStyle;
|
|
_manager.SaveAnnotationsToPage();
|
|
}
|
|
|
|
private static PointF ToPointF(List<float> points)
|
|
{
|
|
var convertedPoints = points.Select(ToInches).ToList();
|
|
return new PointF(convertedPoints[0], convertedPoints[1]);
|
|
}
|
|
|
|
private static double ToInches(double value) => value / 72;
|
|
|
|
private static float ToInches(float value) => value / 72;
|
|
|
|
internal static class AnnotationType
|
|
{
|
|
public const string Image = "pspdfkit/image";
|
|
public const string Ink = "pspdfkit/ink";
|
|
public const string Widget = "pspdfkit/widget";
|
|
public const string FormField = "pspdfkit/form-field-value";
|
|
}
|
|
|
|
internal class AnnotationData
|
|
{
|
|
public List<Annotation> annotations { get; set; } = new();
|
|
|
|
public IEnumerable<List<Annotation>> AnnotationsByReceiver => annotations
|
|
.Where(annot => annot.hasStructuredID)
|
|
.GroupBy(a => a.receiverId)
|
|
.Select(g => g.ToList());
|
|
|
|
public IEnumerable<List<Annotation>> UnstructuredAnnotations => annotations
|
|
.Where(annot => !annot.hasStructuredID)
|
|
.GroupBy(a => a.receiverId)
|
|
.Select(g => g.ToList());
|
|
|
|
public Dictionary<string, Attachment> attachments { get; set; } = new();
|
|
public List<FormFieldValue> formFieldValues { get; set; } = new();
|
|
}
|
|
|
|
internal class Annotation
|
|
{
|
|
private string? _id;
|
|
|
|
public int envelopeId;
|
|
public int receiverId;
|
|
public int index;
|
|
public string egName = EGName.NoName;
|
|
public bool hasStructuredID;
|
|
|
|
public bool isLabel
|
|
{
|
|
get
|
|
{
|
|
if (string.IsNullOrEmpty(egName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var parts = egName.Split('_');
|
|
return parts.Length > 1 && parts[1] == "label";
|
|
}
|
|
}
|
|
|
|
public string id
|
|
{
|
|
get => _id ?? string.Empty;
|
|
set
|
|
{
|
|
_id = value;
|
|
|
|
if (string.IsNullOrWhiteSpace(_id))
|
|
{
|
|
throw new BurnAnnotationException("The identifier of annotation is null or empty.");
|
|
}
|
|
|
|
var parts = value.Split('#');
|
|
|
|
if (parts.Length != 4)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!int.TryParse(parts[0], out envelopeId))
|
|
{
|
|
throw new BurnAnnotationException($"The envelope ID of annotation is not integer. Id: {_id}");
|
|
}
|
|
|
|
if (!int.TryParse(parts[1], out receiverId))
|
|
{
|
|
throw new BurnAnnotationException($"The receiver ID of annotation is not integer. Id: {_id}");
|
|
}
|
|
|
|
if (!int.TryParse(parts[2], out index))
|
|
{
|
|
throw new BurnAnnotationException($"The index of annotation is not integer. Id: {_id}");
|
|
}
|
|
|
|
egName = parts[3];
|
|
|
|
hasStructuredID = true;
|
|
}
|
|
}
|
|
|
|
public List<double> bbox { get; set; } = new();
|
|
|
|
public string type { get; set; } = string.Empty;
|
|
|
|
public bool isSignature { get; set; }
|
|
|
|
public string imageAttachmentId { get; set; } = string.Empty;
|
|
|
|
public Lines lines { get; set; } = new();
|
|
|
|
public int pageIndex { get; set; }
|
|
|
|
public string strokeColor { get; set; } = string.Empty;
|
|
}
|
|
|
|
internal class Ink
|
|
{
|
|
public Lines lines { get; set; } = new();
|
|
|
|
public string strokeColor { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class EGName
|
|
{
|
|
public static readonly string NoName = Guid.NewGuid().ToString();
|
|
public static readonly string Seal = "signature";
|
|
|
|
public static readonly ImmutableDictionary<string, int> Index = new Dictionary<string, int>
|
|
{
|
|
{ NoName, 0 },
|
|
{ Seal, 0 },
|
|
{ "position", 1 },
|
|
{ "city", 2 },
|
|
{ "date", 3 }
|
|
}.ToImmutableDictionary();
|
|
}
|
|
|
|
internal class Lines
|
|
{
|
|
public List<List<List<float>>> points { get; set; } = new();
|
|
}
|
|
|
|
internal class Attachment
|
|
{
|
|
public string binary { get; set; } = string.Empty;
|
|
public string contentType { get; set; } = string.Empty;
|
|
}
|
|
|
|
internal class FormFieldValue
|
|
{
|
|
public string name { get; set; } = string.Empty;
|
|
public string value { get; set; } = string.Empty;
|
|
}
|
|
}
|