HTMLify
ZPL_Code128_Label_Preview.html
Views: 18 | Author: deweyduck6116
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ZPL Code 128 Label Preview</title><style> :root{font-family:Arial,Helvetica,sans-serif;color:#17212d;background:#edf1f5}*{box-sizing:border-box}body{margin:0}header{background:#122c48;color:white;padding:17px 24px}h1{margin:0;font-size:22px}header p{margin:5px 0 0;color:#d4e6f4;font-size:14px}main{display:grid;grid-template-columns:minmax(310px,1fr) minmax(350px,1.15fr);gap:18px;padding:18px;max-width:1600px;margin:auto}.panel{background:white;border:1px solid #cbd5df;border-radius:9px;padding:17px;min-width:0}.panel h2{font-size:17px;margin:0 0 12px}textarea{width:100%;height:430px;resize:vertical;font:13px/1.5 Consolas,monospace;padding:12px;border:1px solid #91a3b5;border-radius:5px}.actions{display:flex;gap:9px;flex-wrap:wrap;margin:12px 0}button{border:0;background:#1167ac;color:#fff;border-radius:5px;padding:10px 14px;font-weight:bold;cursor:pointer}button.secondary{background:#476174}button:disabled{opacity:.55;cursor:not-allowed}.hint{color:#475569;font-size:13px;line-height:1.45}.status{font-weight:bold;margin:8px 0}.status.error{color:#b91c1c}.status.ok{color:#116535}.preview-wrap{overflow:auto;border:1px solid #cbd5df;background:repeating-conic-gradient(#eee 0% 25%,#fff 0% 50%) 0 0/18px 18px;min-height:350px;padding:20px;display:flex;align-items:flex-start;justify-content:center}canvas{background:white;max-width:none;box-shadow:0 2px 12px #0003}ul{padding-left:22px;font-size:13px;line-height:1.5;max-height:180px;overflow:auto}label{font-size:13px}input[type=number]{width:65px;padding:5px} @media(max-width:850px){main{grid-template-columns:1fr}textarea{height:300px}} @media print{header,.editor,.controls,.notes{display:none!important}body,main,.panel,.preview-wrap{background:white;border:0;padding:0;margin:0;box-shadow:none;display:block}canvas{box-shadow:none;max-width:none!important}@page{margin:0}} </style><header><h1>ZPL Code 128 Label Preview</h1><p>Paste ZPL, preview the label, then save an image or print.</p></header><main><section class="panel editor"><h2>ZPL data</h2><textarea id="source" spellcheck="false" aria-label="Paste ZPL data" placeholder="Paste ^XA ... ^XZ here"></textarea><div class="actions"><button id="render">Show label</button><button id="example" class="secondary">Load example</button></div><p class="hint">Everything runs in this HTML file. Your label data stays on your device.</p></section><section class="panel"><h2>Preview</h2><div class="controls"><label>Screen zoom <input id="zoom" type="number" min="25" max="400" value="100">%</label><div class="actions"><button id="png" class="secondary" disabled>Save PNG</button><button id="print" class="secondary" disabled>Print</button></div></div><div id="status" class="status">Paste a label to begin.</div><div class="preview-wrap"><canvas id="label" aria-label="Rendered label"></canvas></div><div class="notes"><p class="hint" id="dimensions"></p><strong>Preview notes</strong><ul id="warnings"></ul><p class="hint">Use the same printer resolution and media settings for final print checks. This file previews common text and Code 128 ZPL commands; it reports unsupported commands below.</p></div></section></main><script> 'use strict'; const patterns=["BaBbBb","BbBaBb","BbBbBa","AbAbBc","AbAcBb","AcAbBb","AbBbAc","AbBcAb","AcBbAb","BbAbAc","BbAcAb","BcAbAb","AaBbCb","AbBaCb","AbBbCa","AaCbBb","AbCaBb","AbCbBa","BbCbAa","BbAaCb","BbAbCa","BaCbAb","BbCaAb","CaBaCa","CaAbBb","CbAaBb","CbAbBa","CaBbAb","CbBaAb","CbBbAa","BaBaBc","BaBcBa","BcBaBa","AaAcBc","AcAaBc","AcAcBa","AaBcAc","AcBaAc","AcBcAa","BaAcAc","BcAaAc","BcAcAa","AaBaCc","AaBcCa","AcBaCa","AaCaBc","AaCcBa","AcCaBa","CaCaBa","BaAcCa","BcAaCa","BaCaAc","BaCcAa","BaCaCa","CaAaBc","CaAcBa","CcAaBa","CaBaAc","CaBcAa","CcBaAa","CaDaAa","BbAdAa","DcAaAa","AaAbBd","AaAdBb","AbAaBd","AbAdBa","AdAaBb","AdAbBa","AaBbAd","AaBdAb","AbBaAd","AbBdAa","AdBaAb","AdBbAa","BdAbAa","BbAaAd","DaCaAa","BdAaAb","AcDaAa","AaAbDb","AbAaDb","AbAbDa","AaDbAb","AbDaAb","AbDbAa","DaAbAb","DbAaAb","DbAbAa","BaBaDa","BaDaBa","DaBaBa","AaAaDc","AaAcDa","AcAaDa","AaDaAc","AaDcAa","DaAaAc","DaAcAa","AaCaDa","AaDaCa","CaAaDa","DaAaCa","BaAdAb","BaAbAd","BaAbCb","BcCaAaB"]; const $=id=>document.getElementById(id), canvas=$('label'), ctx=canvas.getContext('2d'); const example=`^XA ^PW600 ^LL300 ^FO25,20^A0N,30,30^FDROOM IE3^FS ^FO25,65^BY2,3,95^BCN,95,Y,N,N^FD123456^FS ^FO25,220^A0N,25,25^FDProduct 123456 | Qty: 12^FS ^XZ`; function tokens(s){let out=[],rx=/[\^~][A-Za-z0-9@\&]{1,2}/g,m,last=0;while((m=rx.exec(s))){if(m.index>last&&out.length)out[out.length-1].arg+=s.slice(last,m.index);out.push({cmd:m[0],arg:''});last=rx.lastIndex}if(last<s.length&&out.length)out[out.length-1].arg+=s.slice(last);return out} function number(s,d){let n=Number(s);return s==null||s.trim()===''||!Number.isFinite(n)?d:n} function parse(s){const t=tokens(s),warnings=[],labels=[],unsupported=new Set;let state=null,field=null; function note(cmd){if(!unsupported.has(cmd)){unsupported.add(cmd);warnings.push('Unsupported command '+cmd+'; its effect is not shown.')}} function fresh(){state={width:0,height:0,origin:[0,0],shift:0,font:{h:30,w:30,face:'0',rot:'N'},barcode:{w:2,ratio:3,h:100},reverse:false,items:[],ci:28};field=null} function commit(){if(!field)return;let raw=field.data||'';if(field.hex){raw=raw.replace(new RegExp(field.hex+'([0-9a-fA-F]{2})','g'),(_,hex)=>String.fromCharCode(parseInt(hex,16)))}state.items.push({...field,data:raw});field=null} for(const {cmd,arg} of t){if(cmd==='^XA'){if(state){commit();labels.push(state)}fresh();continue}if(!state)continue;let a=arg.split(',');switch(cmd){case '^XZ':commit();labels.push(state);state=null;break;case '^PW':state.width=number(arg,0);break;case '^LL':state.height=number(arg,0);break;case '^LH':state.origin=[number(a[0],0),number(a[1],0)];break;case '^LS':state.shift=number(arg,0);break;case '^CI':state.ci=number(arg,28);if(![0,27,28].includes(state.ci))warnings.push('Character encoding ^CI'+state.ci+' may differ from printer output.');break;case '^FO':case '^FT':commit();field={x:number(a[0],0),y:number(a[1],0),baseline:cmd==='^FT',font:{...state.font},barcode:null,data:'',block:null,reverse:state.reverse,hex:null};break;case '^A0':case '^AN':case '^AA':case '^AB':case '^AC':case '^AD':case '^AE':case '^AF':case '^AG':case '^AH':case '^AI':case '^AJ':case '^AK':case '^AL':case '^AM':case '^AP':case '^AQ':case '^AR':case '^AS':case '^AT':case '^AU':case '^AV':case '^AW':case '^AX':case '^AY':case '^AZ':{let f={face:cmd.slice(2),rot:a[0]||'N',h:number(a[1],30),w:number(a[2],number(a[1],30))};if(field)field.font=f;else state.font=f;if(f.face!=='0')warnings.push('Font '+f.face+' uses an approximate browser font.');break}case '^CF':state.font={face:a[0]||'0',rot:'N',h:number(a[1],30),w:number(a[2],number(a[1],30))};break;case '^BY':state.barcode={w:number(a[0],2),ratio:number(a[1],3),h:number(a[2],100)};break;case '^BC':if(!field)field={x:0,y:0,baseline:false,font:{...state.font},data:'',block:null,reverse:state.reverse,hex:null};field.barcode={...state.barcode,rot:a[0]||'N',h:number(a[1],state.barcode.h),read:(a[2]||'Y')!=='N',above:(a[3]||'N')==='Y',check:(a[4]||'N')==='Y',mode:a[5]||'N'};break;case '^FB':if(field)field.block={width:number(a[0],0),lines:number(a[1],1),gap:number(a[2],0),align:a[3]||'L',indent:number(a[4],0)};break;case '^FH':if(field)field.hex=(arg.trim()||'_')[0];break;case '^FD':if(!field)field={x:0,y:0,baseline:false,font:{...state.font},data:'',block:null,reverse:state.reverse,hex:null};field.data+=arg;break;case '^FS':commit();break;case '^FR':if(field)field.reverse=true;else state.reverse=true;break;case '^FX':case '^CI':break;case '^MD':case '^PR':case '^SN':case '^JU':case '^JUS':break;default:note(cmd)}}if(state){commit();labels.push(state)}if(labels.length>1)warnings.push('Multiple labels detected; showing the first label only.');return {label:labels[0],warnings}} function encode128(raw,warnings){let codes=[],subset='B',i=0;function switchTo(s){if(!codes.length)codes.push(s==='C'?105:104);else if(subset!==s)codes.push(s==='C'?99:100);subset=s}switchTo('B');while(i<raw.length){if(raw[i]==='>'&&i+1<raw.length){const c=raw[i+1];if(c==='>'){raw=raw.slice(0,i)+'>'+raw.slice(i+2)}else if(c===';'){switchTo('C');i+=2;continue}else if(c===':'){switchTo('B');i+=2;continue}else if(c==='9'){codes.push(102);i+=2;continue}else if(c==='8'){codes.push(98);i+=2;continue}else{warnings.push('Barcode control >'+c+' needs printer verification.');i+=2;continue}}let run=(raw.slice(i).match(/^\d+/)||[''])[0].length;if(subset==='B'&&run>=4){let len=run-(run%2);if(len>=4){switchTo('C');continue}}if(subset==='C'){if(run>=2){codes.push(Number(raw.slice(i,i+2)));i+=2;continue}switchTo('B');continue}let n=raw.charCodeAt(i);if(n>=32&&n<=126)codes.push(n-32);else{warnings.push('Character '+JSON.stringify(raw[i])+' cannot be encoded in Code 128 subset B; check the barcode.');codes.push(0)}i++}if(codes.length===1)throw Error('Barcode contains no data.');let checksum=codes[0];for(let j=1;j<codes.length;j++)checksum+=codes[j]*j;codes.push(checksum%103,106);return codes} function barcode(ctx,item,x,y,warnings){let b=item.barcode;if(!['N','R','I','B'].includes(b.rot))warnings.push('Unsupported barcode orientation '+b.rot+'.');let codes=encode128(item.data,warnings),modules=[];for(let code of codes)for(let char of patterns[code])modules.push({bar:char>='A'&&char<='D',width:char.toLowerCase().charCodeAt(0)-96});let bw=Math.max(.5,b.w),height=Math.max(1,b.h),bars=modules.reduce((a,m)=>a+m.width,0)*bw; ctx.save();ctx.translate(x,y);let rot={N:0,R:Math.PI/2,I:Math.PI,B:-Math.PI/2}[b.rot]||0;ctx.rotate(rot);let top=b.read&&b.above?Math.max(18,bw*9):0;ctx.fillStyle='#000';let cursor=0;for(let m of modules){if(m.bar)ctx.fillRect(cursor,top,m.width*bw,height);cursor+=m.width*bw}if(b.read){ctx.font=`${Math.max(14,Math.min(28,bw*9))}px Arial`;ctx.textAlign='center';ctx.textBaseline='top';ctx.fillText(item.data.replace(/>;|>:|>9/g,''),bars/2,b.above?0:top+height+5,bars)}ctx.restore();return {width:bars,height:top+height+(b.read&&!b.above?Math.max(25,bw*12):0)}} function drawText(ctx,item,x,y,warnings){let f=item.font,h=Math.max(1,f.h),w=Math.max(1,f.w),lines=item.data.replace(/\&/g,'\n').split('\n');ctx.save();ctx.translate(x,y);ctx.rotate(({N:0,R:Math.PI/2,I:Math.PI,B:-Math.PI/2})[f.rot]||0);let scale=w/h;ctx.scale(scale,1);ctx.font=`${h}px Arial`;ctx.textBaseline=item.baseline?'alphabetic':'top';ctx.fillStyle='#000';let step=h+(item.block?.gap||0);if(item.block){let max=item.block.lines||lines.length;lines=lines.slice(0,max)}for(let i=0;i<lines.length;i++){let line=lines[i],offset=i===0?(item.block?.indent||0):0;if(item.block?.width){let bound=item.block.width/scale;ctx.textAlign=item.block.align==='C'?'center':item.block.align==='R'?'right':'left';let ax=item.block.align==='C'?bound/2:item.block.align==='R'?bound:offset;ctx.fillText(line,ax,i*step,bound)}else{ctx.textAlign='left';ctx.fillText(line,0,i*step)}}ctx.restore()} function render(){let source=$('source').value,status=$('status'),list=$('warnings');list.replaceChildren();if(!source.trim()){status.textContent='Paste ZPL data to begin.';status.className='status';return}try{let {label,warnings}=parse(source);if(!label)throw Error('No ^XA ... ^XZ label found.');if(!label.items.length)throw Error('No drawable fields found.');let maxX=0,maxY=0;for(let it of label.items){maxX=Math.max(maxX,it.x+(it.barcode?400:Math.max(80,it.data.length*it.font.w*.7)));maxY=Math.max(maxY,it.y+(it.barcode?it.barcode.h+45:it.font.h*2))}let W=Math.round(label.width||Math.max(400,maxX+20)),H=Math.round(label.height||Math.max(200,maxY+20));if(W<1||H<1||W>8000||H>8000)throw Error('Label dimensions must be between 1 and 8000 dots.');canvas.width=W;canvas.height=H;ctx.fillStyle='#fff';ctx.fillRect(0,0,W,H);for(let item of label.items){let x=item.x+label.origin[0]+label.shift,y=item.y+label.origin[1];if(item.reverse)warnings.push('Reverse print ^FR is not reproduced.');if(item.barcode)barcode(ctx,item,x,y,warnings);else drawText(ctx,item,x,y,warnings)}let unique=[...new Set(warnings)];for(let w of unique){let li=document.createElement('li');li.textContent=w;list.append(li)}if(!unique.length){let li=document.createElement('li');li.textContent='No unsupported commands detected.';list.append(li)}status.textContent=`Rendered ${label.items.length} field(s). ${unique.length?unique.length+' preview note(s).':'No warnings.'}`;status.className='status ok';$('dimensions').textContent=`Label: ${W} × ${H} printer dots. Barcode width follows ^BY; print size depends on printer dots per inch.`;$('png').disabled=$('print').disabled=false;zoom()}catch(e){status.textContent='Cannot render: '+e.message;status.className='status error';$('png').disabled=$('print').disabled=true}} function zoom(){canvas.style.width=(canvas.width*Number($('zoom').value||100)/100)+'px';canvas.style.height=(canvas.height*Number($('zoom').value||100)/100)+'px'} $('render').onclick=render;$('example').onclick=()=>{$('source').value=example;render()};$('zoom').oninput=zoom;$('png').onclick=()=>{let a=document.createElement('a');a.href=canvas.toDataURL('image/png');a.download='zpl-label.png';a.click()};$('print').onclick=()=>window.print(); </script></html> |