Render signatures on PDF using PdfSharp
Added functionality to render captured signatures onto a PDF document using the PdfSharp library. Introduced a new `_signatures` field to store signature data and updated `OnInitializedAsync` to fetch and process signatures. Implemented the `DrawSignaturesOnPdf` method to overlay signature images, separator lines, and signer details (name, position, place, date) onto the PDF. Added a helper method `DataUrlToBytes` for decoding Base64-encoded signature images. Defined constants for layout and styling to ensure consistent rendering. Updated `Dispose` to clean up resources.
This commit is contained in:
@@ -92,6 +92,7 @@
|
||||
string? _errorMessage;
|
||||
ClaimsPrincipal? _receiverUser;
|
||||
EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver.EnvelopeReceiverDto? _envelopeReceiver;
|
||||
IReadOnlyList<SignatureDto> _signatures = [];
|
||||
XtraReport? _report;
|
||||
SignatureCaptureDto? _sig;
|
||||
|
||||
@@ -130,6 +131,11 @@
|
||||
}
|
||||
|
||||
_envelopeReceiver = await PageDataService.GetEnvelopeReceiverAsync(EnvelopeKey);
|
||||
_signatures = await PageDataService.GetSignaturesAsync(_receiverUser);
|
||||
|
||||
// Burn signature image + info onto PDF via PdfSharp
|
||||
if (_sig is not null && _signatures.Count > 0)
|
||||
pdfBytes = DrawSignaturesOnPdf(pdfBytes, _signatures, _sig);
|
||||
|
||||
var report = new XtraReport
|
||||
{
|
||||
@@ -176,4 +182,109 @@
|
||||
{
|
||||
_report?.Dispose();
|
||||
}
|
||||
|
||||
// ----- PDF signature rendering -----
|
||||
|
||||
/// <summary>
|
||||
/// Uses PdfSharp to burn the captured signature onto the PDF at each signature field.
|
||||
/// Layout per field (top-left origin, Y down, units = PDF points):
|
||||
/// [top 65%] signature image
|
||||
/// [separator line]
|
||||
/// [bottom 35%] FullName (bold) / Position (optional) / Place, Date
|
||||
/// </summary>
|
||||
static byte[] DrawSignaturesOnPdf(
|
||||
byte[] pdfBytes,
|
||||
IReadOnlyList<SignatureDto> signatures,
|
||||
SignatureCaptureDto sig)
|
||||
{
|
||||
var imgBytes = DataUrlToBytes(sig.DataUrl);
|
||||
if (imgBytes is not { Length: > 0 }) return pdfBytes;
|
||||
|
||||
using var inputMs = new System.IO.MemoryStream(pdfBytes);
|
||||
using var outputMs = new System.IO.MemoryStream();
|
||||
|
||||
var document = PdfSharp.Pdf.IO.PdfReader.Open(
|
||||
inputMs, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Modify);
|
||||
|
||||
const double sigW = 1.77 * 72; // 127.44 pt
|
||||
const double sigH = 1.96 * 72; // 141.12 pt
|
||||
const double imgRatio = 0.60; // top 60% = image
|
||||
const double textRatio = 0.38; // bottom 38% = text (2% padding)
|
||||
|
||||
var black = PdfSharp.Drawing.XColor.FromArgb(255, 20, 20, 20);
|
||||
var darkGray = PdfSharp.Drawing.XColor.FromArgb(255, 80, 80, 80);
|
||||
var lineColor = PdfSharp.Drawing.XColor.FromArgb(180, 100, 100, 120);
|
||||
|
||||
var fontBold = new PdfSharp.Drawing.XFont("Arial", 7.5, PdfSharp.Drawing.XFontStyleEx.Bold);
|
||||
var fontNormal = new PdfSharp.Drawing.XFont("Arial", 6.5, PdfSharp.Drawing.XFontStyleEx.Regular);
|
||||
var linePen = new PdfSharp.Drawing.XPen(lineColor, 0.5);
|
||||
|
||||
var fmtLeft = new PdfSharp.Drawing.XStringFormat
|
||||
{
|
||||
Alignment = PdfSharp.Drawing.XStringAlignment.Near,
|
||||
LineAlignment = PdfSharp.Drawing.XLineAlignment.Near,
|
||||
};
|
||||
|
||||
var date = DateTime.Now.ToString("dd.MM.yyyy");
|
||||
|
||||
foreach (var field in signatures)
|
||||
{
|
||||
int pageIndex = field.Page - 1;
|
||||
if (pageIndex < 0 || pageIndex >= document.PageCount) continue;
|
||||
|
||||
var page = document.Pages[pageIndex];
|
||||
|
||||
using var gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(page);
|
||||
|
||||
double x = field.X;
|
||||
double y = field.Y;
|
||||
|
||||
// --- Image area ---
|
||||
double imgH = sigH * imgRatio;
|
||||
var imgRect = new PdfSharp.Drawing.XRect(x, y, sigW, imgH);
|
||||
|
||||
using var imgStream = new System.IO.MemoryStream(imgBytes);
|
||||
var xImg = PdfSharp.Drawing.XImage.FromStream(imgStream);
|
||||
gfx.DrawImage(xImg, imgRect);
|
||||
|
||||
// --- Separator line ---
|
||||
double lineY = y + imgH + sigH * 0.01;
|
||||
gfx.DrawLine(linePen, x + 2, lineY, x + sigW - 2, lineY);
|
||||
|
||||
// --- Text area ---
|
||||
double textY = lineY + 2;
|
||||
double textH = sigH * textRatio;
|
||||
double lineH = textH / 3.5; // max 3 text rows
|
||||
double padding = 3;
|
||||
|
||||
// Row 1: FullName (bold)
|
||||
var nameRect = new PdfSharp.Drawing.XRect(x + padding, textY, sigW - padding * 2, lineH);
|
||||
gfx.DrawString(sig.FullName, fontBold, new PdfSharp.Drawing.XSolidBrush(black), nameRect, fmtLeft);
|
||||
|
||||
// Row 2: Position (optional)
|
||||
double row2Y = textY + lineH;
|
||||
if (!string.IsNullOrWhiteSpace(sig.Position))
|
||||
{
|
||||
var posRect = new PdfSharp.Drawing.XRect(x + padding, row2Y, sigW - padding * 2, lineH);
|
||||
gfx.DrawString(sig.Position, fontNormal, new PdfSharp.Drawing.XSolidBrush(darkGray), posRect, fmtLeft);
|
||||
row2Y += lineH;
|
||||
}
|
||||
|
||||
// Row 3 (or 2 if no position): Place, Date
|
||||
var placeDate = $"{sig.Place}, {date}";
|
||||
var dateRect = new PdfSharp.Drawing.XRect(x + padding, row2Y, sigW - padding * 2, lineH);
|
||||
gfx.DrawString(placeDate, fontNormal, new PdfSharp.Drawing.XSolidBrush(darkGray), dateRect, fmtLeft);
|
||||
}
|
||||
|
||||
document.Save(outputMs);
|
||||
return outputMs.ToArray();
|
||||
}
|
||||
|
||||
static byte[]? DataUrlToBytes(string? dataUrl)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dataUrl)) return null;
|
||||
var comma = dataUrl.IndexOf(',');
|
||||
if (comma < 0) return null;
|
||||
return Convert.FromBase64String(dataUrl[(comma + 1)..]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user