.net core 连接打印机 使用zpl指令打印机,并且位图本地渲染zpl指令

简介:本章主要使用BinaryKits组件将zpl 指令远程连接打印机打印机内容,且通过解析zpl指令在本地软件预览,实时编辑,兼容Windows、Linux。支持中文、英文、特殊字符等,不依赖系统字体。开源、免费、国产化

1.引入核心组件

Install-Package BinaryKits.Zpl.Label 版本3.3.1

Install-Package BinaryKits.Zpl.Viewer 版本1.2.0

我测试1.2.0版本兼容,升级到3.0版本或其他版本,对ZplElementDrawer在进行绘制Draw时,DrawerOptions调用字体文件非public,无法指定字体文件。

各位道友不做文字处理,可以配置服务器字体文件,但是要担心SkiaSharp默认指向。

Install-Package SkiaSharp 版本2.88.8

Install-Package SkiaSharp.HarfBuzz 版本2.88.8

Install-Package SkiaSharp.NativeAssets.Linux.NoDependencies 版本2.88.8

该三个组件必须保持版本一致,不然会报错

错误:SkiaSharp Value cannot be null. (Parameter 'asset')或者依赖项指定错误、资源文件找不到等等

2.项目核心配置

<PublishSingleFile>false</PublishSingleFile>

禁用单文件发布,强制使用项目框架依赖

3.上传页面

Razor代码

html 复制代码
@Html.AppendCssFile("/Areas/ResourceFile/UploadifyForm.css")
<div class="lr-form-layout lr-form-layout-top-bottom">
    <div class="lr-form-layout-header">
        <div id="lr_add_file_btn"><i class="fa fa-cloud-upload"></i>添加文件</div>
    </div>
    <div class="lr-form-layout-body">
        <div class="lr-form-file-queue-wrap" id="lr_form_file_queue">
            <div class="lr-form-file-queue" id="lr_form_file_queue_list">
                
            </div>
            <div class="lr-form-file-queue-bg">
                <h1>试试将电脑里的文件拖拽到此上传</h1>
            </div>
        </div>
    </div>
</div>
<script src="~/Content/webuploader/webuploader.nolog.min.js"></script>
@Html.AppendJsFile("/Areas/Views/UploadifyForm.js")

css代码

css 复制代码
.lr-form-layout-body {
    padding: 0 5px 5px 5px;
}

.lr-form-file-queue-wrap {
    position: relative;
    width: 100%;
    height: 100%;
    border: 1px solid #ddd;
    border-radius: 4px;
}

.lr-form-file-queue {
    position: relative;
}

.lr-form-file-queue-item {
    position: relative;
    width: 100%;
    height: 50px;
    border-bottom: 1px solid #ddd;
    padding-left: 60px;
}

.lr-file-image {
    position: absolute;
    top: 5px;
    left: 5px;
}

    .lr-file-image img {
        width: 40px;
        height: 40px;
    }

.lr-file-name {
    line-height: 50px;
}

.lr-msg {
    position: absolute;
    top: 0;
    right: 5px;
    font-size: 25px;
    line-height: 50px;
}

    .lr-msg .fa-check-circle {
        color: #5cb85c;
    }

    .lr-msg .fa-exclamation-circle {
        color: #b94a48;
    }

.lr-tool-bar {
    position: absolute;
    top: 0;
    right: 35px;
    font-size: 25px;
    line-height: 50px;
    color: #666;
    cursor: pointer;
}

    .lr-tool-bar > i {
        margin-left: 8px;
    }

    .lr-tool-bar .fa-minus-circle {
        color: #b94a48;
    }

    .lr-tool-bar .fa-cloud-download {
        color: #3498db;
    }

.lr-uploader-progress {
    position: absolute;
    bottom: 2px;
    left: 60px;
    height: 4px;
    width: 500px;
    width: calc(100% - 90px);
}

.lr-uploader-progress-bar {
    position: relative;
    height: 100%;
    background-color: #039cfd;
    border-radius: 4px;
}


