之前公司要求写个云标签的模块(标签包含文字,一维码,二维码和图片),大致过程是将标签转为zpl命令传给打印客户端,客户端再传给标签打印机的,针对zpl命令生成,我想了三种思路,每种思路各有利弊。其中前端我使用的是vue,后端使用的是java,客户端使用的c#。

思路1:使用html2canvas 截取标签的整个div生成图片,将图片传到后台生成zpl命令

1.1 安装插件

npm install html2canvas --save

1.2 引入插件

import html2canvas from 'html2canvas'

1.3 截取div

//获取要被截取的div元素
var zpl=document.getElementById("zpl");
//得到div元素的宽和高
let width = zpl.offsetWidth;
let height = zpl.offsetHeight;
//创建canvas元素
let canvas = document.createElement("canvas");
//设置生成的图片的放大系数
let scaleBy = 1
//设置生成的图片的宽和高
canvas.width = width * scaleBy;
canvas.height = height * scaleBy;
// 设置图片的属性
var opts = {
	allowTaint: true,//允许加载跨域的图片
	tainttest: true, //检测每张图片都已经加载完成
	scale: scaleBy, // 放大系数
	canvas: canvas, //自定义canvas
	logging: true, //日志开关,发布的时候记得改成false
	dpi: 300,//dpi设置
	background:'#fafafa'//背景颜色
};
//开始截图
html2canvas(zpl, opts).then(function (canvas) {
	//我将生成的图片放进一个数组
	that.zplList.push({
		//生成的图片转换成base64,参数1为图片的格式,参数2为图片的清晰度,最大为1,最小为0.1
		base64Url: canvas.toDataURL("image/png", 1.0),
	})
});

1.4 图片保存到后台(见思路2的base64转图片方法),图片转成zpl命令(见最后)

思路2:一个标签的4种组件分别传入后台,在后台分别生成zpl命令。(用于先预览后打印)

2.1 安装插件

//一维码
npm install @xkeshi/vue-barcode vue
//二维码
npm install vue-qr --save

2.2 引入插件

//引入一维码
 import VueBarcode from '@xkeshi/vue-barcode'
//引入二维码
import vueQr from 'vue-qr'

//一维码插件属性
{
                format: "CODE128",//选择要使用的条形码类型
                width: 2,//设置条之间的宽度
                height: 30,//高度
                text:"456",//内容
                displayValue: true, //是否默认显示条形码数据
                textPosition: 'bottom', //条形码数据显示的位置
                background: 'transparent', //条形码背景颜色
                lineColor:"#2196f3",//设置条和文本的颜色
                fontOptions:"bold italic",//使文字加粗体或变斜体
                font: "宋体",//设置文本的字体
                fontSize: 15,//设置文本的大小
                textAlign: "center",//设置文本的水平对齐方式
                textMargin: 5,//设置条形码和文本之间的间距
                margin: 2,//设置条形码周围的空白边距
                valid: function (valid) {
                  	//当内容格式错误时,valid为false,正确为true
                }
}

//二维码插件属性
{
				text: "123",//内容
				size: 5,//大小
				margin: 0//设置二维码周围的空白边距
}

2.3 各组件转化为图片

文本:zpl命令中不识别中文,因此需要在后台生成图片,再转成zpl命令。
一维码和二维码:使用插件生成,在页面中实际为img标签,src中为base64,因此只需要获取src,长,宽,在标签中的左边距,在标签中的上边距。
图片:在后台转成zpl命令。

