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/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/Ecc.java b/java/src/main/java/io/nayuki/qrcodegen/Ecc.java new file mode 100644 index 0000000..e6f21a9 --- /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/MakeAlphaNumericToSegment.java b/java/src/main/java/io/nayuki/qrcodegen/MakeAlphaNumericToSegment.java new file mode 100644 index 0000000..2ed2f40 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/MakeAlphaNumericToSegment.java @@ -0,0 +1,34 @@ +import java.util.Objects; + +public class MakeAlphaNumericToSegment implements MakeSegment { + + /** + * 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 the text (not {@code null}), with only certain characters allowed + * @return a segment (not {@code null}) containing the text + * @throws NullPointerException if the string is {@code null} + * @throws IllegalArgumentException if the string contains non-encodable characters + */ + public QrSegment excute(String text) { + Objects.requireNonNull(text); + if (!QrSegment.ALPHANUMERIC_REGEX.matcher(text).matches()) + throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode"); + + BitBuffer bitBuffer = new BitBuffer(); + changeAlphaNumericStringToSegment(text, bitBuffer); + return new QrSegment(QrSegment.Mode.ALPHANUMERIC, text.length(), bitBuffer); + } + + public static void changeAlphaNumericStringToSegment(String text, BitBuffer bitBuffer) { + int i; + for (i = 0; i <= text.length() - 2; i += 2) { // Process groups of 2 + int temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45; + temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1)); + bitBuffer.appendBits(temp, 11); + } + if (i < text.length()) // 1 character remaining + bitBuffer.appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/MakeBytesToSegment.java b/java/src/main/java/io/nayuki/qrcodegen/MakeBytesToSegment.java new file mode 100644 index 0000000..2b3660c --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/MakeBytesToSegment.java @@ -0,0 +1,36 @@ +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +public class MakeBytesToSegment implements MakeSegment { + + /** + * Returns a segment representing the specified binary data + * encoded in byte mode. All input byte arrays are acceptable. + *

Any text string can be converted to UTF-8 bytes ({@code + * s.getBytes(StandardCharsets.UTF_8)}) and encoded as a byte mode segment.

+ * @param data the binary data (not {@code null}) + * @return a segment (not {@code null}) containing the data + * @throws NullPointerException if the array is {@code null} + */ + public QrSegment excute(String text) { + byte[] data = text.getBytes(StandardCharsets.UTF_8)); + + Objects.requireNonNull(data); + BitBuffer bitBuffer = new BitBuffer(); + for (byte bits : data) + changeByteToSegment(bitBuffer, bits); + return new QrSegment(QrSegment.Mode.BYTE, data.length, bitBuffer); + } + + public QrSegment excuteForBytedata(byte[] data) { + Objects.requireNonNull(data); + BitBuffer bitBuffer = new BitBuffer(); + for (byte bits : data) + changeByteToSegment(bitBuffer, bits); + return new QrSegment(QrSegment.Mode.BYTE, data.length, bitBuffer); + } + + public static void changeByteToSegment(BitBuffer bitBuffer, byte bits) { + bitBuffer.appendBits(bits & 0xFF, 8); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/MakeNumericToSegment.java b/java/src/main/java/io/nayuki/qrcodegen/MakeNumericToSegment.java new file mode 100644 index 0000000..929a6b7 --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/MakeNumericToSegment.java @@ -0,0 +1,33 @@ +import java.util.Objects; + +public class MakeNumericToSegment implements MakeSegment { + + /** + * Returns a segment representing the specified string of decimal digits encoded in numeric mode. + * @param digits the text (not {@code null}), with only digits from 0 to 9 allowed + * @return a segment (not {@code null}) containing the text + * @throws NullPointerException if the string is {@code null} + * @throws IllegalArgumentException if the string contains non-digit characters + */ + public QrSegment excute(String digits) { + Objects.requireNonNull(digits); + if (containNonNumericCharaters(digits)) + throw new IllegalArgumentException("String contains non-numeric characters"); + + BitBuffer bitBuffer = new BitBuffer(); + changeNumericToSegment(digits, bitBuffer); + return new QrSegment(QrSegment.Mode.NUMERIC, digits.length(), bitBuffer); + } + + public static void changeNumericToSegment(String digits, BitBuffer bitBuffer) { + for (int i = 0; i < digits.length(); ) { // Consume up to 3 digits per iteration + int n = Math.min(digits.length() - i, 3); + bitBuffer.appendBits(Integer.parseInt(digits.substring(i, i + n)), n * 3 + 1); + i += n; + } + } + + public static boolean containNonNumericCharaters(String digits) { + return !QrSegment.NUMERIC_REGEX.matcher(digits).matches(); + } +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/MakeSegment.java b/java/src/main/java/io/nayuki/qrcodegen/MakeSegment.java new file mode 100644 index 0000000..cfda06d --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/MakeSegment.java @@ -0,0 +1,4 @@ + +public interface MakeSegment { + public QrSegment excute(String text); +} diff --git a/java/src/main/java/io/nayuki/qrcodegen/MakeSegmentFactory.java b/java/src/main/java/io/nayuki/qrcodegen/MakeSegmentFactory.java new file mode 100644 index 0000000..6a45f5c --- /dev/null +++ b/java/src/main/java/io/nayuki/qrcodegen/MakeSegmentFactory.java @@ -0,0 +1,15 @@ +public class MakeSegmentFactory { + public static MakeSegment getMakeSegment(String text) { + MakeSegment makeSegment = null; + + if (text.equals("")); // Leave result empty + else if (QrSegment.NUMERIC_REGEX.matcher(text).matches()) + makeSegment = new MakeNumericToSegment(); + else if (QrSegment.ALPHANUMERIC_REGEX.matcher(text).matches()) + makeSegment = new MakeAlphaNumericToSegment(); + else + makeSegment = new MakeBytesToSegment(); + + return makeSegment; + } +} 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 dcb8b6b..5ade16c 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCode.java @@ -421,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); @@ -512,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; @@ -830,9 +830,9 @@ public final class QrCode { // 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); } @@ -889,47 +889,4 @@ public final class QrCode { 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 3c50bda..0e25584 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java @@ -53,15 +53,15 @@ public final class QrCodeGeneratorDemo { // 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 + Ecc errCorLvl = Ecc.LOW; // Error correction level - QrCode qr = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol + QrCode qrCode = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol - BufferedImage img = qr.toImage(10, 4); // Convert to bitmap image + BufferedImage img = qrCode.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 + String svg = qrCode.toSvgString(4); // Convert to SVG XML code File svgFile = new File("hello-world-QR.svg"); // File path for output Files.write(svgFile.toPath(), // Write image to file svg.getBytes(StandardCharsets.UTF_8)); @@ -70,97 +70,112 @@ public final class QrCodeGeneratorDemo { // 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; + QrCode qrCode; // Numeric mode encoding (3.33 bits per digit) - qr = QrCode.encodeText("314159265358979323846264338327950288419716939937510", QrCode.Ecc.MEDIUM); - writePng(qr.toImage(13, 1), "pi-digits-QR.png"); + + qrCode = QrCode.encodeText("314159265358979323846264338327950288419716939937510", Ecc.MEDIUM); + writePng(qrCode.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"); + qrCode = QrCode.encodeText("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", Ecc.HIGH); + writePng(qrCode.toImage(10, 2), "alphanumeric-QR.png"); // Unicode text as UTF-8 - qr = QrCode.encodeText("こんにちwa、世界! αβγδ", QrCode.Ecc.QUARTILE); - writePng(qr.toImage(10, 3), "unicode-QR.png"); + 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) - qr = QrCode.encodeText( + qrCode = 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"); + + "a White Rabbit with pink eyes ran close by her.", Ecc.HIGH); + + writePng(qrCode.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; + QrCode qrCode; + List segments; // 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"); + + qrCode = QrCode.encodeText(silver0 + silver1, Ecc.LOW); + writePng(qrCode.toImage(10, 3), "sqrt2-monolithic-QR.png"); + - segs = Arrays.asList( + segments = Arrays.asList( QrSegment.makeAlphanumeric(silver0), QrSegment.makeNumeric(silver1)); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); - writePng(qr.toImage(10, 3), "sqrt2-segmented-QR.png"); + + qrCode = QrCode.encodeSegments(segments, Ecc.LOW); + writePng(qrCode.toImage(10, 3), "sqrt2-segmented-QR.png"); + // Illustration "golden" - String golden0 = "Golden ratio φ = 1."; + 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"); + + qrCode = QrCode.encodeText(golden0 + golden1 + golden2, Ecc.LOW); + writePng(qrCode.toImage(8, 5), "phi-monolithic-QR.png"); + - segs = Arrays.asList( + segments = 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"); + + qrCode = QrCode.encodeSegments(segments, Ecc.LOW); + writePng(qrCode.toImage(8, 5), "phi-segmented-QR.png"); // Illustration "Madoka": kanji, kana, Cyrillic, full-width Latin, Greek characters - String madoka = "「魔法少女まどか☆マギカ」って、 ИАИ desu κα?"; - qr = QrCode.encodeText(madoka, QrCode.Ecc.LOW); - writePng(qr.toImage(9, 4), "madoka-utf8-QR.png"); + String madoka = "占쎈슞異몌쫲類앹빼阿잙뀍寃뀐옙寃묕옙嫄�占쎌겳占쎄묻占쎄텤占쎄텠占쎈씞寃귨옙寃랃옙怨ο옙占쏙옙�꺂癒믪꼨占쏙옙影�袁ы맀影�蹂⑺맟占쏙옙鰲��뀭塋딉옙"; + qrCode = QrCode.encodeText(madoka, Ecc.LOW); + writePng(qrCode.toImage(9, 4), "madoka-utf8-QR.png"); - segs = Arrays.asList(QrSegmentAdvanced.makeKanji(madoka)); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); - writePng(qr.toImage(9, 4), "madoka-kanji-QR.png"); + segments = Arrays.asList(QrSegmentAdvanced.makeKanji(madoka)); + qrCode = QrCode.encodeSegments(segments, Ecc.LOW); + writePng(qrCode.toImage(9, 4), "madoka-kanji-QR.png"); + + } // Creates QR Codes with the same size and contents but different mask patterns. private static void doMaskDemo() throws IOException { - QrCode qr; - List segs; + QrCode qrCode; + List segments; // Project Nayuki URL - segs = QrSegment.makeSegments("https://www.nayuki.io/"); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, -1, true); // Automatic mask - writePng(qr.toImage(8, 6), "project-nayuki-automask-QR.png"); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 3, true); // Force mask 3 - writePng(qr.toImage(8, 6), "project-nayuki-mask3-QR.png"); + + 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"); + qrCode = QrCode.encodeSegments(segments, Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 3, true); // Force mask 3 + writePng(qrCode.toImage(8, 6), "project-nayuki-mask3-QR.png"); // Chinese text as UTF-8 - segs = QrSegment.makeSegments("維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫"); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 0, true); // Force mask 0 - writePng(qr.toImage(10, 3), "unicode-mask0-QR.png"); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 1, true); // Force mask 1 - writePng(qr.toImage(10, 3), "unicode-mask1-QR.png"); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 5, true); // Force mask 5 - writePng(qr.toImage(10, 3), "unicode-mask5-QR.png"); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 7, true); // Force mask 7 - writePng(qr.toImage(10, 3), "unicode-mask7-QR.png"); + segments = QrSegment.makeSegments("力놂옙占쎌쓢占쎌뇥�뇖臾뺥렩Wikipedia塋딅슜嫄앾옙寃켲/占쎈샆�뎚藥�戮먮뒰i占쎈쫨i.占쏙옙/塋딅맚�궦鼇앾옙占쎈뿨�닅占쎈뎨占쎈�깍Ⅴ諛ㅿ옙怨⑸�띰옙堉�歷뜯몼�꽎鼇앸떱姨�亦껋쉮占쏙옙�돦力녠엽�윭占쎌뇥�뇖臾덈�뀐옙�럱占쎈쐭俑앹뮂怡ワ옙鍮�"); + qrCode = QrCode.encodeSegments(segments, Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 0, true); // Force mask 0 + writePng(qrCode.toImage(10, 3), "unicode-mask0-QR.png"); + qrCode = QrCode.encodeSegments(segments, Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 1, true); // Force mask 1 + writePng(qrCode.toImage(10, 3), "unicode-mask1-QR.png"); + qrCode = QrCode.encodeSegments(segments, Ecc.MEDIUM, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 5, true); // Force mask 5 + 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 4cf9de3..1cb9f44 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorWorker.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCodeGeneratorWorker.java @@ -57,11 +57,11 @@ public final class QrCodeGeneratorWorker { boolean isAscii = true; byte[] data = new byte[length]; for (int i = 0; i < data.length; i++) { - int b = input.nextInt(); - if (b < 0 || b > 255) + int one_byte = input.nextInt(); + if (one_byte < 0 || one_byte > 255) throw new RuntimeException(); - data[i] = (byte)b; - isAscii &= b < 128; + data[i] = (byte)one_byte; + isAscii &= one_byte < 128; } // Read encoding parameters @@ -75,14 +75,15 @@ public final class QrCodeGeneratorWorker { throw new RuntimeException(); // Make segments for encoding - List segs; + List segments; if (isAscii) - segs = QrSegment.makeSegments(new String(data, StandardCharsets.US_ASCII)); + segments = QrSegment.makeSegments(new String(data, StandardCharsets.US_ASCII)); else - segs = Arrays.asList(QrSegment.makeBytes(data)); + segments = Arrays.asList(QrSegment.makeBytes(data)); try { // Try to make QR Code symbol - QrCode qr = QrCode.encodeSegments(segs, QrCode.Ecc.values()[errCorLvl], minVersion, maxVersion, mask, boostEcl != 0); + 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/QrSegment.java b/java/src/main/java/io/nayuki/qrcodegen/QrSegment.java index f416200..307f549 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrSegment.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrSegment.java @@ -29,6 +29,8 @@ import java.util.List; import java.util.Objects; import java.util.regex.Pattern; +import QrSegment.Mode; + /** * A segment of character/binary/control data in a QR Code symbol. @@ -46,7 +48,6 @@ import java.util.regex.Pattern; public final class QrSegment { /*---- Static factory functions (mid level) ----*/ - /** * Returns a segment representing the specified binary data * encoded in byte mode. All input byte arrays are acceptable. @@ -57,11 +58,9 @@ public final class QrSegment { * @throws NullPointerException if the array is {@code null} */ public static QrSegment makeBytes(byte[] data) { - Objects.requireNonNull(data); - BitBuffer bb = new BitBuffer(); - for (byte b : data) - bb.appendBits(b & 0xFF, 8); - return new QrSegment(Mode.BYTE, data.length, bb); + MakeBytesToSegment makeBytesToSegment = new MakeBytesToSegment(); + + return makeBytesToSegment.excuteForBytedata(data); } @@ -73,17 +72,9 @@ public final class QrSegment { * @throws IllegalArgumentException if the string contains non-digit characters */ public static QrSegment makeNumeric(String digits) { - Objects.requireNonNull(digits); - if (!NUMERIC_REGEX.matcher(digits).matches()) - throw new IllegalArgumentException("String contains non-numeric characters"); + MakeSegment makeSegment = new MakeNumericToSegment(); - BitBuffer bb = new BitBuffer(); - for (int i = 0; i < digits.length(); ) { // Consume up to 3 digits per iteration - int n = Math.min(digits.length() - i, 3); - bb.appendBits(Integer.parseInt(digits.substring(i, i + n)), n * 3 + 1); - i += n; - } - return new QrSegment(Mode.NUMERIC, digits.length(), bb); + return makeSegment.excute(digits); } @@ -97,23 +88,10 @@ public final class QrSegment { * @throws IllegalArgumentException if the string contains non-encodable characters */ public static QrSegment makeAlphanumeric(String text) { - Objects.requireNonNull(text); - if (!ALPHANUMERIC_REGEX.matcher(text).matches()) - throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode"); + MakeSegment makeSegment = new MakeAlphaNumericToSegment(); - BitBuffer bb = new BitBuffer(); - int i; - for (i = 0; i <= text.length() - 2; i += 2) { // Process groups of 2 - int temp = ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45; - temp += ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1)); - bb.appendBits(temp, 11); - } - if (i < text.length()) // 1 character remaining - bb.appendBits(ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6); - return new QrSegment(Mode.ALPHANUMERIC, text.length(), bb); + return makeSegment.excute(text); } - - /** * Returns a list of zero or more segments to represent the specified Unicode text string. * The result may use various segment modes and switch modes to optimize the length of the bit stream. @@ -121,22 +99,18 @@ public final class QrSegment { * @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text * @throws NullPointerException if the text is {@code null} */ + public static List makeSegments(String text) { Objects.requireNonNull(text); - // Select the most efficient segment encoding automatically - List result = new ArrayList<>(); - if (text.equals("")); // Leave result empty - else if (NUMERIC_REGEX.matcher(text).matches()) - result.add(makeNumeric(text)); - else if (ALPHANUMERIC_REGEX.matcher(text).matches()) - result.add(makeAlphanumeric(text)); - else - result.add(makeBytes(text.getBytes(StandardCharsets.UTF_8))); - return result; - } - + List segments = new ArrayList<>(); + + MakeSegment makeSegment = MakeSegmentFactory.getMakeSegment(text); + + segments.add(makeSegment.excute(text)); + return segments; + } /** * Returns a segment representing an Extended Channel Interpretation * (ECI) designator with the specified assignment value. @@ -144,21 +118,21 @@ public final class QrSegment { * @return a segment (not {@code null}) containing the data * @throws IllegalArgumentException if the value is outside the range [0, 106) */ - public static QrSegment makeEci(int assignVal) { - BitBuffer bb = new BitBuffer(); - if (assignVal < 0) + public static QrSegment makeEci(int assignValue) { + BitBuffer bitBuffer = new BitBuffer(); + if (assignValue < 0) throw new IllegalArgumentException("ECI assignment value out of range"); - else if (assignVal < (1 << 7)) - bb.appendBits(assignVal, 8); - else if (assignVal < (1 << 14)) { - bb.appendBits(2, 2); - bb.appendBits(assignVal, 14); - } else if (assignVal < 1_000_000) { - bb.appendBits(6, 3); - bb.appendBits(assignVal, 21); + else if (assignValue < (1 << 7)) + bitBuffer.appendBits(assignValue, 8); + else if (assignValue < (1 << 14)) { + bitBuffer.appendBits(2, 2); + bitBuffer.appendBits(assignValue, 14); + } else if (assignValue < 1_000_000) { + bitBuffer.appendBits(6, 3); + bitBuffer.appendBits(assignValue, 21); } else throw new IllegalArgumentException("ECI assignment value out of range"); - return new QrSegment(Mode.ECI, 0, bb); + return new QrSegment(Mode.ECI, 0, bitBuffer); } @@ -189,16 +163,15 @@ public final class QrSegment { * @throws NullPointerException if the mode or data is {@code null} * @throws IllegalArgumentException if the character count is negative */ - public QrSegment(Mode md, int numCh, BitBuffer data) { - mode = Objects.requireNonNull(md); + public QrSegment(Mode _mode, int _numberOfCharacters, BitBuffer data) { + mode = Objects.requireNonNull(_mode); Objects.requireNonNull(data); - if (numCh < 0) + if (_numberOfCharacters < 0) throw new IllegalArgumentException("Invalid value"); - numChars = numCh; + numberOfCharacters = _numberOfCharacters; this.data = data.clone(); // Make defensive copy } - /*---- Methods ----*/ /** @@ -213,22 +186,21 @@ public final class QrSegment { // Calculates the number of bits needed to encode the given segments at the given version. // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too // many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE. - static int getTotalBits(List segs, int version) { - Objects.requireNonNull(segs); - long result = 0; - for (QrSegment seg : segs) { - Objects.requireNonNull(seg); - int ccbits = seg.mode.numCharCountBits(version); - if (seg.numChars >= (1 << ccbits)) + static int getTotalBits(List segments, int version) { + Objects.requireNonNull(segments); + long TotalBits = 0; + for (QrSegment segment : segments) { + Objects.requireNonNull(segment); + int characterCountBits = segment.mode.numCharCountBits(version); + if (segment.numberOfCharacters >= (1 << characterCountBits)) return -1; // The segment's length doesn't fit the field's bit width - result += 4L + ccbits + seg.data.bitLength(); - if (result > Integer.MAX_VALUE) + TotalBits += 4L + characterCountBits + segment.data.bitLength(); + if (TotalBits > Integer.MAX_VALUE) return -1; // The sum will overflow an int type } - return (int)result; + return (int)TotalBits; } - /*---- Constants ----*/ /** Describes precisely all strings that are encodable in numeric mode. To test whether a diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java b/java/src/main/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java index b125f18..50066e8 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java @@ -68,7 +68,7 @@ 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);