ESC/POS常用打印指令面向对象封装,PHP处理二维码定位,微信小程序蓝牙打印
生活随笔
收集整理的這篇文章主要介紹了
ESC/POS常用打印指令面向对象封装,PHP处理二维码定位,微信小程序蓝牙打印
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
熱敏小票/標(biāo)簽打印機(jī),使用ESC/POS指令打印,常用指令封裝,適用于GBK編碼
const PER_MM=8,//每毫米像素?cái)?shù) fontSize=12,//每字符像素?cái)?shù) gbk=require('./gbk'),//兼容中文的字符轉(zhuǎn)換庫(kù),文末附鏈接 /*計(jì)算字符串長(zhǎng)度(1個(gè)中文=2個(gè)字符)*/ 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這段是抄來(lái)的,略作調(diào)整 ESC_POS={ALIGN:{C: [0x1b, 0x61, 0x01], // 居中L: [0x1b, 0x61, 0x00], // 左對(duì)齊R: [0x1b, 0x61, 0x02], // 右對(duì)齊},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], //默認(rèn)行高,30點(diǎn)LS_SET(size){return [0x1b,0x33,size]} //size點(diǎn)行高},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 變長(zhǎng) 32,36,37,43,45-57,65-90I25: [0x1d, 0x6b, 0x05], // 0x46 偶數(shù) 48-57 (ITF)CODEBAR: [0x1d, 0x6b, 0x06], // 0x47 變長(zhǎng) 36,43,45-58,65-68 (NW7)CODE93: [0x1d, 0x6b, 0x07], // 0x48 變長(zhǎng) 0-127CODE128: [0x1d, 0x6b, 0x08], // 0x49 變長(zhǎng) 0-127CODE11: [0x1d, 0x6b, 0x09], // 0x4a 變長(zhǎng) 48-57MSI: [0x1d, 0x6b, 0x0a], // 0x4b 變長(zhǎng) 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,默認(rèn)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 // 發(fā)送打印之前必須先調(diào)用setWidth方法SPDevspixelsfontVolumeconstructor(devs,width=48){this.SPDevs=devsthis.setWidth(width)}setWidth(width){this.width=parseInt(width) //如果確定傳入的是number型則不需要parseIntthis.pixels=this.width*PER_MM; //打印內(nèi)容的寬度,用來(lái)控制文本換行,width單位mm}//data應(yīng)當(dāng)是一個(gè)對(duì)象組成的數(shù)組,表示從上到下排列的內(nèi)容塊,一個(gè)塊內(nèi)格式統(tǒng)一print(data){if(this.SPDevs.length===0) return;let ESCPOS=this.buildESCPOS(data);this.SPDevs.forEach(dev=>{dev.port.write(ESCPOS)});}//下面的應(yīng)該是私有方法,因技術(shù)條件限制暫未實(shí)現(xiàn)/*構(gòu)造ESC/POS指令*/buildESCPOS(list){//請(qǐng)注意,初始化時(shí)設(shè)置了打印區(qū)域的寬度,這里預(yù)留了8mm的出血以容錯(cuò),因此設(shè)置的寬度是打印內(nèi)容的像素?cái)?shù)+8mm的像素?cái)?shù)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=>{// 對(duì)齊方式,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指文本內(nèi)容,fill指空白區(qū)域填充內(nèi)容if(i.text||i.fill){//如果修改了字號(hào),則每行字符容量也要修改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);}}//需要注意的是,上面的字號(hào)及下面的加粗等文本格式設(shè)置在一次打印中是長(zhǎng)期有效的,除非后面的內(nèi)容塊中修改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){// 打印文本時(shí),如果有r(right的意思),則以fill(或空格)填充中間,使r內(nèi)容在同一行的末尾。這個(gè)主要用于商品名稱與標(biāo)價(jià)分布在首尾的場(chǎng)景。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;//如果沒(méi)有r但是有fill,則表示以fill在兩側(cè)包圍文本,當(dāng)打印紙比打印口寬度窄很多,又想要同時(shí)實(shí)現(xiàn)居中對(duì)齊和側(cè)邊對(duì)齊兩種需求時(shí),可以用空格包圍文本使其看起來(lái)居中,整體格式依然采取側(cè)邊對(duì)齊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是換行符的十進(jìn)制編碼,line則表示換多少行}else if(typeof i.line=='number') data.push(...(new Array(i.line).fill(10)));else if(i.beep){//不知道是不是我的打印機(jī)問(wèn)題,蜂鳴未生效// data.push(0x1b,0x28,0x41,0x04,0x00,0x30,0x00,0x09,0x02)}else if(typeof i.barcode=='string'){//先過(guò)濾掉任何條形碼都不能支持的內(nèi)容let str=i.barcode.replace(/[^\x00-\x7F]/g,'');if(!str) return true;data.push(...ESC_POS.BARCODE.HEIGHT(60));let codeLen=str.length;//然后根據(jù)內(nèi)容判斷選用哪種格式。沒(méi)錯(cuò),條形碼有多種規(guī)范,不同規(guī)范可以打印的內(nèi)容不太一樣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){//目前暫未找到給二維碼定位的方法,只能在打印區(qū)域內(nèi)調(diào)整對(duì)齊方式。如果確實(shí)想要實(shí)現(xiàn)定位,建議嘗試后面的光柵位圖方式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字節(jié)限制data.push(29,40,107,3,0,49,81,48)data.push(27,74,10)}//打印光柵位圖,后端將圖像處理成點(diǎn)陣數(shù)據(jù),主要用于二維碼定位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),暫時(shí)不可用,可能是設(shè)備支持的問(wèn)題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)//頁(yè)模式,部分指令目前設(shè)備不支持data.push(27,76) //進(jìn)入頁(yè)模式data.push(27,36,0,0) // X歸零data.push(29,36,0,0) // Y歸零,設(shè)備不支持Y向位移data.push(...ESC_POS.FF) //輸出緩沖區(qū)并回到標(biāo)準(zhǔn)模式 */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程序員,并且希望實(shí)現(xiàn)二維碼定位,可以參考我之前發(fā)的一篇文章《PHP二維碼類庫(kù)phpqrcode改造面向?qū)ο箫L(fēng)格》,以及下面這個(gè)方法:
public function textWithQR(string $text,string $qr,string $extra='',int $times=1){$qr=new QRcode($qr); //這個(gè)類見(jiàn)上方鏈接//我用的是76mm打印機(jī),留6mm出血,二維碼每個(gè)點(diǎn)寬高1mm,points屬性是二維碼橫豎點(diǎn)數(shù),因此70減點(diǎn)數(shù)剩下的就是空白區(qū)域的寬度,單位mm$gdBytes=70-$qr->points;$w=$gdBytes*8; //空白區(qū)域?qū)挾?/span>$blank=array_fill(0,$gdBytes,0);$h=$qr->points*8; //空白區(qū)域高度$gd=imagecreate($w,$h);imagecolorallocate($gd,255,255,255);//這里是用GD庫(kù)繪圖,然后再讀取每個(gè)像素點(diǎn)并灰度處理,writeOnImg方法是在GD對(duì)象上繪制文本,具體參考[《50行帶碼搞定PHP GD庫(kù)繪制文本段落》](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]];}如果碰巧你也在做微信小程序,那么我還封裝了一個(gè)藍(lán)牙連接的類,并且本文的第一段代碼塊也有特別的適配,最終使用就像下面這樣方便:
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:'二維碼內(nèi)容1'},{size:'D_W_H',text:'文本1'},{cut:'FULL'},{align:'C',size:25,qrcode:'二維碼內(nèi)容2'},{size:'D_W_H',text:'文本2'},{cut:'FULL'}]})鏈接在這里:《微信小程序藍(lán)牙熱敏打印機(jī)三件套.zip》
單獨(dú)的GBK中文轉(zhuǎn)碼模塊:《gbk.js gb2312編碼字符轉(zhuǎn)Uint8Array,解決打印機(jī)中文亂碼問(wèn)題》
總結(jié)
以上是生活随笔為你收集整理的ESC/POS常用打印指令面向对象封装,PHP处理二维码定位,微信小程序蓝牙打印的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 任务管理器中的PID是什么 怎么查看
- 下一篇: 数学管理联考-无限循环小数如何转化为分数