Introduce functionality to remove and replace metadata in the resulting PDF document. After loading the document with `PdfDocumentProcessor`, the metadata fields (`Author`, `Creator`, `Producer`, `Title`, `Subject`, and `Keywords`) are cleared or replaced with values indicating the document was processed by the "DXApp" system. This ensures proper attribution and removes any association with the original document creator.
166 lines
6.4 KiB
C#
166 lines
6.4 KiB
C#
using DevExpress.Pdf;
|
|
using DXApp.TemplateKitProject.Models;
|
|
|
|
namespace DXApp.TemplateKitProject.Services;
|
|
|
|
public class PdfResultPackageService(
|
|
IConfiguration configuration,
|
|
ILogger<PdfResultPackageService> logger)
|
|
{
|
|
public async Task<string?> CreateResultPackageAsync(
|
|
byte[] originalPdfBytes,
|
|
string originalFileName,
|
|
ZugferdInvoice invoice)
|
|
{
|
|
// 1. Bericht-PDF suchen
|
|
var reportPath = FindReportFile(originalFileName);
|
|
if (reportPath is null)
|
|
{
|
|
logger.LogWarning(
|
|
"Kein Ergebnisbericht gefunden für '{FileName}'.", originalFileName);
|
|
return null;
|
|
}
|
|
|
|
logger.LogDebug(
|
|
"Ergebnisbericht gefunden: '{ReportPath}'.", reportPath);
|
|
|
|
// 2. Ausgabepfad bestimmen
|
|
var outputDir = configuration["PdfResults:OutputDirectory"]
|
|
?? Path.Combine(Path.GetTempPath(), "PdfResults");
|
|
Directory.CreateDirectory(outputDir);
|
|
|
|
var baseName = Path.GetFileNameWithoutExtension(originalFileName);
|
|
var outputPath = Path.Combine(outputDir, $"{baseName}_result.pdf");
|
|
|
|
// 3. Original auf PDF/A-3b hochstufen + Bericht anhängen
|
|
await Task.Run(() =>
|
|
{
|
|
using var inputStream = new MemoryStream(originalPdfBytes);
|
|
|
|
// Schritt 1: PDF/A-3b Konvertierung
|
|
PdfDocumentConverter converter;
|
|
try
|
|
{
|
|
converter = new PdfDocumentConverter(inputStream);
|
|
converter.Convert(PdfCompatibility.PdfA3b);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Konvertierung nach PDF/A-3b fehlgeschlagen. " +
|
|
"Die Originaldatei ist möglicherweise beschädigt oder nicht konvertierbar.", ex);
|
|
}
|
|
|
|
// Schritt 2: Konvertiertes PDF puffern
|
|
using var convertedStream = new MemoryStream();
|
|
converter.SaveDocument(convertedStream);
|
|
convertedStream.Position = 0;
|
|
|
|
// Schritt 3: Anhang einbetten
|
|
using var processor = new PdfDocumentProcessor();
|
|
processor.LoadDocument(convertedStream);
|
|
|
|
// Metadaten des Originals entfernen → Result-PDF gehört uns, nicht dem Ersteller
|
|
processor.Document.Author = "DXApp Verarbeitungssystem";
|
|
processor.Document.Creator = "DXApp";
|
|
processor.Document.Producer = "DXApp";
|
|
processor.Document.Title = string.Empty;
|
|
processor.Document.Subject = string.Empty;
|
|
processor.Document.Keywords = string.Empty;
|
|
|
|
processor.AttachFile(new PdfFileAttachment
|
|
{
|
|
FileName = Path.GetFileName(reportPath),
|
|
Description = "Ergebnisbericht",
|
|
MimeType = "application/pdf",
|
|
Relationship = PdfAssociatedFileRelationship.Supplement,
|
|
CreationDate = DateTime.Now,
|
|
Data = File.ReadAllBytes(reportPath)
|
|
});
|
|
|
|
// Schritt 4: Stempel auf Seite 1 zeichnen
|
|
var firstPage = processor.Document.Pages[0];
|
|
using (var graphics = processor.CreateGraphicsWorldSystem())
|
|
{
|
|
// Stempel-Position: oben rechts
|
|
// Welt-Koordinaten: Ursprung oben links, X nach rechts, Y nach unten
|
|
var stampX = (float)firstPage.CropBox.Width - 200;
|
|
var stampY = 20f;
|
|
var stampWidth = 175f;
|
|
var stampHeight = 50f;
|
|
|
|
// Hintergrund weiß
|
|
using var whiteBrush = new DevExpress.Drawing.DXSolidBrush(
|
|
System.Drawing.Color.White);
|
|
graphics.FillRectangle(whiteBrush,
|
|
new System.Drawing.RectangleF(stampX, stampY, stampWidth, stampHeight));
|
|
|
|
// Rahmen grün
|
|
using var greenPen = new DevExpress.Drawing.DXPen(
|
|
System.Drawing.Color.Green, 1.5f);
|
|
graphics.DrawRectangle(greenPen,
|
|
new System.Drawing.RectangleF(stampX, stampY, stampWidth, stampHeight));
|
|
|
|
// Text Zeile 1: VERARBEITET
|
|
using var greenBrush = new DevExpress.Drawing.DXSolidBrush(
|
|
System.Drawing.Color.Green);
|
|
var fontBold = new DevExpress.Drawing.DXFont(
|
|
"Arial", 11, DevExpress.Drawing.DXFontStyle.Bold);
|
|
graphics.DrawString(
|
|
"✔ VERARBEITET",
|
|
fontBold,
|
|
greenBrush,
|
|
new System.Drawing.PointF(stampX + 8, stampY + 6));
|
|
|
|
// Text Zeile 2: Datum/Uhrzeit
|
|
using var grayBrush = new DevExpress.Drawing.DXSolidBrush(
|
|
System.Drawing.Color.DimGray);
|
|
var fontNormal = new DevExpress.Drawing.DXFont("Arial", 9);
|
|
graphics.DrawString(
|
|
DateTime.Now.ToString("dd.MM.yyyy HH:mm"),
|
|
fontNormal,
|
|
grayBrush,
|
|
new System.Drawing.PointF(stampX + 8, stampY + 28));
|
|
|
|
graphics.AddToPageForeground(
|
|
processor.Document.Pages[0], 96, 96);
|
|
}
|
|
|
|
// Schritt 4: Speichern
|
|
try
|
|
{
|
|
processor.SaveDocument(outputPath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Result-PDF konnte nicht gespeichert werden unter '{outputPath}'. " +
|
|
"Prüfe ob das Verzeichnis existiert und beschreibbar ist.", ex);
|
|
}
|
|
});
|
|
|
|
logger.LogInformation(
|
|
"Result-PDF gespeichert: '{OutputPath}'.", outputPath);
|
|
|
|
return outputPath;
|
|
}
|
|
|
|
private string? FindReportFile(string originalFileName)
|
|
{
|
|
var inputDir = configuration["PdfResultReports:InputDirectory"]
|
|
?? Path.Combine(Path.GetTempPath(), "PdfResultReports");
|
|
|
|
if (!Directory.Exists(inputDir))
|
|
{
|
|
logger.LogWarning("Berichtsverzeichnis nicht gefunden: '{Dir}'.", inputDir);
|
|
return null;
|
|
}
|
|
|
|
// Konvention Option A: {originalname}_report.pdf
|
|
var baseName = Path.GetFileNameWithoutExtension(originalFileName);
|
|
var reportName = $"{baseName}_report.pdf";
|
|
var reportPath = Path.Combine(inputDir, reportName);
|
|
|
|
return File.Exists(reportPath) ? reportPath : null;
|
|
}
|
|
} |