diff --git a/src/Oms.Application/Labeling/Hiprint/LabelPdfFontRegistrar.cs b/src/Oms.Application/Labeling/Hiprint/LabelPdfFontRegistrar.cs new file mode 100644 index 0000000..ee65228 --- /dev/null +++ b/src/Oms.Application/Labeling/Hiprint/LabelPdfFontRegistrar.cs @@ -0,0 +1,88 @@ +using QuestPDF.Drawing; + +namespace Oms.Application.Labeling.Hiprint; + +/// +/// 面单 PDF 中文字体:QuestPDF 默认字体无 CJK,不注册会显示 □ / ?。 +/// +public static class LabelPdfFontRegistrar +{ + public const string FamilyName = "OmsLabelSans"; + + private static int _state; // 0=未试 1=成功 2=失败 + private static string? _sourcePath; + + /// + /// 确保已注册可用中文字体;成功返回 ,否则 null(仅西文)。 + /// + public static string? EnsureRegistered() + { + var state = Volatile.Read(ref _state); + if (state == 1) + { + return FamilyName; + } + + if (state == 2) + { + return null; + } + + foreach (var path in EnumerateCandidates()) + { + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + { + continue; + } + + try + { + using var stream = File.OpenRead(path); + FontManager.RegisterFontWithCustomName(FamilyName, stream); + _sourcePath = path; + Interlocked.Exchange(ref _state, 1); + return FamilyName; + } + catch + { + // 试下一个候选 + } + } + + Interlocked.Exchange(ref _state, 2); + return null; + } + + /// 已成功注册时的字体文件路径(诊断用)。 + public static string? RegisteredSourcePath => _sourcePath; + + private static IEnumerable EnumerateCandidates() + { + var fromEnv = Environment.GetEnvironmentVariable("OMS_LABEL_FONT"); + if (!string.IsNullOrWhiteSpace(fromEnv)) + { + yield return fromEnv.Trim(); + } + + var baseDir = AppContext.BaseDirectory; + yield return Path.Combine(baseDir, "Fonts", "NotoSansSC-Regular.otf"); + yield return Path.Combine(baseDir, "Fonts", "NotoSansSC-Regular.ttf"); + yield return Path.Combine(baseDir, "Fonts", "NotoSansCJKsc-Regular.otf"); + + // Windows + var winFonts = Environment.GetFolderPath(Environment.SpecialFolder.Fonts); + if (!string.IsNullOrWhiteSpace(winFonts)) + { + yield return Path.Combine(winFonts, "msyh.ttc"); + yield return Path.Combine(winFonts, "msyhbd.ttc"); + yield return Path.Combine(winFonts, "simhei.ttf"); + yield return Path.Combine(winFonts, "simsun.ttc"); + } + + // Debian/Ubuntu(Dockerfile 安装 fonts-noto-cjk) + yield return "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"; + yield return "/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf"; + yield return "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc"; + yield return "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"; + } +} diff --git a/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs b/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs index 7d265a6..3472a49 100644 --- a/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs +++ b/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs @@ -23,6 +23,8 @@ public sealed class QuestPdfLabelRenderer(HiprintTemplateParser parser) : ILabel CancellationToken cancellationToken = default) { EnsureLicense(); + // SKU 名称等含中文;未注册 CJK 字体会输出 □ + _ = LabelPdfFontRegistrar.EnsureRegistered(); cancellationToken.ThrowIfCancellationRequested(); if (printDataPages == null || printDataPages.Count == 0) @@ -90,6 +92,12 @@ public sealed class QuestPdfLabelRenderer(HiprintTemplateParser parser) : ILabel .FontSize(text.FontSizePt) .FontColor(ParseColor(text.ColorHex)); + var fontFamily = LabelPdfFontRegistrar.EnsureRegistered(); + if (!string.IsNullOrEmpty(fontFamily)) + { + span.FontFamily(fontFamily); + } + if (text.Bold) { span.Bold(); diff --git a/src/Oms.Application/Labeling/InboundBoxLabelPrintDataBuilder.cs b/src/Oms.Application/Labeling/InboundBoxLabelPrintDataBuilder.cs index 31e3d23..c55b6a2 100644 --- a/src/Oms.Application/Labeling/InboundBoxLabelPrintDataBuilder.cs +++ b/src/Oms.Application/Labeling/InboundBoxLabelPrintDataBuilder.cs @@ -61,6 +61,7 @@ public static class InboundBoxLabelPrintDataBuilder ["boxId"] = box.Id, ["boxSeqNo"] = box.BoxSeqNo, + ["boxCount"] = pageCount, ["markBoxNo"] = box.MarkBoxNo, ["barcode"] = barcode, @@ -103,6 +104,7 @@ public static class InboundBoxLabelPrintDataBuilder ["remark"] = null, ["boxId"] = 1, ["boxSeqNo"] = "1", + ["boxCount"] = 2, ["markBoxNo"] = "BOX-001", ["barcode"] = "BOX-001", ["warehouseName"] = "SZ-01", diff --git a/src/Oms.Application/Labeling/LabelFieldDictionary.cs b/src/Oms.Application/Labeling/LabelFieldDictionary.cs index 410535c..58b9f1c 100644 --- a/src/Oms.Application/Labeling/LabelFieldDictionary.cs +++ b/src/Oms.Application/Labeling/LabelFieldDictionary.cs @@ -85,6 +85,7 @@ public static class LabelFieldDictionary Field("markReference", "唛头参考"), Field("markBoxNo", "箱唛号"), Field("boxSeqNo", "箱序号"), + Field("boxCount", "箱子总数"), Field("barcode", "条码内容"), Field("boxIndexText", "箱序页码"), Field("skuSummary", "SKU 摘要"), diff --git a/src/SimpleAdmin.Api/Dockerfile b/src/SimpleAdmin.Api/Dockerfile index 0d22ae1..d209edf 100644 --- a/src/SimpleAdmin.Api/Dockerfile +++ b/src/SimpleAdmin.Api/Dockerfile @@ -24,6 +24,10 @@ RUN dotnet publish "SimpleAdmin.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime WORKDIR /app +# 面单 QuestPDF 需 CJK 字体,否则中文 SKU 等渲染为 □ +RUN apt-get update \ + && apt-get install -y --no-install-recommends fonts-noto-cjk \ + && rm -rf /var/lib/apt/lists/* COPY --from=build /app/publish . # Local 存储落盘 wwwroot;非 root 用户需可写 uploads(Provider=Cos 时可不依赖此目录) RUN mkdir -p /app/wwwroot/uploads \