diff --git a/EnvelopeGenerator.Application/Common/Dto/DocReceiverElementDto.cs b/EnvelopeGenerator.Application/Common/Dto/DocReceiverElementDto.cs index 808561f6..eedeb896 100644 --- a/EnvelopeGenerator.Application/Common/Dto/DocReceiverElementDto.cs +++ b/EnvelopeGenerator.Application/Common/Dto/DocReceiverElementDto.cs @@ -6,7 +6,7 @@ namespace EnvelopeGenerator.Application.Common.Dto; /// /// Data Transfer Object representing a positioned element assigned to a document receiver. /// -public class DocReceiverElementDto : IDocReceiverElement +public class DocReceiverElementDto : IDocReceiverElement, ISignatureInkPayload { /// /// Gets or sets the unique identifier of the element. @@ -122,4 +122,41 @@ public class DocReceiverElementDto : IDocReceiverElement /// /// public byte[]? Ink { get; set; } -} \ No newline at end of file + + /// + /// Returns the stored signature bytes as an image payload with detected MIME type. + /// + /// Thrown when signature image bytes do not exist. + public (byte[] Bytes, string ContentType, string FileExtension) ToImage() + { + if (Ink is not { Length: > 0 }) + throw new InvalidOperationException("Signature image (Ink) does not exist."); + + if (HasPrefix(Ink, 0x89, 0x50, 0x4E, 0x47)) + return (Ink, "image/png", ".png"); + + if (HasPrefix(Ink, 0xFF, 0xD8, 0xFF)) + return (Ink, "image/jpeg", ".jpg"); + + if (HasPrefix(Ink, 0x52, 0x49, 0x46, 0x46) && + Ink.Length >= 12 && + Ink[8] == 0x57 && Ink[9] == 0x45 && Ink[10] == 0x42 && Ink[11] == 0x50) + return (Ink, "image/webp", ".webp"); + + return (Ink, "application/octet-stream", ".bin"); + } + + private static bool HasPrefix(byte[] data, params byte[] prefix) + { + if (data.Length < prefix.Length) + return false; + + for (var i = 0; i < prefix.Length; i++) + { + if (data[i] != prefix[i]) + return false; + } + + return true; + } +} diff --git a/EnvelopeGenerator.Application/Common/Interfaces/Doc/IPdfPageImageRenderer.cs b/EnvelopeGenerator.Application/Common/Interfaces/Doc/IPdfPageImageRenderer.cs index 361211f0..8dcbf9b3 100644 --- a/EnvelopeGenerator.Application/Common/Interfaces/Doc/IPdfPageImageRenderer.cs +++ b/EnvelopeGenerator.Application/Common/Interfaces/Doc/IPdfPageImageRenderer.cs @@ -1,8 +1,54 @@ +using DigitalData.Core.Exceptions; using EnvelopeGenerator.Application.Common.Dto; +using EnvelopeGenerator.Domain.Interfaces; namespace EnvelopeGenerator.Application.Common.Interfaces.Doc; +/// +/// +/// public interface IPdfPageImageRenderer { + /// + /// + /// + /// + /// + /// Task> RenderPagesAsImagesAsync(byte[] pdfBytes, CancellationToken cancellationToken = default); + + /// + /// + /// + /// + /// + byte[] ComposeSignaturePreviewPng(ISignatureInkPayload element); + + /// + /// + /// + /// + /// + public bool IsSupportedImageFormat(byte[] bytes); } + +/// +/// +/// +public static class PdfPageImageRendererExtensions +{ + /// + /// + /// + /// + /// + /// + /// + public static byte[] EnsureSupportedImageBytes(this IPdfPageImageRenderer renderer, byte[] inkBytes) + { + if (!renderer.IsSupportedImageFormat(inkBytes)) + throw new BadRequestException("Only PNG and JPEG signature formats are supported."); + + return inkBytes; + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Domain/Entities/DocReceiverElement.cs b/EnvelopeGenerator.Domain/Entities/DocReceiverElement.cs index ed17921d..9dd59387 100644 --- a/EnvelopeGenerator.Domain/Entities/DocReceiverElement.cs +++ b/EnvelopeGenerator.Domain/Entities/DocReceiverElement.cs @@ -11,7 +11,7 @@ using System.Collections.Generic; namespace EnvelopeGenerator.Domain.Entities { [Table("TBSIG_DOCUMENT_RECEIVER_ELEMENT", Schema = "dbo")] - public class DocReceiverElement : IDocReceiverElement, IHasReceiver, IHasAddedWhen, IUpdateAuditable + public class DocReceiverElement : IDocReceiverElement, ISignatureInkPayload, IHasReceiver, IHasAddedWhen, IUpdateAuditable { public DocReceiverElement() { diff --git a/EnvelopeGenerator.Domain/EnvelopeGenerator.Domain.csproj b/EnvelopeGenerator.Domain/EnvelopeGenerator.Domain.csproj index fc81b9f0..04fd248f 100644 --- a/EnvelopeGenerator.Domain/EnvelopeGenerator.Domain.csproj +++ b/EnvelopeGenerator.Domain/EnvelopeGenerator.Domain.csproj @@ -2,6 +2,7 @@ net462;net7.0;net8.0;net9.0 + latest 1.4.0 1.4.0 diff --git a/EnvelopeGenerator.Domain/Interfaces/ISignatureInkPayload.cs b/EnvelopeGenerator.Domain/Interfaces/ISignatureInkPayload.cs new file mode 100644 index 00000000..c9a722f9 --- /dev/null +++ b/EnvelopeGenerator.Domain/Interfaces/ISignatureInkPayload.cs @@ -0,0 +1,28 @@ +#if NETFRAMEWORK +using System; +#endif + +namespace EnvelopeGenerator.Domain.Interfaces +{ + public interface ISignatureInkPayload + { +#if nullable + byte[]? Ink { get; } + + string? FullName { get; } + + string? Position { get; } + + string? Place { get; } +#else + byte[] Ink { get; } + + string FullName { get; } + + string Position { get; } + + string Place { get; } +#endif + DateTime? ChangedWhen { get; } + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Infrastructure.Doc/PdfPageImageRenderer.cs b/EnvelopeGenerator.Infrastructure.Doc/PdfPageImageRenderer.cs index 234397d8..7aa15145 100644 --- a/EnvelopeGenerator.Infrastructure.Doc/PdfPageImageRenderer.cs +++ b/EnvelopeGenerator.Infrastructure.Doc/PdfPageImageRenderer.cs @@ -1,7 +1,9 @@ using DevExpress.Drawing; using DevExpress.Pdf; +using DigitalData.Core.Exceptions; using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Application.Common.Interfaces.Doc; +using EnvelopeGenerator.Domain.Interfaces; namespace EnvelopeGenerator.Infrastructure.Doc; @@ -54,4 +56,124 @@ public class PdfPageImageRenderer : IPdfPageImageRenderer return Task.FromResult>(results); } -} + + public byte[] ComposeSignaturePreviewPng(ISignatureInkPayload element) + { + const double sigW = 1.77 * 72; + const double sigH = 1.96 * 72; + const double imgRatio = 0.52; + const double lineH = 11.5; + const double bgPad = 3.0; + const double padding = 3.0; + + var date = element.ChangedWhen!.Value.ToString("dd.MM.yyyy"); + var position = string.IsNullOrWhiteSpace(element.Position) ? null : element.Position.Trim(); + + var x = bgPad; + var y = bgPad; + var imgH = sigH * imgRatio; + var lineY = y + imgH + 1.0; + var textY = lineY + 1.5; + var row1Y = textY; + var row2Y = row1Y + lineH; + var row3Y = position is null ? row2Y : row2Y + lineH; + var contentBottom = row3Y + lineH; + var pageWidth = sigW + (bgPad * 2); + var pageHeight = contentBottom + bgPad; + + var inkBytes = this.EnsureSupportedImageBytes(element.Ink!); + + using var processor = new PdfDocumentProcessor(); + processor.CreateEmptyDocument(); + var page = processor.AddNewPage(new PdfRectangle(0, 0, pageWidth, pageHeight)); + + using var graphics = processor.CreateGraphics(); + + using var bgBrush = new DXSolidBrush(System.Drawing.Color.FromArgb(255, 255, 253, 240)); + using var linePen = new DXPen(System.Drawing.Color.FromArgb(180, 100, 100, 120), 0.5f); + using var nameBrush = new DXSolidBrush(System.Drawing.Color.FromArgb(255, 20, 20, 20)); + using var infoBrush = new DXSolidBrush(System.Drawing.Color.FromArgb(255, 80, 80, 80)); + + var fontBold = new DXFont("Arial", 7.5f, DXFontStyle.Bold); + var fontNormal = new DXFont("Arial", 6.5f, DXFontStyle.Regular); + + graphics.FillRectangle(bgBrush, new System.Drawing.RectangleF(0, 0, (float)pageWidth, (float)pageHeight)); + + var imageArea = new System.Drawing.RectangleF((float)x, (float)y, (float)sigW, (float)imgH); + var imageRect = FitImageContain(imageArea, inkBytes); + graphics.DrawImage(inkBytes, imageRect); + + graphics.DrawLine(linePen, (float)(x + 2), (float)lineY, (float)(x + sigW - 2), (float)lineY); + + graphics.DrawString( + element.FullName!.Trim(), + fontBold, + nameBrush, + new System.Drawing.RectangleF((float)(x + padding), (float)row1Y, (float)(sigW - (padding * 2)), (float)lineH)); + + if (position is not null) + { + graphics.DrawString( + position, + fontNormal, + infoBrush, + new System.Drawing.RectangleF((float)(x + padding), (float)row2Y, (float)(sigW - (padding * 2)), (float)lineH)); + } + + graphics.DrawString( + $"{element.Place!.Trim()}, {date}", + fontNormal, + infoBrush, + new System.Drawing.RectangleF((float)(x + padding), (float)row3Y, (float)(sigW - (padding * 2)), (float)lineH)); + + graphics.AddToPageForeground(page, 72, 72); + + using var pdfStream = new MemoryStream(); + processor.SaveDocument(pdfStream); + pdfStream.Position = 0; + + using var rasterProcessor = new PdfDocumentProcessor(); + rasterProcessor.LoadDocument(pdfStream); + + var renderOptions = PdfPageRenderingParameters.CreateWithResolution(400f); + using DXBitmap bitmap = rasterProcessor.CreateDXBitmap(1, renderOptions); + using var pngStream = new MemoryStream(); + bitmap.Save(pngStream, DXImageFormat.Png); + return pngStream.ToArray(); + } + + private static System.Drawing.RectangleF FitImageContain(System.Drawing.RectangleF area, byte[] imageBytes) + { + using var imageStream = new MemoryStream(imageBytes, writable: false); + using var image = DXImage.FromStream(imageStream); + + if (image.Width <= 0 || image.Height <= 0) + return area; + + var scale = Math.Min(area.Width / image.Width, area.Height / image.Height); + var drawW = image.Width * scale; + var drawH = image.Height * scale; + var drawX = area.X + ((area.Width - drawW) / 2f); + var drawY = area.Y + ((area.Height - drawH) / 2f); + + return new System.Drawing.RectangleF(drawX, drawY, drawW, drawH); + } + + /// + /// + /// + /// + /// + public bool IsSupportedImageFormat(byte[] bytes) + { + var isPng = bytes.Length >= 4 && + bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47; + if (isPng) + return true; + + var isJpeg = bytes.Length >= 3 && + bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF; + return isJpeg; + } + +} \ No newline at end of file diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/ReceiverSignedPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/ReceiverSignedPage.razor index 25ebac73..e668bbf9 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/ReceiverSignedPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/ReceiverSignedPage.razor @@ -317,11 +317,11 @@ // ----- 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 + /// Uses DevExpress PdfGraphics to burn the captured signature onto the PDF at each signature field. + /// Layout per field (input coordinates are top-left origin, units = PDF points): + /// [top 52%] signature image (aspect-ratio preserved) /// [separator line] - /// [bottom 35%] FullName (bold) / Position (optional) / Place, Date + /// [bottom] FullName / Position (optional) / Place, Date /// static byte[] DrawSignaturesOnPdf( byte[] pdfBytes, @@ -331,102 +331,98 @@ var imgBytes = DataUrlToBytes(sig.DataUrl); if (imgBytes is not { Length: > 0 }) return pdfBytes; - using var inputMs = new System.IO.MemoryStream(pdfBytes); + using var inputMs = new System.IO.MemoryStream(pdfBytes); using var outputMs = new System.IO.MemoryStream(); + using var processor = new DevExpress.Pdf.PdfDocumentProcessor(); + processor.LoadDocument(inputMs); - var document = PdfSharp.Pdf.IO.PdfReader.Open( - inputMs, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Modify); + const double sigW = 1.77 * 72; + const double sigH = 1.96 * 72; + const double imgRatio = 0.52; + const double lineH = 11.5; + const double bgPad = 3.0; + const double padding = 3.0; - const double sigW = 1.77 * 72; // 127.44 pt - const double sigH = 1.96 * 72; // 141.12 pt - const double imgRatio = 0.52; // top 52% = image - const double lineH = 11.5; // fixed row height matching font size (bold 7.5pt + normal 6.5pt) - const double bgPad = 3.0; // background box padding around content (pt) - - 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 bgColor = PdfSharp.Drawing.XColor.FromArgb(255, 255, 253, 240); - var bgBrush = new PdfSharp.Drawing.XSolidBrush(bgColor); - - 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, - }; + using var bgBrush = new DevExpress.Drawing.DXSolidBrush(System.Drawing.Color.FromArgb(255, 255, 253, 240)); + using var linePen = new DevExpress.Drawing.DXPen(System.Drawing.Color.FromArgb(180, 100, 100, 120), 0.5f); + using var nameBrush = new DevExpress.Drawing.DXSolidBrush(System.Drawing.Color.FromArgb(255, 20, 20, 20)); + using var infoBrush = new DevExpress.Drawing.DXSolidBrush(System.Drawing.Color.FromArgb(255, 80, 80, 80)); + var fontBold = new DevExpress.Drawing.DXFont("Arial", 7.5f, DevExpress.Drawing.DXFontStyle.Bold); + var fontNormal = new DevExpress.Drawing.DXFont("Arial", 6.5f, DevExpress.Drawing.DXFontStyle.Regular); 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 pageIndex = field.Page - 1; + if (pageIndex < 0 || pageIndex >= processor.Document.Pages.Count) + continue; - var page = document.Pages[pageIndex]; + var page = processor.Document.Pages[pageIndex]; + var pageHeight = page.CropBox.Height; - using var gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(page); + var x = field.X; + var y = field.Y; - double x = field.X; - double y = field.Y; + var imgH = sigH * imgRatio; + var lineY = y + imgH + 1.0; + var textY = lineY + 1.5; + var row1Y = textY; + var row2Y = row1Y + lineH; + var row3Y = string.IsNullOrWhiteSpace(sig.Position) ? row2Y : row2Y + lineH; + var contentBottom = row3Y + lineH; - // --- Calculate layout positions first (needed for bg rect) --- - double imgH = sigH * imgRatio; - double lineY = y + imgH + 1.0; // 1pt gap between image and separator - double textY = lineY + 1.5; // 1.5pt gap below separator line - double padding = 3; + using var graphics = processor.CreateGraphics(); - // Row 1: FullName - double row1Y = textY; - // Row 2: Position (optional) - double row2Y = row1Y + lineH; - // Row 3: Place, Date — immediately after row2 regardless of position - double row3Y = !string.IsNullOrWhiteSpace(sig.Position) ? row2Y + lineH : row2Y; - double contentBottom = row3Y + lineH; + var bgRect = ToPdfRect(x - bgPad, y - bgPad, sigW + (bgPad * 2), (contentBottom - y) + (bgPad * 2), pageHeight); + graphics.FillRectangle(bgBrush, bgRect); - // --- Background rectangle sized to actual content (not full sigH) --- - var bgRect = new PdfSharp.Drawing.XRect( - x - bgPad, - y - bgPad, - sigW + bgPad * 2, - (contentBottom - y) + bgPad * 2); - gfx.DrawRectangle(bgBrush, bgRect); + var imageArea = ToPdfRect(x, y, sigW, imgH, pageHeight); + var imageRect = FitImageContain(imageArea, imgBytes); + graphics.DrawImage(imgBytes, imageRect); - // --- Image area --- - 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); + var lineYPdf = ToPdfY(lineY, pageHeight); + graphics.DrawLine(linePen, (float)(x + 2), lineYPdf, (float)(x + sigW - 2), lineYPdf); - // --- Separator line --- - gfx.DrawLine(linePen, x + 2, lineY, x + sigW - 2, lineY); + graphics.DrawString(sig.FullName, fontBold, nameBrush, ToPdfRect(x + padding, row1Y, sigW - (padding * 2), lineH, pageHeight)); - // --- Text rows --- - // Row 1: FullName (bold) - var nameRect = new PdfSharp.Drawing.XRect(x + padding, row1Y, sigW - padding * 2, lineH); - gfx.DrawString(sig.FullName, fontBold, new PdfSharp.Drawing.XSolidBrush(black), nameRect, fmtLeft); - - // Row 2: Position (optional) 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); + graphics.DrawString(sig.Position, fontNormal, infoBrush, ToPdfRect(x + padding, row2Y, sigW - (padding * 2), lineH, pageHeight)); } - // Row 3: Place, Date - var placeDate = $"{sig.Place}, {date}"; - var dateRect = new PdfSharp.Drawing.XRect(x + padding, row3Y, sigW - padding * 2, lineH); - gfx.DrawString(placeDate, fontNormal, new PdfSharp.Drawing.XSolidBrush(darkGray), dateRect, fmtLeft); + graphics.DrawString($"{sig.Place}, {date}", fontNormal, infoBrush, ToPdfRect(x + padding, row3Y, sigW - (padding * 2), lineH, pageHeight)); + + graphics.AddToPageForeground(page, 72, 72); } - document.Save(outputMs); + processor.SaveDocument(outputMs); return outputMs.ToArray(); } + static System.Drawing.RectangleF FitImageContain(System.Drawing.RectangleF area, byte[] imageBytes) + { + using var imageStream = new System.IO.MemoryStream(imageBytes, writable: false); + using var image = DevExpress.Drawing.DXImage.FromStream(imageStream); + + if (image.Width <= 0 || image.Height <= 0) + return area; + + var scale = Math.Min(area.Width / image.Width, area.Height / image.Height); + var drawW = image.Width * scale; + var drawH = image.Height * scale; + var drawX = area.X + ((area.Width - drawW) / 2f); + var drawY = area.Y + ((area.Height - drawH) / 2f); + + return new System.Drawing.RectangleF(drawX, drawY, drawW, drawH); + } + + static System.Drawing.RectangleF ToPdfRect(double x, double y, double width, double height, double pageHeight) + => new((float)x, (float)(pageHeight - y - height), (float)width, (float)height); + + static float ToPdfY(double y, double pageHeight) + => (float)(pageHeight - y); + static byte[]? DataUrlToBytes(string? dataUrl) { if (string.IsNullOrWhiteSpace(dataUrl)) return null; diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/DevSignatureInkController.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/DevSignatureInkController.cs new file mode 100644 index 00000000..902983b1 --- /dev/null +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/DevSignatureInkController.cs @@ -0,0 +1,99 @@ +using DigitalData.Core.Abstraction.Application.Repository; +using DevExpress.Drawing; +using DevExpress.Pdf; +using EnvelopeGenerator.Application.Common.Dto; +using EnvelopeGenerator.Domain.Entities; +using EnvelopeGenerator.Domain.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using EnvelopeGenerator.Application.Common.Interfaces.Doc; + +namespace EnvelopeGenerator.Server.Controllers; + +/// +/// Temporary development endpoint to download stored signature ink image by element id. +/// +[ApiController] +[AllowAnonymous] +[Route("api/dev/signature-ink")] +public class DevSignatureInkController( + IRepository repository, + IWebHostEnvironment environment, + IConfiguration configuration, + IPdfPageImageRenderer renderer) : ControllerBase +{ + /// + /// Downloads the signature image stored in INK for the given DocumentReceiverElement id. + /// + [HttpGet("{id:int}")] + public async Task DownloadInk([FromRoute] int id, CancellationToken cancel) + { + if (!IsEndpointEnabled()) + return NotFound(); + + var element = await repository.Query + .AsNoTracking() + .Where(e => e.Id == id) + .Select(e => new DocReceiverElementDto + { + Id = e.Id, + Ink = e.Ink + }) + .SingleOrDefaultAsync(cancel); + + if (element is null) + return NotFound($"DocumentReceiverElement not found for id={id}."); + + if (element.Ink is not { Length: > 0 }) + return NotFound($"No signature image (Ink) found for id={id}."); + + var image = element.ToImage(); + return File(image.Bytes, image.ContentType, $"signature-ink-{id}{image.FileExtension}"); + } + + /// + /// Downloads a composed signature image (background + ink + signer metadata) for the given element id. + /// + [HttpGet("{id:int}/composed")] + public async Task DownloadComposedInk([FromRoute] int id, CancellationToken cancel) + { + if (!IsEndpointEnabled()) + return NotFound(); + + var element = await repository.Query + .AsNoTracking() + .Where(e => e.Id == id) + .Select(e => new DocReceiverElementDto + { + Id = e.Id, + Ink = e.Ink, + FullName = e.FullName, + Position = e.Position, + Place = e.Place, + ChangedWhen = e.ChangedWhen + }) + .SingleOrDefaultAsync(cancel); + + if (element is null) + return NotFound($"DocumentReceiverElement not found for id={id}."); + + if (element.Ink is not { Length: > 0 }) + return NotFound($"No signature image (Ink) found for id={id}."); + + if (element.ChangedWhen is null) + return Conflict($"DocumentReceiverElement id={id} has null ChangedWhen. Element is not signed yet or data integrity is broken."); + + if (string.IsNullOrWhiteSpace(element.FullName) || string.IsNullOrWhiteSpace(element.Place)) + return Conflict($"DocumentReceiverElement id={id} is missing signer metadata (FullName or Place)."); + + if (!renderer.IsSupportedImageFormat(element.Ink!)) + return StatusCode(StatusCodes.Status415UnsupportedMediaType); + + var composedBytes = renderer.ComposeSignaturePreviewPng(element); + return File(composedBytes, "image/png", $"signature-composed-{id}.png"); + } + + private bool IsEndpointEnabled() + => environment.IsDevelopment() || configuration.GetValue("DevEndpoints:EnableSignatureInkInProduction"); +} diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/EnvelopeGenerator.Server.csproj b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/EnvelopeGenerator.Server.csproj index 03aa3cc3..ea0d6042 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/EnvelopeGenerator.Server.csproj +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/EnvelopeGenerator.Server.csproj @@ -48,7 +48,6 @@ - diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json index ec34e679..51bf6274 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json @@ -2,6 +2,9 @@ "UseSwagger": true, "UseScalar": true, "UseDbMigration": false, + "DevEndpoints": { + "EnableSignatureInkInProduction": false + }, "DiPMode": true, "Logging": { "LogLevel": {