android打印机--小票打印格式及模板设置

ZhangJun      2022-02-08     233

关键词:

小票打印就是向打印设备发送控制打印格式的指令集,而这些打印格式需要去查询对应打印机的API文档,这里我把常用的api给封装了一下

  • 文字对齐方式
  • 打印字体大小
  • 字体是否加粗
  • 打印二维码
  • 打印条形码
  • 切纸
  • 打开钱箱
  • 字符串转字节数组
  • 字符拼接

PrintFormatUtils.java

/**
 * 打印格式
 * Created by john on 17-3-23.
 */

public class PrintFormatUtils {
    // 对齐方式
    public static final int ALIGN_LEFT = 0;     // 靠左
    public static final int ALIGN_CENTER = 1;   // 居中
    public static final int ALIGN_RIGHT = 2;    // 靠右

    //字体大小
    public static final int FONT_NORMAL = 0;    // 正常
    public static final int FONT_MIDDLE = 1;    // 中等
    public static final int FONT_BIG = 2;       // 大

    //加粗模式
    public static final int FONT_BOLD = 0;              // 字体加粗
    public static final int FONT_BOLD_CANCEL = 1;       // 取消加粗

    /**
     * 打印二维码
     * @param qrCode
     * @return
     */
    public static String getQrCodeCmd(String qrCode) {
        byte[] data;
        int store_len = qrCode.length() + 3;
        byte store_pL = (byte) (store_len % 256);
        byte store_pH = (byte) (store_len / 256);

        // QR Code: Select the model
        //              Hex     1D      28      6B      04      00      31      41      n1(x32)     n2(x00) - size of model
        // set n1 [49 x31, model 1] [50 x32, model 2] [51 x33, micro qr code]
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=140
        byte[] modelQR = {(byte)0x1d, (byte)0x28, (byte)0x6b, (byte)0x04, (byte)0x00, (byte)0x31, (byte)0x41, (byte)0x32, (byte)0x00};

        // QR Code: Set the size of module
        // Hex      1D      28      6B      03      00      31      43      n
        // n depends on the printer
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=141
        byte[] sizeQR = {(byte)0x1d, (byte)0x28, (byte)0x6b, (byte)0x03, (byte)0x00, (byte)0x31, (byte)0x43, (byte)0x08};

        //          Hex     1D      28      6B      03      00      31      45      n
        // Set n for error correction [48 x30 -> 7%] [49 x31-> 15%] [50 x32 -> 25%] [51 x33 -> 30%]
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=142
        byte[] errorQR = {(byte)0x1d, (byte)0x28, (byte)0x6b, (byte)0x03, (byte)0x00, (byte)0x31, (byte)0x45, (byte)0x31};

        // QR Code: Store the data in the symbol storage area
        // Hex      1D      28      6B      pL      pH      31      50      30      d1...dk
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=143
        //                        1D          28          6B         pL          pH  cn(49->x31) fn(80->x50) m(48->x30) d1…dk
        byte[] storeQR = {(byte)0x1d, (byte)0x28, (byte)0x6b, store_pL, store_pH, (byte)0x31, (byte)0x50, (byte)0x30};

        // QR Code: Print the symbol data in the symbol storage area
        // Hex      1D      28      6B      03      00      31      51      m
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=144
        byte[] printQR = {(byte)0x1d, (byte)0x28, (byte)0x6b, (byte)0x03, (byte)0x00, (byte)0x31, (byte)0x51, (byte)0x30};

        data = byteMerger(modelQR, sizeQR);
        data = byteMerger(data, errorQR);
        data = byteMerger(data, storeQR);
        data = byteMerger(data, qrCode.getBytes());
        data = byteMerger(data, printQR);

        return new String(data);
    }