知识点:dpi=152<=>dpmm=6,dpi=203<=>dpmm=8,dpi=300<=>dpmm=12,dpi=600<=>dpmm=24
注意点:zpl生成前,需要将客户端选择的打印机的dpi参数传给前台,因为各组件和标签的参数单位是px,需要根据dpi转换成zpl命令中的参数,下面是我自己测试得来的转换(误差很小):
假设页面中的标签的大小与实际标签大小的比例为n:1,如页面中的标签为400px×400px,实际的标签大小为100mm×100mm,则
文本:
zpl左边距:页面中左边距×dpmm/n
zpl上边距:页面中上边距×dpmm/n
文字大小:页面中文字大小×1.5/(6/dpmm)×n
zpl宽:页面中宽×1.6/(6/dpmm)×n
zpl高:页面中高×1.6/(6/dpmm)×n
---------------------------------------------
图片(一维码,二维码也是图片):
zpl左边距:页面中左边距×dpmm/n
zpl上边距:页面中上边距×dpmm/n
zpl宽:页面中宽×1.5/(6/dpmm)×n
zpl高:页面中高×1.5/(6/dpmm)×n
----------------------------------------------
标签:
zpl宽:页面中宽×dpmm
zpl高:页面中高×dpmm

上面将4种组件和标签的参数全部转换后,就可以传到后台生成图片了。

java文本转图片

@Component
public class StrToImg {
    @Autowired
    private FilePathUtil filePathUtil;
	
    /**
     * text:文本
     * width:宽
     * height:高
     * fontFamily:样式
     * fontSize:大小
     * */
    public String getStrToImgPath(   String text,
                                     double width,
                                     double height,
                                     String fontFamily,
                                     double fontSize) throws IOException {
        String path = filePathUtil.getFilepath() + "/LabelZPL/";
        File newFile = new File(path);
        if(!newFile.exists()){
           newFile.mkdirs();
        }
        String imgPath = new Date().getTime()+".jpeg";
        BufferedImage bi = null;
        Graphics2D g2 = null;
        try{
            bi = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_RGB);//创建图片画布
            g2 = (Graphics2D) bi.getGraphics();
            //g2.setBackground(Color.white);
            Color bg = new Color(255, 255, 255);
            g2.setColor(bg);// 先用白色填充整张图片,也就是背景
            g2.fillRect(0, 0, (int)width, (int)height);//画出矩形区域,以便于在矩形区域内写入文字
            g2.setColor(Color.black);// 先用白色填充整张图片,也就是背景
            Font font = new Font(fontFamily, Font.PLAIN, (int)fontSize);//设置画笔字体
            g2.setFont(font);
            g2.drawString(text,0,font.getSize());

            ImageIO.write(bi, "jpg", new File(path + imgPath));
            return path + imgPath;
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        } finally {
            if (g2 != null){
                g2.dispose();
            }
        }
    }
}

base64转图片