.lr-form-file-queue-bg {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 300px;
    height: 30px;
    line-height: 30px;
    margin: -15px 0 0 -150px;
}

    .lr-form-file-queue-bg h1 {
        color: #666;
        font-size: 20px;
        font-family: Microsoft Yahei;
        padding-bottom: 2px;
        margin: 0;
    }

/*对百度插件的样式重写*/
.webuploader-pick {
    padding: 8px 15px;
    background-color: #039cfd;
}

.webuploader-pick-hover {
    background-color: #027dcb;
}

js代码

javascript 复制代码
            page.uploader = WebUploader.create({
                auto: true,
                swf: top.$.rootUrl + '/Content/webuploader/Uploader.swf',
                // 文件接收服务端。
                server: top.$.rootUrl + '/UploadifyFile',
                // 选择文件的按钮。可选。
                // 内部根据当前运行是创建,可能是input元素,也可能是flash.
                pick: '#lr_add_file_btn',
                dnd: '#lr_form_file_queue',
                paste: 'document.body',
                accept: {
                    extensions: "zpl"
                },
                multiple: true,
                // 上传参数
                formData: {
                    __RequestVerificationToken: $.lrToken
                }
            });
            page.uploader.on('fileQueued', page.fileQueued);
            page.uploader.on('uploadStart', page.uploadStart);
            page.uploader.on('uploadBeforeSend', page.uploadBeforeSend);
            page.uploader.on('uploadProgress', page.uploadProgress);
            page.uploader.on('uploadSuccess', page.uploadSuccess);
            page.uploader.on('uploadError', page.uploadError);
            page.uploader.on('uploadComplete', page.uploadComplete);
            page.uploader.on('error', page.error);


        }

控制代码

cs 复制代码
        /// <summary>
        /// 上传文件
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult> UploadifyFile()
        {
            try
            {
                //没有文件上传,直接返回
                var Filedata = Request.Form.Files["file"];
                if (Filedata == null || string.IsNullOrEmpty(Filedata.FileName) || Filedata.Length == 0)
                {
                    return NotFound();
                }
                //获取文件完整文件名(包含绝对路径)
                //文件存放路径格式:/TagTemplate/{guid}.{后缀名}
                string fileGuid = Guid.NewGuid().ToString();
                long filesize = Filedata.Length;

                // HttpContext.Request 里拿
                var req = HttpContext.Request;
                // 1. 协议:http / https
                string scheme = req.Scheme;
                // 2. Host(域名+端口) 例:172.16.42.206:8201
                string host = req.Host.ToString();
                // 3. 基础地址(协议+host) 
                string baseUrl = $"{req.Scheme}://{req.Host}";
                string httpUrl = $"{baseUrl}/TagTemplate/{fileGuid}.zpl";

                string fullFileName = Path.Combine(_hostEnvironment.WebRootPath, "TagTemplate", string.Format("{0}.zpl", fileGuid));

                //创建文件夹
                string path = Path.GetDirectoryName(fullFileName);
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                if (!System.IO.File.Exists(fullFileName))
                {
                    //保存文件
                    using (var stream = new FileStream(fullFileName, FileMode.Create))
                    {
                        await Filedata.CopyToAsync(stream);
                    }
                    //文件信息写入数据库
                    FileInfoEntity fileInfoEntity = new FileInfoEntity();
                    fileInfoEntity.Create();
                    fileInfoEntity.F_FileId = fileGuid;
                    if (!string.IsNullOrEmpty(folderId))
                    {
                        fileInfoEntity.F_FolderId = folderId;
                    }
                    else
                    {
                        fileInfoEntity.F_FolderId = "0";
                    }
                    fileInfoEntity.F_FileName = Filedata.FileName;
                    fileInfoEntity.F_EnabledMark = 0;
                    fileInfoEntity.F_FilePath = httpUrl;
                    fileInfoEntity.F_FileSize = filesize.ToString();
                    fileInfoEntity.F_FileExtensions = ".zpl";
                    fileInfoEntity.F_FileType = "zpl";
                    fileInfoBLL.SaveForm("", fileInfoEntity);
                    return Success("上传成功。");
                }
                else
                {
                    return Fail("文件已经存在。");
                }
            }
            catch (Exception ex)
            {
                return Fail(ex.Message);
            }
        }

