Replaced iText with GdPicture for PDF manipulation, including: - Added `Width` and `Height` properties to `IDocReceiverElement`. - Updated `Pdf.cs` to use GdPicture's API for drawing and exporting. - Introduced GdPicture licensing management with `EnsureInitialized`. - Refactored `Background` method to align with GdPicture's coordinate system. - Simplified resource management in `Dispose` and `DisposeAsync`. Updated project files: - Modified target frameworks in `EnvelopeGenerator.PdfEditor.csproj` to remove `net7.0` and `net9.0`, and added `net8.0-windows`. - Replaced iText and BouncyCastle dependencies with GdPicture packages. - Added `Microsoft.Bcl.AsyncInterfaces` to `EnvelopeGenerator.Web.csproj`. Performed general cleanup and refactoring to remove unused iText code.
209 lines
8.2 KiB
C#
209 lines
8.2 KiB
C#
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<MemoryStream, MemoryStream> FromMemory(byte[] documentBytes)
|
|
{
|
|
EnsureInitialized();
|
|
return new Pdf<MemoryStream, MemoryStream>(new MemoryStream(documentBytes), new MemoryStream());
|
|
}
|
|
|
|
public static Pdf<MemoryStream, MemoryStream> FromMemory(MemoryStream stream, MemoryStream
|
|
#if NET
|
|
?
|
|
#endif
|
|
outputStream = null)
|
|
{
|
|
EnsureInitialized();
|
|
return new Pdf<MemoryStream, MemoryStream>(stream, outputStream ?? new MemoryStream(), disposeInputStream: false, disposeOutputStream: outputStream is null);
|
|
}
|
|
}
|
|
|
|
public class Pdf<TInputStream, TOutputStream> : 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flushes the current PDF bytes to the output stream and returns it.
|
|
/// After calling this, the instance should not be modified further.
|
|
/// </summary>
|
|
public TOutputStream ExportStream()
|
|
{
|
|
_outputStream.SetLength(0);
|
|
_outputStream.Position = 0;
|
|
_outputStream.Write(_pdfBytes, 0, _pdfBytes.Length);
|
|
_outputStream.Position = 0;
|
|
return _outputStream;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> 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
|
|
}
|
|
}
|