上一篇主要介绍了如何通过蓝牙打印机和wifi云打印机的连接与数据发送,这一篇,我们就介绍向打印机发送打印指令,来打印字符和图片。

由于公司暂且买了两台打印机,一台佳博GP-58MIII,一台GP-SH584。

我们先来看一下最终票据打印效果图:

一、ESC/POS指令

ESC/POS指令体系是由EPSON发明的一套专有POS打印机指令系统,市面上绝大部分打印机兼容ESC/POS指令。

由于我使用的是佳博的蓝牙打印机,我们来看一下佳博打印机背后的型号说明:

上图可见,两台打印机是58mm纸宽的打印机,一台支持USB和蓝牙连接,一台支持USB和wifi云,并且都支持ESC/POS命令。

二、常用的打印命令

手机通过蓝牙或者ip端口向打印机发送的都是纯字节流。打印机通过遵循ESC/POS指令控制打印。

2.1 初始化打印机

指令:该指令会清除打印缓冲区中的数据,但是接收缓冲区的数据并不会清除,一般开始打印的时候需要调用。

ASCII码    ESC  @
十进制码    27  64
十六进制码  1B  40

佳博SDK将ESC指令封装成了一个EsCommand类,对应的代码如下:

EscCommand esc = new EscCommand();
esc.addInitializePrinter();//初始化打印机

//SDK源码内部
public void addInitializePrinter() {
    byte[] command = new byte[]{27, 64};
    this.addArrayToCommand(command);
}
//此处省略了部分核心代码
outputStream.write(esc.getCommand , offset, len);
...

2.2 打印文本

没有对应指令,直接输出文本

// 打印文字
esc.addText("发货单\n");

public void addText(String text) {
    this.addStrToCommand(text);
}

2.3  打印多列文本

通用打印纸都是有固定宽度的,比如58mm打印纸最大宽度为384个像素店,80mm打印纸最大宽度为576个像素点。经过大量测试结果表明,一般最大字节数是32个字节(总宽度 = 左侧宽度 + 右侧宽度)

/**
* 左侧宽度 + 右侧宽度 = 打印纸总宽度
* 打印纸一行最大的字节
*/
private static final int LINE_BYTE_SIZE = 32;

/**
* 打印三列时,中间一列的中心线距离打印纸左侧的距离
*/
private static final int LEFT_LENGTH = 16;

/**
* 打印三列时,中间一列的中心线距离打印纸右侧的距离
*/
private static final int RIGHT_LENGTH = 16;

/**
* 打印四列时,第一列居左,第二列居中,第四列居右,第三列位于第二列和第四列中间
* 第三列距离第二列的距离
*/
private static final int MIDDLE_RIGHT_MIDDLE_LENGTH = 8;

/**
* 打印四列时,第一列居左,第二列居中,第四列居右,第三列位于第二列和第四列中间
* 第三列距离第四列的距离
*/
private static final int MIDDLE_RIGHT_MIDDLE_RIGHT = 8;

/**
* 打印三列时,第一列汉字最多显示几个文字
*/
private static final int LEFT_TEXT_MAX_LENGTH = 5;

当我们打印两列时,一列居左,一列居右;打印三列时,一列居左,一列居中,一列居右。那两列之间的空格,我们用下面的方法可以完美的计算出来剩余空格。这样就不用手动添加多个空格了。

/**
* 获取数据长度
*
* @param msg
* @return
*/
@SuppressLint("NewApi")
private static int getBytesLength(String msg) {
    return msg.getBytes(Charset.forName("GB2312")).length;
}

/**
* 打满一列列
*
* @return
*/
@SuppressLint("NewApi")
public static String printOneFullData() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < LINE_BYTE_SIZE; i++) {
        sb.append("-");
    }
    return sb.toString();
}

