增强条码和自由表格元素的渲染逻辑,支持更多边框样式和参数配置,优化打印输出效果。更新相关方法以提升灵活性和用户体验。

This commit is contained in:
geht
2026-05-13 13:04:31 +08:00
parent 2c8620522b
commit b6e468abfa

View File

@@ -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 输出的 &lt;text&gt; 节点 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")