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; } }