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:
2026-07-01 10:30:35 +02:00
parent 2d22bfcd06
commit 76ff3e47e1

View File

@@ -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)..]);
}
}