2025-09-26 16:38:35 +02:00

176 lines
5.7 KiB
C#

using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using EnvelopeGenerator.Domain.Interfaces;
using iText.Kernel.Colors;
using iText.Kernel.Geom;
#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 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;
public Pdf(TInputStream inputStream, TOutputStream outputStream)
{
_inputStream = inputStream;
_outputStream = outputStream;
_reader = new PdfReader(inputStream);
_writer = new PdfWriter(outputStream);
_doc = new PdfDocument(_reader, _writer);
}
/// <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> 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
{
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;
Rectangle pageSize = page.GetPageSize();
canvas.ConcatMatrix(1, 0, 0, -1, 0, pageSize.GetHeight());
// 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();
_inputStream?.Dispose();
_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 { _inputStream?.Dispose(); } catch { }
try { _outputStream?.Dispose(); } catch { }
}
_disposed = true;
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_doc?.Close();
if (_inputStream is IAsyncDisposable asyncInput)
await asyncInput.DisposeAsync();
else
_inputStream.Dispose();
if (_outputStream is IAsyncDisposable asyncOutput)
await asyncOutput.DisposeAsync();
else
_outputStream?.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
#endregion
}
}