commit 72921ece2999bd9a5dd6987743280a12a58d2242 Author: Nayuki Minase Date: Sun Apr 10 06:11:57 2016 +0000 Initial commit of QR Code generator library, in Java and JavaScript. diff --git a/BitBuffer.java b/BitBuffer.java new file mode 100644 index 0000000..e9a3dd1 --- /dev/null +++ b/BitBuffer.java @@ -0,0 +1,95 @@ +/* + * QR Code generator library (Java) + * + * Copyright (c) 2016 Project Nayuki + * https://www.nayuki.io/page/qr-code-generator-library + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.qrcodegen; + +import java.util.Arrays; + + +/** + * An appendable sequence of bits. Bits are packed in big endian within a byte. + */ +final class BitBuffer { + + /*---- Fields ----*/ + + private byte[] data; + private int bitLength; + + + + /*---- Constructor ----*/ + + // Creates an empty bit buffer (length 0). + public BitBuffer() { + data = new byte[16]; + bitLength = 0; + } + + + + /*---- Methods ----*/ + + // Returns the number of bits in the buffer, which is a non-negative value. + public int bitLength() { + return bitLength; + } + + + // Returns a copy of all bytes, padding up to the nearest byte. + public byte[] getBytes() { + return Arrays.copyOf(data, (bitLength + 7) / 8); + } + + + // Appends the given number of bits of the given value to this sequence. + // If 0 <= len <= 31, then this requires 0 <= val < 2^len. + public void appendBits(int val, int len) { + if (len < 0 || len > 32 || len < 32 && (val & ((1 << len) - 1)) != val) + throw new IllegalArgumentException("Value out of range"); + ensureCapacity(bitLength + len); + for (int i = len - 1; i >= 0; i--, bitLength++) // Append bit by bit + data[bitLength >>> 3] |= ((val >>> i) & 1) << (7 - (bitLength & 7)); + } + + + // Appends the data of the given segment to this bit buffer. + public void appendData(QrSegment seg) { + if (seg == null) + throw new NullPointerException(); + ensureCapacity(bitLength + seg.bitLength); + for (int i = 0; i < seg.bitLength; i++, bitLength++) { // Append bit by bit + int bit = (seg.getByte(i >>> 3) >>> (7 - (i & 7))) & 1; + data[bitLength >>> 3] |= bit << (7 - (bitLength & 7)); + } + } + + + // Expands the buffer if necessary, so that it can hold at least the given bit length. + private void ensureCapacity(int newBitLen) { + while (data.length < newBitLen) + data = Arrays.copyOf(data, data.length * 2); + } + +} diff --git a/QrCode.java b/QrCode.java new file mode 100644 index 0000000..16ec193 --- /dev/null +++ b/QrCode.java @@ -0,0 +1,848 @@ +/* + * QR Code generator library (Java) + * + * Copyright (c) 2016 Project Nayuki + * https://www.nayuki.io/page/qr-code-generator-library + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.qrcodegen; + +import java.awt.image.BufferedImage; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + + +/** + * Represents an immutable square grid of black and white cells for a QR Code symbol, and + * provides static functions to create a QR Code from user-supplied textual or binary data. + *

This class covers the QR Code model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and only 3 character encoding modes.

+ */ +public final class QrCode { + + /*---- Public static factory functions ----*/ + + /** + * Returns a QR Code symbol representing the specified Unicode text string at the specified error correction level. + * As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer Unicode + * code points (not UTF-16 code units). The smallest possible QR Code version is automatically chosen for the output. + * @param text the text to be encoded, which can be any Unicode string + * @param ecl the error correction level to use + * @return a QR Code representing the text + * @throws NullPointerException if the text or error correction level is {@code null} + * @throws IllegalArgumentException if the text fails to fit in the largest version QR Code, which means it is too long + */ + public static QrCode encodeText(String text, Ecc ecl) { + if (text == null || ecl == null) + throw new NullPointerException(); + QrSegment seg; // Select the most efficient segment encoding automatically + if (QrSegment.NUMERIC_REGEX.matcher(text).matches()) + seg = QrSegment.makeNumeric(text); + else if (QrSegment.ALPHANUMERIC_REGEX.matcher(text).matches()) + seg = QrSegment.makeAlphanumeric(text); + else + seg = QrSegment.makeBytes(text.getBytes(StandardCharsets.UTF_8)); + return encodeSegments(Arrays.asList(seg), ecl); + } + + + /** + * Returns a QR Code symbol representing the specified binary data string at the specified error correction level. + * This function always encodes using the binary segment mode, not any text mode. The maximum number of + * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + * @param data the binary data to encode + * @param ecl the error correction level to use + * @return a QR Code representing the binary data + * @throws NullPointerException if the data or error correction level is {@code null} + * @throws IllegalArgumentException if the data fails to fit in the largest version QR Code, which means it is too long + */ + public static QrCode encodeBinary(byte[] data, Ecc ecl) { + if (data == null || ecl == null) + throw new NullPointerException(); + QrSegment seg = QrSegment.makeBytes(data); + return encodeSegments(Arrays.asList(seg), ecl); + } + + + /** + * Returns a QR Code symbol representing the specified data segments at the specified error + * correction level. The smallest possible QR Code version is automatically chosen for the output. + *

This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and binary) to encode text more efficiently. This + * function is considered to be lower level than simply encoding text or binary data.

+ * @param segs the segments to encode + * @param ecl the error correction level to use + * @return a QR Code representing the segments + * @throws NullPointerException if the list of segments, a segment, or the error correction level is {@code null} + * @throws IllegalArgumentException if the data fails to fit in the largest version QR Code, which means it is too long + */ + public static QrCode encodeSegments(List segs, Ecc ecl) { + if (segs == null || ecl == null) + throw new NullPointerException(); + + // Find the minimal version number to use + int version, dataCapacityBits; + outer: + for (version = 1; ; version++) { // Increment until the data fits in the QR Code + if (version > 40) // All versions could not fit the given data + throw new IllegalArgumentException("Data too long"); + dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + + // Calculate the total number of bits needed at this version number + // to encode all the segments (i.e. segment metadata and payloads) + int dataUsedBits = 0; + for (QrSegment seg : segs) { + if (seg == null) + throw new NullPointerException(); + if (seg.numChars < 0) + throw new AssertionError(); + int ccbits = seg.mode.numCharCountBits(version); + if (seg.numChars >= (1 << ccbits)) { + // Segment length value doesn't fit in the length field's bit-width, so fail immediately + continue outer; + } + dataUsedBits += 4 + ccbits + seg.bitLength; + } + if (dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + } + + // Create the data bit string by concatenating all segments + BitBuffer bb = new BitBuffer(); + for (QrSegment seg : segs) { + bb.appendBits(seg.mode.modeBits, 4); + bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version)); + bb.appendData(seg); + } + + // Add terminator and pad up to a byte if applicable + bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength())); + bb.appendBits(0, (8 - bb.bitLength() % 8) % 8); + + // Pad with alternate bytes until data capacity is reached + for (int padByte = 0xEC; bb.bitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + bb.appendBits(padByte, 8); + if (bb.bitLength() % 8 != 0) + throw new AssertionError(); + + // Create the QR Code symbol + return new QrCode(version, ecl, bb.getBytes(), -1); + } + + + + /*---- Instance fields ----*/ + + // Public immutable scalar parameters + + /** This QR Code symbol's version number, which is always between 1 and 40 (inclusive). */ + public final int version; + + /** The width and height of this QR Code symbol, measured in modules. + * Always equal to version × 4 + 17, in the range 21 to 177. */ + public final int size; + + /** The error correction level used in this QR Code symbol. Never {@code null}. */ + public final Ecc errorCorrectionLevel; + + /** The mask pattern used in this QR Code symbol, in the range 0 to 7 (i.e. unsigned 3-bit integer). + * Note that even if a constructor was called with automatic masking requested + * (mask = -1), the resulting object will still have a mask value between 0 and 7. */ + public final int mask; + + // Private grids of modules/pixels (conceptually immutable) + private boolean[][] modules; // The modules of this QR Code symbol (false = white, true = black) + private boolean[][] isFunction; // Indicates function modules that are not subjected to masking + + + + /*---- Constructors ----*/ + + /** + * Creates a new QR Code symbol with the specified version number, error correction level, binary data string, and mask number. + *

This cumbersome constructor can be invoked directly by the user, but is considered + * to be even lower level than {@link #encodeSegments(List,Ecc)}.

+ * @param ver the version number to use, which must be in the range 1 to 40, inclusive + * @param ecl the error correction level to use + * @param dataCodewords the raw binary user data to encode + * @param mask the mask pattern to use, which is either -1 for automatic choice or from 0 to 7 for fixed choice + * @throws NullPointerException if the byte array or error correction level is {@code null} + * @throws IllegalArgumentException if the version or mask value is out of range + */ + public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { + // Check arguments + if (ecl == null) + throw new NullPointerException(); + if (ver < 1 || ver > 40 || mask < -1 || mask > 7) + throw new IllegalArgumentException("Value out of range"); + if (dataCodewords == null) + throw new NullPointerException(); + + // Initialize fields + version = ver; + size = ver * 4 + 17; + errorCorrectionLevel = ecl; + modules = new boolean[size][size]; // Entirely white grid + isFunction = new boolean[size][size]; + + // Draw function patterns, draw all codewords, do masking + drawFunctionPatterns(); + byte[] allCodewords = appendErrorCorrection(dataCodewords); + drawCodewords(allCodewords); + this.mask = handleConstructorMasking(mask); + } + + + /** + * Creates a new QR Code symbol based on the specified existing object, but with a potentially + * different mask pattern. The version, error correction level, codewords, etc. of the newly + * created object are all identical to the argument object; only the mask may differ. + * @param qr the existing QR Code to copy and modify + * @param mask the new mask pattern, 0 to 7 to force a fixed choice or -1 for an automatic choice + * @throws NullPointerException if the QR Code is {@code null} + * @throws IllegalArgumentException if the mask value is out of range + */ + public QrCode(QrCode qr, int mask) { + // Check arguments + if (qr == null) + throw new NullPointerException(); + if (mask < -1 || mask > 7) + throw new IllegalArgumentException("Mask value out of range"); + + // Copy scalar fields + version = qr.version; + size = qr.size; + errorCorrectionLevel = qr.errorCorrectionLevel; + + // Handle grid fields + isFunction = qr.isFunction; // Shallow copy because the data is read-only + modules = qr.modules.clone(); // Deep copy + for (int i = 0; i < modules.length; i++) + modules[i] = modules[i].clone(); + + // Handle masking + applyMask(qr.mask); // Undo old mask + this.mask = handleConstructorMasking(mask); + } + + + + /*---- Public instance methods ----*/ + + /** + * Returns the color of the module (pixel) at the specified coordinates, which is either 0 for white or 1 for black. The top + * left corner has the coordinates (x=0, y=0). If the specified coordinates are out of bounds, then 0 (white) is returned. + * @param x the x coordinate, where 0 is the left edge and size−1 is the right edge + * @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge + * @return the module's color, which is either 0 (white) or 1 (black) + */ + public int getModule(int x, int y) { + if (x < 0 || x >= size || y < 0 || y >= size) + return 0; // Infinite white border + else + return modules[y][x] ? 1 : 0; + } + + + /** + * Returns a new image object representing this QR Code, with the specified module scale and number + * of border modules. For example, the arguments scale=10, border=4 means to pad the QR Code symbol + * with 4 white border modules on all four edges, then use 10*10 pixels to represent each module. + * The resulting image only contains the hex colors 000000 and FFFFFF. + * @param scale the module scale factor, which must be positive + * @param border the number of border modules to add, which must be non-negative + * @return an image representing this QR Code, with padding and scaling + * @throws IllegalArgumentException if the scale or border is out of range + */ + public BufferedImage toImage(int scale, int border) { + if (scale <= 0 || border < 0) + throw new IllegalArgumentException("Value out of range"); + BufferedImage result = new BufferedImage((size + border * 2) * scale, (size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < result.getHeight(); y++) { + for (int x = 0; x < result.getWidth(); x++) { + int val = getModule(x / scale - border, y / scale - border); // 0 or 1 + result.setRGB(x, y, val == 1 ? 0x000000 : 0xFFFFFF); + } + } + return result; + } + + + /** + * Based on the specified number of border modules to add as padding, this returns a + * string whose contents represents an SVG XML file that depicts this QR Code symbol. + * Note that Unix newlines (\n) are always used, regardless of the platform. + * @param border the number of border modules to add, which must be non-negative + * @return a string representing this QR Code as an SVG document + */ + public String toSvgString(int border) { + if (border < 0) + throw new IllegalArgumentException("Border must be non-negative"); + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("\n"); + sb.append(String.format("\n", size + border * 2)); + sb.append("\t\n"); + sb.append("\n"); + return sb.toString(); + } + + + + /*---- Private helper methods for constructor: Drawing function modules ----*/ + + private void drawFunctionPatterns() { + // Draw the horizontal and vertical timing patterns + for (int i = 0; i < size; i++) { + setFunctionModule(6, i, i % 2 == 0); + setFunctionModule(i, 6, i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + drawFinderPattern(3, 3); + drawFinderPattern(size - 4, 3); + drawFinderPattern(3, size - 4); + + // Draw the numerous alignment patterns + int[] alignPatPos = getAlignmentPatternPositions(version); + int numAlign = alignPatPos.length; + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + if (i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0) + continue; // Skip the three finder corners + else + drawAlignmentPattern(alignPatPos[i], alignPatPos[j]); + } + } + + // Draw configuration data + drawFormatBits(0); // Dummy mask value; overwritten later in the constructor + drawVersion(); + } + + + // Draws two copies of the format bits (with its own error correction code) + // based on this object's error correction level and mask fields. + private void drawFormatBits(int mask) { + // Calculate error correction code and pack bits + int data = errorCorrectionLevel.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >>> 9) * 0x537); + data = data << 10 | rem; + data ^= 0x5412; // uint15 + if ((data & ((1 << 15) - 1)) != data) + throw new AssertionError(); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setFunctionModule(8, i, ((data >>> i) & 1) != 0); + setFunctionModule(8, 7, ((data >>> 6) & 1) != 0); + setFunctionModule(8, 8, ((data >>> 7) & 1) != 0); + setFunctionModule(7, 8, ((data >>> 8) & 1) != 0); + for (int i = 9; i < 15; i++) + setFunctionModule(14 - i, 8, ((data >>> i) & 1) != 0); + + // Draw second copy + for (int i = 0; i <= 7; i++) + setFunctionModule(size - 1 - i, 8, ((data >>> i) & 1) != 0); + for (int i = 8; i < 15; i++) + setFunctionModule(8, size - 15 + i, ((data >>> i) & 1) != 0); + setFunctionModule(8, size - 8, true); + } + + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field (which only has an effect for 7 <= version <= 40). + private void drawVersion() { + if (version < 7) + return; + + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25); + int data = version << 12 | rem; // uint18 + if ((data & ((1 << 18) - 1)) != data) + throw new AssertionError(); + + // Draw two copies + for (int i = 0; i < 18; i++) { + boolean bit = ((data >>> i) & 1) != 0; + setFunctionModule(size - 11 + i % 3, i / 3, bit); + setFunctionModule(i / 3, size - 11 + i % 3, bit); + } + } + + + // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). + private void drawFinderPattern(int x, int y) { + for (int i = -4; i <= 4; i++) { + for (int j = -4; j <= 4; j++) { + int dist = Math.max(Math.abs(i), Math.abs(j)); // Chebyshev/infinity norm + int xx = x + j; + int yy = y + i; + if (0 <= xx && xx < size && 0 <= yy && yy < size) + setFunctionModule(xx, yy, dist != 2 && dist != 4); + } + } + } + + + // Draws a 5*5 alignment pattern, with the center module at (x, y). + private void drawAlignmentPattern(int x, int y) { + for (int i = -2; i <= 2; i++) { + for (int j = -2; j <= 2; j++) + setFunctionModule(x + j, y + i, Math.max(Math.abs(i), Math.abs(j)) != 1); + } + } + + + // Sets the color of a module and marks it as a function module. + // Only used by the constructor. Coordinates must be in range. + private void setFunctionModule(int x, int y, boolean isBlack) { + modules[y][x] = isBlack; + isFunction[y][x] = true; + } + + + /*---- Private helper methods for constructor: Codewords and masking ----*/ + + // Returns a new byte string representing the given data with the appropriate error correction + // codewords appended to it, based on this object's version and error correction level. + private byte[] appendErrorCorrection(byte[] data) { + if (data.length != getNumDataCodewords(version, errorCorrectionLevel)) + throw new IllegalArgumentException(); + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[errorCorrectionLevel.ordinal()][version]; + int numEcc = NUM_ERROR_CORRECTION_CODEWORDS[errorCorrectionLevel.ordinal()][version]; + if (numEcc % numBlocks != 0) + throw new AssertionError(); + int eccLen = numEcc / numBlocks; + int numShortBlocks = numBlocks - getNumRawDataModules(version) / 8 % numBlocks; + int shortBlockLen = getNumRawDataModules(version) / 8 / numBlocks; + + byte[][] blocks = new byte[numBlocks][]; + ReedSolomonGenerator rs = new ReedSolomonGenerator(eccLen); + for (int i = 0, k = 0; i < numBlocks; i++) { + byte[] dat = Arrays.copyOfRange(data, k, k + shortBlockLen - eccLen + (i < numShortBlocks ? 0 : 1)); + byte[] block = Arrays.copyOf(dat, shortBlockLen + 1); + k += dat.length; + byte[] ecc = rs.getRemainder(dat); + System.arraycopy(ecc, 0, block, block.length - eccLen, ecc.length); + blocks[i] = block; + } + + byte[] result = new byte[getNumRawDataModules(version) / 8]; + for (int i = 0, k = 0; i < blocks[0].length; i++) { + for (int j = 0; j < blocks.length; j++) { + if (i != shortBlockLen - eccLen || j >= numShortBlocks) { + result[k] = blocks[j][i]; + k++; + } + } + } + return result; + } + + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code symbol. Function modules need to be marked off before this is called. + private void drawCodewords(byte[] data) { + if (data == null) + throw new NullPointerException(); + if (data.length != getNumRawDataModules(version) / 8) + throw new IllegalArgumentException(); + + int i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < size; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + int x = right - j; // Actual x coordinate + boolean upwards = ((right & 2) == 0) ^ (x < 6); + int y = upwards ? size - 1 - vert : vert; // Actual y coordinate + if (!isFunction[y][x] && i < data.length * 8) { + modules[y][x] = (data[i >>> 3] >>> (7 - (i & 7)) & 1) != 0; + i++; + } + } + } + } + if (i != data.length * 8) + throw new AssertionError(); + } + + + // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical + // properties, calling applyMask(m) twice with the same value is equivalent to no change at all. + // This means it is possible to apply a mask, undo it, and try another mask. Note that a final + // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). + private void applyMask(int mask) { + if (mask < 0 || mask > 7) + throw new IllegalArgumentException("Mask value out of range"); + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + boolean invert; + switch (mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: throw new AssertionError(); + } + modules[y][x] ^= invert & !isFunction[y][x]; + } + } + } + + + // A messy helper function for the constructors. This QR Code must be in an unmasked state when this + // method is called. The given argument is the requested mask, which is -1 for auto or 0 to 7 for fixed. + // This method applies and returns the actual mask chosen, from 0 to 7. + private int handleConstructorMasking(int mask) { + if (mask == -1) { // Automatically choose best mask + int minPenalty = Integer.MAX_VALUE; + for (int i = 0; i < 8; i++) { + drawFormatBits(i); + applyMask(i); + int penalty = getPenaltyScore(); + if (penalty < minPenalty) { + mask = i; + minPenalty = penalty; + } + applyMask(i); // Undoes the mask due to XOR + } + } + if (mask < 0 || mask > 7) + throw new AssertionError(); + drawFormatBits(mask); // Overwrite old format bits + applyMask(mask); // Apply the final choice of mask + return mask; // The caller shall assign this value to the final-declared field + } + + + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + private int getPenaltyScore() { + int result = 0; + + // Adjacent modules in row having same color + for (int y = 0; y < size; y++) { + boolean colorX = modules[y][0]; + for (int x = 1, runX = 1; x < size; x++) { + if (modules[y][x] != colorX) { + colorX = modules[y][x]; + runX = 1; + } else { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } + } + } + // Adjacent modules in column having same color + for (int x = 0; x < size; x++) { + boolean colorY = modules[0][x]; + for (int y = 1, runY = 1; y < size; y++) { + if (modules[y][x] != colorY) { + colorY = modules[y][x]; + runY = 1; + } else { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } + } + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < size - 1; y++) { + for (int x = 0; x < size - 1; x++) { + boolean color = modules[y][x]; + if ( color == modules[y][x + 1] && + color == modules[y + 1][x] && + color == modules[y + 1][x + 1]) + result += PENALTY_N2; + } + } + + // Finder-like pattern in rows + for (int y = 0; y < size; y++) { + for (int x = 0, bits = 0; x < size; x++) { + bits = ((bits << 1) & 0x7FF) | (modules[y][x] ? 1 : 0); + if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated + result += PENALTY_N3; + } + } + // Finder-like pattern in columns + for (int x = 0; x < size; x++) { + for (int y = 0, bits = 0; y < size; y++) { + bits = ((bits << 1) & 0x7FF) | (modules[y][x] ? 1 : 0); + if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated + result += PENALTY_N3; + } + } + + // Balance of black and white modules + int black = 0; + for (boolean[] row : modules) { + for (boolean color : row) { + if (color) + black++; + } + } + int total = size * size; + // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% + for (int k = 0; black*20 < (9-k)*total || black*20 > (11+k)*total; k++) + result += PENALTY_N4; + return result; + } + + + + /*---- Static helper functions ----*/ + + // Returns a set of positions of the alignment patterns in ascending order. These positions are + // used on both the x and y axes. Each value in the resulting array is in the range [0, 177). + // This stateless pure function could be implemented as table of 40 variable-length lists of unsigned bytes. + private static int[] getAlignmentPatternPositions(int ver) { + if (ver < 1 || ver > 40) + throw new IllegalArgumentException("Version number out of range"); + else if (ver == 1) + return new int[]{}; + else { + int numAlign = ver / 7 + 2; + int step; + if (ver != 32) + step = (ver * 4 + numAlign * 2 + 1) / (2 * numAlign - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2 + else // C-C-C-Combo breaker! + step = 26; + + int[] result = new int[numAlign]; + int size = ver * 4 + 17; + result[0] = 6; + for (int i = result.length - 1, pos = size - 7; i >= 1; i--, pos -= step) + result[i] = pos; + return result; + } + } + + + // Returns the number of raw data modules (bits) available at the given version number. + // These data modules are used for both user data codewords and error correction codewords. + // This stateless pure function could be implemented as a 40-entry lookup table. + private static int getNumRawDataModules(int ver) { + if (ver < 1 || ver > 40) + throw new IllegalArgumentException("Version number out of range"); + + int size = ver * 4 + 17; + int result = size * size; // Number of modules in the whole QR symbol square + result -= 64 * 3; // Subtract the three finders with separators + result -= 15 * 2 + 1; // Subtract the format information and black module + result -= (size - 16) * 2; // Subtract the timing patterns + // The four lines above are equivalent to: int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (numAlign - 1) * (numAlign - 1) * 25; // Subtract alignment patterns not overlapping with timing patterns + result -= (numAlign - 2) * 2 * 20; // Subtract alignment patterns that overlap with timing patterns + // The two lines above are equivalent to: result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 18 * 2; // Subtract version information + } + return result; + } + + + // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any + // QR Code of the given version number and error correction level, with remainder bits discarded. + // This stateless pure function could be implemented as a (40*4)-cell lookup table. + private static int getNumDataCodewords(int ver, Ecc ecl) { + return getNumRawDataModules(ver) / 8 - NUM_ERROR_CORRECTION_CODEWORDS[ecl.ordinal()][ver]; + } + + + /*---- Tables of constants ----*/ + + // For use in getPenaltyScore(), when evaluating which mask is best. + private static final int PENALTY_N1 = 3; + private static final int PENALTY_N2 = 3; + private static final int PENALTY_N3 = 40; + private static final int PENALTY_N4 = 10; + + + private static final short[][] NUM_ERROR_CORRECTION_CODEWORDS = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 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, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low + {-1, 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium + {-1, 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile + {-1, 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High + }; + + private static final byte[][] NUM_ERROR_CORRECTION_BLOCKS = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 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, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-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}, // Low + {-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}, // Medium + {-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}, // Quartile + {-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}, // High + }; + + + + /*---- Public helper enumeration ----*/ + + /** + * Represents the error correction level used in a QR Code symbol. + */ + public enum Ecc { + // Constants declared in ascending order of error protection. + LOW(1), MEDIUM(0), QUARTILE(3), HIGH(2); + + // In the range 0 to 3 (unsigned 2-bit integer). + public final int formatBits; + + // Constructor. + private Ecc(int fb) { + formatBits = fb; + } + } + + + + /*---- Private helper class ----*/ + + /** + * Computes the Reed-Solomon error correction codewords for a sequence of data codewords + * at a given degree. Objects are immutable, and the state only depends on the degree. + * This class exists because the divisor polynomial does not need to be recalculated for every input. + */ + private static final class ReedSolomonGenerator { + + /*-- Immutable field --*/ + + // Coefficients of the divisor polynomial, stored from highest to lowest power, excluding the leading term which + // is always 1. For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + private final byte[] coefficients; + + + /*-- Constructor --*/ + + /** + * Creates a Reed-Solomon ECC generator for the specified degree. This could be implemented + * as a lookup table over all possible parameter values, instead of as an algorithm. + * @param degree the divisor polynomial degree, which must be between 1 and 255 + * @throws IllegalArgumentException if degree < 1 or degree > 255 + */ + public ReedSolomonGenerator(int degree) { + if (degree < 1 || degree > 255) + throw new IllegalArgumentException("Degree out of range"); + + // Start with the monomial x^0 + coefficients = new byte[degree]; + coefficients[degree - 1] = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + int root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (int j = 0; j < coefficients.length; j++) { + coefficients[j] = (byte)multiply(coefficients[j] & 0xFF, root); + if (j + 1 < coefficients.length) + coefficients[j] ^= coefficients[j + 1]; + } + root = (root << 1) ^ ((root >>> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) + } + } + + + /*-- Method --*/ + + /** + * Computes and returns the Reed-Solomon error correction codewords for the specified sequence of data codewords. + * The returned object is always a new byte array. This method does not alter this object's state (because it is immutable). + * @param data the sequence of data codewords + * @return the Reed-Solomon error correction codewords + * @throws NullPointerException if the data is {@code null} + */ + public byte[] getRemainder(byte[] data) { + if (data == null) + throw new NullPointerException(); + + // Compute the remainder by performing polynomial division + byte[] result = new byte[coefficients.length]; + for (byte b : data) { + int factor = (b ^ result[0]) & 0xFF; + System.arraycopy(result, 1, result, 0, result.length - 1); + result[result.length - 1] = 0; + for (int j = 0; j < result.length; j++) + result[j] ^= multiply(coefficients[j] & 0xFF, factor); + } + return result; + } + + + /*-- Static function --*/ + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. + private static int multiply(int x, int y) { + if ((x & 0xFF) != x || (y & 0xFF) != y) + throw new IllegalArgumentException("Byte out of range"); + // Russian peasant multiplication + int z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >>> 7) * 0x11D); + z ^= ((y >>> i) & 1) * x; + } + if ((z & 0xFF) != z) + throw new AssertionError(); + return z; + } + + } + +} diff --git a/QrCodeGeneratorDemo.java b/QrCodeGeneratorDemo.java new file mode 100644 index 0000000..e644f9a --- /dev/null +++ b/QrCodeGeneratorDemo.java @@ -0,0 +1,147 @@ +/* + * QR Code generator demo (Java) + * + * Run this command-line program with no arguments. The program creates/overwrites a bunch of + * PNG and SVG files in the current working directory to demonstrate the creation of QR Codes. + * + * Copyright (c) 2016 Project Nayuki + * https://www.nayuki.io/page/qr-code-generator-library + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.qrcodegen; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import javax.imageio.ImageIO; + + +public final class QrCodeGeneratorDemo { + + // The main application program. + public static void main(String[] args) throws IOException { + doBasicDemo(); + doVarietyDemo(); + doSegmentDemo(); + } + + + // Creates a single QR Code, then writes it to a PNG file and an SVG file. + private static void doBasicDemo() throws IOException { + String text = "Hello, world!"; // User-supplied Unicode text + QrCode.Ecc errCorLvl = QrCode.Ecc.LOW; // Error correction level + + QrCode qr = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol + + BufferedImage img = qr.toImage(10, 4); // Convert to bitmap image + File imgFile = new File("hello-world-QR.png"); // File path for output + ImageIO.write(img, "png", imgFile); // Write image to file + + String svg = qr.toSvgString(4); // Convert to SVG XML code + try (Writer out = new OutputStreamWriter( + new FileOutputStream("hello-world-QR.svg"), + StandardCharsets.UTF_8)) { + out.write(svg); // Create/overwrite file and write SVG data + } + } + + + // Creates a variety of QR Codes that exercise different features of the library, and writes each one to file. + private static void doVarietyDemo() throws IOException { + QrCode qr; + + // Project Nayuki URL + qr = QrCode.encodeText("https://www.nayuki.io/", QrCode.Ecc.HIGH); + qr = new QrCode(qr, 3); // Change mask, forcing to mask #3 + writePng(qr.toImage(8, 6), "project-nayuki-QR.png"); + + // Numeric mode encoding (3.33 bits per digit) + qr = QrCode.encodeText("314159265358979323846264338327950288419716939937510", QrCode.Ecc.MEDIUM); + writePng(qr.toImage(13, 1), "pi-digits-QR.png"); + + // Alphanumeric mode encoding (5.5 bits per character) + qr = QrCode.encodeText("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", QrCode.Ecc.HIGH); + writePng(qr.toImage(10, 2), "alphanumeric-QR.png"); + + // Unicode text as UTF-8, and different masks + qr = QrCode.encodeText("こんにちwa、世界! αβγδ", QrCode.Ecc.QUARTILE); + writePng(new QrCode(qr, 0).toImage(10, 3), "unicode-mask0-QR.png"); + writePng(new QrCode(qr, 1).toImage(10, 3), "unicode-mask1-QR.png"); + writePng(new QrCode(qr, 5).toImage(10, 3), "unicode-mask5-QR.png"); + writePng(new QrCode(qr, 7).toImage(10, 3), "unicode-mask7-QR.png"); + + // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) + qr = QrCode.encodeText("Alice was beginning to get very tired of sitting by her sister on the bank, " + + "and of having nothing to do: once or twice she had peeped into the book her sister was reading, " + + "but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice " + + "'without pictures or conversations?' So she was considering in her own mind (as well as she could, " + + "for the hot day made her feel very sleepy and stupid), whether the pleasure of making a " + + "daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly " + + "a White Rabbit with pink eyes ran close by her.", + QrCode.Ecc.HIGH); + writePng(qr.toImage(6, 10), "alice-wonderland-QR.png"); + } + + + // Creates QR Codes with manually specified segments for better compactness. + private static void doSegmentDemo() throws IOException { + QrCode qr; + List segs; + + // Illustration "silver" + String silver0 = "THE SQUARE ROOT OF 2 IS 1."; + String silver1 = "41421356237309504880168872420969807856967187537694807317667973799"; + qr = QrCode.encodeText(silver0 + silver1, QrCode.Ecc.LOW); + writePng(qr.toImage(10, 3), "sqrt2-monolithic-QR.png"); + + segs = Arrays.asList( + QrSegment.makeAlphanumeric(silver0), + QrSegment.makeNumeric(silver1)); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); + writePng(qr.toImage(10, 3), "sqrt2-segmented-QR.png"); + + // Illustration "golden" + String golden0 = "Golden ratio φ = 1."; + String golden1 = "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374"; + String golden2 = "......"; + qr = QrCode.encodeText(golden0 + golden1 + golden2, QrCode.Ecc.LOW); + writePng(qr.toImage(8, 5), "phi-monolithic-QR.png"); + + segs = Arrays.asList( + QrSegment.makeBytes(golden0.getBytes(StandardCharsets.UTF_8)), + QrSegment.makeNumeric(golden1), + QrSegment.makeAlphanumeric(golden2)); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); + writePng(qr.toImage(8, 5), "phi-segmented-QR.png"); + } + + + // Helper function to reduce code duplication. + private static void writePng(BufferedImage img, String filepath) throws IOException { + ImageIO.write(img, "png", new File(filepath)); + } + +} diff --git a/QrSegment.java b/QrSegment.java new file mode 100644 index 0000000..20c0609 --- /dev/null +++ b/QrSegment.java @@ -0,0 +1,222 @@ +/* + * QR Code generator library (Java) + * + * Copyright (c) 2016 Project Nayuki + * https://www.nayuki.io/page/qr-code-generator-library + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.qrcodegen; + +import java.util.Arrays; +import java.util.regex.Pattern; + + +/** + * Represents a character string to be encoded in a QR Code symbol. Each segment has + * a mode, and a sequence of characters that is already encoded as a sequence of bits. + * Instances of this class are immutable. + *

This segment class imposes no length restrictions, but QR Codes have restrictions. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes.

+ */ +public final class QrSegment { + + /*---- Static factory functions ----*/ + + /** + * Returns a segment representing the specified binary data encoded in byte mode. + * @param data the binary data + * @return a segment containing the data + * @throws NullPointerException if the array is {@code null} + */ + public static QrSegment makeBytes(byte[] data) { + if (data == null) + throw new NullPointerException(); + return new QrSegment(Mode.BYTE, data.length, data, data.length * 8); + } + + + /** + * Returns a segment representing the specified string of decimal digits encoded in numeric mode. + * @param digits a string consisting of digits from 0 to 9 + * @return a segment containing the data + * @throws NullPointerException if the string is {@code null} + * @throws IllegalArgumentException if the string contains non-digit characters + */ + public static QrSegment makeNumeric(String digits) { + if (digits == null) + throw new NullPointerException(); + if (!NUMERIC_REGEX.matcher(digits).matches()) + throw new IllegalArgumentException("String contains non-numeric characters"); + + BitBuffer bb = new BitBuffer(); + int i; + for (i = 0; i + 3 <= digits.length(); i += 3) // Process groups of 3 + bb.appendBits(Integer.parseInt(digits.substring(i, i + 3)), 10); + int rem = digits.length() - i; + if (rem > 0) // 1 or 2 digits remaining + bb.appendBits(Integer.parseInt(digits.substring(i)), rem * 3 + 1); + return new QrSegment(Mode.NUMERIC, digits.length(), bb.getBytes(), bb.bitLength()); + } + + + /** + * Returns a segment representing the specified text string encoded in alphanumeric mode. The characters allowed are: + * 0 to 9, A to Z (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + * @param text a string of text, with only certain characters allowed + * @return a segment containing the data + * @throws NullPointerException if the string is {@code null} + * @throws IllegalArgumentException if the string contains non-encodable characters + */ + public static QrSegment makeAlphanumeric(String text) { + if (text == null) + throw new NullPointerException(); + if (!ALPHANUMERIC_REGEX.matcher(text).matches()) + throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode"); + + BitBuffer bb = new BitBuffer(); + int i; + for (i = 0; i + 2 <= text.length(); i += 2) { // Process groups of 2 + int temp = ALPHANUMERIC_ENCODING_TABLE[text.charAt(i) - ' '] * 45; + temp += ALPHANUMERIC_ENCODING_TABLE[text.charAt(i + 1) - ' ']; + bb.appendBits(temp, 11); + } + if (i < text.length()) // 1 character remaining + bb.appendBits(ALPHANUMERIC_ENCODING_TABLE[text.charAt(i) - ' '], 6); + return new QrSegment(Mode.ALPHANUMERIC, text.length(), bb.getBytes(), bb.bitLength()); + } + + + + /*---- Instance fields ----*/ + + /** The mode indicator for this segment. Never {@code null}. */ + public final Mode mode; + + /** The length of this segment's unencoded data, measured in characters. Always zero or positive. */ + public final int numChars; + + /** The bits of this segment packed into a byte array in big endian. Accessed through {@link getByte(int)}. Not {@code null}. */ + private final byte[] data; + + /** The length of this segment's encoded data, measured in bits. Satisfies 0 ≤ {@code bitLength} ≤ {@code data.length} × 8. */ + public final int bitLength; + + + /*---- Constructor ----*/ + + /** + * Creates a new QR Code data segment with the specified parameters and data. + * @param md the mode, which is not {@code null} + * @param numCh the data length in characters, which is non-negative + * @param bitLen the data length in bits, which is non-negative + * @param b the bits packed into bytes, which is not {@code null} + * @throws NullPointerException if the mode or array is {@code null} + * @throws IllegalArgumentException if the character count or bit length are negative or invalid + */ + public QrSegment(Mode md, int numCh, byte[] b, int bitLen) { + if (md == null || b == null) + throw new NullPointerException(); + if (numCh < 0 || bitLen < 0 || bitLen > b.length * 8L) + throw new IllegalArgumentException("Invalid value"); + mode = md; + numChars = numCh; + data = Arrays.copyOf(b, (bitLen + 7) / 8); // Trim to precise length and also make defensive copy + bitLength = bitLen; + } + + + /*---- Method ----*/ + + /** + * Returns the data byte at the specified index. + * @param index the index to retrieve from, satisfying 0 ≤ {@code index} < ceil({@code bitLength} ÷ 8) + * @return the data byte at the specified index + * @throws IndexOutOfBoundsException if the index is out of bounds + */ + public byte getByte(int index) { + if (index < 0 || index > data.length) + throw new IndexOutOfBoundsException(); + return data[index]; + } + + + /*---- Constants ----*/ + + /** Can test whether a string is encodable in numeric mode (such as by using {@link #makeNumeric(String)}). */ + public static final Pattern NUMERIC_REGEX = Pattern.compile("[0-9]*"); + + /** Can test whether a string is encodable in alphanumeric mode (such as by using {@link #makeAlphanumeric(String)}). */ + public static final Pattern ALPHANUMERIC_REGEX = Pattern.compile("[A-Z0-9 $%*+./:-]*"); + + private static final byte[] ALPHANUMERIC_ENCODING_TABLE = { + // SP, !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?, @, // ASCII codes 32 to 64 + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, -1, // Array indices 0 to 32 + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, // Array indices 33 to 58 + // A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, // ASCII codes 65 to 90 + }; + + + + /*---- Public helper enumeration ----*/ + + /** + * The mode field of a segment. Immutable. Provides methods to retrieve closely related values. + */ + public enum Mode { + // Constants. + NUMERIC (0x1, 10, 12, 14), + ALPHANUMERIC(0x2, 9, 11, 13), + BYTE (0x4, 8, 16, 16), + KANJI (0x8, 8, 10, 12); + + + /*-- Fields --*/ + + /** An unsigned 4-bit integer value (range 0 to 15) representing the mode indicator bits for this mode object. */ + public final int modeBits; + + private final int[] numBitsCharCount; + + + // Constructor. + private Mode(int mode, int... ccbits) { + this.modeBits = mode; + numBitsCharCount = ccbits; + } + + + /*-- Method --*/ + + /** + * Returns the bit width of the segment character count field for this mode object at the specified version number. + * @param ver the version number, which is between 1 to 40, inclusive + * @return the number of bits for the character count, which is between 8 to 16, inclusive + * @throws IllegalArgumentException if the version number is out of range + */ + public int numCharCountBits(int ver) { + if ( 1 <= ver && ver <= 9) return numBitsCharCount[0]; + else if (10 <= ver && ver <= 26) return numBitsCharCount[1]; + else if (27 <= ver && ver <= 40) return numBitsCharCount[2]; + else throw new IllegalArgumentException("Version number out of range"); + } + } + +} diff --git a/qrcodegen-demo.js b/qrcodegen-demo.js new file mode 100644 index 0000000..b48d505 --- /dev/null +++ b/qrcodegen-demo.js @@ -0,0 +1,107 @@ +/* + * QR Code generator demo (JavaScript) + * + * Copyright (c) 2016 Project Nayuki + * https://www.nayuki.io/page/qr-code-generator-library + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +"use strict"; + + +function redrawQrCode() { + // Get error correction level + var ecl; + if (document.getElementById("errcorlvl-medium").checked) + ecl = qrcodegen.QrCode.Ecc.MEDIUM; + else if (document.getElementById("errcorlvl-quartile").checked) + ecl = qrcodegen.QrCode.Ecc.QUARTILE; + else if (document.getElementById("errcorlvl-high").checked) + ecl = qrcodegen.QrCode.Ecc.HIGH; + else // In case no radio button is depressed + ecl = qrcodegen.QrCode.Ecc.LOW; + + // Get text and compute QR Code + var text = document.getElementById("text-input").value; + var qr = qrcodegen.encodeText(text, ecl); + + // Get scale and border + var scale = parseInt(document.getElementById("scale-input").value, 10); + var border = parseInt(document.getElementById("border-input").value, 10); + if (scale <= 0 || border < 0 || scale > 30 || border > 100) + return; + + // Draw QR Code onto canvas + var canvas = document.getElementById("qrcode-canvas"); + var width = (qr.getSize() + border * 2) * scale; + if (canvas.width != width) { + canvas.width = width; + canvas.height = width; + } + var ctx = canvas.getContext("2d"); + for (var y = -border; y < qr.getSize() + border; y++) { + for (var x = -border; x < qr.getSize() + border; x++) { + ctx.fillStyle = qr.getModule(x, y) == 1 ? "#000000" : "#FFFFFF"; + ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale); + } + } + + // Show statistics + var stats = "QR Code version = " + qr.getVersion() + ", "; + stats += "mask pattern = " + qr.getMask() + ", "; + stats += "character count = " + countUnicodeChars(text) + ",\n"; + stats += "encoding mode = "; + var seg = qrcodegen.encodeTextToSegment(text); + if (seg.getMode() == qrcodegen.QrSegment.Mode.NUMERIC) + stats += "numeric"; + else if (seg.getMode() == qrcodegen.QrSegment.Mode.ALPHANUMERIC) + stats += "alphanumeric"; + else if (seg.getMode() == qrcodegen.QrSegment.Mode.BYTE) + stats += "byte"; + else if (seg.getMode() == qrcodegen.QrSegment.Mode.BYTE) + stats += "kanji"; + else + stats += "unknown"; + stats += ", data bits = " + (4 + seg.getMode().numCharCountBits(qr.getVersion()) + seg.getBits().length) + "."; + var elem = document.getElementById("statistics-output"); + while (elem.firstChild != null) + elem.removeChild(elem.firstChild); + elem.appendChild(document.createTextNode(stats)); +} + + +function countUnicodeChars(str) { + var result = 0; + for (var i = 0; i < str.length; i++, result++) { + var c = str.charCodeAt(i); + if (c < 0xD800 || c >= 0xE000) + continue; + else if (0xD800 <= c && c < 0xDC00) { // High surrogate + i++; + var d = str.charCodeAt(i); + if (0xDC00 <= d && d < 0xE000) // Low surrogate + continue; + } + throw "Invalid UTF-16 string"; + } + return result; +} + + +redrawQrCode(); diff --git a/qrcodegen-js-demo.html b/qrcodegen-js-demo.html new file mode 100644 index 0000000..1fea5e8 --- /dev/null +++ b/qrcodegen-js-demo.html @@ -0,0 +1,81 @@ + + + + + + QR Code generator library demo (JavaScript) + + + + +

QR Code generator demo library (JavaScript)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Text string:
Error correction: + + + + +
Scale: pixels per module
Border: modules
Statistics:
QR Code: + +
+
+ + + +
+

Copyright © 2016 Project Nayuki – https://www.nayuki.io/page/qr-code-generator-library

+ + diff --git a/qrcodegen.js b/qrcodegen.js new file mode 100644 index 0000000..8dacbe1 --- /dev/null +++ b/qrcodegen.js @@ -0,0 +1,975 @@ +/* + * QR Code generator library (JavaScript) + * + * Copyright (c) 2016 Project Nayuki + * https://www.nayuki.io/page/qr-code-generator-library + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +"use strict"; + + +/* + * Module "qrcodegen". Public members inside this namespace: + * - Function encodeText(str text, Ecc ecl) -> QrCode + * - Function encodeTextToSegment(str text) -> QrSegment + * - Function encodeBinary(list data, Ecc ecl) -> QrCode + * - Function encodeSegments(list segs, Ecc ecl) -> QrCode + * - Class QrCode: + * - Constructor QrCode(QrCode qr, int mask) + * - Constructor QrCode(list bytes, int mask, int version, Ecc ecl) + * - Method getVersion() -> int + * - Method getSize() -> int + * - Method getErrorCorrectionLevel() -> Ecc + * - Method getMask() -> int + * - Method getModule(int x, int y) -> int + * - Method isFunctionModule(int x, int y) -> bool + * - Method toSvgString(int border) -> str + * - Enum Ecc: + * - Constants LOW, MEDIUM, QUARTILE, HIGH + * - Fields int ordinal, formatBits + * - Class QrSegment: + * - Function makeBytes(list data) -> QrSegment + * - Function makeNumeric(str data) -> QrSegment + * - Function makeAlphanumeric(str data) -> QrSegment + * - Constructor QrSegment(Mode mode, int numChars, list bitData) + * - Method getMode() -> Mode + * - Method getNumChars() -> int + * - Method getBits() -> list + * - Enum Mode: + * - Constants NUMERIC, ALPHANUMERIC, BYTE, KANJI + * - Method getModeBits() -> int + * - Method numCharCountBits(int ver) -> int + */ +var qrcodegen = new function() { + + /*---- Public static factory functions for QrCode ----*/ + + /* + * Returns a QR Code symbol representing the given Unicode text string at the given error correction level. + * As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer Unicode + * code points (not UTF-16 code units). The smallest possible QR Code version is automatically chosen for the output. + */ + this.encodeText = function(text, ecl) { + var seg = this.encodeTextToSegment(text); + return this.encodeSegments([seg], ecl); + }; + + + /* + * Returns a single QR segment representing the given Unicode text string. + */ + this.encodeTextToSegment = function(text) { + // Select the most efficient segment encoding automatically + if (QrSegment.NUMERIC_REGEX.test(text)) + return this.QrSegment.makeNumeric(text); + else if (QrSegment.ALPHANUMERIC_REGEX.test(text)) + return this.QrSegment.makeAlphanumeric(text); + else + return this.QrSegment.makeBytes(toUtf8ByteArray(text)); + }; + + + /* + * Returns a QR Code symbol representing the given binary data string at the given error correction level. + * This function always encodes using the binary segment mode, not any text mode. The maximum number of + * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + */ + this.encodeBinary = function(data, ecl) { + var seg = this.QrSegment.makeBytes(data); + return this.encodeSegments([seg], ecl); + }; + + + /* + * Returns a QR Code symbol representing the given data segments at the given error + * correction level. The smallest possible QR Code version is automatically chosen for the output. + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and binary) to encode text more efficiently. This + * function is considered to be lower level than simply encoding text or binary data. + */ + this.encodeSegments = function(segs, ecl) { + // Find the minimal version number to use + var version, dataCapacityBits; + outer: + for (version = 1; ; version++) { // Increment until the data fits in the QR Code + if (version > 40) // All versions could not fit the given data + throw "Data too long"; + dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available + + // Calculate the total number of bits needed at this version number + // to encode all the segments (i.e. segment metadata and payloads) + var dataUsedBits = 0; + for (var i = 0; i < segs.length; i++) { + var seg = segs[i]; + if (seg.numChars < 0) + throw "Assertion error"; + var ccbits = seg.getMode().numCharCountBits(version); + if (seg.getNumChars() >= (1 << ccbits)) { + // Segment length value doesn't fit in the length field's bit-width, so fail immediately + continue outer; + } + dataUsedBits += 4 + ccbits + seg.getBits().length; + } + if (dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + } + + // Create the data bit string by concatenating all segments + var bb = new BitBuffer(); + segs.forEach(function(seg) { + bb.appendBits(seg.getMode().getModeBits(), 4); + bb.appendBits(seg.getNumChars(), seg.getMode().numCharCountBits(version)); + bb.appendData(seg); + }); + + // Add terminator and pad up to a byte if applicable + bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength())); + bb.appendBits(0, (8 - bb.bitLength() % 8) % 8); + + // Pad with alternate bytes until data capacity is reached + for (var padByte = 0xEC; bb.bitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + bb.appendBits(padByte, 8); + if (bb.bitLength() % 8 != 0) + throw "Assertion error"; + + // Create the QR Code symbol + return new this.QrCode(bb.getBytes(), -1, version, ecl); + }; + + + + /* + * A class that represents an immutable square grid of black and white cells for a QR Code symbol, + * with associated static functions to create a QR Code from user-supplied textual or binary data. + * This class covers the QR Code model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and only 3 character encoding modes. + * + * This constructor can be called in one of two ways: + * - new QrCode(bytes, mask, version, errCorLvl): + * Creates a new QR Code symbol with the given version number, error correction level, binary data array, + * and mask number. This cumbersome constructor can be invoked directly by the user, but is considered + * to be even lower level than qrcodegen.encodeSegments(). + * - new QrCode(qr, mask): + * Creates a new QR Code symbol based on the given existing object, but with a potentially different + * mask pattern. The version, error correction level, codewords, etc. of the newly created object are + * all identical to the argument object; only the mask may differ. + * In both cases, mask = -1 is for automatic choice or 0 to 7 for fixed choice. + */ + this.QrCode = function(initData, mask, version, errCorLvl) { + + /*-- Constructor --*/ + + // Handle simple scalar fields + if (mask < -1 || mask > 7) + throw "Mask value out of range"; + var size; + if (initData instanceof Array) { + if (version < 1 || version > 40) + throw "Version value out of range"; + size = version * 4 + 17; + } else if (initData instanceof qrcodegen.QrCode) { + version = initData.getVersion(); + size = initData.getSize(); + errCorLvl = initData.getErrorCorrectionLevel(); + } else + throw "Invalid initial data"; + + // Initialize both grids to be size*size arrays of Boolean false + var row = []; + for (var i = 0; i < size; i++) + row.push(false); + var modules = []; + var isFunction = []; + for (var i = 0; i < size; i++) { + modules.push(row.slice()); + isFunction.push(row.slice()); + } + + // Handle grid fields + if (initData instanceof Array) { + // Draw function patterns, draw all codewords, do masking + drawFunctionPatterns(); + var allCodewords = appendErrorCorrection(initData); + drawCodewords(allCodewords); + } else if (initData instanceof qrcodegen.QrCode) { + for (var y = 0; y < size; y++) { + for (var x = 0; x < size; x++) { + modules[y][x] = initData.getModule(x, y) == 1; + isFunction[y][x] = initData.isFunctionModule(x, y); + } + } + applyMask(initData.getMask()); // Undo old mask + } else + throw "Invalid initial data"; + + // Handle masking + if (mask == -1) { // Automatically choose best mask + var minPenalty = Infinity; + for (var i = 0; i < 8; i++) { + drawFormatBits(i); + applyMask(i); + var penalty = getPenaltyScore(); + if (penalty < minPenalty) { + mask = i; + minPenalty = penalty; + } + applyMask(i); // Undoes the mask due to XOR + } + } + if (mask < 0 || mask > 7) + throw "Assertion error"; + drawFormatBits(mask); // Overwrite old format bits + applyMask(mask); // Apply the final choice of mask + + + /*-- Accessor methods --*/ + + // Returns this QR Code symbol's version number, which is always between 1 and 40 (inclusive). + this.getVersion = function() { + return version; + }; + + // Returns the width and height of this QR Code symbol, measured in modules. + // Always equal to version * 4 + 17, in the range 21 to 177. + this.getSize = function() { + return size; + }; + + // Returns the error correction level used in this QR Code symbol. + this.getErrorCorrectionLevel = function() { + return errCorLvl; + }; + + // Returns the mask pattern used in this QR Code symbol, in the range 0 to 7 (i.e. unsigned 3-bit integer). + // Note that even if a constructor was called with automatic masking requested + // (mask = -1), the resulting object will still have a mask value between 0 and 7. + this.getMask = function() { + return mask; + }; + + // Returns the color of the module (pixel) at the given coordinates, which is either 0 for white or 1 for black. The top + // left corner has the coordinates (x=0, y=0). If the given coordinates are out of bounds, then 0 (white) is returned. + this.getModule = function(x, y) { + if (x < 0 || x >= size || y < 0 || y >= size) + return 0; // Infinite white border + else + return modules[y][x] ? 1 : 0; + }; + + // Tests whether the module at the given coordinates is a function module (true) or not (false). The top left + // corner has the coordinates (x=0, y=0). If the given coordinates are out of bounds, then false is returned. + this.isFunctionModule = function(x, y) { + if (x < 0 || x >= size || y < 0 || y >= size) + return false; // Infinite border + else + return isFunction[y][x]; + }; + + + /*-- Public instance methods --*/ + + // Based on the given number of border modules to add as padding, this returns a + // string whose contents represents an SVG XML file that depicts this QR Code symbol. + this.toSvgString = function(border) { + if (border < 0) + throw "Border must be non-negative"; + var result = "\n"; + result += "\n"; + result += "\n"; + result += "\t\n"; + result += "\n"; + return result; + }; + + + /*-- Private helper methods for constructor: Drawing function modules --*/ + + function drawFunctionPatterns() { + // Draw the horizontal and vertical timing patterns + for (var i = 0; i < size; i++) { + setFunctionModule(6, i, i % 2 == 0); + setFunctionModule(i, 6, i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + drawFinderPattern(3, 3); + drawFinderPattern(size - 4, 3); + drawFinderPattern(3, size - 4); + + // Draw the numerous alignment patterns + var alignPatPos = QrCode.getAlignmentPatternPositions(version); + var numAlign = alignPatPos.length; + for (var i = 0; i < numAlign; i++) { + for (var j = 0; j < numAlign; j++) { + if (i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0) + continue; // Skip the three finder corners + else + drawAlignmentPattern(alignPatPos[i], alignPatPos[j]); + } + } + + // Draw configuration data + drawFormatBits(0); // Dummy mask value; overwritten later in the constructor + drawVersion(); + } + + + // Draws two copies of the format bits (with its own error correction code) + // based on this object's error correction level and mask fields. + function drawFormatBits(mask) { + // Calculate error correction code and pack bits + var data = errCorLvl.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3 + var rem = data; + for (var i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >>> 9) * 0x537); + data = data << 10 | rem; + data ^= 0x5412; // uint15 + if ((data & ((1 << 15) - 1)) != data) + throw "Assertion error"; + + // Draw first copy + for (var i = 0; i <= 5; i++) + setFunctionModule(8, i, ((data >>> i) & 1) != 0); + setFunctionModule(8, 7, ((data >>> 6) & 1) != 0); + setFunctionModule(8, 8, ((data >>> 7) & 1) != 0); + setFunctionModule(7, 8, ((data >>> 8) & 1) != 0); + for (var i = 9; i < 15; i++) + setFunctionModule(14 - i, 8, ((data >>> i) & 1) != 0); + + // Draw second copy + for (var i = 0; i <= 7; i++) + setFunctionModule(size - 1 - i, 8, ((data >>> i) & 1) != 0); + for (var i = 8; i < 15; i++) + setFunctionModule(8, size - 15 + i, ((data >>> i) & 1) != 0); + setFunctionModule(8, size - 8, true); + } + + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field (which only has an effect for 7 <= version <= 40). + function drawVersion() { + if (version < 7) + return; + + // Calculate error correction code and pack bits + var rem = version; // version is uint6, in the range [7, 40] + for (var i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25); + var data = version << 12 | rem; // uint18 + if ((data & ((1 << 18) - 1)) != data) + throw "Assertion error"; + + // Draw two copies + for (var i = 0; i < 18; i++) { + var bit = ((data >>> i) & 1) != 0; + var a = size - 11 + i % 3; + var b = Math.floor(i / 3); + setFunctionModule(a, b, bit); + setFunctionModule(b, a, bit); + } + } + + + // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). + function drawFinderPattern(x, y) { + for (var i = -4; i <= 4; i++) { + for (var j = -4; j <= 4; j++) { + var dist = Math.max(Math.abs(i), Math.abs(j)); // Chebyshev/infinity norm + var xx = x + j; + var yy = y + i; + if (0 <= xx && xx < size && 0 <= yy && yy < size) + setFunctionModule(xx, yy, dist != 2 && dist != 4); + } + } + } + + + // Draws a 5*5 alignment pattern, with the center module at (x, y). + function drawAlignmentPattern(x, y) { + for (var i = -2; i <= 2; i++) { + for (var j = -2; j <= 2; j++) + setFunctionModule(x + j, y + i, Math.max(Math.abs(i), Math.abs(j)) != 1); + } + } + + + // Sets the color of a module and marks it as a function module. + // Only used by the constructor. Coordinates must be in range. + function setFunctionModule(x, y, isBlack) { + modules[y][x] = isBlack; + isFunction[y][x] = true; + } + + + /*---- Private helper methods for constructor: Codewords and masking ----*/ + + // Returns a new byte string representing the given data with the appropriate error correction + // codewords appended to it, based on this object's version and error correction level. + function appendErrorCorrection(data) { + if (data.length != QrCode.getNumDataCodewords(version, errCorLvl)) + throw "Invalid argument"; + var numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[errCorLvl.ordinal][version]; + var numEcc = QrCode.NUM_ERROR_CORRECTION_CODEWORDS[errCorLvl.ordinal][version]; + if (numEcc % numBlocks != 0) + throw "Assertion error"; + var eccLen = Math.floor(numEcc / numBlocks); + var numShortBlocks = numBlocks - Math.floor(QrCode.getNumRawDataModules(version) / 8) % numBlocks; + var shortBlockLen = Math.floor(QrCode.getNumRawDataModules(version) / (numBlocks * 8)); + + var blocks = []; + var rs = new ReedSolomonGenerator(eccLen); + for (var i = 0, k = 0; i < numBlocks; i++) { + var dat = data.slice(k, k + shortBlockLen - eccLen + (i < numShortBlocks ? 0 : 1)); + k += dat.length; + var ecc = rs.getRemainder(dat); + if (i < numShortBlocks) + dat.push(0); + ecc.forEach(function(b) { + dat.push(b); + }); + blocks.push(dat); + } + + var result = []; + for (var i = 0; i < blocks[0].length; i++) { + for (var j = 0; j < blocks.length; j++) { + if (i != shortBlockLen - eccLen || j >= numShortBlocks) + result.push(blocks[j][i]); + } + } + if (result.length != Math.floor(QrCode.getNumRawDataModules(version) / 8)) + throw "Assertion error"; + return result; + } + + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code symbol. Function modules need to be marked off before this is called. + function drawCodewords(data) { + if (data.length != Math.floor(QrCode.getNumRawDataModules(version) / 8)) + throw "Invalid argument"; + var i = 0; // Bit index into the data + // Do the funny zigzag scan + for (var right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (var vert = 0; vert < size; vert++) { // Vertical counter + for (var j = 0; j < 2; j++) { + var x = right - j; // Actual x coordinate + var upwards = ((right & 2) == 0) ^ (x < 6); + var y = upwards ? size - 1 - vert : vert; // Actual y coordinate + if (!isFunction[y][x] && i < data.length * 8) { + modules[y][x] = (data[i >>> 3] >>> (7 - (i & 7)) & 1) != 0; + i++; + } + } + } + } + if (i != data.length * 8) + throw "Assertion error"; + } + + + // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical + // properties, calling applyMask(m) twice with the same value is equivalent to no change at all. + // This means it is possible to apply a mask, undo it, and try another mask. Note that a final + // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). + function applyMask(mask) { + if (mask < 0 || mask > 7) + throw "Mask value out of range"; + for (var y = 0; y < size; y++) { + for (var x = 0; x < size; x++) { + var invert; + switch (mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: throw "Assertion error"; + } + modules[y][x] ^= invert & !isFunction[y][x]; + } + } + } + + + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + function getPenaltyScore() { + var result = 0; + + // Adjacent modules in row having same color + for (var y = 0; y < size; y++) { + var colorX = modules[y][0]; + for (var x = 1, runX = 1; x < size; x++) { + if (modules[y][x] != colorX) { + colorX = modules[y][x]; + runX = 1; + } else { + runX++; + if (runX == 5) + result += QrCode.PENALTY_N1; + else if (runX > 5) + result++; + } + } + } + // Adjacent modules in column having same color + for (var x = 0; x < size; x++) { + var colorY = modules[0][x]; + for (var y = 1, runY = 1; y < size; y++) { + if (modules[y][x] != colorY) { + colorY = modules[y][x]; + runY = 1; + } else { + runY++; + if (runY == 5) + result += QrCode.PENALTY_N1; + else if (runY > 5) + result++; + } + } + } + + // 2*2 blocks of modules having same color + for (var y = 0; y < size - 1; y++) { + for (var x = 0; x < size - 1; x++) { + var color = modules[y][x]; + if ( color == modules[y][x + 1] && + color == modules[y + 1][x] && + color == modules[y + 1][x + 1]) + result += QrCode.PENALTY_N2; + } + } + + // Finder-like pattern in rows + for (var y = 0; y < size; y++) { + for (var x = 0, bits = 0; x < size; x++) { + bits = ((bits << 1) & 0x7FF) | (modules[y][x] ? 1 : 0); + if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated + result += QrCode.PENALTY_N3; + } + } + // Finder-like pattern in columns + for (var x = 0; x < size; x++) { + for (var y = 0, bits = 0; y < size; y++) { + bits = ((bits << 1) & 0x7FF) | (modules[y][x] ? 1 : 0); + if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated + result += QrCode.PENALTY_N3; + } + } + + // Balance of black and white modules + var black = 0; + modules.forEach(function(row) { + row.forEach(function(color) { + if (color) + black++; + }); + }); + var total = size * size; + // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% + for (var k = 0; black*20 < (9-k)*total || black*20 > (11+k)*total; k++) + result += QrCode.PENALTY_N4; + return result; + } + }; + + + /*---- Private static helper functions ----*/ + + var QrCode = {}; // Private object to assign properties to + + // Returns a set of positions of the alignment patterns in ascending order. These positions are + // used on both the x and y axes. Each value in the resulting array is in the range [0, 177). + // This stateless pure function could be implemented as table of 40 variable-length lists of unsigned bytes. + QrCode.getAlignmentPatternPositions = function(ver) { + if (ver < 1 || ver > 40) + throw "Version number out of range"; + else if (ver == 1) + return []; + else { + var size = ver * 4 + 17; + var numAlign = Math.floor(ver / 7) + 2; + var step; + if (ver != 32) + step = Math.ceil((size - 13) / (2 * numAlign - 2)) * 2; + else // C-C-C-Combo breaker! + step = 26; + + var result = []; + for (var i = numAlign - 1, pos = size - 7; i >= 1; i--, pos -= step) + result.push(pos); + result.push(6); + result.reverse(); + return result; + } + }; + + // Returns the number of raw data modules (bits) available at the given version number. + // These data modules are used for both user data codewords and error correction codewords. + // This stateless pure function could be implemented as a 40-entry lookup table. + QrCode.getNumRawDataModules = function(ver) { + if (ver < 1 || ver > 40) + throw "Version number out of range"; + var result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + var numAlign = Math.floor(ver / 7) + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 18 * 2; // Subtract version information + } + return result; + }; + + // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any + // QR Code of the given version number and error correction level, with remainder bits discarded. + // This stateless pure function could be implemented as a (40*4)-cell lookup table. + QrCode.getNumDataCodewords = function(ver, ecl) { + return Math.floor(QrCode.getNumRawDataModules(ver) / 8) - QrCode.NUM_ERROR_CORRECTION_CODEWORDS[ecl.ordinal][ver]; + }; + + + /*---- Tables of constants ----*/ + + // For use in getPenaltyScore(), when evaluating which mask is best. + QrCode.PENALTY_N1 = 3; + QrCode.PENALTY_N2 = 3; + QrCode.PENALTY_N3 = 40; + QrCode.PENALTY_N4 = 10; + + QrCode.NUM_ERROR_CORRECTION_CODEWORDS = [ + // Version: (note that index 0 is for padding, and is set to an illegal value) + // 0, 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, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + [null, 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750], // Low + [null, 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372], // Medium + [null, 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040], // Quartile + [null, 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430], // High + ]; + + QrCode.NUM_ERROR_CORRECTION_BLOCKS = [ + // Version: (note that index 0 is for padding, and is set to an illegal value) + // 0, 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, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + [null, 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], // Low + [null, 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], // Medium + [null, 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], // Quartile + [null, 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], // High + ]; + + + /* + * A public helper enumeration that represents the error correction level used in a QR Code symbol. + * The fields 'ordinal' and 'formatBits' are in the range 0 to 3 (unsigned 2-bit integer). + */ + this.QrCode.Ecc = { + // Constants declared in ascending order of error protection + LOW : {ordinal: 0, formatBits: 1}, + MEDIUM : {ordinal: 1, formatBits: 0}, + QUARTILE: {ordinal: 2, formatBits: 3}, + HIGH : {ordinal: 3, formatBits: 2}, + }; + + + + /* + * A public class that represents a character string to be encoded in a QR Code symbol. + * Each segment has a mode, and a sequence of characters that is already encoded as + * a sequence of bits. Instances of this class are immutable. + * This segment class imposes no length restrictions, but QR Codes have restrictions. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + */ + this.QrSegment = function(mode, numChars, bitData) { + /*-- Accessor methods --*/ + this.getMode = function() { + return mode; + }; + this.getNumChars = function() { + return numChars; + }; + this.getBits = function() { + return bitData.slice(); + }; + }; + + /*-- Static factory functions --*/ + + // Returns a segment representing the given binary data encoded in byte mode. + this.QrSegment.makeBytes = function(data) { + var bb = new BitBuffer(); + data.forEach(function(b) { + bb.appendBits(b, 8); + }); + return new this(this.Mode.BYTE, data.length, bb.getBits()); + }; + + // Returns a segment representing the given string of decimal digits encoded in numeric mode. + this.QrSegment.makeNumeric = function(digits) { + if (!QrSegment.NUMERIC_REGEX.test(digits)) + throw "String contains non-numeric characters"; + var bb = new BitBuffer(); + var i; + for (i = 0; i + 3 <= digits.length; i += 3) // Process groups of 3 + bb.appendBits(parseInt(digits.substr(i, 3), 10), 10); + var rem = digits.length - i; + if (rem > 0) // 1 or 2 digits remaining + bb.appendBits(parseInt(digits.substring(i), 10), rem * 3 + 1); + return new this(this.Mode.NUMERIC, digits.length, bb.getBits()); + }; + + // Returns a segment representing the given text string encoded in alphanumeric mode. The characters allowed are: + // 0 to 9, A to Z (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + this.QrSegment.makeAlphanumeric = function(text) { + if (!QrSegment.ALPHANUMERIC_REGEX.test(text)) + throw "String contains unencodable characters in alphanumeric mode"; + var bb = new BitBuffer(); + var i; + for (i = 0; i + 2 <= text.length; i += 2) { // Process groups of 2 + var temp = QrSegment.ALPHANUMERIC_ENCODING_TABLE[text.charCodeAt(i) - 32] * 45; + temp += QrSegment.ALPHANUMERIC_ENCODING_TABLE[text.charCodeAt(i + 1) - 32]; + bb.appendBits(temp, 11); + } + if (i < text.length) // 1 character remaining + bb.appendBits(QrSegment.ALPHANUMERIC_ENCODING_TABLE[text.charCodeAt(i) - 32], 6); + return new this(this.Mode.ALPHANUMERIC, text.length, bb.getBits()); + }; + + /*-- Constants --*/ + + var QrSegment = {}; // Private object to assign properties to + + // Can test whether a string is encodable in numeric mode (such as by using QrSegment.makeNumeric()). + QrSegment.NUMERIC_REGEX = /^[0-9]*$/; + + // Can test whether a string is encodable in alphanumeric mode (such as by using QrSegment.makeAlphanumeric()). + QrSegment.ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+.\/:-]*$/; + + QrSegment.ALPHANUMERIC_ENCODING_TABLE = [ + // SP, !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?, @, // ASCII codes 32 to 64 + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, -1, // Array indices 0 to 32 + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, // Array indices 33 to 58 + // A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, // ASCII codes 65 to 90 + ]; + + + /* + * A public helper enumeration that represents the mode field of a segment. + * Objects are immutable. Provides methods to retrieve closely related values. + */ + this.QrSegment.Mode = { // Constants + NUMERIC : new Mode(0x1, [10, 12, 14]), + ALPHANUMERIC: new Mode(0x2, [ 9, 11, 13]), + BYTE : new Mode(0x4, [ 8, 16, 16]), + KANJI : new Mode(0x8, [ 8, 10, 12]), + }; + + // Private constructor for the enum. + function Mode(mode, ccbits) { + // Returns an unsigned 4-bit integer value (range 0 to 15) representing the mode indicator bits for this mode object. + this.getModeBits = function() { + return mode; + }; + + // Returns the bit width of the segment character count field for this mode object at the given version number. + this.numCharCountBits = function(ver) { + if ( 1 <= ver && ver <= 9) return ccbits[0]; + else if (10 <= ver && ver <= 26) return ccbits[1]; + else if (27 <= ver && ver <= 40) return ccbits[2]; + else throw "Version number out of range"; + }; + } + + + + /*---- Private helper functions and classes ----*/ + + // Returns a new array of bytes representing the given string encoded in UTF-8. + function toUtf8ByteArray(str) { + var result = []; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + if (c < 0x80) + result.push(c); + else if (c < 0x800) { + result.push(0xC0 | ((c >>> 6) & 0x1F)); + result.push(0x80 | ((c >>> 0) & 0x3F)); + } else if (0xD800 <= c && c < 0xDC00) { // High surrogate + i++; + if (i < str.length) { + var d = str.charCodeAt(i); + if (0xDC00 <= d && d < 0xE000) { // Low surrogate + c = ((c & 0x3FF) << 10 | (d & 0x3FF)) + 0x10000; + result.push(0xF0 | ((c >>> 18) & 0x07)); + result.push(0x80 | ((c >>> 12) & 0x3F)); + result.push(0x80 | ((c >>> 6) & 0x3F)); + result.push(0x80 | ((c >>> 0) & 0x3F)); + continue; + } + } + throw "Invalid UTF-16 string"; + } else if (0xDC00 <= c && c < 0xE000) // Low surrogate + throw "Invalid UTF-16 string"; + else if (c < 0x10000) { + result.push(0xE0 | ((c >>> 12) & 0x0F)); + result.push(0x80 | ((c >>> 6) & 0x3F)); + result.push(0x80 | ((c >>> 0) & 0x3F)); + } else + throw "Assertion error"; + } + return result; + } + + + + /* + * A private helper class that computes the Reed-Solomon error correction codewords for a sequence of + * data codewords at a given degree. Objects are immutable, and the state only depends on the degree. + * This class exists because the divisor polynomial does not need to be recalculated for every input. + * This constructor creates a Reed-Solomon ECC generator for the given degree. This could be implemented + * as a lookup table over all possible parameter values, instead of as an algorithm. + */ + function ReedSolomonGenerator(degree) { + if (degree < 1 || degree > 255) + throw "Degree out of range"; + + // Coefficients of the divisor polynomial, stored from highest to lowest power, excluding the leading term which + // is always 1. For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + var coefficients = []; + + // Start with the monomial x^0 + for (var i = 0; i < degree - 1; i++) + coefficients.push(0); + coefficients.push(1); + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + var root = 1; + for (var i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (var j = 0; j < coefficients.length; j++) { + coefficients[j] = ReedSolomonGenerator.multiply(coefficients[j], root); + if (j + 1 < coefficients.length) + coefficients[j] ^= coefficients[j + 1]; + } + root = (root << 1) ^ ((root >>> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) + } + + // Computes and returns the Reed-Solomon error correction codewords for the given sequence of data codewords. + // The returned object is always a new byte array. This method does not alter this object's state (because it is immutable). + this.getRemainder = function(data) { + // Compute the remainder by performing polynomial division + var result = coefficients.map(function() { return 0; }); + data.forEach(function(b) { + var factor = b ^ result[0]; + result.shift(); + result.push(0); + for (var j = 0; j < result.length; j++) + result[j] ^= ReedSolomonGenerator.multiply(coefficients[j], factor); + }); + return result; + }; + } + + // This static function returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and + // result are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. + ReedSolomonGenerator.multiply = function(x, y) { + if ((x & 0xFF) != x || (y & 0xFF) != y) + throw "Byte out of range"; + // Russian peasant multiplication + var z = 0; + for (var i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >>> 7) * 0x11D); + z ^= ((y >>> i) & 1) * x; + } + if ((z & 0xFF) != z) + throw "Assertion error"; + return z; + }; + + + + /* + * A private helper class that represents an appendable sequence of bits. + * This constructor creates an empty bit buffer (length 0). + */ + function BitBuffer() { + // Array of bits; each item is the integer 0 or 1 + var bitData = []; + + /*-- Methods --*/ + + // Returns the number of bits in the buffer, which is a non-negative value. + this.bitLength = function() { + return bitData.length; + }; + + // Returns a copy of all bits. + this.getBits = function() { + return bitData.slice(); + }; + + // Returns a copy of all bytes, padding up to the nearest byte. + this.getBytes = function() { + var result = []; + var numBytes = Math.ceil(bitData.length / 8); + for (var i = 0; i < numBytes; i++) + result.push(0); + bitData.forEach(function(bit, i) { + result[i >>> 3] |= bit << (7 - (i & 7)); + }); + return result; + }; + + // Appends the given number of bits of the given value to this sequence. + // If 0 <= len <= 31, then this requires 0 <= val < 2^len. + this.appendBits = function(val, len) { + if (len < 0 || len > 32 || len < 32 && (val & ((1 << len) - 1)) != val) + throw "Value out of range"; + for (var i = len - 1; i >= 0; i--) // Append bit by bit + bitData.push((val >>> i) & 1); + }; + + // Appends the bit data of the given segment to this bit buffer. + this.appendData = function(seg) { + seg.getBits().forEach(function(b) { // Append bit by bit + bitData.push(b); + }); + }; + } + +};