[feat:] refsid integration
This commit is contained in:
+252
-141
@@ -124,7 +124,7 @@ public class PdfBuilder
|
|||||||
{
|
{
|
||||||
var owner = Settings._instance.customers.customers.FirstOrDefault(c => c.ID == addressSet.owner_id);
|
var owner = Settings._instance.customers.customers.FirstOrDefault(c => c.ID == addressSet.owner_id);
|
||||||
if (owner != null && !string.IsNullOrWhiteSpace(owner.sender_address))
|
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
|
catch
|
||||||
{
|
{
|
||||||
@@ -134,11 +134,16 @@ public class PdfBuilder
|
|||||||
// national addresses
|
// national addresses
|
||||||
for (var i = 0; i < addressSet.KasPersons.Count; i++)
|
for (var i = 0; i < addressSet.KasPersons.Count; i++)
|
||||||
{
|
{
|
||||||
|
string senderLineID = senderLine;
|
||||||
if (!addressSet.KasPersons[i].IsGermany()) continue;
|
if (!addressSet.KasPersons[i].IsGermany()) continue;
|
||||||
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
|
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
|
||||||
if (string.IsNullOrWhiteSpace(addr)) continue;
|
if (string.IsNullOrWhiteSpace(addr)) continue;
|
||||||
if (!string.IsNullOrEmpty(senderLine))
|
if (addressSet.KasPersons[i].refsid != null || addressSet.KasPersons[i].refsid != 0)
|
||||||
addresses_german.Add(senderLine + (addr ?? ""));
|
senderLineID += $"ID: {addressSet.KasPersons[i].refsid}\n</font6>";
|
||||||
|
else
|
||||||
|
senderLineID += "\n</font6>";
|
||||||
|
if (!string.IsNullOrEmpty(senderLineID))
|
||||||
|
addresses_german.Add(senderLineID + (addr ?? ""));
|
||||||
else
|
else
|
||||||
addresses_german.Add(addr);
|
addresses_german.Add(addr);
|
||||||
}
|
}
|
||||||
@@ -146,11 +151,16 @@ public class PdfBuilder
|
|||||||
// international addresses
|
// international addresses
|
||||||
for (var i = 0; i < addressSet.KasPersons.Count; i++)
|
for (var i = 0; i < addressSet.KasPersons.Count; i++)
|
||||||
{
|
{
|
||||||
|
string senderLineID = senderLine;
|
||||||
if (addressSet.KasPersons[i].IsGermany()) continue;
|
if (addressSet.KasPersons[i].IsGermany()) continue;
|
||||||
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
|
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
|
||||||
if (string.IsNullOrWhiteSpace(addr)) continue;
|
if (string.IsNullOrWhiteSpace(addr)) continue;
|
||||||
if (!string.IsNullOrEmpty(senderLine))
|
if (addressSet.KasPersons[i].refsid != null || addressSet.KasPersons[i].refsid != 0)
|
||||||
addresses_inter.Add(senderLine + (addr ?? ""));
|
senderLineID += $"ID: {addressSet.KasPersons[i].refsid}\n</font6>";
|
||||||
|
else
|
||||||
|
senderLineID += "\n</font6>";
|
||||||
|
if (!string.IsNullOrEmpty(senderLineID))
|
||||||
|
addresses_inter.Add(senderLineID + (addr ?? ""));
|
||||||
else
|
else
|
||||||
addresses_inter.Add(addr);
|
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,
|
private void DrawPageWithPlaceholder(XGraphics gfx, List<string> addresses, ref int addressIndex,
|
||||||
ref bool isFirstCell, string placeholderText)
|
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)
|
private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -325,139 +345,249 @@ public class PdfBuilder
|
|||||||
var paddingBottomPoints = MmToPoints(_settings.cellPaddingBottomMm);
|
var paddingBottomPoints = MmToPoints(_settings.cellPaddingBottomMm);
|
||||||
|
|
||||||
var maxWidth = Math.Max(0, cellWidth - paddingLeftPoints - paddingRightPoints);
|
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 runs = ParseStyledText(text);
|
||||||
var rawLines = text.Split(new[] { "\n" }, StringSplitOptions.None);
|
var lines = WrapRunsToLines(gfx, runs, maxWidth);
|
||||||
var lines = rawLines.Where(l => l != null).ToArray();
|
|
||||||
|
|
||||||
// Use a conservative line height in points
|
var currentY = y + paddingTopPoints;
|
||||||
var lineHeight = _regularFont.Size * 1.2;
|
var bottomLimit = y + paddingTopPoints + maxHeight;
|
||||||
|
|
||||||
// Calculate total height of the text block
|
|
||||||
var totalHeight = lines.Length * lineHeight;
|
|
||||||
|
|
||||||
// Start drawing from the top of the cell (align addresses to top)
|
|
||||||
var startY = y + paddingTopPoints;
|
|
||||||
|
|
||||||
var currentY = startY;
|
|
||||||
|
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
// Stop if we've reached the top boundary
|
var lineHeight = GetLineHeightForRuns(line);
|
||||||
if (currentY + lineHeight > y + cellHeight - paddingBottomPoints + 0.001)
|
|
||||||
|
if (currentY + lineHeight > bottomLimit + 0.001)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Parse and draw the line with markdown support
|
DrawLineRuns(gfx, line, x + paddingLeftPoints, currentY);
|
||||||
DrawLineWithMarkdown(gfx, line, x + paddingLeftPoints, currentY, maxWidth);
|
|
||||||
currentY += lineHeight;
|
currentY += lineHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(line)) return;
|
|
||||||
var currentX = x;
|
var currentX = x;
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
while (i < line.Length)
|
foreach (var run in line)
|
||||||
{
|
{
|
||||||
if (currentX - x >= maxWidth)
|
if (string.IsNullOrEmpty(run.Text))
|
||||||
break;
|
|
||||||
|
|
||||||
var remainingWidth = maxWidth - (currentX - x);
|
|
||||||
|
|
||||||
// Check for small-font tag <font6> ... </font6>
|
|
||||||
if (i <= line.Length - 7 && line.Substring(i, 7) == "<font6>")
|
|
||||||
{
|
|
||||||
var endTag = line.IndexOf("</font6>", i + 7, StringComparison.Ordinal);
|
|
||||||
if (endTag != -1)
|
|
||||||
{
|
|
||||||
var inner = line.Substring(i + 7, endTag - (i + 7));
|
|
||||||
if (!string.IsNullOrEmpty(inner))
|
|
||||||
{
|
|
||||||
var measuredSmall = gfx.MeasureString(inner, _smallFont);
|
|
||||||
|
|
||||||
if (measuredSmall.Width > remainingWidth)
|
|
||||||
{
|
|
||||||
inner = TruncateTextToWidth(gfx, inner, _smallFont, remainingWidth);
|
|
||||||
measuredSmall = gfx.MeasureString(inner, _smallFont);
|
|
||||||
}
|
|
||||||
|
|
||||||
gfx.DrawString(inner, _smallFont, XBrushes.Black,
|
|
||||||
new XRect(currentX, y, remainingWidth, _smallFont.Size * 1.2),
|
|
||||||
XStringFormats.TopLeft);
|
|
||||||
currentX += measuredSmall.Width;
|
|
||||||
}
|
|
||||||
|
|
||||||
i = endTag + 8; // move past </font6>
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for bold marker (**)
|
gfx.DrawString(
|
||||||
if (i < line.Length - 1 && line[i] == '*' && line[i + 1] == '*')
|
run.Text,
|
||||||
{
|
run.Font,
|
||||||
// Find closing **
|
XBrushes.Black,
|
||||||
var endIndex = line.IndexOf("**", i + 2, StringComparison.Ordinal);
|
new XRect(currentX, y, 10000, run.Font.Size * 1.2),
|
||||||
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);
|
XStringFormats.TopLeft);
|
||||||
currentX += measured.Width;
|
|
||||||
i = endIndex + 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular text until next ** or end of line
|
currentX += gfx.MeasureString(run.Text, run.Font).Width;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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)
|
private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -503,25 +633,6 @@ public class PdfBuilder
|
|||||||
return availableHeightMm / _settings.rowsPerPage;
|
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)
|
public void ExportRunningSheets(int setID, string path)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user