[feat:] refsid integration

This commit is contained in:
2026-06-28 15:06:57 +02:00
parent 42f8e499a4
commit 53b5b4b92c
+252 -141
View File
@@ -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 = "<font6>" + owner.sender_address.Replace("\n", " ").Trim() + "</font6>\n";
senderLine = "<font6>" + 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</font6>";
else
senderLineID += "\n</font6>";
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</font6>";
else
senderLineID += "\n</font6>";
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<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)
@@ -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 List<TextToken> ParseStyledText(string text)
{
var tokens = new List<TextToken>();
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();
}
}
private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth)
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) == "<font6>")
{
FlushBuffer();
style = TextStyle.Small;
i += 7;
continue;
}
if (i <= text.Length - 8 && text.Substring(i, 8) == "</font6>")
{
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<TextRun> 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<List<TextRun>> WrapRunsToLines(XGraphics gfx, List<TextToken> tokens, double maxWidth)
{
var lines = new List<List<TextRun>>();
var currentLine = new List<TextRun>();
double currentWidth = 0;
void PushLine()
{
lines.Add(currentLine);
currentLine = new List<TextRun>();
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<TextRun> 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;
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>
if (string.IsNullOrEmpty(run.Text))
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),
gfx.DrawString(
run.Text,
run.Font,
XBrushes.Black,
new XRect(currentX, y, 10000, run.Font.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
@@ -503,25 +633,6 @@ public class PdfBuilder
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)
{