Enhanced PDFBurner to use a static FontProvider for better font support when rendering form field values. In ReportCreator, removed unused HEAD_TITLE and HEAD_SUBJECT fields from the SQL query and related mapping, streamlining report item loading.
278 lines
9.3 KiB
C#
278 lines
9.3 KiB
C#
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using iText.IO.Image;
|
|
using iText.Kernel.Colors;
|
|
using iText.Kernel.Pdf;
|
|
using iText.Kernel.Pdf.Canvas;
|
|
using iText.Layout;
|
|
using iText.Layout.Element;
|
|
using iText.Layout.Font;
|
|
using iText.Layout.Properties;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Newtonsoft.Json;
|
|
using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions;
|
|
using LayoutImage = iText.Layout.Element.Image;
|
|
|
|
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
|
|
public class PDFBurner
|
|
{
|
|
private static readonly FontProvider FontProvider = CreateFontProvider();
|
|
private readonly ILogger<PDFBurner> _logger;
|
|
private readonly PDFBurnerParams _pdfBurnerParams;
|
|
|
|
public PDFBurner() : this(NullLogger<PDFBurner>.Instance, new PDFBurnerParams())
|
|
{
|
|
}
|
|
|
|
public PDFBurner(ILogger<PDFBurner> logger, PDFBurnerParams? pdfBurnerParams = null)
|
|
{
|
|
_logger = logger;
|
|
_pdfBurnerParams = pdfBurnerParams ?? new PDFBurnerParams();
|
|
}
|
|
|
|
public byte[] BurnAnnotsToPDF(byte[] sourceBuffer, IList<string> instantJsonList, int envelopeId)
|
|
{
|
|
if (sourceBuffer is null || sourceBuffer.Length == 0)
|
|
{
|
|
throw new BurnAnnotationException("Source document is empty");
|
|
}
|
|
|
|
try
|
|
{
|
|
using var inputStream = new MemoryStream(sourceBuffer);
|
|
using var outputStream = new MemoryStream();
|
|
using var reader = new PdfReader(inputStream);
|
|
using var writer = new PdfWriter(outputStream);
|
|
using var pdf = new PdfDocument(reader, writer);
|
|
|
|
foreach (var json in instantJsonList ?? Enumerable.Empty<string>())
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var annotationData = JsonConvert.DeserializeObject<AnnotationData>(json);
|
|
if (annotationData?.annotations is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
annotationData.annotations.Reverse();
|
|
|
|
foreach (var annotation in annotationData.annotations)
|
|
{
|
|
try
|
|
{
|
|
switch (annotation.type)
|
|
{
|
|
case AnnotationType.Image:
|
|
AddImageAnnotation(pdf, annotation, annotationData.attachments);
|
|
break;
|
|
case AnnotationType.Ink:
|
|
AddInkAnnotation(pdf, 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(pdf, annotation, formFieldValue.value);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Error applying annotation {AnnotationId} on envelope {EnvelopeId}", annotation.id, envelopeId);
|
|
throw new BurnAnnotationException("Adding annotation failed", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
pdf.Close();
|
|
return outputStream.ToArray();
|
|
}
|
|
catch (BurnAnnotationException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to burn annotations for envelope {EnvelopeId}", envelopeId);
|
|
throw new BurnAnnotationException("Annotations could not be burned", ex);
|
|
}
|
|
}
|
|
|
|
private void AddImageAnnotation(PdfDocument pdf, Annotation annotation, Dictionary<string, Attachment>? attachments)
|
|
{
|
|
if (attachments is null || string.IsNullOrWhiteSpace(annotation.imageAttachmentId) || !attachments.TryGetValue(annotation.imageAttachmentId, out var attachment))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var page = pdf.GetPage(annotation.pageIndex + 1);
|
|
var bounds = annotation.bbox.Select(ToInches).ToList();
|
|
var x = (float)bounds[0];
|
|
var y = (float)bounds[1];
|
|
var width = (float)bounds[2];
|
|
var height = (float)bounds[3];
|
|
|
|
var imageBytes = Convert.FromBase64String(attachment.binary);
|
|
var imageData = ImageDataFactory.Create(imageBytes);
|
|
var image = new LayoutImage(imageData)
|
|
.ScaleAbsolute(width, height)
|
|
.SetFixedPosition(annotation.pageIndex + 1, x, y);
|
|
|
|
using var canvas = new Canvas(new PdfCanvas(page), page.GetPageSize());
|
|
canvas.Add(image);
|
|
}
|
|
|
|
private void AddInkAnnotation(PdfDocument pdf, Annotation annotation)
|
|
{
|
|
if (annotation.lines?.points is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var page = pdf.GetPage(annotation.pageIndex + 1);
|
|
var canvas = new PdfCanvas(page);
|
|
var color = ParseColor(annotation.strokeColor);
|
|
canvas.SetStrokeColor(color);
|
|
canvas.SetLineWidth(1);
|
|
|
|
foreach (var segment in annotation.lines.points)
|
|
{
|
|
var first = true;
|
|
foreach (var point in segment)
|
|
{
|
|
var (px, py) = (ToInches(point[0]), ToInches(point[1]));
|
|
if (first)
|
|
{
|
|
canvas.MoveTo(px, py);
|
|
first = false;
|
|
}
|
|
else
|
|
{
|
|
canvas.LineTo(px, py);
|
|
}
|
|
}
|
|
canvas.Stroke();
|
|
}
|
|
}
|
|
|
|
private static FontProvider CreateFontProvider()
|
|
{
|
|
var provider = new FontProvider();
|
|
provider.AddStandardPdfFonts();
|
|
provider.AddSystemFonts();
|
|
return provider;
|
|
}
|
|
|
|
private void AddFormFieldValue(PdfDocument pdf, Annotation annotation, string value)
|
|
{
|
|
var bounds = annotation.bbox.Select(ToInches).ToList();
|
|
var x = (float)bounds[0];
|
|
var y = (float)bounds[1];
|
|
var width = (float)bounds[2];
|
|
var height = (float)bounds[3];
|
|
|
|
var page = pdf.GetPage(annotation.pageIndex + 1);
|
|
var canvas = new Canvas(new PdfCanvas(page), page.GetPageSize());
|
|
canvas.SetProperty(Property.FONT_PROVIDER, FontProvider);
|
|
canvas.SetProperty(Property.FONT, FontProvider.GetFontSet());
|
|
|
|
var paragraph = new Paragraph(value)
|
|
.SetFontSize(_pdfBurnerParams.FontSize)
|
|
.SetFontColor(ColorConstants.BLACK)
|
|
.SetFontFamily(_pdfBurnerParams.FontName);
|
|
|
|
if (_pdfBurnerParams.FontStyle.HasFlag(FontStyle.Italic))
|
|
{
|
|
paragraph.SetItalic();
|
|
}
|
|
|
|
if (_pdfBurnerParams.FontStyle.HasFlag(FontStyle.Bold))
|
|
{
|
|
paragraph.SetBold();
|
|
}
|
|
|
|
canvas.ShowTextAligned(
|
|
paragraph,
|
|
x + (float)_pdfBurnerParams.TopMargin,
|
|
y + (float)_pdfBurnerParams.YOffset,
|
|
annotation.pageIndex + 1,
|
|
iText.Layout.Properties.TextAlignment.LEFT,
|
|
iText.Layout.Properties.VerticalAlignment.TOP,
|
|
0);
|
|
}
|
|
|
|
private static DeviceRgb ParseColor(string? color)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(color))
|
|
{
|
|
return new DeviceRgb(0, 0, 0);
|
|
}
|
|
|
|
try
|
|
{
|
|
var drawingColor = ColorTranslator.FromHtml(color);
|
|
return new DeviceRgb(drawingColor.R, drawingColor.G, drawingColor.B);
|
|
}
|
|
catch
|
|
{
|
|
return new DeviceRgb(0, 0, 0);
|
|
}
|
|
}
|
|
|
|
private static double ToInches(double value) => value / 72d;
|
|
private static double ToInches(float value) => value / 72d;
|
|
|
|
#region Model
|
|
private static class AnnotationType
|
|
{
|
|
public const string Image = "pspdfkit/image";
|
|
public const string Ink = "pspdfkit/ink";
|
|
public const string Widget = "pspdfkit/widget";
|
|
}
|
|
|
|
private sealed class AnnotationData
|
|
{
|
|
public List<Annotation>? annotations { get; set; }
|
|
public Dictionary<string, Attachment>? attachments { get; set; }
|
|
public List<FormFieldValue>? formFieldValues { get; set; }
|
|
}
|
|
|
|
private sealed class Annotation
|
|
{
|
|
public string id { get; set; } = string.Empty;
|
|
public List<double> bbox { get; set; } = new();
|
|
public string type { get; set; } = string.Empty;
|
|
public string imageAttachmentId { get; set; } = string.Empty;
|
|
public Lines? lines { get; set; }
|
|
public int pageIndex { get; set; }
|
|
public string strokeColor { get; set; } = string.Empty;
|
|
public string egName { get; set; } = string.Empty;
|
|
}
|
|
|
|
private sealed class Lines
|
|
{
|
|
public List<List<List<float>>> points { get; set; } = new();
|
|
}
|
|
|
|
private sealed class Attachment
|
|
{
|
|
public string binary { get; set; } = string.Empty;
|
|
public string contentType { get; set; } = string.Empty;
|
|
}
|
|
|
|
private sealed class FormFieldValue
|
|
{
|
|
public string name { get; set; } = string.Empty;
|
|
public string value { get; set; } = string.Empty;
|
|
}
|
|
#endregion
|
|
}
|