diff --git a/java/src/main/java/io/nayuki/qrcodegen/AlphanumericMode.java b/java/src/main/java/io/nayuki/qrcodegen/AlphanumericMode.java new file mode 100644 index 0000000..f0d59ba --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/AlphanumericMode.java @@ -0,0 +1,25 @@ +package io.nayuki.qrcodegen; + +import java.nio.charset.StandardCharsets; + +public class AlphanumericMode extends QrMode { + protected AlphanumericMode(int mode, int... ccbits) { + modeBits = mode; + numBitsCharCount = ccbits; + } + + protected AlphanumericMode() { + modeBits = 0x2; + numBitsCharCount[0] = 9; + numBitsCharCount[1] = 11; + numBitsCharCount[2] = 13; + } + + public int getcost(int pre, int codePoint) { + return pre + 33; + } + + public QrSegment making(String str) { + return QrSegment.makeAlphanumeric(str); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/BitBuffer.java b/java/src/main/java/io/nayuki/qrcodegen/BitBuffer.java index 624d129..4ff3e42 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/BitBuffer.java +++ b/java/src/main/java/io/nayuki/qrcodegen/BitBuffer.java @@ -125,5 +125,29 @@ public final class BitBuffer implements Cloneable { throw new AssertionError(e); } } + + + // Pad with alternating bytes until data capacity is reached + public void addPad(int dataCapacityBits) { + for (int padByte = 0xEC; bitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + appendBits(padByte, 8); + } + + + // Pack bits into bytes in big endian + public byte[] toCodewords() { + byte[] dataCodewords = new byte[bitLength() / 8]; + for (int i = 0; i < bitLength(); i++) + dataCodewords[i >>> 3] |= getBit(i) << (7 - (i & 7)); + return dataCodewords; + } + + + // Add terminator and pad up to a byte if applicable + public void addTerminator(int dataCapacityBits) { + appendBits(0, Math.min(4, dataCapacityBits - bitLength())); + appendBits(0, (8 - bitLength() % 8) % 8); + assert bitLength() % 8 == 0; + } -} +} \ No newline at end of file diff --git a/java/src/main/java/io/nayuki/qrcodegen/Button.java b/java/src/main/java/io/nayuki/qrcodegen/Button.java new file mode 100644 index 0000000..66fca4e --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Button.java @@ -0,0 +1,17 @@ +package io.nayuki.qrcodegen; + +public class Button { + public Command theCommand; + + public Button(Command theCommand) { + setCommand(theCommand); + } + + public void setCommand(Command newCommand) { + this.theCommand = newCommand; + } + + public boolean pressed(int y, int x, int msk) { + return theCommand.excute(y, x, msk); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/ByteMode.java b/java/src/main/java/io/nayuki/qrcodegen/ByteMode.java new file mode 100644 index 0000000..1270381 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/ByteMode.java @@ -0,0 +1,25 @@ +package io.nayuki.qrcodegen; + +import java.nio.charset.StandardCharsets; + +public class ByteMode extends QrMode { + protected ByteMode(int mode, int... ccbits) { + modeBits = mode; + numBitsCharCount = ccbits; + } + + protected ByteMode() { + modeBits = 0x4; + numBitsCharCount[0] = 8; + numBitsCharCount[1] = 16; + numBitsCharCount[2] = 16; + } + + public int getcost(int pre, int codePoint) { + return pre + QrSegmentAdvanced.countUtf8Bytes(codePoint) * 8 * 6; + } + + public QrSegment making(String str) { + return QrSegment.makeBytes(str.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Command.java b/java/src/main/java/io/nayuki/qrcodegen/Command.java new file mode 100644 index 0000000..b12845e --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Command.java @@ -0,0 +1,5 @@ +package io.nayuki.qrcodegen; + +public interface Command { + public abstract boolean excute(int y, int x, int msk); +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Ecc.java b/java/src/main/java/io/nayuki/qrcodegen/Ecc.java new file mode 100644 index 0000000..1ce9752 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Ecc.java @@ -0,0 +1,61 @@ +package io.nayuki.qrcodegen; + +/** + * The error correction level in a QR Code symbol. + */ +public class Ecc { + /** The QR Code can tolerate about 7% erroneous codewords. */ + public static final Ecc LOW = new Ecc(1, + new byte[] { -1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, + 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, + new byte[] { -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 }); + /** The QR Code can tolerate about 15% erroneous codewords. */ + public static final Ecc MEDIUM = new Ecc(0, + new byte[] { -1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 }, + new byte[] { -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 }); + /** The QR Code can tolerate about 25% erroneous codewords. */ + public static final Ecc QUARTILE = new Ecc(3, + new byte[] { -1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, + 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, + new byte[] { -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 }); + /** The QR Code can tolerate about 30% erroneous codewords. */ + public static final Ecc HIGH = new Ecc(2, + new byte[] { -1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, + new byte[] { -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 }); + + private final int formatBits; + private final byte[] blockLength; + private final byte[] numberOfBlocks; + + // For formatBits and blockLength, array index is Version: (note that index 0 is + // for padding, and is set to an illegal value) + private Ecc(int formatBits, byte[] blockLength, byte[] numberOfBlocks) { + this.formatBits = formatBits; + this.blockLength = blockLength; + this.numberOfBlocks = numberOfBlocks; + } + + public int getFormatBits() { + return formatBits; + } + + public byte getBlockLength(int version) { + return blockLength[version]; + } + + public byte getNumberOfBlock(int version) { + return numberOfBlocks[version]; + } + + // Must in ascending order of error protection + // so that values() work properly + public static Ecc[] values() { + return new Ecc[] { LOW, MEDIUM, QUARTILE, HIGH }; + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/EciMode.java b/java/src/main/java/io/nayuki/qrcodegen/EciMode.java new file mode 100644 index 0000000..ce33fa9 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/EciMode.java @@ -0,0 +1,23 @@ +package io.nayuki.qrcodegen; + +public class EciMode extends QrMode { + protected EciMode(int mode, int... ccbits) { + modeBits = mode; + numBitsCharCount = ccbits; + } + + protected EciMode() { + modeBits = 0x7; + numBitsCharCount[0] = 0; + numBitsCharCount[1] = 0; + numBitsCharCount[2] = 0; + } + + public QrSegment making(int prmt) { + return QrSegment.makeEci(prmt); + } + + public int getcost(int pre, int codePoint) { + return pre; + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/KanjiMode.java b/java/src/main/java/io/nayuki/qrcodegen/KanjiMode.java new file mode 100644 index 0000000..9729f93 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/KanjiMode.java @@ -0,0 +1,23 @@ +package io.nayuki.qrcodegen; + +public class KanjiMode extends QrMode { + protected KanjiMode(int mode, int... ccbits) { + modeBits = mode; + numBitsCharCount = ccbits; + } + + protected KanjiMode() { + modeBits = 0x8; + numBitsCharCount[0] = 8; + numBitsCharCount[1] = 10; + numBitsCharCount[2] = 12; + } + + public int getcost(int pre, int codePoint) { + return pre + 78; + } + + public QrSegment making(String str) { + return QrSegmentAdvanced.makeKanji(str); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Msk0.java b/java/src/main/java/io/nayuki/qrcodegen/Msk0.java new file mode 100644 index 0000000..a125577 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Msk0.java @@ -0,0 +1,7 @@ +package io.nayuki.qrcodegen; + +public class Msk0 { + public boolean operation(int y, int x, int msk) { + return ((x + y) % 2 == 0); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Msk1.java b/java/src/main/java/io/nayuki/qrcodegen/Msk1.java new file mode 100644 index 0000000..f9f92e3 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Msk1.java @@ -0,0 +1,7 @@ +package io.nayuki.qrcodegen; + +public class Msk1 { + public boolean operation(int y, int x, int msk) { + return (y % 2 == 0); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Msk2.java b/java/src/main/java/io/nayuki/qrcodegen/Msk2.java new file mode 100644 index 0000000..42c145e --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Msk2.java @@ -0,0 +1,7 @@ +package io.nayuki.qrcodegen; + +public class Msk2 { + public boolean operation(int y, int x, int msk) { + return (x % 3 == 0); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Msk3.java b/java/src/main/java/io/nayuki/qrcodegen/Msk3.java new file mode 100644 index 0000000..45064c0 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Msk3.java @@ -0,0 +1,7 @@ +package io.nayuki.qrcodegen; + +public class Msk3 { + public boolean operation(int y, int x, int msk) { + return ((x + y) % 3 == 0); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Msk4.java b/java/src/main/java/io/nayuki/qrcodegen/Msk4.java new file mode 100644 index 0000000..d0b13a1 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Msk4.java @@ -0,0 +1,7 @@ +package io.nayuki.qrcodegen; + +public class Msk4 { + public boolean operation(int y, int x, int msk) { + return ((x / 3 + y / 2) % 2 == 0); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Msk5.java b/java/src/main/java/io/nayuki/qrcodegen/Msk5.java new file mode 100644 index 0000000..decc5df --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Msk5.java @@ -0,0 +1,7 @@ +package io.nayuki.qrcodegen; + +public class Msk5 { + public boolean operation(int y, int x, int msk) { + return (x * y % 2 + x * y % 3 == 0); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Msk6.java b/java/src/main/java/io/nayuki/qrcodegen/Msk6.java new file mode 100644 index 0000000..555fb64 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Msk6.java @@ -0,0 +1,7 @@ +package io.nayuki.qrcodegen; + +public class Msk6 { + public boolean operation(int y, int x, int msk) { + return ((x * y % 2 + x * y % 3) % 2 == 0); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/Msk7.java b/java/src/main/java/io/nayuki/qrcodegen/Msk7.java new file mode 100644 index 0000000..7d98190 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/Msk7.java @@ -0,0 +1,7 @@ +package io.nayuki.qrcodegen; + +public class Msk7 { + public boolean operation(int y, int x, int msk) { + return (((x + y) % 2 + x * y % 3) % 2 == 0); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/MskCommandFactory.java b/java/src/main/java/io/nayuki/qrcodegen/MskCommandFactory.java new file mode 100644 index 0000000..93bc177 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/MskCommandFactory.java @@ -0,0 +1,46 @@ +package io.nayuki.qrcodegen; + +public class MskCommandFactory { + public static Command getCommand(int msk) { + Command theCommand = null; + + Msk0 msk0 = new Msk0(); + Msk1 msk1 = new Msk1(); + Msk2 msk2 = new Msk2(); + Msk3 msk3 = new Msk3(); + Msk4 msk4 = new Msk4(); + Msk5 msk5 = new Msk5(); + Msk6 msk6 = new Msk6(); + Msk7 msk7 = new Msk7(); + + switch (msk) { + case 0: + theCommand = new msk0Command(msk0); + break; + case 1: + theCommand = new msk1Command(msk1); + break; + case 2: + theCommand = new msk2Command(msk2); + break; + case 3: + theCommand = new msk3Command(msk3); + break; + case 4: + theCommand = new msk4Command(msk4); + break; + case 5: + theCommand = new msk5Command(msk5); + break; + case 6: + theCommand = new msk6Command(msk6); + break; + case 7: + theCommand = new msk7Command(msk7); + break; + default: + throw new AssertionError(); + } + return theCommand; + } +} \ No newline at end of file diff --git a/java/src/main/java/io/nayuki/qrcodegen/NumericMode.java b/java/src/main/java/io/nayuki/qrcodegen/NumericMode.java new file mode 100644 index 0000000..a8af039 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/NumericMode.java @@ -0,0 +1,23 @@ +package io.nayuki.qrcodegen; + +public class NumericMode extends QrMode { + protected NumericMode(int mode, int... ccbits) { + modeBits = mode; + numBitsCharCount = ccbits; + } + + protected NumericMode() { + modeBits = 0x1; + numBitsCharCount[0] = 10; + numBitsCharCount[1] = 12; + numBitsCharCount[2] = 1; + } + + public int getcost(int pre, int codePoint) { + return pre + 20; + } + + public QrSegment making(String str) { + return QrSegment.makeNumeric(str); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrCode.java b/java/src/main/java/io/nayuki/qrcodegen/QrCode.java index ea0196c..5ade16c 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCode.java @@ -52,6 +52,12 @@ import java.util.Objects; */ public final class QrCode { + private static final int FINDER_SIZE = 3; + + private static final int TIMING_COORDINATE = 6; + + + /*---- Static factory functions (high level) ----*/ /** @@ -128,8 +134,8 @@ public final class QrCode { * between modes (such as alphanumeric and byte) to encode text in less space. * This is a mid-level API; the high-level API is {@link #encodeText(String,Ecc)} * and {@link #encodeBinary(byte[],Ecc)}.

- * @param segs the segments to encode - * @param ecl the error correction level to use (not {@code null}) (boostable) + * @param segments the segments to encode + * @param errorCorrectionLevel the error correction level to use (not {@code null}) (boostable) * @param minVersion the minimum allowed version of the QR Code (at least 1) * @param maxVersion the maximum allowed version of the QR Code (at most 40) * @param mask the mask number to use (between 0 and 7 (inclusive)), or −1 for automatic mask @@ -141,64 +147,87 @@ public final class QrCode { * @throws DataTooLongException if the segments fail to fit in * the maxVersion QR Code at the ECL, which means they are too long */ - public static QrCode encodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) { - Objects.requireNonNull(segs); - Objects.requireNonNull(ecl); - if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) + public static QrCode encodeSegments(List segments, Ecc errorCorrectionLevel, int minVersion, int maxVersion, int mask, boolean boostEcl) { + Objects.requireNonNull(segments); + Objects.requireNonNull(errorCorrectionLevel); + final boolean isVersionInRange = MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION; + final boolean isMaskOutOfRange = mask < -1 || mask > 7; + if (!isVersionInRange || isMaskOutOfRange) throw new IllegalArgumentException("Invalid value"); - // Find the minimal version number to use - int version, dataUsedBits; - for (version = minVersion; ; version++) { - int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available - dataUsedBits = QrSegment.getTotalBits(segs, version); - if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) - break; // This version number is found to be suitable - if (version >= maxVersion) { // All versions in the range could not fit the given data - String msg = "Segment too long"; - if (dataUsedBits != -1) - msg = String.format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits); - throw new DataTooLongException(msg); - } - } - assert dataUsedBits != -1; - // Increase the error correction level while the data still fits in the current version number - for (Ecc newEcl : Ecc.values()) { // From low to high - if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) - ecl = newEcl; - } + int version = findMinimalVersion(segments, errorCorrectionLevel, minVersion, maxVersion); - // Concatenate all segments to create the data bit string - 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.data); - } - assert bb.bitLength() == dataUsedBits; + int dataUsedBits = QrSegment.getTotalBits(segments, version); + + errorCorrectionLevel = findMaximalErrorCorrectionLevel(errorCorrectionLevel, boostEcl, version, dataUsedBits); + + + BitBuffer bitBuffer = segmentsToBitBuffer(segments, version); + assert bitBuffer.bitLength() == dataUsedBits; + + + int dataCapacityBits = getNumDataCodewords(version, errorCorrectionLevel) * 8; + assert bitBuffer.bitLength() <= dataCapacityBits; + bitBuffer.addTerminator(dataCapacityBits); - // Add terminator and pad up to a byte if applicable - int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; - assert bb.bitLength() <= dataCapacityBits; - bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength())); - bb.appendBits(0, (8 - bb.bitLength() % 8) % 8); - assert bb.bitLength() % 8 == 0; - // Pad with alternating bytes until data capacity is reached - for (int padByte = 0xEC; bb.bitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) - bb.appendBits(padByte, 8); + bitBuffer.addPad(dataCapacityBits); - // Pack bits into bytes in big endian - byte[] dataCodewords = new byte[bb.bitLength() / 8]; - for (int i = 0; i < bb.bitLength(); i++) - dataCodewords[i >>> 3] |= bb.getBit(i) << (7 - (i & 7)); + + byte[] dataCodewords = bitBuffer.toCodewords(); // Create the QR Code object - return new QrCode(version, ecl, dataCodewords, mask); + return new QrCode(version, errorCorrectionLevel, dataCodewords, mask); } + + /*---- Private helper methods for encodeSegments ----*/ + + // Concatenate all segments to create the data bit string + private static BitBuffer segmentsToBitBuffer(List segments, int version) { + BitBuffer bitBuffer = new BitBuffer(); + for (QrSegment segment : segments) { + bitBuffer.appendBits(segment.mode.modeBits, 4); + bitBuffer.appendBits(segment.numChars, segment.mode.numCharCountBits(version)); + bitBuffer.appendData(segment.data); + } + return bitBuffer; + } + + + // Increase the error correction level while the data still fits in the current version number + private static Ecc findMaximalErrorCorrectionLevel(Ecc errorCorrectionLevel, boolean boostEcl, int version, + int dataUsedBits) { + for (Ecc newEcl : Ecc.values()) { // From low to high + final boolean canIncreaseErrorCorrectionLevel = dataUsedBits <= getNumDataCodewords(version, newEcl) * 8; + if (boostEcl && canIncreaseErrorCorrectionLevel) + errorCorrectionLevel = newEcl; + } + return errorCorrectionLevel; + } + + + //Returns the minimal version number to use + private static int findMinimalVersion(List segments, Ecc errorCorrectionLevel, int minVersion, + int maxVersion) { + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, errorCorrectionLevel) * 8; // Number of data bits available + dataUsedBits = QrSegment.getTotalBits(segments, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + String message = "Segment too long"; + if (dataUsedBits != -1) + message = String.format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits); + throw new DataTooLongException(message); + } + } + assert dataUsedBits != -1; + return version; + } /*---- Instance fields ----*/ @@ -238,32 +267,32 @@ public final class QrCode { * error correction level, data codeword bytes, and mask number. *

This is a low-level API that most users should not use directly. A mid-level * API is the {@link #encodeSegments(List,Ecc,int,int,int,boolean)} function.

- * @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 version the version number to use, which must be in the range 1 to 40 (inclusive) + * @param errorCorrectionLevel the error correction level to use * @param dataCodewords the bytes representing segments to encode (without ECC) - * @param msk the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice + * @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, * or if the data is the wrong length for the specified version and error correction level */ - public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int msk) { + public QrCode(int version, Ecc errorCorrectionLevel, byte[] dataCodewords, int mask) { // Check arguments and initialize fields - if (ver < MIN_VERSION || ver > MAX_VERSION) + if (version < MIN_VERSION || version > MAX_VERSION) throw new IllegalArgumentException("Version value out of range"); - if (msk < -1 || msk > 7) + if (mask < -1 || mask > 7) throw new IllegalArgumentException("Mask value out of range"); - version = ver; - size = ver * 4 + 17; - errorCorrectionLevel = Objects.requireNonNull(ecl); + this.version = version; + this.size = version * 4 + 17; + this.errorCorrectionLevel = Objects.requireNonNull(errorCorrectionLevel); Objects.requireNonNull(dataCodewords); - modules = new boolean[size][size]; // Initially all white - isFunction = new boolean[size][size]; + this.modules = new boolean[size][size]; // Initially all white + this.isFunction = new boolean[size][size]; // Compute ECC, draw modules, do masking drawFunctionPatterns(); byte[] allCodewords = addEccAndInterleave(dataCodewords); drawCodewords(allCodewords); - this.mask = handleConstructorMasking(msk); + this.mask = handleConstructorMasking(mask); isFunction = null; } @@ -354,29 +383,37 @@ public final class QrCode { private void drawFunctionPatterns() { // Draw horizontal and vertical timing patterns for (int i = 0; i < size; i++) { - setFunctionModule(6, i, i % 2 == 0); - setFunctionModule(i, 6, i % 2 == 0); + setFunctionModule(TIMING_COORDINATE, i, i % 2 == 0); + setFunctionModule(i, TIMING_COORDINATE, 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); + drawFinderPattern(FINDER_SIZE, FINDER_SIZE); + drawFinderPattern(size - 1 - FINDER_SIZE, FINDER_SIZE); + drawFinderPattern(FINDER_SIZE, size - 1 - FINDER_SIZE); - // Draw numerous alignment patterns + drawAlignmentsPatterns(); + + // Draw configuration data + drawFormatBits(0); // Dummy mask value; overwritten later in the constructor + drawVersion(); + } + + + // Draw numerous alignment patterns + private void drawAlignmentsPatterns() { int[] alignPatPos = getAlignmentPatternPositions(); int numAlign = alignPatPos.length; for (int i = 0; i < numAlign; i++) { for (int j = 0; j < numAlign; j++) { - // Don't draw on the three finder corners - if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)) + final boolean isLeftTop = i == 0 && j == 0; + final boolean isLeftBottom = i == 0 && j == numAlign - 1; + final boolean isRightTop = i == numAlign - 1 && j == 0; + final boolean onThreeFinderCorners = isLeftTop || isLeftBottom || isRightTop; + if (!onThreeFinderCorners) drawAlignmentPattern(alignPatPos[i], alignPatPos[j]); } } - - // Draw configuration data - drawFormatBits(0); // Dummy mask value; overwritten later in the constructor - drawVersion(); } @@ -384,7 +421,7 @@ public final class QrCode { // based on the given mask and this object's error correction level field. private void drawFormatBits(int msk) { // Calculate error correction code and pack bits - int data = errorCorrectionLevel.formatBits << 3 | msk; // errCorrLvl is uint2, mask is uint3 + int data = errorCorrectionLevel.getFormatBits() << 3 | msk; // errCorrLvl is uint2, mask is uint3 int rem = data; for (int i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537); @@ -475,8 +512,8 @@ public final class QrCode { throw new IllegalArgumentException(); // Calculate parameter numbers - int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[errorCorrectionLevel.ordinal()][version]; - int blockEccLen = ECC_CODEWORDS_PER_BLOCK [errorCorrectionLevel.ordinal()][version]; + int numBlocks = errorCorrectionLevel.getNumberOfBlock(version); + int blockEccLen = errorCorrectionLevel.getBlockLength(version); int rawCodewords = getNumRawDataModules(version) / 8; int numShortBlocks = numBlocks - rawCodewords % numBlocks; int shortBlockLen = rawCodewords / numBlocks; @@ -543,23 +580,20 @@ public final class QrCode { // before masking. Due to the arithmetic of XOR, calling applyMask() with // the same mask value a second time will undo the mask. A final well-formed // QR Code needs exactly one (not zero, two, etc.) mask applied. + private void applyMask(int msk) { + + if (msk < 0 || msk > 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 (msk) { - 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(); - } + + Command mskCommand = MskCommandFactory.getCommand(msk); + Button button = new Button(mskCommand); + invert = button.pressed(y, x, msk); + modules[y][x] ^= invert & !isFunction[y][x]; } } @@ -569,51 +603,69 @@ public final class QrCode { // A messy helper function for the constructor. 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 msk) { - if (msk == -1) { // Automatically choose best mask - int minPenalty = Integer.MAX_VALUE; - for (int i = 0; i < 8; i++) { - applyMask(i); - drawFormatBits(i); - int penalty = getPenaltyScore(); - if (penalty < minPenalty) { - msk = i; - minPenalty = penalty; - } - applyMask(i); // Undoes the mask due to XOR + private int handleConstructorMasking(int mask) { + if (mask == -1) { + mask = findBestMask(); + } + assert 0 <= mask && mask <= 7; + applyMask(mask); // Apply the final choice of mask + drawFormatBits(mask); // Overwrite old format bits + return mask; // The caller shall assign this value to the final-declared field + } + + + // Automatically choose best mask + private int findBestMask() { + int mask = -1; + int minPenalty = Integer.MAX_VALUE; + for (int i = 0; i < 8; i++) { + applyMask(i); + drawFormatBits(i); + int penalty = getPenaltyScore(); + if (penalty < minPenalty) { + mask = i; + minPenalty = penalty; } + applyMask(i); // Undoes the mask due to XOR } - assert 0 <= msk && msk <= 7; - applyMask(msk); // Apply the final choice of mask - drawFormatBits(msk); // Overwrite old format bits - return msk; // The caller shall assign this value to the final-declared field + return mask; } + private int havingSameColor(int run, boolean runColor, int[] runHistory, int result, int y, int x) { + if (modules[y][x] == runColor) { + run++; + if (run == 5) + result += PENALTY_N1; + else if (run > 5) + result++; + } else { + finderPenaltyAddHistory(run, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = modules[y][x]; + run = 1; + } + + return result; + } + // 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; + + + int result = 0; + // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < size; y++) { boolean runColor = false; int runX = 0; int[] runHistory = new int[7]; for (int x = 0; x < size; x++) { - if (modules[y][x] == runColor) { - runX++; - if (runX == 5) - result += PENALTY_N1; - else if (runX > 5) - result++; - } else { - finderPenaltyAddHistory(runX, runHistory); - if (!runColor) - result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; - runColor = modules[y][x]; - runX = 1; - } + result += havingSameColor(runX, runColor, runHistory, result, y, x); } result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; } @@ -623,34 +675,14 @@ public final class QrCode { int runY = 0; int[] runHistory = new int[7]; for (int y = 0; y < size; y++) { - if (modules[y][x] == runColor) { - runY++; - if (runY == 5) - result += PENALTY_N1; - else if (runY > 5) - result++; - } else { - finderPenaltyAddHistory(runY, runHistory); - if (!runColor) - result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; - runColor = modules[y][x]; - runY = 1; - } + result += havingSameColor(runY, runColor, runHistory, result, y, x); } result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; } - - // 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; - } - } - + + result += twobytwoHavingSameColor(modules); + + // Balance of black and white modules int black = 0; for (boolean[] row : modules) { @@ -665,11 +697,24 @@ public final class QrCode { result += k * PENALTY_N4; return result; } - - - + + private int twobytwoHavingSameColor(boolean[][] modules) { + int result = 0; + // 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; + } + } + return result; + } + /*---- Private helper functions ----*/ - + // Returns an ascending list of positions of alignment patterns for this version number. // Each position is in the range [0,177), and are used on both the x and y axes. // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. @@ -690,15 +735,22 @@ public final class QrCode { return result; } } - - + + // Returns the number of data bits that can be stored in a QR Code of the given version number, after // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. - private static int getNumRawDataModules(int ver) { + private static int getNumRawDataModules(int ver) { if (ver < MIN_VERSION || ver > MAX_VERSION) throw new IllegalArgumentException("Version number out of range"); - + + int result = calculateNumOfModules(ver); + + assert 208 <= result && result <= 29648; + return result; + } + + private static int calculateNumOfModules(int ver) { int size = ver * 4 + 17; int result = size * size; // Number of modules in the whole QR Code square result -= 8 * 8 * 3; // Subtract the three finders with separators @@ -713,11 +765,9 @@ public final class QrCode { if (ver >= 7) result -= 6 * 3 * 2; // Subtract version information } - assert 208 <= result && result <= 29648; return result; } - - + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be // implemented as a lookup table over all possible parameter values, instead of as an algorithm. private static byte[] reedSolomonComputeDivisor(int degree) { @@ -727,7 +777,7 @@ public final class QrCode { // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. byte[] result = new byte[degree]; result[degree - 1] = 1; // Start off with the monomial x^0 - + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), // and drop the highest monomial term which is always 1x^degree. // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). @@ -743,8 +793,8 @@ public final class QrCode { } return result; } - - + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. private static byte[] reedSolomonComputeRemainder(byte[] data, byte[] divisor) { Objects.requireNonNull(data); @@ -759,33 +809,33 @@ public final class QrCode { } return result; } - - + + // 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 reedSolomonMultiply(int x, int y) { assert x >> 8 == 0 && y >> 8 == 0; - // Russian peasant multiplication - int z = 0; - for (int i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >>> 7) * 0x11D); - z ^= ((y >>> i) & 1) * x; - } - assert z >>> 8 == 0; - return z; + // Russian peasant multiplication + int z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >>> 7) * 0x11D); + z ^= ((y >>> i) & 1) * x; + } + assert z >>> 8 == 0; + return z; } - - + + // 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. static int getNumDataCodewords(int ver, Ecc ecl) { - return getNumRawDataModules(ver) / 8 - - ECC_CODEWORDS_PER_BLOCK [ecl.ordinal()][ver] - * NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal()][ver]; + return getNumRawDataModules(ver) / 8 + - ecl.getBlockLength(ver) + * ecl.getNumberOfBlock(ver); } - - + + // Can only be called immediately after a white run is added, and // returns either 0, 1, or 2. A helper function for getPenaltyScore(). private int finderPenaltyCountPatterns(int[] runHistory) { @@ -793,10 +843,10 @@ public final class QrCode { assert n <= size * 3; boolean core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) - + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); } - - + + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). private int finderPenaltyTerminateAndCount(boolean currentRunColor, int currentRunLength, int[] runHistory) { if (currentRunColor) { // Terminate black run @@ -807,8 +857,8 @@ public final class QrCode { finderPenaltyAddHistory(currentRunLength, runHistory); return finderPenaltyCountPatterns(runHistory); } - - + + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). private void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) { if (runHistory[0] == 0) @@ -816,70 +866,27 @@ public final class QrCode { System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1); runHistory[0] = currentRunLength; } - - + + // Returns true iff the i'th bit of x is set to 1. static boolean getBit(int x, int i) { return ((x >>> i) & 1) != 0; } - - + + /*---- Constants and tables ----*/ - + /** The minimum version number (1) supported in the QR Code Model 2 standard. */ public static final int MIN_VERSION = 1; - + /** The maximum version number (40) supported in the QR Code Model 2 standard. */ public static final int MAX_VERSION = 40; - - + + // 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 byte[][] ECC_CODEWORDS_PER_BLOCK = { - // 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, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low - {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium - {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile - {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // 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 ----*/ - - /** - * The error correction level in a QR Code symbol. - */ - public enum Ecc { - // Must be declared in ascending order of error protection - // so that the implicit ordinal() and values() work properly - /** The QR Code can tolerate about 7% erroneous codewords. */ LOW(1), - /** The QR Code can tolerate about 15% erroneous codewords. */ MEDIUM(0), - /** The QR Code can tolerate about 25% erroneous codewords. */ QUARTILE(3), - /** The QR Code can tolerate about 30% erroneous codewords. */ HIGH(2); - - // In the range 0 to 3 (unsigned 2-bit integer). - final int formatBits; - - // Constructor. - private Ecc(int fb) { - formatBits = fb; - } - } - -} + +} \ No newline at end of file diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java b/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java index 828eae6..0e25584 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java @@ -73,6 +73,7 @@ public final class QrCodeGeneratorDemo { QrCode qrCode; // Numeric mode encoding (3.33 bits per digit) + qrCode = QrCode.encodeText("314159265358979323846264338327950288419716939937510", Ecc.MEDIUM); writePng(qrCode.toImage(13, 1), "pi-digits-QR.png"); @@ -83,6 +84,7 @@ public final class QrCodeGeneratorDemo { // Unicode text as UTF-8 qrCode = QrCode.encodeText("占쎄괭占쎄뎐占쎄쾽占쎄굶wa占쎄낌�닟占쎈르塋딉옙 �뀭汝뷸Ь�걣", Ecc.QUARTILE); writePng(qrCode.toImage(10, 3), "unicode-QR.png"); + // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) qrCode = QrCode.encodeText( @@ -93,7 +95,9 @@ public final class QrCodeGeneratorDemo { + "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.", Ecc.HIGH); + writePng(qrCode.toImage(6, 10), "alice-wonderland-QR.png"); + } @@ -105,26 +109,33 @@ public final class QrCodeGeneratorDemo { // Illustration "silver" String silver0 = "THE SQUARE ROOT OF 2 IS 1."; String silver1 = "41421356237309504880168872420969807856967187537694807317667973799"; + qrCode = QrCode.encodeText(silver0 + silver1, Ecc.LOW); writePng(qrCode.toImage(10, 3), "sqrt2-monolithic-QR.png"); + segments = Arrays.asList( QrSegment.makeAlphanumeric(silver0), QrSegment.makeNumeric(silver1)); + qrCode = QrCode.encodeSegments(segments, Ecc.LOW); writePng(qrCode.toImage(10, 3), "sqrt2-segmented-QR.png"); + // Illustration "golden" String golden0 = "Golden ratio 占쏙옙 = 1."; String golden1 = "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374"; String golden2 = "......"; + qrCode = QrCode.encodeText(golden0 + golden1 + golden2, Ecc.LOW); writePng(qrCode.toImage(8, 5), "phi-monolithic-QR.png"); + segments = Arrays.asList( QrSegment.makeBytes(golden0.getBytes(StandardCharsets.UTF_8)), QrSegment.makeNumeric(golden1), QrSegment.makeAlphanumeric(golden2)); + qrCode = QrCode.encodeSegments(segments, Ecc.LOW); writePng(qrCode.toImage(8, 5), "phi-segmented-QR.png"); @@ -136,6 +147,8 @@ public final class QrCodeGeneratorDemo { segments = Arrays.asList(QrSegmentAdvanced.makeKanji(madoka)); qrCode = QrCode.encodeSegments(segments, Ecc.LOW); writePng(qrCode.toImage(9, 4), "madoka-kanji-QR.png"); + + } @@ -145,6 +158,7 @@ public final class QrCodeGeneratorDemo { List segments; // Project Nayuki URL + segments = QrSegment.makeSegments("https://www.nayuki.io/"); qrCode = QrCode.encodeSegments(segments, Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, -1, true); // Automatic mask writePng(qrCode.toImage(8, 6), "project-nayuki-automask-QR.png"); @@ -161,6 +175,7 @@ public final class QrCodeGeneratorDemo { writePng(qrCode.toImage(10, 3), "unicode-mask5-QR.png"); qrCode = QrCode.encodeSegments(segments, Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 7, true); // Force mask 7 writePng(qrCode.toImage(10, 3), "unicode-mask7-QR.png"); + } diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorWorker.java b/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorWorker.java index 12d77e7..1cb9f44 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorWorker.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorWorker.java @@ -83,6 +83,7 @@ public final class QrCodeGeneratorWorker { try { // Try to make QR Code symbol QrCode qr = QrCode.encodeSegments(segments, Ecc.values()[errCorLvl], minVersion, maxVersion, mask, boostEcl != 0); + // Print grid of modules System.out.println(qr.version); for (int y = 0; y < qr.size; y++) { diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrMode.java b/java/src/main/java/io/nayuki/qrcodegen/QrMode.java new file mode 100644 index 0000000..3f0127f --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/QrMode.java @@ -0,0 +1,43 @@ +package io.nayuki.qrcodegen; + +public abstract class QrMode { + /*-- Fields --*/ + + // The mode indicator bits, which is a uint4 value (range 0 to 15). + int modeBits; + + // Number of character count bits for three different version ranges. + protected int[] numBitsCharCount; + + int headCost; + /*-- Method --*/ + + // Returns the bit width of the character count field for a segment in this mode + // in a QR Code at the given version number. The result is in the range [0, 16]. + int numCharCountBits(int ver) { + assert QrCode.MIN_VERSION <= ver && ver <= QrCode.MAX_VERSION; + return numBitsCharCount[(ver + 7) / 17]; + } + + protected QrMode() { + this.modeBits = 0; + this.numBitsCharCount = null; + } + + public void get(QrMode md) { + this.modeBits = md.modeBits; + this.numBitsCharCount = md.numBitsCharCount; + } + + public int whichMode() { + return modeBits; + } + + protected QrSegment making(String s) { + return null; + } + + public int getcost(int pre, int codePoint) { + return pre; + } +} \ No newline at end of file diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java b/java/src/main/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java index 1efe7b5..50066e8 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java @@ -23,12 +23,20 @@ package io.nayuki.qrcodegen; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.Objects; +import java.util.Scanner; + import io.nayuki.qrcodegen.QrSegment.Mode; @@ -60,18 +68,18 @@ public final class QrSegmentAdvanced { * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 is violated * @throws DataTooLongException if the text fails to fit in the maxVersion QR Code at the ECL */ - public static List makeSegmentsOptimally(String text, QrCode.Ecc ecl, int minVersion, int maxVersion) { + public static List makeSegmentsOptimally(String text, Ecc ecl, int minVersion, int maxVersion) { // Check arguments Objects.requireNonNull(text); Objects.requireNonNull(ecl); - if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION)) + if (not_Valid_Version(minVersion, maxVersion)) throw new IllegalArgumentException("Invalid value"); // Iterate through version numbers, and make tentative segments List segs = null; int[] codePoints = toCodePoints(text); for (int version = minVersion; ; version++) { - if (version == minVersion || version == 10 || version == 27) + if (is_valid_version(minVersion, version)) segs = makeSegmentsOptimally(codePoints, version); assert segs != null; @@ -88,22 +96,31 @@ public final class QrSegmentAdvanced { } } } + + + private static boolean is_valid_version(int minVersion, int version) { + return version == minVersion || version == 10 || version == 27; + } + + + private static boolean not_Valid_Version(int minVersion, int maxVersion) { + return !(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION); + } // Returns a new list of segments that is optimal for the given text at the given version number. private static List makeSegmentsOptimally(int[] codePoints, int version) { if (codePoints.length == 0) return new ArrayList<>(); - Mode[] charModes = computeCharacterModes(codePoints, version); + QrMode[] charModes = computeCharacterModes(codePoints, version); return splitIntoSegments(codePoints, charModes); } - // Returns a new array representing the optimal mode per code point based on the given text and version. - private static Mode[] computeCharacterModes(int[] codePoints, int version) { + private static QrMode[] computeCharacterModes(int[] codePoints, int version) { if (codePoints.length == 0) throw new IllegalArgumentException(); - final Mode[] modeTypes = {Mode.BYTE, Mode.ALPHANUMERIC, Mode.NUMERIC, Mode.KANJI}; // Do not modify + final QrMode[] modeTypes = {new ByteMode(), new AlphanumericMode(), new NumericMode(), new KanjiMode()}; // Do not modify final int numModes = modeTypes.length; // Segment header sizes, measured in 1/6 bits @@ -114,7 +131,7 @@ public final class QrSegmentAdvanced { // charModes[i][j] represents the mode to encode the code point at // index i such that the final segment ends in modeTypes[j] and the // total number of bits is minimized over all possible choices - Mode[][] charModes = new Mode[codePoints.length][numModes]; + QrMode[][] charModes = new QrMode[codePoints.length][numModes]; // At the beginning of each iteration of the loop below, // prevCosts[j] is the exact minimum number of 1/6 bits needed to @@ -122,27 +139,42 @@ public final class QrSegmentAdvanced { int[] prevCosts = headCosts.clone(); // Calculate costs using dynamic programming + prevCosts = calculate_cost(codePoints, modeTypes, numModes, headCosts, charModes, prevCosts); + + // Find optimal ending mode + QrMode curMode = null; + curMode = find_optimal(modeTypes, numModes, prevCosts, curMode); + + // Get optimal mode for each code point by tracing backwards + QrMode[] result = new QrMode[codePoints.length]; + get_optimal(modeTypes, numModes, charModes, curMode, result); + return result; + } + + + private static int[] calculate_cost(int[] codePoints, final QrMode[] modeTypes, final int numModes, + final int[] headCosts, QrMode[][] charModes, int[] prevCosts) { for (int i = 0; i < codePoints.length; i++) { - int c = codePoints[i]; + int codePoint = codePoints[i]; int[] curCosts = new int[numModes]; { // Always extend a byte mode segment - curCosts[0] = prevCosts[0] + countUtf8Bytes(c) * 8 * 6; - charModes[i][0] = modeTypes[0]; + charModes[i][0] = new ByteMode(); + curCosts[0] = charModes[i][0].getcost(prevCosts[0], codePoint); } // Extend a segment if possible - if (QrSegment.ALPHANUMERIC_CHARSET.indexOf(c) != -1) { // Is alphanumeric - curCosts[1] = prevCosts[1] + 33; // 5.5 bits per alphanumeric char - charModes[i][1] = modeTypes[1]; + if (QrSegment.ALPHANUMERIC_CHARSET.indexOf(codePoint) != -1) { // Is alphanumeric + charModes[i][1] = new AlphanumericMode(); + curCosts[1] = charModes[i][1].getcost(prevCosts[1], codePoint); // 5.5 bits per alphanumeric char } - if ('0' <= c && c <= '9') { // Is numeric - curCosts[2] = prevCosts[2] + 20; // 3.33 bits per digit - charModes[i][2] = modeTypes[2]; + if ('0' <= codePoint && codePoint <= '9') { // Is numeric + charModes[i][2] = new NumericMode(); + curCosts[2] = charModes[i][2].getcost(prevCosts[2], codePoint); // 3.33 bits per digit } - if (isKanji(c)) { - curCosts[3] = prevCosts[3] + 78; // 13 bits per Shift JIS char - charModes[i][3] = modeTypes[3]; + if (isKanji(codePoint)) { + charModes[i][3] = new KanjiMode(); + curCosts[3] = charModes[i][3].getcost(prevCosts[3], codePoint);; // 13 bits per Shift JIS char } - + // Start new segment at the end to switch modes for (int j = 0; j < numModes; j++) { // To mode for (int k = 0; k < numModes; k++) { // From mode @@ -156,18 +188,12 @@ public final class QrSegmentAdvanced { prevCosts = curCosts; } - - // Find optimal ending mode - Mode curMode = null; - for (int i = 0, minCost = 0; i < numModes; i++) { - if (curMode == null || prevCosts[i] < minCost) { - minCost = prevCosts[i]; - curMode = modeTypes[i]; - } - } - - // Get optimal mode for each code point by tracing backwards - Mode[] result = new Mode[charModes.length]; + return prevCosts; + } + + + private static void get_optimal(final QrMode[] modeTypes, final int numModes, QrMode[][] charModes, QrMode curMode, + QrMode[] result) { for (int i = result.length - 1; i >= 0; i--) { for (int j = 0; j < numModes; j++) { if (modeTypes[j] == curMode) { @@ -177,34 +203,45 @@ public final class QrSegmentAdvanced { } } } - return result; + } + + + private static QrMode find_optimal(final QrMode[] modeTypes, final int numModes, int[] prevCosts, QrMode curMode) { + for (int i = 0, minCost = 0; i < numModes; i++) { + if (curMode == null || prevCosts[i] < minCost) { + minCost = prevCosts[i]; + curMode = modeTypes[i]; + } + } + return curMode; + } + + + private static boolean is_numeric(int convertedPoint) { + return '0' <= convertedPoint && convertedPoint <= '9'; + } + + + private static boolean is_alphanumeric(int convertedPoint) { + return QrSegment.ALPHANUMERIC_CHARSET.indexOf(convertedPoint) != -1; } // Returns a new list of segments based on the given text and modes, such that // consecutive code points in the same mode are put into the same segment. - private static List splitIntoSegments(int[] codePoints, Mode[] charModes) { + private static List splitIntoSegments(int[] codePoints, QrMode[] charModes) { if (codePoints.length == 0) throw new IllegalArgumentException(); List result = new ArrayList<>(); // Accumulate run of modes - Mode curMode = charModes[0]; + QrMode curMode = charModes[0]; int start = 0; for (int i = 1; ; i++) { if (i < codePoints.length && charModes[i] == curMode) continue; String s = new String(codePoints, start, i - start); - if (curMode == Mode.BYTE) - result.add(QrSegment.makeBytes(s.getBytes(StandardCharsets.UTF_8))); - else if (curMode == Mode.NUMERIC) - result.add(QrSegment.makeNumeric(s)); - else if (curMode == Mode.ALPHANUMERIC) - result.add(QrSegment.makeAlphanumeric(s)); - else if (curMode == Mode.KANJI) - result.add(makeKanji(s)); - else - throw new AssertionError(); + result.add(curMode.making(s)); if (i >= codePoints.length) return result; curMode = charModes[i]; @@ -215,8 +252,8 @@ public final class QrSegmentAdvanced { // Returns a new array of Unicode code points (effectively // UTF-32 / UCS-4) representing the given UTF-16 string. - private static int[] toCodePoints(String s) { - int[] result = s.codePoints().toArray(); + private static int[] toCodePoints(String str) { + int[] result = str.codePoints().toArray(); for (int c : result) { if (Character.isSurrogate((char)c)) throw new IllegalArgumentException("Invalid UTF-16 string"); @@ -226,12 +263,12 @@ public final class QrSegmentAdvanced { // Returns the number of UTF-8 bytes needed to encode the given Unicode code point. - private static int countUtf8Bytes(int cp) { - if (cp < 0) throw new IllegalArgumentException("Invalid code point"); - else if (cp < 0x80) return 1; - else if (cp < 0x800) return 2; - else if (cp < 0x10000) return 3; - else if (cp < 0x110000) return 4; + public static int countUtf8Bytes(int currentPoint) { + if (currentPoint < 0) throw new IllegalArgumentException("Invalid code point"); + else if (currentPoint < 0x80) return 1; + else if (currentPoint < 0x800) return 2; + else if (currentPoint < 0x10000) return 3; + else if (currentPoint < 0x110000) return 4; else throw new IllegalArgumentException("Invalid code point"); } @@ -277,8 +314,7 @@ public final class QrSegmentAdvanced { */ public static boolean isEncodableAsKanji(String text) { Objects.requireNonNull(text); - return text.chars().allMatch( - c -> isKanji((char)c)); + return text.chars().allMatch(c -> isKanji((char)c)); } @@ -286,119 +322,8 @@ public final class QrSegmentAdvanced { return c < UNICODE_TO_QR_KANJI.length && UNICODE_TO_QR_KANJI[c] != -1; } - - // Data derived from ftp://ftp.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/SHIFTJIS.TXT - private static final String PACKED_QR_KANJI_TO_UNICODE = - "MAAwATAC/wz/DjD7/xr/G/8f/wEwmzCcALT/QACo/z7/4/8/MP0w/jCdMJ4wA07dMAUwBjAHMPwgFSAQ/w8AXDAcIBb/XCAmICUgGCAZIBwgHf8I/wkwFDAV/zv/Pf9b/10wCDAJMAowCzAMMA0wDjAPMBAwEf8LIhIAsQDX//8A9/8dImD/HP8eImYiZyIeIjQmQiZA" + - "ALAgMiAzIQP/5f8EAKIAo/8F/wP/Bv8K/yAApyYGJgUlyyXPJc4lxyXGJaEloCWzJbIlvSW8IDswEiGSIZAhkSGTMBP/////////////////////////////IggiCyKGIocigiKDIioiKf////////////////////8iJyIoAKwh0iHUIgAiA///////////////////" + - "//////////8iICKlIxIiAiIHImEiUiJqImsiGiI9Ih0iNSIrIiz//////////////////yErIDAmbyZtJmogICAhALb//////////yXv/////////////////////////////////////////////////xD/Ef8S/xP/FP8V/xb/F/8Y/xn///////////////////8h" + - "/yL/I/8k/yX/Jv8n/yj/Kf8q/yv/LP8t/y7/L/8w/zH/Mv8z/zT/Nf82/zf/OP85/zr///////////////////9B/0L/Q/9E/0X/Rv9H/0j/Sf9K/0v/TP9N/07/T/9Q/1H/Uv9T/1T/Vf9W/1f/WP9Z/1r//////////zBBMEIwQzBEMEUwRjBHMEgwSTBKMEswTDBN" + - "ME4wTzBQMFEwUjBTMFQwVTBWMFcwWDBZMFowWzBcMF0wXjBfMGAwYTBiMGMwZDBlMGYwZzBoMGkwajBrMGwwbTBuMG8wcDBxMHIwczB0MHUwdjB3MHgweTB6MHswfDB9MH4wfzCAMIEwgjCDMIQwhTCGMIcwiDCJMIowizCMMI0wjjCPMJAwkTCSMJP/////////////" + - "////////////////////////MKEwojCjMKQwpTCmMKcwqDCpMKowqzCsMK0wrjCvMLAwsTCyMLMwtDC1MLYwtzC4MLkwujC7MLwwvTC+ML8wwDDBMMIwwzDEMMUwxjDHMMgwyTDKMMswzDDNMM4wzzDQMNEw0jDTMNQw1TDWMNcw2DDZMNow2zDcMN0w3jDf//8w4DDh" + - "MOIw4zDkMOUw5jDnMOgw6TDqMOsw7DDtMO4w7zDwMPEw8jDzMPQw9TD2/////////////////////wORA5IDkwOUA5UDlgOXA5gDmQOaA5sDnAOdA54DnwOgA6EDowOkA6UDpgOnA6gDqf////////////////////8DsQOyA7MDtAO1A7YDtwO4A7kDugO7A7wDvQO+" + - "A78DwAPBA8MDxAPFA8YDxwPIA8n/////////////////////////////////////////////////////////////////////////////////////////////////////////////BBAEEQQSBBMEFAQVBAEEFgQXBBgEGQQaBBsEHAQdBB4EHwQgBCEEIgQjBCQEJQQm" + - "BCcEKAQpBCoEKwQsBC0ELgQv////////////////////////////////////////BDAEMQQyBDMENAQ1BFEENgQ3BDgEOQQ6BDsEPAQ9//8EPgQ/BEAEQQRCBEMERARFBEYERwRIBEkESgRLBEwETQROBE///////////////////////////////////yUAJQIlDCUQ" + - "JRglFCUcJSwlJCU0JTwlASUDJQ8lEyUbJRclIyUzJSslOyVLJSAlLyUoJTclPyUdJTAlJSU4JUL/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "/////////////////////////////////////06cVRZaA5Y/VMBhG2MoWfaQIoR1gxx6UGCqY+FuJWXthGaCppv1aJNXJ2WhYnFbm1nQhnuY9H1ifb6bjmIWfJ+It1uJXrVjCWaXaEiVx5eNZ09O5U8KT01PnVBJVvJZN1nUWgFcCWDfYQ9hcGYTaQVwunVPdXB5+32t" + - "fe+Aw4QOiGOLApBVkHpTO06VTqVX34CykMF4704AWPFuopA4ejKDKIKLnC9RQVNwVL1U4VbgWftfFZjybeuA5IUt////////lmKWcJagl/tUC1PzW4dwz3+9j8KW6FNvnVx6uk4ReJOB/G4mVhhVBGsdhRqcO1nlU6ltZnTclY9WQk6RkEuW8oNPmQxT4VW2WzBfcWYg" + - "ZvNoBGw4bPNtKXRbdsh6Tpg0gvGIW4pgku1tsnWrdsqZxWCmiwGNipWyaY5TrVGG//9XElgwWURbtF72YChjqWP0bL9vFHCOcRRxWXHVcz9+AYJ2gtGFl5BgkludG1hpZbxsWnUlUflZLlllX4Bf3GK8ZfpqKmsna7Rzi3/BiVadLJ0OnsRcoWyWg3tRBFxLYbaBxmh2" + - "cmFOWU/6U3hgaW4pek+X804LUxZO7k9VTz1PoU9zUqBT71YJWQ9awVu2W+F50WaHZ5xntmtMbLNwa3PCeY15vno8e4eCsYLbgwSDd4Pvg9OHZoqyVimMqI/mkE6XHoaKT8Rc6GIRcll1O4Hlgr2G/ozAlsWZE5nVTstPGonjVt5YSljKXvtf62AqYJRgYmHQYhJi0GU5" + - "////////m0FmZmiwbXdwcHVMdoZ9dYKlh/mVi5aOjJ1R8VK+WRZUs1uzXRZhaGmCba94jYTLiFeKcpOnmrhtbJmohtlXo2f/hs6SDlKDVodUBF7TYuFkuWg8aDhru3NyeLp6a4maidKNa48DkO2Vo5aUl2lbZlyzaX2YTZhOY5t7IGor//9qf2i2nA1vX1JyVZ1gcGLs" + - "bTtuB27RhFuJEI9EThScOVP2aRtqOpeEaCpRXHrDhLKR3JOMVludKGgigwWEMXylUgiCxXTmTn5Pg1GgW9JSClLYUudd+1WaWCpZ5luMW5hb215yXnlgo2EfYWNhvmPbZWJn0WhTaPprPmtTbFdvIm+Xb0V0sHUYduN3C3r/e6F8IX3pfzZ/8ICdgmaDnomzisyMq5CE" + - "lFGVk5WRlaKWZZfTmSiCGE44VCtcuF3Mc6l2THc8XKl/640LlsGYEZhUmFhPAU8OU3FVnFZoV/pZR1sJW8RckF4MXn5fzGPuZzpl12XiZx9oy2jE////////al9eMGvFbBdsfXV/eUhbY3oAfQBfvYmPihiMtI13jsyPHZjimg6bPE6AUH1RAFmTW5xiL2KAZOxrOnKg" + - "dZF5R3+ph/uKvItwY6yDypegVAlUA1WraFRqWIpweCdndZ7NU3RbooEahlCQBk4YTkVOx08RU8pUOFuuXxNgJWVR//9nPWxCbHJs43B4dAN6dnquewh9Gnz+fWZl53JbU7tcRV3oYtJi4GMZbiCGWooxjd2S+G8BeaabWk6oTqtOrE+bT6BQ0VFHevZRcVH2U1RTIVN/" + - "U+tVrFiDXOFfN19KYC9gUGBtYx9lWWpLbMFywnLtd++A+IEFggiFTpD3k+GX/5lXmlpO8FHdXC1mgWltXEBm8ml1c4loUHyBUMVS5FdHXf6TJmWkayNrPXQ0eYF5vXtLfcqCuYPMiH+JX4s5j9GR0VQfkoBOXVA2U+VTOnLXc5Z36YLmjq+ZxpnImdJRd2Eahl5VsHp6" + - "UHZb05BHloVOMmrbkedcUVxI////////Y5h6n2yTl3SPYXqqcYqWiHyCaBd+cGhRk2xS8lQbhauKE3+kjs2Q4VNmiIh5QU/CUL5SEVFEVVNXLXPqV4tZUV9iX4RgdWF2YWdhqWOyZDplbGZvaEJuE3Vmej18+31MfZl+S39rgw6DSobNigiKY4tmjv2YGp2PgriPzpvo" + - "//9Sh2IfZINvwJaZaEFQkWsgbHpvVHp0fVCIQIojZwhO9lA5UCZQZVF8UjhSY1WnVw9YBVrMXvphsmH4YvNjcmkcailyfXKscy54FHhvfXl3DICpiYuLGYzijtKQY5N1lnqYVZoTnnhRQ1OfU7Nee18mbhtukHOEc/59Q4I3igCK+pZQTk5QC1PkVHxW+lnRW2Rd8V6r" + - "XydiOGVFZ69uVnLQfMqItIChgOGD8IZOioeN6JI3lseYZ58TTpROkk8NU0hUSVQ+Wi9fjF+hYJ9op2qOdFp4gYqeiqSLd5GQTl6byU6kT3xPr1AZUBZRSVFsUp9SuVL+U5pT41QR////////VA5ViVdRV6JZfVtUW11bj13lXedd9154XoNeml63XxhgUmFMYpdi2GOn" + - "ZTtmAmZDZvRnbWghaJdpy2xfbSptaW4vbp11MnaHeGx6P3zgfQV9GH1efbGAFYADgK+AsYFUgY+CKoNSiEyIYYsbjKKM/JDKkXWScXg/kvyVpJZN//+YBZmZmtidO1JbUqtT91QIWNVi92/gjGqPX565UUtSO1RKVv16QJF3nWCe0nNEbwmBcHURX/1g2pqoctuPvGtk" + - "mANOylbwV2RYvlpaYGhhx2YPZgZoOWixbfd11X06gm6bQk6bT1BTyVUGXW9d5l3uZ/tsmXRzeAKKUJOWiN9XUF6nYytQtVCsUY1nAFTJWF5Zu1uwX2liTWOhaD1rc24IcH2Rx3KAeBV4JnltZY59MIPciMGPCZabUmRXKGdQf2qMoVG0V0KWKlg6aYqAtFSyXQ5X/HiV" + - "nfpPXFJKVItkPmYoZxRn9XqEe1Z9IpMvaFybrXs5UxlRilI3////////W99i9mSuZOZnLWu6hamW0XaQm9ZjTJMGm6t2v2ZSTglQmFPCXHFg6GSSZWNoX3Hmc8p1I3uXfoKGlYuDjNuReJkQZaxmq2uLTtVO1E86T39SOlP4U/JV41bbWOtZy1nJWf9bUFxNXgJeK1/X" + - "YB1jB2UvW1xlr2W9ZehnnWti//9re2wPc0V5SXnBfPh9GX0rgKKBAoHziZaKXoppimaKjIrujMeM3JbMmPxrb06LTzxPjVFQW1db+mFIYwFmQmshbstsu3I+dL111HjBeTqADIAzgeqElI+ebFCef18Pi1idK3r6jvhbjZbrTgNT8Vf3WTFayVukYIluf28Gdb6M6luf" + - "hQB74FByZ/SCnVxhhUp+HoIOUZlcBGNojWZlnHFueT59F4AFix2OypBuhseQqlAfUvpcOmdTcHxyNZFMkciTK4LlW8JfMWD5TjtT1luIYktnMWuKculz4HougWuNo5FSmZZRElPXVGpb/2OIajl9rJcAVtpTzlRo////////W5dcMV3eT+5hAWL+bTJ5wHnLfUJ+TX/S" + - "ge2CH4SQiEaJcouQjnSPL5AxkUuRbJbGkZxOwE9PUUVTQV+TYg5n1GxBbgtzY34mkc2Sg1PUWRlbv23ReV1+LnybWH5xn1H6iFOP8E/KXPtmJXeseuOCHJn/UcZfqmXsaW9riW3z//9ulm9kdv59FF3hkHWRh5gGUeZSHWJAZpFm2W4aXrZ90n9yZviFr4X3ivhSqVPZ" + - "WXNej1+QYFWS5JZkULdRH1LdUyBTR1PsVOhVRlUxVhdZaFm+WjxbtVwGXA9cEVwaXoReil7gX3Bif2KEYttjjGN3ZgdmDGYtZnZnfmiiah9qNWy8bYhuCW5YcTxxJnFndcd3AXhdeQF5ZXnweuB7EXynfTmAloPWhIuFSYhdiPOKH4o8ilSKc4xhjN6RpJJmk36UGJac" + - "l5hOCk4ITh5OV1GXUnBXzlg0WMxbIl44YMVk/mdhZ1ZtRHK2dXN6Y4S4i3KRuJMgVjFX9Jj+////////Yu1pDWuWce1+VIB3gnKJ5pjfh1WPsVw7TzhP4U+1VQdaIFvdW+lfw2FOYy9lsGZLaO5pm214bfF1M3W5dx95XnnmfTOB44KvhaqJqoo6jquPm5Aykd2XB066" + - "TsFSA1h1WOxcC3UaXD2BTooKj8WWY5dteyWKz5gIkWJW81Oo//+QF1Q5V4JeJWOobDRwindhfIt/4IhwkEKRVJMQkxiWj3RemsRdB11pZXBnoo2olttjbmdJaRmDxZgXlsCI/m+EZHpb+E4WcCx1XWYvUcRSNlLiWdNfgWAnYhBlP2V0Zh9mdGjyaBZrY24FcnJ1H3bb" + - "fL6AVljwiP2Jf4qgipOKy5AdkZKXUpdZZYl6DoEGlrteLWDcYhplpWYUZ5B383pNfE1+PoEKjKyNZI3hjl94qVIHYtljpWRCYpiKLXqDe8CKrJbqfXaCDIdJTtlRSFNDU2Bbo1wCXBZd3WImYkdksGgTaDRsyW1FbRdn029ccU5xfWXLen97rX3a////////fkp/qIF6" + - "ghuCOYWmim6Mzo31kHiQd5KtkpGVg5uuUk1VhG84cTZRaHmFflWBs3zOVkxYUVyoY6pm/mb9aVpy2XWPdY55DnlWed98l30gfUSGB4o0ljuQYZ8gUOdSdVPMU+JQCVWqWO5ZT3I9W4tcZFMdYONg82NcY4NjP2O7//9kzWXpZvld42nNaf1vFXHlTol16Xb4epN8333P" + - "fZyAYYNJg1iEbIS8hfuIxY1wkAGQbZOXlxyaElDPWJdhjoHThTWNCJAgT8NQdFJHU3Ngb2NJZ19uLI2zkB9P11xejMplz32aU1KIllF2Y8NbWFtrXApkDWdRkFxO1lkaWSpscIpRVT5YFVmlYPBiU2fBgjVpVZZAmcSaKE9TWAZb/oAQXLFeL1+FYCBhS2I0Zv9s8G7e" + - "gM6Bf4LUiIuMuJAAkC6Wip7bm9tO41PwWSd7LJGNmEyd+W7dcCdTU1VEW4ViWGKeYtNsom/vdCKKF5Q4b8GK/oM4UeeG+FPq////////U+lPRpBUj7BZaoExXf166o+/aNqMN3L4nEhqPYqwTjlTWFYGV2ZixWOiZeZrTm3hbltwrXfteu97qn27gD2AxobLipWTW1bj" + - "WMdfPmWtZpZqgGu1dTeKx1Akd+VXMF8bYGVmemxgdfR6Gn9ugfSHGJBFmbN7yXVcevl7UYTE//+QEHnpepKDNlrhd0BOLU7yW5lf4GK9Zjxn8WzohmuId4o7kU6S85nQahdwJnMqgueEV4yvTgFRRlHLVYtb9V4WXjNegV8UXzVfa1+0YfJjEWaiZx1vbnJSdTp3OoB0" + - "gTmBeId2ir+K3I2FjfOSmpV3mAKc5VLFY1d29GcVbIhzzYzDk66Wc20lWJxpDmnMj/2TmnXbkBpYWmgCY7Rp+09Dbyxn2I+7hSZ9tJNUaT9vcFdqWPdbLH0scipUCpHjnbROrU9OUFxQdVJDjJ5USFgkW5peHV6VXq1e918fYIxitWM6Y9Bor2xAeId5jnoLfeCCR4oC" + - "iuaORJAT////////kLiRLZHYnw5s5WRYZOJldW70doR7G5Bpk9FuulTyX7lkpI9Nj+2SRFF4WGtZKVxVXpdt+36PdRyMvI7imFtwuU8da79vsXUwlvtRTlQQWDVYV1msXGBfkmWXZ1xuIXZ7g9+M7ZAUkP2TTXgleDpSql6mVx9ZdGASUBJRWlGs//9RzVIAVRBYVFhY" + - "WVdblVz2XYtgvGKVZC1ncWhDaLxo33bXbdhub22bcG9xyF9Tddh5d3tJe1R7UnzWfXFSMIRjhWmF5IoOiwSMRo4PkAOQD5QZlnaYLZowldhQzVLVVAxYAlwOYadknm0ed7N65YD0hASQU5KFXOCdB1M/X5dfs22ccnl3Y3m/e+Rr0nLsiq1oA2phUfh6gWk0XEqc9oLr" + - "W8WRSXAeVnhcb2DHZWZsjIxakEGYE1RRZseSDVlIkKNRhU5NUeqFmYsOcFhjepNLaWKZtH4EdXdTV2lgjt+W42xdToxcPF8Qj+lTAozRgImGeV7/ZeVOc1Fl////////WYJcP5fuTvtZil/Nio1v4XmweWJb54RxcytxsV50X/Vje2SaccN8mE5DXvxOS1fcVqJgqW/D" + - "fQ2A/YEzgb+PsomXhqRd9GKKZK2Jh2d3bOJtPnQ2eDRaRn91gq2ZrE/zXsNi3WOSZVdnb3bDckyAzIC6jymRTVANV/lakmiF//9pc3Fkcv2Mt1jyjOCWapAZh3955HfnhClPL1JlU1pizWfPbMp2fXuUfJWCNoWEj+tm3W8gcgZ+G4OrmcGeplH9e7F4cnu4gId7SGro" + - "XmGAjHVRdWBRa5Jibox2epGXmupPEH9wYpx7T5WlnOlWelhZhuSWvE80UiRTSlPNU9teBmQsZZFnf2w+bE5ySHKvc+11VH5BgiyF6Yype8SRxnFpmBKY72M9Zml1anbkeNCFQ4buUypTUVQmWYNeh198YLJiSWJ5YqtlkGvUbMx1snaueJF52H3Lf3eApYirirmMu5B/" + - "l16Y22oLfDhQmVw+X65nh2vYdDV3CX+O////////nztnynoXUzl1i5rtX2aBnYPxgJhfPF/FdWJ7RpA8aGdZ61qbfRB2fossT/VfamoZbDdvAnTieWiIaIpVjHle32PPdcV50oLXkyiS8oSchu2cLVTBX2xljG1ccBWMp4zTmDtlT3T2Tg1O2FfgWStaZlvMUaheA16c" + - "YBZidmV3//9lp2ZubW5yNnsmgVCBmoKZi1yMoIzmjXSWHJZET65kq2tmgh6EYYVqkOhcAWlTmKiEeoVXTw9Sb1+pXkVnDXmPgXmJB4mGbfVfF2JVbLhOz3Jpm5JSBlQ7VnRYs2GkYm5xGllufIl83n0blvBlh4BeThlPdVF1WEBeY15zXwpnxE4mhT2ViZZbfHOYAVD7" + - "WMF2VninUiV3pYURe4ZQT1kJckd7x33oj7qP1JBNT79SyVopXwGXrU/dgheS6lcDY1VraXUriNyPFHpCUt9Yk2FVYgpmrmvNfD+D6VAjT/hTBVRGWDFZSVudXPBc710pXpZisWNnZT5luWcL////////bNVs4XD5eDJ+K4DegrOEDITshwKJEooqjEqQppLSmP2c851s" + - "Tk9OoVCNUlZXSlmoXj1f2F/ZYj9mtGcbZ9Bo0lGSfSGAqoGoiwCMjIy/kn6WMlQgmCxTF1DVU1xYqGSyZzRyZ3dmekaR5lLDbKFrhlgAXkxZVGcsf/tR4XbG//9kaXjom1Seu1fLWblmJ2eaa85U6WnZXlWBnGeVm6pn/pxSaF1Opk/jU8hiuWcrbKuPxE+tfm2ev04H" + - "YWJugG8rhRNUc2cqm0Vd83uVXKxbxoccbkqE0XoUgQhZmXyNbBF3IFLZWSJxIXJfd9uXJ51haQtaf1oYUaVUDVR9Zg5234/3kpic9Fnqcl1uxVFNaMl9v33sl2KeumR4aiGDAlmEW19r23MbdvJ9soAXhJlRMmcontl27mdiUv+ZBVwkYjt8foywVU9gtn0LlYBTAU5f" + - "UbZZHHI6gDaRzl8ld+JThF95fQSFrIozjo2XVmfzha6UU2EJYQhsuXZS////////iu2POFUvT1FRKlLHU8tbpV59YKBhgmPWZwln2m5nbYxzNnM3dTF5UIjVipiQSpCRkPWWxIeNWRVOiE9ZTg6KiY8/mBBQrV58WZZbuV64Y9pj+mTBZtxpSmnYbQtutnGUdSh6r3+K" + - "gACESYTJiYGLIY4KkGWWfZkKYX5ikWsy//9sg210f8x//G3Af4WHuoj4Z2WDsZg8lvdtG31hhD2Rak5xU3VdUGsEb+uFzYYtiadSKVQPXGVnTmiodAZ0g3XiiM+I4ZHMluKWeF+Lc4d6y4ROY6B1ZVKJbUFunHQJdVl4a3ySloZ63J+NT7ZhbmXFhlxOhk6uUNpOIVHM" + - "W+5lmWiBbbxzH3ZCd616HHzngm+K0pB8kc+WdZgYUpt90VArU5hnl23LcdB0M4HojyqWo5xXnp90YFhBbZl9L5heTuRPNk+LUbdSsV26YBxzsnk8gtOSNJa3lvaXCp6Xn2Jmpmt0UhdSo3DIiMJeyWBLYZBvI3FJfD599IBv////////hO6QI5MsVEKbb2rTcImMwo3v" + - "lzJStFpBXspfBGcXaXxplG1qbw9yYnL8e+2AAYB+h0uQzlFtnpN5hICLkzKK1lAtVIyKcWtqjMSBB2DRZ6Cd8k6ZTpicEIprhcGFaGkAbn54l4FV////////////////////////////////////////////////////////////////////////////////////////" + - "/////////////////////////////18MThBOFU4qTjFONk48Tj9OQk5WTlhOgk6FjGtOioISXw1Ojk6eTp9OoE6iTrBOs062Ts5OzU7ETsZOwk7XTt5O7U7fTvdPCU9aTzBPW09dT1dPR092T4hPj0+YT3tPaU9wT5FPb0+GT5ZRGE/UT99Pzk/YT9tP0U/aT9BP5E/l" + - "UBpQKFAUUCpQJVAFTxxP9lAhUClQLE/+T+9QEVAGUENQR2cDUFVQUFBIUFpQVlBsUHhQgFCaUIVQtFCy////////UMlQylCzUMJQ1lDeUOVQ7VDjUO5Q+VD1UQlRAVECURZRFVEUURpRIVE6UTdRPFE7UT9RQFFSUUxRVFFievhRaVFqUW5RgFGCVthRjFGJUY9RkVGT" + - "UZVRllGkUaZRolGpUapRq1GzUbFRslGwUbVRvVHFUclR21HghlVR6VHt//9R8FH1Uf5SBFILUhRSDlInUipSLlIzUjlST1JEUktSTFJeUlRSalJ0UmlSc1J/Un1SjVKUUpJScVKIUpGPqI+nUqxSrVK8UrVSwVLNUtdS3lLjUuaY7VLgUvNS9VL4UvlTBlMIdThTDVMQ" + - "Uw9TFVMaUyNTL1MxUzNTOFNAU0ZTRU4XU0lTTVHWU15TaVNuWRhTe1N3U4JTllOgU6ZTpVOuU7BTtlPDfBKW2VPfZvxx7lPuU+hT7VP6VAFUPVRAVCxULVQ8VC5UNlQpVB1UTlSPVHVUjlRfVHFUd1RwVJJUe1SAVHZUhFSQVIZUx1SiVLhUpVSsVMRUyFSo////////" + - "VKtUwlSkVL5UvFTYVOVU5lUPVRRU/VTuVO1U+lTiVTlVQFVjVUxVLlVcVUVVVlVXVThVM1VdVZlVgFSvVYpVn1V7VX5VmFWeVa5VfFWDValVh1WoVdpVxVXfVcRV3FXkVdRWFFX3VhZV/lX9VhtV+VZOVlBx31Y0VjZWMlY4//9Wa1ZkVi9WbFZqVoZWgFaKVqBWlFaP" + - "VqVWrla2VrRWwla8VsFWw1bAVshWzlbRVtNW11buVvlXAFb/VwRXCVcIVwtXDVcTVxhXFlXHVxxXJlc3VzhXTlc7V0BXT1dpV8BXiFdhV39XiVeTV6BXs1ekV6pXsFfDV8ZX1FfSV9NYClfWV+NYC1gZWB1YclghWGJYS1hwa8BYUlg9WHlYhVi5WJ9Yq1i6WN5Yu1i4" + - "WK5YxVjTWNFY11jZWNhY5VjcWORY31jvWPpY+Vj7WPxY/VkCWQpZEFkbaKZZJVksWS1ZMlk4WT560llVWVBZTllaWVhZYllgWWdZbFlp////////WXhZgVmdT15Pq1mjWbJZxlnoWdxZjVnZWdpaJVofWhFaHFoJWhpaQFpsWklaNVo2WmJaalqaWrxavlrLWsJavVrj" + - "Wtda5lrpWtZa+lr7WwxbC1sWWzJa0FsqWzZbPltDW0VbQFtRW1VbWltbW2VbaVtwW3NbdVt4ZYhbeluA//9bg1umW7hbw1vHW8lb1FvQW+Rb5lviW95b5VvrW/Bb9lvzXAVcB1wIXA1cE1wgXCJcKFw4XDlcQVxGXE5cU1xQXE9bcVxsXG5OYlx2XHlcjFyRXJRZm1yr" + - "XLtctly8XLdcxVy+XMdc2VzpXP1c+lztXYxc6l0LXRVdF11cXR9dG10RXRRdIl0aXRldGF1MXVJdTl1LXWxdc112XYddhF2CXaJdnV2sXa5dvV2QXbddvF3JXc1d013SXdZd213rXfJd9V4LXhpeGV4RXhteNl43XkReQ15AXk5eV15UXl9eYl5kXkdedV52XnqevF5/" + - "XqBewV7CXshe0F7P////////XtZe417dXtpe217iXuFe6F7pXuxe8V7zXvBe9F74Xv5fA18JX11fXF8LXxFfFl8pXy1fOF9BX0hfTF9OXy9fUV9WX1dfWV9hX21fc193X4Nfgl9/X4pfiF+RX4dfnl+ZX5hfoF+oX61fvF/WX/tf5F/4X/Ff3WCzX/9gIWBg//9gGWAQ" + - "YClgDmAxYBtgFWArYCZgD2A6YFpgQWBqYHdgX2BKYEZgTWBjYENgZGBCYGxga2BZYIFgjWDnYINgmmCEYJtglmCXYJJgp2CLYOFguGDgYNNgtF/wYL1gxmC1YNhhTWEVYQZg9mD3YQBg9GD6YQNhIWD7YPFhDWEOYUdhPmEoYSdhSmE/YTxhLGE0YT1hQmFEYXNhd2FY" + - "YVlhWmFrYXRhb2FlYXFhX2FdYVNhdWGZYZZhh2GsYZRhmmGKYZFhq2GuYcxhymHJYfdhyGHDYcZhumHLf3lhzWHmYeNh9mH6YfRh/2H9Yfxh/mIAYghiCWINYgxiFGIb////////Yh5iIWIqYi5iMGIyYjNiQWJOYl5iY2JbYmBiaGJ8YoJiiWJ+YpJik2KWYtRig2KU" + - "Ytdi0WK7Ys9i/2LGZNRiyGLcYsxiymLCYsdim2LJYwxi7mLxYydjAmMIYu9i9WNQYz5jTWQcY09jlmOOY4Bjq2N2Y6Njj2OJY59jtWNr//9jaWO+Y+ljwGPGY+NjyWPSY/ZjxGQWZDRkBmQTZCZkNmUdZBdkKGQPZGdkb2R2ZE5lKmSVZJNkpWSpZIhkvGTaZNJkxWTH" + - "ZLtk2GTCZPFk54IJZOBk4WKsZONk72UsZPZk9GTyZPplAGT9ZRhlHGUFZSRlI2UrZTRlNWU3ZTZlOHVLZUhlVmVVZU1lWGVeZV1lcmV4ZYJlg4uKZZtln2WrZbdlw2XGZcFlxGXMZdJl22XZZeBl4WXxZ3JmCmYDZftnc2Y1ZjZmNGYcZk9mRGZJZkFmXmZdZmRmZ2Zo" + - "Zl9mYmZwZoNmiGaOZolmhGaYZp1mwWa5Zslmvma8////////ZsRmuGbWZtpm4GY/ZuZm6WbwZvVm92cPZxZnHmcmZyeXOGcuZz9nNmdBZzhnN2dGZ15nYGdZZ2NnZGeJZ3BnqWd8Z2pnjGeLZ6ZnoWeFZ7dn72e0Z+xns2fpZ7hn5GfeZ91n4mfuZ7lnzmfGZ+dqnGge" + - "aEZoKWhAaE1oMmhO//9os2graFloY2h3aH9on2iPaK1olGidaJtog2quaLlodGi1aKBoumkPaI1ofmkBaMppCGjYaSJpJmjhaQxozWjUaOdo1Wk2aRJpBGjXaONpJWj5aOBo72koaSppGmkjaSFoxml5aXdpXGl4aWtpVGl+aW5pOWl0aT1pWWkwaWFpXmldaYFpammy" + - "aa5p0Gm/acFp02m+ac5b6GnKad1pu2nDaadqLmmRaaBpnGmVabRp3mnoagJqG2n/awpp+WnyaedqBWmxah5p7WoUaetqCmoSasFqI2oTakRqDGpyajZqeGpHamJqWWpmakhqOGoiapBqjWqgaoRqomqj////////apeGF2q7asNqwmq4arNqrGreatFq32qqatpq6mr7" + - "awWGFmr6axJrFpsxax9rOGs3dtxrOZjua0drQ2tJa1BrWWtUa1trX2tha3hreWt/a4BrhGuDa41rmGuVa55rpGuqa6trr2uya7Frs2u3a7xrxmvLa9Nr32vsa+tr82vv//+evmwIbBNsFGwbbCRsI2xebFVsYmxqbIJsjWyabIFsm2x+bGhsc2ySbJBsxGzxbNNsvWzX" + - "bMVs3WyubLFsvmy6bNts72zZbOptH4hNbTZtK209bThtGW01bTNtEm0MbWNtk21kbVpteW1ZbY5tlW/kbYVt+W4VbgpttW3HbeZtuG3Gbext3m3Mbeht0m3Fbfpt2W3kbdVt6m3ubi1ubm4ubhlucm5fbj5uI25rbitudm5Nbh9uQ246bk5uJG7/bh1uOG6CbqpumG7J" + - "brdu0269bq9uxG6ybtRu1W6PbqVuwm6fb0FvEXBMbuxu+G7+bz9u8m8xbu9vMm7M////////bz5vE273b4Zvem94b4FvgG9vb1tv829tb4JvfG9Yb45vkW/Cb2Zvs2+jb6FvpG+5b8Zvqm/fb9Vv7G/Ub9hv8W/ub9twCXALb/pwEXABcA9v/nAbcBpvdHAdcBhwH3Aw" + - "cD5wMnBRcGNwmXCScK9w8XCscLhws3CucN9wy3Dd//9w2XEJcP1xHHEZcWVxVXGIcWZxYnFMcVZxbHGPcftxhHGVcahxrHHXcblxvnHScclx1HHOceBx7HHncfVx/HH5cf9yDXIQchtyKHItcixyMHIycjtyPHI/ckByRnJLclhydHJ+coJygXKHcpJylnKicqdyuXKy" + - "csNyxnLEcs5y0nLicuBy4XL5cvdQD3MXcwpzHHMWcx1zNHMvcylzJXM+c05zT57Yc1dzanNoc3BzeHN1c3tzenPIc7NzznO7c8Bz5XPuc950onQFdG90JXP4dDJ0OnRVdD90X3RZdEF0XHRpdHB0Y3RqdHZ0fnSLdJ50p3TKdM901HPx////////dOB043TndOl07nTy" + - "dPB08XT4dPd1BHUDdQV1DHUOdQ11FXUTdR51JnUsdTx1RHVNdUp1SXVbdUZ1WnVpdWR1Z3VrdW11eHV2dYZ1h3V0dYp1iXWCdZR1mnWddaV1o3XCdbN1w3W1db11uHW8dbF1zXXKddJ12XXjdd51/nX///91/HYBdfB1+nXydfN2C3YNdgl2H3YndiB2IXYidiR2NHYw" + - "djt2R3ZIdkZ2XHZYdmF2YnZodml2anZndmx2cHZydnZ2eHZ8doB2g3aIdot2jnaWdpN2mXaadrB2tHa4drl2unbCds121nbSdt524Xbldud26oYvdvt3CHcHdwR3KXckdx53JXcmdxt3N3c4d0d3Wndod2t3W3dld393fnd5d453i3eRd6B3nnewd7Z3uXe/d7x3vXe7" + - "d8d3zXfXd9p33Hfjd+53/HgMeBJ5JnggeSp4RXiOeHR4hnh8eJp4jHijeLV4qniveNF4xnjLeNR4vni8eMV4ynjs////////eOd42nj9ePR5B3kSeRF5GXkseSt5QHlgeVd5X3laeVV5U3l6eX95inmdeaefS3mqea55s3m5ebp5yXnVeed57HnheeN6CHoNehh6GXog" + - "eh95gHoxejt6Pno3ekN6V3pJemF6Ynppn516cHp5en16iHqXepV6mHqWeql6yHqw//96tnrFesR6v5CDesd6ynrNes961XrTetl62nrdeuF64nrmeu168HsCew97CnsGezN7GHsZex57NXsoezZ7UHt6ewR7TXsLe0x7RXt1e2V7dHtne3B7cXtse257nXuYe597jXuc" + - "e5p7i3uSe497XXuZe8t7wXvMe897tHvGe9176XwRfBR75nvlfGB8AHwHfBN783v3fBd8DXv2fCN8J3wqfB98N3wrfD18THxDfFR8T3xAfFB8WHxffGR8VnxlfGx8dXyDfJB8pHytfKJ8q3yhfKh8s3yyfLF8rny5fL18wHzFfMJ82HzSfNx84ps7fO988nz0fPZ8+n0G" + - "////////fQJ9HH0VfQp9RX1LfS59Mn0/fTV9Rn1zfVZ9Tn1yfWh9bn1PfWN9k32JfVt9j319fZt9un2ufaN9tX3Hfb19q349faJ9r33cfbh9n32wfdh93X3kfd59+33yfeF+BX4KfiN+IX4SfjF+H34Jfgt+In5GfmZ+O341fjl+Q343//9+Mn46fmd+XX5Wfl5+WX5a" + - "fnl+an5pfnx+e36DfdV+fY+ufn9+iH6Jfox+kn6QfpN+lH6Wfo5+m36cfzh/On9Ff0x/TX9Of1B/UX9Vf1R/WH9ff2B/aH9pf2d/eH+Cf4Z/g3+If4d/jH+Uf55/nX+af6N/r3+yf7l/rn+2f7iLcX/Ff8Z/yn/Vf9R/4X/mf+l/83/5mNyABoAEgAuAEoAYgBmAHIAh" + - "gCiAP4A7gEqARoBSgFiAWoBfgGKAaIBzgHKAcIB2gHmAfYB/gISAhoCFgJuAk4CagK1RkICsgNuA5YDZgN2AxIDagNaBCYDvgPGBG4EpgSOBL4FL////////louBRoE+gVOBUYD8gXGBboFlgWaBdIGDgYiBioGAgYKBoIGVgaSBo4FfgZOBqYGwgbWBvoG4gb2BwIHC" + - "gbqByYHNgdGB2YHYgciB2oHfgeCB54H6gfuB/oIBggKCBYIHggqCDYIQghaCKYIrgjiCM4JAglmCWIJdglqCX4Jk//+CYoJogmqCa4IugnGCd4J4gn6CjYKSgquCn4K7gqyC4YLjgt+C0oL0gvOC+oOTgwOC+4L5gt6DBoLcgwmC2YM1gzSDFoMygzGDQIM5g1CDRYMv" + - "gyuDF4MYg4WDmoOqg5+DooOWgyODjoOHg4qDfIO1g3ODdYOgg4mDqIP0hBOD64POg/2EA4PYhAuDwYP3hAeD4IPyhA2EIoQgg72EOIUGg/uEbYQqhDyFWoSEhHeEa4SthG6EgoRphEaELIRvhHmENYTKhGKEuYS/hJ+E2YTNhLuE2oTQhMGExoTWhKGFIYT/hPSFF4UY" + - "hSyFH4UVhRSE/IVAhWOFWIVI////////hUGGAoVLhVWFgIWkhYiFkYWKhaiFbYWUhZuF6oWHhZyFd4V+hZCFyYW6hc+FuYXQhdWF3YXlhdyF+YYKhhOGC4X+hfqGBoYihhqGMIY/hk1OVYZUhl+GZ4ZxhpOGo4aphqqGi4aMhraGr4bEhsaGsIbJiCOGq4bUht6G6Ybs" + - "//+G34bbhu+HEocGhwiHAIcDhvuHEYcJhw2G+YcKhzSHP4c3hzuHJYcphxqHYIdfh3iHTIdOh3SHV4doh26HWYdTh2OHaogFh6KHn4eCh6+Hy4e9h8CH0JbWh6uHxIezh8eHxoe7h++H8ofgiA+IDYf+h/aH94gOh9KIEYgWiBWIIoghiDGINog5iCeIO4hEiEKIUohZ" + - "iF6IYohriIGIfoieiHWIfYi1iHKIgoiXiJKIroiZiKKIjYikiLCIv4ixiMOIxIjUiNiI2YjdiPmJAoj8iPSI6IjyiQSJDIkKiROJQ4keiSWJKokriUGJRIk7iTaJOIlMiR2JYIle////////iWaJZIltiWqJb4l0iXeJfomDiYiJiomTiZiJoYmpiaaJrImvibKJuom9" + - "ib+JwInaidyJ3YnnifSJ+IoDihaKEIoMihuKHYolijaKQYpbilKKRopIinyKbYpsimKKhYqCioSKqIqhipGKpYqmipqKo4rEis2KworaiuuK84rn//+K5IrxixSK4IriiveK3orbiwyLB4saiuGLFosQixeLIIszl6uLJosriz6LKItBi0yLT4tOi0mLVotbi1qLa4tf" + - "i2yLb4t0i32LgIuMi46LkouTi5aLmYuajDqMQYw/jEiMTIxOjFCMVYxijGyMeIx6jIKMiYyFjIqMjYyOjJSMfIyYYh2MrYyqjL2MsoyzjK6MtozIjMGM5IzjjNqM/Yz6jPuNBI0FjQqNB40PjQ2NEJ9OjROMzY0UjRaNZ41tjXGNc42BjZmNwo2+jbqNz43ajdaNzI3b" + - "jcuN6o3rjd+N4438jgiOCY3/jh2OHo4Qjh+OQo41jjCONI5K////////jkeOSY5MjlCOSI5ZjmSOYI4qjmOOVY52jnKOfI6BjoeOhY6EjouOio6TjpGOlI6ZjqqOoY6sjrCOxo6xjr6OxY7IjsuO247jjvyO+47rjv6PCo8FjxWPEo8ZjxOPHI8fjxuPDI8mjzOPO485" + - "j0WPQo8+j0yPSY9Gj06PV49c//+PYo9jj2SPnI+fj6OPrY+vj7eP2o/lj+KP6o/vkIeP9JAFj/mP+pARkBWQIZANkB6QFpALkCeQNpA1kDmP+JBPkFCQUZBSkA6QSZA+kFaQWJBekGiQb5B2lqiQcpCCkH2QgZCAkIqQiZCPkKiQr5CxkLWQ4pDkYkiQ25ECkRKRGZEy" + - "kTCRSpFWkViRY5FlkWmRc5FykYuRiZGCkaKRq5GvkaqRtZG0kbqRwJHBkcmRy5HQkdaR35HhkduR/JH1kfaSHpH/khSSLJIVkhGSXpJXkkWSSZJkkkiSlZI/kkuSUJKckpaSk5KbklqSz5K5kreS6ZMPkvqTRJMu////////kxmTIpMakyOTOpM1kzuTXJNgk3yTbpNW" + - "k7CTrJOtk5STuZPWk9eT6JPlk9iTw5Pdk9CTyJPklBqUFJQTlAOUB5QQlDaUK5Q1lCGUOpRBlFKURJRblGCUYpRelGqSKZRwlHWUd5R9lFqUfJR+lIGUf5WClYeVipWUlZaVmJWZ//+VoJWolaeVrZW8lbuVuZW+lcpv9pXDlc2VzJXVldSV1pXcleGV5ZXiliGWKJYu" + - "li+WQpZMlk+WS5Z3llyWXpZdll+WZpZylmyWjZaYlpWWl5aqlqeWsZaylrCWtJa2lriWuZbOlsuWyZbNiU2W3JcNltWW+ZcElwaXCJcTlw6XEZcPlxaXGZcklyqXMJc5lz2XPpdEl0aXSJdCl0mXXJdgl2SXZpdoUtKXa5dxl3mXhZd8l4GXepeGl4uXj5eQl5yXqJem" + - "l6OXs5e0l8OXxpfIl8uX3Jftn0+X8nrfl/aX9ZgPmAyYOJgkmCGYN5g9mEaYT5hLmGuYb5hw////////mHGYdJhzmKqYr5ixmLaYxJjDmMaY6ZjrmQOZCZkSmRSZGJkhmR2ZHpkkmSCZLJkumT2ZPplCmUmZRZlQmUuZUZlSmUyZVZmXmZiZpZmtma6ZvJnfmduZ3ZnY" + - "mdGZ7ZnumfGZ8pn7mfiaAZoPmgWZ4poZmiuaN5pFmkKaQJpD//+aPppVmk2aW5pXml+aYpplmmSaaZprmmqarZqwmryawJrPmtGa05rUmt6a35rimuOa5prvmuua7pr0mvGa95r7mwabGJsamx+bIpsjmyWbJ5somymbKpsumy+bMptEm0ObT5tNm06bUZtYm3Sbk5uD" + - "m5GblpuXm5+boJuom7SbwJvKm7mbxpvPm9Gb0pvjm+Kb5JvUm+GcOpvym/Gb8JwVnBScCZwTnAycBpwInBKcCpwEnC6cG5wlnCScIZwwnEecMpxGnD6cWpxgnGecdpx4nOec7JzwnQmdCJzrnQOdBp0qnSadr50jnR+dRJ0VnRKdQZ0/nT6dRp1I////////nV2dXp1k" + - "nVGdUJ1ZnXKdiZ2Hnaudb516nZqdpJ2pnbKdxJ3BnbuduJ26ncadz53Cndmd0534nead7Z3vnf2eGp4bnh6edZ55nn2egZ6InouejJ6SnpWekZ6dnqWeqZ64nqqerZdhnsyezp7PntCe1J7cnt6e3Z7gnuWe6J7v//+e9J72nvee+Z77nvye/Z8Hnwh2t58VnyGfLJ8+" + - "n0qfUp9Un2OfX59gn2GfZp9nn2yfap93n3Kfdp+Vn5yfoFgvaceQWXRkUdxxmf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + - "/////////////////////////////////////////////w=="; +// Data derived from ftp://ftp.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/SHIFTJIS.TXT + private static final String PACKED_QR_KANJI_TO_UNICODE = readPacked_KANJI(); private static short[] UNICODE_TO_QR_KANJI = new short[1 << 16]; @@ -407,18 +332,31 @@ public final class QrSegmentAdvanced { Arrays.fill(UNICODE_TO_QR_KANJI, (short)-1); byte[] bytes = Base64.getDecoder().decode(PACKED_QR_KANJI_TO_UNICODE); for (int i = 0; i < bytes.length; i += 2) { - char c = (char)(((bytes[i] & 0xFF) << 8) | (bytes[i + 1] & 0xFF)); - if (c == 0xFFFF) + char convertChar = (char)(((bytes[i] & 0xFF) << 8) | (bytes[i + 1] & 0xFF)); + if (convertChar == 0xFFFF) continue; - assert UNICODE_TO_QR_KANJI[c] == -1; - UNICODE_TO_QR_KANJI[c] = (short)(i / 2); + assert UNICODE_TO_QR_KANJI[convertChar] == -1; + UNICODE_TO_QR_KANJI[convertChar] = (short)(i / 2); } } + private static String readPacked_KANJI() { + String str = ""; + try { + File file = new File("./packet_KanJI.txt"); + Scanner scan = new Scanner(file); + str +=scan.nextLine(); + scan.close(); + } + catch(FileNotFoundException e){ + } + + return str; + } /*---- Miscellaneous ----*/ private QrSegmentAdvanced() {} // Not instantiable -} +} \ No newline at end of file diff --git a/java/src/main/java/io/nayuki/qrcodegen/msk0Command.java b/java/src/main/java/io/nayuki/qrcodegen/msk0Command.java new file mode 100644 index 0000000..357bcdc --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/msk0Command.java @@ -0,0 +1,13 @@ +package io.nayuki.qrcodegen; + +public class msk0Command implements Command{ + private Msk0 theMsk0; + + public msk0Command(Msk0 theMsk0) { + this.theMsk0 = theMsk0; + } + + public boolean excute(int y, int x, int msk) { + return theMsk0.operation(y, x, msk); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/msk1Command.java b/java/src/main/java/io/nayuki/qrcodegen/msk1Command.java new file mode 100644 index 0000000..a503249 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/msk1Command.java @@ -0,0 +1,13 @@ +package io.nayuki.qrcodegen; + +public class msk1Command implements Command { + private Msk1 theMsk1; + + public msk1Command(Msk1 theMsk1) { + this.theMsk1 = theMsk1; + } + + public boolean excute(int y, int x, int msk) { + return theMsk1.operation(y, x, msk); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/msk2Command.java b/java/src/main/java/io/nayuki/qrcodegen/msk2Command.java new file mode 100644 index 0000000..d62f58e --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/msk2Command.java @@ -0,0 +1,13 @@ +package io.nayuki.qrcodegen; + +public class msk2Command implements Command { + private Msk2 theMsk2; + + public msk2Command(Msk2 theMsk2) { + this.theMsk2 = theMsk2; + } + + public boolean excute(int y, int x, int msk) { + return theMsk2.operation(y, x, msk); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/msk3Command.java b/java/src/main/java/io/nayuki/qrcodegen/msk3Command.java new file mode 100644 index 0000000..402f2a2 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/msk3Command.java @@ -0,0 +1,13 @@ +package io.nayuki.qrcodegen; + +public class msk3Command implements Command { + private Msk3 theMsk3; + + public msk3Command(Msk3 theMsk3) { + this.theMsk3 = theMsk3; + } + + public boolean excute(int y, int x, int msk) { + return theMsk3.operation(y, x, msk); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/msk4Command.java b/java/src/main/java/io/nayuki/qrcodegen/msk4Command.java new file mode 100644 index 0000000..ca755fc --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/msk4Command.java @@ -0,0 +1,13 @@ +package io.nayuki.qrcodegen; + +public class msk4Command implements Command { + private Msk4 theMsk4; + + public msk4Command(Msk4 theMsk4) { + this.theMsk4 = theMsk4; + } + + public boolean excute(int y, int x, int msk) { + return theMsk4.operation(y, x, msk); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/msk5Command.java b/java/src/main/java/io/nayuki/qrcodegen/msk5Command.java new file mode 100644 index 0000000..7a6f4b1 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/msk5Command.java @@ -0,0 +1,13 @@ +package io.nayuki.qrcodegen; + +public class msk5Command implements Command { + private Msk5 theMsk5; + + public msk5Command(Msk5 theMsk5) { + this.theMsk5 = theMsk5; + } + + public boolean excute(int y, int x, int msk) { + return theMsk5.operation(y, x, msk); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/msk6Command.java b/java/src/main/java/io/nayuki/qrcodegen/msk6Command.java new file mode 100644 index 0000000..078c270 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/msk6Command.java @@ -0,0 +1,13 @@ +package io.nayuki.qrcodegen; + +public class msk6Command implements Command { + private Msk6 theMsk6; + + public msk6Command(Msk6 theMsk6) { + this.theMsk6 = theMsk6; + } + + public boolean excute(int y, int x, int msk) { + return theMsk6.operation(y, x, msk); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/msk7Command.java b/java/src/main/java/io/nayuki/qrcodegen/msk7Command.java new file mode 100644 index 0000000..b5c9e2c --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/msk7Command.java @@ -0,0 +1,13 @@ +package io.nayuki.qrcodegen; + +public class msk7Command implements Command { + private Msk7 theMsk7; + + public msk7Command(Msk7 theMsk7) { + this.theMsk7 = theMsk7; + } + + public boolean excute(int y, int x, int msk) { + return theMsk7.operation(y, x, msk); + } +}