From b2e7441a60e9d0fa9bda8a95fac61958a14cef85 Mon Sep 17 00:00:00 2001 From: Paul Donald Date: Thu, 2 May 2024 01:21:57 +0200 Subject: [PATCH] luci-lib-uqr: introduce JS library Initial idea by Christian Marangi Signed-off-by: Paul Donald --- libs/luci-lib-uqr/Makefile | 11 +++++++ .../htdocs/luci-static/resources/uqr.js | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 libs/luci-lib-uqr/Makefile create mode 100644 libs/luci-lib-uqr/htdocs/luci-static/resources/uqr.js diff --git a/libs/luci-lib-uqr/Makefile b/libs/luci-lib-uqr/Makefile new file mode 100644 index 0000000000..688fdef407 --- /dev/null +++ b/libs/luci-lib-uqr/Makefile @@ -0,0 +1,11 @@ +# This is free software, licensed under the MIT License. + +include $(TOPDIR)/rules.mk + +PKG_LICENSE=MIT + +LUCI_TITLE:=µQR - micro QR: SVG QRcode generator JS library + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/libs/luci-lib-uqr/htdocs/luci-static/resources/uqr.js b/libs/luci-lib-uqr/htdocs/luci-static/resources/uqr.js new file mode 100644 index 0000000000..53ed03750f --- /dev/null +++ b/libs/luci-lib-uqr/htdocs/luci-static/resources/uqr.js @@ -0,0 +1,33 @@ +// MIT License + +// Copyright (c) Project Nayuki +// Copyright (c) 2023 Anthony Fu +// Minified and modified/stripped of not useful components +'use strict'; +var QrCodeDataType=(t=>(t[t.Border=-1]="Border",t[t.Data=0]="Data",t[t.Function=1]="Function",t[t.Position=2]="Position",t[t.Timing=3]="Timing",t[t.Alignment=4]="Alignment",t))(QrCodeDataType||{}),__defProp=Object.defineProperty,__defNormalProp=(t,e,n)=>e in t?__defProp(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,__publicField=(t,e,n)=>(__defNormalProp(t,"symbol"!=typeof e?e+"":e,n),n);const LOW=[0,1],MEDIUM=[1,0],QUARTILE=[2,3],HIGH=[3,2],EccMap={L:LOW,M:MEDIUM,Q:QUARTILE,H:HIGH},NUMERIC_REGEX=/^[0-9]*$/,ALPHANUMERIC_REGEX=/^[A-Z0-9 $%*+.\/:-]*$/,ALPHANUMERIC_CHARSET="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:",MIN_VERSION=1,MAX_VERSION=40,PENALTY_N1=3,PENALTY_N2=3,PENALTY_N3=40,PENALTY_N4=10,ECC_CODEWORDS_PER_BLOCK=[[-1,7,10,15,20,26,18,20,24,30,18,20,24,26,30,22,24,28,30,28,28,28,28,30,30,26,28,30,30,30,30,30,30,30,30,30,30,30,30,30,30],[-1,10,16,26,18,24,16,18,22,22,26,30,22,22,24,24,28,28,26,26,26,26,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28],[-1,13,22,18,26,18,24,18,22,20,24,28,26,24,20,30,24,28,28,26,30,28,30,30,30,30,28,30,30,30,30,30,30,30,30,30,30,30,30,30,30],[-1,17,28,22,16,22,28,26,26,24,28,24,28,22,24,24,30,28,28,26,28,30,24,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30]],NUM_ERROR_CORRECTION_BLOCKS=[[-1,1,1,1,1,1,2,2,2,2,4,4,4,4,4,6,6,6,6,7,8,8,9,9,10,12,12,12,13,14,15,16,17,18,19,19,20,21,22,24,25],[-1,1,1,1,2,2,4,4,4,5,5,5,8,9,9,10,10,11,13,14,16,17,17,18,20,21,23,25,26,28,29,31,33,35,37,38,40,43,45,47,49],[-1,1,1,2,2,4,4,6,6,8,8,8,10,12,16,12,17,16,18,21,20,23,23,25,27,29,34,34,35,38,40,43,45,48,51,53,56,59,62,65,68],[-1,1,1,2,4,4,4,5,6,8,8,11,11,16,16,18,16,19,21,25,25,25,34,30,32,35,37,40,42,45,48,51,54,57,60,63,66,70,74,77,81]];class QrCode{constructor(t,e,n,r){if(this.version=t,this.ecc=e,__publicField(this,"size"),__publicField(this,"mask"),__publicField(this,"modules",[]),__publicField(this,"types",[]),tMAX_VERSION)throw new RangeError("Version value out of range");if(r<-1||r>7)throw new RangeError("Mask value out of range");this.size=4*t+17;const o=Array.from({length:this.size},(()=>!1));for(let t=0;t0)));this.drawFunctionPatterns();const s=this.addEccAndInterleave(n);if(this.drawCodewords(s),-1===r){let t=1e9;for(let e=0;e<8;e++){this.applyMask(e),this.drawFormatBits(e);const n=this.getPenaltyScore();n=0&&t=0&&e>>9);const r=21522^(e<<10|n);for(let t=0;t<=5;t++)this.setFunctionModule(8,t,getBit(r,t));this.setFunctionModule(8,7,getBit(r,6)),this.setFunctionModule(8,8,getBit(r,7)),this.setFunctionModule(7,8,getBit(r,8));for(let t=9;t<15;t++)this.setFunctionModule(14-t,8,getBit(r,t));for(let t=0;t<8;t++)this.setFunctionModule(this.size-1-t,8,getBit(r,t));for(let t=8;t<15;t++)this.setFunctionModule(8,this.size-15+t,getBit(r,t));this.setFunctionModule(8,this.size-8,!0)}drawVersion(){if(this.version<7)return;let t=this.version;for(let e=0;e<12;e++)t=t<<1^7973*(t>>>11);const e=this.version<<12|t;for(let t=0;t<18;t++){const n=getBit(e,t),r=this.size-11+t%3,o=Math.floor(t/3);this.setFunctionModule(r,o,n),this.setFunctionModule(o,r,n)}}drawFinderPattern(t,e){for(let n=-4;n<=4;n++)for(let r=-4;r<=4;r++){const o=Math.max(Math.abs(r),Math.abs(n)),s=t+r,i=e+n;s>=0&&s=0&&i{(t!==a-o||n>=i)&&u.push(e[t])}));return u}drawCodewords(t){if(t.length!==Math.floor(getNumRawDataModules(this.version)/8))throw new RangeError("Invalid argument");let e=0;for(let n=this.size-1;n>=1;n-=2){6===n&&(n=5);for(let r=0;r>>3],7-(7&e)),e++)}}}applyMask(t){if(t<0||t>7)throw new RangeError("Mask value out of range");for(let e=0;e5&&t++):(this.finderPenaltyAddHistory(r,o),n||(t+=40*this.finderPenaltyCountPatterns(o)),n=this.modules[e][s],r=1);t+=40*this.finderPenaltyTerminateAndCount(n,r,o)}for(let e=0;e5&&t++):(this.finderPenaltyAddHistory(r,o),n||(t+=40*this.finderPenaltyCountPatterns(o)),n=this.modules[s][e],r=1);t+=40*this.finderPenaltyTerminateAndCount(n,r,o)}for(let e=0;et+(e?1:0)),e);const n=this.size*this.size;return t+=10*(Math.ceil(Math.abs(20*e-10*n)/n)-1),t}getAlignmentPatternPositions(){if(1===this.version)return[];{const t=Math.floor(this.version/7)+2,e=32===this.version?26:2*Math.ceil((4*this.version+4)/(2*t-2)),n=[6];for(let r=this.size-7;n.length0&&t[2]===e&&t[3]===3*e&&t[4]===e&&t[5]===e;return(n&&t[0]>=4*e&&t[6]>=e?1:0)+(n&&t[6]>=4*e&&t[0]>=e?1:0)}finderPenaltyTerminateAndCount(t,e,n){return t&&(this.finderPenaltyAddHistory(e,n),e=0),e+=this.size,this.finderPenaltyAddHistory(e,n),this.finderPenaltyCountPatterns(n)}finderPenaltyAddHistory(t,e){0===e[0]&&(t+=this.size),e.pop(),e.unshift(t)}}function appendBits(t,e,n){if(e<0||e>31||t>>>e!=0)throw new RangeError("Value out of range");for(let r=e-1;r>=0;r--)n.push(t>>>r&1)}function getBit(t,e){return 0!=(t>>>e&1)}class QrSegment{constructor(t,e,n){if(this.mode=t,this.numChars=e,this.bitData=n,e<0)throw new RangeError("Invalid argument");this.bitData=n.slice()}getData(){return this.bitData.slice()}}const MODE_NUMERIC=[1,10,12,14],MODE_ALPHANUMERIC=[2,9,11,13],MODE_BYTE=[4,8,16,16];function numCharCountBits(t,e){return t[Math.floor((e+7)/17)+1]}function makeBytes(t){const e=[];for(const n of t)appendBits(n,8,e);return new QrSegment(MODE_BYTE,t.length,e)}function makeNumeric(t){if(!isNumeric(t))throw new RangeError("String contains non-numeric characters");const e=[];for(let n=0;n=1<MAX_VERSION)throw new RangeError("Version number out of range");let e=(16*t+128)*t+64;if(t>=2){const n=Math.floor(t/7)+2;e-=(25*n-10)*n-55,t>=7&&(e-=36)}return e}function getNumDataCodewords(t,e){return Math.floor(getNumRawDataModules(t)/8)-ECC_CODEWORDS_PER_BLOCK[e[0]][t]*NUM_ERROR_CORRECTION_BLOCKS[e[0]][t]}function reedSolomonComputeDivisor(t){if(t<1||t>255)throw new RangeError("Degree out of range");const e=[];for(let n=0;n0));for(const r of t){const t=r^n.shift();n.push(0),e.forEach(((e,r)=>n[r]^=reedSolomonMultiply(e,t)))}return n}function reedSolomonMultiply(t,e){if(t>>>8!=0||e>>>8!=0)throw new RangeError("Byte out of range");let n=0;for(let r=7;r>=0;r--)n=n<<1^285*(n>>>7),n^=(e>>>r&1)*t;return n}function encodeSegments(t,e,n=1,r=40,o=-1,s=!0){if(!(MIN_VERSION<=n&&n<=r&&r<=MAX_VERSION)||o<-1||o>7)throw new RangeError("Invalid value");let i,a;for(i=n;;i++){const n=8*getNumDataCodewords(i,e),o=getTotalBits(t,i);if(o<=n){a=o;break}if(i>=r)throw new RangeError("Data too long")}for(const t of[MEDIUM,QUARTILE,HIGH])s&&a<=8*getNumDataCodewords(i,t)&&(e=t);const h=[];for(const e of t){appendBits(e.mode[0],4,h),appendBits(e.numChars,numCharCountBits(e.mode,i),h);for(const t of e.getData())h.push(t)}const l=8*getNumDataCodewords(i,e);appendBits(0,Math.min(4,l-h.length),h),appendBits(0,(8-h.length%8)%8,h);for(let t=236;h.length0));return h.forEach(((t,e)=>u[e>>>3]|=t<<7-(7&e))),new QrCode(i,e,u,o)}function encode(t,e){const{ecc:n="L",boostEcc:r=!1,minVersion:o=1,maxVersion:s=40,maskPattern:i=-1,border:a=1}=e||{},h="string"==typeof t?makeSegments(t):Array.isArray(t)?[makeBytes(t)]:void 0;if(!h)throw new Error("uqr only supports encoding string and binary data, but got: "+typeof t);const l=encodeSegments(h,EccMap[n],o,s,i,r),u=addBorder({version:l.version,maskPattern:l.mask,size:l.size,data:l.modules,types:l.types},a);return e?.invert&&(u.data=u.data.map((t=>t.map((t=>!t))))),e?.onEncoded?.(u),u}function addBorder(t,e=1){if(!e)return t;const{size:n}=t,r=n+2*e;t.size=r,t.data.forEach((t=>{for(let n=0;n!1))),t.data.push(Array.from({length:r},(t=>!1)));const o=QrCodeDataType.Border;t.types.forEach((t=>{for(let n=0;no))),t.types.push(Array.from({length:r},(t=>o)));return t} +return L.Class.extend({ + renderSVG: function(data, options = {}) { + const result = encode(data, options); + const { + pixelSize = 1, + whiteColor = "white", + blackColor = "black" + } = options; + const height = result.size * pixelSize; + const width = result.size * pixelSize; + let svg = ``; + const paths = []; + for (let row = 0; row < result.size; row++) { + for (let col = 0; col < result.size; col++) { + const x = col * pixelSize; + const y = row * pixelSize; + if (result.data[row][col]) + paths.push(`M${x},${y}h${pixelSize}v${pixelSize}h-${pixelSize}z`); + } + } + svg += ``; + svg += ``; + svg += ""; + return svg; + }, +}); -- 2.30.2