Files
EnvelopeGenerator/EnvelopeGenerator.Infrastructure.Doc/PdfPageImageRenderer.cs
TekH f1c8e223dd Refactor ComposeSignaturePreviewPng to return Stream
Updated the `ComposeSignaturePreviewPng` method in the `IPdfPageImageRenderer` interface to return a `Stream` instead of a `byte[]` for improved memory efficiency when handling large image data.

- Added `ComposeSignaturePreviewPngBytes` extension method to convert the `Stream` to a `byte[]` for backward compatibility.
- Updated `PdfPageImageRenderer` to return a `Stream` and adjusted its internal logic to reset the `MemoryStream` position before returning.
- Modified `DevSignatureInkController` to use the `Stream`-based implementation for HTTP responses.
- Made minor formatting adjustments in `PdfPageImageRenderer`.

These changes optimize memory usage while maintaining compatibility with existing workflows that require `byte[]`.
2026-10-19 14:49:48 +02:00

181 lines
6.8 KiB
C#

using DevExpress.Drawing;
using DevExpress.Pdf;
using DigitalData.Core.Exceptions;
using EnvelopeGenerator.Application.Common.Dto;
using EnvelopeGenerator.Application.Common.Interfaces.Doc;
using EnvelopeGenerator.Domain.Interfaces;
namespace EnvelopeGenerator.Infrastructure.Doc;
public class PdfPageImageRenderer : IPdfPageImageRenderer
{
private const float RenderDpi = 110f;
public Task<IReadOnlyList<PdfPageImageDto>> RenderPagesAsImagesAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(pdfBytes);
if (pdfBytes.Length == 0)
return Task.FromResult<IReadOnlyList<PdfPageImageDto>>(Array.Empty<PdfPageImageDto>());
using var stream = new MemoryStream(pdfBytes, writable: false);
using var processor = new PdfDocumentProcessor();
processor.LoadDocument(stream);
var pages = processor.Document.Pages;
if (pages.Count == 0)
return Task.FromResult<IReadOnlyList<PdfPageImageDto>>(Array.Empty<PdfPageImageDto>());
var renderOptions = PdfPageRenderingParameters.CreateWithResolution(RenderDpi);
var results = new List<PdfPageImageDto>(pages.Count);
for (var i = 0; i < pages.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var pageNumber = i + 1;
var page = pages[i];
var cropBox = page.CropBox;
using DXBitmap bitmap = processor.CreateDXBitmap(pageNumber, renderOptions);
using var imageStream = new MemoryStream();
bitmap.Save(imageStream, DXImageFormat.Png);
var dataUrl = $"data:image/png;base64,{Convert.ToBase64String(imageStream.ToArray())}";
results.Add(new PdfPageImageDto
{
PageNumber = pageNumber,
WidthPt = cropBox.Width,
HeightPt = cropBox.Height,
WidthPx = bitmap.Width,
HeightPx = bitmap.Height,
ImageDataUrl = dataUrl
});
}
return Task.FromResult<IReadOnlyList<PdfPageImageDto>>(results);
}
public Stream ComposeSignaturePreviewPng(ISignatureInkPayload element)
{
const double sigW = 1.77 * 72;
const double sigH = 1.96 * 72;
const double imgRatio = 0.52;
const double lineH = 11.5;
const double bgPad = 3.0;
const double padding = 3.0;
var date = element.ChangedWhen!.Value.ToString("dd.MM.yyyy");
var position = string.IsNullOrWhiteSpace(element.Position) ? null : element.Position.Trim();
var x = bgPad;
var y = bgPad;
var imgH = sigH * imgRatio;
var lineY = y + imgH + 1.0;
var textY = lineY + 1.5;
var row1Y = textY;
var row2Y = row1Y + lineH;
var row3Y = position is null ? row2Y : row2Y + lineH;
var contentBottom = row3Y + lineH;
var pageWidth = sigW + (bgPad * 2);
var pageHeight = contentBottom + bgPad;
var inkBytes = this.EnsureSupportedImageBytes(element.Ink!);
using var processor = new PdfDocumentProcessor();
processor.CreateEmptyDocument();
var page = processor.AddNewPage(new PdfRectangle(0, 0, pageWidth, pageHeight));
using var graphics = processor.CreateGraphics();
using var bgBrush = new DXSolidBrush(System.Drawing.Color.FromArgb(255, 255, 253, 240));
using var linePen = new DXPen(System.Drawing.Color.FromArgb(180, 100, 100, 120), 0.5f);
using var nameBrush = new DXSolidBrush(System.Drawing.Color.FromArgb(255, 20, 20, 20));
using var infoBrush = new DXSolidBrush(System.Drawing.Color.FromArgb(255, 80, 80, 80));
var fontBold = new DXFont("Arial", 7.5f, DXFontStyle.Bold);
var fontNormal = new DXFont("Arial", 6.5f, DXFontStyle.Regular);
graphics.FillRectangle(bgBrush, new System.Drawing.RectangleF(0, 0, (float)pageWidth, (float)pageHeight));
var imageArea = new System.Drawing.RectangleF((float)x, (float)y, (float)sigW, (float)imgH);
var imageRect = FitImageContain(imageArea, inkBytes);
graphics.DrawImage(inkBytes, imageRect);
graphics.DrawLine(linePen, (float)(x + 2), (float)lineY, (float)(x + sigW - 2), (float)lineY);
graphics.DrawString(
element.FullName!.Trim(),
fontBold,
nameBrush,
new System.Drawing.RectangleF((float)(x + padding), (float)row1Y, (float)(sigW - (padding * 2)), (float)lineH));
if (position is not null)
{
graphics.DrawString(
position,
fontNormal,
infoBrush,
new System.Drawing.RectangleF((float)(x + padding), (float)row2Y, (float)(sigW - (padding * 2)), (float)lineH));
}
graphics.DrawString(
$"{element.Place!.Trim()}, {date}",
fontNormal,
infoBrush,
new System.Drawing.RectangleF((float)(x + padding), (float)row3Y, (float)(sigW - (padding * 2)), (float)lineH));
graphics.AddToPageForeground(page, 72, 72);
using var pdfStream = new MemoryStream();
processor.SaveDocument(pdfStream);
pdfStream.Position = 0;
using var rasterProcessor = new PdfDocumentProcessor();
rasterProcessor.LoadDocument(pdfStream);
var renderOptions = PdfPageRenderingParameters.CreateWithResolution(400f);
using DXBitmap bitmap = rasterProcessor.CreateDXBitmap(1, renderOptions);
var pngStream = new MemoryStream();
bitmap.Save(pngStream, DXImageFormat.Png);
pngStream.Position = 0;
return pngStream;
}
private static System.Drawing.RectangleF FitImageContain(System.Drawing.RectangleF area, byte[] imageBytes)
{
using var imageStream = new MemoryStream(imageBytes, writable: false);
using var image = DXImage.FromStream(imageStream);
if (image.Width <= 0 || image.Height <= 0)
return area;
var scale = Math.Min(area.Width / image.Width, area.Height / image.Height);
var drawW = image.Width * scale;
var drawH = image.Height * scale;
var drawX = area.X + ((area.Width - drawW) / 2f);
var drawY = area.Y + ((area.Height - drawH) / 2f);
return new System.Drawing.RectangleF(drawX, drawY, drawW, drawH);
}
/// <summary>
///
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public bool IsSupportedImageFormat(byte[] bytes)
{
var isPng = bytes.Length >= 4 &&
bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47;
if (isPng)
return true;
var isJpeg = bytes.Length >= 3 &&
bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF;
return isJpeg;
}
}