蓝牙打印
打印机都有自己的命令集如ECS、PCL、PS、CPCL、TSPL等,CPCL是目前小型移动打印机的主流指令集之一。
android、ios端等接入蓝牙打印机一般使用两种方式
一、可以使用厂家提供的SDK,基于SDK封装的能力实现蓝牙打印,厂家提供的SDK能力相对更加强大,其在标准指令基础上封装了更高级的指令使用相对更加方便,可使用不同打印机厂家的自由指令功能等,但不同设备厂家有不同的SDK
二、使用相对标准的CPCL指令集生成打印命令,其兼容性相对更好,不需要安装SDK直接连接打印机传输数据就可以打印,且可以在各个端android、ios、微信小程序、pc等都可以使用。
打印模版
目前自定义打印模版一般都是使用PC的HTML编辑器来编辑模版,其生成的都是HTML模版在蓝牙打印机上不能直接使用需要进行转换,不同打印机对CPCL有自己的实现会有兼容性等问题。
CPCL指令集
CPCL指令由一系列基础指令构成,本文指令兼容性参考汉印打印机
一条完整的打印指令由以下结构构成
以! 开头(加空格) 偏移量 dpi dpi 最大高度 打印份数(实测在有些打印机内份数不生效) 打印内容(打印指令都是以大写字母开头) PRIN执行打印指令
scss
! 0 200 200 800 1
PAGE-WIDTH 400
TEXT 4 0 30 40 Hello World
PRINT
1、基础文字指令
scss
TEXT 4 0 30 40 Hello World
文字指令以TEXT为标记,字体名称\编号(不同打印机会有自己的内置字体) 文字大小标识 打印位置x、y点位坐标 打印内容
文字内容可通过SETBOLD、SETMAG等命令来加粗、放大
2、基础线条指令
scss
LINE 100 100 100 200 1
文字指令以LINE为标记,起点位置x、y点位坐标 终点位置x、y点位坐标 线条宽度,也可使用BOX指令绘制方形框(参数同LINE)
3、基础条形码
vbnet
BARCODE-TEXT 7 0 5
BARCODE UPCA 1 1 40 0 145 40123456784
BARCODE-TEXT OFF
条形码以BARCODE为标记,条码类型 窄条的单位宽度 宽条与窄条的比率 条码高度 起始点x、y点位坐标 条码数据,使用BARCODE-TEXT命令控制打印条码数据 字体号 字体大小 偏移量,或者使用OFF禁用
4、基础二维码
BARCODE QR 100 100 M 2 U 6
HA,abc
ENDQR
BARCODE指定QR类型,起始点x、y点位坐标,M 2/M 1 二维码规范符号,U 200 模块的宽高 纠错级别 二维码数据
5、基础图片\ICON打印
erlang
EG 50 50 100 100 ...
图片打印使用EG指定类型,图片宽高 起始位置 图片数据(十六进制值)
模版转换
模版转换主要有两种方式
一、将dom结构转换成CPCL指令重新绘制出来,主要涉及css相关px与打印机分辨率之间转换,文字大小、加粗、盒模型位置计算、图片数据转换等方面。模版编辑器生成的dom结构相对固定,可根据节点类型做相应处理。但CPCL指令可绘制内容类型有限,有些html内容无对应CPCL指令,且文字大小、加粗、css旋转角度等也无法做到完全还原。
二、将html转化为图片,整体绘制图片。图片可以完全还原模版但图片转换够数据内容相对要庞大的多。受限于蓝牙传输速率等待时间可能几分钟甚至几十分钟,效率非常低下。
因此,一般将两种方式结合在一起使用,大部分内容使用CPCL指令转换重绘。icon等转化为图片来绘制。
1、尺寸单位处理
根据蓝牙打印机设置的分辨率(点/英寸)与px进行转换
如! 0 200 200 800 1 设置的宽高分别为200 200
js
function js_getDPI() {
const arrDPI = [];
if (window.screen.deviceXDPI !== undefined) {
arrDPI[0] = window.deviceXDPI;
arrDPI[1] = window.deviceYDPI;
} else {
const tmpNode = document.createElement('DIV');
tmpNode.style.cssText =
'position: fixed;width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden';
document.body.append(tmpNode);
arrDPI[0] = parseInt(tmpNode.offsetWidth);
arrDPI[1] = parseInt(tmpNode.offsetHeight);
tmpNode.parentNode.removeChild(tmpNode);
}
return arrDPI;
}
const a = js_getDPI();
1 / a[0]) * 200; // 1像素宽度
1 / a[1]) * 200; // 1像素高度
2、文字转换
文字位置:基本方法是首先获取文字盒模型位置再根据padding属性值和文字对齐方式计算出文字实际位置,最终计算出来的位置为文字左上角的绝对定位位置,可先获取文字占位尺寸再根据盒模型位置计算出内部容器尺寸再减去响应行高、居中对齐等。其中文字字体、大小、加粗等需要根据CPCL指令做响应适配(CPCL可控制内容没有css那么强大)
js
function getTextSize(text, fontSize) {
const fz = fontSize || '12px';
const size = [];
const tmpNode = document.createElement('span');
tmpNode.style.cssText = `font-size: ${fz};position: fixed;display: inline-block;width:auto;height:auto;line-height: 1;visibility:hidden;`;
tmpNode.innerText = text;
document.body.append(tmpNode);
size[0] = parseInt(tmpNode.offsetWidth);
size[1] = parseInt(tmpNode.offsetHeight);
tmpNode.parentNode.removeChild(tmpNode);
return size;
}
文字字号\加粗
CPCL可打印字号只有固定几种(如:2424、2020、28*28等),需要在相应字体选择上通过放大、加粗等组合设置来适配(实际效果配合模版设置调试)。
js
{
normal: 0,
100: 0,
200: 0,
300: 1,
400: 1,
500: 2,
600: 2,
700: 2,
bold: 2
}
function getFontSize(fontSize) {
const s = fontSize.replace('px', '') * 1;
let res = {
size: [55, 0],
mag: null
};
if (s >= 12) {
res = {
size: [2, 0],
mag: null
};
}
if (s >= 16) {
res = {
size: [55, 0],
mag: '2 2'
};
}
if (s >= 20) {
res = {
size: [2, 0],
mag: '2 2'
};
}
if (s >= 28) {
res = {
size: [55, 0],
mag: '4 4'
};
}
if (s >= 36) {
res = {
size: [55, 0],
mag: '5 5'
};
}
if (s >= 44) {
res = {
size: [55, 0],
mag: '6 6'
};
}
if (s >= 52) {
res = {
size: [55, 0],
mag: '7 7'
};
}
return res;
}
2、线条边框
线条边框根据需要做横线竖线等转换,亦可做一定角度旋转,注意线条务重复绘制
3、条形码
条形码尺寸高度可根据设置转换,但其宽度完全根据内容数据绘制,注意html模版是否有宽度撑满容器等设置。CPCL无法适配。
4、二维码
二维码基本可还原,CPCL只能旋转90及倍数角度。同时也不可长宽比缩放
5、图片转换
图片转换需要把图片加载到本地后转换为16进制数据后绘制。首先加载图片将其绘制到canvas上借助canvas获取其点位数据后做rgb黑白转换及像素压缩以减小其数据量。图片转换后数据非常庞大,除logo等内容不建议加入其他图片,蓝牙加载会很慢。
js
function images(temp) {
let { top, left, width, height } = temp.style;
if (width === 'auto' || (height && !width)) {
width = height;
}
const { url } = temp;
const lP = ...;
const tP = ...;
const w = width.replace('px', '') * 1;
const h = height.replace('px', '') * 1;
img2pix(url, w, h, lP, tP);
}
export const img2pix = (src, width, height, left, top) => {
const img = new Image();
if (width) {
img.width = 100;
}
if (height) {
img.height = 100;
}
img.crossOrigin = '';
img.src = src;
img.onload = () => {
const imgItem = initCanvas(img);
const str = `EG ${imgItem.width} ${imgItem.height} ${left} ${top} ${imgItem.str16}`;
imgData.push(str);
};
img.onerror = e => {
console.error(e);
};
};
const initCanvas = img => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d', { willReadFrequently: true });
let width = img.width;
const yu = width % 8;
if (yu !== 0) {
width += 8 - yu;
}
let height = img.height;
canvas.width = width;
canvas.height = height;
context.drawImage(img, 0, 0, width, height);
context.drawImage(img, 0, 0, width, height);
const imgRawData = context.getImageData(0, 0, width, height);
return overwriteImageData({
imageData: imgRawData.data,
width: imgRawData.width,
height: imgRawData.height
});
};
批量打印
批量打印时,多个蓝牙打印模版数据和做字符串拼接一次性打印,可根据批量数据内容拼接不同数据模版(几k、几十k一批)可提升打印流畅性。多个模版数据一次性传入蓝牙打印机可流畅打印。每单独批次发送数据蓝牙打印机出纸打印会停顿一下。
总结
html模版像CPCL指令之间转换主要注意px和点之间单位转换,文字大小、加粗等多适配。条形码、二维码务缩放拉伸。图片转换后数据包大小等。
最后,本人并非CPCL方面专家,有写的不对的地方欢迎指正。
模版编辑器及CPCL转换代码