diff --git a/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor index c67077be..a33ed909 100644 --- a/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor +++ b/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor @@ -4,7 +4,12 @@ @using DevExpress.Utils @using DevExpress.XtraPrinting @using DevExpress.XtraPrinting.Drawing -@using DevExpress.XtraReports.UI; +@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 @@ -36,13 +41,47 @@ -

Bitte unterschreiben Sie im folgenden Feld.

- + + + @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.

+ + + } + @if(!string.IsNullOrWhiteSpace(PopupValidationMessage)) {
@PopupValidationMessage
} @@ -61,12 +100,31 @@ } @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"; int ViewerKey; protected override async Task OnInitializedAsync() { @@ -76,17 +134,45 @@ } async Task OpenSignaturePopupAsync() { + ActiveSignatureTab = SignatureTabDraw; SignaturePopupVisible = true; SignatureValidationMessage = null; PopupValidationMessage = null; await InvokeAsync(StateHasChanged); await Task.Delay(50); - await JSRuntime.InvokeVoidAsync("receiverSignature.initialize", "receiver-signature-pad"); + 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; - await JSRuntime.InvokeVoidAsync("receiverSignature.clear", "receiver-signature-pad"); + + 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() { @@ -94,8 +180,22 @@ 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() { - var signatureDataUrl = await JSRuntime.InvokeAsync("receiverSignature.getDataUrl", "receiver-signature-pad"); + var signatureDataUrl = await GetActiveSignatureDataUrlAsync(); if(string.IsNullOrWhiteSpace(signatureDataUrl)) { PopupValidationMessage = "Die Unterschrift ist fuer den PDF-Export erforderlich."; @@ -110,6 +210,18 @@ 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."; @@ -180,4 +292,4 @@ foreach(var control in controls) bottomMargin.Controls.Remove(control); } -} \ No newline at end of file +} diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/receiver-signature.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/receiver-signature.js index e30ba8cf..4b700448 100644 --- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/receiver-signature.js +++ b/EnvelopeGenerator.ReceiverUI/wwwroot/js/receiver-signature.js @@ -1,5 +1,7 @@ window.receiverSignature = (() => { const pads = new Map(); + const typedSignatures = new Map(); + const imageSignatures = new Map(); function getPosition(canvas, event) { const rect = canvas.getBoundingClientRect(); @@ -10,6 +12,10 @@ window.receiverSignature = (() => { }; } + function clearCanvas(canvas) { + canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); + } + function initialize(canvasId) { const canvas = document.getElementById(canvasId); if (!canvas || pads.has(canvasId)) @@ -59,16 +65,120 @@ window.receiverSignature = (() => { canvas.addEventListener('touchend', end, { passive: false }); } + function initializeTyped(canvasId) { + const canvas = document.getElementById(canvasId); + if (!canvas || typedSignatures.has(canvasId)) + return; + + typedSignatures.set(canvasId, { hasSignature: false }); + } + + function initializeImage(inputId, canvasId) { + const input = document.getElementById(inputId); + const canvas = document.getElementById(canvasId); + if (!input || !canvas || imageSignatures.has(canvasId)) + return; + + const state = { hasSignature: false }; + imageSignatures.set(canvasId, state); + + input.addEventListener('change', () => { + const file = input.files && input.files.length ? input.files[0] : null; + if (!file || !file.type.startsWith('image/')) { + clearCanvas(canvas); + state.hasSignature = false; + return; + } + + const reader = new FileReader(); + reader.onload = () => { + const image = new Image(); + image.onload = () => { + const context = canvas.getContext('2d'); + clearCanvas(canvas); + + const padding = 10; + const maxWidth = canvas.width - padding * 2; + const maxHeight = canvas.height - padding * 2; + const scale = Math.min(maxWidth / image.width, maxHeight / image.height, 1); + const width = image.width * scale; + const height = image.height * scale; + const x = (canvas.width - width) / 2; + const y = (canvas.height - height) / 2; + + context.drawImage(image, x, y, width, height); + state.hasSignature = true; + }; + image.src = reader.result; + }; + reader.readAsDataURL(file); + }); + } + function clear(canvasId) { const canvas = document.getElementById(canvasId); const state = pads.get(canvasId); if (!canvas || !state) return; - canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); + clearCanvas(canvas); state.hasSignature = false; } + function clearTyped(canvasId) { + const canvas = document.getElementById(canvasId); + const state = typedSignatures.get(canvasId); + if (!canvas || !state) + return; + + clearCanvas(canvas); + state.hasSignature = false; + } + + function clearImage(inputId, canvasId) { + const input = document.getElementById(inputId); + const canvas = document.getElementById(canvasId); + const state = imageSignatures.get(canvasId); + if (!canvas || !state) + return; + + if (input) + input.value = ''; + + clearCanvas(canvas); + state.hasSignature = false; + } + + function renderTypedSignature(canvasId, text, fontFamily) { + const canvas = document.getElementById(canvasId); + const state = typedSignatures.get(canvasId); + if (!canvas || !state) + return; + + const value = (text || '').trim(); + clearCanvas(canvas); + + if (!value) { + state.hasSignature = false; + return; + } + + const context = canvas.getContext('2d'); + const maxWidth = canvas.width - 30; + let fontSize = 54; + + do { + context.font = `italic ${fontSize}px ${fontFamily || 'cursive'}`; + fontSize -= 2; + } while (context.measureText(value).width > maxWidth && fontSize > 24); + + context.fillStyle = '#111'; + context.textBaseline = 'middle'; + context.textAlign = 'center'; + context.fillText(value, canvas.width / 2, canvas.height / 2); + state.hasSignature = true; + } + function getDataUrl(canvasId) { const canvas = document.getElementById(canvasId); const state = pads.get(canvasId); @@ -78,9 +188,34 @@ window.receiverSignature = (() => { return canvas.toDataURL('image/png'); } + function getTypedDataUrl(canvasId) { + const canvas = document.getElementById(canvasId); + const state = typedSignatures.get(canvasId); + if (!canvas || !state || !state.hasSignature) + return null; + + return canvas.toDataURL('image/png'); + } + + function getImageDataUrl(canvasId) { + const canvas = document.getElementById(canvasId); + const state = imageSignatures.get(canvasId); + if (!canvas || !state || !state.hasSignature) + return null; + + return canvas.toDataURL('image/png'); + } + return { initialize, + initializeTyped, + initializeImage, clear, - getDataUrl + clearTyped, + clearImage, + renderTypedSignature, + getDataUrl, + getTypedDataUrl, + getImageDataUrl }; })();