diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverReportSignedPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverReportSignedPage.razor index 97f925fb..120de638 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverReportSignedPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverReportSignedPage.razor @@ -92,6 +92,7 @@ string? _errorMessage; ClaimsPrincipal? _receiverUser; EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver.EnvelopeReceiverDto? _envelopeReceiver; + IReadOnlyList _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 ----- + + /// + /// 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 + /// + static byte[] DrawSignaturesOnPdf( + byte[] pdfBytes, + IReadOnlyList 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)..]); + } }