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)
{
try
{
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 (Exception ex)
{
Logger.Log($"Error while searching for fonts: {ex.Message}",Logger.LogType.Error);
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);
}
catch (Exception ex)
{
Logger.Log($"Error while font resolving: {ex.Message}",Logger.LogType.Error);
}
}
private static void EnsureFontResolverRegistered()
{
try
{
if (GlobalFontSettings.FontResolver != null) return;
//var fontsDir = Path.Combine(AppContext.BaseDirectory, "fonts");
GlobalFontSettings.FontResolver = new StableFontResolver(Global._instance.font_path);
}
catch (Exception ex)
{
Logger.Log($"Error while ensuring font resolver register state: {ex.Message}",Logger.LogType.Error);
}
}
private static string StripStyleSuffix(string name)
{
try
{
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;
}
catch (Exception ex)
{
Logger.Log($"Error while stripping style suffix: {ex.Message}",Logger.LogType.Error);
}
return null;
}
///
/// Creates a PDF document with address stickers from an AddressSet with a placeholder in the first cell.
///
/// The ID of the AddressSet to use
/// Text for the first cell (top-left)
/// Path where the PDF should be saved
public void CreateAddressLabelPdfFromAddressSetWithPlaceholder(int addressSetId, string placeholderText, bool pvst,
string outputPath)
{
try
{
// Find the AddressSet by ID
var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId);
// foreach (var pers in addressSet.KasPersons)
// {
// AddressCreator.CreateFinalMarkdownString(pers.id);
// }
// addressSet.KasPersons = addressSet.KasPersons.OrderBy(x => x.IsGermany()).ThenBy(y => y.used_plz).ToList();
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_german = new List();
var addresses_inter = new List();
// 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 = "" + owner.sender_address.Replace("\n", " ").Trim() + "\\n";
}
catch
{
senderLine = null;
}
// national addresses
for (var i = 0; i < addressSet.KasPersons.Count; i++)
{
string senderLineID = senderLine;
if (!addressSet.KasPersons[i].IsGermany()) continue;
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
if (string.IsNullOrWhiteSpace(addr)) continue;
if (pvst)
senderLineID += $"**PvSt, DP AG, Entgelt bezahlt, ZKZ {Settings._instance.pdfExport.rsZKZ}**\n";
if (addressSet.KasPersons[i].refsid != null || addressSet.KasPersons[i].refsid != 0)
senderLineID += $"ID: {addressSet.KasPersons[i].refsid}\n";
else
senderLineID += "\n";
if (!string.IsNullOrEmpty(senderLineID))
addresses_german.Add(senderLineID + (addr ?? ""));
else
addresses_german.Add(addr);
}
// international addresses
for (var i = 0; i < addressSet.KasPersons.Count; i++)
{
string senderLineID = senderLine;
if (addressSet.KasPersons[i].IsGermany()) continue;
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
if (string.IsNullOrWhiteSpace(addr)) continue;
if (pvst)
senderLineID += $"**PvSt, DP AG, Entgelt bezahlt, ZKZ {Settings._instance.pdfExport.rsZKZ}**\n";
if (addressSet.KasPersons[i].refsid != null || addressSet.KasPersons[i].refsid != 0)
senderLineID += $"ID: {addressSet.KasPersons[i].refsid}\n";
else
senderLineID += "\n";
if (!string.IsNullOrEmpty(senderLineID))
addresses_inter.Add(senderLineID + (addr ?? ""));
else
addresses_inter.Add(addr);
}
if (addresses_german.Count == 0 && addresses_inter.Count == 0)
{
MessageBox.Show(MainWindow._instance, "Keine validen Adressen konnten generiert werden. Abbruch.", "Fehler");
return;
}
string output_inter = outputPath.Replace(".pdf", "-international.pdf");
if(addresses_german.Count > 0) CreateAddressLabelPdfWithPlaceholder(addresses_german, placeholderText, outputPath);
if(addresses_inter.Count > 0) CreateAddressLabelPdfWithPlaceholder(addresses_inter, placeholderText, output_inter);
if (_settings.exportRunningSheets)
{
ExportRunningSheets(addressSetId, outputPath);
}
//CreateAddressLabelPdfWithPlaceholder(addresses_german, placeholderText, outputPath);
//CreateAddressLabelPdfWithPlaceholder(addresses_inter, placeholderText, output_inter);
}
catch (Exception ex)
{
Logger.Log($"Error while creating address label pdf from address set with placeholder: {ex.Message}",Logger.LogType.Error);
}
}
///
/// Creates a PDF document with a single placeholder cell for other information.
///
/// Array of addresses
/// Text for the first cell (top-left)
/// Path where the PDF should be saved
public void CreateAddressLabelPdfWithPlaceholder(List addresses, string placeholderText, string outputPath)
{
try
{
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);
}
catch (Exception ex)
{
Logger.Log($"Error while creating address label pdf with placeholder: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawPageWithPlaceholder(XGraphics gfx, List addresses, ref int addressIndex,
ref bool isFirstCell, string placeholderText)
{
try
{
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);
}
}
}
catch (Exception ex)
{
Logger.Log($"Error while drawing page with placholder: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawCell(XGraphics gfx, double x, double y, string? address)
{
try
{
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);
}
catch (Exception ex)
{
Logger.Log($"Error while drawing cell: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawEmptyCell(XGraphics gfx, double x, double y)
{
try
{
var cellWidthPoints = MmToPoints(GetCellWidthMm());
var cellHeightPoints = MmToPoints(GetCellHeightMm());
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
gfx.DrawRectangle(XPens.Black, rect);
}
catch (Exception ex)
{
Logger.Log($"Error while drawing empty cell: {ex.Message}",Logger.LogType.Error);
}
}
private enum TextStyle
{
Regular,
Bold,
Small
}
private sealed class TextToken
{
public string Text { get; }
public TextStyle Style { get; }
public bool LineBreak { get; }
public TextToken(string text, TextStyle style, bool lineBreak = false)
{
Text = text;
Style = style;
LineBreak = lineBreak;
}
}
private sealed class TextRun
{
public string Text { get; set; } = "";
public XFont Font { get; set; } = null!;
public bool LineBreakAfter { get; set; }
}
private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight)
{
try
{
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);
var maxHeight = Math.Max(0, cellHeight - paddingTopPoints - paddingBottomPoints);
var runs = ParseStyledText(text);
var lines = WrapRunsToLines(gfx, runs, maxWidth);
var currentY = y + paddingTopPoints;
var bottomLimit = y + paddingTopPoints + maxHeight;
foreach (var line in lines)
{
var lineHeight = GetLineHeightForRuns(line);
if (currentY + lineHeight > bottomLimit + 0.001)
break;
DrawLineRuns(gfx, line, x + paddingLeftPoints, currentY);
currentY += lineHeight;
}
}
catch (Exception ex)
{
Logger.Log($"Error while drawing markdown text: {ex.Message}", Logger.LogType.Error);
}
}
private List ParseStyledText(string text)
{
var tokens = new List();
if (string.IsNullOrEmpty(text))
return tokens;
var i = 0;
var style = TextStyle.Regular;
var buffer = new System.Text.StringBuilder();
void FlushBuffer()
{
if (buffer.Length > 0)
{
tokens.Add(new TextToken(buffer.ToString(), style));
buffer.Clear();
}
}
while (i < text.Length)
{
if (text[i] == '\r')
{
i++;
continue;
}
if (text[i] == '\n')
{
FlushBuffer();
tokens.Add(new TextToken("", style, lineBreak: true));
i++;
continue;
}
if (i <= text.Length - 7 && text.Substring(i, 7) == "")
{
FlushBuffer();
style = TextStyle.Small;
i += 7;
continue;
}
if (i <= text.Length - 8 && text.Substring(i, 8) == "")
{
FlushBuffer();
style = TextStyle.Regular;
i += 8;
continue;
}
if (i < text.Length - 1 && text[i] == '*' && text[i + 1] == '*')
{
FlushBuffer();
style = style == TextStyle.Bold ? TextStyle.Regular : TextStyle.Bold;
i += 2;
continue;
}
if (i < text.Length - 1 && text[i] == '\\' && text[i + 1] == 'n')
{
FlushBuffer();
tokens.Add(new TextToken("", style, lineBreak: true));
i += 2;
continue;
}
buffer.Append(text[i]);
i++;
}
FlushBuffer();
return tokens;
}
private double GetLineHeightForRuns(IEnumerable line)
{
double max = 0;
foreach (var run in line)
{
if (run.Font != null)
{
var h = run.Font.GetHeight();
if (h > max) max = h;
}
}
return max > 0 ? max * 0.88 : _regularFont.GetHeight();
}
private List> WrapRunsToLines(XGraphics gfx, List tokens, double maxWidth)
{
var lines = new List>();
var currentLine = new List();
double currentWidth = 0;
void PushLine()
{
lines.Add(currentLine);
currentLine = new List();
currentWidth = 0;
}
foreach (var token in tokens)
{
if (token.LineBreak)
{
PushLine();
continue;
}
var font = token.Style switch
{
TextStyle.Bold => _boldFont,
TextStyle.Small => _smallFont,
_ => _regularFont
};
var parts = token.Text.Split('\n');
for (int p = 0; p < parts.Length; p++)
{
var part = parts[p];
if (part.Length > 0)
{
var remaining = part;
while (!string.IsNullOrEmpty(remaining))
{
var available = maxWidth - currentWidth;
if (available <= 0.001)
{
PushLine();
available = maxWidth;
}
var fit = FitTextToWidth(gfx, remaining, font, available);
if (string.IsNullOrEmpty(fit))
{
PushLine();
continue;
}
currentLine.Add(new TextRun
{
Text = fit,
Font = font
});
currentWidth += gfx.MeasureString(fit, font).Width;
remaining = remaining.Substring(fit.Length);
if (!string.IsNullOrEmpty(remaining))
PushLine();
}
}
if (p < parts.Length - 1)
PushLine();
}
}
if (currentLine.Count > 0 || lines.Count == 0)
lines.Add(currentLine);
return lines;
}
private void DrawLineRuns(XGraphics gfx, List line, double x, double y)
{
try
{
var currentX = x;
foreach (var run in line)
{
if (string.IsNullOrEmpty(run.Text))
continue;
gfx.DrawString(
run.Text,
run.Font,
XBrushes.Black,
new XRect(currentX, y, 10000, run.Font.Size * 1.2),
XStringFormats.TopLeft);
currentX += gfx.MeasureString(run.Text, run.Font).Width;
}
}
catch (Exception ex)
{
Logger.Log($"Error while drawing markdown line: {ex.Message}", Logger.LogType.Error);
}
}
private string FitTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth)
{
if (string.IsNullOrEmpty(text))
return "";
if (gfx.MeasureString(text, font).Width <= maxWidth)
return text;
var lo = 0;
var hi = text.Length;
while (lo < hi)
{
var mid = (lo + hi + 1) / 2;
var candidate = text.Substring(0, mid);
if (gfx.MeasureString(candidate, font).Width <= maxWidth)
lo = mid;
else
hi = mid - 1;
}
return lo > 0 ? text.Substring(0, lo) : "";
}
private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth)
{
try
{
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;
}
catch (Exception ex)
{
Logger.Log($"Error while truncating text to width: {ex.Message}",Logger.LogType.Error);
}
return null;
}
///
/// Converts millimeters to points (1 mm = 2.834645669 points)
///
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;
}
public void ExportRunningSheets(int setID, string path)
{
string international_path = path;
if (path.EndsWith(".pdf"))
{
path = path.Substring(0, path.Length - 4);
international_path = path;
path = path + "-Laufzettel.pdf";
international_path = international_path + "-Laufzettel-International.pdf";
}
else
{
path = path + "-Laufzettel.pdf";
international_path = international_path + "-Laufzettel-International.pdf";
}
CreateGermanyRunningSheets(setID, path);
CreateInternationalRunningSheets(setID, international_path);
}
public void CreateGermanyRunningSheets(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";
var grouped_nums = GroupAddresses(setID).ToList();
// Zwei Tabellen pro A4-Seite
int sheets = (grouped_nums.Count + 1) / 2;
// Layout
double marginX = 20;
double marginY = 20;
double gapY = 12;
for (int pageIndex = 0; pageIndex < sheets; pageIndex++)
{
var page = document.AddPage();
page.Size = PageSize.A4;
var gfx = XGraphics.FromPdfPage(page);
double pageW = page.Width.Point;
double pageH = page.Height.Point;
double usableW = pageW - 2 * marginX;
double usableH = pageH - 2 * marginY;
double tableH = (usableH - gapY) / 2.0;
double top1 = marginY;
double top2 = marginY + tableH + gapY;
var fontLabel = new XFont("Arial", 6, XFontStyleEx.Bold);
var fontText = new XFont("Arial", 6, XFontStyleEx.Regular);
var fontBig = new XFont("Arial", 30, XFontStyleEx.Bold);
int firstIndex = pageIndex * 2;
int palletNumber =
Settings._instance.pdfExport.rsPalNr +
(firstIndex / Settings._instance.pdfExport.rsBundNrPal);
int bundleOnPallet =
(firstIndex % Settings._instance.pdfExport.rsBundNrPal) + 1;
DrawGermanyRunningSheet(
gfx,
marginX,
top1,
usableW,
tableH,
list,
grouped_nums[firstIndex],
grouped_nums,
fontLabel,
fontText,
fontBig,
palletNumber,
bundleOnPallet,
firstIndex + 1
);
int secondIndex = firstIndex + 1;
int palletNumber2 =
Settings._instance.pdfExport.rsPalNr +
(secondIndex / Settings._instance.pdfExport.rsBundNrPal);
int bundleOnPallet2 =
(secondIndex % Settings._instance.pdfExport.rsBundNrPal) + 1;
if (firstIndex + 1 < grouped_nums.Count)
{
DrawGermanyRunningSheet(
gfx,
marginX,
top2,
usableW,
tableH,
list,
grouped_nums[secondIndex],
grouped_nums,
fontLabel,
fontText,
fontBig,
palletNumber2,
bundleOnPallet2,
secondIndex + 1
);
}
}
document.Save(path);
}
private void DrawGermanyRunningSheet(
XGraphics gfx,
double x,
double y,
double w,
double h,
KasAddressList list,
dynamic result,
List<(int,string,string,string,int)> grouped_nums,
XFont fontLabel,
XFont fontText,
XFont fontBig,
int pal_nr,
int bundleOnPallet,
int totalBundleNumber){
double line = 1.0;
string sender = Customer.GetCustomerByID(list.owner_id)?.sender_address ?? "[Absender]";
string customerName = Customer.GetCustomerByID(list.owner_id)?.name ?? "[Kunde]";
string start = result.Item3?.ToString() ?? "[Start]";
string end = result.Item4?.ToString() ?? "[Ende]";
string amount = result.Item5?.ToString() ?? "[Anzahl]";
string groupNo = result.Item2?.ToString() ?? "[PLZ]";
string fraction = result.Item1?.ToString() ?? "[Fraktion]";
var options = Settings._instance.pdfExport;
int total_frac = 0;
foreach (var item in grouped_nums)
{
if (item.Item2 == result.Item2) total_frac++;
}
// Outer border
gfx.DrawRectangle(XPens.Black, x, y, w, h);
// Row heights
double r1 = h * 0.1;
double r2 = h * 0.1;
double r3 = h * 0.54;
double r4 = h * 0.30;
// Main horizontal lines
gfx.DrawLine(XPens.Black, x, y + r1, x + w, y + r1);
gfx.DrawLine(XPens.Black, x, y + r1 + r2, x + w, y + r1 + r2);
gfx.DrawLine(XPens.Black, x, y + r1 + r2 + r3, x + w, y + r1 + r2 + r3);
// Top row columns
double c1 = w * 0.39;
double c2 = w * 0.20;
double c3 = w * 0.26;
double c4 = w * 0.15;
gfx.DrawLine(XPens.Black, x + c1, y, x + c1, y + r1);
gfx.DrawLine(XPens.Black, x + c1 + c2, y, x + c1 + c2, y + r1);
gfx.DrawLine(XPens.Black, x + c1 + c2 + c3, y, x + c1 + c2 + c3, y + r1);
// Second row columns
gfx.DrawLine(XPens.Black, x + c1, y + r1, x + c1, y + r1 + r2);
gfx.DrawLine(XPens.Black, x + c1 + c2, y + r1, x + c1 + c2, y + r1 + r2);
gfx.DrawLine(XPens.Black, x + c1 + c2 + c3, y + r1, x + c1 + c2 + c3, y + r1 + r2);
// Middle large area split
double midSplit = x + w * 0.55;
gfx.DrawLine(XPens.Black, midSplit, y + r1 + r2, midSplit, y + r1 + r2 + r3);
// Bottom section split
double leftBottomW = w * 0.42;
gfx.DrawLine(XPens.Black, x + leftBottomW, y + r1 + r2 + r3, x + leftBottomW, y + h);
// Bottom left rows
double blY1 = y + r1 + r2 + r3 + (r4 * 0.14);
double blY2 = y + r1 + r2 + r3 + (r4 * 0.28);
double blY3 = y + r1 + r2 + r3 + (r4 * 0.42);
double blY4 = y + r1 + r2 + r3 + (r4 * 0.56);
double blY5 = y + r1 + r2 + r3 + (r4 * 0.70);
gfx.DrawLine(XPens.Black, x, blY1, x + leftBottomW, blY1);
gfx.DrawLine(XPens.Black, x, blY2, x + leftBottomW, blY2);
gfx.DrawLine(XPens.Black, x, blY3, x + leftBottomW, blY3);
gfx.DrawLine(XPens.Black, x, blY4, x + leftBottomW, blY4);
gfx.DrawLine(XPens.Black, x, blY5, x + leftBottomW, blY5);
// Labels top row
gfx.DrawString("Absender:", fontLabel, XBrushes.Black, new XRect(x + 5, y + 4, c1 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Kunden-Nr. Absender:", fontLabel, XBrushes.Black, new XRect(x + c1 + 5, y + 4, c2 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("ZKZ/Titel:", fontLabel, XBrushes.Black, new XRect(x + c1 + c2 + 5, y + 4, c3 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Anzahl Sendungen:", fontLabel, XBrushes.Black, new XRect(x + c1 + c2 + c3 + 5, y + 4, c4 - 10, 14), XStringFormats.TopLeft);
// Values top row
gfx.DrawString(sender, fontText, XBrushes.Black, new XRect(x + 5, y + 20, c1 - 10, r1 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsKNrAbs, fontText, XBrushes.Black, new XRect(x + c1 + 5, y + 20, c2 - 10, r1 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsZKZ, fontText, XBrushes.Black, new XRect(x + c1 + c2 + 5, y + 20, c3 - 10, r1 - 22), XStringFormats.TopLeft);
gfx.DrawString(amount, fontText, XBrushes.Black, new XRect(x + c1 + c2 + c3 + 5, y + 20, c4 - 10, r1 - 22), XStringFormats.TopLeft);
// Second row labels
gfx.DrawString("Einlieferer:", fontLabel, XBrushes.Black, new XRect(x + 5, y + r1 + 4, c1 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Kunden-Nr. Einlieferer:", fontLabel, XBrushes.Black, new XRect(x + c1 + 5, y + r1 + 4, c2 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Interne Vermerke:", fontLabel, XBrushes.Black, new XRect(x + c1 + c2 + 5, y + r1 + 4, c3 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Laufzeit", fontLabel, XBrushes.Black, new XRect(x + c1 + c2 + c3 + 5, y + r1 + 4, c4 - 10, 14), XStringFormats.TopLeft);
// Second row values
gfx.DrawString(customerName, fontText, XBrushes.Black, new XRect(x + 5, y + r1 + 20, c1 - 10, r2 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsKNrEmp, fontText, XBrushes.Black, new XRect(x + c1 + 5, y + r1 + 20, c2 - 10, r2 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsIntVer, fontText, XBrushes.Black, new XRect(x + c1 + c2 + 5, y + r1 + 20, c3 - 10, r2 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsLaufz, fontText, XBrushes.Black, new XRect(x + c1 + c2 + c3 + 5, y + r1 + 20, c4 - 10, r2 - 22), XStringFormats.TopLeft);
// Middle area
gfx.DrawString(groupNo, fontBig, XBrushes.Black,
new XRect(x + 5, y + r1 + r2 + 5, w * 0.50 - 10, r3 - 10),
XStringFormats.Center);
gfx.DrawString("PRESSE/ELN", fontBig, XBrushes.Black,
new XRect(x + 5, y + r1 + r2 + 5, w * 0.50 - 10, r3 - 90),
XStringFormats.Center);
gfx.DrawString($"Fraktion {fraction}/{total_frac}", fontText, XBrushes.Black,
new XRect(x + 5, y + r1 + r2 + r3 - 18, w * 0.50 - 10, 14),
XStringFormats.CenterLeft);
gfx.DrawString("Bereich für postalische Zwecke:", fontLabel, XBrushes.Black,
new XRect(midSplit + 5, y + r1 + r2 + 4, w - (midSplit - x) - 10, 14),
XStringFormats.TopLeft);
// Bottom left labels
// Bottom left labels
gfx.DrawString("Einlieferungsdatum:", fontLabel, XBrushes.Black,
new XRect(x + 5, y + r1 + r2 + r3 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("AM-Auftragsnummer:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY1 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("Bundgewicht:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY2 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("Paletten-Nr.:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY3 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("Bund-Nr./Bunde auf Palette:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY4 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("Bund-Nr. von Gesamtanzahl:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY5 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
// Bottom placeholders rechts daneben, gleiche Zeile
double valueX = x + leftBottomW * 0.55;
double valueW = leftBottomW - (valueX - x) - 5;
gfx.DrawString(options.rsEinldat, fontText, XBrushes.Black,
new XRect(valueX, y + r1 + r2 + r3 + 4, valueW, 14),
XStringFormats.TopRight);
gfx.DrawString(options.rsAMANr, fontText, XBrushes.Black,
new XRect(valueX, blY1 + 4, valueW, 14),
XStringFormats.TopRight);
if (options.rsBundNrPal == null) goto nopal;
gfx.DrawString(pal_nr.ToString(), fontText, XBrushes.Black,
new XRect(valueX, blY3 + 4, valueW, 14),
XStringFormats.TopRight);
gfx.DrawString(
$"{bundleOnPallet}/{Settings._instance.pdfExport.rsBundNrPal}",
fontText,
XBrushes.Black,
new XRect(valueX, blY4 + 4, valueW, 14),
XStringFormats.TopRight);
nopal:
gfx.DrawString(
$"{totalBundleNumber}/{grouped_nums.Count}",
fontText,
XBrushes.Black,
new XRect(valueX, blY5 + 4, valueW, 14),
XStringFormats.TopRight);
// Right bottom postal area label
gfx.DrawString("Feld für Palettenlabel/NVE:", fontLabel, XBrushes.Black,
new XRect(x + leftBottomW + 5, y + r1 + r2 + r3 + 4, w - leftBottomW - 10, 14),
XStringFormats.TopLeft);
}
public void CreateInternationalRunningSheets(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";
var grouped_nums = GroupAddressesInternational(setID).ToList();
int sheets = (grouped_nums.Count + 1) / 2;
double marginX = 20;
double marginY = 20;
double gapY = 12;
for (int pageIndex = 0; pageIndex < sheets; pageIndex++)
{
var page = document.AddPage();
page.Size = PageSize.A4;
var gfx = XGraphics.FromPdfPage(page);
double pageW = page.Width.Point;
double pageH = page.Height.Point;
double usableW = pageW - 2 * marginX;
double usableH = pageH - 2 * marginY;
double tableH = (usableH - gapY) / 2.0;
double top1 = marginY;
double top2 = marginY + tableH + gapY;
var fontLabel = new XFont("Arial", 6, XFontStyleEx.Bold);
var fontText = new XFont("Arial", 6, XFontStyleEx.Regular);
var fontBig = new XFont("Arial", 30, XFontStyleEx.Bold);
int firstIndex = pageIndex * 2;
int palletNumber =
Settings._instance.pdfExport.rsPalNr +
(firstIndex / Settings._instance.pdfExport.rsBundNrPal);
int bundleOnPallet =
(firstIndex % Settings._instance.pdfExport.rsBundNrPal) + 1;
DrawInternationalRunningSheet(
gfx,
marginX,
top1,
usableW,
tableH,
list,
grouped_nums[firstIndex],
grouped_nums,
fontLabel,
fontText,
fontBig,
palletNumber,
bundleOnPallet,
firstIndex + 1
);
if (firstIndex + 1 < grouped_nums.Count)
{
int secondIndex = firstIndex + 1;
int palletNumber2 =
Settings._instance.pdfExport.rsPalNr +
(secondIndex / Settings._instance.pdfExport.rsBundNrPal);
int bundleOnPallet2 =
(secondIndex % Settings._instance.pdfExport.rsBundNrPal) + 1;
DrawInternationalRunningSheet(
gfx,
marginX,
top2,
usableW,
tableH,
list,
grouped_nums[secondIndex],
grouped_nums,
fontLabel,
fontText,
fontBig,
palletNumber2,
bundleOnPallet2,
secondIndex + 1
);
}
}
document.Save(path);
}
private void DrawInternationalRunningSheet(
XGraphics gfx,
double x,
double y,
double w,
double h,
KasAddressList list,
dynamic result,
List<(int,string,string,string,int)> grouped_nums,
XFont fontLabel,
XFont fontText,
XFont fontBig,
int pal_nr,
int bundleOnPallet,
int totalBundleNumber,
string category = "ECONOMY Non-EU")
{
double line = 1.0;
string sender = Customer.GetCustomerByID(list.owner_id)?.sender_address ?? "[Absender]";
string customerName = Customer.GetCustomerByID(list.owner_id)?.name ?? "[Kunde]";
string start = result.Item3?.ToString() ?? "[Start]";
string end = result.Item4?.ToString() ?? "[Ende]";
string amount = result.Item5?.ToString() ?? "[Anzahl]";
string groupNo = result.Item2?.ToString() ?? "[PLZ]";
string fraction = result.Item1?.ToString() ?? "[Fraktion]";
var options = Settings._instance.pdfExport;
int total_frac = 0;
foreach (var item in grouped_nums)
{
if (item.Item2 == result.Item2) total_frac++;
}
// Outer border
gfx.DrawRectangle(XPens.Black, x, y, w, h);
// Row heights
double r1 = h * 0.1;
double r2 = h * 0.1;
double r3 = h * 0.54;
double r4 = h * 0.30;
// Main horizontal lines
gfx.DrawLine(XPens.Black, x, y + r1, x + w, y + r1);
gfx.DrawLine(XPens.Black, x, y + r1 + r2, x + w, y + r1 + r2);
gfx.DrawLine(XPens.Black, x, y + r1 + r2 + r3, x + w, y + r1 + r2 + r3);
// Top row columns
double c1 = w * 0.39;
double c2 = w * 0.20;
double c3 = w * 0.26;
double c4 = w * 0.15;
gfx.DrawLine(XPens.Black, x + c1, y, x + c1, y + r1);
gfx.DrawLine(XPens.Black, x + c1 + c2, y, x + c1 + c2, y + r1);
gfx.DrawLine(XPens.Black, x + c1 + c2 + c3, y, x + c1 + c2 + c3, y + r1);
// Second row columns
gfx.DrawLine(XPens.Black, x + c1, y + r1, x + c1, y + r1 + r2);
gfx.DrawLine(XPens.Black, x + c1 + c2, y + r1, x + c1 + c2, y + r1 + r2);
gfx.DrawLine(XPens.Black, x + c1 + c2 + c3, y + r1, x + c1 + c2 + c3, y + r1 + r2);
// Middle large area split
double midSplit = x + w * 0.55;
gfx.DrawLine(XPens.Black, midSplit, y + r1 + r2, midSplit, y + r1 + r2 + r3);
// Bottom section split
double leftBottomW = w * 0.42;
gfx.DrawLine(XPens.Black, x + leftBottomW, y + r1 + r2 + r3, x + leftBottomW, y + h);
// Bottom left rows
double blY1 = y + r1 + r2 + r3 + (r4 * 0.14);
double blY2 = y + r1 + r2 + r3 + (r4 * 0.28);
double blY3 = y + r1 + r2 + r3 + (r4 * 0.42);
double blY4 = y + r1 + r2 + r3 + (r4 * 0.56);
double blY5 = y + r1 + r2 + r3 + (r4 * 0.70);
gfx.DrawLine(XPens.Black, x, blY1, x + leftBottomW, blY1);
gfx.DrawLine(XPens.Black, x, blY2, x + leftBottomW, blY2);
gfx.DrawLine(XPens.Black, x, blY3, x + leftBottomW, blY3);
gfx.DrawLine(XPens.Black, x, blY4, x + leftBottomW, blY4);
gfx.DrawLine(XPens.Black, x, blY5, x + leftBottomW, blY5);
// Labels top row
gfx.DrawString("Absender:", fontLabel, XBrushes.Black, new XRect(x + 5, y + 4, c1 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Kunden-Nr. Absender:", fontLabel, XBrushes.Black, new XRect(x + c1 + 5, y + 4, c2 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("ZKZ/Titel:", fontLabel, XBrushes.Black, new XRect(x + c1 + c2 + 5, y + 4, c3 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Anzahl Sendungen:", fontLabel, XBrushes.Black, new XRect(x + c1 + c2 + c3 + 5, y + 4, c4 - 10, 14), XStringFormats.TopLeft);
// Values top row
gfx.DrawString(sender, fontText, XBrushes.Black, new XRect(x + 5, y + 20, c1 - 10, r1 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsKNrAbs, fontText, XBrushes.Black, new XRect(x + c1 + 5, y + 20, c2 - 10, r1 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsZKZ, fontText, XBrushes.Black, new XRect(x + c1 + c2 + 5, y + 20, c3 - 10, r1 - 22), XStringFormats.TopLeft);
gfx.DrawString(amount, fontText, XBrushes.Black, new XRect(x + c1 + c2 + c3 + 5, y + 20, c4 - 10, r1 - 22), XStringFormats.TopLeft);
// Second row labels
gfx.DrawString("Einlieferer:", fontLabel, XBrushes.Black, new XRect(x + 5, y + r1 + 4, c1 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Kunden-Nr. Einlieferer:", fontLabel, XBrushes.Black, new XRect(x + c1 + 5, y + r1 + 4, c2 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Interne Vermerke:", fontLabel, XBrushes.Black, new XRect(x + c1 + c2 + 5, y + r1 + 4, c3 - 10, 14), XStringFormats.TopLeft);
gfx.DrawString("Laufzeit", fontLabel, XBrushes.Black, new XRect(x + c1 + c2 + c3 + 5, y + r1 + 4, c4 - 10, 14), XStringFormats.TopLeft);
// Second row values
gfx.DrawString(customerName, fontText, XBrushes.Black, new XRect(x + 5, y + r1 + 20, c1 - 10, r2 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsKNrEmp, fontText, XBrushes.Black, new XRect(x + c1 + 5, y + r1 + 20, c2 - 10, r2 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsIntVer, fontText, XBrushes.Black, new XRect(x + c1 + c2 + 5, y + r1 + 20, c3 - 10, r2 - 22), XStringFormats.TopLeft);
gfx.DrawString(options.rsLaufz, fontText, XBrushes.Black, new XRect(x + c1 + c2 + c3 + 5, y + r1 + 20, c4 - 10, r2 - 22), XStringFormats.TopLeft);
// Middle area
gfx.DrawString(groupNo, fontBig, XBrushes.Black,
new XRect(x + 5, y + r1 + r2 + 5, w * 0.50 - 10, r3 - 10),
XStringFormats.Center);
gfx.DrawString("PRESSE/ELN", fontBig, XBrushes.Black,
new XRect(x + 5, y + r1 + r2 + 5, w * 0.50 - 10, r3 - 90),
XStringFormats.Center);
gfx.DrawString($"Fraktion {fraction}/{total_frac}", fontText, XBrushes.Black,
new XRect(x + 5, y + r1 + r2 + r3 - 18, w * 0.50 - 10, 14),
XStringFormats.CenterLeft);
gfx.DrawString("Bereich für postalische Zwecke:", fontLabel, XBrushes.Black,
new XRect(midSplit + 5, y + r1 + r2 + 4, w - (midSplit - x) - 10, 14),
XStringFormats.TopLeft);
// Bottom left labels
// Bottom left labels
gfx.DrawString("Einlieferungsdatum:", fontLabel, XBrushes.Black,
new XRect(x + 5, y + r1 + r2 + r3 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("AM-Auftragsnummer:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY1 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("Bundgewicht:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY2 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("Paletten-Nr.:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY3 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("Bund-Nr./Bunde auf Palette:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY4 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
gfx.DrawString("Bund-Nr. von Gesamtanzahl:", fontLabel, XBrushes.Black,
new XRect(x + 5, blY5 + 4, leftBottomW - 10, 14),
XStringFormats.TopLeft);
// Bottom placeholders rechts daneben, gleiche Zeile
double valueX = x + leftBottomW * 0.55;
double valueW = leftBottomW - (valueX - x) - 5;
gfx.DrawString(options.rsEinldat, fontText, XBrushes.Black,
new XRect(valueX, y + r1 + r2 + r3 + 4, valueW, 14),
XStringFormats.TopRight);
gfx.DrawString(options.rsAMANr, fontText, XBrushes.Black,
new XRect(valueX, blY1 + 4, valueW, 14),
XStringFormats.TopRight);
gfx.DrawString(options.rsBundGew, fontText, XBrushes.Black,
new XRect(valueX, blY2 + 4, valueW, 14),
XStringFormats.TopRight);
if (options.rsBundNrPal == 0) goto nopal;
gfx.DrawString(pal_nr.ToString(), fontText, XBrushes.Black,
new XRect(valueX, blY3 + 4, valueW, 14),
XStringFormats.TopRight);
gfx.DrawString(
$"{bundleOnPallet}/{Settings._instance.pdfExport.rsBundNrPal}",
fontText,
XBrushes.Black,
new XRect(valueX, blY4 + 4, valueW, 14),
XStringFormats.TopRight);
nopal:
gfx.DrawString(
$"{totalBundleNumber}/{grouped_nums.Count}",
fontText,
XBrushes.Black,
new XRect(valueX, blY5 + 4, valueW, 14),
XStringFormats.TopRight);
// Right bottom postal area label
gfx.DrawString("Feld für Palettenlabel/NVE:", fontLabel, XBrushes.Black,
new XRect(x + leftBottomW + 5, y + r1 + r2 + r3 + 4, w - leftBottomW - 10, 14),
XStringFormats.TopLeft);
}
///
/// Calculates address groups to summarize for the single pages of the running sheets.
///
///
/// List of quadruples consisting of a number and the starting of the plz, as well as first, last plz and total amount of addresses
public List<(int, string, string, string, int)> GroupAddresses(int setID)
{
int grpcount = Settings._instance.pdfExport.rsNumGrouped; // Amount of addresses per group
int stpoint = 2; // group starting point (first n characters of the plz)
KasAddressList list = Settings._instance.addressSets.GetAddressSetByID(setID);
if (list == null)
throw new Exception("AddressSet nicht gefunden");
List<(int, string, string, string, int)> output = new();
List> sorted_list = list.KasPersons
.Where(x => !string.IsNullOrEmpty(x?.used_plz) &&
x.used_plz.Length >= stpoint && x.IsGermany())
.OrderBy(x => x.used_plz)
.GroupBy(x => x.used_plz.Substring(0, stpoint))
.ToList();
foreach (var group in sorted_list)
{
string start = group.Key;
int fraktion = 0;
for (int count = 0; count < group.Count(); count += grpcount)
{
fraktion++;
int currentGroupSize = Math.Min(grpcount, group.Count() - count);
string first = group.ElementAt(count).used_plz;
string last = group.ElementAt(count + currentGroupSize - 1).used_plz;
output.Add((fraktion, start, first, last, currentGroupSize));
}
}
return output;
}
public List<(int, string, string, string, int)> GroupAddressesInternational(int setID)
{
int grpcount = Settings._instance.pdfExport.rsNumGrouped; // Amount of addresses per group
KasAddressList list = Settings._instance.addressSets.GetAddressSetByID(setID);
if (list == null)
throw new Exception("AddressSet nicht gefunden");
List<(int, string, string, string, int)> output = new();
List> sorted_list = list.KasPersons
.Where(x => !string.IsNullOrEmpty(x?.used_plz) && !x.IsGermany())
.OrderBy(x => x.used_plz)
.GroupBy(x => x.land)
.ToList();
foreach (var group in sorted_list)
{
string start = group.Key;
int fraktion = 0;
for (int count = 0; count < group.Count(); count += grpcount)
{
fraktion++;
int currentGroupSize = Math.Min(grpcount, group.Count() - count);
string first = group.ElementAt(count).used_plz;
string last = group.ElementAt(count + currentGroupSize - 1).used_plz;
output.Add((fraktion, start, first, last, currentGroupSize));
}
}
return output;
}
}