You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin Forms蓝牙打印iText7生成PDF至P80D热敏打印机乱码求助

解决Xamarin Forms蓝牙连接P80D热敏打印机打印PDF乱码问题

问题描述

我在Xamarin Forms中通过蓝牙连接P80D热敏打印机,纯文本打印可正常工作,但发送PDF文件时打印出乱码符号。

我的代码

生成PDF的方法

public byte[] Invoice()
{
    using (MemoryStream stream = new MemoryStream())
    {
        //创建构建PDF结构所需的实例
        PdfWriter writer = new PdfWriter(stream);
        PdfDocument pdf = new PdfDocument(writer);
        Document document = new Document(pdf, PageSize.A4);
        document.SetMargins(2, 2, 2, 2);
        PdfFont font = PdfFontFactory.CreateFont(iText.IO.Font.Constants.StandardFonts.HELVETICA);
        PdfFont fontTitulo = PdfFontFactory.CreateFont(iText.IO.Font.Constants.StandardFonts.TIMES_ROMAN);


        //显示企业、发票和客户数据
        Paragraph cabecera = new Paragraph("FACTURA").SetHorizontalAlignment(HorizontalAlignment.CENTER).SetTextAlignment(TextAlignment.CENTER).SetFontSize(11);
        Paragraph saltoLinea = new Paragraph(new Text("\n"));

        string dataQR = "395410026|0|0|0|0|0";
        byte[] qr = generaQR(dataQR);
        ImageData imageDataQR = ImageDataFactory.Create(qr);
        iText.Layout.Element.Image imageQR = new iText.Layout.Element.Image(imageDataQR).ScaleToFit(80f, 80f);


        document.Add(cabecera);
        document.Add(saltoLinea);
        document.Add(imageQR);

        document.Close();

        byte[] pdfBytes = stream.ToArray();

        return pdfBytes;
    }
}

发送至打印机的方法

