diff --git a/EnvelopeGenerator.Application/Common/Dto/PdfSignatureAppearanceDto.cs b/EnvelopeGenerator.Application/Common/Dto/PdfSignatureAppearanceDto.cs index a3716d4e..f3afa1fc 100644 --- a/EnvelopeGenerator.Application/Common/Dto/PdfSignatureAppearanceDto.cs +++ b/EnvelopeGenerator.Application/Common/Dto/PdfSignatureAppearanceDto.cs @@ -3,7 +3,38 @@ namespace EnvelopeGenerator.Application.Common.Dto; /// /// /// -/// +public enum PdfSignatureCoordinateUnit +{ + /// + /// + /// + Point = 0, + + /// + /// + /// + Inch = 1 +} + +/// +/// +/// +public enum PdfSignatureCoordinateOrigin +{ + /// + /// + /// + BottomLeft = 0, + + /// + /// + /// + TopLeft = 1 +} + +/// +/// +/// /// /// /// @@ -16,6 +47,8 @@ namespace EnvelopeGenerator.Application.Common.Dto; /// /// /// +/// +/// 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) { /// /// diff --git a/EnvelopeGenerator.Infrastructure.Crypt/Services/PdfDigitalSignatureService.cs b/EnvelopeGenerator.Infrastructure.Crypt/Services/PdfDigitalSignatureService.cs index e84280f0..6f26379d 100644 --- a/EnvelopeGenerator.Infrastructure.Crypt/Services/PdfDigitalSignatureService.cs +++ b/EnvelopeGenerator.Infrastructure.Crypt/Services/PdfDigitalSignatureService.cs @@ -43,8 +43,8 @@ public sealed class PdfDigitalSignatureService(IOptions 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 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 CheckPdfSignatureAsync(Stream inputPdf, CancellationToken cancellationToken = default) {