diff --git a/Tasks/PdfBuilder.cs b/Tasks/PdfBuilder.cs
index afcc455..8541511 100644
--- a/Tasks/PdfBuilder.cs
+++ b/Tasks/PdfBuilder.cs
@@ -124,7 +124,7 @@ public class PdfBuilder
{
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";
+ senderLine = "" + owner.sender_address.Replace("\n", " ").Trim() + "\\n";
}
catch
{
@@ -134,11 +134,16 @@ public class PdfBuilder
// 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 (!string.IsNullOrEmpty(senderLine))
- addresses_german.Add(senderLine + (addr ?? ""));
+ 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);
}
@@ -146,11 +151,16 @@ public class PdfBuilder
// 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 (!string.IsNullOrEmpty(senderLine))
- addresses_inter.Add(senderLine + (addr ?? ""));
+ 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);
}
@@ -223,24 +233,6 @@ public class PdfBuilder
}
- private void DrawPage(XGraphics gfx, List 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 addresses, ref int addressIndex,
ref bool isFirstCell, string placeholderText)
@@ -315,6 +307,34 @@ public class PdfBuilder
}
+ 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
@@ -325,139 +345,249 @@ public class PdfBuilder
var paddingBottomPoints = MmToPoints(_settings.cellPaddingBottomMm);
var maxWidth = Math.Max(0, cellWidth - paddingLeftPoints - paddingRightPoints);
+ var maxHeight = Math.Max(0, cellHeight - paddingTopPoints - paddingBottomPoints);
- // 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();
+ var runs = ParseStyledText(text);
+ var lines = WrapRunsToLines(gfx, runs, maxWidth);
- // 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;
+ var currentY = y + paddingTopPoints;
+ var bottomLimit = y + paddingTopPoints + maxHeight;
foreach (var line in lines)
{
- // Stop if we've reached the top boundary
- if (currentY + lineHeight > y + cellHeight - paddingBottomPoints + 0.001)
+ var lineHeight = GetLineHeightForRuns(line);
+
+ if (currentY + lineHeight > bottomLimit + 0.001)
break;
- // Parse and draw the line with markdown support
- DrawLineWithMarkdown(gfx, line, x + paddingLeftPoints, currentY, maxWidth);
+ DrawLineRuns(gfx, line, x + paddingLeftPoints, currentY);
currentY += lineHeight;
}
}
catch (Exception ex)
{
- Logger.Log($"Error while drawing markdown text: {ex.Message}",Logger.LogType.Error);
+ Logger.Log($"Error while drawing markdown text: {ex.Message}", Logger.LogType.Error);
}
-
}
- private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth)
+ 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.8 : _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
{
- if (string.IsNullOrWhiteSpace(line)) return;
var currentX = x;
- var i = 0;
- while (i < line.Length)
+ foreach (var run in line)
{
- if (currentX - x >= maxWidth)
- break;
+ if (string.IsNullOrEmpty(run.Text))
+ continue;
- var remainingWidth = maxWidth - (currentX - x);
+ gfx.DrawString(
+ run.Text,
+ run.Font,
+ XBrushes.Black,
+ new XRect(currentX, y, 10000, run.Font.Size * 1.2),
+ XStringFormats.TopLeft);
- // Check for small-font tag ...
- if (i <= line.Length - 7 && line.Substring(i, 7) == "")
- {
- var endTag = line.IndexOf("", 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
- 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;
+ currentX += gfx.MeasureString(run.Text, run.Font).Width;
}
}
catch (Exception ex)
{
- Logger.Log($"Error while drawing markdown line: {ex.Message}",Logger.LogType.Error);
+ 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
@@ -502,26 +632,7 @@ public class PdfBuilder
var availableHeightMm = 297d - _settings.pageMarginTopMm - _settings.pageMarginBottomMm;
return availableHeightMm / _settings.rowsPerPage;
}
-
- // Configuration methods to allow customization
-
- ///
- /// Sets the cell dimensions in millimeters
- ///
- public void SetCellDimensions(double width, double height)
- {
- if (width <= 0 || height <= 0)
- throw new ArgumentException("Cell dimensions must be positive");
- }
-
- ///
- /// Sets the page margins in millimeters
- ///
- 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)
{