    /**
     * 打印条码
     * @param barcode
     * @return
     */
    public static String getBarcodeCmd(String barcode) {
        // 打印 Code-128 条码时需要使用字符集前缀
        // "{A" 表示大写字母
        // "{B" 表示所有字母,数字,符号
        // "{C" 表示数字,可以表示 00 - 99 的范围


        byte[] data;

        String btEncode;

        if (barcode.length() < 18) {
            // 字符长度小于15的时候直接输出字符串
            btEncode = "{B" + barcode;
        } else {
            // 否则做一点优化

            int startPos = 0;
            btEncode = "{B";

            for (int i = 0; i < barcode.length(); i++) {
                char curChar = barcode.charAt(i);

                if (curChar < 48 || curChar > 57 || i == (barcode.length() - 1)) {
                    // 如果是非数字或者是最后一个字符

                    if (i - startPos >= 10) {
                        if (startPos == 0) {
                            btEncode = "";
                        }

                        btEncode += "{C";

                        boolean isFirst = true;
                        int numCode = 0;

                        for (int j = startPos; j < i; j++) {

                            if (isFirst) { // 处理第一位
                                numCode = (barcode.charAt(j) - 48) * 10;
                                isFirst = false;
                            } else { // 处理第二位
                                numCode += (barcode.charAt(j) - 48);
                                btEncode += (char) numCode;
                                isFirst = true;
                            }

                        }

                        btEncode += "{B";

                        if (!isFirst) {
                            startPos = i - 1;
                        } else {
                            startPos = i;
                        }
                    }

                    for (int k = startPos; k <= i; k++) {
                        btEncode += barcode.charAt(k);
                    }
                    startPos = i + 1;
                }

            }
        }


        // 设置 HRI 的位置,02 表示下方
        byte[] hriPosition = {(byte) 0x1d, (byte) 0x48, (byte) 0x02};
        // 最后一个参数表示宽度 取值范围 1-6 如果条码超长则无法打印
        byte[] width = {(byte) 0x1d, (byte) 0x77, (byte) 0x02};
        byte[] height = {(byte) 0x1d, (byte) 0x68, (byte) 0xfe};
        // 最后两个参数 73 : CODE 128 || 编码的长度
        byte[] barcodeType = {(byte) 0x1d, (byte) 0x6b, (byte) 73, (byte) btEncode.length()};
        byte[] print = {(byte) 10, (byte) 0};

        data = PrintFormatUtils.byteMerger(hriPosition, width);
        data = PrintFormatUtils.byteMerger(data, height);
        data = PrintFormatUtils.byteMerger(data, barcodeType);
        data = PrintFormatUtils.byteMerger(data, btEncode.getBytes());
        data = PrintFormatUtils.byteMerger(data, print);

        return new String(data);
    }

    /**
     * 切纸
     * @return
     */
    public static String getCutPaperCmd() {
        // 走纸并切纸,最后一个参数控制走纸的长度
        byte[] data = {(byte) 0x1d, (byte) 0x56, (byte) 0x42, (byte) 0x15};

        return new String(data);
    }

    /**
     * 对齐方式
     * @param alignMode
     * @return
     */
    public static String getAlignCmd(int alignMode) {
        byte[] data = {(byte) 0x1b, (byte) 0x61, (byte) 0x0};
        if (alignMode == ALIGN_LEFT) {
            data[2] = (byte) 0x00;
        } else if (alignMode == ALIGN_CENTER) {
            data[2] = (byte) 0x01;
        } else if (alignMode == ALIGN_RIGHT) {
            data[2] = (byte) 0x02;
        }

        return new String(data);
    }

    /**
     * 字体大小
     * @param fontSize
     * @return
     */
    public static String getFontSizeCmd(int fontSize) {
        byte[] data = {(byte) 0x1d, (byte) 0x21, (byte) 0x0};
        if (fontSize == FONT_NORMAL) {
            data[2] = (byte) 0x00;
        } else if (fontSize == FONT_MIDDLE) {
            data[2] = (byte) 0x01;
        } else if (fontSize == FONT_BIG) {
            data[2] = (byte) 0x11;
        }

        return new String(data);
    }

    /**
     * 加粗模式
     * @param fontBold
     * @return
     */
    public static String getFontBoldCmd(int fontBold) {
        byte[] data = {(byte) 0x1b, (byte) 0x45, (byte) 0x0};

        if (fontBold == FONT_BOLD) {
            data[2] = (byte) 0x01;
        } else if (fontBold == FONT_BOLD_CANCEL) {
            data[2] = (byte) 0x00;
        }

        return new String(data);
    }

    /**
     * 打开钱箱
     * @return
     */
    public static String getOpenDrawerCmd() {
        byte[] data = new byte[4];
        data[0] = 0x10;
        data[1] = 0x14;
        data[2] = 0x00;
        data[3] = 0x00;

        return new String(data);
    }

    /**
     * 字符串转字节数组
     * @param str
     * @return
     */
    public static byte[] stringToBytes(String str) {
        byte[] data = null;

        try {
            byte[] strBytes = str.getBytes("utf-8");

            data = (new String(strBytes, "utf-8")).getBytes("gbk");
        } catch (UnsupportedEncodingException exception) {
            exception.printStackTrace();
        }

        return data;
    }

    /**
     * 字节数组合并
     * @param bytesA
     * @param bytesB
     * @return
     */
    public static byte[] byteMerger(byte[] bytesA, byte[] bytesB) {
        byte[] bytes = new byte[bytesA.length + bytesB.length];
        System.arraycopy(bytesA, 0, bytes, 0, bytesA.length);
        System.arraycopy(bytesB, 0, bytes, bytesA.length, bytesB.length);
        return bytes;
    }
}

有了打印格式,还要对具体的打印小票设置打印模板,主要就是利用上面的打印格式工具类,进行字符或字符串拼接,设置文字间空格的长度,以及使用换行符换行等。