@Component
public class Base64ToImg {
    @Autowired
    private FilePathUtil filePathUtil;
	 /**
     * data:base64字符串
     * */
    public String generateImg(String data){
        String base64Data =  data.split(",")[1];
        /**
         * 2.解码成字节数组
         */
        Base64.Decoder decoder = Base64.getDecoder();
        byte[] bytes = decoder.decode(base64Data);

        /**
         * 3.字节流转文件
         */
        FileOutputStream fos = null;
        String path = filePathUtil.getFilepath() + "/Base64ZPL/";
        File newFile = new File(path);
        if(!newFile.exists()){
            newFile.mkdirs();
        }
        String imgPath = new Date().getTime()+".jpeg";
        try {
            fos = new FileOutputStream(path+imgPath);
            fos.write(bytes);
            return path+imgPath;
        } catch (IOException e) {
            return "";
        } finally {
            if (fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

如果前台支持缩放组件,则后台需要改变图片大小

public class ChangeImgSize {

 	 /**
     * newWidth:转换后的宽
     * newHeight:转换后的高
     * path:图片路径
     * */
    public static boolean changeSize(double newWidth, double newHeight, String path) {
        try {
            //字节流转图片对象
            BufferedImage input = ImageIO.read(new File(path));
            Image scaledImage = input.getScaledInstance((int)newWidth, (int)newHeight,Image.SCALE_DEFAULT);
            //构建图片流
            BufferedImage tag = new BufferedImage((int)newWidth,(int)newHeight, BufferedImage.TYPE_INT_ARGB);
            //绘制改变尺寸后的图
            tag.getGraphics().drawImage(scaledImage, 0, 0,null);
            //输出流
            BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(path));
            ImageIO.write(tag, "PNG", out);
            out.close();
            return true;
        } catch (IOException e) {
            return false;
        }
    }
}

2.4 图片转为zpl命令(方法见最后)

思路3:将页面中的标签和4种组件的参数传给后台,后台去生成图片。(用于自动打印)

3.1 安装jar包

//maven安装
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.0</version>
</dependency>

//gradle安装
compile group: 'com.google.zxing', name: 'core', version: '3.3.0'

3.2 各组件生成图片

不同于思路2,思路3需要将打印机的dpi传给后台,后台利用思路2中的算法去生成图片。
注意点:前台生成的二维码和后台生成的二维码区别不大,但是后台生成的一维码格式没有前台生成的一维码格式丰富,需要前台砍掉一些格式的选择,其次后台生成的一维码下面没有文字,需要将前台一维码下面的文字不显示。

java生成二维码和一维码

@Component
public class CreateQRCodeOrBarCode {
    @Autowired
    private FilePathUtil filePathUtil;

    private final String CHARSET = "utf-8";
    private final int BLACK = 0xFF000000;
    private final int WHITE = 0xFFFFFFFF;

	/**
     * width:宽
     * height:高
     * content:二维码内容
     * */
    public String CreateQRCode(String content,double width,double height) {
        HashMap hints = new HashMap();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");// 字符集
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);// 纠错级别
        hints.put(EncodeHintType.MARGIN, 0);// 空白

        try {
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content,
                    BarcodeFormat.QR_CODE, (int)width, (int)height, hints);
            String qrCodePath = filePathUtil.getFilepath()+ "/QRCode/";
            File newFile = new File(qrCodePath);
            if(!newFile.exists()){
                newFile.mkdirs();
            }
            String imgPath = new Date().getTime()+".jpeg";

            MatrixToImageWriter.writeToFile(bitMatrix, "jpeg", new File(qrCodePath+imgPath));
            return qrCodePath+imgPath;
        } catch (Exception e) {
            System.out.println(e.toString());
            return "";
        }
    }

	 /**
     * width:宽
     * height:高
     * content:一维码内容
     * format:一维码格式
     * */
    public String createBarCode(String content,double width,double height,String format) {
        BitMatrix bitMatrix = toBarCodeMatrix(content,width,height,format);
        BufferedImage image = toBufferedImage(bitMatrix);
        try {
            String barCodePath = filePathUtil.getFilepath() + "/BarCode/";
            File newFile = new File(barCodePath);
            if(!newFile.exists()){
                newFile.mkdirs();
            }
            String imgPath = new Date().getTime()+".jpeg";
            ImageIO.write(image, "jpeg", new File(barCodePath+imgPath));
            return barCodePath+imgPath;
        } catch (IOException e) {
            System.out.println(e.toString());
            return "";
        }
    }

    public BitMatrix toBarCodeMatrix(String content,double width,double height,String format) {
        try {
            HashMap hints = new HashMap();
            hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);// 纠错级别
            hints.put(EncodeHintType.MARGIN, 0);

            BitMatrix bitMatrix = null;
            if(format.equals("CODE39")){
                bitMatrix = new MultiFormatWriter().encode(content,
                        BarcodeFormat.CODE_39, (int)width, (int)height, hints);
            }else if(format.equals("EAN8")){
                bitMatrix = new MultiFormatWriter().encode(content,
                        BarcodeFormat.EAN_8, (int)width, (int)height, hints);
            }else if(format.equals("EAN13")){
                bitMatrix = new MultiFormatWriter().encode(content,
                        BarcodeFormat.EAN_13, (int)width, (int)height, hints);
            }else if(format.equals("ITF")){
                bitMatrix = new MultiFormatWriter().encode(content,
                        BarcodeFormat.ITF, (int)width, (int)height, hints);
            }else if(format.equals("UPC")){
                bitMatrix = new MultiFormatWriter().encode(content,
                        BarcodeFormat.UPC_A, (int)width, (int)height, hints);
            }else{
                bitMatrix = new MultiFormatWriter().encode(content,
                        BarcodeFormat.CODE_128, (int)width, (int)height, hints);
            }

            return bitMatrix;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public BufferedImage toBufferedImage(BitMatrix matrix) {
        int width = matrix.getWidth();
        int height = matrix.getHeight();
        BufferedImage image = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
            }
        }
        return image;
    }
}

