diff --git a/.gitignore b/.gitignore index 3c3629e..2e513f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ node_modules +**/.DS_Store +**/*.js +**/*.d.ts + +!qr.d.ts +!rollup.config.js diff --git a/Readme.md b/README.md similarity index 64% rename from Readme.md rename to README.md index 80f6b05..9eb6c4b 100644 --- a/Readme.md +++ b/README.md @@ -4,14 +4,16 @@ This is a very simple QR-Code generator that outputs a PNG-Buffer. It's written ## Usage - var qr=require('qrpng'); - qr('this is a sample code', function(err, png) { - // png contains the PNG as a buffer. You can write it to a file for example. - }); +``` + import qrcode from 'qrpng; + + const uint8array = qrcode('my text for the code'); + document.getElementById('qrimg').src = 'image/png;base64,' + btoa(String.fromCharCode.apply(null, uint8array)); +``` It has only one function: - function qr(content[, scale], callback) + function generate(content[, scale]) scale is the pixel extent of a QR-Code data pixel. diff --git a/lib/crc32.ts b/lib/crc32.ts new file mode 100644 index 0000000..f15b87a --- /dev/null +++ b/lib/crc32.ts @@ -0,0 +1,279 @@ +const CRC_TABLE = new Int32Array([ + 0x00000000, + 0x77073096, + 0xee0e612c, + 0x990951ba, + 0x076dc419, + 0x706af48f, + 0xe963a535, + 0x9e6495a3, + 0x0edb8832, + 0x79dcb8a4, + 0xe0d5e91e, + 0x97d2d988, + 0x09b64c2b, + 0x7eb17cbd, + 0xe7b82d07, + 0x90bf1d91, + 0x1db71064, + 0x6ab020f2, + 0xf3b97148, + 0x84be41de, + 0x1adad47d, + 0x6ddde4eb, + 0xf4d4b551, + 0x83d385c7, + 0x136c9856, + 0x646ba8c0, + 0xfd62f97a, + 0x8a65c9ec, + 0x14015c4f, + 0x63066cd9, + 0xfa0f3d63, + 0x8d080df5, + 0x3b6e20c8, + 0x4c69105e, + 0xd56041e4, + 0xa2677172, + 0x3c03e4d1, + 0x4b04d447, + 0xd20d85fd, + 0xa50ab56b, + 0x35b5a8fa, + 0x42b2986c, + 0xdbbbc9d6, + 0xacbcf940, + 0x32d86ce3, + 0x45df5c75, + 0xdcd60dcf, + 0xabd13d59, + 0x26d930ac, + 0x51de003a, + 0xc8d75180, + 0xbfd06116, + 0x21b4f4b5, + 0x56b3c423, + 0xcfba9599, + 0xb8bda50f, + 0x2802b89e, + 0x5f058808, + 0xc60cd9b2, + 0xb10be924, + 0x2f6f7c87, + 0x58684c11, + 0xc1611dab, + 0xb6662d3d, + 0x76dc4190, + 0x01db7106, + 0x98d220bc, + 0xefd5102a, + 0x71b18589, + 0x06b6b51f, + 0x9fbfe4a5, + 0xe8b8d433, + 0x7807c9a2, + 0x0f00f934, + 0x9609a88e, + 0xe10e9818, + 0x7f6a0dbb, + 0x086d3d2d, + 0x91646c97, + 0xe6635c01, + 0x6b6b51f4, + 0x1c6c6162, + 0x856530d8, + 0xf262004e, + 0x6c0695ed, + 0x1b01a57b, + 0x8208f4c1, + 0xf50fc457, + 0x65b0d9c6, + 0x12b7e950, + 0x8bbeb8ea, + 0xfcb9887c, + 0x62dd1ddf, + 0x15da2d49, + 0x8cd37cf3, + 0xfbd44c65, + 0x4db26158, + 0x3ab551ce, + 0xa3bc0074, + 0xd4bb30e2, + 0x4adfa541, + 0x3dd895d7, + 0xa4d1c46d, + 0xd3d6f4fb, + 0x4369e96a, + 0x346ed9fc, + 0xad678846, + 0xda60b8d0, + 0x44042d73, + 0x33031de5, + 0xaa0a4c5f, + 0xdd0d7cc9, + 0x5005713c, + 0x270241aa, + 0xbe0b1010, + 0xc90c2086, + 0x5768b525, + 0x206f85b3, + 0xb966d409, + 0xce61e49f, + 0x5edef90e, + 0x29d9c998, + 0xb0d09822, + 0xc7d7a8b4, + 0x59b33d17, + 0x2eb40d81, + 0xb7bd5c3b, + 0xc0ba6cad, + 0xedb88320, + 0x9abfb3b6, + 0x03b6e20c, + 0x74b1d29a, + 0xead54739, + 0x9dd277af, + 0x04db2615, + 0x73dc1683, + 0xe3630b12, + 0x94643b84, + 0x0d6d6a3e, + 0x7a6a5aa8, + 0xe40ecf0b, + 0x9309ff9d, + 0x0a00ae27, + 0x7d079eb1, + 0xf00f9344, + 0x8708a3d2, + 0x1e01f268, + 0x6906c2fe, + 0xf762575d, + 0x806567cb, + 0x196c3671, + 0x6e6b06e7, + 0xfed41b76, + 0x89d32be0, + 0x10da7a5a, + 0x67dd4acc, + 0xf9b9df6f, + 0x8ebeeff9, + 0x17b7be43, + 0x60b08ed5, + 0xd6d6a3e8, + 0xa1d1937e, + 0x38d8c2c4, + 0x4fdff252, + 0xd1bb67f1, + 0xa6bc5767, + 0x3fb506dd, + 0x48b2364b, + 0xd80d2bda, + 0xaf0a1b4c, + 0x36034af6, + 0x41047a60, + 0xdf60efc3, + 0xa867df55, + 0x316e8eef, + 0x4669be79, + 0xcb61b38c, + 0xbc66831a, + 0x256fd2a0, + 0x5268e236, + 0xcc0c7795, + 0xbb0b4703, + 0x220216b9, + 0x5505262f, + 0xc5ba3bbe, + 0xb2bd0b28, + 0x2bb45a92, + 0x5cb36a04, + 0xc2d7ffa7, + 0xb5d0cf31, + 0x2cd99e8b, + 0x5bdeae1d, + 0x9b64c2b0, + 0xec63f226, + 0x756aa39c, + 0x026d930a, + 0x9c0906a9, + 0xeb0e363f, + 0x72076785, + 0x05005713, + 0x95bf4a82, + 0xe2b87a14, + 0x7bb12bae, + 0x0cb61b38, + 0x92d28e9b, + 0xe5d5be0d, + 0x7cdcefb7, + 0x0bdbdf21, + 0x86d3d2d4, + 0xf1d4e242, + 0x68ddb3f8, + 0x1fda836e, + 0x81be16cd, + 0xf6b9265b, + 0x6fb077e1, + 0x18b74777, + 0x88085ae6, + 0xff0f6a70, + 0x66063bca, + 0x11010b5c, + 0x8f659eff, + 0xf862ae69, + 0x616bffd3, + 0x166ccf45, + 0xa00ae278, + 0xd70dd2ee, + 0x4e048354, + 0x3903b3c2, + 0xa7672661, + 0xd06016f7, + 0x4969474d, + 0x3e6e77db, + 0xaed16a4a, + 0xd9d65adc, + 0x40df0b66, + 0x37d83bf0, + 0xa9bcae53, + 0xdebb9ec5, + 0x47b2cf7f, + 0x30b5ffe9, + 0xbdbdf21c, + 0xcabac28a, + 0x53b39330, + 0x24b4a3a6, + 0xbad03605, + 0xcdd70693, + 0x54de5729, + 0x23d967bf, + 0xb3667a2e, + 0xc4614ab8, + 0x5d681b02, + 0x2a6f2b94, + 0xb40bbe37, + 0xc30c8ea1, + 0x5a05df1b, + 0x2d02ef8d, +]); + +export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Int8Array | Int16Array | Int32Array; + +export function signed(data: TypedArray) { + if (!ArrayBuffer.isView(data)) throw new Error("invalid view"); + const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + let crc = -1; + for (let byte of bytes.values()) { + crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8); + } + return crc ^ -1; +} + +export function unsigned(data: TypedArray) { + return signed(data) >>> 0; +} + +export function bytearray(data: TypedArray) { + const result = new Int32Array([signed(data)]); + return new Uint8Array(result.buffer, result.byteOffset, result.byteLength); +} diff --git a/lib/makepng.js b/lib/makepng.js deleted file mode 100644 index 1ec2202..0000000 --- a/lib/makepng.js +++ /dev/null @@ -1,88 +0,0 @@ -/* -** © 2013 by Philipp Dunkel . Licensed under MIT License. -*/ - -module.exports = makepng; - -var crc32 = require('buffer-crc32'); -var zlib = require('zlib'); - -function makepng(data, callback) { - var width = data[0].length; - var height = data.length; - data = IDAT(data, function(err, data) { - if(err) return callback(err); - data = [ - header(), - IHDR(0, 1, width, height), - data, - IEND()]; - data = Buffer.concat(data); - callback(undefined, data); - }); -} - -function header() { - return Buffer('89504E470D0A1A0A', 'hex'); -} - -function IHDR(colortype, bitdepth, width, height) { - var ihdr = Buffer(13); - ihdr.writeUInt32BE(width, 0); // Width: 4 bytes - ihdr.writeUInt32BE(height, 4); // Height: 4 bytes - ihdr.writeUInt8(bitdepth, 8); // Bit depth: 1 byte - ihdr.writeUInt8(colortype, 9); // Color type: 1 byte 0=grayscale - ihdr.writeUInt8(0, 10); // Compression method: 1 byte - ihdr.writeUInt8(0, 11); // Filter method: 1 byte - ihdr.writeUInt8(0, 12); // Interlace method: 1 byte - return chunkify('IHDR', ihdr); -} - -function IDAT(data, callback) { - data = data.map(function(line) { - line = bitmap(line); - line.unshift(0); - return Buffer(line); - }); - data = Buffer.concat(data); - zlib.deflate(data, function(err, data) { - if(err) return callback(err); - callback(undefined, chunkify('IDAT', data)); - }); -} - -function bitmap(data) { - var res = []; - while(data.length) { - res.push(bitmapChunk(data.slice(0, 8))); - data = data.slice(8); - } - return res; -} - -function bitmapChunk(dat) { - /*jslint bitwise:true */ - var dbyte = 0; - dat.forEach(function(bit, idx) { - bit = !bit ? 0x01 : 0x00; - dbyte = dbyte | (bit << (7 - idx)); - }); - return dbyte; -} - -function IEND() { - return chunkify('IEND', Buffer(0)); -} - -function chunkify(type, buf) { - var len = bufInt32(buf.length); - buf = Buffer.concat([Buffer(type.substr(0, 4), 'ascii'), buf], buf.length + 4); - var crc = bufInt32(crc32.unsigned(buf)); - return Buffer.concat([len, buf, crc], len.length + buf.length + crc.length); -} - -function bufInt32(val) { - var res = Buffer(4); - res.writeUInt32BE(val, 0); - return res; -} diff --git a/lib/png.ts b/lib/png.ts new file mode 100644 index 0000000..67a8cb8 --- /dev/null +++ b/lib/png.ts @@ -0,0 +1,115 @@ +import { unsigned as crc32 } from "./crc32"; +import { deflate } from "pako"; + +const SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; +const HEADER = [ + 0, + 0, + 0, + 13, // HEADER.length - IHDR & CRC + 0x49, + 0x48, + 0x44, + 0x52, // IHDR + 0, + 0, + 0, + 0, // width + 0, + 0, + 0, + 0, // height + 1, + 0, // bitdepth , colortype + 0, + 0, + 0, // compression, filter, interlace + 0, + 0, + 0, + 0, // CRC +]; +const DATA = [ + 0, + 0, + 0, + 0, // data.length + 0x49, + 0x44, + 0x41, + 0x54, +]; + +const FOOTER = [ + 0, + 0, + 0, + 0, // FOOTER.length - IEND & CRC + 0x49, + 0x45, + 0x4e, + 0x44, // IEND + 0, + 0, + 0, + 0, // CRC +]; + +export function bitmap(pixels: boolean[][]) { + const { width, height, data } = compileData(pixels); + + const result = new ArrayBuffer(SIGNATURE.length + HEADER.length + DATA.length + (data.length + 4) + FOOTER.length); + + let baseOffset = 0; + + new Uint8Array(result, baseOffset, SIGNATURE.length).set(SIGNATURE); + baseOffset = SIGNATURE.length; + + new Uint8Array(result, baseOffset, HEADER.length).set(HEADER); + setUint32(result, baseOffset + 8, width); + setUint32(result, baseOffset + 12, height); + setUint32(result, baseOffset + HEADER.length - 4, crc32(new Uint8Array(result, baseOffset + 4, HEADER.length - 8))); + baseOffset += HEADER.length; + + new Uint8Array(result, baseOffset, DATA.length).set(DATA); + setUint32(result, baseOffset, data.length); + new Uint8Array(result, baseOffset + DATA.length, data.length).set(data); + setUint32(result, baseOffset + DATA.length + data.byteLength, crc32(new Uint8Array(result, baseOffset + 4, DATA.length + data.byteLength + 4 - 8))); + baseOffset += DATA.length + data.byteLength + 4; + + new Uint8Array(result, baseOffset, FOOTER.length).set(FOOTER); + setUint32(result, baseOffset + FOOTER.length - 4, crc32(new Uint8Array(result, baseOffset + 4, FOOTER.length - 8))); + + return new Uint8Array(result, 0, result.byteLength); +} + +function setUint32(buffer: ArrayBuffer, offset: number, value: number) { + const view = new DataView(buffer, offset, 4); + view.setUint32(0, value, false); +} + +function compileData(data: boolean[][]): { width: number; height: number; data: Uint8Array } { + console.error(printmap(data)); + const height = Math.ceil(data.length / 8) * 8; + const width = Math.ceil(data[0].length / 8) * 8; + const buffer = new Uint8Array(((width + 1) * height) / 8); + + let byteOff = 0; + for (let line = 0; line < height; line++) { + buffer[byteOff] = 0; // filter-type for scanline + byteOff++; + for (let off = 0; off < width; off += 8) { + const linedata = data[line] || []; + const byte = (!linedata[off + 0] ? 0b10_00_00_00 : 0) | (!linedata[off + 1] ? 0b01_00_00_00 : 0) | (!linedata[off + 2] ? 0b00_10_00_00 : 0) | (!linedata[off + 3] ? 0b00_01_00_00 : 0) | (!linedata[off + 4] ? 0b00_00_10_00 : 0) | (!linedata[off + 5] ? 0b00_00_01_00 : 0) | (!linedata[off + 6] ? 0b00_00_00_10 : 0) | (!linedata[off + 7] ? 0b00_00_00_01 : 0); + buffer[byteOff] = byte; + byteOff++; + } + } + + const result = deflate(buffer); + return { width, height, data: result }; +} + +function printmap(data: boolean[][]) { + return data.map((l) => l.map((p) => (p ? "*" : " ")).join("")).join("\n"); +} diff --git a/lib/qr.ts b/lib/qr.ts new file mode 100644 index 0000000..e431bc1 --- /dev/null +++ b/lib/qr.ts @@ -0,0 +1,56 @@ +import { QRCode } from "./qr/code"; +import { bitmap } from "./png"; + +function generate(text: string, scale: number = 8): Uint8Array { + if (text.length > 1273) throw new Error("text too long"); + const qr = new QRCode(level(text), 2); + qr.addData(text); + qr.make(); + const pixels = code(qr, scale); + return bitmap(pixels); +} + +export default generate; + +function code(qr: QRCode, scale: number = 1) { + var res: boolean[][] = []; + var mods = qr.getModuleCount(); + + for (let cnt = 3; cnt; cnt--) for (let cnt = scale; cnt; cnt--) res.push(new Array((mods + 2 * 3) * scale).fill(false)); + + for (let idx = 0; idx < mods; idx += 1) { + const lp = line(qr, idx, scale); + res.push(...new Array(scale).fill(lp)); + } + + for (let cnt = 3; cnt; cnt--) for (let cnt = scale; cnt; cnt--) res.push(new Array((mods + 2 * 3) * scale).fill(false)); + + return res; +} + +function line(qr: QRCode, l: number, scale: number) { + var mods = qr.getModuleCount(); + var res: boolean[] = []; + + for (let cnt = 3; cnt; cnt--) res.push(...new Array(scale).fill(false)); + + for (let idx = 0; idx < mods; idx += 1) { + res.push(...new Array(scale).fill(qr.isDark(l, idx))); + } + + for (let cnt = 3; cnt; cnt--) res.push(...new Array(scale).fill(false)); + + return res; +} + +var levels = [7, 14, 24, 34, 44, 58, 64, 84, 98, 119, 137, 155, 177, 194, 220, 250, 280, 310, 338, 382, 403, 439, 461, 511, 535, 593, 625, 658, 698, 742, 790, 842, 898, 958, 983, 1051, 1093, 1139, 1219, 1273]; + +function level(text: string): number { + var res = 9999; + levels.forEach(function (max, lev) { + if (max < text.length) return; + res = Math.min(lev, res); + }); + if (res >= levels.length) throw new Error("text too long (max: " + levels[levels.length - 1] + ")"); + return res + 1; +} diff --git a/lib/qr/bitbuffer.ts b/lib/qr/bitbuffer.ts new file mode 100644 index 0000000..d89ddd3 --- /dev/null +++ b/lib/qr/bitbuffer.ts @@ -0,0 +1,27 @@ +export class QRBitBuffer { + public buffer: number[] = []; + public length: number = 0; + + get(index: number) { + var bufIndex = Math.floor(index / 8); + return ((this.buffer[bufIndex] >>> (7 - (index % 8))) & 1) == 1; + } + put(num: number, length: number) { + for (var i = 0; i < length; i++) { + this.putBit(((num >>> (length - i - 1)) & 1) == 1); + } + } + get bitLength() { + return this.length; + } + putBit(bit: boolean) { + var bufIndex = Math.floor(this.length / 8); + if (this.buffer.length <= bufIndex) { + this.buffer.push(0); + } + if (bit) { + this.buffer[bufIndex] |= 0x80 >>> this.length % 8; + } + this.length++; + } +} diff --git a/lib/qr/block.ts b/lib/qr/block.ts new file mode 100644 index 0000000..2e9602a --- /dev/null +++ b/lib/qr/block.ts @@ -0,0 +1,257 @@ +const RS_BLOCK_TABLE: number[][] = [ + // L + // M + // Q + // H + + // 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + // 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + // 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + // 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + // 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + // 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + // 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + // 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + // 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + // 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], + //NOTE added by Ryan Day.to make greater than version 10 qrcodes + // this table starts on page 40 of the spec PDF. google ISO/IEC 18004 + // 11 + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], + //12 + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], + //13 + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], + //14 + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], + //15 + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12, 7, 37, 13], + //16 + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], + //17 + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], + //18 + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], + //19 + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], + //20 + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], + //21 + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], + //22 + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], + //23 + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], + //24 + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], + //25 + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], + //26 + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], + //27 + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], + //28 + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], + //29 + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], + //30 + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], + //31 + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], + //32 + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], + //33 + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], + //34 + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], + //35 + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], + //36 + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], + //37 + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], + //38 + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], + //39 + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], + //40 + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16], +]; + +import { ErrorCorrectLevel } from "./const"; + +export class QRRSBlock { + constructor(totalCount: number, dataCount: number) { + this.totalCount = totalCount; + this.dataCount = dataCount; + } + public totalCount: number; + public dataCount: number; + static getRSBlocks(typeNumber: number, errorCorrectLevel: number) { + var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel); + + if (rsBlock == undefined) { + throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel); + } + + var length = rsBlock.length / 3; + + var list = new Array(); + + for (var i = 0; i < length; i++) { + var count = rsBlock[i * 3 + 0]; + var totalCount = rsBlock[i * 3 + 1]; + var dataCount = rsBlock[i * 3 + 2]; + + for (var j = 0; j < count; j++) { + list.push(new QRRSBlock(totalCount, dataCount)); + } + } + + return list; + } + static getRsBlockTable(typeNumber: number, errorCorrectLevel: number) { + switch (errorCorrectLevel) { + case ErrorCorrectLevel.L: + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; + case ErrorCorrectLevel.M: + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; + case ErrorCorrectLevel.Q: + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; + case ErrorCorrectLevel.H: + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; + default: + return undefined; + } + } +} diff --git a/lib/qr/byte.ts b/lib/qr/byte.ts new file mode 100644 index 0000000..df838ae --- /dev/null +++ b/lib/qr/byte.ts @@ -0,0 +1,19 @@ +import { Mode } from "./const"; +import { QRBitBuffer } from "./bitbuffer"; + +export class QR8bitByte { + constructor(data: string) { + this.data = data; + } + private data: string; + public readonly mode = Mode.MODE_8BIT_BYTE; + public get length() { + return this.data.length; + } + write(buffer: QRBitBuffer) { + for (var i = 0; i < this.data.length; i++) { + // not JIS ... + buffer.put(this.data.charCodeAt(i), 8); + } + } +} diff --git a/lib/qr/code.ts b/lib/qr/code.ts new file mode 100644 index 0000000..1da6bfc --- /dev/null +++ b/lib/qr/code.ts @@ -0,0 +1,342 @@ +import * as QRUtil from "./utils"; +import { Grid } from "./grid"; +import { QRPolynomial } from "./polynomial"; +import { QRRSBlock } from "./block"; +import { QRBitBuffer } from "./bitbuffer"; +import { QR8bitByte } from "./byte"; + +export class QRCode { + constructor(typeNumber: number, errorCorrectLevel: number) { + this.typeNumber = typeNumber; + this.errorCorrectLevel = errorCorrectLevel; + } + private typeNumber: number; + private errorCorrectLevel: number; + private modules: Grid = new Grid(0); + private moduleCount: number = 0; + private dataCache?: Uint32Array; + private dataList: QR8bitByte[] = []; + + public addData(data: string): void { + let newData = new QR8bitByte(data); + this.dataList.push(newData); + this.dataCache = undefined; + } + + isDark(row: number, col: number) { + if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) { + throw new Error(row + "," + col); + } + return this.modules.get(row, col); + } + + getModuleCount() { + return this.moduleCount; + } + + make() { + this.makeImpl(false, this.getBestMaskPattern()); + } + + makeImpl(test, maskPattern) { + this.moduleCount = this.typeNumber * 4 + 17; + this.modules = new Grid(this.moduleCount); + + this.setupPositionProbePattern(0, 0); + this.setupPositionProbePattern(this.moduleCount - 7, 0); + this.setupPositionProbePattern(0, this.moduleCount - 7); + this.setupPositionAdjustPattern(); + this.setupTimingPattern(); + this.setupTypeInfo(test, maskPattern); + + if (this.typeNumber >= 7) { + this.setupTypeNumber(test); + } + + if (this.dataCache == null) { + this.dataCache = createData(this.typeNumber, this.errorCorrectLevel, this.dataList); + } + + this.mapData(this.dataCache, maskPattern); + } + + setupPositionProbePattern(row: number, col: number) { + for (var r = -1; r <= 7; r++) { + if (row + r <= -1 || this.moduleCount <= row + r) continue; + + for (var c = -1; c <= 7; c++) { + if (col + c <= -1 || this.moduleCount <= col + c) continue; + + if ((0 <= r && r <= 6 && (c == 0 || c == 6)) || (0 <= c && c <= 6 && (r == 0 || r == 6)) || (2 <= r && r <= 4 && 2 <= c && c <= 4)) { + this.modules.set(row + r, col + c, true); + } else { + this.modules.set(row + r, col + c, false); + } + } + } + } + + getBestMaskPattern() { + var minLostPoint = 0; + var pattern = 0; + + for (var i = 0; i < 8; i++) { + this.makeImpl(true, i); + + var lostPoint = QRUtil.getLostPoint(this); + + if (i == 0 || minLostPoint > lostPoint) { + minLostPoint = lostPoint; + pattern = i; + } + } + + return pattern; + } + + setupTimingPattern() { + for (var r = 8; r < this.moduleCount - 8; r++) { + if (this.modules.get(r, 6) !== undefined) { + continue; + } + this.modules.set(r, 6, r % 2 == 0); + } + + for (var c = 8; c < this.moduleCount - 8; c++) { + if (this.modules.get(6, c) !== undefined) { + continue; + } + this.modules.set(6, c, c % 2 == 0); + } + } + + setupPositionAdjustPattern() { + let pos = QRUtil.getPatternPosition(this.typeNumber); + + for (var i = 0; i < pos.length; i++) { + for (var j = 0; j < pos.length; j++) { + var row = pos[i]; + var col = pos[j]; + + if (this.modules.get(row, col) !== undefined) { + continue; + } + + for (var r = -2; r <= 2; r++) { + for (var c = -2; c <= 2; c++) { + if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) { + this.modules.set(row + r, col + c, true); + } else { + this.modules.set(row + r, col + c, false); + } + } + } + } + } + } + + setupTypeNumber(test) { + var bits = QRUtil.getBCHTypeNumber(this.typeNumber); + + for (var i = 0; i < 18; i++) { + var mod = !test && ((bits >> i) & 1) == 1; + this.modules.set(Math.floor(i / 3), (i % 3) + this.moduleCount - 8 - 3, mod); + } + + for (var i = 0; i < 18; i++) { + var mod = !test && ((bits >> i) & 1) == 1; + this.modules.set((i % 3) + this.moduleCount - 8 - 3, Math.floor(i / 3), mod); + } + } + + setupTypeInfo(test, maskPattern) { + var data = (this.errorCorrectLevel << 3) | maskPattern; + var bits = QRUtil.getBCHTypeInfo(data); + + // vertical + for (var i = 0; i < 15; i++) { + var mod = !test && ((bits >> i) & 1) == 1; + + if (i < 6) { + this.modules.set(i, 8, mod); + } else if (i < 8) { + this.modules.set(i + 1, 8, mod); + } else { + this.modules.set(this.moduleCount - 15 + i, 8, mod); + } + } + + // horizontal + for (var i = 0; i < 15; i++) { + var mod = !test && ((bits >> i) & 1) == 1; + + if (i < 8) { + this.modules.set(8, this.moduleCount - i - 1, mod); + } else if (i < 9) { + this.modules.set(8, 15 - i - 1 + 1, mod); + } else { + this.modules.set(8, 15 - i - 1, mod); + } + } + + // fixed module + this.modules.set(this.moduleCount - 8, 8, !test); + } + + mapData(data, maskPattern) { + var inc = -1; + var row = this.moduleCount - 1; + var bitIndex = 7; + var byteIndex = 0; + + for (var col = this.moduleCount - 1; col > 0; col -= 2) { + if (col == 6) col--; + + while (true) { + for (var c = 0; c < 2; c++) { + if (this.modules.get(row, col - c) == undefined) { + var dark = false; + + if (byteIndex < data.length) { + dark = ((data[byteIndex] >>> bitIndex) & 1) == 1; + } + + var mask = QRUtil.getMask(maskPattern, row, col - c); + + if (mask) { + dark = !dark; + } + + this.modules.set(row, col - c, dark); + bitIndex--; + + if (bitIndex == -1) { + byteIndex++; + bitIndex = 7; + } + } + } + + row += inc; + + if (row < 0 || this.moduleCount <= row) { + row -= inc; + inc = -inc; + break; + } + } + } + } +} + +const PAD0 = 0xec; +const PAD1 = 0x11; + +function createData(typeNumber: number, errorCorrectLevel: number, dataList: QR8bitByte[]) { + var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); + + var buffer = new QRBitBuffer(); + + for (var i = 0; i < dataList.length; i++) { + var data = dataList[i]; + buffer.put(data.mode, 4); + buffer.put(data.length, QRUtil.getLengthInBits(data.mode, typeNumber)); + data.write(buffer); + } + + // calc num max data. + var totalDataCount = 0; + for (var i = 0; i < rsBlocks.length; i++) { + totalDataCount += rsBlocks[i].dataCount; + } + + if (buffer.bitLength > totalDataCount * 8) { + throw new Error("code length overflow. (" + buffer.bitLength + ">" + totalDataCount * 8 + ")"); + } + + // end code + if (buffer.bitLength + 4 <= totalDataCount * 8) { + buffer.put(0, 4); + } + + // padding + while (buffer.bitLength % 8 != 0) { + buffer.putBit(false); + } + + // padding + while (true) { + if (buffer.bitLength >= totalDataCount * 8) { + break; + } + buffer.put(PAD0, 8); + + if (buffer.bitLength >= totalDataCount * 8) { + break; + } + buffer.put(PAD1, 8); + } + + return createBytes(buffer, rsBlocks); +} + +function createBytes(buffer: QRBitBuffer, rsBlocks: QRRSBlock[]): Uint32Array { + var offset = 0; + + var maxDcCount = 0; + var maxEcCount = 0; + + var dcdata = new Array(rsBlocks.length); + var ecdata = new Array(rsBlocks.length); + + for (var r = 0; r < rsBlocks.length; r++) { + var dcCount = rsBlocks[r].dataCount; + var ecCount = rsBlocks[r].totalCount - dcCount; + + maxDcCount = Math.max(maxDcCount, dcCount); + maxEcCount = Math.max(maxEcCount, ecCount); + + dcdata[r] = new Array(dcCount); + + for (var i = 0; i < dcdata[r].length; i++) { + dcdata[r][i] = 0xff & buffer.buffer[i + offset]; + } + offset += dcCount; + + var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); + var rawPoly = new QRPolynomial(dcdata[r], rsPoly.length - 1); + + var modPoly = rawPoly.mod(rsPoly); + ecdata[r] = new Array(rsPoly.length - 1); + for (var i = 0; i < ecdata[r].length; i++) { + var modIndex = i + modPoly.length - ecdata[r].length; + ecdata[r][i] = modIndex >= 0 ? modPoly.get(modIndex) : 0; + } + } + + var totalCodeCount = 0; + for (var i = 0; i < rsBlocks.length; i++) { + totalCodeCount += rsBlocks[i].totalCount; + } + + var data = new Uint32Array(totalCodeCount); + var index = 0; + + for (var i = 0; i < maxDcCount; i++) { + for (var r = 0; r < rsBlocks.length; r++) { + if (i < dcdata[r].length) { + data[index++] = dcdata[r][i]; + } + } + } + + for (var i = 0; i < maxEcCount; i++) { + for (var r = 0; r < rsBlocks.length; r++) { + if (i < ecdata[r].length) { + data[index++] = ecdata[r][i]; + } + } + } + + return data; +} diff --git a/lib/qr/const.ts b/lib/qr/const.ts new file mode 100644 index 0000000..4bf1e95 --- /dev/null +++ b/lib/qr/const.ts @@ -0,0 +1,22 @@ +export const Mode = { + MODE_NUMBER: 1 << 0, + MODE_ALPHA_NUM: 1 << 1, + MODE_8BIT_BYTE: 1 << 2, + MODE_KANJI: 1 << 3, +}; +export const ErrorCorrectLevel = { + L: 1, + M: 0, + Q: 3, + H: 2, +}; +export const MaskPattern = { + PATTERN000: 0, + PATTERN001: 1, + PATTERN010: 2, + PATTERN011: 3, + PATTERN100: 4, + PATTERN101: 5, + PATTERN110: 6, + PATTERN111: 7, +}; diff --git a/lib/qr/grid.ts b/lib/qr/grid.ts new file mode 100644 index 0000000..f0aa703 --- /dev/null +++ b/lib/qr/grid.ts @@ -0,0 +1,38 @@ +export class Grid { + constructor(rows: number, cols: number = rows) { + this.rowCount = rows; + this.colCount = cols; + } + private dataHash: { [key: string]: T } = {}; + private rowCount: number = 0; + private colCount: number = 0; + public get rows() { + return this.rowCount; + } + public get columns() { + return this, this.colCount; + } + public get(row: number, col: number): T | undefined { + return this.dataHash[`${row}/${col}`]; + } + public set(row: number, col: number, val: T) { + this.dataHash[`${row}/${col}`] = val; + } + public has(row: number, col: number): boolean { + return "undefined" !== typeof this.get(row, col); + } + public delete(row: number, col: number) { + delete this.dataHash[`${row}/${col}`]; + } + public get data() { + const data = []; + for (let row = 0; row < this.rows; row++) { + const rowdata: (T | undefined)[] = []; + data.push(rowdata); + for (let col = 0; col < this.columns; col++) { + rowdata.push(this.get(row, col)); + } + } + return data; + } +} diff --git a/lib/qr/math.ts b/lib/qr/math.ts new file mode 100644 index 0000000..bcc76cc --- /dev/null +++ b/lib/qr/math.ts @@ -0,0 +1,18 @@ +const EXP_TABLE: number[] = new Array(256); +const LOG_TABLE: number[] = new Array(256); +for (var i = 0; i < 8; i++) EXP_TABLE[i] = 1 << i; +for (var i = 8; i < 256; i++) EXP_TABLE[i] = EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ EXP_TABLE[i - 8]; +for (var i = 0; i < 255; i++) LOG_TABLE[EXP_TABLE[i]] = i; + +export function glog(n: number) { + if (n < 1) { + throw new Error("glog(" + n + ")"); + } + return LOG_TABLE[n]; +} + +export function gexp(n: number) { + while (n < 0) n += 255; + while (n >= 256) n -= 255; + return EXP_TABLE[n]; +} diff --git a/lib/qr/polynomial.ts b/lib/qr/polynomial.ts new file mode 100644 index 0000000..dfc7832 --- /dev/null +++ b/lib/qr/polynomial.ts @@ -0,0 +1,51 @@ +import * as QRMath from "./math"; + +export class QRPolynomial { + constructor(num: number[], shift: number) { + if (num.length == undefined) { + throw new Error(num.length + "/" + shift); + } + + let offset = 0; + while (offset < num.length && num[offset] == 0) { + offset++; + } + + this.num = new Array(num.length - offset + shift); + for (var i = 0; i < num.length - offset; i++) { + this.num[i] = num[i + offset]; + } + } + private num: number[]; + + get length() { + return this.num.length; + } + get(index: number): number { + return this.num[index]; + } + multiply(e: QRPolynomial): QRPolynomial { + let num = new Array(this.length + e.length - 1); + + for (let i = 0; i < this.length; i++) { + for (let j = 0; j < e.length; j++) { + num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j))); + } + } + return new QRPolynomial(num, 0); + } + mod(e: QRPolynomial): QRPolynomial { + if (this.length - e.length < 0) { + return this; + } + + let ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0)); + let num = new Array(this.length); + + for (let i = 0; i < this.length; i++) num[i] = this.get(i); + for (let i = 0; i < e.length; i++) num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio); + + // recursive call + return new QRPolynomial(num, 0).mod(e); + } +} diff --git a/lib/qr/utils.ts b/lib/qr/utils.ts new file mode 100644 index 0000000..f318a86 --- /dev/null +++ b/lib/qr/utils.ts @@ -0,0 +1,256 @@ +import { QRPolynomial } from "./polynomial"; +import { MaskPattern, Mode } from "./const"; +import * as QRMath from "./math"; +import { QRCode } from "./code"; + +const PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170], +]; + +const G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0); +const G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0); +const G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1); + +export function getBCHTypeInfo(data: number) { + var d = data << 10; + while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { + d ^= G15 << (getBCHDigit(d) - getBCHDigit(G15)); + } + return ((data << 10) | d) ^ G15_MASK; +} + +export function getBCHTypeNumber(data: number) { + var d = data << 12; + while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { + d ^= G18 << (getBCHDigit(d) - getBCHDigit(G18)); + } + return (data << 12) | d; +} + +export function getBCHDigit(data: number) { + var digit = 0; + + while (data != 0) { + digit++; + data >>>= 1; + } + + return digit; +} + +export function getPatternPosition(typeNumber: number) { + return PATTERN_POSITION_TABLE[typeNumber - 1] || []; +} + +export function getMask(maskPattern: number, i: number, j: number) { + switch (maskPattern) { + case MaskPattern.PATTERN000: + return (i + j) % 2 == 0; + case MaskPattern.PATTERN001: + return i % 2 == 0; + case MaskPattern.PATTERN010: + return j % 3 == 0; + case MaskPattern.PATTERN011: + return (i + j) % 3 == 0; + case MaskPattern.PATTERN100: + return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0; + case MaskPattern.PATTERN101: + return ((i * j) % 2) + ((i * j) % 3) == 0; + case MaskPattern.PATTERN110: + return (((i * j) % 2) + ((i * j) % 3)) % 2 == 0; + case MaskPattern.PATTERN111: + return (((i * j) % 3) + ((i + j) % 2)) % 2 == 0; + + default: + throw new Error("bad maskPattern:" + maskPattern); + } +} + +export function getErrorCorrectPolynomial(errorCorrectLength: number) { + var a = new QRPolynomial([1], 0); + + for (var i = 0; i < errorCorrectLength; i++) { + a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0)); + } + + return a; +} + +export function getLengthInBits(mode: number, type: number) { + if (1 <= type && type < 10) { + // 1 - 9 + + switch (mode) { + case Mode.MODE_NUMBER: + return 10; + case Mode.MODE_ALPHA_NUM: + return 9; + case Mode.MODE_8BIT_BYTE: + return 8; + case Mode.MODE_KANJI: + return 8; + default: + throw new Error("mode:" + mode); + } + } else if (type < 27) { + // 10 - 26 + + switch (mode) { + case Mode.MODE_NUMBER: + return 12; + case Mode.MODE_ALPHA_NUM: + return 11; + case Mode.MODE_8BIT_BYTE: + return 16; + case Mode.MODE_KANJI: + return 10; + default: + throw new Error("mode:" + mode); + } + } else if (type < 41) { + // 27 - 40 + + switch (mode) { + case Mode.MODE_NUMBER: + return 14; + case Mode.MODE_ALPHA_NUM: + return 13; + case Mode.MODE_8BIT_BYTE: + return 16; + case Mode.MODE_KANJI: + return 12; + default: + throw new Error("mode:" + mode); + } + } else { + throw new Error("type:" + type); + } +} + +export function getLostPoint(qrCode: QRCode) { + let moduleCount = qrCode.getModuleCount(); + + let lostPoint = 0; + + // LEVEL1 + + for (var row = 0; row < moduleCount; row++) { + for (var col = 0; col < moduleCount; col++) { + var sameCount = 0; + var dark = qrCode.isDark(row, col); + + for (var r = -1; r <= 1; r++) { + if (row + r < 0 || moduleCount <= row + r) { + continue; + } + + for (var c = -1; c <= 1; c++) { + if (col + c < 0 || moduleCount <= col + c) { + continue; + } + + if (r == 0 && c == 0) { + continue; + } + + if (dark == qrCode.isDark(row + r, col + c)) { + sameCount++; + } + } + } + + if (sameCount > 5) { + lostPoint += 3 + sameCount - 5; + } + } + } + + // LEVEL2 + + for (var row = 0; row < moduleCount - 1; row++) { + for (var col = 0; col < moduleCount - 1; col++) { + var count = 0; + if (qrCode.isDark(row, col)) count++; + if (qrCode.isDark(row + 1, col)) count++; + if (qrCode.isDark(row, col + 1)) count++; + if (qrCode.isDark(row + 1, col + 1)) count++; + if (count == 0 || count == 4) { + lostPoint += 3; + } + } + } + + // LEVEL3 + + for (var row = 0; row < moduleCount; row++) { + for (var col = 0; col < moduleCount - 6; col++) { + if (qrCode.isDark(row, col) && !qrCode.isDark(row, col + 1) && qrCode.isDark(row, col + 2) && qrCode.isDark(row, col + 3) && qrCode.isDark(row, col + 4) && !qrCode.isDark(row, col + 5) && qrCode.isDark(row, col + 6)) { + lostPoint += 40; + } + } + } + + for (var col = 0; col < moduleCount; col++) { + for (var row = 0; row < moduleCount - 6; row++) { + if (qrCode.isDark(row, col) && !qrCode.isDark(row + 1, col) && qrCode.isDark(row + 2, col) && qrCode.isDark(row + 3, col) && qrCode.isDark(row + 4, col) && !qrCode.isDark(row + 5, col) && qrCode.isDark(row + 6, col)) { + lostPoint += 40; + } + } + } + + // LEVEL4 + + var darkCount = 0; + + for (var col = 0; col < moduleCount; col++) { + for (var row = 0; row < moduleCount; row++) { + if (qrCode.isDark(row, col)) { + darkCount++; + } + } + } + + var ratio = Math.abs((100 * darkCount) / moduleCount / moduleCount - 50) / 5; + lostPoint += ratio * 10; + + return lostPoint; +} diff --git a/lib/qrcode.js b/lib/qrcode.js deleted file mode 100644 index 4d72841..0000000 --- a/lib/qrcode.js +++ /dev/null @@ -1,1194 +0,0 @@ -/** - * QRCode for JavaScript - * - * modified by Ryan Day for nodejs support - * Copyright (c) 2011 Ryan Day - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/mit-license.php - * - * EXPORTS: - * { - * QRCode:QRCode - * QRErrorCorrectLevel:QRErrorCorrectLevel - * } -//--------------------------------------------------------------------- -// QRCode for JavaScript -// -// Copyright (c) 2009 Kazuhiko Arase -// -// URL: http://www.d-project.com/ -// -// Licensed under the MIT license: -// http://www.opensource.org/licenses/mit-license.php -// -// The word "QR Code" is registered trademark of -// DENSO WAVE INCORPORATED -// http://www.denso-wave.com/qrcode/faqpatent-e.html -// -//--------------------------------------------------------------------- -*/ - -//--------------------------------------------------------------------- -// QRCode -//--------------------------------------------------------------------- - -exports = module.exports = QRCode; -exports.QRCode = QRCode; - -var QRDataArray = (typeof Uint32Array == 'undefined' ? Uint32Array : Array); - -function QRCode(typeNumber, errorCorrectLevel) { - this.typeNumber = typeNumber; - this.errorCorrectLevel = errorCorrectLevel; - this.modules = null; - this.moduleCount = 0; - this.dataCache = null; - this.dataList = new QRDataArray(); -} - -QRCode.prototype = { - - addData: function(data) { - var newData = new QR8bitByte(data); - this.dataList.push(newData); - this.dataCache = null; - }, - - isDark: function(row, col) { - if(row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) { - throw new Error(row + "," + col); - } - return this.modules[row][col]; - }, - - getModuleCount: function() { - return this.moduleCount; - }, - - make: function() { - this.makeImpl(false, this.getBestMaskPattern()); - }, - - makeImpl: function(test, maskPattern) { - - this.moduleCount = this.typeNumber * 4 + 17; - this.modules = new QRDataArray(this.moduleCount); - - for(var row = 0; row < this.moduleCount; row++) { - - this.modules[row] = new QRDataArray(this.moduleCount); - - for(var col = 0; col < this.moduleCount; col++) { - this.modules[row][col] = null; //(col + row) % 3; - } - } - - this.setupPositionProbePattern(0, 0); - this.setupPositionProbePattern(this.moduleCount - 7, 0); - this.setupPositionProbePattern(0, this.moduleCount - 7); - this.setupPositionAdjustPattern(); - this.setupTimingPattern(); - this.setupTypeInfo(test, maskPattern); - - if(this.typeNumber >= 7) { - this.setupTypeNumber(test); - } - - if(this.dataCache == null) { - this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList); - } - - this.mapData(this.dataCache, maskPattern); - }, - - setupPositionProbePattern: function(row, col) { - - for(var r = -1; r <= 7; r++) { - - if(row + r <= -1 || this.moduleCount <= row + r) continue; - - for(var c = -1; c <= 7; c++) { - - if(col + c <= -1 || this.moduleCount <= col + c) continue; - - if((0 <= r && r <= 6 && (c == 0 || c == 6)) || (0 <= c && c <= 6 && (r == 0 || r == 6)) || (2 <= r && r <= 4 && 2 <= c && c <= 4)) { - this.modules[row + r][col + c] = true; - } else { - this.modules[row + r][col + c] = false; - } - } - } - }, - - getBestMaskPattern: function() { - - var minLostPoint = 0; - var pattern = 0; - - for(var i = 0; i < 8; i++) { - - this.makeImpl(true, i); - - var lostPoint = QRUtil.getLostPoint(this); - - if(i == 0 || minLostPoint > lostPoint) { - minLostPoint = lostPoint; - pattern = i; - } - } - - return pattern; - }, - - setupTimingPattern: function() { - - for(var r = 8; r < this.moduleCount - 8; r++) { - if(this.modules[r][6] != null) { - continue; - } - this.modules[r][6] = (r % 2 == 0); - } - - for(var c = 8; c < this.moduleCount - 8; c++) { - if(this.modules[6][c] != null) { - continue; - } - this.modules[6][c] = (c % 2 == 0); - } - }, - - setupPositionAdjustPattern: function() { - - var pos = QRUtil.getPatternPosition(this.typeNumber); - pos = pos || ''; - for(var i = 0; i < pos.length; i++) { - - for(var j = 0; j < pos.length; j++) { - - var row = pos[i]; - var col = pos[j]; - - if(this.modules[row][col] != null) { - continue; - } - - for(var r = -2; r <= 2; r++) { - - for(var c = -2; c <= 2; c++) { - - if(r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) { - this.modules[row + r][col + c] = true; - } else { - this.modules[row + r][col + c] = false; - } - } - } - } - } - }, - - setupTypeNumber: function(test) { - - var bits = QRUtil.getBCHTypeNumber(this.typeNumber); - - for(var i = 0; i < 18; i++) { - var mod = (!test && ((bits >> i) & 1) == 1); - this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod; - } - - for(var i = 0; i < 18; i++) { - var mod = (!test && ((bits >> i) & 1) == 1); - this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod; - } - }, - - setupTypeInfo: function(test, maskPattern) { - - var data = (this.errorCorrectLevel << 3) | maskPattern; - var bits = QRUtil.getBCHTypeInfo(data); - - // vertical - for(var i = 0; i < 15; i++) { - - var mod = (!test && ((bits >> i) & 1) == 1); - - if(i < 6) { - this.modules[i][8] = mod; - } else if(i < 8) { - this.modules[i + 1][8] = mod; - } else { - this.modules[this.moduleCount - 15 + i][8] = mod; - } - } - - // horizontal - for(var i = 0; i < 15; i++) { - - var mod = (!test && ((bits >> i) & 1) == 1); - - if(i < 8) { - this.modules[8][this.moduleCount - i - 1] = mod; - } else if(i < 9) { - this.modules[8][15 - i - 1 + 1] = mod; - } else { - this.modules[8][15 - i - 1] = mod; - } - } - - // fixed module - this.modules[this.moduleCount - 8][8] = (!test); - - }, - - mapData: function(data, maskPattern) { - - var inc = -1; - var row = this.moduleCount - 1; - var bitIndex = 7; - var byteIndex = 0; - - for(var col = this.moduleCount - 1; col > 0; col -= 2) { - - if(col == 6) col--; - - while(true) { - - for(var c = 0; c < 2; c++) { - - if(this.modules[row][col - c] == null) { - - var dark = false; - - if(byteIndex < data.length) { - dark = (((data[byteIndex] >>> bitIndex) & 1) == 1); - } - - var mask = QRUtil.getMask(maskPattern, row, col - c); - - if(mask) { - dark = !dark; - } - - this.modules[row][col - c] = dark; - bitIndex--; - - if(bitIndex == -1) { - byteIndex++; - bitIndex = 7; - } - } - } - - row += inc; - - if(row < 0 || this.moduleCount <= row) { - row -= inc; - inc = -inc; - break; - } - } - } - - } - -}; - -QRCode.PAD0 = 0xEC; -QRCode.PAD1 = 0x11; - -QRCode.createData = function(typeNumber, errorCorrectLevel, dataList) { - - var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); - - var buffer = new QRBitBuffer(); - - for(var i = 0; i < dataList.length; i++) { - var data = dataList[i]; - buffer.put(data.mode, 4); - buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber)); - data.write(buffer); - } - - // calc num max data. - var totalDataCount = 0; - for(var i = 0; i < rsBlocks.length; i++) { - totalDataCount += rsBlocks[i].dataCount; - } - - if(buffer.getLengthInBits() > totalDataCount * 8) { - throw new Error("code length overflow. (" + buffer.getLengthInBits() + ">" + totalDataCount * 8 + ")"); - } - - // end code - if(buffer.getLengthInBits() + 4 <= totalDataCount * 8) { - buffer.put(0, 4); - } - - // padding - while(buffer.getLengthInBits() % 8 != 0) { - buffer.putBit(false); - } - - // padding - while(true) { - - if(buffer.getLengthInBits() >= totalDataCount * 8) { - break; - } - buffer.put(QRCode.PAD0, 8); - - if(buffer.getLengthInBits() >= totalDataCount * 8) { - break; - } - buffer.put(QRCode.PAD1, 8); - } - - return QRCode.createBytes(buffer, rsBlocks); -}; - -QRCode.createBytes = function(buffer, rsBlocks) { - - var offset = 0; - - var maxDcCount = 0; - var maxEcCount = 0; - - var dcdata = new QRDataArray(rsBlocks.length); - var ecdata = new QRDataArray(rsBlocks.length); - - for(var r = 0; r < rsBlocks.length; r++) { - - var dcCount = rsBlocks[r].dataCount; - var ecCount = rsBlocks[r].totalCount - dcCount; - - maxDcCount = Math.max(maxDcCount, dcCount); - maxEcCount = Math.max(maxEcCount, ecCount); - - dcdata[r] = new QRDataArray(dcCount); - - for(var i = 0; i < dcdata[r].length; i++) { - dcdata[r][i] = 0xff & buffer.buffer[i + offset]; - } - offset += dcCount; - - var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); - var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1); - - var modPoly = rawPoly.mod(rsPoly); - ecdata[r] = new QRDataArray(rsPoly.getLength() - 1); - for(var i = 0; i < ecdata[r].length; i++) { - var modIndex = i + modPoly.getLength() - ecdata[r].length; - ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0; - } - - } - - var totalCodeCount = 0; - for(var i = 0; i < rsBlocks.length; i++) { - totalCodeCount += rsBlocks[i].totalCount; - } - - var data = new QRDataArray(totalCodeCount); - var index = 0; - - for(var i = 0; i < maxDcCount; i++) { - for(var r = 0; r < rsBlocks.length; r++) { - if(i < dcdata[r].length) { - data[index++] = dcdata[r][i]; - } - } - } - - for(var i = 0; i < maxEcCount; i++) { - for(var r = 0; r < rsBlocks.length; r++) { - if(i < ecdata[r].length) { - data[index++] = ecdata[r][i]; - } - } - } - - return data; - -}; - -//--------------------------------------------------------------------- -// QR8bitByte -//--------------------------------------------------------------------- - -function QR8bitByte(data) { - this.mode = QRMode.MODE_8BIT_BYTE; - this.data = data; -} - -QR8bitByte.prototype = { - - getLength: function(buffer) { - return this.data.length; - }, - - write: function(buffer) { - for(var i = 0; i < this.data.length; i++) { - // not JIS ... - buffer.put(this.data.charCodeAt(i), 8); - } - } -}; - -//--------------------------------------------------------------------- -// QRMode -//--------------------------------------------------------------------- - -var QRMode = { - MODE_NUMBER: 1 << 0, - MODE_ALPHA_NUM: 1 << 1, - MODE_8BIT_BYTE: 1 << 2, - MODE_KANJI: 1 << 3 -}; - -//--------------------------------------------------------------------- -// QRErrorCorrectLevel -//--------------------------------------------------------------------- -//exported - -var QRErrorCorrectLevel = exports.QRErrorCorrectLevel = { - L: 1, - M: 0, - Q: 3, - H: 2 -}; - -//--------------------------------------------------------------------- -// QRMaskPattern -//--------------------------------------------------------------------- - -var QRMaskPattern = { - PATTERN000: 0, - PATTERN001: 1, - PATTERN010: 2, - PATTERN011: 3, - PATTERN100: 4, - PATTERN101: 5, - PATTERN110: 6, - PATTERN111: 7 -}; - -//--------------------------------------------------------------------- -// QRUtil -//--------------------------------------------------------------------- - -var QRUtil = { - - PATTERN_POSITION_TABLE: [ - [], - [6, 18], - [6, 22], - [6, 26], - [6, 30], - [6, 34], - [6, 22, 38], - [6, 24, 42], - [6, 26, 46], - [6, 28, 50], - [6, 30, 54], - [6, 32, 58], - [6, 34, 62], - [6, 26, 46, 66], - [6, 26, 48, 70], - [6, 26, 50, 74], - [6, 30, 54, 78], - [6, 30, 56, 82], - [6, 30, 58, 86], - [6, 34, 62, 90], - [6, 28, 50, 72, 94], - [6, 26, 50, 74, 98], - [6, 30, 54, 78, 102], - [6, 28, 54, 80, 106], - [6, 32, 58, 84, 110], - [6, 30, 58, 86, 114], - [6, 34, 62, 90, 118], - [6, 26, 50, 74, 98, 122], - [6, 30, 54, 78, 102, 126], - [6, 26, 52, 78, 104, 130], - [6, 30, 56, 82, 108, 134], - [6, 34, 60, 86, 112, 138], - [6, 30, 58, 86, 114, 142], - [6, 34, 62, 90, 118, 146], - [6, 30, 54, 78, 102, 126, 150], - [6, 24, 50, 76, 102, 128, 154], - [6, 28, 54, 80, 106, 132, 158], - [6, 32, 58, 84, 110, 136, 162], - [6, 26, 54, 82, 110, 138, 166], - [6, 30, 58, 86, 114, 142, 170] - ], - - G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0), - G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0), - G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1), - - getBCHTypeInfo: function(data) { - var d = data << 10; - while(QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) { - d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15))); - } - return((data << 10) | d) ^ QRUtil.G15_MASK; - }, - - getBCHTypeNumber: function(data) { - var d = data << 12; - while(QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) { - d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18))); - } - return(data << 12) | d; - }, - - getBCHDigit: function(data) { - - var digit = 0; - - while(data != 0) { - digit++; - data >>>= 1; - } - - return digit; - }, - - getPatternPosition: function(typeNumber) { - return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]; - }, - - getMask: function(maskPattern, i, j) { - - switch(maskPattern) { - - case QRMaskPattern.PATTERN000: - return(i + j) % 2 == 0; - case QRMaskPattern.PATTERN001: - return i % 2 == 0; - case QRMaskPattern.PATTERN010: - return j % 3 == 0; - case QRMaskPattern.PATTERN011: - return(i + j) % 3 == 0; - case QRMaskPattern.PATTERN100: - return(Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0; - case QRMaskPattern.PATTERN101: - return(i * j) % 2 + (i * j) % 3 == 0; - case QRMaskPattern.PATTERN110: - return((i * j) % 2 + (i * j) % 3) % 2 == 0; - case QRMaskPattern.PATTERN111: - return((i * j) % 3 + (i + j) % 2) % 2 == 0; - - default: - throw new Error("bad maskPattern:" + maskPattern); - } - }, - - getErrorCorrectPolynomial: function(errorCorrectLength) { - - var a = new QRPolynomial([1], 0); - - for(var i = 0; i < errorCorrectLength; i++) { - a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0)); - } - - return a; - }, - - getLengthInBits: function(mode, type) { - - if(1 <= type && type < 10) { - - // 1 - 9 - - switch(mode) { - case QRMode.MODE_NUMBER: - return 10; - case QRMode.MODE_ALPHA_NUM: - return 9; - case QRMode.MODE_8BIT_BYTE: - return 8; - case QRMode.MODE_KANJI: - return 8; - default: - throw new Error("mode:" + mode); - } - - } else if(type < 27) { - - // 10 - 26 - - switch(mode) { - case QRMode.MODE_NUMBER: - return 12; - case QRMode.MODE_ALPHA_NUM: - return 11; - case QRMode.MODE_8BIT_BYTE: - return 16; - case QRMode.MODE_KANJI: - return 10; - default: - throw new Error("mode:" + mode); - } - - } else if(type < 41) { - - // 27 - 40 - - switch(mode) { - case QRMode.MODE_NUMBER: - return 14; - case QRMode.MODE_ALPHA_NUM: - return 13; - case QRMode.MODE_8BIT_BYTE: - return 16; - case QRMode.MODE_KANJI: - return 12; - default: - throw new Error("mode:" + mode); - } - - } else { - throw new Error("type:" + type); - } - }, - - getLostPoint: function(qrCode) { - - var moduleCount = qrCode.getModuleCount(); - - var lostPoint = 0; - - // LEVEL1 - - for(var row = 0; row < moduleCount; row++) { - - for(var col = 0; col < moduleCount; col++) { - - var sameCount = 0; - var dark = qrCode.isDark(row, col); - - for(var r = -1; r <= 1; r++) { - - if(row + r < 0 || moduleCount <= row + r) { - continue; - } - - for(var c = -1; c <= 1; c++) { - - if(col + c < 0 || moduleCount <= col + c) { - continue; - } - - if(r == 0 && c == 0) { - continue; - } - - if(dark == qrCode.isDark(row + r, col + c)) { - sameCount++; - } - } - } - - if(sameCount > 5) { - lostPoint += (3 + sameCount - 5); - } - } - } - - // LEVEL2 - - for(var row = 0; row < moduleCount - 1; row++) { - for(var col = 0; col < moduleCount - 1; col++) { - var count = 0; - if(qrCode.isDark(row, col)) count++; - if(qrCode.isDark(row + 1, col)) count++; - if(qrCode.isDark(row, col + 1)) count++; - if(qrCode.isDark(row + 1, col + 1)) count++; - if(count == 0 || count == 4) { - lostPoint += 3; - } - } - } - - // LEVEL3 - - for(var row = 0; row < moduleCount; row++) { - for(var col = 0; col < moduleCount - 6; col++) { - if(qrCode.isDark(row, col) && !qrCode.isDark(row, col + 1) && qrCode.isDark(row, col + 2) && qrCode.isDark(row, col + 3) && qrCode.isDark(row, col + 4) && !qrCode.isDark(row, col + 5) && qrCode.isDark(row, col + 6)) { - lostPoint += 40; - } - } - } - - for(var col = 0; col < moduleCount; col++) { - for(var row = 0; row < moduleCount - 6; row++) { - if(qrCode.isDark(row, col) && !qrCode.isDark(row + 1, col) && qrCode.isDark(row + 2, col) && qrCode.isDark(row + 3, col) && qrCode.isDark(row + 4, col) && !qrCode.isDark(row + 5, col) && qrCode.isDark(row + 6, col)) { - lostPoint += 40; - } - } - } - - // LEVEL4 - - var darkCount = 0; - - for(var col = 0; col < moduleCount; col++) { - for(var row = 0; row < moduleCount; row++) { - if(qrCode.isDark(row, col)) { - darkCount++; - } - } - } - - var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; - lostPoint += ratio * 10; - - return lostPoint; - } - -}; - -//--------------------------------------------------------------------- -// QRMath -//--------------------------------------------------------------------- - -var QRMath = { - - glog: function(n) { - - if(n < 1) { - throw new Error("glog(" + n + ")"); - } - - return QRMath.LOG_TABLE[n]; - }, - - gexp: function(n) { - - while(n < 0) { - n += 255; - } - - while(n >= 256) { - n -= 255; - } - - return QRMath.EXP_TABLE[n]; - }, - - EXP_TABLE: new Array(256), - - LOG_TABLE: new Array(256) - -}; - -for(var i = 0; i < 8; i++) { - QRMath.EXP_TABLE[i] = 1 << i; -} -for(var i = 8; i < 256; i++) { - QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8]; -} -for(var i = 0; i < 255; i++) { - QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i; -} - -//--------------------------------------------------------------------- -// QRPolynomial -//--------------------------------------------------------------------- - -function QRPolynomial(num, shift) { - - if(num.length == undefined) { - throw new Error(num.length + "/" + shift); - } - - var offset = 0; - - while(offset < num.length && num[offset] == 0) { - offset++; - } - - this.num = new Array(num.length - offset + shift); - for(var i = 0; i < num.length - offset; i++) { - this.num[i] = num[i + offset]; - } -} - -QRPolynomial.prototype = { - - get: function(index) { - return this.num[index]; - }, - - getLength: function() { - return this.num.length; - }, - - multiply: function(e) { - - var num = new Array(this.getLength() + e.getLength() - 1); - - for(var i = 0; i < this.getLength(); i++) { - for(var j = 0; j < e.getLength(); j++) { - num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j))); - } - } - - return new QRPolynomial(num, 0); - }, - - mod: function(e) { - - if(this.getLength() - e.getLength() < 0) { - return this; - } - - var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0)); - - var num = new Array(this.getLength()); - - for(var i = 0; i < this.getLength(); i++) { - num[i] = this.get(i); - } - - for(var i = 0; i < e.getLength(); i++) { - num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio); - } - - // recursive call - return new QRPolynomial(num, 0).mod(e); - } -}; - -//--------------------------------------------------------------------- -// QRRSBlock -//--------------------------------------------------------------------- - -function QRRSBlock(totalCount, dataCount) { - this.totalCount = totalCount; - this.dataCount = dataCount; -} - -QRRSBlock.RS_BLOCK_TABLE = [ -// L -// M -// Q -// H - -// 1 -[1, 26, 19], - [1, 26, 16], - [1, 26, 13], - [1, 26, 9], - // 2 - [1, 44, 34], - [1, 44, 28], - [1, 44, 22], - [1, 44, 16], - // 3 - [1, 70, 55], - [1, 70, 44], - [2, 35, 17], - [2, 35, 13], - // 4 - [1, 100, 80], - [2, 50, 32], - [2, 50, 24], - [4, 25, 9], - // 5 - [1, 134, 108], - [2, 67, 43], - [2, 33, 15, 2, 34, 16], - [2, 33, 11, 2, 34, 12], - // 6 - [2, 86, 68], - [4, 43, 27], - [4, 43, 19], - [4, 43, 15], - // 7 - [2, 98, 78], - [4, 49, 31], - [2, 32, 14, 4, 33, 15], - [4, 39, 13, 1, 40, 14], - // 8 - [2, 121, 97], - [2, 60, 38, 2, 61, 39], - [4, 40, 18, 2, 41, 19], - [4, 40, 14, 2, 41, 15], - // 9 - [2, 146, 116], - [3, 58, 36, 2, 59, 37], - [4, 36, 16, 4, 37, 17], - [4, 36, 12, 4, 37, 13], - // 10 - [2, 86, 68, 2, 87, 69], - [4, 69, 43, 1, 70, 44], - [6, 43, 19, 2, 44, 20], - [6, 43, 15, 2, 44, 16] - //NOTE added by Ryan Day.to make greater than version 10 qrcodes - // this table starts on page 40 of the spec PDF. google ISO/IEC 18004 - // 11 - , - [4, 101, 81], - [1, 80, 50, 4, 81, 51], - [4, 50, 22, 4, 51, 23], - [3, 36, 12, 8, 37, 13] - //12 - , - [2, 116, 92, 2, 117, 93], - [6, 58, 36, 2, 59, 37], - [4, 46, 20, 6, 47, 21], - [7, 42, 14, 4, 43, 15] - //13 - , - [4, 133, 107], - [8, 59, 37, 1, 60, 38], - [8, 44, 20, 4, 45, 21], - [12, 33, 11, 4, 34, 12] - //14 - , - [3, 145, 115, 1, 146, 116], - [4, 64, 40, 5, 65, 41], - [11, 36, 16, 5, 37, 17], - [11, 36, 12, 5, 37, 13] - //15 - , - [5, 109, 87, 1, 110, 88], - [5, 65, 41, 5, 66, 42], - [5, 54, 24, 7, 55, 25], - [11, 36, 12, 7, 37, 13] - //16 - , - [5, 122, 98, 1, 123, 99], - [7, 73, 45, 3, 74, 46], - [15, 43, 19, 2, 44, 20], - [3, 45, 15, 13, 46, 16] - //17 - , - [1, 135, 107, 5, 136, 108], - [10, 74, 46, 1, 75, 47], - [1, 50, 22, 15, 51, 23], - [2, 42, 14, 17, 43, 15] - //18 - , - [5, 150, 120, 1, 151, 121], - [9, 69, 43, 4, 70, 44], - [17, 50, 22, 1, 51, 23], - [2, 42, 14, 19, 43, 15] - //19 - , - [3, 141, 113, 4, 142, 114], - [3, 70, 44, 11, 71, 45], - [17, 47, 21, 4, 48, 22], - [9, 39, 13, 16, 40, 14] - //20 - , - [3, 135, 107, 5, 136, 108], - [3, 67, 41, 13, 68, 42], - [15, 54, 24, 5, 55, 25], - [15, 43, 15, 10, 44, 16] - //21 - , - [4, 144, 116, 4, 145, 117], - [17, 68, 42], - [17, 50, 22, 6, 51, 23], - [19, 46, 16, 6, 47, 17] - //22 - , - [2, 139, 111, 7, 140, 112], - [17, 74, 46], - [7, 54, 24, 16, 55, 25], - [34, 37, 13] - //23 - , - [4, 151, 121, 5, 152, 122], - [4, 75, 47, 14, 76, 48], - [11, 54, 24, 14, 55, 25], - [16, 45, 15, 14, 46, 16] - //24 - , - [6, 147, 117, 4, 148, 118], - [6, 73, 45, 14, 74, 46], - [11, 54, 24, 16, 55, 25], - [30, 46, 16, 2, 47, 17] - //25 - , - [8, 132, 106, 4, 133, 107], - [8, 75, 47, 13, 76, 48], - [7, 54, 24, 22, 55, 25], - [22, 45, 15, 13, 46, 16] - //26 - , - [10, 142, 114, 2, 143, 115], - [19, 74, 46, 4, 75, 47], - [28, 50, 22, 6, 51, 23], - [33, 46, 16, 4, 47, 17] - //27 - , - [8, 152, 122, 4, 153, 123], - [22, 73, 45, 3, 74, 46], - [8, 53, 23, 26, 54, 24], - [12, 45, 15, 28, 46, 16] - //28 - , - [3, 147, 117, 10, 148, 118], - [3, 73, 45, 23, 74, 46], - [4, 54, 24, 31, 55, 25], - [11, 45, 15, 31, 46, 16] - //29 - , - [7, 146, 116, 7, 147, 117], - [21, 73, 45, 7, 74, 46], - [1, 53, 23, 37, 54, 24], - [19, 45, 15, 26, 46, 16] - //30 - , - [5, 145, 115, 10, 146, 116], - [19, 75, 47, 10, 76, 48], - [15, 54, 24, 25, 55, 25], - [23, 45, 15, 25, 46, 16] - //31 - , - [13, 145, 115, 3, 146, 116], - [2, 74, 46, 29, 75, 47], - [42, 54, 24, 1, 55, 25], - [23, 45, 15, 28, 46, 16] - //32 - , - [17, 145, 115], - [10, 74, 46, 23, 75, 47], - [10, 54, 24, 35, 55, 25], - [19, 45, 15, 35, 46, 16] - //33 - , - [17, 145, 115, 1, 146, 116], - [14, 74, 46, 21, 75, 47], - [29, 54, 24, 19, 55, 25], - [11, 45, 15, 46, 46, 16] - //34 - , - [13, 145, 115, 6, 146, 116], - [14, 74, 46, 23, 75, 47], - [44, 54, 24, 7, 55, 25], - [59, 46, 16, 1, 47, 17] - //35 - , - [12, 151, 121, 7, 152, 122], - [12, 75, 47, 26, 76, 48], - [39, 54, 24, 14, 55, 25], - [22, 45, 15, 41, 46, 16] - //36 - , - [6, 151, 121, 14, 152, 122], - [6, 75, 47, 34, 76, 48], - [46, 54, 24, 10, 55, 25], - [2, 45, 15, 64, 46, 16] - //37 - , - [17, 152, 122, 4, 153, 123], - [29, 74, 46, 14, 75, 47], - [49, 54, 24, 10, 55, 25], - [24, 45, 15, 46, 46, 16] - //38 - , - [4, 152, 122, 18, 153, 123], - [13, 74, 46, 32, 75, 47], - [48, 54, 24, 14, 55, 25], - [42, 45, 15, 32, 46, 16] - //39 - , - [20, 147, 117, 4, 148, 118], - [40, 75, 47, 7, 76, 48], - [43, 54, 24, 22, 55, 25], - [10, 45, 15, 67, 46, 16] - //40 - , - [19, 148, 118, 6, 149, 119], - [18, 75, 47, 31, 76, 48], - [34, 54, 24, 34, 55, 25], - [20, 45, 15, 61, 46, 16] -]; - -QRRSBlock.getRSBlocks = function(typeNumber, errorCorrectLevel) { - - var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel); - - if(rsBlock == undefined) { - throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel); - } - - var length = rsBlock.length / 3; - - var list = new Array(); - - for(var i = 0; i < length; i++) { - - var count = rsBlock[i * 3 + 0]; - var totalCount = rsBlock[i * 3 + 1]; - var dataCount = rsBlock[i * 3 + 2]; - - for(var j = 0; j < count; j++) { - list.push(new QRRSBlock(totalCount, dataCount)); - } - } - - return list; -} - -QRRSBlock.getRsBlockTable = function(typeNumber, errorCorrectLevel) { - - switch(errorCorrectLevel) { - case QRErrorCorrectLevel.L: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; - case QRErrorCorrectLevel.M: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; - case QRErrorCorrectLevel.Q: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; - case QRErrorCorrectLevel.H: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; - default: - return undefined; - } -} - -//--------------------------------------------------------------------- -// QRBitBuffer -//--------------------------------------------------------------------- - -function QRBitBuffer() { - this.buffer = new Array(); - this.length = 0; -} - -QRBitBuffer.prototype = { - - get: function(index) { - var bufIndex = Math.floor(index / 8); - return((this.buffer[bufIndex] >>> (7 - index % 8)) & 1) == 1; - }, - - put: function(num, length) { - for(var i = 0; i < length; i++) { - this.putBit(((num >>> (length - i - 1)) & 1) == 1); - } - }, - - getLengthInBits: function() { - return this.length; - }, - - putBit: function(bit) { - - var bufIndex = Math.floor(this.length / 8); - if(this.buffer.length <= bufIndex) { - this.buffer.push(0); - } - - if(bit) { - this.buffer[bufIndex] |= (0x80 >>> (this.length % 8)); - } - - this.length++; - } -}; diff --git a/package.json b/package.json index ae033c8..a4691b2 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,17 @@ { "name": "qrpng", - "version": "0.1.2", + "version": "1.0.0", "description": "Quickly create a QR-Code PNG in pure JavaScript", "main": "./qr.js", + "files": [ + "qr.js", + "qr.d.ts", + "Readme.md" + ], + "scripts": { + "build": "tsc && rollup -c rollup.config.js", + "prepublishOnly": "npm run build" + }, "repository": { "type": "git", "url": "https://github.com/pipobscure/qrpng.git" @@ -13,5 +22,15 @@ }, "dependencies": { "buffer-crc32": "*" - } + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^15.1.0", + "@rollup/plugin-node-resolve": "^9.0.0", + "@types/node": "^14.11.2", + "@types/pako": "^1.0.1", + "pako": "^1.0.11", + "rollup": "^2.28.2", + "typescript": "^4.0.3" + }, + "license": "MIT" } diff --git a/qr.d.ts b/qr.d.ts new file mode 100644 index 0000000..ddc7125 --- /dev/null +++ b/qr.d.ts @@ -0,0 +1,4 @@ +declare module "qr" { + function generate(test: string, scale?: number): Uint8Array; + export = generate; +} diff --git a/qr.js b/qr.js deleted file mode 100644 index dc1da49..0000000 --- a/qr.js +++ /dev/null @@ -1,89 +0,0 @@ -/* -** © 2013 by Philipp Dunkel . Licensed under MIT License. -*/ - -module.exports = makeQR; - -var QRCode = require('./lib/qrcode.js').QRCode; -var PNG = require('./lib/makepng.js'); - -function makeQR(text, scale, callback) { - if(('function' === typeof scale) && !callback) { - callback = scale; - scale = 1; - } - text = text.substr(0, 1273); - var qr = new QRCode(level(text), 2); - qr.addData(text); - qr.make(); - qr = code(qr, scale); - PNG(qr, callback); -} - -function code(qr, scale) { - var res = []; - var mods = qr.getModuleCount(); - var idx, idy; - var l; - for(idy = 0; idy < scale * 2; idy += 1) { - res.push(blankline(qr, scale)); - } - for(idx = 0; idx < mods; idx += 1) { - for(idy = 0; idy < scale; idy += 1) { - l = line(qr, idx, scale); - res.push(l); - } - } - for(idy = 0; idy < scale * 2; idy += 1) { - res.push(blankline(qr, scale)); - } - return res; -} - -function blankline(qr, scale) { - var mods = qr.getModuleCount(); - var res = []; - var idx, idy; - for(idy = 0; idy < scale * 2; idy += 1) { - res.push(0); - } - for(idx = 0; idx < mods; idx += 1) { - for(idy = 0; idy < scale; idy += 1) { - res.push(0); - } - } - for(idy = 0; idy < scale * 2; idy += 1) { - res.push(0); - } - return res; -} - -function line(qr, l, scale) { - var mods = qr.getModuleCount(); - var res = []; - var idx, idy; - for(idy = 0; idy < scale * 2; idy += 1) { - res.push(0); - } - for(idx = 0; idx < mods; idx += 1) { - for(idy = 0; idy < scale; idy += 1) { - res.push(qr.isDark(l, idx) ? 1 : 0); - } - } - for(idy = 0; idy < scale * 2; idy += 1) { - res.push(0); - } - return res; -} - -var levels = [7, 14, 24, 34, 44, 58, 64, 84, 98, 119, 137, 155, 177, 194, 220, 250, 280, 310, 338, 382, 403, 439, 461, 511, 535, 593, 625, 658, 698, 742, 790, 842, 898, 958, 983, 1051, 1093, 1139, 1219, 1273]; - -function level(text) { - var res = 9999; - levels.forEach(function(max, lev) { - if(max < text.length) return; - res = Math.min(lev, res); - }); - if(res >= levels.length) throw(new Error('text too long (max: ' + levels[levels.length - 1] + ')')); - return res + 1; -} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..8972384 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,15 @@ +import noderesolve from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; + +const config = { + input: "lib/qr.js", + output: { + name: "qrpng", + file: "qr.js", + format: "cjs", + exports: "default", + }, + plugins: [noderesolve(), commonjs()], +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8723218 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,60 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, + "module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": false /* Generates corresponding '.d.ts' file. */, + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": false /* Generates corresponding '.map' file. */, + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./dist/debug" /* Redirect output structure to the directory. */, + // "rootDir": "./lib", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": false /* Enable all strict type-checking options. */, + //"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + "noUnusedLocals": true /* Report errors on unused locals. */, + "noUnusedParameters": true /* Report errors on unused parameters. */, + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + "rootDirs": ["lib/"] /* List of root folders whose combined content represents the structure of the project at runtime. */, + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + + /* Source Map Options */ + // "sourceRoot": "src", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + } +}