Introduced `ISignatureInkPayload` interface to standardize signature ink data and metadata. Updated `DocReceiverElementDto` and `DocReceiverElement` to implement this interface. Enhanced `IPdfPageImageRenderer` with methods for signature preview generation and image format validation. Replaced `PdfSharp` with `DevExpress` for PDF rendering in `ReceiverSignedPage.razor`. Added `DevSignatureInkController` with endpoints for downloading raw and composed signature images. Updated project files to remove `SixLabors.ImageSharp` and integrate DevExpress. Added configuration for development endpoints in `appsettings.json`. Improved exception handling, validation, and code maintainability.
179 lines
6.8 KiB
C#
179 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 byte[] 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);
|
|
using var pngStream = new MemoryStream();
|
|
bitmap.Save(pngStream, DXImageFormat.Png);
|
|
return pngStream.ToArray();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
} |