Files
Antifraude.Net/Antifraude.Net/GestionaDenuncias.Shared/Helpers/PdfHelper.cs
2026-05-06 13:48:23 +02:00

160 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using PdfSharpCore.Pdf.IO;
namespace GestionaDenuncias.Shared.Helpers
{
public static class PdfHelper
{
/// <summary>
/// Fusiona varios ficheros (PDF, imágenes, TXT) en un único PDF.
/// Los .txt se renderizan con márgenes iguales, alineación a la izquierda y ajuste de líneas,
/// preservando líneas en blanco.
/// </summary>
/// <param name="files">Secuencia de tuplas (FileName, ContentBytes)</param>
/// <returns>Bytes del PDF combinado</returns>
public static byte[] MergeFilesToPdf(IEnumerable<(string FileName, byte[] Content)> files)
{
if (files == null) throw new ArgumentNullException(nameof(files));
var outputDoc = new PdfDocument();
foreach (var (fileName, content) in files)
{
if (string.IsNullOrEmpty(fileName) || content == null || content.Length == 0)
throw new InvalidOperationException($"El archivo '{fileName}' no tiene contenido.");
var ext = Path.GetExtension(fileName).ToLowerInvariant();
switch (ext)
{
case ".pdf":
using (var src = PdfReader.Open(new MemoryStream(content), PdfDocumentOpenMode.Import))
foreach (var page in src.Pages)
outputDoc.AddPage(page);
break;
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
using (var img = XImage.FromStream(() => new MemoryStream(content)))
{
var page = outputDoc.AddPage();
page.Width = XUnit.FromPoint(img.PointWidth);
page.Height = XUnit.FromPoint(img.PointHeight);
using var gfxImg = XGraphics.FromPdfPage(page);
gfxImg.DrawImage(img, 0, 0, page.Width, page.Height);
}
break;
case ".txt":
// Renderizado de TXT con margen y ajuste de líneas, preservando líneas en blanco
var text = Encoding.UTF8.GetString(content);
PdfPage pageTxt = outputDoc.AddPage();
XGraphics gfxTxt = XGraphics.FromPdfPage(pageTxt);
const double marginLeft = 40;
const double marginRight = 40;
const double marginTop = 40;
const double marginBottom = 40;
var font = new XFont("Courier New", 10, XFontStyle.Regular);
var sample = gfxTxt.MeasureString("Ay", font);
var lineHeight = sample.Height * 1.2;
double y = marginTop;
double pageWidth = pageTxt.Width.Point;
double pageHeight = pageTxt.Height.Point;
double maxWidth = pageWidth - marginLeft - marginRight;
foreach (var origLine in text.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'))
{
// Línea en blanco: preservarla
if (string.IsNullOrWhiteSpace(origLine))
{
y += lineHeight;
if (y + lineHeight > pageHeight - marginBottom)
{
gfxTxt.Dispose();
pageTxt = outputDoc.AddPage();
gfxTxt = XGraphics.FromPdfPage(pageTxt);
y = marginTop;
}
continue;
}
var words = origLine.Split(' ');
string currentLine = string.Empty;
foreach (var word in words)
{
var testLine = string.IsNullOrEmpty(currentLine) ? word : currentLine + " " + word;
var size = gfxTxt.MeasureString(testLine, font);
if (size.Width <= maxWidth)
{
currentLine = testLine;
}
else
{
// Dibujar la línea acumulada
gfxTxt.DrawString(
currentLine,
font,
XBrushes.Black,
new XRect(marginLeft, y, maxWidth, lineHeight),
XStringFormats.TopLeft);
y += lineHeight;
currentLine = word;
// Paginación si se sale por abajo
if (y + lineHeight > pageHeight - marginBottom)
{
gfxTxt.Dispose();
pageTxt = outputDoc.AddPage();
gfxTxt = XGraphics.FromPdfPage(pageTxt);
y = marginTop;
}
}
}
// Dibujar la última línea del párrafo
if (!string.IsNullOrEmpty(currentLine))
{
gfxTxt.DrawString(
currentLine,
font,
XBrushes.Black,
new XRect(marginLeft, y, maxWidth, lineHeight),
XStringFormats.TopLeft);
y += lineHeight;
if (y + lineHeight > pageHeight - marginBottom)
{
gfxTxt.Dispose();
pageTxt = outputDoc.AddPage();
gfxTxt = XGraphics.FromPdfPage(pageTxt);
y = marginTop;
}
}
}
gfxTxt.Dispose();
break;
default:
throw new NotSupportedException($"Extensión no soportada: {ext}");
}
}
using var ms = new MemoryStream();
outputDoc.Save(ms);
return ms.ToArray();
}
}
}