@page "/receiver" @using System.Drawing @using DevExpress.Drawing @using DevExpress.Utils @using DevExpress.XtraPrinting @using DevExpress.XtraPrinting.Drawing @using XtraReport = DevExpress.XtraReports.UI.XtraReport @using BottomMarginBand = DevExpress.XtraReports.UI.BottomMarginBand @using XRLabel = DevExpress.XtraReports.UI.XRLabel @using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox @using XRControl = DevExpress.XtraReports.UI.XRControl @using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode @using EnvelopeGenerator.ReceiverUI.Services; @inject IJSRuntime JSRuntime @inject InMemoryReportStorageWebExtension ReportStorage
Unterschrift

@if(SignatureApplied) { Die Unterschrift wurde dem Bericht hinzugefuegt. Sie koennen die Unterschrift erneuern oder das signierte PDF exportieren. } else { Bitte fuegen Sie vor dem PDF-Export Ihre Unterschrift hinzu. }

@if(!string.IsNullOrWhiteSpace(SignatureValidationMessage)) {
@SignatureValidationMessage
}
@if(ActiveSignatureTab == SignatureTabDraw) {

Bitte unterschreiben Sie im folgenden Feld.

} else if(ActiveSignatureTab == SignatureTabText) {

Geben Sie Ihre Unterschrift als Text ein und waehlen Sie eine Schriftart.

} else {

Laden Sie ein Bild Ihrer Unterschrift hoch.

}

Bitte geben Sie die folgenden Angaben ein. Das Datum wird automatisch hinzugefuegt.