public async Task Print(string deviceName, byte[] pdfBytes)
{
    using (BluetoothAdapter bluetoothAdapter = BluetoothAdapter.DefaultAdapter)
    {
        BluetoothDevice device = (from bd in bluetoothAdapter?.BondedDevices
                                    where bd?.Name == deviceName
                                    select bd).FirstOrDefault();
        try
        {
            using (BluetoothSocket bluetoothSocket = device?.
                    CreateRfcommSocketToServiceRecord(
                    UUID.FromString("00001101-0000-1000-8000-00805f9b34fb")))
            {
                bluetoothSocket?.Connect();
                bluetoothSocket?.OutputStream.Write(pdfBytes, 0, pdfBytes.Length);
                bluetoothSocket.Close();
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

问题原因

P80D这类热敏打印机不具备PDF解析能力,它只能识别ESC/POS打印指令、纯文本或者原始位图数据。直接发送PDF的二进制字节流,打印机无法解析PDF的文件结构,会把这些字节当作无意义的ASCII字符打印,导致乱码。


解决方案

方案1:将PDF转换为位图,再用ESC/POS指令打印

热敏打印机支持打印黑白位图,先把PDF转成适配打印机纸宽的位图,再封装成ESC/POS指令发送。

  1. 安装PDF转图像的库:在Xamarin项目中安装PdfSharp(注意兼容Xamarin平台)。
  2. PDF转Bitmap:
public Bitmap PdfToBitmap(byte[] pdfBytes)
{
    using (MemoryStream ms = new MemoryStream(pdfBytes))
    {
        PdfDocument document = PdfReader.Open(ms, PdfDocumentOpenMode.Import);
        PdfPage page = document.Pages[0]; // 仅处理第一页,多页需循环遍历
        XGraphics gfx = XGraphics.FromPdfPage(page);
        Bitmap bitmap = new Bitmap((int)page.Width.Point, (int)page.Height.Point);
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.Clear(Color.White);
            gfx.DrawImage(XImage.FromGdiPlusImage(bitmap), 0, 0);
        }
        return bitmap;
    }
}
  1. Bitmap转ESC/POS指令:
public byte[] BitmapToEscPos(Bitmap bitmap)
{
    // 适配80mm纸宽(对应384像素)
    int targetWidth = 384;
    float scale = (float)targetWidth / bitmap.Width;
    int targetHeight = (int)(bitmap.Height * scale);
    Bitmap resizedBitmap = new Bitmap(bitmap, targetWidth, targetHeight);

    // 转为黑白单色位图
    Bitmap monochromeBitmap = new Bitmap(targetWidth, targetHeight);
    using (Graphics g = Graphics.FromImage(monochromeBitmap))
    {
        ColorMatrix colorMatrix = new ColorMatrix(
            new float[][]
            {
                new float[] {.3f, .3f, .3f, 0, 0},
                new float[] {.59f, .59f, .59f, 0, 0},
                new float[] {.11f, .11f, .11f, 0, 0},
                new float[] {0, 0, 0, 1, 0},
                new float[] {0, 0, 0, 0, 1}
            });
        ImageAttributes attributes = new ImageAttributes();
        attributes.SetColorMatrix(colorMatrix);
        g.DrawImage(resizedBitmap, new Rectangle(0, 0, targetWidth, targetHeight), 0, 0, resizedBitmap.Width, resizedBitmap.Height, GraphicsUnit.Pixel, attributes);
    }

    // 生成ESC/POS打印指令
    MemoryStream ms = new MemoryStream();
    ms.Write(new byte[] { 0x1B, 0x40 }, 0, 2); // 初始化打印机
    ms.Write(new byte[] { 0x1B, 0x2A, 0x01 }, 0, 3); // 位图打印模式
    ms.Write(BitConverter.GetBytes((short)targetWidth), 0, 2);
    ms.Write(BitConverter.GetBytes((short)targetHeight), 0, 2);

    // 逐行写入图像数据
    for (int y = 0; y < targetHeight; y++)
    {
        for (int x = 0; x < targetWidth; x += 8)
        {
            byte b = 0;
            for (int i = 0; i < 8 && x + i < targetWidth; i++)
            {
                Color pixel = monochromeBitmap.GetPixel(x + i, y);
                if (pixel.GetBrightness() < 0.5) // 黑色像素标记
                {
                    b |= (byte)(0x80 >> i);
                }
            }
            ms.WriteByte(b);
        }
    }
    ms.Write(new byte[] { 0x0A, 0x0A }, 0, 2); // 换行

    return ms.ToArray();
}
  1. 修改Print方法:
public async Task Print(string deviceName, byte[] pdfBytes)
{
    Bitmap pdfBitmap = PdfToBitmap(pdfBytes);
    byte[] printData = BitmapToEscPos(pdfBitmap);

    using (BluetoothAdapter bluetoothAdapter = BluetoothAdapter.DefaultAdapter)
    {
        BluetoothDevice device = (from bd in bluetoothAdapter?.BondedDevices
                                    where bd?.Name == deviceName
                                    select bd).FirstOrDefault();
        try
        {
            using (BluetoothSocket bluetoothSocket = device?.
                    CreateRfcommSocketToServiceRecord(
                    UUID.FromString("00001101-0000-1000-8000-00805f9b34fb")))
            {
                bluetoothSocket?.Connect();
                bluetoothSocket?.OutputStream.Write(printData, 0, printData.Length);
                bluetoothSocket.Close();
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

方案2:直接用ESC/POS指令构建打印内容(更高效)

你的PDF内容仅包含标题和二维码,完全可以跳过PDF生成,直接用ESC/POS指令构建打印内容,避免格式转换:

public byte[] BuildInvoiceEscPos()
{
    MemoryStream ms = new MemoryStream();
    ms.Write(new byte[] { 0x1B, 0x40 }, 0, 2); // 初始化打印机

    // 居中对齐+放大字体
    ms.Write(new byte[] { 0x1B, 0x61, 0x01 }, 0, 3);
    ms.Write(new byte[] { 0x1B, 0x21, 0x10 }, 0, 3);
    // 打印标题
    byte[] titleBytes = Encoding.Default.GetBytes("FACTURA\n");
    ms.Write(titleBytes, 0, titleBytes.Length);

    // 恢复默认字体
    ms.Write(new byte[] { 0x1B, 0x21, 0x00 }, 0, 3);
    // 生成并打印二维码
    string dataQR = "395410026|0|0|0|0|0";
    byte[] qrBytes = GenerateQrEscPos(dataQR);
    ms.Write(qrBytes, 0, qrBytes.Length);

    // 换行+切纸
    ms.Write(new byte[] { 0x1B, 0x64, 0x03 }, 0, 3); // 换行3行
    ms.Write(new byte[] { 0x1D, 0x56, 0x00 }, 0, 3); // 自动切纸

    return ms.ToArray();
}

// 生成ESC/POS二维码指令
public byte[] GenerateQrEscPos(string qrData)
{
    MemoryStream ms = new MemoryStream();
    ms.Write(new byte[] { 0x1D, 0x6B, 0x04 }, 0, 3); // 初始化二维码
    ms.Write(new byte[] { 0x1D, 0x6B, 0x03, 0x05 }, 0, 4); // 设置二维码大小(5为适中)
    // 写入数据长度
    short dataLen = (short)qrData.Length;
    ms.Write(new byte[] { 0x1D, 0x6B, 0x02, (byte)(dataLen & 0xFF), (byte)(dataLen >> 8) }, 0, 5);
    // 写入二维码数据
    byte[] dataBytes = Encoding.Default.GetBytes(qrData);
    ms.Write(dataBytes, 0, dataBytes.Length);
    ms.Write(new byte[] { 0x1D, 0x6B, 0x01 }, 0, 3); // 打印二维码
    return ms.ToArray();
}

调用时直接传入BuildInvoiceEscPos()返回的字节流即可。


内容的提问来源于stack exchange,提问作者Axel Eddy Martinez Ayala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 15:10:57