UNPKG

@opuu/cat-printer

Version:

SDK to interact with chinese non standard thermal printers (cat printers) over bluetooth

3 lines (2 loc) 10.5 kB
(function(d,m){typeof exports=="object"&&typeof module<"u"?m(exports):typeof define=="function"&&define.amd?define(["exports"],m):(d=typeof globalThis<"u"?globalThis:d||self,m(d["cat-printer"]={}))})(this,function(d){"use strict";var V=Object.defineProperty;var H=(d,m,b)=>m in d?V(d,m,{enumerable:!0,configurable:!0,writable:!0,value:b}):d[m]=b;var S=(d,m,b)=>H(d,typeof m!="symbol"?m+"":m,b);var E=(s=>(s[s.ApplyEnergy=190]="ApplyEnergy",s[s.GetDeviceState=163]="GetDeviceState",s[s.GetDeviceInfo=168]="GetDeviceInfo",s[s.UpdateDevice=169]="UpdateDevice",s[s.SetDpi=164]="SetDpi",s[s.Lattice=166]="Lattice",s[s.Retract=160]="Retract",s[s.Feed=161]="Feed",s[s.Speed=189]="Speed",s[s.Energy=175]="Energy",s[s.Bitmap=162]="Bitmap",s))(E||{}),D=(s=>(s[s.Transfer=0]="Transfer",s[s.Response=1]="Response",s))(D||{});function C(s){let e=0;for(const t of s){e^=t;for(let i=0;i<8;i++)e&128?e=e<<1^7:e<<=1;e&=255}return e}function T(s,e=128){const t=new Uint8Array(Math.ceil(s.length/8));for(let i=0;i<s.length;i++){const r=Math.floor(i/8),o=i%8,n=s[i],h=n&255,a=n>>8&255,c=n>>16&255;(h+a+c)/3<e&&(t[r]|=1<<o)}return t}class R{dither(e,t){const i=e.data,r=t.brightness!==void 0?t.brightness:128;for(let o=0;o<i.length;o+=4){const h=(i[o]+i[o+1]+i[o+2])/3<r?0:255;i[o]=i[o+1]=i[o+2]=h}return e}}class I{constructor(){S(this,"thresholdMap",[[0,2],[3,1]])}dither(e,t){const i=e.data,r=e.width,o=e.height;for(let n=0;n<o;n++)for(let h=0;h<r;h++){const a=(n*r+h)*4,c=i[a],l=i[a+1],u=i[a+2],y=(c+l+u)/3,_=(this.thresholdMap[n%2][h%2]+1)*(255/5),g=y<_?0:255;i[a]=i[a+1]=i[a+2]=g}return e}}class P{dither(e,t){const i=e.data,r=e.width,o=e.height;for(let n=0;n<o;n++)for(let h=0;h<r;h++){const a=(n*r+h)*4,c=i[a],l=i[a+1],u=i[a+2],_=(c+l+u)/3<128?0:255,g=_,w=_,A=_;i[a]=g,i[a+1]=w,i[a+2]=A;const v=c-g,f=l-w,p=u-A;this.distributeError(i,r,h+1,n,v*7/16,f*7/16,p*7/16),this.distributeError(i,r,h-1,n+1,v*3/16,f*3/16,p*3/16),this.distributeError(i,r,h,n+1,v*5/16,f*5/16,p*5/16),this.distributeError(i,r,h+1,n+1,v*1/16,f*1/16,p*1/16)}return e}distributeError(e,t,i,r,o,n,h){if(i>=0&&i<t&&r>=0&&r<e.length/(t*4)){const a=(r*t+i)*4;e[a]=Math.max(0,Math.min(255,e[a]+o)),e[a+1]=Math.max(0,Math.min(255,e[a+1]+n)),e[a+2]=Math.max(0,Math.min(255,e[a+2]+h))}}}class F{dither(e,t){const i=e.data,r=e.width,o=e.height,n=4,h=new Uint8ClampedArray(i.length);for(let a=3;a<i.length;a+=4)h[a]=i[a];for(let a=0;a<o;a++)for(let c=0;c<r;c++){const l=(a*r+c)*4,u=(i[l]+i[l+1]+i[l+2])/3,y=Math.floor((255-u)/50),_=Math.floor(c/n)*n,g=Math.floor(a/n)*n,w=c-_,A=a-g,f=Math.sqrt((w-n/2)**2+(A-n/2)**2)<y?0:255;h[l]=h[l+1]=h[l+2]=f}return new ImageData(h,r,o)}}const x=Object.freeze(Object.defineProperty({__proto__:null,BayerDithering:I,DotDithering:F,FloydSteinbergDithering:P,ThresholdDithering:R},Symbol.toStringTag,{value:"Module"}));class N{constructor(e={}){S(this,"device",null);S(this,"server",null);S(this,"txCharacteristic",null);S(this,"rxCharacteristic",null);S(this,"printerState",{outOfPaper:!1,coverOpen:!1,overheat:!1,lowPower:!1,paused:!1,busy:!1});S(this,"options");S(this,"modelName","");this.options={debug:!1,speed:32,energy:24e3,finishFeed:100,...e}}async connect(){if(!navigator.bluetooth)throw new Error("Bluetooth API is not available in this browser");try{if(this.device=await navigator.bluetooth.requestDevice({filters:[{services:[44848]}],optionalServices:[44592]}),this.modelName=this.device.name||"",this.log(`Connected to printer model: ${this.modelName}`),!this.device.gatt)throw new Error("Bluetooth GATT not available");this.server=await this.device.gatt.connect();const e=await this.server.getPrimaryService(44592);return this.txCharacteristic=await e.getCharacteristic(44545),this.rxCharacteristic=await e.getCharacteristic(44546),await this.rxCharacteristic.startNotifications(),this.rxCharacteristic.addEventListener("characteristicvaluechanged",this.handleNotification.bind(this)),await this.getDeviceState(),await this.prepare(this.options.speed,this.options.energy),this.printerState}catch(e){throw console.error("Connection error:",e),e}}async disconnect(){if(this.rxCharacteristic)try{await this.rxCharacteristic.stopNotifications()}catch(e){this.log("Error stopping notifications:",e)}this.server&&this.server.connected&&this.server.disconnect(),this.device=null,this.server=null,this.txCharacteristic=null,this.rxCharacteristic=null,this.log("Disconnected from printer")}getState(){return{...this.printerState}}isConnected(){var e;return((e=this.server)==null?void 0:e.connected)===!0&&this.txCharacteristic!==null}async printText(e,t={}){if(!this.isConnected())throw new Error("Printer not connected");const r={...{fontFamily:"sans-serif",fontSize:16,align:"start",lineSpacing:8,rotate:0,flipH:!1,flipV:!1,brightness:128,offset:0},...t},o=await this.textToBitmap(e,r);r.offset&&r.offset!==0&&(await this.setSpeed(8),r.offset>0?await this.feed(r.offset):await this.retract(-r.offset),await this.setSpeed(this.options.speed)),await this.printBitmap(o)}async printImage(e,t={}){if(!this.isConnected())throw new Error("Printer not connected");const r={...{dither:"bayer",rotate:0,flipH:!1,flipV:!1,brightness:128,offset:0},...t},o=await this.imageToBitmap(e,r);r.offset&&r.offset!==0&&(await this.setSpeed(8),r.offset>0?await this.feed(r.offset):await this.retract(-r.offset),await this.setSpeed(this.options.speed)),await this.printBitmap(o)}async printMultiple(e){if(!this.isConnected())throw new Error("Printer not connected");await this.prepare(this.options.speed,this.options.energy);for(const t of e)t.type==="text"?await this.printText(t.content,t.options):t.type==="image"&&await this.printImage(t.content,t.options);await this.finish(this.options.finishFeed)}async feed(e){if(e<=0)return;const t=this.makeCommand(E.Feed,new Uint8Array([e&255,e>>8&255]));await this.write(t)}async retract(e){if(e<=0)return;const t=this.makeCommand(E.Retract,new Uint8Array([e&255,e>>8&255]));await this.write(t)}async setSpeed(e){const t=this.makeCommand(E.Speed,new Uint8Array([e]));await this.write(t)}async setEnergy(e){const t=new Uint8Array([e&255,e>>8&255,e>>16&255,e>>24&255]),i=this.makeCommand(E.Energy,t);await this.write(i)}async applyEnergy(){const e=this.makeCommand(E.ApplyEnergy,new Uint8Array([1]));await this.write(e)}async getDeviceState(){const e=this.makeCommand(E.GetDeviceState,new Uint8Array([1]));return await this.write(e),this.printerState}async prepare(e,t){await this.setSpeed(e),await this.setEnergy(t),await this.applyEnergy()}async finish(e=0){e>0&&(await this.setSpeed(8),await this.feed(e))}async draw(e){if(!this.isConnected())throw new Error("Printer not connected");const t=this.makeCommand(E.Bitmap,e);await this.write(t)}async printBitmap(e){const{width:t,height:i,data:r}=e,o=Math.ceil(t/8);for(let n=0;n<i;n++){const h=n*o,a=h+o,c=r.slice(h,a);c.every(l=>l===0)||await this.draw(c)}}makeCommand(e,t,i=D.Transfer){return new Uint8Array([81,120,e,i,t.length&255,t.length>>8,...t,C(t),255])}async write(e){if(!this.txCharacteristic)throw new Error("Printer not properly connected");this.log("Sending data:",Array.from(e).map(t=>t.toString(16).padStart(2,"0")).join(" "));try{await this.txCharacteristic.writeValueWithoutResponse(e)}catch(t){throw this.log("Error writing to printer:",t),t}}handleNotification(e){const t=e.target;if(!t.value)return;const i=new Uint8Array(t.value.buffer);if(this.log("Received notification:",Array.from(i).map(r=>r.toString(16).padStart(2,"0")).join(" ")),i.length>=7&&i[2]===E.GetDeviceState){const r=i[6];this.printerState={outOfPaper:!!(r&1),coverOpen:!!(r&2),overheat:!!(r&4),lowPower:!!(r&8),paused:!!(r&16),busy:!!(r&128)},this.log("Printer state updated:",this.printerState)}}async textToBitmap(e,t){const i=document.createElement("canvas"),r=i.getContext("2d");i.width=384,i.height=500,r.fillStyle="white",r.fillRect(0,0,i.width,i.height),r.fillStyle="black";const o=t.fontWeight?`${t.fontWeight} `:"";r.font=`${o}${t.fontSize}px ${t.fontFamily}`,r.textAlign=t.align==="start"?"left":t.align==="end"?"right":t.align;const n=e.split(` `),h=t.fontSize+(t.lineSpacing||8);let a=t.fontSize;for(const g of n){const w=t.align==="start"?0:t.align==="end"?i.width:i.width/2;r.fillText(g,w,a),a+=h}const c=document.createElement("canvas");c.width=i.width,c.height=a;const l=c.getContext("2d");if(l.drawImage(i,0,0),t.rotate!==0||t.flipH||t.flipV){const g=document.createElement("canvas");g.width=c.width,g.height=c.height;const w=g.getContext("2d");w.save(),w.translate(g.width/2,g.height/2),t.rotate&&t.rotate!==0&&w.rotate(t.rotate*Math.PI/180),t.flipH&&w.scale(-1,1),t.flipV&&w.scale(1,-1),w.drawImage(c,-c.width/2,-c.height/2),w.restore(),c.width=g.width,c.height=g.height,l.drawImage(g,0,0)}const u=l.getImageData(0,0,c.width,c.height),y=new Uint32Array(u.data.buffer),_=T(y);return{width:c.width,height:c.height,data:_}}async imageToBitmap(e,t){const i=new Image;i.crossOrigin="anonymous";const o=await new Promise((f,p)=>{i.onload=()=>f(i),i.onerror=()=>p(new Error(`Failed to load image: ${e}`)),i.src=e}),n=document.createElement("canvas"),h=n.getContext("2d"),a=384,c=o.width/o.height,l=Math.floor(a/c);if(n.width=a,n.height=l,h.fillStyle="white",h.fillRect(0,0,n.width,n.height),h.drawImage(o,0,0,a,l),t.rotate!==0||t.flipH||t.flipV){const f=document.createElement("canvas");f.width=n.width,f.height=n.height;const p=f.getContext("2d");p.save(),p.translate(f.width/2,f.height/2),t.rotate&&t.rotate!==0&&p.rotate(t.rotate*Math.PI/180),t.flipH&&p.scale(-1,1),t.flipV&&p.scale(1,-1),p.drawImage(n,-n.width/2,-n.height/2),p.restore(),n.width=f.width,n.height=f.height,h.drawImage(f,0,0)}const u=h.getImageData(0,0,n.width,n.height);let y=u;t.dither&&t.dither!=="none"&&(t.dither==="threshold"?y=new R().dither(u,t):t.dither==="bayer"?y=new I().dither(u,t):t.dither==="floyd-steinberg"?y=new P().dither(u,t):t.dither==="dot"&&(y=new F().dither(u,t)),h.putImageData(y,0,0));const _=h.getImageData(0,0,n.width,n.height),g=new Uint32Array(_.data.buffer);let w=128;(t.dither==="threshold"||t.dither==="none")&&(w=t.brightness!==void 0?t.brightness:128);const A=T(g,w),v=n.toDataURL("image/png");return{width:n.width,height:n.height,data:A,dataurl:v}}log(...e){this.options.debug&&console.log("[CatPrinter]",...e)}}d.CAT_ADV_SRV=44848,d.CAT_PRINT_RX_CHAR=44546,d.CAT_PRINT_SRV=44592,d.CAT_PRINT_TX_CHAR=44545,d.CatPrinter=N,d.Command=E,d.CommandType=D,d.DEF_CANVAS_WIDTH=384,d.DEF_ENERGY=24e3,d.DEF_FINISH_FEED=100,d.DEF_SPEED=32,d.Dithering=x,d.crc8=C,d.rgbaToBits=T,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});