3.3 图片转为zpl命令(方法见最后)

总结

拓展:标签中各组件可以增加一个数据源字段(除图片),然后传入对象数组,for循环数组,
对象中的key值与数据源相同时,取出对象的value值赋给组件的值,就能用一个模板打印出很多的标签了。
上面的三种思路各有利弊,下面是分析:
思路1:优点:快速简洁,代码简单。
缺点:截取出来的图片很模糊,有时候打印出来的标签中复杂的二维码我都扫不出来,所以方案也被boss pass掉了,其次用模板打印很多的标签,截图本身是异步的方法,需要加以考虑。
思路2:优点:相比于思路1打印出来清楚很多,相比思路3前端的插件功能强大,尤其是一维码。
缺点:相比思路1代码复杂多了,还要获取dpi写算法,生成很多的图片再转换成zpl,相比思路3,因为是将一维码和二维码的src传给后台,所以首先要预览下,做不出传入对象数组,就能自动打印的接口。
思路3:优点:相比于思路1打印出来清楚很多,相比思路3能写出只要传入一个模板和对象数组,就能一下子全部打印出来的接口,且不需要预览。
缺点:相比思路1代码复杂多了,相比思路2,需要舍弃前台一维码插件的很多功能。

最后附上java图片转zpl的代码和zpl命令测试地址

zpl命令测试地址
http://labelary.com/viewer.html

java图片转zpl

