Refactor PDF editor with custom rendering
Replaced `DxPdfViewer` with a custom PDF rendering solution in `SenderPdfImageEditor` to improve flexibility and control. Added support for displaying placeholder messages and interactive signature fields. Introduced the `SenderPdfFieldOverlay` record to encapsulate field data. Updated `SenderEditorPage` logic to manage page counts, image URLs, and field overlays. Added a method to estimate PDF page counts. Refactored field removal logic to use index-based operations. Enhanced styling in `envelope-viewer.css` for improved layout and responsiveness. Updated localization files with new strings for page labels and placeholders. Adjusted JavaScript selectors in `envelope-editor.js` to support the new rendering approach.
This commit is contained in:
@@ -276,10 +276,13 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<SenderPdfImageEditor PdfBytes="@_pdfBytes"
|
||||
IsPlacementMode="@(_pendingReceiverForPlacement is not null)"
|
||||
ViewerCssClass="@ViewerCssClass"
|
||||
EditorClick="OnPdfAreaClickAsync" />
|
||||
<SenderPdfImageEditor IsPlacementMode="@(_pendingReceiverForPlacement is not null)"
|
||||
PageCount="@_pageCount"
|
||||
PageImageDataUrls="@_pageImageDataUrls"
|
||||
PdfDataUrl="@PdfDataUrl"
|
||||
Fields="@FieldOverlays"
|
||||
EditorClick="OnPdfAreaClickAsync"
|
||||
RemoveField="OnRemoveFieldByIndexAsync" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -459,15 +462,16 @@
|
||||
const double SigWidthPt = 1.77 * 72; // 127.44 pt
|
||||
const double SigHeightPt = 1.96 * 72; // 141.12 pt
|
||||
|
||||
// CssClass for DxPdfViewer — used by JS to locate page elements
|
||||
// CssClass for JS click coordinate helper
|
||||
const string ViewerCssClass = "sender-editor-pdf-viewer";
|
||||
|
||||
// ── State ──
|
||||
bool _pdfLoaded = false;
|
||||
string _fileName = string.Empty;
|
||||
string? _errorMessage;
|
||||
byte[]? _pdfBytes; // Current rendered PDF (original + placeholders burned in)
|
||||
byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw
|
||||
int _pageCount;
|
||||
Dictionary<int, string> _pageImageDataUrls = [];
|
||||
int? _loadedEnvelopeId;
|
||||
int? _editingEnvelopeId;
|
||||
bool _isLoadingEnvelopeForEdit;
|
||||
@@ -475,6 +479,14 @@
|
||||
List<SignatureFieldDraft> _signatureFields = [];
|
||||
ReceiverDraft? _pendingReceiverForPlacement; // Set when user clicks "Signatur hinzufügen"
|
||||
|
||||
IReadOnlyList<SenderPdfFieldOverlay> FieldOverlays => _signatureFields
|
||||
.Select((f, i) => new SenderPdfFieldOverlay(i, f.Page, f.XPt, f.YPt, f.ReceiverName, f.Color))
|
||||
.ToList();
|
||||
|
||||
string? PdfDataUrl => _originalPdfBytes is { Length: > 0 }
|
||||
? $"data:application/pdf;base64,{Convert.ToBase64String(_originalPdfBytes)}"
|
||||
: null;
|
||||
|
||||
// ── Save state ──
|
||||
bool _isSaving = false;
|
||||
bool _savePopupVisible = false;
|
||||
@@ -582,7 +594,9 @@
|
||||
|
||||
_originalPdfBytes = document?.ByteData;
|
||||
_pdfLoaded = _originalPdfBytes is { Length: > 0 };
|
||||
_pdfBytes = _pdfLoaded ? _originalPdfBytes : null;
|
||||
_pageCount = EstimatePdfPageCount(_originalPdfBytes) ??
|
||||
Math.Max(1, _signatureFields.Select(f => f.Page).DefaultIfEmpty(1).Max());
|
||||
_pageImageDataUrls.Clear();
|
||||
_fileName = $"Envelope-{envelopeId}.pdf";
|
||||
_pendingReceiverForPlacement = null;
|
||||
_loadedEnvelopeId = envelopeId;
|
||||
@@ -624,12 +638,11 @@
|
||||
_originalPdfBytes = ms.ToArray();
|
||||
_fileName = file.Name;
|
||||
_pdfLoaded = true;
|
||||
_pageCount = EstimatePdfPageCount(_originalPdfBytes) ?? 1;
|
||||
_pageImageDataUrls.Clear();
|
||||
_signatureFields.Clear();
|
||||
_pendingReceiverForPlacement = null;
|
||||
|
||||
// Rendered PDF starts as the clean original (no placeholders yet)
|
||||
_pdfBytes = _originalPdfBytes;
|
||||
|
||||
Logger.LogInformation("PDF loaded: {Name} ({Size} bytes)", _fileName, _originalPdfBytes.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -693,19 +706,18 @@
|
||||
_signatureFields.Add(field);
|
||||
_pendingReceiverForPlacement = null;
|
||||
|
||||
// Keep original PDF bytes unchanged. Signature boxes are tracked in-memory.
|
||||
_pdfBytes = _originalPdfBytes;
|
||||
|
||||
Logger.LogInformation(
|
||||
"[SenderEditor] Field added: Page={Page} X={X:F1}pt Y={Y:F1}pt Receiver={Receiver}",
|
||||
page1Based, xPt, yPt, field.ReceiverName);
|
||||
}
|
||||
|
||||
// ── Remove a single field ──
|
||||
async Task RemoveFieldAsync(SignatureFieldDraft field)
|
||||
async Task OnRemoveFieldByIndexAsync(int index)
|
||||
{
|
||||
_signatureFields.Remove(field);
|
||||
_pdfBytes = _originalPdfBytes;
|
||||
if (index < 0 || index >= _signatureFields.Count)
|
||||
return;
|
||||
|
||||
_signatureFields.RemoveAt(index);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -714,10 +726,39 @@
|
||||
{
|
||||
_signatureFields.Clear();
|
||||
_pendingReceiverForPlacement = null;
|
||||
_pdfBytes = _originalPdfBytes;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
static int? EstimatePdfPageCount(byte[]? documentBytes)
|
||||
{
|
||||
if (documentBytes is null || documentBytes.Length == 0)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var text = System.Text.Encoding.ASCII.GetString(documentBytes);
|
||||
const string marker = "/Type /Page";
|
||||
var count = 0;
|
||||
var index = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
index = text.IndexOf(marker, index, StringComparison.Ordinal);
|
||||
if (index < 0)
|
||||
break;
|
||||
|
||||
count++;
|
||||
index += marker.Length;
|
||||
}
|
||||
|
||||
return count > 0 ? count : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void Cancel() => NavigationManager.NavigateTo("/sender");
|
||||
|
||||
void GoToDashboard()
|
||||
|
||||
Reference in New Issue
Block a user