友情帮助朋友做一个小程序打印自定义内容模块,本以为只是简单的文本、条形码、二维码等内容,实际拿到需求后发现是打印图片,瞬间傻眼。研究一周,最终才在小程序上直接实现,特此Mark。
打印机:GP-2120T
流程:小程序canvas生成画布,可自定义控件,以所见即所得方式通过蓝牙打印
难点:TSPL的BITMAP指令需要十六进制图片数据、位图宽高,所以需要根据图片尺寸、像素,重构以获取新的图片数据
BITMAP x,y,width,height,mode,bitmap data
width、height、data 三者对应不上打印时会出现多种异常
数据处理
// 默认打印参数
var defaultConfig = {
printSpeed: 4,
printConcentration: 1,
direction: 0,
paperOffset: 2
};
//填充打印数据
function fillPrintData(data, config) {
//以Array存储
var writeArray = new Array();
//获取XML中打印页数,因为入口限制了数据为XML,需要手动提取数据
var pageNums = data.pageNums;
var paper = paperXmlConvertToMap(data.xml);
var command = "SIZE " + paper.w + " mm," + paper.h + " mm\n";
writeArray.push(charToArrayBuffer(command));
command = "REFERENCE 0,0\n";
writeArray.push(charToArrayBuffer(command));
setConfigInfo(config ? config : defaultConfig, writeArray);
command = "CLS\n";
writeArray.push(charToArrayBuffer(command));
//图片处理,采取逐行打印方式,速度感人……
//可以根据图片大小自行处理height,要考虑小程序蓝牙传输的字节限制
var sendImageInfo = overwriteImageData(data);
let arr = sendImageInfo.array, width = sendImageInfo.width, height = sendImageInfo.height, tempArr = [];
for (var i = 0; i < arr.length / data.width; i++) {
command = 'BITMAP 104,' + i + ',' + width + ',1,0,';
var bitmapHeader = charToArray(command);
var subArr = arr.slice(i * width, i * width + width);
tempArr = tempArr.concat(bitmapHeader).concat(subArr);
if (tempArr.length > 150){
writeArray.push(new Uint8Array(tempArr));
tempArr = [];
}else{
if (i == arr.length / w - 1){
writeArray.push(new Uint8Array(tempArr));
}
}
}
command = 'PRINT 1,' + parseInt(pageNums) + '\n';
writeArray.push(charToArrayBuffer(command));
return writeArray;
}
//常用参数指令处理
function setConfigInfo(config, writeArray) {
var command = "SPEED " + config.printSpeed + "\n";
writeArray.push(charToArrayBuffer(command));
command = "DENSITY " + config.printConcentration + "\n";
writeArray.push(charToArrayBuffer(command));
//反转打印
// command = "DIRECTION " + config.反转打印 ? "1" : "0";
command = "DIRECTION " + config.direction + "\n";
writeArray.push(charToArrayBuffer(command));
//纸张类型无设置指令
//送纸偏移
command = "GAP " + config.paperOffset + " mm\n";
writeArray.push(charToArrayBuffer(command));
//剥离模式
// command = "SET CUTTER " + config.剥离模式 ? "1" : "OFF";
// writeArray.push(charToArrayBuffer(command));
//蜂鸣--打印前
// if (config.蜂鸣)
// command = "BEEP";
// writeArray.push(charToArrayBuffer(command));
}
XML提取,使用了github上xmldom/dom-parser.js处理
function paperXmlConvertToMap(xml) {
var xmlParse = new xmlParser.DOMParser();
var doc = xmlParse.parseFromString(xml);
var paper = doc.getElementsByTagName("paper");
var map = {};
for (var i = 0; i < paper[0].attributes.length; i++) {
map[paper[0].attributes[i].name] = getNumberValue(paper[0].attributes[i].value);
}
return map;
}
数据类型转换
function getNumberValue(value) {
if (!isNaN(value)) return parseInt(value); else return value;
}
function charToArrayBuffer(str) {
var out = new ArrayBuffer(str.length)
var uint8 = new Uint8Array(out)
var strs = str.split("")
for (var i = 0; i < strs.length; i++) {
uint8[i] = strs[i].charCodeAt()
}
return uint8
}
function charToArray(str) {
var arr = [];
var strs = str.split("")
for (var i = 0; i < strs.length; i++) {
arr[i] = strs[i].charCodeAt()
}
return arr
}
图片处理:一开始走了很多弯路,研究了各种格式的图片构成,以Android-SDK取样失败,可能指令异常,打印时连打印机名字都被改了……
参考Windows-SDK图片处理功能平移至小程序,data:wx.canvasGetImageData。
function overwriteImageData(data){
let sendWidth = data.width, sendHeight = data.height;
if (data.height % 8 != 0)
sendHeight = sendHeight + 8 - sendHeight % 8;
if (data.width % 8 != 0)
sendWidth = sendWidth + 8 - sendWidth % 8;
let sendImageData = new ArrayBuffer(sendWidth * sendHeight / 8);
sendImageData = memset(sendImageData);
let clsLBit = [127, 191, 223, 239, 247, 251, 253, 254];
let sumRed = 0, sumGreen = 0, sumBlue = 0;
let total = sendWidth * sendHeight;
let pix = data.imageData;
for (var i = 0; i < pix.length; i += 4) {
sumRed += pix[i]
sumGreen += pix[i + 1]
sumBlue += pix[i + 2]
}
let avgRed = parseInt(sumRed / total);
let avgGreen = parseInt(sumGreen / total);
let avgBlue = parseInt(sumBlue / total);
let m = 4;
for (var j = 0; j < image.height; j++) {
for (var i = 0; i < image.width; i++) {
if ((pix[m * i] * 0.299 + pix[m * i + 1] * 0.587 + [m * i + 2] * 0.114) < (avgRed * 0.299 + avgGreen * 0.587 + avgBlue * 0.114))
sendImageData[parseInt(j * sendWidth / 8 + i / 8)] &= clsLBit[i % 8];
}
}
return {array: Array.from(sendImageData), width: sendWidth / 8, height: sendHeight};
}
function memset(arrayBuffer) {
let view = new Uint8Array(arrayBuffer);
for (let i = 0; i < view.length; i++) {
view[i] = 255;
}
return view
}
发送指令
function sendData() {
var arr = bleDataUtil.fillPrintData(printDataArr, config);
if (arr.length > 0) {
sendDataTimer = setInterval(function () {
if (arr.length > 0) {
printData(arr[0]["buffer"]);
arr.splice(0, 1);
} else {
clearSendDataTimer();
}
}, 100);
}
// else closeConnection();
}
此种处理适用于PNG、JPG、BMP,其他的没有做测试。
因为项目本身限制太多,要求小程序处理canvas数据和图片,所以只能如此实现,完全不使用接口。
如果可以,最好还是选择接口处理。
后记
没有最终测试,不排除是否还有其他问题
感谢
尝试过各种方式处理,明白原理后最终才得以实现,感谢以下网站和作者,还有很多参考资料没有保留,非常感谢。
[1]: http://www.vgot.net/test/image2base64.php img转base64
[2]: http://www.ab126.com/goju/1711.html ASCII转换工具
[5]:https://www.jianshu.com/p/8953953253b9 JavaScript PNG 图片编码和解码