有些小票打印的内容有可能是通用的,比如底部结束语–可能是公司宣传语或广告语,这些内容是否展示需要根据具体需求加以控制,还有二维码、条形码打印,是否切纸等需要根据实际场景取舍,所以最好封装一个打印配置类,以控制打印内容显示。

/**
 * 打印模板
 */
public class PrintContract {

    /**
     * 打印内容
     */
    public static StringBuilder createXxTxt(String ...) {

        StringBuilder builder = new StringBuilder();

        //设置大号字体以及加粗
        builder.append(PrintFormatUtils.getFontSizeCmd(PrintFormatUtils.FONT_BIG));
        builder.append(PrintFormatUtils.getFontBoldCmd(PrintFormatUtils.FONT_BOLD));

        // 标题
        builder.append("Title");
       //换行,调用次数根据换行数来控制
        addLineSeparator(builder);

        //设置普通字体大小、不加粗
        builder.append(PrintFormatUtils.getFontSizeCmd(PrintFormatUtils.FONT_NORMAL));
        builder.append(PrintFormatUtils.getFontBoldCmd(PrintFormatUtils.FONT_BOLD_CANCEL));

        //内容
        ......

        //设置某两列文字间空格数, x需要计算出来
        addIdenticalStrToStringBuilder(builder, x, " ");

        //切纸
        builder.append(PrintFormatUtils.getCutPaperCmd());

        return builder;
    }

    /**
     * 向StringBuilder中添加指定数量的相同字符
     *
     * @param printCount   添加的字符数量
     * @param identicalStr 添加的字符
     */

    private static void addIdenticalStrToStringBuilder(StringBuilder builder, int printCount, String identicalStr) {
        for (int i = 0; i < printCount; i++) {
            builder.append(identicalStr);
        }
    }

