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;
|
string? _errorMessage;
|
||||||
ClaimsPrincipal? _receiverUser;
|
ClaimsPrincipal? _receiverUser;
|
||||||
EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver.EnvelopeReceiverDto? _envelopeReceiver;
|
EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver.EnvelopeReceiverDto? _envelopeReceiver;
|
||||||
|
IReadOnlyList<SignatureDto> _signatures = [];
|
||||||
XtraReport? _report;
|
XtraReport? _report;
|
||||||
SignatureCaptureDto? _sig;
|
SignatureCaptureDto? _sig;
|
||||||
|
|
||||||
@@ -130,6 +131,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
_envelopeReceiver = await PageDataService.GetEnvelopeReceiverAsync(EnvelopeKey);
|
_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
|
var report = new XtraReport
|
||||||
{
|
{
|
||||||
@@ -176,4 +182,109 @@
|
|||||||
{
|
{
|
||||||
_report?.Dispose();
|
_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