using EnvelopeGenerator.Domain.Interfaces; using GdPicture14; #if NETFRAMEWORK using System; using System.IO; using System.Threading.Tasks; using System.Collections.Generic; #endif 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()); } public static Pdf FromMemory(MemoryStream stream, MemoryStream #if NET ? #endif outputStream = null) { EnsureInitialized(); return new Pdf(stream, outputStream ?? new MemoryStream(), disposeInputStream: false, disposeOutputStream: outputStream is null); } } public class Pdf : IDisposable, IAsyncDisposable where TInputStream : Stream where TOutputStream : Stream { private readonly TInputStream _inputStream; private readonly TOutputStream _outputStream; 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; _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(); } } /// /// Flushes the current PDF bytes to the output stream and returns it. /// After calling this, the instance should not be modified further. /// public TOutputStream ExportStream() { _outputStream.SetLength(0); _outputStream.Position = 0; _outputStream.Write(_pdfBytes, 0, _pdfBytes.Length); _outputStream.Position = 0; return _outputStream; } /// /// 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 { 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) { gdpdf.SelectPage(sig.Page); // 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; // 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; // 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); // Bottom accent line — DeviceRgb(204, 202, 198) gdpdf.SetFillColor(204, 202, 198); gdpdf.DrawRectangle(x, y + h - bottomLineHeightInch, w, bottomLineHeightInch, true, false); } 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 Dispose private bool _disposed = false; ~Pdf() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_disposed) return; if (disposing) { if (_disposeInputStream) _inputStream?.Dispose(); if (_disposeOutputStream) _outputStream?.Dispose(); } _disposed = true; } public async ValueTask DisposeAsync() { if (_disposed) return; if (_disposeInputStream) { if (_inputStream is IAsyncDisposable asyncInput) await asyncInput.DisposeAsync(); else _inputStream?.Dispose(); } if (_disposeOutputStream) { if (_outputStream is IAsyncDisposable asyncOutput) await asyncOutput.DisposeAsync(); else _outputStream?.Dispose(); } _disposed = true; GC.SuppressFinalize(this); } #endregion } }