增强条码和自由表格元素的渲染逻辑,支持更多边框样式和参数配置,优化打印输出效果。更新相关方法以提升灵活性和用户体验。
This commit is contained in:
@@ -169,6 +169,16 @@ public static class NativePrintRenderService
|
||||
var bgCss = bg != null ? $"background:{bg};" : string.Empty;
|
||||
var rotCss = rotate != 0 ? $"transform:rotate({rotate}deg);transform-origin:top left;" : string.Empty;
|
||||
|
||||
// ─ 元素级边框(title/subtitle/text/date/pageNo/reportHeader/reportFooter 均支持)─
|
||||
var bWidth = Math.Max(0d, style?["borderWidth"]?.GetValue<double>() ?? 0d);
|
||||
var bColor = ReadAsString(style?["borderColor"], "#222") ?? "#222";
|
||||
string ElemBorderSide(string hideKey) =>
|
||||
bWidth > 0 && !ReadBoolDefault(style?[hideKey], false)
|
||||
? $"{(int)Math.Round(bWidth)}px solid {bColor}" : "none";
|
||||
var elemBorderCss = bWidth > 0
|
||||
? $"border-top:{ElemBorderSide("hideBorderTop")};border-right:{ElemBorderSide("hideBorderRight")};border-bottom:{ElemBorderSide("hideBorderBottom")};border-left:{ElemBorderSide("hideBorderLeft")};"
|
||||
: string.Empty;
|
||||
|
||||
// ─ reportHeader/reportFooter 位置覆盖 ─
|
||||
var isReportHeader = type == "reportHeader";
|
||||
var isReportFooter = type == "reportFooter";
|
||||
@@ -193,7 +203,7 @@ public static class NativePrintRenderService
|
||||
$"top:{renderY.ToString("0.###", CultureInfo.InvariantCulture)}mm;" +
|
||||
$"width:{renderW.ToString("0.###", CultureInfo.InvariantCulture)}mm;" +
|
||||
$"height:{h.ToString("0.###", CultureInfo.InvariantCulture)}mm;" +
|
||||
$"z-index:{zIndex};{bgCss}{rotCss}";
|
||||
$"z-index:{zIndex};{bgCss}{rotCss}{elemBorderCss}";
|
||||
|
||||
return type switch
|
||||
{
|
||||
@@ -332,8 +342,11 @@ public static class NativePrintRenderService
|
||||
var format = ParseBarcodeFormat(ReadAsString(el["format"]));
|
||||
var displayValue = !string.Equals(ReadAsString(el["displayValue"], "true"), "false", StringComparison.OrdinalIgnoreCase);
|
||||
var textAlign = ReadAsString(el["textAlign"], "center")!;
|
||||
var lineWidthPx = Math.Max(1, (int)Math.Round(el["lineWidth"]?.GetValue<double>() ?? 2d));
|
||||
var barHeightPx = Math.Max(10, (int)Math.Round(el["barHeight"]?.GetValue<double>() ?? 60d));
|
||||
var barFontSize = Math.Max(8, (int)Math.Round(el["fontSize"]?.GetValue<double>() ?? 14d));
|
||||
|
||||
var inner = BuildBarcodeSvgInner(value, format, displayValue, textAlign);
|
||||
var inner = BuildBarcodeSvgInner(value, format, displayValue, textAlign, lineWidthPx, barHeightPx, barFontSize);
|
||||
|
||||
var wrapStyle = "display:flex;align-items:center;justify-content:center;overflow:hidden;";
|
||||
|
||||
@@ -461,8 +474,8 @@ public static class NativePrintRenderService
|
||||
/// <summary>
|
||||
/// 表格单元格用:包装统一的 BuildBarcodeSvgInner,保持与独立 barcode 元素一致的视觉比例。
|
||||
/// </summary>
|
||||
private static string BuildBarcodeCellSvg(string value, string? format = null, bool displayValue = true, string? textAlign = null)
|
||||
=> BuildBarcodeSvgInner(value ?? string.Empty, ParseBarcodeFormat(format), displayValue, textAlign);
|
||||
private static string BuildBarcodeCellSvg(string value, string? format = null, bool displayValue = true, string? textAlign = null, int barFontSize = 14)
|
||||
=> BuildBarcodeSvgInner(value ?? string.Empty, ParseBarcodeFormat(format), displayValue, textAlign, 2, 60, barFontSize);
|
||||
|
||||
/// <summary>
|
||||
/// 调用 ZXing.Net 生成条码 SVG 的统一入口:用与 web 端 jsbarcode 默认参数等价的比例
|
||||
@@ -473,7 +486,8 @@ public static class NativePrintRenderService
|
||||
/// textAlign 控制底部文字对齐:center / left / right / justify(两端),通过 SVG
|
||||
/// 后处理修改 ZXing 输出的 <text> 节点 x/text-anchor 实现,与 web 端逻辑同源。
|
||||
/// </summary>
|
||||
private static string BuildBarcodeSvgInner(string value, BarcodeFormat format, bool displayValue, string? textAlign = null)
|
||||
private static string BuildBarcodeSvgInner(string value, BarcodeFormat format, bool displayValue,
|
||||
string? textAlign = null, int lineWidth = 2, int barHeight = 60, int barFontSize = 14)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return string.Empty;
|
||||
try
|
||||
@@ -481,11 +495,9 @@ public static class NativePrintRenderService
|
||||
// moduleCount 仅用于派生稳定的 SVG viewBox 宽度,ZXing 实际按编码后的真实模块数
|
||||
// 1:1 绘制;多给一点宽度不会让条变粗,只会让两侧留白略多。
|
||||
var moduleCount = EstimateBarcodeModuleCount(value, format);
|
||||
const int lineWidth = 2;
|
||||
const int barHeight = 60;
|
||||
var fontFooter = displayValue ? 18 : 0; // jsbarcode 默认 fontSize 14 + 上下 padding ≈ 18px
|
||||
var widthPx = Math.Max(120, moduleCount * lineWidth);
|
||||
var heightPx = barHeight + fontFooter;
|
||||
var fontFooter = displayValue ? Math.Max(8, barFontSize) + 4 : 0; // fontSize + padding ≈ jsbarcode 行为
|
||||
var widthPx = Math.Max(120, moduleCount * lineWidth);
|
||||
var heightPx = Math.Max(10, barHeight) + fontFooter;
|
||||
|
||||
var writer = new BarcodeWriterSvg
|
||||
{
|
||||
@@ -665,10 +677,23 @@ public static class NativePrintRenderService
|
||||
var lineKeys = ResolveFreeTableCellLineStyleKeys(el, cell.Row, cell.Col, rs, cs, rowCount, colCount);
|
||||
var borderCss = BorderSidesToCssFragment(sides, borderWidth, borderColor, lineKeys);
|
||||
|
||||
// 跨列宽度
|
||||
// 跨列/跨行高度(用于自适应字号和内边距)
|
||||
var spanW = 0d;
|
||||
for (var ci = cell.Col; ci < cell.Col + cs && ci < colWidths.Length; ci++)
|
||||
spanW += colWidths[ci];
|
||||
var spanH = 0d;
|
||||
for (var ri = cell.Row; ri < cell.Row + rs && ri < rowHeights.Length; ri++)
|
||||
spanH += rowHeights[ri];
|
||||
|
||||
// 随行高自适应内边距(行越密 padding 越小,防止把行撑高导致底部被裁切)
|
||||
const double pxPerMm96 = 96d / 25.4d;
|
||||
var vPadMm = Math.Max(0.15d, Math.Min(0.8d, spanH * 0.08d));
|
||||
var hPadMm = Math.Max(0.3d, Math.Min(1.2d, vPadMm * 1.6d));
|
||||
|
||||
// 随行高自动收缩字号(与前端 renderFreeTable fitFontSize 逻辑一致)
|
||||
var innerHmm = Math.Max(0.1d, spanH - vPadMm * 2d);
|
||||
var innerHpx = innerHmm * pxPerMm96;
|
||||
var fitFontSize = Math.Max(1d, Math.Min(cell.FontSize, Math.Floor(innerHpx * 0.82d)));
|
||||
|
||||
// 内容渲染(支持所有 contentType)
|
||||
var innerHtml = RenderFreeTableCellContent(cell, data);
|
||||
@@ -678,13 +703,13 @@ public static class NativePrintRenderService
|
||||
var ws = nowrap ? "nowrap" : "normal";
|
||||
var wb = nowrap ? "normal" : "break-all";
|
||||
var ow = nowrap ? "normal" : "anywhere";
|
||||
var lh = nowrap ? $"{rh.ToString("0.###", CultureInfo.InvariantCulture)}mm" : "1.3";
|
||||
var lh = nowrap ? $"{innerHmm.ToString("0.###", CultureInfo.InvariantCulture)}mm" : "1.15";
|
||||
var widthCss = $"width:{spanW.ToString("0.###", CultureInfo.InvariantCulture)}mm;";
|
||||
|
||||
var rsAttr = rs > 1 ? $" rowspan=\"{rs}\"" : string.Empty;
|
||||
var csAttr = cs > 1 ? $" colspan=\"{cs}\"" : string.Empty;
|
||||
|
||||
sb.Append($"<td{rsAttr}{csAttr} style=\"box-sizing:border-box;{borderCss}{widthCss}padding:2mm;text-align:{cell.Align};vertical-align:{cell.VerticalAlign};font-size:{cell.FontSize.ToString("0.###", CultureInfo.InvariantCulture)}px;color:{cell.Color};background:{cell.BackgroundColor};white-space:{ws};word-break:{wb};overflow-wrap:{ow};line-height:{lh};\">");
|
||||
sb.Append($"<td{rsAttr}{csAttr} style=\"box-sizing:border-box;{borderCss}{widthCss}padding:{vPadMm.ToString("0.###", CultureInfo.InvariantCulture)}mm {hPadMm.ToString("0.###", CultureInfo.InvariantCulture)}mm;text-align:{cell.Align};vertical-align:{cell.VerticalAlign};font-size:{fitFontSize.ToString("0.###", CultureInfo.InvariantCulture)}px;color:{cell.Color};background:{cell.BackgroundColor};white-space:{ws};word-break:{wb};overflow-wrap:{ow};line-height:{lh};\">");
|
||||
sb.Append(innerHtml);
|
||||
sb.Append("</td>");
|
||||
}
|
||||
@@ -714,12 +739,16 @@ public static class NativePrintRenderService
|
||||
public bool HideBorderRight { get; init; }
|
||||
public bool HideBorderBottom { get; init; }
|
||||
public bool HideBorderLeft { get; init; }
|
||||
public bool? FillCell { get; init; } // null → true for media content
|
||||
public double ContentScale { get; init; } = 100d;
|
||||
public string? ImageFit { get; init; }
|
||||
public int DecimalPlaces { get; init; } = 2;
|
||||
public string? AmountType { get; init; }
|
||||
public bool AutoWrap { get; init; } = true;
|
||||
public bool? FillCell { get; init; } // null → true for media content
|
||||
public double ContentScale { get; init; } = 100d;
|
||||
public string? ImageFit { get; init; }
|
||||
public int DecimalPlaces { get; init; } = 2;
|
||||
public string? AmountType { get; init; }
|
||||
public bool AutoWrap { get; init; } = true;
|
||||
// 条码专属
|
||||
public string? BarcodeFormat { get; init; }
|
||||
public bool DisplayBarcodeValue { get; init; } = true;
|
||||
public int BarcodeFontSize { get; init; } = 14;
|
||||
}
|
||||
|
||||
private static FreeTableAnchorCell ParseFreeTableCell(JsonObject c)
|
||||
@@ -755,7 +784,10 @@ public static class NativePrintRenderService
|
||||
ImageFit = imgFit,
|
||||
DecimalPlaces = Math.Clamp(c["decimalPlaces"]?.GetValue<int>() ?? 2, 0, 6),
|
||||
AmountType = amtType,
|
||||
AutoWrap = !string.Equals(ReadAsString(c["autoWrap"]), "false", StringComparison.OrdinalIgnoreCase),
|
||||
AutoWrap = !string.Equals(ReadAsString(c["autoWrap"]), "false", StringComparison.OrdinalIgnoreCase),
|
||||
BarcodeFormat = ReadAsString(c["barcodeFormat"]),
|
||||
DisplayBarcodeValue = !string.Equals(ReadAsString(c["displayValue"]), "false", StringComparison.OrdinalIgnoreCase),
|
||||
BarcodeFontSize = Math.Max(8, (int)Math.Round(c["barcodeFontSize"]?.GetValue<double>() ?? 14d)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1029,9 +1061,9 @@ public static class NativePrintRenderService
|
||||
}
|
||||
if (ct == "barcode")
|
||||
{
|
||||
var ws = fillCell ? "100%" : $"{scale.ToString("0", CultureInfo.InvariantCulture)}%";
|
||||
var hs = fillCell ? "100%" : $"{Math.Max(20d, scale * 0.6d).ToString("0", CultureInfo.InvariantCulture)}%";
|
||||
var svg = BuildBarcodeCellSvg(innerArg);
|
||||
var ws = fillCell ? "100%" : $"{scale.ToString("0", CultureInfo.InvariantCulture)}%";
|
||||
var hs = fillCell ? "100%" : $"{Math.Max(20d, scale * 0.6d).ToString("0", CultureInfo.InvariantCulture)}%";
|
||||
var svg = BuildBarcodeCellSvg(innerArg, cell.BarcodeFormat, cell.DisplayBarcodeValue, null, cell.BarcodeFontSize);
|
||||
return $"<div style=\"display:flex;align-items:center;justify-content:center;width:{ws};height:{hs};margin:0 auto;overflow:hidden;\">{svg}</div>";
|
||||
}
|
||||
return EscapeHtml(displayValue);
|
||||
@@ -1191,7 +1223,7 @@ public static class NativePrintRenderService
|
||||
var autoWrap = !string.Equals(ReadAsString(col?["autoWrap"], "true"), "false", StringComparison.OrdinalIgnoreCase);
|
||||
var raw = !string.IsNullOrWhiteSpace(field) ? ResolveField(row, field!) : null;
|
||||
var text = FormatColumnValue(raw, col, contentType);
|
||||
var html = ResolveTableCellInnerHtml(contentType, text);
|
||||
var html = ResolveTableCellInnerHtml(contentType, text, col);
|
||||
|
||||
// 字体:fontFamily / fontColor / fontSize(含 autoFitFont 自适应)
|
||||
var fontFamily = ReadAsString(col?["fontFamily"], "inherit") ?? "inherit";
|
||||
@@ -1568,7 +1600,7 @@ public static class NativePrintRenderService
|
||||
return new List<JsonObject>();
|
||||
}
|
||||
|
||||
private static string ResolveTableCellInnerHtml(string? contentType, string value)
|
||||
private static string ResolveTableCellInnerHtml(string? contentType, string value, JsonNode? col = null)
|
||||
{
|
||||
var t = (contentType ?? "text").Trim().ToLowerInvariant();
|
||||
if (t == "qrcode")
|
||||
@@ -1585,7 +1617,10 @@ public static class NativePrintRenderService
|
||||
}
|
||||
if (t == "barcode")
|
||||
{
|
||||
var svg = BuildBarcodeCellSvg(value);
|
||||
var fmt = ReadAsString(col?["barcodeFormat"]);
|
||||
var dv = !string.Equals(ReadAsString(col?["displayValue"]), "false", StringComparison.OrdinalIgnoreCase);
|
||||
var bFontSize = Math.Max(8, (int)Math.Round(col?["barcodeFontSize"]?.GetValue<double>() ?? 14d));
|
||||
var svg = BuildBarcodeCellSvg(value, fmt, dv, null, bFontSize);
|
||||
return $"<div style=\"display:flex;align-items:center;justify-content:center;width:100%;height:100%;overflow:hidden;\">{svg}</div>";
|
||||
}
|
||||
if (t == "image")
|
||||
|
||||
Reference in New Issue
Block a user