admin/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs
zzh 8a1649ea66
All checks were successful
Build and Deploy / deploy (push) Successful in 1m13s
feat: 入库箱唛/SKU 标签打印与订单改销售产品
支持按 box/sku 模板生成 PDF;待支付前可改产品并自动重排货重算费。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 18:39:07 +08:00

190 lines
5.8 KiB
C#

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace Oms.Application.Labeling.Hiprint;
/// <summary>
/// QuestPDF 实现的面单渲染器。
/// </summary>
public sealed class QuestPdfLabelRenderer(HiprintTemplateParser parser) : ILabelPdfRenderer
{
private static int _licenseConfigured;
public Task<byte[]> RenderAsync(
string templateJson,
IReadOnlyDictionary<string, object?> printData,
CancellationToken cancellationToken = default) =>
RenderManyAsync(templateJson, [printData], cancellationToken);
public Task<byte[]> RenderManyAsync(
string templateJson,
IReadOnlyList<IReadOnlyDictionary<string, object?>> printDataPages,
CancellationToken cancellationToken = default)
{
EnsureLicense();
cancellationToken.ThrowIfCancellationRequested();
if (printDataPages == null || printDataPages.Count == 0)
{
throw new ArgumentException("printDataPages 不能为空", nameof(printDataPages));
}
if (string.IsNullOrWhiteSpace(templateJson))
{
throw new ArgumentException("templateJson 不能为空", nameof(templateJson));
}
var bytes = Document.Create(container =>
{
for (var i = 0; i < printDataPages.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var pageIr = parser.Parse(templateJson, printDataPages[i]);
var pageWidthPt = MmToPt(pageIr.WidthMm);
var pageHeightPt = MmToPt(pageIr.HeightMm);
container.Page(page =>
{
page.Size(pageWidthPt, pageHeightPt);
page.Margin(0);
page.Content().Layers(layers =>
{
layers.PrimaryLayer().Background(Colors.White);
foreach (var op in pageIr.Ops)
{
DrawOp(layers, op);
}
});
});
}
}).GeneratePdf();
return Task.FromResult(bytes);
}
private static void DrawOp(LayersDescriptor layers, LabelDrawOp op)
{
switch (op)
{
case LabelTextOp text:
{
var box = layers.Layer()
.Unconstrained()
.TranslateX(text.LeftPt)
.TranslateY(text.TopPt)
.Width(text.WidthPt)
.Height(text.HeightPt);
var aligned = text.Align switch
{
"center" => box.AlignCenter(),
"right" or "end" => box.AlignRight(),
_ => box.AlignLeft()
};
var span = aligned
.AlignTop()
.Text(text.Text)
.FontSize(text.FontSizePt)
.FontColor(ParseColor(text.ColorHex));
if (text.Bold)
{
span.Bold();
}
break;
}
case LabelBarcodeOp barcode:
{
var pxW = Math.Max(40, (int)(barcode.WidthPt * 2));
var pxH = Math.Max(40, (int)(barcode.HeightPt * 2));
var png = BarcodeImageGenerator.CreateBarcodePng(barcode.Content, pxW, pxH, barcode.QrCode);
layers.Layer()
.Unconstrained()
.TranslateX(barcode.LeftPt)
.TranslateY(barcode.TopPt)
.Width(barcode.WidthPt)
.Height(barcode.HeightPt)
.Image(png)
.FitArea();
break;
}
case LabelLineOp line:
{
var w = line.Vertical ? Math.Max(line.ThicknessPt, 0.5f) : line.WidthPt;
var h = line.Vertical ? line.HeightPt : Math.Max(line.ThicknessPt, 0.5f);
layers.Layer()
.Unconstrained()
.TranslateX(line.LeftPt)
.TranslateY(line.TopPt)
.Width(w)
.Height(h)
.Background(ParseColor(line.ColorHex));
break;
}
case LabelRectOp rect:
layers.Layer()
.Unconstrained()
.TranslateX(rect.LeftPt)
.TranslateY(rect.TopPt)
.Width(rect.WidthPt)
.Height(rect.HeightPt)
.BorderColor(ParseColor(rect.BorderColorHex))
.Border(rect.BorderWidthPt);
break;
case LabelImageOp image when !string.IsNullOrWhiteSpace(image.Src) && File.Exists(image.Src):
layers.Layer()
.Unconstrained()
.TranslateX(image.LeftPt)
.TranslateY(image.TopPt)
.Width(image.WidthPt)
.Height(image.HeightPt)
.Image(image.Src)
.FitArea();
break;
}
}
private static void EnsureLicense()
{
if (Interlocked.Exchange(ref _licenseConfigured, 1) == 1)
{
return;
}
QuestPDF.Settings.License = LicenseType.Community;
}
private static float MmToPt(float mm) => mm * 72f / 25.4f;
private static Color ParseColor(string? hex)
{
if (string.IsNullOrWhiteSpace(hex))
{
return Colors.Black;
}
var value = hex.Trim();
if (!value.StartsWith('#'))
{
value = "#" + value;
}
try
{
return Color.FromHex(value);
}
catch
{
return Colors.Black;
}
}
}