    /**
     * 根据字符串截取前指定字节数,按照GBK编码进行截取
     *
     * @param str 原字符串
     * @param len 截取的字节数
     * @return 截取后的字符串
     */
    private static String subStringByGBK(String str, int len) {
        String result = null;
        if (str != null) {
            try {
                byte[] a = str.getBytes("GBK");
                if (a.length <= len) {
                    result = str;
                } else if (len > 0) {
                    result = new String(a, 0, len, "GBK");
                    int length = result.length();
                    if (str.charAt(length - 1) != result.charAt(length - 1)) {
                        if (length < 2) {
                            result = null;
                        } else {
                            result = result.substring(0, length - 1);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 添加换行符
     */
    private static void addLineSeparator(StringBuilder builder) {
        builder.append("
");
    }

    /**
     * 在GBK编码下,获取其字符串占据的字符个数
     */
    private static int getCharCountByGBKEncoding(String text) {
        try {
            return text.getBytes("GBK").length;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


   /**
    * 打印相关配置
    */
    public static class PrintConfig {
        public int maxLength = 30;

        public boolean printBarcode = false;  // 打印条码
        public boolean printQrCode = false;   // 打印二维码
        public boolean printEndText = true;   // 打印结束语
        public boolean needCutPaper = false;  // 是否切纸
    }

}

有了打印模板,接下来就是调用打印设备打印方法发送打印指令

//调用打印机打印方法,传入上面某个小票打印模板返回的字符串
String str = PrintContract.createXxTxt(...);
printer.print(str, null);

//打开钱箱方法
printer.print(PrintFormatUtils.getOpenDrawerCmd(), null);

浏览器打印58小票方法及限制

浏览器打印58小票浏览器端的打印是极其普遍的一个应用场景,如票据、OA管理、应用文档笔记等的打印。随着基于云端管理的web应用越来越广泛,对于浏览器的打印需求会越来越多。小票打印就是近年来的一个新的浏览器打印... 查看详情

crt100中心定位好但是电脑小票数值低

参考技术A要根据您的机型和软件配套设置。一般在管理软件里面---系统设置(或者前台设置)---外接设备设置(或打印机设置)---选择打印机---选择打印格式---保存设置。如果还是不行,可以给我留言 查看详情

关于一体机打印新加菜按钮更改为下单小票打印设置faq(适用正餐6.0.1.0+,轻餐4.0.6.2+)

适用版本:正餐6.0.1.0+,轻餐4.0.6.2+实际场景:更新后小票设置中的打印新加菜按钮更换为下单小票打印设置,更换后,设置中,有3个选项:1.仅打印新加菜  (选中后,订单加菜后前台小票只打印新点的菜品,之前订单... 查看详情

优库蓝牙小票打印机怎么弄

...术A先安装好纸并开机,然后在手机设置上开启蓝牙。1.将打印机装好打印纸,确保出纸方向是从上方出来,然后接通电源开机。2、查看打印机顶部亮灯是否一个在online,一个在receipt(票据)上。3、打开APP,找到基础---设置--打... 查看详情

小票打印机乱码问题

...-----------------------------editor:YeungChidate:2016/3/29describe:小票打印机乱码问题文章内容:一、参数解释二、串口连接打印机设置案例--------------------------------------串口是一种非常通用的设备通信的协议;一、参数解释波特率这是一个... 查看详情

小票打印机页面打印无法自适应长度导致超长空白打印

 1、问题描述:58或80打印机,打印内容只有两三行内容,结果会按照设置的纸张大小(58的有三种2102973276),必定打出该纸张大小,头部出现页码,底部出现网站url地址2、原因:IE浏览器搞的鬼,默认有选择页眉页脚导致上... 查看详情

delphi10seattle小票打印控件tq_printer

...持Android、iOS以及WIN32/64的开发,可控制兼容ESC/POS的小票打印机。目前控件已在多个Android、iOS项目中配合蓝牙小 查看详情

java票据打印,类似于超市的小票那样的,怎么控制打印的纸张大小啊?

类似于超市的小票那样的,怎么控制打印的纸张大小啊?若答案满意,我送出我所有的分!谢谢大家了!最好给个C/S版的源码看看啊!楼上的人一看就是在混分。其实这种问题应该到更专业的地方问,比如CSDN或者JAVAEYE。还是把... 查看详情

web使用热敏打印小票(ie环境)

概述在html页下使用EpsonP60II热敏纸下打印小票,使用的打印方案为调用window.print()。代码实现1、定义窗体,设置宽度和高度<bodyonload="window.external.Print(0,0);"style="margin-top:0px;"><formid= 查看详情

模拟打印小票(支持暂停功能)

publicclassMainFrameextendsJFrameprivateJButtonprintButton=newJButton("打印");privateJButtonsuspendButton=newJButton("暂停");privateJLabelprintText=newJLabel("模拟小票打印");privateJTextAreaprintTextArea=newJT 查看详情

uniapp实现蓝牙小票打印功能

...的一个项目增加了小票蓝牙打印的功能,由于之前对蓝牙打印机了解不多,所以遇到的坑比较多,花了点时间把蓝牙连接、打印模块封装成通用组件,并写了个打印的例子,这里做个记录,以防忘记。组件:组件例子项目需要实... 查看详情

如何用lodop能过usb热敏打印机打开钱箱

热敏打印机驱动里设置打印时开钱箱,打印小票的时候会自动开启钱箱。参考技术A  usb端口只能借用打印驱动,选择打印机之后,  用SEND_PRINT_RAWDATA语句发送相关指令。 参考技术B热敏打印机驱动里设置打印时开钱箱,打印... 查看详情

小票打印机维修方法

...先捏住弹簧一起放置上5.零部件可以任意拆卸组装,建议打印机做标记,把好的零部件安置到好的打印机上面6.打印机的螺丝全部都可以拆卸,背板底部的灰尘可以直接的清理7.发动机不要随便的拆卸工具:小型起子,通用起子 查看详情

完成打印购物小票并计算积分

importjava.util.Scanner;/***@author蓝色以太*完成打印购物小票并计算积分*/publicclassShoppingReceipt{publicstaticvoidmain(String[]args){Scannersc=newScanner(System.in);finalintNUM1=245,NUM2=570,NUM3=320;System.out.print 查看详情

单据打印模板默认启用打印机本身设置尺寸设置方法

  usePaperKind设置为true就会启用打印机本身配置参数。合川公司由原针式打印机更换为爱普生热敏打印机,更换后,单据打印不全,同一打印模板针式打印机可以打印全,爱普生标签打印机打印不全,经多次测试分析后,... 查看详情

更好的小票打印体验,huanent.printer2.0发布

huanent.printer2.0是一个专注消费小票打印的类库。拥有许多先进的特性例如居中打印、自动换行等特性,可以通过简洁的代码来打印出复杂的消费小票。huanent.printer通过MIT方式授权,可随意用于商业用途,只要注明作者即可。你可... 查看详情

dynamics365onlinepdf导出及打印

...引入了Word和Excel模板,对于项目中的一些格式化导出打印需求有了一定的帮助,不再需要进行很重的自定义开发。在介绍本篇要分享的这个新功能前,如果需要实现PDF的导出和打印一般有三种方式:第一也是最简... 查看详情

关于一体机外卖单不打印外卖单号faq(适用正餐6.0.09,轻餐4.0.6.1,轻餐4.0.6.2)

适用情景:升级版本后打印机打印出的外卖小票不出现外卖单号。解决方案:设置-功能设置-小票设置-小票自定义-前台小票-外卖订单-------选择编辑,选中右侧中外卖单号或者外卖订单编号,点击保存即可。图示如下:1.2.3.4. 查看详情