4.预览页面

Razor代码

html 复制代码
@Html.AppendCssFile("/Areas/Views/PreviewZpl.css")
<div class="container">
    <!-- 顶部工具栏 -->
    <div class="toolbar">
        <span class="file-name" id="fileName"></span>
        <a id="lr_refresh" class="btn btn-default"><i class="fa fa-refresh"></i>&nbsp;刷新</a>
        <a id="lr_print" class="btn btn-default"><i class="fa fa-print"></i>&nbsp;打印</a>
        <a id="lr_save" class="btn btn-default"><i class="fa fa-save">&nbsp;保存</i></a>
    </div>

    <!-- 主体:左编辑,右预览 -->
    <div class="main-content">
        <!-- 左侧 ZPL 编辑器 -->
        <div class="panel">
            <div class="panel-header">
                <i class="layui-icon layui-icon-edit"></i> ZPL 源代码
            </div>
            <textarea id="zplEditor" placeholder="在此输入或编辑ZPL指令..."></textarea>
        </div>

        <!-- 右侧预览 -->
        <div class="panel">
            <div class="panel-header">
                <i class="layui-icon layui-icon-picture"></i> 标签预览
            </div>
            <div class="preview-wrapper" id="previewWrapper">
                <img id="previewImg" />
            </div>
        </div>
    </div>
</div>
@Html.AppendJsFile("/Areas/Views/PreviewZpl.js")

css代码

css 复制代码
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body, html {
    width: 100%;
    height: 100%;
    font-family: "Microsoft YaHei", sans-serif;
    overflow: hidden;
}

.container {
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
}

/* 顶部工具栏 */
.toolbar {
    height: 50px;
    background: #f8f8f8;
    border-bottom: 1px solid #e6e6e6;
    display: flex;
    align-items: center;
    padding: 0 20px;
    gap: 10px;
}

    .toolbar .file-name {
        flex: 1;
        font-size: 14px;
        color: #333;
        font-weight: 500;
    }

    .toolbar .btn {
        padding: 6px 18px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 13px;
        transition: all 0.2s;
    }

.btn-save {
    background: #1E9FFF;
    color: #fff;
}

    .btn-save:hover {
        background: #0d8de6;
    }

.btn-close {
    background: #ff5722;
    color: #fff;
}

    .btn-close:hover {
        background: #e64a19;
    }

.btn-refresh {
    background: #67c23a;
    color: #fff;
}

    .btn-refresh:hover {
        background: #5aaf2f;
    }

/* 主体区域 */
.main-content {
    flex: 1;
    display: flex;
    overflow: hidden;
}

.panel {
    width: 50%;
    height: 100%;
    display: flex;
    flex-direction: column;
    border-right: 1px solid #e6e6e6;
}

    .panel:last-child {
        border-right: none;
    }

.panel-header {
    height: 40px;
    background: #f5f5f5;
    display: flex;
    align-items: center;
    padding: 0 15px;
    font-size: 13px;
    color: #666;
    border-bottom: 1px solid #e6e6e6;
    font-weight: 500;
}

/* 左侧编辑器 */
#zplEditor {
    flex: 1;
    width: 100%;
    padding: 12px;
    border: none;
    outline: none;
    resize: none;
    font-family: "Consolas", "Monaco", "Courier New", monospace;
    font-size: 13px;
    line-height: 1.6;
    background: #1e1e1e;
    color: #d4d4d4;
    tab-size: 4;
}

    #zplEditor::-webkit-scrollbar {
        width: 10px;
        height: 10px;
    }

    #zplEditor::-webkit-scrollbar-track {
        background: #2d2d2d;
    }

    #zplEditor::-webkit-scrollbar-thumb {
        background: #555;
        border-radius: 5px;
    }

        #zplEditor::-webkit-scrollbar-thumb:hover {
            background: #666;
        }

/* 右侧预览区 */
.preview-wrapper {
    flex: 1;
    background: #e0e0e0;
    overflow: auto;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 20px;
}

