云标签--ZPL命令生成(知识点:div截图,一维码和二维码的前端插件,一维码和二维码的后端jar包,图片调整长宽,图片生成zpl命令)
之前公司要求写个云标签的模块(标签包含文字,一维码,二维码和图片),大致过程是将标签转为zpl命令传给打印客户端,客户端再传给标签打印机的,针对zpl命令生成,我想了三种思路,每种思路各有利弊。其中前端我使用的是vue,后端使用的是java,客户端使用的c#。思路1:使用html2canvas 截取标签的整个div生成图片,将图片传到后台生成zpl命令1.1安装插件npm install html
之前公司要求写个云标签的模块(标签包含文字,一维码,二维码和图片),大致过程是将标签转为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);
}
}
更多推荐
所有评论(0)