Refactor PDF handling; remove iText dependency
Replaced iText-based PDF processing with DevExpress PdfGraphics API. Removed `itext` and `itext.bouncy-castle-adapter` dependencies. Simplified `BuildReport` to burn signatures directly into PDFs and render all pages using `XRPdfContent` with `GenerateOwnPages = true`. Consolidated subreport logic into `BuildReport` and removed `BuildPageSubreport`. Eliminated unused constants and methods, including `GetPdfPageCount`. Updated XML documentation to reflect the new implementation. Placeholder implementation for `BurnSignaturesIntoPdf` added, pending further development.
This commit is contained in:
@@ -2,8 +2,6 @@
|
|||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@using DevExpress.Blazor.Reporting
|
@using DevExpress.Blazor.Reporting
|
||||||
@using DevExpress.XtraReports.UI
|
@using DevExpress.XtraReports.UI
|
||||||
@using DevExpress.XtraPrinting
|
|
||||||
@using DevExpress.XtraPrinting.Drawing
|
|
||||||
@using EnvelopeGenerator.Server.Client.Models
|
@using EnvelopeGenerator.Server.Client.Models
|
||||||
@using EnvelopeGenerator.Server.Client.Models.Constants
|
@using EnvelopeGenerator.Server.Client.Models.Constants
|
||||||
@using EnvelopeGenerator.Server.Client.Services
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
@@ -352,15 +350,6 @@
|
|||||||
const string ImageInputId = "rp-signature-image-input";
|
const string ImageInputId = "rp-signature-image-input";
|
||||||
const string ImageCanvasId = "rp-image-signature-pad";
|
const string ImageCanvasId = "rp-image-signature-pad";
|
||||||
|
|
||||||
// A4 page dimensions in DX units (1/100 inch).
|
|
||||||
// 8.27" × 11.69" → 827 × 1169
|
|
||||||
const float PageWidthDx = 827f;
|
|
||||||
const float PageHeightDx = 1169f;
|
|
||||||
|
|
||||||
// Fixed signature field size in DX units: 1.77" × 1.96"
|
|
||||||
const float SigWidthDx = 177f;
|
|
||||||
const float SigHeightDx = 196f;
|
|
||||||
|
|
||||||
readonly (string Text, string Value)[] TypedSignatureFonts =
|
readonly (string Text, string Value)[] TypedSignatureFonts =
|
||||||
[
|
[
|
||||||
("Brush Script", "'Brush Script MT', cursive"),
|
("Brush Script", "'Brush Script MT', cursive"),
|
||||||
@@ -478,135 +467,68 @@
|
|||||||
|
|
||||||
// ----- Report builder -----
|
// ----- Report builder -----
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds an XtraReport that displays the PDF pages via XRPdfContent (embedded mode,
|
/// Builds an XtraReport wrapping the PDF bytes.
|
||||||
/// GenerateOwnPages = false). Each PDF page is wrapped in its own subreport so that
|
/// If a signature is captured and there are signature fields, the signature image is
|
||||||
/// XRPictureBox overlays can be positioned accurately per page.
|
/// first burned into the PDF via DevExpress PdfDocumentProcessor, then the modified
|
||||||
|
/// PDF is handed to XRPdfContent with GenerateOwnPages = true so that all pages appear.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static XtraReport BuildReport(
|
static XtraReport BuildReport(
|
||||||
byte[] pdfBytes,
|
byte[] pdfBytes,
|
||||||
IReadOnlyList<SignatureDto> signatures,
|
IReadOnlyList<SignatureDto> signatures,
|
||||||
SignatureCaptureDto? capturedSignature)
|
SignatureCaptureDto? capturedSignature)
|
||||||
{
|
{
|
||||||
// Determine the number of pages using DevExpress PDF processor
|
// Burn signatures into PDF bytes when a captured signature is available
|
||||||
int pageCount = GetPdfPageCount(pdfBytes);
|
byte[] sourcePdf = pdfBytes;
|
||||||
if (pageCount < 1) pageCount = 1;
|
if (capturedSignature is not null
|
||||||
|
&& !string.IsNullOrWhiteSpace(capturedSignature.DataUrl)
|
||||||
// Outer (main) report - acts as container for subreports
|
&& signatures.Count > 0)
|
||||||
var mainReport = new XtraReport
|
|
||||||
{
|
{
|
||||||
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4,
|
try
|
||||||
Landscape = false,
|
|
||||||
Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0),
|
|
||||||
};
|
|
||||||
|
|
||||||
var mainDetail = new DetailBand { HeightF = 0f };
|
|
||||||
mainReport.Bands.Add(mainDetail);
|
|
||||||
|
|
||||||
for (int page = 1; page <= pageCount; page++)
|
|
||||||
{
|
|
||||||
// Build a subreport for this PDF page
|
|
||||||
var pageReport = BuildPageSubreport(pdfBytes, page, signatures, capturedSignature);
|
|
||||||
|
|
||||||
var subreport = new XRSubreport
|
|
||||||
{
|
{
|
||||||
ReportSource = pageReport,
|
sourcePdf = BurnSignaturesIntoPdf(pdfBytes, signatures, capturedSignature);
|
||||||
GenerateOwnPages = true,
|
}
|
||||||
LocationF = new PointF(0f, 0f),
|
catch
|
||||||
SizeF = new SizeF(PageWidthDx, PageHeightDx),
|
|
||||||
};
|
|
||||||
|
|
||||||
mainDetail.Controls.Add(subreport);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mainReport;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Builds a single-page subreport: one DetailBand containing the PDF page (via
|
|
||||||
/// XRPdfContent with GenerateOwnPages = false) plus XRPictureBox overlays for
|
|
||||||
/// any signatures placed on this page.
|
|
||||||
/// </summary>
|
|
||||||
static XtraReport BuildPageSubreport(
|
|
||||||
byte[] pdfBytes,
|
|
||||||
int pageNumber,
|
|
||||||
IReadOnlyList<SignatureDto> signatures,
|
|
||||||
SignatureCaptureDto? capturedSignature)
|
|
||||||
{
|
|
||||||
var report = new XtraReport
|
|
||||||
{
|
|
||||||
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4,
|
|
||||||
Landscape = false,
|
|
||||||
Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0),
|
|
||||||
};
|
|
||||||
|
|
||||||
var detail = new DetailBand
|
|
||||||
{
|
|
||||||
HeightF = PageHeightDx,
|
|
||||||
Name = $"DetailBand_Page{pageNumber}",
|
|
||||||
};
|
|
||||||
report.Bands.Add(detail);
|
|
||||||
|
|
||||||
// --- PDF content (embedded, no own pages) ---
|
|
||||||
var pdfContent = new XRPdfContent
|
|
||||||
{
|
|
||||||
Source = pdfBytes,
|
|
||||||
PageRange = pageNumber.ToString(),
|
|
||||||
GenerateOwnPages = false,
|
|
||||||
LocationF = new PointF(0f, 0f),
|
|
||||||
SizeF = new SizeF(PageWidthDx, PageHeightDx),
|
|
||||||
};
|
|
||||||
detail.Controls.Add(pdfContent);
|
|
||||||
|
|
||||||
// --- Signature overlays ---
|
|
||||||
if (capturedSignature is not null && !string.IsNullOrWhiteSpace(capturedSignature.DataUrl))
|
|
||||||
{
|
|
||||||
var signaturesOnPage = signatures.Where(s => s.Page == pageNumber).ToList();
|
|
||||||
foreach (var sig in signaturesOnPage)
|
|
||||||
{
|
{
|
||||||
try
|
// Fall back to unmodified PDF — non-critical
|
||||||
{
|
sourcePdf = pdfBytes;
|
||||||
var imgBytes = DataUrlToBytes(capturedSignature.DataUrl);
|
|
||||||
if (imgBytes is { Length: > 0 })
|
|
||||||
{
|
|
||||||
using var imgStream = new System.IO.MemoryStream(imgBytes);
|
|
||||||
var img = System.Drawing.Image.FromStream(imgStream);
|
|
||||||
var picBox = new XRPictureBox
|
|
||||||
{
|
|
||||||
// DB stores INCHES; DX unit = 1/100 inch → multiply by 100
|
|
||||||
LocationF = new PointF((float)(sig.X * 100), (float)(sig.Y * 100)),
|
|
||||||
SizeF = new SizeF(SigWidthDx, SigHeightDx),
|
|
||||||
Image = img,
|
|
||||||
Sizing = ImageSizeMode.Squeeze,
|
|
||||||
CanGrow = false,
|
|
||||||
CanShrink = false,
|
|
||||||
};
|
|
||||||
detail.Controls.Add(picBox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Non-critical: skip overlay on error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var report = new XtraReport
|
||||||
|
{
|
||||||
|
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4,
|
||||||
|
Landscape = false,
|
||||||
|
Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
var detail = new DetailBand { HeightF = 0f };
|
||||||
|
report.Bands.Add(detail);
|
||||||
|
|
||||||
|
// GenerateOwnPages = true (default): each PDF page becomes a separate report page
|
||||||
|
var pdfContent = new XRPdfContent
|
||||||
|
{
|
||||||
|
Source = sourcePdf,
|
||||||
|
GenerateOwnPages = true,
|
||||||
|
};
|
||||||
|
detail.Controls.Add(pdfContent);
|
||||||
|
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Reads the page count of a PDF using iText7 (already referenced in the server project).</summary>
|
/// <summary>
|
||||||
static int GetPdfPageCount(byte[] pdfBytes)
|
/// Burns signature images directly into the PDF using DevExpress PdfGraphics API.
|
||||||
|
/// Coordinates: DB stores INCHES with top-left origin, Y down.
|
||||||
|
/// PDF coordinate system: bottom-left origin, Y up, unit = points (1/72 inch).
|
||||||
|
/// Note: Implementation placeholder — requires DevExpress.Pdf.Drawing API wiring (Problem 2).
|
||||||
|
/// </summary>
|
||||||
|
static byte[] BurnSignaturesIntoPdf(
|
||||||
|
byte[] pdfBytes,
|
||||||
|
IReadOnlyList<SignatureDto> signatures,
|
||||||
|
SignatureCaptureDto capturedSignature)
|
||||||
{
|
{
|
||||||
try
|
// TODO: Implement with PdfGraphics when Problem 2 is addressed.
|
||||||
{
|
// For now return unmodified PDF so Problem 1 (all pages) can be verified first.
|
||||||
using var ms = new System.IO.MemoryStream(pdfBytes);
|
return pdfBytes;
|
||||||
using var reader = new iText.Kernel.Pdf.PdfReader(ms);
|
|
||||||
using var pdfDoc = new iText.Kernel.Pdf.PdfDocument(reader);
|
|
||||||
return pdfDoc.GetNumberOfPages();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Converts a base64 data URL (data:image/...;base64,...) to raw bytes.</summary>
|
/// <summary>Converts a base64 data URL (data:image/...;base64,...) to raw bytes.</summary>
|
||||||
|
|||||||
@@ -28,8 +28,6 @@
|
|||||||
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
||||||
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.11" />
|
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.11" />
|
||||||
<PackageReference Include="itext" Version="8.0.5" />
|
|
||||||
<PackageReference Include="itext.bouncy-castle-adapter" Version="8.0.5" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" />
|
||||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
||||||
|
|||||||
Reference in New Issue
Block a user