/**
* 打印两列
*
* @param leftText  左侧文字
* @param rightText 右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printTwoData(String leftText, String rightText) {
    StringBuilder sb = new StringBuilder();
    int leftTextLength = getBytesLength(leftText);
    int rightTextLength = getBytesLength(rightText);
    sb.append(leftText);

    // 计算两侧文字中间的空格
    int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;

    for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
        sb.append(" ");
    }
    sb.append(rightText);
    return sb.toString();
}

/**
* 打印三列
*
* @param leftText   左侧文字
* @param middleText 中间文字
* @param rightText  右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printThreeData(String leftText, String middleText, String rightText) {
    StringBuilder sb = new StringBuilder();
    // 左边最多显示 LEFT_TEXT_MAX_LENGTH 个汉字 + 两个点
    if (leftText.length() > LEFT_TEXT_MAX_LENGTH) {
        leftText = leftText.substring(0, LEFT_TEXT_MAX_LENGTH) + "..";
    }
    int leftTextLength = getBytesLength(leftText);
    int middleTextLength = getBytesLength(middleText);
    int rightTextLength = getBytesLength(rightText);

    sb.append(leftText);
    // 计算左侧文字和中间文字的空格长度
    int marginBetweenLeftAndMiddle = LEFT_LENGTH - leftTextLength - middleTextLength / 2;

    for (int i = 0; i < marginBetweenLeftAndMiddle; i++) {
        sb.append(" ");
    }
    sb.append(middleText);

    // 计算右侧文字和中间文字的空格长度
    int marginBetweenMiddleAndRight = RIGHT_LENGTH - middleTextLength / 2 - rightTextLength;

    for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
        sb.append(" ");
    }

    // 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
    sb.delete(sb.length() - 1, sb.length()).append(rightText);
    return sb.toString();
}

/**
* 打印四列
*
* @param leftText           左侧文字
* @param middleText         中间文字
* @param middleAndThirdText 中间与右侧中间的文字
* @param rightText          右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printFourData(String leftText, String middleText, String middleAndThirdText, String rightText) {
    StringBuilder sb = new StringBuilder();
    // 左边最多显示 LEFT_TEXT_MAX_LENGTH 个汉字 + 两个点
    if (leftText.length() > LEFT_TEXT_MAX_LENGTH) {
        leftText = leftText.substring(0, LEFT_TEXT_MAX_LENGTH) + "..";
    }
    int leftTextLength = getBytesLength(leftText);
    int middleTextLength = getBytesLength(middleText);
    int rightTextLength = getBytesLength(rightText);
    int middleAndThirdTextLength = getBytesLength(middleAndThirdText);

    sb.append(leftText);

    // 计算左侧文字和中间文字的空格长度
    int marginBetweenLeftAndMiddle = LEFT_LENGTH -
    leftTextLength -
    (middleTextLength > 1 ? middleTextLength / 2 : 1);
    for (int i = 0; i < marginBetweenLeftAndMiddle; i++) {
        sb.append(" ");
    }
    sb.append(middleText);

    //计算中间文字和第三列文字的空格长度
    int marginBetweenMiddleAndThirdText = MIDDLE_RIGHT_MIDDLE_LENGTH -
                (middleTextLength > 1 ? middleTextLength / 2 : 1) -
                (middleAndThirdTextLength > 1 ? middleAndThirdTextLength / 2 : 1);
    for (int i = 0; i < marginBetweenMiddleAndThirdText; i++) {
        sb.append(" ");
    }
    sb.append(middleAndThirdText);

    // 计算第三列文字文字和右侧文字的空格长度
    int marginBetweenThirdTextAndRight = MIDDLE_RIGHT_MIDDLE_RIGHT - (middleAndThirdTextLength > 1 ? middleAndThirdTextLength / 2 : 1) - rightTextLength;
    for (int i = 0; i < marginBetweenThirdTextAndRight; i++) {
        sb.append(" ");
    }
    // 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
//        sb.delete(sb.length() - 1, sb.length()).append(rightText);
    sb.append(rightText);
    return sb.toString();
}

 

2.4 设置对齐方式

指令:

ASCII码    ESC   a  n
十进制码    27   97  n
十六进制码  1B   61  n

n的取值,0左对齐,1中间对齐,2右对齐。

// 设置打印居中
esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);

public void addSelectJustification(EscCommand.JUSTIFICATION just) {
    byte[] command = new byte[]{27, 97, just.getValue()};
    this.addArrayToCommand(command);
}

public static enum JUSTIFICATION {
    LEFT(0),//左对齐
    CENTER(1),//居中
    RIGHT(2);//右对齐

    private final int value;

    private JUSTIFICATION(int value) {
        this.value = value;
    }

    public byte getValue() {
        return (byte)this.value;
    }
}

2.5 字体倍高倍宽

指令:

ASCII码    ESC   !  n
十进制码    27   33  n
十六进制码  1B   21  n

n的取值,0正常,16倍高,32倍宽,128下划线

// 设置为倍高倍宽
esc.addSelectPrintModes(EscCommand.FONT.FONTA, EscCommand.ENABLE.OFF, EscCommand.ENABLE.ON, EscCommand.ENABLE.ON, EscCommand.ENABLE.OFF);
// 取消倍高倍宽
esc.addSelectPrintModes(EscCommand.FONT.FONTA, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF);

//SDK源码
public void addSelectPrintModes(EscCommand.FONT font, EscCommand.ENABLE emphasized, EscCommand.ENABLE doubleheight, EscCommand.ENABLE doublewidth, EscCommand.ENABLE underline) {
        byte temp = 0;
        if (font == EscCommand.FONT.FONTB) {
            temp = 1;
        }

        if (emphasized == EscCommand.ENABLE.ON) {
            temp = (byte)(temp | 8);
        }

        if (doubleheight == EscCommand.ENABLE.ON) {
            temp = (byte)(temp | 16);
        }

        if (doublewidth == EscCommand.ENABLE.ON) {
            temp = (byte)(temp | 32);
        }

        if (underline == EscCommand.ENABLE.ON) {
            temp = (byte)(temp | 128);
        }

        byte[] command = new byte[]{27, 33, temp};
        this.addArrayToCommand(command);
    }

2.6 打印并走纸n行

指令:

ASCII码    ESC   d  n
十进制码    27  100  n
十六进制码  1B   64  n
//打印走纸多少行
esc.addPrintAndFeedLines((byte) 3);

//SDK源码
public void addPrintAndFeedLines(byte n) {
    byte[] command = new byte[]{27, 100, n};
    this.addArrayToCommand(command);
}

三、打印条码

3.1 条码高度指令

ASCII码    GS   h  n
十进制码    29 104  n
十六进制码  1D  68  n

n介于1到255之间,n默认值为162

public void addSetBarcodeHeight(byte height) {
    byte[] command = new byte[]{29, 104, height};
    this.addArrayToCommand(command);
}

3.2 条码宽度指令

ASCII码    GS   w  n
十进制码    29 119  n
十六进制码  1D  77  n

n介于2到6之间,默认n=3

public void addSetBarcodeHeight(byte height) {
    byte[] command = new byte[]{29, 104, height};
    this.addArrayToCommand(command);
}

3.3 打印条码

esc.addSetBarcodeHeight((byte) 80); //设置条码高度为80点
esc.addSetBarcodeWidth((byte) 3); // 设置条码单元宽度为3
esc.addCODE128(esc.genCode128("201811080001"));


public void addCODE128(String content) {
    byte[] command = new byte[]{29, 107, 73, (byte)content.length()};
    this.addArrayToCommand(command);
    this.addStrToCommand(content, command[3]);
}

四、打印二维码

QRCode命令打印,此命令只在支持QRCode命令打印的机型才能使用。 在不支持二维码指令打印的机型上,则需要发送二维条码图片。

// 设置纠错等级
esc.addSelectErrorCorrectionLevelForQRCode((byte) 0x31);
// 设置qrcode模块大小
esc.addSelectSizeOfModuleForQRCode((byte) 10);
// 设置qrcode内容
esc.addStoreQRCodeData("www.whoot.com");
// 打印QRCode
esc.addPrintQRCode();

五、打印图片

很多小票上面会附上二维码或者logo,当打印机不支持二维码指令时,可以打印二维码的图片。由于热敏打印机只能打印黑白两色,所以首先需要把图片转成纯黑白的,再调用图片打印指令进行打印。

5.1 图片分辨率调整

58mm的热敏打印机,可打印区域最大宽度时384个像素点。80mm打印机可打印区域最大宽度为576个像素点。如果分辨率过大,超出了打印机可打印的最大宽度,那么超出部分将无法打印。所以在打印之前,我们需要调整图片的分辨率。

Bitmap b = BitmapFactory.decodeResource(App.getContext().getResources(), R.drawable.gprinter);
esc.addRastBitImage(b, 384, 0);
public void addRastBitImage(Bitmap bitmap, int nWidth, int nMode) {
    if (bitmap != null) {
        //因为横向每8个像素店组成一个字节,这里将位图宽度规范化为8的整数倍
        int width = (nWidth + 7) / 8 * 8;
        int height = bitmap.getHeight() * width / bitmap.getWidth();
        Bitmap grayBitmap = GpUtils.toGrayscale(bitmap);
        Bitmap rszBitmap = GpUtils.resizeImage(grayBitmap, width, height);
        byte[] src = GpUtils.bitmapToBWPix(rszBitmap);
        byte[] command = new byte[8];
        height = src.length / width;
        command[0] = 29;
        command[1] = 118;
        command[2] = 48;
        command[3] = (byte)(nMode & 1);
        command[4] = (byte)(width / 8 % 256);
        command[5] = (byte)(width / 8 / 256);
        command[6] = (byte)(height % 256);
        command[7] = (byte)(height / 256);
        this.addArrayToCommand(command);
        byte[] codecontent = GpUtils.pixToEscRastBitImageCmd(src);

        for(int k = 0; k < codecontent.length; ++k) {
            this.Command.add(codecontent[k]);
        }
    } else {
        Log.d("BMP", "bmp.  null ");
    }

}

/**
* 图片去色,返回灰度图片
*
*/
public static Bitmap toGrayscale(Bitmap bmpOriginal) {
    int height = bmpOriginal.getHeight();
    int width = bmpOriginal.getWidth();
    Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Config.RGB_565);
    Canvas c = new Canvas(bmpGrayscale);
    Paint paint = new Paint();
    ColorMatrix cm = new ColorMatrix();
    cm.setSaturation(0.0F);
    ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
    paint.setColorFilter(f);
    c.drawBitmap(bmpOriginal, 0.0F, 0.0F, paint);
    return bmpGrayscale;
}

github地址:https://github.com/zoujin6649/PrinterDemo

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