public class ImgToZPL {
    private int blackLimit = 380;
    private int total;
    private int widthBytes;
    private boolean compressHex = false;
    private static Map<Integer, String> mapCode = new HashMap<Integer, String>();
    {
        mapCode.put(1, "G");
        mapCode.put(2, "H");
        mapCode.put(3, "I");
        mapCode.put(4, "J");
        mapCode.put(5, "K");
        mapCode.put(6, "L");
        mapCode.put(7, "M");
        mapCode.put(8, "N");
        mapCode.put(9, "O");
        mapCode.put(10, "P");
        mapCode.put(11, "Q");
        mapCode.put(12, "R");
        mapCode.put(13, "S");
        mapCode.put(14, "T");
        mapCode.put(15, "U");
        mapCode.put(16, "V");
        mapCode.put(17, "W");
        mapCode.put(18, "X");
        mapCode.put(19, "Y");
        mapCode.put(20, "g");
        mapCode.put(40, "h");
        mapCode.put(60, "i");
        mapCode.put(80, "j");
        mapCode.put(100, "k");
        mapCode.put(120, "l");
        mapCode.put(140, "m");
        mapCode.put(160, "n");
        mapCode.put(180, "o");
        mapCode.put(200, "p");
        mapCode.put(220, "q");
        mapCode.put(240, "r");
        mapCode.put(260, "s");
        mapCode.put(280, "t");
        mapCode.put(300, "u");
        mapCode.put(320, "v");
        mapCode.put(340, "w");
        mapCode.put(360, "x");
        mapCode.put(380, "y");
        mapCode.put(400, "z");
    }
    public String convertfromImg(BufferedImage image) throws IOException {
        String cuerpo = createBody(image);
        if(compressHex)
            cuerpo = encodeHexAscii(cuerpo);
        return headDoc() + cuerpo;
    }
    private String createBody(BufferedImage orginalImage) throws IOException {
        StringBuffer sb = new StringBuffer();
        Graphics2D graphics = orginalImage.createGraphics();
        graphics.drawImage(orginalImage, 0, 0, null);
        int height = orginalImage.getHeight();
        int width = orginalImage.getWidth();
        int rgb, red, green, blue, index=0;
        char auxBinaryChar[] =  {'0', '0', '0', '0', '0', '0', '0', '0'};
        widthBytes = width/8;
        if(width%8>0){
            widthBytes= (((int)(width/8))+1);
        } else {
            widthBytes= width/8;
        }
        this.total = widthBytes*height;
        for (int h = 0; h<height; h++)
        {
            for (int w = 0; w<width; w++)
            {
                rgb = orginalImage.getRGB(w, h);
                red = (rgb >> 16 ) & 0x000000FF;
                green = (rgb >> 8 ) & 0x000000FF;
                blue = (rgb) & 0x000000FF;
                char auxChar = '1';
                int totalColor = red + green + blue;
                if(totalColor>blackLimit){
                    auxChar = '0';
                }
                auxBinaryChar[index] = auxChar;
                index++;
                if(index==8 || w==(width-1)){
                    sb.append(fourByteBinary(new String(auxBinaryChar)));
                    auxBinaryChar =  new char[]{'0', '0', '0', '0', '0', '0', '0', '0'};
                    index=0;
                }
            }
            sb.append("\n");
        }
        return sb.toString();
    }
    private String fourByteBinary(String binaryStr){
        int decimal = Integer.parseInt(binaryStr,2);
        if (decimal>15){
            return Integer.toString(decimal,16).toUpperCase();
        } else {
            return "0" + Integer.toString(decimal,16).toUpperCase();
        }
    }
    private String encodeHexAscii(String code){
        int maxlinea =  widthBytes * 2;
        StringBuffer sbCode = new StringBuffer();
        StringBuffer sbLinea = new StringBuffer();
        String previousLine = null;
        int counter = 1;
        char aux = code.charAt(0);
        boolean firstChar = false;
        for(int i = 1; i< code.length(); i++ ){
            if(firstChar){
                aux = code.charAt(i);
                firstChar = false;
                continue;
            }
            if(code.charAt(i)=='\n'){
                if(counter>=maxlinea && aux=='0'){
                    sbLinea.append(",");
                } else     if(counter>=maxlinea && aux=='F'){
                    sbLinea.append("!");
                } else if (counter>20){
                    int multi20 = (counter/20)*20;
                    int resto20 = (counter%20);
                    sbLinea.append(mapCode.get(multi20));
                    if(resto20!=0){
                        sbLinea.append(mapCode.get(resto20) + aux);
                    } else {
                        sbLinea.append(aux);
                    }
                } else {
                    sbLinea.append(mapCode.get(counter) + aux);
                    if(mapCode.get(counter)==null){
                    }
                }
                counter = 1;
                firstChar = true;
                if(sbLinea.toString().equals(previousLine)){
                    sbCode.append(":");
                } else {
                    sbCode.append(sbLinea.toString());
                }
                previousLine = sbLinea.toString();
                sbLinea.setLength(0);
                continue;
            }
            if(aux == code.charAt(i)){
                counter++;
            } else {
                if(counter>20){
                    int multi20 = (counter/20)*20;
                    int resto20 = (counter%20);
                    sbLinea.append(mapCode.get(multi20));
                    if(resto20!=0){
                        sbLinea.append(mapCode.get(resto20) + aux);
                    } else {
                        sbLinea.append(aux);
                    }
                } else {
                    sbLinea.append(mapCode.get(counter) + aux);
                }
                counter = 1;
                aux = code.charAt(i);
            }
        }
        return sbCode.toString();
    }
    private String headDoc(){
        String str = "^GFA,"+ total + ","+ total + "," + widthBytes +", ";
        return str;
    }
    public void setCompressHex(boolean compressHex) {
        this.compressHex = compressHex;
    }
        public void setBlacknessLimitPercentage(int percentage){
        blackLimit = (percentage * 768 / 100);
    }
}
Logo

前往低代码交流专区

更多推荐