.preview-canvas {
    background: #fff;
    box-shadow: 0 2px 10px rgba(0,0,0,0.15);
}

.preview-empty {
    color: #999;
    font-size: 14px;
    text-align: center;
    margin-top: 50px;
}

js代码

javascript 复制代码
            // 刷新
            $('#lr_refresh').on('click', function () {
                var imgUrl = top.$.rootUrl + '/GetZplFileImage?zpl=' + $('#zplEditor').val();
                $('#previewImg').attr("src", imgUrl);
            });
            // 打印
            $('#lr_print').on('click', function () {
                var postData = {};
                postData.zpl = $('#zplEditor').val();
                // 调用力软保存表单方法
                $.lrSaveForm(top.$.rootUrl + '/PrintZpl', postData, function (res) {

                }, function (errMsg) {
                    //保存失败回调:不关闭,只提示错误
                    learun.alert.error(errMsg);
                });
            });
            // 保存
            $('#lr_save').on('click', function () {
                var postData = {};
                postData.keyValue = keyValue;
                postData.zpl = $('#zplEditor').val();

                // 调用力软保存表单方法
                $.lrSaveForm(top.$.rootUrl + '//SaveZpl', postData, function (res) {
                    // 刷新父页面表格
                    top.$("#gridtable").trigger("reloadGrid");
                    // 关闭当前弹窗
                    learun.layerClose(window.name);
                }, function (errMsg) {
                    //保存失败回调:不关闭,只提示错误
                    learun.alert.error(errMsg);
                });
            });
        InitData: function () {
            if (!!keyValue) {
                $.lrSetForm(top.$.rootUrl + '//GetZplFileContent?keyValue=' + keyValue, function (data) {
                    $('#zplEditor').val(data.content);
                    var imgUrl = top.$.rootUrl + '/GetZplFileImage?zpl=' + data.content;
                    $('#previewImg').attr("src", imgUrl);
                });
            }
        },

控制器代码

cs 复制代码
        /// <summary>
        /// 获取zpl指令内容
        /// </summary>
        /// <param name="keyValue">keyValue主键</param>
        /// <returns></returns>
        [HttpGet]
        public ActionResult GetZplFileContent(string keyValue)
        {
            var data = fileInfoBLL.GetEntity(keyValue);
            string filename = WebUtility.UrlDecode(data.F_FileName);//客户端保存的文件名  
            string filepath = Path.Combine(_hostEnvironment.WebRootPath, "TagTemplate", string.Format("{0}.zpl", data.F_FileId));

            // 1. 读取文件内容
            string content = System.IO.File.ReadAllText(filepath);
            var jsonData = new
            {
                content = content
            };
            return Success(jsonData);
        }

        /// <summary>
        /// ZPL预览接口
        /// </summary>
        /// <param name="zpl">ZPL字符串</param>
        /// <returns></returns>
        [HttpGet]
        public ActionResult GetZplFileImage(string zpl)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(zpl))
                {
                    return Fail("ZPL指令不能为空");
                }
                var imgBytes = ZplPrintHelper.RenderZplToPng(zpl);
                if (imgBytes == null || imgBytes.Length == 0)
                {
                    return Fail("渲染失败,无标签数据");
                }
                return File(imgBytes, "image/png");
            }
            catch (Exception ex)
            {
                return Fail(ex.Message);
            }
        }

        [HttpPost]
        public ActionResult SaveZpl(string keyValue, string zpl)
        {
            var data = fileInfoBLL.GetEntity(keyValue);
            string filename = WebUtility.UrlDecode(data.F_FileName);//客户端保存的文件名  
            string filepath = Path.Combine(_hostEnvironment.WebRootPath, "TagTemplate", string.Format("{0}.txt", data.F_FileId));
            data.F_ModifyDate = DateTime.Now;
            fileInfoBLL.SaveForm(keyValue, data);
            System.IO.File.WriteAllText(filepath, zpl);
            return Success("保存成功");
        }

        [HttpPost]
        public async Task<ActionResult> PrintZpl(string zpl)
        {
            string Message = string.Empty;
            if (!await ZplPrintHelper.IsPrinterOnlineAsync("192.168.123.100"))
            {
                Message = $"打印机不在线,请检查网络或电源";
                return Fail(Message);
            }
            bool success = await ZplPrintHelper.SendZplAsync(zpl, "192.168.123.100");
            Message = $"打印成功";
            return Success(Message);
        }

