457 lines
16 KiB
C#
457 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using PdfSharp;
|
|
using PdfSharp.Drawing;
|
|
using PdfSharp.Fonts;
|
|
using PdfSharp.Pdf;
|
|
|
|
namespace Logof_Client;
|
|
|
|
public class PdfBuilder
|
|
{
|
|
private readonly PdfExportSettings _settings;
|
|
private readonly XFont _boldFont;
|
|
private readonly XFont _regularFont;
|
|
private readonly XFont _smallFont;
|
|
|
|
public PdfBuilder(PdfExportSettings? settings = null)
|
|
{
|
|
EnsureFontResolverRegistered();
|
|
_settings = settings ?? new PdfExportSettings();
|
|
|
|
// Select first font from build output fonts folder (AppContext.BaseDirectory/fonts)
|
|
var chosenFamily = "Arial";
|
|
try
|
|
{
|
|
if (Directory.Exists(Global._instance.font_path))
|
|
{
|
|
var first = Directory.EnumerateFiles(Global._instance.font_path, "*.ttf").FirstOrDefault();
|
|
if (!string.IsNullOrEmpty(first))
|
|
chosenFamily = StripStyleSuffix(Path.GetFileNameWithoutExtension(first)) ?? chosenFamily;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
chosenFamily = "Arial";
|
|
}
|
|
|
|
_boldFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Bold);
|
|
_regularFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Regular);
|
|
_smallFont = new XFont(chosenFamily, _settings.smallFontSize, XFontStyleEx.Regular);
|
|
}
|
|
|
|
private static void EnsureFontResolverRegistered()
|
|
{
|
|
if (GlobalFontSettings.FontResolver != null) return;
|
|
//var fontsDir = Path.Combine(AppContext.BaseDirectory, "fonts");
|
|
GlobalFontSettings.FontResolver = new StableFontResolver(Global._instance.font_path);
|
|
}
|
|
|
|
private static string StripStyleSuffix(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name)) return name;
|
|
var idx = name.IndexOf('-');
|
|
if (idx < 0) idx = name.IndexOf('_');
|
|
if (idx > 0)
|
|
return name.Substring(0, idx);
|
|
return name;
|
|
}
|
|
|
|
|
|
/// <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];
|
|
var addresses = new List<string>();
|
|
|
|
// find customer (owner) to include sender_address
|
|
string senderLine = null;
|
|
try
|
|
{
|
|
var owner = Settings._instance.customers.customers.FirstOrDefault(c => c.ID == addressSet.owner_id);
|
|
if (owner != null && !string.IsNullOrWhiteSpace(owner.sender_address))
|
|
senderLine = "<font6>" + owner.sender_address.Replace("\n", " ").Trim() + "</font6>\n";
|
|
}
|
|
catch
|
|
{
|
|
senderLine = null;
|
|
}
|
|
|
|
for (var i = 0; i < addressSet.KasPersons.Count; i++)
|
|
{
|
|
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
|
|
if (string.IsNullOrWhiteSpace(addr)) continue;
|
|
if (!string.IsNullOrEmpty(senderLine))
|
|
addresses.Add(senderLine + (addr ?? ""));
|
|
else
|
|
addresses.Add(addr);
|
|
}
|
|
|
|
if (addresses.Count == 0)
|
|
{
|
|
MessageBox.Show(MainWindow._instance, "Keine validen Adressen konnten generiert werden. Abbruch.", "Fehler");
|
|
return;
|
|
}
|
|
CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath);
|
|
|
|
if (_settings.exportRunningSheets)
|
|
{
|
|
ExportRunningSheets(addressSetId, 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(List<string> addresses, string placeholderText, string outputPath)
|
|
{
|
|
if (addresses == null || addresses.Count == 0)
|
|
throw new ArgumentException("Addresses array cannot be null or empty");
|
|
|
|
var document = new PdfDocument();
|
|
|
|
var addressIndex = 0;
|
|
var isFirstCell = true;
|
|
|
|
while (addressIndex < addresses.Count || 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, List<string> addresses, ref int addressIndex)
|
|
{
|
|
for (var row = 0; row < _settings.rowsPerPage; row++)
|
|
{
|
|
for (var col = 0; col < _settings.columnsPerPage; col++)
|
|
{
|
|
if (addressIndex >= addresses.Count) break;
|
|
|
|
var x = MmToPoints(_settings.pageMarginLeftMm + col * GetCellWidthMm());
|
|
var y = MmToPoints(_settings.pageMarginTopMm + row * GetCellHeightMm());
|
|
|
|
DrawCell(gfx, x, y, addresses[addressIndex]);
|
|
addressIndex++;
|
|
}
|
|
|
|
if (addressIndex >= addresses.Count) break;
|
|
}
|
|
}
|
|
|
|
private void DrawPageWithPlaceholder(XGraphics gfx, List<string> addresses, ref int addressIndex,
|
|
ref bool isFirstCell, string placeholderText)
|
|
{
|
|
for (var row = 0; row < _settings.rowsPerPage; row++)
|
|
for (var col = 0; col < _settings.columnsPerPage; col++)
|
|
{
|
|
var x = MmToPoints(_settings.pageMarginLeftMm + col * GetCellWidthMm());
|
|
var y = MmToPoints(_settings.pageMarginTopMm + row * GetCellHeightMm());
|
|
|
|
// First cell: placeholder
|
|
if (isFirstCell)
|
|
{
|
|
DrawCell(gfx, x, y, placeholderText);
|
|
isFirstCell = false;
|
|
}
|
|
else if (addressIndex < addresses.Count)
|
|
{
|
|
DrawCell(gfx, x, y, addresses[addressIndex]);
|
|
addressIndex++;
|
|
}
|
|
else
|
|
{
|
|
DrawEmptyCell(gfx, x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawCell(XGraphics gfx, double x, double y, string? address)
|
|
{
|
|
var cellWidthPoints = MmToPoints(GetCellWidthMm());
|
|
var cellHeightPoints = MmToPoints(GetCellHeightMm());
|
|
|
|
// 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(GetCellWidthMm());
|
|
var cellHeightPoints = MmToPoints(GetCellHeightMm());
|
|
|
|
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(_settings.cellPaddingLeftMm);
|
|
var paddingRightPoints = MmToPoints(_settings.cellPaddingRightMm);
|
|
var paddingTopPoints = MmToPoints(_settings.cellPaddingTopMm);
|
|
var paddingBottomPoints = MmToPoints(_settings.cellPaddingBottomMm);
|
|
|
|
var maxWidth = Math.Max(0, cellWidth - paddingLeftPoints - paddingRightPoints);
|
|
|
|
// 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 top of the cell (align addresses to top)
|
|
var startY = y + paddingTopPoints;
|
|
|
|
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)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(line)) return;
|
|
var currentX = x;
|
|
var i = 0;
|
|
|
|
while (i < line.Length)
|
|
{
|
|
if (currentX - x >= maxWidth)
|
|
break;
|
|
|
|
var remainingWidth = maxWidth - (currentX - x);
|
|
|
|
// Check for small-font tag <font6> ... </font6>
|
|
if (i <= line.Length - 7 && line.Substring(i, 7) == "<font6>")
|
|
{
|
|
var endTag = line.IndexOf("</font6>", i + 7, StringComparison.Ordinal);
|
|
if (endTag != -1)
|
|
{
|
|
var inner = line.Substring(i + 7, endTag - (i + 7));
|
|
if (!string.IsNullOrEmpty(inner))
|
|
{
|
|
var measuredSmall = gfx.MeasureString(inner, _smallFont);
|
|
|
|
if (measuredSmall.Width > remainingWidth)
|
|
{
|
|
inner = TruncateTextToWidth(gfx, inner, _smallFont, remainingWidth);
|
|
measuredSmall = gfx.MeasureString(inner, _smallFont);
|
|
}
|
|
|
|
gfx.DrawString(inner, _smallFont, XBrushes.Black,
|
|
new XRect(currentX, y, remainingWidth, _smallFont.Size * 1.2),
|
|
XStringFormats.TopLeft);
|
|
currentX += measuredSmall.Width;
|
|
}
|
|
|
|
i = endTag + 8; // move past </font6>
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// 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));
|
|
var measured = gfx.MeasureString(boldText, _boldFont);
|
|
|
|
if (measured.Width > remainingWidth)
|
|
{
|
|
boldText = TruncateTextToWidth(gfx, boldText, _boldFont, remainingWidth);
|
|
measured = gfx.MeasureString(boldText, _boldFont);
|
|
}
|
|
|
|
// Draw bold text and measure width accurately
|
|
gfx.DrawString(boldText, _boldFont, XBrushes.Black,
|
|
new XRect(currentX, y, remainingWidth, _boldFont.Size * 1.2),
|
|
XStringFormats.TopLeft);
|
|
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))
|
|
{
|
|
var measured = gfx.MeasureString(regularText, _regularFont);
|
|
|
|
if (measured.Width > remainingWidth)
|
|
{
|
|
regularText = TruncateTextToWidth(gfx, regularText, _regularFont, remainingWidth);
|
|
measured = gfx.MeasureString(regularText, _regularFont);
|
|
}
|
|
|
|
gfx.DrawString(regularText, _regularFont, XBrushes.Black,
|
|
new XRect(currentX, y, remainingWidth, _regularFont.Size * 1.2), XStringFormats.TopLeft);
|
|
currentX += measured.Width;
|
|
}
|
|
|
|
i = textEnd;
|
|
}
|
|
}
|
|
|
|
private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth)
|
|
{
|
|
if (string.IsNullOrEmpty(text))
|
|
return text;
|
|
|
|
for (var len = text.Length; len > 0; len--)
|
|
{
|
|
var truncated = text.Substring(0, len);
|
|
var measured = gfx.MeasureString(truncated, font);
|
|
if (measured.Width <= maxWidth)
|
|
return truncated;
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts millimeters to points (1 mm = 2.834645669 points)
|
|
/// </summary>
|
|
private double MmToPoints(double mm)
|
|
{
|
|
return mm * 2.834645669;
|
|
}
|
|
|
|
private double GetCellWidthMm()
|
|
{
|
|
var availableWidthMm = 210d - _settings.pageMarginLeftMm - _settings.pageMarginRightMm;
|
|
return availableWidthMm / _settings.columnsPerPage;
|
|
}
|
|
|
|
private double GetCellHeightMm()
|
|
{
|
|
var availableHeightMm = 297d - _settings.pageMarginTopMm - _settings.pageMarginBottomMm;
|
|
return availableHeightMm / _settings.rowsPerPage;
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
|
|
public void ExportRunningSheets(int setID, string path)
|
|
{
|
|
if (path.EndsWith(".pdf"))
|
|
{
|
|
path = path.Substring(0, path.Length - 4);
|
|
path = path + "-Laufzettel.pdf";
|
|
}
|
|
else
|
|
{
|
|
path = path + "-Laufzettel.pdf";
|
|
}
|
|
|
|
CreateRunningSheets(setID, path);
|
|
|
|
}
|
|
|
|
public void CreateRunningSheets(int setID, string path)
|
|
{
|
|
KasAddressList list = Settings._instance.addressSets.GetAddressSetByID(setID);
|
|
var document = new PdfDocument();
|
|
document.Info.Title = $"Laufzettel für {list.Name}";
|
|
document.Info.Subject = "powered by logofclient";
|
|
document.Info.Author = "logofclient";
|
|
|
|
int margin = 50;
|
|
|
|
List<(int, int)> grouped_nums = GroupAddresses(setID);
|
|
|
|
var page = document.AddPage();
|
|
page.Size = PageSize.A4;
|
|
|
|
var gfx = XGraphics.FromPdfPage(page);
|
|
|
|
var width = page.Width.Point-margin;
|
|
var height = page.Height.Point-margin;
|
|
gfx.DrawLine(XPens.Black, margin, margin, margin, height);
|
|
gfx.DrawLine(XPens.Black, margin, margin, width, margin);
|
|
gfx.DrawLine(XPens.Black, width, margin, margin, height);
|
|
gfx.DrawLine(XPens.Black, margin, height, width, margin);
|
|
|
|
var font = new XFont("Times New Roman", 20, XFontStyleEx.BoldItalic);
|
|
|
|
gfx.DrawString($"Versand {list.Name}", font, XBrushes.Black,
|
|
new XRect(0, 0, page.Width.Point, page.Height.Point), XStringFormats.Center);
|
|
|
|
|
|
document.Save(path);
|
|
}
|
|
|
|
public List<(int, int)> GroupAddresses(int setID)
|
|
{
|
|
|
|
|
|
return null;
|
|
}
|
|
}
|