ESC/POS常用打印指令面向对象封装,PHP处理二维码定位,微信小程序蓝牙打印
热敏小票/标签打印机,使用ESC/POS指令打印,常用指令封装,适用于GBK编码
const PER_MM=8,//每毫米像素数
fontSize=12,//每字符像素数
gbk=require('./gbk'),//兼容中文的字符转换库,文末附链接
/*计算字符串长度(1个中文=2个字符)*/
charLen=str=>{let width=0;for(let i=0;i<str.length;i++){width+=gbk.isAscii(str.charCodeAt(i))?1:2;}return width;
},
//ESC_POS这段是抄来的,略作调整
ESC_POS={ALIGN:{C: [0x1b, 0x61, 0x01], // 居中L: [0x1b, 0x61, 0x00], // 左对齐R: [0x1b, 0x61, 0x02], // 右对齐},BEEP:[0x1b,0x07], // 蜂鸣器COLOR:{BLACK:[0x1b,0x72,0x00],RED:[0x1b,0x72,0x01]},/*文本格式*/TEXT: {NORMAL: [0x1b, 0x21, 0x00], // Normal textD_H: [0x1b, 0x21, 0x10], // Double height textD_W: [0x1b, 0x21, 0x20], // Double width textD_W_H: [0x1b, 0x21, 0x30], // Double width & height textUNDERL_OFF: [0x1b, 0x2d, 0x00], // Underline font OFFUNDERL_ON: [0x1b, 0x2d, 0x01], // Underline font 1-dot ONUNDERL_2: [0x1b, 0x2d, 0x02], // Underline font 2-dot ONBOLD_OFF: [0x1b, 0x45, 0x00], // Bold font OFFBOLD_ON: [0x1b, 0x45, 0x01], // Bold font ONITALIC_OFF: [0x1b, 0x35], // Italic font ONITALIC_ON: [0x1b, 0x34], // Italic font ONFONT_A: [0x1b, 0x4d, 0x00], // Font type AFONT_B: [0x1b, 0x4d, 0x01], // Font type BFONT_C: [0x1b, 0x4d, 0x02], // Font type C},LINE_SPACING:{LS_DEFAULT:[0x1b,0x32], //默认行高,30点LS_SET(size){return [0x1b,0x33,size]} //size点行高},CUT: {FULL: [0x1d, 0x56, 0x00], // 全切PART: [0x1d, 0x56, 0x01], // 半切FULL_TO: [0x1d, 0x56, 0x40], // 走纸到切纸位置+n/144英寸并全切A_TO: [0x1d, 0x56, 0x41], // 走纸到切纸位置+n/144英寸并半切B_TO: [0x1d, 0x56, 0x42], // 走纸到切纸位置+n/144英寸并半切},BARCODE: {TXT_OFF: [0x1d, 0x48, 0x00], // HRI barcode chars OFFTXT_ABV: [0x1d, 0x48, 0x01], // HRI barcode chars aboveTXT_BLW: [0x1d, 0x48, 0x02], // HRI barcode chars belowTXT_BTH: [0x1d, 0x48, 0x03], // HRI barcode chars both above and belowFONT_A: [0x1d, 0x66, 0x00], // Font type A for HRI barcode charsFONT_B: [0x1d, 0x66, 0x01], // Font type B for HRI barcode charsHEIGHT(h){return [0x1d,0x68,h]},// Barcode Height [1-255]WIDTH(w){return [0x1d,0x77,w]},// Barcode Width [2-6]HEIGHT_DEFAULT: [0x1d, 0x68, 0x64], // Barcode height default:100WIDTH_DEFAULT: [0x1d, 0x77, 0x01], // Barcode width default:1UPC_A: [0x1d, 0x6b, 0x00], // 0x41 11,12 48-57UPC_E: [0x1d, 0x6b, 0x01], // 0x42 11,12 48-57EAN13: [0x1d, 0x6b, 0x02], // 0x43 12,13 48-57EAN8: [0x1d, 0x6b, 0x03], // 0x44 7,8 48-57CODE39: [0x1d, 0x6b, 0x04], // 0x45 变长 32,36,37,43,45-57,65-90I25: [0x1d, 0x6b, 0x05], // 0x46 偶数 48-57 (ITF)CODEBAR: [0x1d, 0x6b, 0x06], // 0x47 变长 36,43,45-58,65-68 (NW7)CODE93: [0x1d, 0x6b, 0x07], // 0x48 变长 0-127CODE128: [0x1d, 0x6b, 0x08], // 0x49 变长 0-127CODE11: [0x1d, 0x6b, 0x09], // 0x4a 变长 48-57MSI: [0x1d, 0x6b, 0x0a], // 0x4b 变长 48-57},QRCODE:{SIZE(size){return [0x1D,0x28,0x6b,0x03,0x00,0x31,0x43,size]},CORRECT_L:[0x1D,0x28,0x6b,0x03,0x00,0x31,0x45,0x30], // 可覆盖%7,默认CORRECT_M:[0x1D,0x28,0x6b,0x03,0x00,0x31,0x45,0x31], // 可覆盖%15CORRECT_Q:[0x1D,0x28,0x6b,0x03,0x00,0x31,0x45,0x32], // 可覆盖%25CORRECT_H:[0x1D,0x28,0x6b,0x03,0x00,0x31,0x45,0x33], // 可覆盖%30},/*** [HARDWARE Printer hardware]* @type {Object}*/HARDWARE: {INIT: [0x1b, 0x40], // Clear data in buffer and reset modesHW_SELECT: [0x1b, 0x3d, 0x01], // Printer selectHW_RESET: [0x1b, 0x3f, 0x0a, 0x00], // Reset printer hardware},/*** [CASH_DRAWER Cash Drawer]* @type {Object}*/CASH_DRAWER: {CD_KICK_2: [0x1b, 0x70, 0x00], // Sends a pulse to pin 2 []CD_KICK_5: [0x1b, 0x70, 0x01], // Sends a pulse to pin 5 []},/*** [MARGINS Margins sizes]* @type {Object}*/MARGINS: {BOTTOM: [0x1b, 0x4f], // Fix bottom sizeLEFT: [0x1b, 0x6c], // Fix left sizeRIGHT: [0x1b, 0x51], // Fix right size},/*** [IMAGE_FORMAT Image format]* @type {Object}*/IMAGE_FORMAT: {S_RASTER_N: [0x1d, 0x76, 0x30, 0x00], // Set raster image normal sizeS_RASTER_2W: [0x1d, 0x76, 0x30, 0x01], // Set raster image double widthS_RASTER_2H: [0x1d, 0x76, 0x30, 0x02], // Set raster image double heightS_RASTER_Q: [0x1d, 0x76, 0x30, 0x03], // Set raster image quadruple},/*** [BITMAP_FORMAT description]* @type {Object}*/BITMAP_FORMAT: {BITMAP_S8:[0x1b,0x2a,0x00],BITMAP_D8:[0x1b,0x2a,0x01],BITMAP_S24:[0x1b,0x2a,0x20],BITMAP_D24:[0x1b,0x2a,0x21]},/*** [GSV0_FORMAT description]* @type {Object}*/GSV0_FORMAT: {GSV0_NORMAL: [0x1d, 0x76, 0x30, 0x00],GSV0_DW: [0x1d, 0x76, 0x30, 0x01],GSV0_DH: [0x1d, 0x76, 0x30, 0x02],GSV0_DWDH: [0x1d, 0x76, 0x30, 0x03]},
};class Printer {width // 发送打印之前必须先调用setWidth方法SPDevspixelsfontVolumeconstructor(devs,width=48){this.SPDevs=devsthis.setWidth(width)}setWidth(width){this.width=parseInt(width) //如果确定传入的是number型则不需要parseIntthis.pixels=this.width*PER_MM; //打印内容的宽度,用来控制文本换行,width单位mm}//data应当是一个对象组成的数组,表示从上到下排列的内容块,一个块内格式统一print(data){if(this.SPDevs.length===0) return;let ESCPOS=this.buildESCPOS(data);this.SPDevs.forEach(dev=>{dev.port.write(ESCPOS)});}//下面的应该是私有方法,因技术条件限制暂未实现/*构造ESC/POS指令*/buildESCPOS(list){//请注意,初始化时设置了打印区域的宽度,这里预留了8mm的出血以容错,因此设置的宽度是打印内容的像素数+8mm的像素数let data=Array.from(ESC_POS.HARDWARE.INIT.concat([29,87,(this.pixels+PER_MM*8)%256,parseInt((this.pixels+PER_MM*8)/256),27,74,10]));this.fontVolume=parseInt(this.pixels/fontSize);list.forEach(i=>{// 对齐方式,align为ESC_POS.ALIGN成员键名if(i.align&&ESC_POS.ALIGN[i.align]) data.push(...ESC_POS.ALIGN[i.align]);// 颜色(如果支持的话),color为ESC_POS.COLOR成员键名if(i.color&&ESC_POS.COLOR[i.color]) data.push(...ESC_POS.COLOR[i.color]);// text和fill都是打印的字符,text指文本内容,fill指空白区域填充内容if(i.text||i.fill){//如果修改了字号,则每行字符容量也要修改if(i.size&&ESC_POS.TEXT[i.size]){data.push(...ESC_POS.TEXT[i.size])if(i.size=='D_W'||i.size=='D_W_H'){this.fontVolume=parseInt(this.pixels/fontSize/2);}else if(i.size=='NORMAL'||i.size=='D_H'){this.fontVolume=parseInt(this.pixels/fontSize);}}//需要注意的是,上面的字号及下面的加粗等文本格式设置在一次打印中是长期有效的,除非后面的内容块中修改if(i.blod&&ESC_POS.TEXT['BLOD_'+i.blod]) data.push(...ESC_POS.TEXT['FONT_'+i.blod]);if(i.font&&ESC_POS.TEXT['FONT_'+i.font]) data.push(...ESC_POS.TEXT['FONT_'+i.font]);if(i.underl&&ESC_POS.TEXT['UNDERL_'+i.underl]) data.push(...ESC_POS.TEXT['FONT_'+i.underl]);//如果文本不为空,则打印文本,否则打印填充字符,即以填充字符组成的分割带if(i.text){// 打印文本时,如果有r(right的意思),则以fill(或空格)填充中间,使r内容在同一行的末尾。这个主要用于商品名称与标价分布在首尾的场景。if(i.r) i.text+=(new Array(this.fontVolume-(charLen(i.text)+charLen(i.r))%this.fontVolume).fill(i.fill?i.fill:' ').join(''))+i.r;//如果没有r但是有fill,则表示以fill在两侧包围文本,当打印纸比打印口宽度窄很多,又想要同时实现居中对齐和侧边对齐两种需求时,可以用空格包围文本使其看起来居中,整体格式依然采取侧边对齐else if(i.fill){let count=this.fontVolume-charLen(i.text);if(count>0) i.text=(new Array(Math.ceil(count/2)).fill(i.fill).join(''))+i.text+(new Array(parseInt(count/2)).fill(i.fill).join(''));}data.push(...gbk.U2B(i.text,this.fontVolume));}else{if(this.fontVolume<charLen(i.fill)) i.fill='-';let str=new Array(parseInt(this.fontVolume/charLen(i.fill))).fill(i.fill).join('')if(charLen(str)<this.fontVolume){for(let i=charLen(str);i<this.fontVolume;i++){if(i%2==0) str+=' ';else str=' '+str;}}data.push(...gbk.U2B(str,this.fontVolume));}//10是换行符的十进制编码,line则表示换多少行}else if(typeof i.line=='number') data.push(...(new Array(i.line).fill(10)));else if(i.beep){//不知道是不是我的打印机问题,蜂鸣未生效// data.push(0x1b,0x28,0x41,0x04,0x00,0x30,0x00,0x09,0x02)}else if(typeof i.barcode=='string'){//先过滤掉任何条形码都不能支持的内容let str=i.barcode.replace(/[^\x00-\x7F]/g,'');if(!str) return true;data.push(...ESC_POS.BARCODE.HEIGHT(60));let codeLen=str.length;//然后根据内容判断选用哪种格式。没错,条形码有多种规范,不同规范可以打印的内容不太一样if(/[^\x30-\x39]/.test(str)==false){if(codeLen==7||codeLen==8) data.push(...ESC_POS.BARCODE.EAN8);else if(codeLen==11) data.push(...ESC_POS.BARCODE.UPC_A);else if(codeLen==12||codeLen==13) data.push(...ESC_POS.BARCODE.EAN13);else data.push(...ESC_POS.BARCODE[codeLen%2==0?'I25':'CODE11']);}else if(/[\x00-\x1F\x21-\x23\x26-\x2A\x2C\x3A-\x40\x5B-\x7F]/.test(str)) data.push(...ESC_POS.BARCODE.CODE93);else data.push(...ESC_POS.BARCODE[/[\x20\x25\x45-\x5A]/.test(str)?'CODE39':'CODEBAR']);data.push(...str.split('').map(c=>c.charCodeAt(0)),0x00);}else if(i.qrcode){//目前暂未找到给二维码定位的方法,只能在打印区域内调整对齐方式。如果确实想要实现定位,建议尝试后面的光栅位图方式let buffer=gbk.U2B(i.qrcode),len=buffer.length+3data.push(27,74,10)let p=parseInt(i.size)*PER_MM,poi;if(p<80){p=80}else if(p>1000){p=1000}if(len<29){poi=19}else if(len<54){poi=23}else if(len<85){poi=27}else{poi=len<119?31:35}data.push(...ESC_POS.QRCODE.SIZE(p>poi?(p/poi).toFixed():1));data.push(...ESC_POS.QRCODE.CORRECT_M);data.push(29,40,107,len%256,parseInt(len/256),49,80,48,...buffer) //可能有126字节限制data.push(29,40,107,3,0,49,81,48)data.push(27,74,10)}//打印光栅位图,后端将图像处理成点阵数据,主要用于二维码定位else if(i.raster) data.push(29,118,48,0,i.x%256,parseInt(i.x/256),i.y%256,parseInt(i.y/256),...i.raster,27,74,10);/* // 以光栅格式绘图(Function 112),暂时不可用,可能是设备支持的问题data.push(29,40,76,4,0,48,1,51,51)data.push(29,40,76,17,0,48,112,48,1,1,49,56,0,1,0,255,255,255,255,255,255,255)data.push(29,40,76,2,0,48,2)//页模式,部分指令目前设备不支持data.push(27,76) //进入页模式data.push(27,36,0,0) // X归零data.push(29,36,0,0) // Y归零,设备不支持Y向位移data.push(...ESC_POS.FF) //输出缓冲区并回到标准模式 */if(i.cut){if(ESC_POS.CUT[i.cut]) data.push(...ESC_POS.CUT[i.cut]);}});if(!list[list.length-1].cut) data.push(...ESC_POS.CUT.FULL);return data;}
}
module.exports=Printer;
如果你是PHP程序员,并且希望实现二维码定位,可以参考我之前发的一篇文章《PHP二维码类库phpqrcode改造面向对象风格》,以及下面这个方法:
public function textWithQR(string $text,string $qr,string $extra='',int $times=1){$qr=new QRcode($qr); //这个类见上方链接//我用的是76mm打印机,留6mm出血,二维码每个点宽高1mm,points属性是二维码横竖点数,因此70减点数剩下的就是空白区域的宽度,单位mm$gdBytes=70-$qr->points;$w=$gdBytes*8; //空白区域宽度$blank=array_fill(0,$gdBytes,0);$h=$qr->points*8; //空白区域高度$gd=imagecreate($w,$h);imagecolorallocate($gd,255,255,255);//这里是用GD库绘图,然后再读取每个像素点并灰度处理,writeOnImg方法是在GD对象上绘制文本,具体参考[《50行带码搞定PHP GD库绘制文本段落》](https://blog.csdn.net/warmbook/article/details/111567238)$pos=Image::writeOnImg($gd,$text,9,22,$w,0,intval($h/2)+22,3,30,'./src/font/simhei.ttf');$textBytes=intval(ceil($pos[2]/8));$textPixel=$textBytes*8;$rightBlank=array_fill(0,$gdBytes-$textBytes,0);$raster=[];for($y=0;$y<$h;$y++){if($y%8===0){$qrLine=[];for($i=0;$i<$qr->points;$i++) $qrLine[]=intval($qr->data[intval($y/8)][$i])*255;}if($y>=$pos[1]-22&&$y<$pos[1]+$pos[3]-22){$bytes=[];for($x=0;$x<$textPixel;$x++){$bits[]=imagecolorat($gd,$x,$y)>0;if($x%8===7){$byte=0;foreach($bits AS $k => $v) $byte+=$v*pow(2,7-$k);$bytes[]=$byte;$bits=[];}}array_push($raster,...$bytes,...$rightBlank);}else array_push($raster,...$blank);array_push($raster,...$qrLine);}return [['raster'=>$raster,'x'=>70,'y'=>$h]];}
如果碰巧你也在做微信小程序,那么我还封装了一个蓝牙连接的类,并且本文的第一段代码块也有特别的适配,最终使用就像下面这样方便:
const app=getApp(),Printer=require('./path/to/Printer.js')
app.globalData.printer=new Printer(res.data)
app.globalData.printer.print({ESCPOS:[{align:'C',size:25,qrcode:'二维码内容1'},{size:'D_W_H',text:'文本1'},{cut:'FULL'},{align:'C',size:25,qrcode:'二维码内容2'},{size:'D_W_H',text:'文本2'},{cut:'FULL'}]})
链接在这里:《微信小程序蓝牙热敏打印机三件套.zip》
单独的GBK中文转码模块:《gbk.js gb2312编码字符转Uint8Array,解决打印机中文乱码问题》
ESC/POS常用打印指令面向对象封装,PHP处理二维码定位,微信小程序蓝牙打印相关推荐
- 微信小程序蓝牙打印二维码
会有很多算是废话的内容,但是都是我踩的坑,顺便记录下.(不一定适用所有的打印机,) 由于这个项目我只负责二维码打印,所以前面的蓝牙连接,文字打印我就不多说了,我自己也不是很清楚.不过有一点我是要说下的 ...
- 微信小程序蓝牙打印以及打印二维码
方式一: 通过微信与机器建立连接之后然后通过指定指令去进行打印 方式二: 不使用指令,使用微信api进行打印,先要进行转码,打印中文会有一个乱码的情况可以看一下下面的链接, 乱码情况: https:/ ...
- 标签云打印/微信小程序蓝牙标签打印开放平台功能
微信小程序蓝牙标签打印/标签云打印开放平台(www.herro.cn),是在云端部署的云平台,支持开发者通过API调用完成标签蓝牙打印或标签云打印功能. 平台蓝牙打印模块支持各厂商各品牌蓝牙标签打印 ...
- android热敏打印机图片乱码,微信小程序小票打印功能(以及中文乱码的解决)...
因为业务需求,需要实现微信小程序连接热敏打印机打印小票.首先我要先知道微信小程序有没有蓝牙操作相关的API,然后就是如何蓝牙连接打印机,发送打印指令了. 通过查看小程序文档,我看到微信小程序是支持蓝牙 ...
- 微信小程序蓝牙标签打印/标签云打印开放平台(2)
微信小程序蓝牙标签打印/标签云打印开放云平台(下面简称"平台" www.herro.cn 技术服务TEL:15759216805),支持开发者通过API调用完成标签蓝牙打印或标签云 ...
- 微信小程序小票打印功能(以及中文乱码的解决)
因为业务需求,需要实现微信小程序连接热敏打印机打印小票.首先我要先知道微信小程序有没有蓝牙操作相关的API,然后就是如何蓝牙连接打印机,发送打印指令了. 通过查看小程序文档,我看到微信小程序是支持蓝牙 ...
- 微信小程序蓝牙标签打印/标签云打印开放云平台(4)
微信小程序蓝牙标签打印/标签云打印开放云平台(下面简称"平台" www.herro.cn 技术服务TEL:15759216805),支持开发者通过API调用完成标签蓝牙打印或标签云 ...
- 解决vue3-print-nb打印二维码定位(qrcode.vue) 问题
解决vue3-print-nb打印二维码定位(qrcode.vue) 问题 在使用vu3-print-nb搭配qrcode.vue产生二维码的时候,我的代码是设置了全部元素都居中显示的,效果如下 &l ...
- Android 实现系统打印机打印图片,文本,以及二维码生成与解析
打码机:扫码,生成打印自定义标签 一.Android 打印机要支持,网络.WiFi :手机与打印机在同一网络下才行,本文用WiFi连接 打开系统打印服务: 设置--更多设置--打印--选择默认打印 ...
- 封装 localStorage 缓存,兼容网页,微信小程序,uni-app
封装的缓存功能,兼容网页,微信小程序,uni-app 使用,支持设置缓存,获取缓存,移除缓存,清空缓存,设置缓存时间,分组缓存设置. 把最下面的 Str4.js 代码拷贝到项目内可以直接使用,调用方式 ...
最新文章
- Spring Cloud Config配置中心的使用
- 深度2万字好文:图像处理-基于 PyTorch 的 YOLO v5 表情识别(附源代码)
- Linux疑难杂症解决方案100篇(二十)-万字长文带你读懂正则表达式(建议收藏)
- SAP ABAP收货或者货物移动(MIGO,MB11,MB1A)在保存时候的增强点
- xmu 1254.异或求和
- 输入流输出流是以内存为标准_构建用于测试的超大内存输入流
- java在线网页客服聊天_管理员消息java 网站用户在线和客服聊天
- php 中绑定的 gd 库,为PHP添加GD库支持
- XDebug On Ubuntu
- 数据库知识点总结归纳
- Jmeter教程(图文版)
- 林轩田机器学习基石和技法资源
- Java语言这些年的发展
- 宝塔Linux面板登录的账号密码忘了怎么办?
- c语言逐语句调试和逐过程调试,逐语句调试和逐过程调试的区别
- 玩转外贸LinkedIn必备的三大特质,以及突破六度人脉技巧
- 上海基诺墙绘 中荷学生共同创作涂鸦 “We are伐木累”示好
- 2023版大数据学习路线图(适合自学)
- RK3568外设资源
- 摄像机跟随之第三人称视角(一)