5.打印核心处理类

各位道友自己封装吧,我都写在一个类里面

cs 复制代码
    public class ZplPrintHelper
    {
        /// <summary>
        /// 离线ZPL转PNG图片,内网离线,无外网,纯条码/二维码/英文数字
        /// </summary>
        /// <param name="zpl">ZPL指令文本</param>
        /// <returns>png字节数组</returns>
        public static byte[] RenderZplToPng(string zpl, IWebHostEnvironment _hostEnvironment)
        {
            // 正则读取 ^PW(宽度) ^LL(高度)
            int pw = 400;
            int ll = 300;
            var matchPW = Regex.Match(zpl, @"\^PW(\d+)");
            var matchLL = Regex.Match(zpl, @"\^LL(\d+)");
            if (matchPW.Success) pw = int.Parse(matchPW.Groups[1].Value);
            if (matchLL.Success) ll = int.Parse(matchLL.Groups[1].Value);

            // 打印机存储,必须传入,存放图片、下载资源
            IPrinterStorage printerStorage = new PrinterStorage();
            var analyzer = new ZplAnalyzer(printerStorage);
            var analyzeResult = analyzer.Analyze(zpl);

            // 新版构造只需要 printerStorage
            DrawerOptionsHelper.HostEnvironment = _hostEnvironment;
            DrawerOptions opt = new DrawerOptions
            {
                FontLoader = DrawerOptionsHelper.CustomTtfFontLoader,
                OpaqueBackground = true,
                Antialias = true
            };
            var drawer = new ZplElementDrawer(printerStorage, opt);

            foreach (var labelInfo in analyzeResult.LabelInfos)
            {
                byte[] pngBytes = drawer.Draw(labelInfo.ZplElements);
                if (pngBytes == null || pngBytes.Length == 0)
                {
                    LogHelper.Error("RenderZplToPng:drawer.Draw失败,渲染资源缺失", "ZPL");
                    return null;
                }
                else
                {
                    // 裁剪到 ^PW x ^LL 画布
                    pngBytes = CropToLabelSize(pngBytes, pw, ll);
                    return pngBytes;
                }
            }
            return null;
        }

        /// <summary>
        /// 把渲染出来的大图,裁剪居中放到指定宽高的白底画布
        /// </summary>
        private static byte[] CropToLabelSize(byte[] sourcePng, int targetW, int targetH)
        {
            using var srcBmp = SKBitmap.Decode(sourcePng);
            if (srcBmp == null)
            {
                LogHelper.Error("CropToLabelSize:SKBitmap.Decode解码失败,无效PNG", "ZPL");
                return null;
            }

            // 目标画布:严格PW × LL,透明底
            using var destBmp = new SKBitmap(targetW, targetH, SKColorType.Bgra8888, SKAlphaType.Premul);
            using var canvas = new SKCanvas(destBmp);
            canvas.Clear(SKColors.Transparent);

            // =========重点:offsetX=0,offsetY=0!左上角对齐,不居中=========
            // ZPL ^FO(x,y) 就是相对于标签左上角的坐标,居中一定会错位!
            canvas.DrawBitmap(srcBmp, 0, 0);

            using var img = SKImage.FromBitmap(destBmp);
            using var data = img.Encode(SKEncodedImageFormat.Png, 100);
            return data.ToArray();
        }

        /// <summary>
        /// 检测打印机是否在线(能否建立 TCP 连接)
        /// </summary>
        /// <param name="_printerIp">打印机IP</param>
        /// <param name="_port">打印机端口 默认9100</param>
        /// <param name="_timeoutMs">超时</param>
        /// <returns></returns>
        public static async Task<bool> IsPrinterOnlineAsync(string _printerIp,int _port=9100,int _timeoutMs=5000)
        {
            try
            {
                using (var client = new TcpClient())
                {
                    if (!IPAddress.TryParse(_printerIp, out var ipAddress))
                        return false;

                    // 使用 Task.Run 包装 BeginConnect,避免 FromAsync 的陷阱
                    var connectTask = Task.Run(() =>
                    {
                        IAsyncResult ar = client.BeginConnect(ipAddress, _port, null, null);
                        try
                        {
                            // 等待完成
                            ar.AsyncWaitHandle.WaitOne(_timeoutMs);
                            if (ar.IsCompleted)
                            {
                                client.EndConnect(ar); // 安全调用
                                return true;
                            }
                            else
                            {
                                return false; // 超时
                            }
                        }
                        catch (SocketException)
                        {
                            return false;
                        }
                        finally
                        {
                            ar.AsyncWaitHandle.Close();
                        }
                    });

                    var success = await connectTask;
                    return success;
                }
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// 异步发送 ZPL 指令到打印机
        /// </summary>
        /// <param name="zplCommand">完整的 ZPL 指令字符串</param>
        /// <param name="_printerIp">打印机IP</param>
        /// <param name="_port">打印机端口 默认9100</param>
        /// <param name="_timeoutMs">超时</param>
        /// <returns>成功返回 true</returns>
        public static async Task<bool> SendZplAsync(string zplCommand, string _printerIp, int _port = 9100, int _timeoutMs = 5000)
        {
            if (string.IsNullOrWhiteSpace(zplCommand))
                throw new ArgumentException("ZPL 指令不能为空", nameof(zplCommand));

            using (var client = new TcpClient())
            {
                if (!IPAddress.TryParse(_printerIp, out var ipAddress))
                    throw new ArgumentException($"无效的 IP 地址: {_printerIp}", nameof(_printerIp));

                // 使用 Task.Run 在后台线程执行同步阻塞的 WaitOne(避免死锁)
                await Task.Run(() =>
                {
                    IAsyncResult ar = null;
                    try
                    {
                        // 启动异步连接
                        ar = client.BeginConnect(ipAddress, _port, null, null);

                        // 等待连接完成或超时
                        bool completed = ar.AsyncWaitHandle.WaitOne(_timeoutMs);
                        if (!completed)
                        {
                            throw new TimeoutException($"连接打印机 {_printerIp}:{_port} 超时({_timeoutMs}ms)");
                        }

                        // 安全调用 EndConnect(此时 ar != null 且已完成)
                        client.EndConnect(ar);
                    }
                    catch (SocketException ex)
                    {
                        throw new Exception($"无法连接到打印机 {_printerIp}:{_port} - {ex.Message}", ex);
                    }
                    finally
                    {
                        ar?.AsyncWaitHandle?.Close(); // 释放等待句柄
                    }
                });

                // 连接成功,发送数据
                using (var stream = client.GetStream())
                {
                    byte[] data = Encoding.UTF8.GetBytes(zplCommand);
                    await stream.WriteAsync(data, 0, data.Length);
                    await stream.FlushAsync();
                }
            }
            return true;
        }
    }

6.文字处理类

该类主要处理字体文件,不依赖系统字体文件。字体文件各位道友自己找镜像文件吧。

cs 复制代码
    public class DrawerOptionsHelper
    {
        /// <summary>
        /// web下发路径
        /// </summary>
        public static IWebHostEnvironment HostEnvironment = null;
        // 字体缓存,全局复用SKTypeface,防止内存泄漏
        private static readonly ConcurrentDictionary<string, SKTypeface> _fontCache = new();

        /// <summary>
        /// 自定义字体加载:优先读取本地ttf,失败再回退系统字体
        /// fontName:ZPL ^A0 / ^A1 传进来的标识
        /// </summary>
        public static Func<string, SKTypeface> CustomTtfFontLoader = fontName =>
        {
            try
            {
                // 命中缓存直接返回
                if (_fontCache.TryGetValue(fontName, out var cacheTf))
                {
                    return cacheTf;
                }

                SKTypeface typeface = null;
                var fontMgr = SKFontManager.Default;
                var families = fontMgr.FontFamilies.ToList();

                // ========== 【重点】fontName=0 对应 ZPL ^A0,这里加载本地TTF ==========
                if (fontName == "0" || fontName == "A" || !string.IsNullOrEmpty(fontName))
                {
                    // 字体文件路径,可配置,建议绝对路径
                    string ttfPath = Path.Combine(HostEnvironment.WebRootPath, "ZplFonts", "LZHONGHEI.ttf");
                    if (File.Exists(ttfPath))
                    {
                        using var stream = File.OpenRead(ttfPath);
                        typeface = SKTypeface.FromStream(stream);
                    }

                    // TTF加载失败,回退原来系统字体逻辑
                    if (typeface == null)
                    {
                        if (families.Contains("Helvetica"))
                        {
                            typeface = SKTypeface.FromFamilyName("Helvetica", SKFontStyleWeight.Bold, SKFontStyleWidth.SemiCondensed, SKFontStyleSlant.Upright);
                        }
                        else if (families.Contains("Roboto Condensed"))
                        {
                            typeface = SKTypeface.FromFamilyName("Roboto Condensed", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
                        }
                        else if (families.Contains("Swis721 BT"))
                        {
                            typeface = SKTypeface.FromFamilyName("Swis721 BT");
                        }
                        else if (families.Contains("Arial"))
                        {
                            typeface = SKTypeface.FromFamilyName("Arial", SKFontStyleWeight.Bold, SKFontStyleWidth.Condensed, SKFontStyleSlant.Upright);
                        }
                        else
                        {
                            typeface = SKTypeface.FromFamilyName("Helvetica", SKFontStyleWeight.Bold, SKFontStyleWidth.SemiCondensed, SKFontStyleSlant.Upright);
                        }
                    }
                }
                else
                {
                    // 其它字体标识(^A1、^A2等)沿用原有英文等宽逻辑
                    if (families.Contains("DejaVu Sans Mono"))
                    {
                        typeface = SKTypeface.FromFamilyName("DejaVu Sans Mono", SKFontStyleWeight.Normal, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
                    }
                    else if (families.Contains("Courier New"))
                    {
                        typeface = SKTypeface.FromFamilyName("Courier New", SKFontStyleWeight.Medium, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
                    }
                    else
                    {
                        typeface = families.Contains("Courier")
                            ? SKTypeface.FromFamilyName("Courier", SKFontStyleWeight.Medium, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright)
                            : SKTypeface.FromFamilyName("DejaVu Sans Mono", SKFontStyleWeight.Normal, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
                    }
                }

                // 兜底,防止null
                typeface ??= SKTypeface.Default;

                _fontCache.TryAdd(fontName, typeface);
                return typeface;
            }
            catch (Exception ex)
            {
                LogHelper.Error($"DrawerOptionsHelper:CustomTtfFontLoader失败,{ex.Message}", "ZPL");
                return null;
            }
        };
    }

结果展示

编辑模板完成,点击刷新,道友们也可以自行zpl文本改变实时刷新

相关推荐
csdn_aspnet3 小时前
C# .NetCore Swagger ISchemaFilter Long 转 String 防精度丢失
c#·.netcore·swagger
下页、再停留20 小时前
【C#桌面客户端系列学习-11】将SQLite数据库打包到exe里
数据库·c#·visual studio
下页、再停留20 小时前
【C#桌面客户端系列学习-12】封装SQLite数据库基础操作工具类(CURD)
数据库·c#·visual studio
dwm888888881 天前
c# 创建使用本地NuGet包
开发语言·c#
进击的大海贼1 天前
【无标题】
c#
csdn_aspnet1 天前
C# .NetCore Swagger ISchemaFilter 注入示例值、描述
c#·.netcore·swagger
海天鹰1 天前
C#:object.ShortcutKeys 不支持非功能单独按键
c#
下页、再停留1 天前
【C#桌面客户端系列学习-10】连接SQLite数据库
数据库·c#·visual studio