[feat:] refsid integration
This commit is contained in:
+257
-146
@@ -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 void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth)
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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 <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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
/// <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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user