@if(!string.IsNullOrWhiteSpace(PopupValidationMessage)) {
@PopupValidationMessage
}
@if(Report is not null) { } @code { const string SignatureTabDraw = "draw"; const string SignatureTabText = "text"; const string SignatureTabImage = "image"; const string DrawCanvasId = "receiver-signature-pad"; const string TypedCanvasId = "receiver-typed-signature-pad"; const string ImageInputId = "receiver-signature-image-input"; const string ImageCanvasId = "receiver-image-signature-pad"; readonly (string Text, string Value)[] TypedSignatureFonts = { ("Brush Script", "'Brush Script MT', cursive"), ("Segoe Script", "'Segoe Script', cursive"), ("Lucida Handwriting", "'Lucida Handwriting', cursive"), ("Comic Sans", "'Comic Sans MS', cursive"), ("Cursive", "cursive") }; DxReportViewer reportViewer; XtraReport? Report; bool SignatureApplied; bool SignaturePopupVisible; string? SignatureValidationMessage; string? PopupValidationMessage; string ActiveSignatureTab = SignatureTabDraw; string TypedSignatureText = string.Empty; string TypedSignatureFont = "'Brush Script MT', cursive"; string SignerFullName = string.Empty; string SignerPosition = string.Empty; string SignaturePlace = string.Empty; int ViewerKey; protected override async Task OnInitializedAsync() { Report = CreateReportInstance(); await Task.CompletedTask; } async Task OpenSignaturePopupAsync() { ActiveSignatureTab = SignatureTabDraw; SignaturePopupVisible = true; SignatureValidationMessage = null; PopupValidationMessage = null; await InvokeAsync(StateHasChanged); await Task.Delay(50); await InitializeActiveSignatureTabAsync(); } async Task SetSignatureTabAsync(string tab) { ActiveSignatureTab = tab; PopupValidationMessage = null; await InvokeAsync(StateHasChanged); await Task.Delay(50); await InitializeActiveSignatureTabAsync(); } async Task InitializeActiveSignatureTabAsync() { if(ActiveSignatureTab == SignatureTabDraw) { await JSRuntime.InvokeVoidAsync("receiverSignature.initialize", DrawCanvasId); } else if(ActiveSignatureTab == SignatureTabText) { await JSRuntime.InvokeVoidAsync("receiverSignature.initializeTyped", TypedCanvasId); await RenderTypedSignatureAsync(); } else { await JSRuntime.InvokeVoidAsync("receiverSignature.initializeImage", ImageInputId, ImageCanvasId); } } async Task RenewSignatureAsync() { PopupValidationMessage = null; if(ActiveSignatureTab == SignatureTabDraw) { await JSRuntime.InvokeVoidAsync("receiverSignature.clear", DrawCanvasId); } else if(ActiveSignatureTab == SignatureTabText) { TypedSignatureText = string.Empty; await JSRuntime.InvokeVoidAsync("receiverSignature.clearTyped", TypedCanvasId); } else { await JSRuntime.InvokeVoidAsync("receiverSignature.clearImage", ImageInputId, ImageCanvasId); } } void CloseSignaturePopup() { PopupValidationMessage = null; SignaturePopupVisible = false; } async Task OnTypedSignatureChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) { TypedSignatureText = args.Value?.ToString() ?? string.Empty; await RenderTypedSignatureAsync(); } async Task OnTypedSignatureFontChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) { TypedSignatureFont = args.Value?.ToString() ?? TypedSignatureFont; await RenderTypedSignatureAsync(); } async Task RenderTypedSignatureAsync() { await JSRuntime.InvokeVoidAsync("receiverSignature.renderTypedSignature", TypedCanvasId, TypedSignatureText, TypedSignatureFont); } async Task ApplySignatureAsync() { if(string.IsNullOrWhiteSpace(SignerFullName)) { PopupValidationMessage = "Bitte geben Sie Vor- und Nachname ein."; return; } if(string.IsNullOrWhiteSpace(SignaturePlace)) { PopupValidationMessage = "Bitte geben Sie den Ort ein."; return; } var signatureDataUrl = await GetActiveSignatureDataUrlAsync(); if(string.IsNullOrWhiteSpace(signatureDataUrl)) { PopupValidationMessage = "Die Unterschrift ist fuer den PDF-Export erforderlich."; return; } PopupValidationMessage = null; SignatureValidationMessage = null; Report = CreateSignedReportInstance(signatureDataUrl, SignerFullName.Trim(), SignerPosition.Trim(), SignaturePlace.Trim()); SignatureApplied = true; SignaturePopupVisible = false; ViewerKey++; } async Task GetActiveSignatureDataUrlAsync() { if(ActiveSignatureTab == SignatureTabDraw) return await JSRuntime.InvokeAsync("receiverSignature.getDataUrl", DrawCanvasId); if(ActiveSignatureTab == SignatureTabText) { await RenderTypedSignatureAsync(); return await JSRuntime.InvokeAsync("receiverSignature.getTypedDataUrl", TypedCanvasId); } return await JSRuntime.InvokeAsync("receiverSignature.getImageDataUrl", ImageCanvasId); } async Task ExportSignedPdfAsync() { if(!SignatureApplied || Report is null) { SignatureValidationMessage = "Bitte fuegen Sie die Unterschrift zuerst zum Bericht hinzu."; return; } try { SignatureValidationMessage = null; await reportViewer.ExportToAsync(ExportFormat.Pdf); } catch(Exception) { SignatureValidationMessage = "Das signierte PDF konnte nicht exportiert werden. Bitte laden Sie die Seite neu und versuchen Sie es erneut."; } } XtraReport CreateReportInstance() { return ReportStorage.TryGetReport("LargeDatasetReport", out var savedReport) ? savedReport : PredefinedReports.ReportsFactory.GetReport("LargeDatasetReport"); } XtraReport CreateSignedReportInstance(string signatureDataUrl, string signerFullName, string signerPosition, string signaturePlace) { var report = CreateReportInstance(); AddSignature(report, signatureDataUrl, signerFullName, signerPosition, signaturePlace); return report; } static void AddSignature(XtraReport report, string signatureDataUrl, string signerFullName, string signerPosition, string signaturePlace) { var imageBytes = Convert.FromBase64String(signatureDataUrl[(signatureDataUrl.IndexOf(',') + 1)..]); using var imageStream = new MemoryStream(imageBytes); var imageSource = new ImageSource(DXImage.FromStream(imageStream)); var bottomMargin = report.Bands.OfType().FirstOrDefault(); if(bottomMargin is null) { bottomMargin = new BottomMarginBand(); report.Bands.Add(bottomMargin); } RemoveExistingSignature(bottomMargin); // Layout constants const float sigX = 390F; const float sigWidth = 230F; const float sigImgHeight = 70F; const float infoHeight = 65F; // up to 4 lines at 8pt const float innerGap = 5F; const float bottomPad = 6F; const float defaultTopPad = 8F; const float maxBandHeight = 210F; float requiredHeight = defaultTopPad + sigImgHeight + innerGap + infoHeight + bottomPad; // Grow band if needed, but cap at maxBandHeight to avoid overlapping page content bottomMargin.HeightF = Math.Min(maxBandHeight, Math.Max(bottomMargin.HeightF, requiredHeight)); // If band is tighter than required, compress top padding so content still fits float topPad = Math.Max(0F, bottomMargin.HeightF - bottomPad - infoHeight - innerGap - sigImgHeight); float imageY = topPad; float labelY = imageY + sigImgHeight + innerGap; var signatureInformation = string.IsNullOrWhiteSpace(signerPosition) ? $"Empfaengerunterschrift\n{signerFullName}\n{signaturePlace}, {DateTime.Now:d}" : $"Empfaengerunterschrift\n{signerFullName}\n{signerPosition}\n{signaturePlace}, {DateTime.Now:d}"; var signature = new XRPictureBox { Name = "receiverSignatureImage", ImageSource = imageSource, BoundsF = new RectangleF(sigX, imageY, sigWidth, sigImgHeight), Sizing = ImageSizeMode.ZoomImage, Borders = BorderSide.Bottom, BorderColor = System.Drawing.Color.FromArgb(73, 80, 87) }; var signatureLabel = new XRLabel { Name = "receiverSignatureLabel", Text = signatureInformation, Multiline = true, BoundsF = new RectangleF(sigX, labelY, sigWidth, infoHeight), Font = new DXFont("Open Sans", 8F, DXFontStyle.Regular), ForeColor = System.Drawing.Color.FromArgb(73, 80, 87), TextAlignment = TextAlignment.TopLeft }; bottomMargin.Controls.AddRange(new XRControl[] { signature, signatureLabel }); } static void RemoveExistingSignature(BottomMarginBand bottomMargin) { var controls = bottomMargin.Controls .Cast() .Where(control => control.Name is "receiverSignatureLabel" or "receiverSignatureImage") .ToArray(); foreach(var control in controls) bottomMargin.Controls.Remove(control); } }