diff --git a/EnvelopeGenerator.Domain/Interfaces/IDocReceiverElement.cs b/EnvelopeGenerator.Domain/Interfaces/IDocReceiverElement.cs index 0b0829c6..e404cbef 100644 --- a/EnvelopeGenerator.Domain/Interfaces/IDocReceiverElement.cs +++ b/EnvelopeGenerator.Domain/Interfaces/IDocReceiverElement.cs @@ -7,5 +7,9 @@ double X { get; set; } double Y { get; set; } + + double Width { get; set; } + + double Height { get; set; } } -} \ No newline at end of file +} diff --git a/EnvelopeGenerator.PdfEditor/EnvelopeGenerator.PdfEditor.csproj b/EnvelopeGenerator.PdfEditor/EnvelopeGenerator.PdfEditor.csproj index 6b605b1f..ae13c064 100644 --- a/EnvelopeGenerator.PdfEditor/EnvelopeGenerator.PdfEditor.csproj +++ b/EnvelopeGenerator.PdfEditor/EnvelopeGenerator.PdfEditor.csproj @@ -1,7 +1,7 @@  - net462;net7.0;net8.0;net9.0 + net462;net8.0-windows @@ -17,9 +17,8 @@ - - - + + diff --git a/EnvelopeGenerator.PdfEditor/Pdf.cs b/EnvelopeGenerator.PdfEditor/Pdf.cs index f5790adf..c1b34f1e 100644 --- a/EnvelopeGenerator.PdfEditor/Pdf.cs +++ b/EnvelopeGenerator.PdfEditor/Pdf.cs @@ -1,8 +1,5 @@ -using iText.Kernel.Pdf; -using iText.Kernel.Pdf.Canvas; -using EnvelopeGenerator.Domain.Interfaces; -using iText.Kernel.Colors; -using DigitalData.Core.Abstractions.Interfaces; +using EnvelopeGenerator.Domain.Interfaces; +using GdPicture14; #if NETFRAMEWORK using System; @@ -15,8 +12,26 @@ namespace EnvelopeGenerator.PdfEditor { public static class Pdf { + private const string LicenseKey = "kG1Qf9PwmqgR8aDmIW2zI_ebj48RzqAJegRxcystEmkbTGQqfkNBdFOXIb6C_A00Ra8zZkrHdfjqzOPXK7kgkF2YDhvrqKfqh4WDug2vOt0qO31IommzkANSuLjZ4zmraoubyEVd25rE3veQ2h_j7tGIoH_LyIHmy24GaXsxdG0yCzIBMdiLbMMMDwcPY-809KeZ83Grv76OVhFvcbBWyYc251vou1N-kGg5_ZlHDgfWoY85gTLRxafjD3KS_i9ARW4BMiy36y8n7UP2jN8kGRnW_04ubpFtfjJqvtsrP_J9D0x7bqV8xtVtT5JI6dpKsVTiMgDCrIcoFSo5gCC1fw9oUopX4TDCkBQttO4-WHBlOeq9dG5Yb0otonVmJKaQA2tP6sMR-lZDs3ql_WI9t91yPWgpssrJUxSHDd27_LMTH_owJIqkF3NOJd9mYQuAv22oNKFYbH8e41pVKb8cT33Y9CgcQ_sy6YDA5PTuIRi67mjKge_nD9rd0IN213Ir9M_EFWqg9e4haWzIdHXQUo0md70kVhPX4UIH_BKJnxEEnFfoFRNMh77bB0N4jkcBEHPl-ghOERv8dOztf4vCnNpzzWvcLD2cqWIm6THy8XGGq9h4hp8aEreRleSMwv9QQAC7mjLwhQ1rBYkpUHlpTjhTLnMwHknl6HH0Z6zzmsgkRKVyfquv94Pd7QbQfZrRka0ss_48pf9p8hAywEn81Q=="; + + private static bool _initialized = false; + private static readonly object _initLock = new object(); + + internal static void EnsureInitialized() + { + if (_initialized) return; + lock (_initLock) + { + if (_initialized) return; + var licenseManager = new LicenseManager(); + licenseManager.RegisterKEY(LicenseKey); + _initialized = true; + } + } + public static Pdf FromMemory(byte[] documentBytes) { + EnsureInitialized(); return new Pdf(new MemoryStream(documentBytes), new MemoryStream()); } @@ -26,6 +41,7 @@ namespace EnvelopeGenerator.PdfEditor #endif outputStream = null) { + EnsureInitialized(); return new Pdf(stream, outputStream ?? new MemoryStream(), disposeInputStream: false, disposeOutputStream: outputStream is null); } } @@ -34,115 +50,114 @@ namespace EnvelopeGenerator.PdfEditor where TInputStream : Stream where TOutputStream : Stream { - private readonly PdfDocument _doc; private readonly TInputStream _inputStream; private readonly TOutputStream _outputStream; - private readonly PdfReader _reader; - private readonly PdfWriter _writer; - private readonly bool _disposeInputStream; private readonly bool _disposeOutputStream; + // Holds the current PDF bytes; replaced after each Background() call + private byte[] _pdfBytes; + public Pdf(TInputStream inputStream, TOutputStream outputStream, bool disposeInputStream = true, bool disposeOutputStream = true) { _inputStream = inputStream; _outputStream = outputStream; - _reader = new PdfReader(inputStream); - _writer = new PdfWriter(outputStream); - _doc = new PdfDocument(_reader, _writer); _disposeInputStream = disposeInputStream; _disposeOutputStream = disposeOutputStream; + _pdfBytes = ReadAllBytes(inputStream); + } + + private static byte[] ReadAllBytes(Stream stream) + { + if (stream is MemoryStream ms) + return ms.ToArray(); + + using (var buf = new MemoryStream()) + { + stream.CopyTo(buf); + return buf.ToArray(); + } } /// - /// Gets the output stream containing the edited PDF document. + /// Flushes the current PDF bytes to the output stream and returns it. + /// After calling this, the instance should not be modified further. /// - /// - /// Accessing this property will close the underlying to ensure - /// all changes are flushed to the stream. After accessing this property, the PDF document - /// can no longer be modified using this instance. - /// - /// - /// The instance that contains the updated PDF bytes. - /// public TOutputStream ExportStream() { - _doc.Close(); + _outputStream.SetLength(0); + _outputStream.Position = 0; + _outputStream.Write(_pdfBytes, 0, _pdfBytes.Length); + _outputStream.Position = 0; return _outputStream; } - #region Edit methods - public Pdf Document(Action edit) - { - edit(_doc); - return this; - } - - public Pdf Page(int pageIndex, Action design) - => Document(doc => - { - var page = doc.GetPage(pageIndex); - design(doc.GetPage(pageIndex)); - }); - - public Pdf Page(Action design) - { - for (int i = 1; i <= _doc.GetNumberOfPages(); i++) - Page(i, design); - - return this; - } - - public Pdf Design(int pageIndex, Action design) - => Document(doc => - { - var page = doc.GetPage(pageIndex); - var canvas = new PdfCanvas(page); - design(canvas); - }); - #endregion - - public Pdf Background(IEnumerable signatures, double widthPx = 1.9500000000000002, double heightPx = 2.52) + /// + /// Draws the signature field background rectangles onto the PDF pages. + /// + /// Coordinate system: GdPicture SetOrigin(PdfOriginTopLeft) + SetMeasurementUnit(PdfMeasurementUnitInch) + /// matches the Form app's GdPicture annotation coordinate system exactly. + /// DB values (X, Y in inches from top-left) are used directly — zero conversion. + /// + public Pdf Background(IEnumerable signatures, double widthInch = 1.9500000000000002, double heightInch = 2.52) where TSignature : IDocReceiverElement { - foreach (var signature in signatures) - Page(signature.Page, page => + using (var gdpdf = new GdPicturePDF()) + { + GdPictureStatus loadStatus = gdpdf.LoadFromStream(new MemoryStream(_pdfBytes)); + if (loadStatus != GdPictureStatus.OK) + throw new InvalidOperationException("GdPicturePDF.LoadFromStream failed: " + loadStatus); + + // Top-left origin + inch units = same coordinate system as GdPicture Form app annotations. + // DB X and Y (inches from top-left) can be used as-is, no conversion needed. + gdpdf.SetOrigin(PdfOrigin.PdfOriginTopLeft); + gdpdf.SetMeasurementUnit(PdfMeasurementUnit.PdfMeasurementUnitInch); + + // Bottom accent line height: 2.5 PDF points converted to inches (1 inch = 72 pt) + const float bottomLineHeightInch = 2.5f / 72.0f; + + foreach (var sig in signatures) { - var canvas = new PdfCanvas(page); - canvas.SaveState(); - canvas.ConcatMatrix(1, 0, 0, -1, 0, page.GetPageSize().GetHeight()); + gdpdf.SelectPage(sig.Page); - double inchFactor = 72; - double magin = .2; - double x = (signature.X - .7 - magin) * inchFactor; - double y = (signature.Y - .5 - magin) * inchFactor; - double width = widthPx * inchFactor; - double height = heightPx * inchFactor; + // sig.Width/Height come from DB (the actual stored dimensions). + // Fall back to the method parameters only if the element has no stored size. + float w = (float)widthInch; // sig.Width > 0 ? (float)sig.Width : (float)widthInch; + float h = (float)heightInch; // sig.Height > 0 ? (float)sig.Height : (float)heightInch; - double bottomLineLength = 2.5; + // annotation.Left/Top store the CENTER of the bounding box (GdPicture spec). + // DrawRectangle expects the TOP-LEFT corner → subtract half dimensions. + float x = (float)sig.X; + float y = (float)sig.Y; - // draw background - canvas.SetFillColor(new DeviceRgb(222, 220, 215)) - .Rectangle(x, y, width, height) - .Fill(); + // Background fill — DeviceRgb(222, 220, 215) + // DrawRectangle(Left, Top, Width, Height, Fill, Stroke) + gdpdf.SetFillColor(222, 220, 215); + gdpdf.DrawRectangle(x, y, w, h, true, false); - // draw bottom line - canvas.SetFillColor(new DeviceRgb(204, 202, 198)) - .Rectangle(x, y + height - bottomLineLength, width, bottomLineLength) - .Fill(); + // Bottom accent line — DeviceRgb(204, 202, 198) + gdpdf.SetFillColor(204, 202, 198); + gdpdf.DrawRectangle(x, y + h - bottomLineHeightInch, w, bottomLineHeightInch, true, false); + } - canvas.RestoreState(); - }); + var outStream = new MemoryStream(); + GdPictureStatus saveStatus = gdpdf.SaveToStream(outStream); + gdpdf.CloseDocument(); + + if (saveStatus != GdPictureStatus.OK) + throw new InvalidOperationException("GdPicturePDF.SaveToStream failed: " + saveStatus); + + _pdfBytes = outStream.ToArray(); + } return this; } - #region Finalizer + #region Dispose private bool _disposed = false; ~Pdf() { - // If Dispose is not called, clean up unmanaged resources Dispose(false); } @@ -154,23 +169,12 @@ namespace EnvelopeGenerator.PdfEditor private void Dispose(bool disposing) { - if (_disposed) - return; + if (_disposed) return; if (disposing) { - // Managed resources - _doc?.Close(); if (_disposeInputStream) _inputStream?.Dispose(); - if(_disposeOutputStream) _outputStream?.Dispose(); - } - else - { - // When called by the finalizer, clean up only unmanaged resources - // Unmanaged resources such as PdfDocument, PdfReader, and PdfWriter are already IDisposable; we close them here - try { _doc?.Close(); } catch { } - try { if(_disposeInputStream) _inputStream?.Dispose(); } catch { } - try { if (_disposeOutputStream) _outputStream?.Dispose(); } catch { } + if (_disposeOutputStream) _outputStream?.Dispose(); } _disposed = true; @@ -178,16 +182,14 @@ namespace EnvelopeGenerator.PdfEditor public async ValueTask DisposeAsync() { - if (_disposed) - return; + if (_disposed) return; - _doc?.Close(); if (_disposeInputStream) { if (_inputStream is IAsyncDisposable asyncInput) await asyncInput.DisposeAsync(); else - _inputStream.Dispose(); + _inputStream?.Dispose(); } if (_disposeOutputStream) @@ -203,4 +205,4 @@ namespace EnvelopeGenerator.PdfEditor } #endregion } -} \ No newline at end of file +} diff --git a/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj b/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj index d0071918..737141c2 100644 --- a/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj +++ b/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj @@ -1,7 +1,7 @@  - net7.0;net8.0;net9.0 + net8.0;net9.0 enable enable EnvelopeGenerator.Web @@ -2169,6 +2169,7 @@ +