[init:] Initialized PDF Creation
This commit is contained in:
325
Tasks/PdfBuilder.cs
Normal file
325
Tasks/PdfBuilder.cs
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using PdfSharp;
|
||||||
|
using PdfSharp.Drawing;
|
||||||
|
using PdfSharp.Pdf;
|
||||||
|
|
||||||
|
namespace Logof_Client;
|
||||||
|
|
||||||
|
public class PdfBuilder
|
||||||
|
{
|
||||||
|
// Table layout
|
||||||
|
private const int CellsPerRow = 3;
|
||||||
|
private const int CellsPerPage = 21; // 3 columns × 7 rows
|
||||||
|
private readonly XFont _boldFont = new("Arial", 9, XFontStyleEx.Bold);
|
||||||
|
private readonly double _cellHeight = 42.43; // mm
|
||||||
|
private readonly double _cellPaddingBottom = 5; // mm
|
||||||
|
|
||||||
|
// Padding inside cells
|
||||||
|
private readonly double _cellPaddingLeft = 5; // mm
|
||||||
|
private readonly double _cellPaddingTop = 5; // mm
|
||||||
|
|
||||||
|
// Cell dimensions (in mm)
|
||||||
|
private readonly double _cellWidth = 70; // mm
|
||||||
|
|
||||||
|
// Font settings
|
||||||
|
private readonly double _fontSize = 9;
|
||||||
|
|
||||||
|
private readonly double _marginBottom = 1; // mm
|
||||||
|
|
||||||
|
// Paper and layout settings
|
||||||
|
private readonly double _marginLeft = 0; // mm
|
||||||
|
private readonly double _marginRight = 0; // mm
|
||||||
|
private readonly double _marginTop = 0; // mm
|
||||||
|
private readonly XFont _regularFont = new("Arial", 9, XFontStyleEx.Regular);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a PDF document with address stickers from an AddressSet in a 3×7 grid layout on A4 pages.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addressSetId">The ID of the AddressSet to use</param>
|
||||||
|
/// <param name="outputPath">Path where the PDF should be saved</param>
|
||||||
|
public void CreateAddressLabelPdfFromAddressSet(int addressSetId, string outputPath)
|
||||||
|
{
|
||||||
|
// Find the AddressSet by ID
|
||||||
|
var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId);
|
||||||
|
if (addressSet == null)
|
||||||
|
throw new ArgumentException($"AddressSet with ID {addressSetId} not found");
|
||||||
|
|
||||||
|
if (addressSet.KasPersons == null || addressSet.KasPersons.Count == 0)
|
||||||
|
throw new ArgumentException($"AddressSet with ID {addressSetId} contains no addresses");
|
||||||
|
|
||||||
|
// Generate markdown addresses from all KasPersons in the set
|
||||||
|
var addresses = new string?[addressSet.KasPersons.Count];
|
||||||
|
for (var i = 0; i < addressSet.KasPersons.Count; i++)
|
||||||
|
addresses[i] = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].refsid);
|
||||||
|
|
||||||
|
CreateAddressLabelPdf(addresses, outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a PDF document with address stickers from an AddressSet with a placeholder in the first cell.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addressSetId">The ID of the AddressSet to use</param>
|
||||||
|
/// <param name="placeholderText">Text for the first cell (top-left)</param>
|
||||||
|
/// <param name="outputPath">Path where the PDF should be saved</param>
|
||||||
|
public void CreateAddressLabelPdfFromAddressSetWithPlaceholder(int addressSetId, string placeholderText,
|
||||||
|
string outputPath)
|
||||||
|
{
|
||||||
|
// Find the AddressSet by ID
|
||||||
|
var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId);
|
||||||
|
if (addressSet == null)
|
||||||
|
throw new ArgumentException($"AddressSet with ID {addressSetId} not found");
|
||||||
|
|
||||||
|
if (addressSet.KasPersons == null || addressSet.KasPersons.Count == 0)
|
||||||
|
throw new ArgumentException($"AddressSet with ID {addressSetId} contains no addresses");
|
||||||
|
|
||||||
|
// Generate markdown addresses from all KasPersons in the set
|
||||||
|
var addresses = new string?[addressSet.KasPersons.Count];
|
||||||
|
for (var i = 0; i < addressSet.KasPersons.Count; i++)
|
||||||
|
addresses[i] = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].refsid);
|
||||||
|
|
||||||
|
CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a PDF document with address stickers in a 3×7 grid layout on A4 pages.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addresses">Array of addresses (from CreateFinalMarkdownString)</param>
|
||||||
|
/// <param name="outputPath">Path where the PDF should be saved</param>
|
||||||
|
public void CreateAddressLabelPdf(string?[] addresses, string outputPath)
|
||||||
|
{
|
||||||
|
if (addresses == null || addresses.Length == 0)
|
||||||
|
throw new ArgumentException("Addresses array cannot be null or empty");
|
||||||
|
|
||||||
|
var document = new PdfDocument();
|
||||||
|
|
||||||
|
var addressIndex = 0;
|
||||||
|
while (addressIndex < addresses.Length)
|
||||||
|
{
|
||||||
|
var page = document.AddPage();
|
||||||
|
page.Size = PageSize.A4;
|
||||||
|
|
||||||
|
using (var gfx = XGraphics.FromPdfPage(page))
|
||||||
|
{
|
||||||
|
// Draw the grid and fill cells
|
||||||
|
DrawPage(gfx, addresses, ref addressIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the document
|
||||||
|
document.Save(outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a PDF document with a single placeholder cell for other information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addresses">Array of addresses</param>
|
||||||
|
/// <param name="placeholderText">Text for the first cell (top-left)</param>
|
||||||
|
/// <param name="outputPath">Path where the PDF should be saved</param>
|
||||||
|
public void CreateAddressLabelPdfWithPlaceholder(string?[] addresses, string placeholderText, string outputPath)
|
||||||
|
{
|
||||||
|
if (addresses == null || addresses.Length == 0)
|
||||||
|
throw new ArgumentException("Addresses array cannot be null or empty");
|
||||||
|
|
||||||
|
var document = new PdfDocument();
|
||||||
|
|
||||||
|
var addressIndex = 0;
|
||||||
|
var isFirstCell = true;
|
||||||
|
|
||||||
|
while (addressIndex < addresses.Length || isFirstCell)
|
||||||
|
{
|
||||||
|
var page = document.AddPage();
|
||||||
|
page.Size = PageSize.A4;
|
||||||
|
|
||||||
|
using (var gfx = XGraphics.FromPdfPage(page))
|
||||||
|
{
|
||||||
|
DrawPageWithPlaceholder(gfx, addresses, ref addressIndex, ref isFirstCell, placeholderText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.Save(outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPage(XGraphics gfx, string?[] addresses, ref int addressIndex)
|
||||||
|
{
|
||||||
|
var cellCount = 0;
|
||||||
|
|
||||||
|
for (var row = 0; row < 7; row++)
|
||||||
|
{
|
||||||
|
for (var col = 0; col < 3; col++)
|
||||||
|
{
|
||||||
|
if (addressIndex >= addresses.Length) break;
|
||||||
|
|
||||||
|
var x = MmToPoints(_marginLeft + col * _cellWidth);
|
||||||
|
var y = MmToPoints(_marginTop + row * _cellHeight);
|
||||||
|
|
||||||
|
DrawCell(gfx, x, y, addresses[addressIndex]);
|
||||||
|
addressIndex++;
|
||||||
|
cellCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addressIndex >= addresses.Length) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPageWithPlaceholder(XGraphics gfx, string?[] addresses, ref int addressIndex,
|
||||||
|
ref bool isFirstCell, string placeholderText)
|
||||||
|
{
|
||||||
|
var cellCount = 0;
|
||||||
|
|
||||||
|
for (var row = 0; row < 7; row++)
|
||||||
|
for (var col = 0; col < 3; col++)
|
||||||
|
{
|
||||||
|
var x = MmToPoints(_marginLeft + col * _cellWidth);
|
||||||
|
var y = MmToPoints(_marginTop + row * _cellHeight);
|
||||||
|
|
||||||
|
// First cell: placeholder
|
||||||
|
if (isFirstCell)
|
||||||
|
{
|
||||||
|
DrawCell(gfx, x, y, placeholderText);
|
||||||
|
isFirstCell = false;
|
||||||
|
}
|
||||||
|
else if (addressIndex < addresses.Length)
|
||||||
|
{
|
||||||
|
DrawCell(gfx, x, y, addresses[addressIndex]);
|
||||||
|
addressIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DrawEmptyCell(gfx, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
cellCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCell(XGraphics gfx, double x, double y, string? address)
|
||||||
|
{
|
||||||
|
var cellWidthPoints = MmToPoints(_cellWidth);
|
||||||
|
var cellHeightPoints = MmToPoints(_cellHeight);
|
||||||
|
|
||||||
|
// Draw cell border
|
||||||
|
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
|
||||||
|
gfx.DrawRectangle(XPens.Black, rect);
|
||||||
|
|
||||||
|
// Draw address content if available
|
||||||
|
if (!string.IsNullOrEmpty(address)) DrawMarkdownText(gfx, address, x, y, cellWidthPoints, cellHeightPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEmptyCell(XGraphics gfx, double x, double y)
|
||||||
|
{
|
||||||
|
var cellWidthPoints = MmToPoints(_cellWidth);
|
||||||
|
var cellHeightPoints = MmToPoints(_cellHeight);
|
||||||
|
|
||||||
|
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
|
||||||
|
gfx.DrawRectangle(XPens.Black, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight)
|
||||||
|
{
|
||||||
|
var paddingLeftPoints = MmToPoints(_cellPaddingLeft);
|
||||||
|
var paddingTopPoints = MmToPoints(_cellPaddingTop);
|
||||||
|
var paddingBottomPoints = MmToPoints(_cellPaddingBottom);
|
||||||
|
|
||||||
|
var maxWidth = cellWidth - paddingLeftPoints * 2;
|
||||||
|
|
||||||
|
// Split text by newlines and remove empty trailing lines
|
||||||
|
var rawLines = text.Split(new[] { "\n" }, StringSplitOptions.None);
|
||||||
|
var lines = rawLines.Where(l => l != null).ToArray();
|
||||||
|
|
||||||
|
// Use a conservative line height in points
|
||||||
|
var lineHeight = _regularFont.Size * 1.2;
|
||||||
|
|
||||||
|
// Calculate total height of the text block
|
||||||
|
var totalHeight = lines.Length * lineHeight;
|
||||||
|
|
||||||
|
// Start drawing from the lower-left corner of the cell
|
||||||
|
var startY = y + cellHeight - paddingBottomPoints - totalHeight;
|
||||||
|
if (startY < y + paddingTopPoints)
|
||||||
|
startY = y + paddingTopPoints; // don't overflow top padding
|
||||||
|
|
||||||
|
var currentY = startY;
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
// Stop if we've reached the top boundary
|
||||||
|
if (currentY + lineHeight > y + cellHeight - paddingBottomPoints + 0.001)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Parse and draw the line with markdown support
|
||||||
|
DrawLineWithMarkdown(gfx, line, x + paddingLeftPoints, currentY, maxWidth);
|
||||||
|
currentY += lineHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth)
|
||||||
|
{
|
||||||
|
var currentX = x;
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
while (i < line.Length)
|
||||||
|
{
|
||||||
|
// Check for bold marker (**)
|
||||||
|
if (i < line.Length - 1 && line[i] == '*' && line[i + 1] == '*')
|
||||||
|
{
|
||||||
|
// Find closing **
|
||||||
|
var endIndex = line.IndexOf("**", i + 2, StringComparison.Ordinal);
|
||||||
|
if (endIndex != -1)
|
||||||
|
{
|
||||||
|
var boldText = line.Substring(i + 2, endIndex - (i + 2));
|
||||||
|
// Draw bold text and measure width accurately
|
||||||
|
gfx.DrawString(boldText, _boldFont, XBrushes.Black,
|
||||||
|
new XRect(currentX, y, maxWidth - (currentX - x), _boldFont.Size * 1.2),
|
||||||
|
XStringFormats.TopLeft);
|
||||||
|
var measured = gfx.MeasureString(boldText, _boldFont);
|
||||||
|
currentX += measured.Width;
|
||||||
|
i = endIndex + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular text until next ** or end of line
|
||||||
|
var nextBoldIndex = line.IndexOf("**", i, StringComparison.Ordinal);
|
||||||
|
var textEnd = nextBoldIndex == -1 ? line.Length : nextBoldIndex;
|
||||||
|
var regularText = line.Substring(i, textEnd - i);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(regularText))
|
||||||
|
{
|
||||||
|
gfx.DrawString(regularText, _regularFont, XBrushes.Black,
|
||||||
|
new XRect(currentX, y, maxWidth - (currentX - x), _regularFont.Size * 1.2), XStringFormats.TopLeft);
|
||||||
|
var measured = gfx.MeasureString(regularText, _regularFont);
|
||||||
|
currentX += measured.Width;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = textEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts millimeters to points (1 mm = 2.834645669 points)
|
||||||
|
/// </summary>
|
||||||
|
private double MmToPoints(double mm)
|
||||||
|
{
|
||||||
|
return mm * 2.834645669;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration methods to allow customization
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the cell dimensions in millimeters
|
||||||
|
/// </summary>
|
||||||
|
public void SetCellDimensions(double width, double height)
|
||||||
|
{
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
throw new ArgumentException("Cell dimensions must be positive");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the page margins in millimeters
|
||||||
|
/// </summary>
|
||||||
|
public void SetMargins(double left, double top, double right, double bottom)
|
||||||
|
{
|
||||||
|
if (left < 0 || top < 0 || right < 0 || bottom < 0)
|
||||||
|
throw new ArgumentException("Margins cannot be negative");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user