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指令发送。
- 安装PDF转图像的库:在Xamarin项目中安装
PdfSharp(注意兼容Xamarin平台)。 - 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; } }
- 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(); }
- 修改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
相关产品推荐
相关产品推荐

