Add support for visual PDF signatures

Enhanced `PdfSignOptionsDto` with a `VisualSignature` property and introduced the `PdfVisualSignatureOptionsDto` class for configuring visual signature options. Updated `PdfDigitalSignatureService` to handle visual signatures, including page number resolution, signature bounds calculation, and appearance customization.

Refactored `PdfSignatureController` to support visual signatures by extending `SignPdfForm` with `SignatureImageFile` and `VSignature` properties. Improved validation for page numbers and signature dimensions. Streamlined `Sign` and `IsSigned` endpoints for better clarity and reduced redundancy.

Added XML documentation for improved API clarity and performed general code cleanup to enhance maintainability.
This commit is contained in:
2026-10-16 10:10:04 +02:00
parent 04cea1ca4b
commit 198eabcbae
4 changed files with 163 additions and 31 deletions

View File

@@ -6,38 +6,102 @@ using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Server.Controllers;
/// <summary>
///
/// </summary>
/// <param name="signatureService"></param>
[ApiController]
[Route("api/[controller]")]
public class PdfSignatureController(IPdfDigitalSignatureService signatureService) : ControllerBase
{
public sealed class SignPdfForm
/// <summary>
///
/// </summary>
public class CheckPdfSignatureForm
{
public IFormFile? File { get; set; }
public string? UUID { get; set; }
public string? Name { get; set; }
public string? Reason { get; set; }
public string? Location { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
/// <summary>
///
/// </summary>
public virtual required IFormFile File { get; init; }
}
public sealed class CheckPdfSignatureForm
/// <summary>
///
/// </summary>
public sealed class SignPdfForm : CheckPdfSignatureForm
{
public IFormFile? File { get; set; }
/// <summary>
///
/// </summary>
public required IFormFile SignatureImageFile { get; init; }
/// <summary>
///
/// </summary>
public required string? UUID { get; init; }
/// <summary>
///
/// </summary>
public required string? Name { get; init; }
/// <summary>
///
/// </summary>
public required string? Reason { get; init; }
/// <summary>
///
/// </summary>
public required string? Location { get; init; }
/// <summary>
///
/// </summary>
public required string? Email { get; init; }
/// <summary>
///
/// </summary>
public required string? Phone { get; init; }
/// <summary>
///
/// </summary>
public required VSignature Signature { get; init; }
/// <summary>
///
/// </summary>
/// <param name="PageNumber"></param>
/// <param name="Left"></param>
/// <param name="Bottom"></param>
/// <param name="Width"></param>
/// <param name="Height"></param>
/// <param name="ShowLabels"></param>
public record VSignature(
int PageNumber = 1,
double Left = 100,
double Bottom = 100,
double Width = 220,
double Height = 80,
bool ShowLabels = true);
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("sign")]
[Consumes("multipart/form-data")]
public async Task<IActionResult> Sign([FromForm] SignPdfForm request, CancellationToken cancel)
{
var file = request.File;
if (file is null || file.Length == 0)
return BadRequest("PDF file is required.");
if (!file.FileName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
return BadRequest("Only PDF files are supported.");
await using var signatureImageInput = request.SignatureImageFile.OpenReadStream();
using var signatureImageBuffer = new MemoryStream();
await signatureImageInput.CopyToAsync(signatureImageBuffer, cancel);
var signOptions = new PdfSignOptionsDto
{
@@ -47,31 +111,37 @@ public class PdfSignatureController(IPdfDigitalSignatureService signatureService
Location = request.Location,
Email = request.Email,
Phone = request.Phone,
CertificationLevel = PdfSignatureCertificationLevel.FormFillingAndAnnotations
CertificationLevel = PdfSignatureCertificationLevel.FormFillingAndAnnotations,
VisualSignature = new PdfVisualSignatureOptionsDto(
ImageBytes: signatureImageBuffer.ToArray(),
PageNumber: request.Signature.PageNumber,
Left: request.Signature.Left,
Bottom: request.Signature.Bottom,
Width: request.Signature.Width,
Height: request.Signature.Height,
ShowLabels: request.Signature.ShowLabels)
};
await using var input = file.OpenReadStream();
await using var output = new MemoryStream();
await using var input = request.File.OpenReadStream();
var output = new MemoryStream();
await signatureService.SignPdfAsync(input, output, signOptions, cancel);
output.Position = 0;
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
var fileName = Path.GetFileNameWithoutExtension(request.File.FileName);
return File(output, "application/pdf", $"{fileName}-signed.pdf");
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("is-signed")]
[Consumes("multipart/form-data")]
public async Task<IActionResult> IsSigned([FromForm] CheckPdfSignatureForm request, CancellationToken cancel)
{
var file = request.File;
if (file is null || file.Length == 0)
return BadRequest("PDF file is required.");
if (!file.FileName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
return BadRequest("Only PDF files are supported.");
await using var input = file.OpenReadStream();
await using var input = request.File.OpenReadStream();
var result = await signatureService.CheckPdfSignatureAsync(input, cancel);
return Ok(result);
}