Support flexible coordinate systems for PDF signatures

Added enums `PdfSignatureCoordinateUnit` and `PdfSignatureCoordinateOrigin` to support defining signature coordinates in points or inches and relative to either the bottom-left or top-left corner of the page.

Updated `PdfSignatureAppearanceDto` to include `CoordinateUnit` and `CoordinateOrigin` parameters with default values. Refactored `PdfDigitalSignatureService` to use a new `BuildSignatureBounds` method for calculating signature bounds, which handles unit conversion, origin adjustments, and validation.

Introduced `GetPageHeightInPoints` to retrieve page height for top-left origin calculations. Improved signature field naming precision and added error handling for invalid dimensions and page numbers.

Included a TODO for optimizing the signature check algorithm.
This commit is contained in:
2026-10-20 14:18:20 +02:00
parent 67d0b07926
commit e729adb441
2 changed files with 94 additions and 4 deletions

View File

@@ -3,7 +3,38 @@ namespace EnvelopeGenerator.Application.Common.Dto;
/// <summary>
///
/// </summary>
/// <param name="ImageBytes"></param>
public enum PdfSignatureCoordinateUnit
{
/// <summary>
///
/// </summary>
Point = 0,
/// <summary>
///
/// </summary>
Inch = 1
}
/// <summary>
///
/// </summary>
public enum PdfSignatureCoordinateOrigin
{
/// <summary>
///
/// </summary>
BottomLeft = 0,
/// <summary>
///
/// </summary>
TopLeft = 1
}
/// <summary>
///
/// </summary>
/// <param name="PageNumber"></param>
/// <param name="Left"></param>
/// <param name="Bottom"></param>
@@ -16,6 +47,8 @@ namespace EnvelopeGenerator.Application.Common.Dto;
/// <param name="ShowLocation"></param>
/// <param name="ShowDistinguishedName"></param>
/// <param name="DateTimeFormat"></param>
/// <param name="CoordinateUnit"></param>
/// <param name="CoordinateOrigin"></param>
public sealed record PdfSignatureAppearanceDto(
int PageNumber,
double Left,
@@ -28,7 +61,9 @@ public sealed record PdfSignatureAppearanceDto(
bool ShowReason = false,
bool ShowLocation = false,
bool ShowDistinguishedName = false,
string DateTimeFormat = "dd.MM.yyyy HH:mm:ss zzz")
string DateTimeFormat = "dd.MM.yyyy HH:mm:ss zzz",
PdfSignatureCoordinateUnit CoordinateUnit = PdfSignatureCoordinateUnit.Point,
PdfSignatureCoordinateOrigin CoordinateOrigin = PdfSignatureCoordinateOrigin.BottomLeft)
{
/// <summary>
///

View File

@@ -43,8 +43,8 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
var builder = request.Appearance is null ? new PdfDeferredSignatureBuilder(signerInfo) : new (signerInfo, new PdfSignatureFieldInfo(request.Appearance.PageNumber)
{
Name = $"Sig_{DateTimeOffset.UtcNow:yyyyMMddHHmmss}",
SignatureBounds = new PdfRectangle(request.Appearance.Left, request.Appearance.Bottom, request.Appearance.Left + request.Appearance.Width, request.Appearance.Bottom + request.Appearance.Height)
Name = $"Sig_{DateTimeOffset.UtcNow:yyyyMMddHHmmssfff}",
SignatureBounds = BuildSignatureBounds(request.Appearance, workingInput)
});
builder.Name = request.Name;
@@ -105,6 +105,61 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
}
}
private static PdfRectangle BuildSignatureBounds(PdfSignatureAppearanceDto appearance, Stream inputPdf)
{
var left = appearance.Left;
var bottom = appearance.Bottom;
var width = appearance.Width;
var height = appearance.Height;
if (appearance.CoordinateUnit == PdfSignatureCoordinateUnit.Inch)
{
left *= 72.0;
bottom *= 72.0;
width *= 72.0;
height *= 72.0;
}
if (width <= 0 || height <= 0)
throw new BadRequestException("Signature appearance width and height must be greater than zero.");
if (appearance.CoordinateOrigin == PdfSignatureCoordinateOrigin.TopLeft)
{
var pageHeight = GetPageHeightInPoints(inputPdf, appearance.PageNumber);
bottom = pageHeight - bottom - height;
}
return new PdfRectangle(left, bottom, left + width, bottom + height);
}
private static double GetPageHeightInPoints(Stream inputPdf, int pageNumber)
{
if (pageNumber <= 0)
throw new BadRequestException("Signature page number must be greater than zero.");
var previousPosition = inputPdf.CanSeek ? inputPdf.Position : 0;
try
{
if (inputPdf.CanSeek)
inputPdf.Position = 0;
using var processor = new PdfDocumentProcessor();
processor.LoadDocument(inputPdf);
var pageCount = processor.Document.Pages.Count;
if (pageNumber > pageCount)
throw new BadRequestException($"Signature page number {pageNumber} is out of range. Document has {pageCount} page(s).");
return processor.Document.Pages[pageNumber - 1].CropBox.Height;
}
finally
{
if (inputPdf.CanSeek)
inputPdf.Position = previousPosition;
}
}
//TODO: verify and optimize (remove memory stream etc.) the check algortihm
public async Task<PdfSignatureCheckResultDto> CheckPdfSignatureAsync(Stream inputPdf, CancellationToken cancellationToken = default)
{