Add PDF attachment and PDF/A conversion features

Introduced endpoints for embedding attachments in PDFs and converting
between standard PDFs and PDF/A formats. Added `PdfAttachmentController`
and `PdfConversionController` with multipart/form-data and Base64-based
support. Implemented commands, handlers, and validators for these
operations.

Extended `IPdfProcessor` with methods for adding attachments and
PDF/A conversion. Partially implemented functionality in
`DevExpressPdfProcessor`, including `ConvertFromPdfAAsync`.

Added `attachment.xml` and `withoutAttachment.pdf` as resources for
testing. Marked endpoints as `[Obsolete]` to indicate incomplete
implementation. Improved validation and error handling for commands.
This commit is contained in:
2026-07-30 13:43:55 +02:00
parent a242458d2f
commit a6694bfce7
9 changed files with 876 additions and 0 deletions

View File

@@ -1093,4 +1093,113 @@ public class DevExpressPdfProcessor : IPdfProcessor
}
#endregion
#region Add Attachments (Phase 2)
/// <summary>
/// Embeds one or more files as attachments in a PDF document (supports PDF/A-3).
/// </summary>
public async Task<byte[]> AddAttachmentsAsync(
Stream pdfStream,
IReadOnlyList<(string FileName, byte[] Content, string? MimeType)> attachments)
{
// 1. Validate input
ArgumentNullException.ThrowIfNull(pdfStream, nameof(pdfStream));
ArgumentNullException.ThrowIfNull(attachments, nameof(attachments));
if (pdfStream.Length == 0)
throw new BadRequestException("PDF stream cannot be empty");
if (pdfStream.Position != 0)
throw new BadRequestException("PDF stream must be at position 0");
if (attachments.Count == 0)
throw new BadRequestException("At least one attachment is required");
// TODO: Implement AddFileAttachment using DevExpress.Pdf low-level API
// Current limitation: DevExpress.Pdf.PdfDocumentProcessor doesn't directly support adding attachments
// Workaround options:
// 1. Use PdfDocumentProcessor.Document to manipulate PDF structure directly (advanced)
// 2. Use third-party library for this specific operation
// 3. Wait for DevExpress API update
throw new NotImplementedException(
"Add attachments feature is not yet implemented. " +
"DevExpress.Pdf high-level API doesn't directly support adding file attachments. " +
"This requires low-level PDF structure manipulation.");
}
private string InferMimeType(string fileName)
{
string extension = Path.GetExtension(fileName).ToLowerInvariant();
return extension switch
{
".xml" => "application/xml",
".pdf" => "application/pdf",
".json" => "application/json",
".txt" => "text/plain",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
_ => "application/octet-stream"
};
}
#endregion
#region PDF Conversion (Phase 3)
/// <summary>
/// Converts a standard PDF to PDF/A format.
/// </summary>
public async Task<byte[]> ConvertToPdfAAsync(Stream pdfStream, string pdfALevel)
{
// 1. Validate input
ArgumentNullException.ThrowIfNull(pdfStream, nameof(pdfStream));
if (pdfStream.Length == 0)
throw new BadRequestException("PDF stream cannot be empty");
if (pdfStream.Position != 0)
throw new BadRequestException("PDF stream must be at position 0");
if (string.IsNullOrWhiteSpace(pdfALevel))
throw new BadRequestException("PDF/A level is required");
// TODO: Implement PDF to PDF/A conversion using DevExpress
// Current limitation: DevExpress.Pdf.PdfDocumentProcessor doesn't directly support PDF/A conversion
// Requires using specialized PDF/A conversion libraries or low-level PDF manipulation
throw new NotImplementedException(
$"PDF to PDF/A conversion ({pdfALevel}) is not yet implemented. " +
"DevExpress.Pdf high-level API doesn't directly support PDF/A conversion. " +
"This requires specialized PDF/A conversion logic.");
}
/// <summary>
/// Converts a PDF/A document to a standard PDF (removes PDF/A restrictions).
/// </summary>
public async Task<byte[]> ConvertFromPdfAAsync(Stream pdfStream)
{
// 1. Validate input
ArgumentNullException.ThrowIfNull(pdfStream, nameof(pdfStream));
if (pdfStream.Length == 0)
throw new BadRequestException("PDF stream cannot be empty");
if (pdfStream.Position != 0)
throw new BadRequestException("PDF stream must be at position 0");
// 2. Load PDF/A
using var processor = new PdfDocumentProcessor();
processor.LoadDocument(pdfStream);
// 3. Save as standard PDF
// DevExpress SaveDocument without special options creates standard PDF
using var outputStream = new MemoryStream();
processor.SaveDocument(outputStream);
return await Task.FromResult(outputStream.ToArray());
}
#endregion
}