@page "/" @inject IJSRuntime JS

Sign PDF (Blazor)

@if (!string.IsNullOrWhiteSpace(ErrorMessage)) {
@ErrorMessage
} @if (!HasPdf) {
Drop or select a PDF to start.
} @if (HasPdf) {
@if (ShowSignature) {
} @if (ShowText) {
}
Page @DisplayPage / @PageCount
} @if (ShowSignaturePadModal) { } @code { private ElementReference PdfCanvasRef; private ElementReference PdfHostRef; private ElementReference OverlayRef; private string? PdfBase64; private string? OriginalPdfBase64; private int PageIndex; private int PageCount; private double ViewportWidthPx = 800; private double ViewportHeightPx; private bool ShowSignaturePadModal; private bool ShowSignature; private bool ShowText; private string SignatureCanvasId { get; } = $"sig-{Guid.NewGuid():N}"; private string? SignatureDataUrl; private bool AutoDate = true; private double OverlayXpx = 20; private double OverlayYpx = 20; private double OverlayWidthPx = 200; private double OverlayHeightPx = 80; private bool IsDragging; private double DragStartX; private double DragStartY; private double StartLeft; private double StartTop; private string TextValue = "Text"; private string? ErrorMessage; private DateTimeOffset _lastDragRender = DateTimeOffset.MinValue; private bool HasPdf => !string.IsNullOrWhiteSpace(PdfBase64); private int DisplayPage => PageIndex + 1; private bool CanPrev => PageIndex > 0; private bool CanNext => PageIndex + 1 < PageCount; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await JS.InvokeVoidAsync("pdfInterop.ensureReady"); } if (ShowSignaturePadModal) { await JS.InvokeVoidAsync("pdfInterop.initSignaturePad", SignatureCanvasId); } } private async Task HandleFileSelected(InputFileChangeEventArgs e) { ErrorMessage = null; try { if (e.FileCount == 0) { return; } var file = e.File; await using var stream = file.OpenReadStream(maxAllowedSize: 20 * 1024 * 1024); using var ms = new MemoryStream(); await stream.CopyToAsync(ms); PdfBase64 = Convert.ToBase64String(ms.ToArray()); OriginalPdfBase64 = PdfBase64; // Show the canvas before we start rendering await InvokeAsync(StateHasChanged); await Task.Yield(); // Make sure pdf.js is ready await JS.InvokeVoidAsync("pdfInterop.ensureReady"); var result = await JS.InvokeAsync("pdfInterop.loadPdf", PdfBase64); PageCount = result.Pages; PageIndex = 0; await RenderPage(); } catch (Exception ex) { ErrorMessage = $"Fehler beim Laden der PDF: {ex.Message}"; PdfBase64 = null; PageCount = 0; PageIndex = 0; } } private async Task RenderPage() { if (!HasPdf) { return; } try { var viewport = await JS.InvokeAsync("pdfInterop.renderPage", PageIndex, "pdf-canvas", ViewportWidthPx); ViewportWidthPx = viewport.Width; ViewportHeightPx = viewport.Height; StateHasChanged(); } catch (Exception ex) { ErrorMessage = $"Fehler beim Rendern: {ex.Message}"; } } private async Task Reset() { ErrorMessage = null; CloseOverlays(); ShowSignaturePadModal = false; OverlayXpx = 20; OverlayYpx = 20; OverlayWidthPx = 200; OverlayHeightPx = 80; TextValue = "Text"; if (string.IsNullOrWhiteSpace(OriginalPdfBase64)) { return; } PdfBase64 = OriginalPdfBase64; PageIndex = 0; var result = await JS.InvokeAsync("pdfInterop.loadPdf", PdfBase64); PageCount = result.Pages; await RenderPage(); } private void CloseOverlays() { ShowSignature = false; ShowText = false; SignatureDataUrl = null; } private void ShowSignaturePad() { ShowSignaturePadModal = true; } private async Task ConfirmSignature() { SignatureDataUrl = await JS.InvokeAsync("pdfInterop.getSignatureDataUrl", SignatureCanvasId); if (string.IsNullOrWhiteSpace(SignatureDataUrl)) { return; } OverlayWidthPx = 200; OverlayHeightPx = 80; OverlayXpx = 20; OverlayYpx = 20; ShowSignature = true; ShowText = false; ShowSignaturePadModal = false; } private void CloseSignaturePad() { ShowSignaturePadModal = false; } private void ClearSignature() { JS.InvokeVoidAsync("pdfInterop.clearSignaturePad", SignatureCanvasId); } private void ShowTextOverlay(bool autoDate) { TextValue = autoDate ? DateTimeOffset.Now.ToString("M/d/yyyy HH:mm:ss zzz") : "Text"; OverlayWidthPx = 240; OverlayHeightPx = 40; OverlayXpx = 20; OverlayYpx = 20; ShowText = true; ShowSignature = false; } private void StartDrag(PointerEventArgs args) { IsDragging = true; DragStartX = args.ClientX; DragStartY = args.ClientY; StartLeft = OverlayXpx; StartTop = OverlayYpx; if (OverlayRef.Context != null) { JS.InvokeVoidAsync("pdfInterop.capturePointer", OverlayRef, args.PointerId); } } private void OnDrag(PointerEventArgs args) { if (!IsDragging) { return; } var dx = args.ClientX - DragStartX; var dy = args.ClientY - DragStartY; OverlayXpx = StartLeft + dx; OverlayYpx = StartTop + dy; var now = DateTimeOffset.UtcNow; if (now - _lastDragRender > TimeSpan.FromMilliseconds(16)) { _lastDragRender = now; InvokeAsync(StateHasChanged); } } private void EndDrag(PointerEventArgs args) { IsDragging = false; if (OverlayRef.Context != null) { JS.InvokeVoidAsync("pdfInterop.releasePointer", OverlayRef, args.PointerId); } } private async Task ApplySignature() { if (SignatureDataUrl is null || !HasPdf) { return; } PdfBase64 = await JS.InvokeAsync("pdfInterop.applySignature", new { base64 = PdfBase64, pageIndex = PageIndex, left = OverlayXpx, top = OverlayYpx, width = OverlayWidthPx, height = OverlayHeightPx, renderWidth = ViewportWidthPx, renderHeight = ViewportHeightPx, dataUrl = SignatureDataUrl, autoDate = AutoDate, }); CloseOverlays(); await RenderPage(); } private async Task ApplyText() { if (string.IsNullOrWhiteSpace(TextValue) || !HasPdf) { return; } PdfBase64 = await JS.InvokeAsync("pdfInterop.applyText", new { base64 = PdfBase64, pageIndex = PageIndex, left = OverlayXpx, top = OverlayYpx, width = OverlayWidthPx, height = OverlayHeightPx, renderWidth = ViewportWidthPx, renderHeight = ViewportHeightPx, text = TextValue, fontSize = 20, }); CloseOverlays(); await RenderPage(); } private void CancelOverlay() { CloseOverlays(); } private async Task PrevPage() { if (!CanPrev) { return; } PageIndex--; await RenderPage(); } private async Task NextPage() { if (!CanNext) { return; } PageIndex++; await RenderPage(); } private async Task Download() { if (!HasPdf) { return; } await JS.InvokeVoidAsync("pdfInterop.downloadPdf", PdfBase64, "document-signed.pdf"); } private record RenderResult(int Pages); private record ViewportInfo(double Width, double Height, double PageWidth, double PageHeight); }