feat(sender-editor): use rendered page metadata for field placement
This commit is contained in:
@@ -1,17 +1,18 @@
|
|||||||
@using EnvelopeGenerator.Application.Resources
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using EnvelopeGenerator.Application.Common.Dto
|
||||||
@using Microsoft.Extensions.Localization
|
@using Microsoft.Extensions.Localization
|
||||||
@inject IStringLocalizer<Resource> UiLocalizer
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
<div class="pdf-editor-wrapper sender-image-editor sender-editor-pdf-viewer"
|
<div class="pdf-editor-wrapper sender-image-editor sender-editor-pdf-viewer"
|
||||||
style="cursor: @(IsPlacementMode ? "crosshair" : "default");"
|
style="cursor: @(IsPlacementMode ? "crosshair" : "default");"
|
||||||
@onclick="HandleClickAsync">
|
@onclick="HandleClickAsync">
|
||||||
@if (PageCount <= 0)
|
@if (PageCount <= 0)
|
||||||
{
|
{
|
||||||
<div class="sender-image-editor__empty">@L("SenderEditor_NoPagesToDisplay", "Keine Seiten zur Anzeige verfügbar.")</div>
|
<div class="sender-image-editor__empty">@L("SenderEditor_NoPagesToDisplay", "Keine Seiten zur Anzeige verfügbar.")</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (PageImageDataUrls.Count == 0 && !string.IsNullOrWhiteSpace(PdfDataUrl))
|
@if (Pages.Count == 0 && !string.IsNullOrWhiteSpace(PdfDataUrl))
|
||||||
{
|
{
|
||||||
<div class="sender-image-editor__native-preview">
|
<div class="sender-image-editor__native-preview">
|
||||||
<iframe class="sender-image-editor__native-frame"
|
<iframe class="sender-image-editor__native-frame"
|
||||||
@@ -21,17 +22,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="sender-image-editor__pages">
|
<div class="sender-image-editor__pages">
|
||||||
@for (var pageNumber = 1; pageNumber <= PageCount; pageNumber++)
|
@for (var pageIndex = 0; pageIndex < PageCount; pageIndex++)
|
||||||
{
|
{
|
||||||
|
var pagePreview = pageIndex < Pages.Count ? Pages[pageIndex] : null;
|
||||||
|
var pageNumber = pagePreview?.PageNumber ?? (pageIndex + 1);
|
||||||
|
var widthPt = pagePreview?.WidthPt ?? 595.0;
|
||||||
|
var heightPt = pagePreview?.HeightPt ?? 842.0;
|
||||||
|
|
||||||
<div class="sender-image-editor__page-block"
|
<div class="sender-image-editor__page-block"
|
||||||
data-editor-page="@pageNumber"
|
data-editor-page="@pageNumber"
|
||||||
data-page-width-pt="595"
|
data-page-width-pt="@widthPt"
|
||||||
data-page-height-pt="842">
|
data-page-height-pt="@heightPt">
|
||||||
<div class="sender-image-editor__page-canvas">
|
<div class="sender-image-editor__page-canvas">
|
||||||
@if (PageImageDataUrls.TryGetValue(pageNumber, out var imageDataUrl) && !string.IsNullOrWhiteSpace(imageDataUrl))
|
@if (!string.IsNullOrWhiteSpace(pagePreview?.ImageDataUrl))
|
||||||
{
|
{
|
||||||
<img class="sender-image-editor__page-image"
|
<img class="sender-image-editor__page-image"
|
||||||
src="@imageDataUrl"
|
src="@(pagePreview!.ImageDataUrl)"
|
||||||
alt="PDF Page @pageNumber"
|
alt="PDF Page @pageNumber"
|
||||||
draggable="false" />
|
draggable="false" />
|
||||||
}
|
}
|
||||||
@@ -44,7 +50,7 @@
|
|||||||
{
|
{
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="sender-image-editor__field"
|
class="sender-image-editor__field"
|
||||||
style="@GetFieldStyle(field)"
|
style="@GetFieldStyle(field, widthPt, heightPt)"
|
||||||
title="@field.ReceiverName"
|
title="@field.ReceiverName"
|
||||||
@onclick:stopPropagation="true"
|
@onclick:stopPropagation="true"
|
||||||
@onclick="() => RemoveField.InvokeAsync(field.Index)">
|
@onclick="() => RemoveField.InvokeAsync(field.Index)">
|
||||||
@@ -66,7 +72,7 @@
|
|||||||
public int PageCount { get; set; }
|
public int PageCount { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public IReadOnlyDictionary<int, string> PageImageDataUrls { get; set; } = new Dictionary<int, string>();
|
public IReadOnlyList<PdfPageImageDto> Pages { get; set; } = Array.Empty<PdfPageImageDto>();
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string? PdfDataUrl { get; set; }
|
public string? PdfDataUrl { get; set; }
|
||||||
@@ -80,12 +86,12 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> RemoveField { get; set; }
|
public EventCallback<int> RemoveField { get; set; }
|
||||||
|
|
||||||
private static string GetFieldStyle(SenderPdfFieldOverlay field)
|
private static string GetFieldStyle(SenderPdfFieldOverlay field, double pageWidthPt, double pageHeightPt)
|
||||||
{
|
{
|
||||||
var leftPercent = (field.XPt / 595.0) * 100.0;
|
var leftPercent = (field.XPt / pageWidthPt) * 100.0;
|
||||||
var topPercent = (field.YPt / 842.0) * 100.0;
|
var topPercent = (field.YPt / pageHeightPt) * 100.0;
|
||||||
var widthPercent = (127.44 / 595.0) * 100.0;
|
var widthPercent = (127.44 / pageWidthPt) * 100.0;
|
||||||
var heightPercent = (141.12 / 842.0) * 100.0;
|
var heightPercent = (141.12 / pageHeightPt) * 100.0;
|
||||||
|
|
||||||
return string.Join(' ',
|
return string.Join(' ',
|
||||||
$"left:{leftPercent:F4}%;",
|
$"left:{leftPercent:F4}%;",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
@inject AppVersionService AppVersion
|
@inject AppVersionService AppVersion
|
||||||
@inject ILogger<SenderEditorPage> Logger
|
@inject ILogger<SenderEditorPage> Logger
|
||||||
@inject DocumentService DocumentService
|
@inject DocumentService DocumentService
|
||||||
|
@inject PdfRenderService PdfRenderService
|
||||||
@inject ReceiverService ReceiverService
|
@inject ReceiverService ReceiverService
|
||||||
@inject EnvelopeService EnvelopeService
|
@inject EnvelopeService EnvelopeService
|
||||||
@inject IStringLocalizer<Resource> UiLocalizer
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
@@ -278,7 +279,7 @@
|
|||||||
{
|
{
|
||||||
<SenderPdfImageEditor IsPlacementMode="@(_pendingReceiverForPlacement is not null)"
|
<SenderPdfImageEditor IsPlacementMode="@(_pendingReceiverForPlacement is not null)"
|
||||||
PageCount="@_pageCount"
|
PageCount="@_pageCount"
|
||||||
PageImageDataUrls="@_pageImageDataUrls"
|
Pages="@_pagePreviews"
|
||||||
PdfDataUrl="@PdfDataUrl"
|
PdfDataUrl="@PdfDataUrl"
|
||||||
Fields="@FieldOverlays"
|
Fields="@FieldOverlays"
|
||||||
EditorClick="OnPdfAreaClickAsync"
|
EditorClick="OnPdfAreaClickAsync"
|
||||||
@@ -471,7 +472,7 @@
|
|||||||
string? _errorMessage;
|
string? _errorMessage;
|
||||||
byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw
|
byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw
|
||||||
int _pageCount;
|
int _pageCount;
|
||||||
Dictionary<int, string> _pageImageDataUrls = [];
|
List<PdfPageImageDto> _pagePreviews = [];
|
||||||
int? _loadedEnvelopeId;
|
int? _loadedEnvelopeId;
|
||||||
int? _editingEnvelopeId;
|
int? _editingEnvelopeId;
|
||||||
bool _isLoadingEnvelopeForEdit;
|
bool _isLoadingEnvelopeForEdit;
|
||||||
@@ -594,9 +595,11 @@
|
|||||||
|
|
||||||
_originalPdfBytes = document?.ByteData;
|
_originalPdfBytes = document?.ByteData;
|
||||||
_pdfLoaded = _originalPdfBytes is { Length: > 0 };
|
_pdfLoaded = _originalPdfBytes is { Length: > 0 };
|
||||||
_pageCount = EstimatePdfPageCount(_originalPdfBytes) ??
|
_pagePreviews = [];
|
||||||
Math.Max(1, _signatureFields.Select(f => f.Page).DefaultIfEmpty(1).Max());
|
_pageCount = _pagePreviews.Count > 0
|
||||||
_pageImageDataUrls.Clear();
|
? _pagePreviews.Count
|
||||||
|
: EstimatePdfPageCount(_originalPdfBytes)
|
||||||
|
?? Math.Max(1, _signatureFields.Select(f => f.Page).DefaultIfEmpty(1).Max());
|
||||||
_fileName = $"Envelope-{envelopeId}.pdf";
|
_fileName = $"Envelope-{envelopeId}.pdf";
|
||||||
_pendingReceiverForPlacement = null;
|
_pendingReceiverForPlacement = null;
|
||||||
_loadedEnvelopeId = envelopeId;
|
_loadedEnvelopeId = envelopeId;
|
||||||
@@ -638,8 +641,12 @@
|
|||||||
_originalPdfBytes = ms.ToArray();
|
_originalPdfBytes = ms.ToArray();
|
||||||
_fileName = file.Name;
|
_fileName = file.Name;
|
||||||
_pdfLoaded = true;
|
_pdfLoaded = true;
|
||||||
_pageCount = EstimatePdfPageCount(_originalPdfBytes) ?? 1;
|
_pagePreviews = (await PdfRenderService.RenderPagesAsync(_originalPdfBytes))
|
||||||
_pageImageDataUrls.Clear();
|
.OrderBy(p => p.PageNumber)
|
||||||
|
.ToList();
|
||||||
|
_pageCount = _pagePreviews.Count > 0
|
||||||
|
? _pagePreviews.Count
|
||||||
|
: EstimatePdfPageCount(_originalPdfBytes) ?? 1;
|
||||||
_signatureFields.Clear();
|
_signatureFields.Clear();
|
||||||
_pendingReceiverForPlacement = null;
|
_pendingReceiverForPlacement = null;
|
||||||
|
|
||||||
@@ -682,10 +689,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approximate points based on A4. This is temporary until the image-based viewer
|
int page1Based = coords.PageIndex + 1;
|
||||||
// pipeline provides precise per-page dimensions.
|
var pagePreview = _pagePreviews.FirstOrDefault(p => p.PageNumber == page1Based);
|
||||||
const double pageWidthPt = 595.0;
|
var pageWidthPt = pagePreview?.WidthPt ?? 595.0;
|
||||||
const double pageHeightPt = 842.0;
|
var pageHeightPt = pagePreview?.HeightPt ?? 842.0;
|
||||||
|
|
||||||
// Convert normalised [0,1] → PDF points; clamp so box stays inside page
|
// Convert normalised [0,1] → PDF points; clamp so box stays inside page
|
||||||
double xPt = coords.NormX * pageWidthPt;
|
double xPt = coords.NormX * pageWidthPt;
|
||||||
@@ -694,8 +701,6 @@
|
|||||||
xPt = Math.Max(0, Math.Min(xPt, pageWidthPt - SigWidthPt));
|
xPt = Math.Max(0, Math.Min(xPt, pageWidthPt - SigWidthPt));
|
||||||
yPt = Math.Max(0, Math.Min(yPt, pageHeightPt - SigHeightPt));
|
yPt = Math.Max(0, Math.Min(yPt, pageHeightPt - SigHeightPt));
|
||||||
|
|
||||||
int page1Based = coords.PageIndex + 1;
|
|
||||||
|
|
||||||
var field = new SignatureFieldDraft(
|
var field = new SignatureFieldDraft(
|
||||||
XPt: xPt,
|
XPt: xPt,
|
||||||
YPt: yPt,
|
YPt: yPt,
|
||||||
|
|||||||
Reference in New Issue
Block a user