- Added `disposeOutputStream` parameter to Pdf constructor - Updated `FromMemory` to set output stream disposal based on null value - Ensured proper cleanup of output stream in Dispose and DisposeAsync methods - Improved resource management to prevent potential memory leaks
206 lines
6.8 KiB
C#
206 lines
6.8 KiB
C#
using iText.Kernel.Pdf;
|
|
using iText.Kernel.Pdf.Canvas;
|
|
using EnvelopeGenerator.Domain.Interfaces;
|
|
using iText.Kernel.Colors;
|
|
#if NETFRAMEWORK
|
|
using System;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using System.Collections.Generic;
|
|
#endif
|
|
|
|
namespace EnvelopeGenerator.PdfEditor
|
|
{
|
|
public static class Pdf
|
|
{
|
|
public static Pdf<MemoryStream, MemoryStream> FromMemory(byte[] documentBytes)
|
|
{
|
|
return new Pdf<MemoryStream, MemoryStream>(new MemoryStream(documentBytes), new MemoryStream());
|
|
}
|
|
|
|
public static Pdf<MemoryStream, MemoryStream> FromMemory(MemoryStream stream, MemoryStream
|
|
#if NET
|
|
?
|
|
#endif
|
|
outputStream = null)
|
|
{
|
|
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 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;
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the output stream containing the edited PDF document.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Accessing this property will close the underlying <see cref="PdfDocument"/> to ensure
|
|
/// all changes are flushed to the stream. After accessing this property, the PDF document
|
|
/// can no longer be modified using this <see cref="Pdf{TInputStream, TOutputStream}"/> instance.
|
|
/// </remarks>
|
|
/// <returns>
|
|
/// The <typeparamref name="TOutputStream"/> instance that contains the updated PDF bytes.
|
|
/// </returns>
|
|
public TOutputStream ExportStream()
|
|
{
|
|
_doc.Close();
|
|
return _outputStream;
|
|
}
|
|
|
|
#region Edit methods
|
|
public Pdf<TInputStream, TOutputStream> Document(Action<PdfDocument> edit)
|
|
{
|
|
edit(_doc);
|
|
return this;
|
|
}
|
|
|
|
public Pdf<TInputStream, TOutputStream> Page(int pageIndex, Action<PdfPage> design)
|
|
=> Document(doc =>
|
|
{
|
|
var page = doc.GetPage(pageIndex);
|
|
design(doc.GetPage(pageIndex));
|
|
});
|
|
|
|
public Pdf<TInputStream, TOutputStream> Page(Action<PdfPage> design)
|
|
{
|
|
for (int i = 1; i <= _doc.GetNumberOfPages(); i++)
|
|
Page(i, design);
|
|
|
|
return this;
|
|
}
|
|
|
|
public Pdf<TInputStream, TOutputStream> Design(int pageIndex, Action<PdfCanvas> design)
|
|
=> Document(doc =>
|
|
{
|
|
var page = doc.GetPage(pageIndex);
|
|
var canvas = new PdfCanvas(page);
|
|
design(canvas);
|
|
});
|
|
#endregion
|
|
|
|
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures)
|
|
where TSignature : ISignature
|
|
{
|
|
// once per page
|
|
Page(page =>
|
|
{
|
|
var canvas = new PdfCanvas(page);
|
|
canvas.ConcatMatrix(1, 0, 0, -1, 0, page.GetPageSize().GetHeight());
|
|
});
|
|
|
|
foreach (var signature in signatures)
|
|
Page(signature.Page, page =>
|
|
{
|
|
var canvas = new PdfCanvas(page);
|
|
double inchFactor = 72;
|
|
double magin = .2;
|
|
double x = (signature.X - .7 - magin) * inchFactor;
|
|
double y = (signature.Y - .5 - magin) * inchFactor;
|
|
double width = 1.9500000000000002 * inchFactor;
|
|
double height = 2.52 * inchFactor;
|
|
|
|
double bottomLineLength = 2.5;
|
|
|
|
// draw background
|
|
canvas.SetFillColor(new DeviceRgb(222, 220, 215))
|
|
.Rectangle(x, y, width, height)
|
|
.Fill();
|
|
|
|
// draw bottom line
|
|
canvas.SetFillColor(new DeviceRgb(204, 202, 198))
|
|
.Rectangle(x, y + height - bottomLineLength, width, bottomLineLength)
|
|
.Fill();
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
#region Finalizer
|
|
private bool _disposed = false;
|
|
|
|
~Pdf()
|
|
{
|
|
// If Dispose is not called, clean up unmanaged resources
|
|
Dispose(false);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private void Dispose(bool disposing)
|
|
{
|
|
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 { }
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
_doc?.Close();
|
|
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
|
|
}
|
|
} |