From a2ce2c4ac581fb1ba672ee4fdec20704efd046df Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Wed, 5 Apr 2017 11:48:52 +0800 Subject: [PATCH] made java implementation a separate library --- java/.gitignore | 26 ++ java/advanced/.gitignore | 1 + java/advanced/build.gradle | 3 + .../resources/qr_kanji_to_unicode.bin | Bin 0 -> 16384 bytes .../nayuki/qrcodegen/QrSegmentAdvanced.java | 313 ++++++++++++++ java/android/.gitignore | 1 + java/android/build.gradle | 18 + .../io/nayuki/qrcodegen/QrCodeAndroid.java | 34 ++ java/build.gradle | 28 ++ java/common/.gitignore | 1 + java/common/build.gradle | 0 .../src}/io/nayuki/qrcodegen/BitBuffer.java | 0 .../src}/io/nayuki/qrcodegen/QrCode.java | 96 +---- .../src}/io/nayuki/qrcodegen/QrSegment.java | 2 +- java/demo/.gitignore | 1 + java/demo/build.gradle | 4 + .../nayuki/qrcodegen/QrCodeGeneratorDemo.java | 143 +++++++ java/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54208 bytes java/gradle/wrapper/gradle-wrapper.properties | 6 + java/gradlew | 172 ++++++++ java/gradlew.bat | 84 ++++ .../nayuki/qrcodegen/QrCodeGeneratorDemo.java | 147 ------- .../nayuki/qrcodegen/QrSegmentAdvanced.java | 403 ------------------ java/javase/.gitignore | 1 + java/javase/build.gradle | 3 + .../src/io/nayuki/qrcodegen/QrCodeJavaSE.java | 32 ++ java/settings.gradle | 7 + java/svg/.gitignore | 1 + java/svg/build.gradle | 3 + .../io/nayuki/qrcodegen/QrCodeJavaSVG.java | 43 ++ 30 files changed, 947 insertions(+), 626 deletions(-) create mode 100644 java/.gitignore create mode 100644 java/advanced/.gitignore create mode 100644 java/advanced/build.gradle create mode 100644 java/advanced/resources/qr_kanji_to_unicode.bin create mode 100644 java/advanced/src/io/nayuki/qrcodegen/QrSegmentAdvanced.java create mode 100644 java/android/.gitignore create mode 100644 java/android/build.gradle create mode 100644 java/android/src/io/nayuki/qrcodegen/QrCodeAndroid.java create mode 100644 java/build.gradle create mode 100644 java/common/.gitignore create mode 100644 java/common/build.gradle rename java/{ => common/src}/io/nayuki/qrcodegen/BitBuffer.java (100%) rename java/{ => common/src}/io/nayuki/qrcodegen/QrCode.java (91%) rename java/{ => common/src}/io/nayuki/qrcodegen/QrSegment.java (99%) create mode 100644 java/demo/.gitignore create mode 100644 java/demo/build.gradle create mode 100644 java/demo/src/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java create mode 100644 java/gradle/wrapper/gradle-wrapper.jar create mode 100644 java/gradle/wrapper/gradle-wrapper.properties create mode 100755 java/gradlew create mode 100644 java/gradlew.bat delete mode 100644 java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java delete mode 100644 java/io/nayuki/qrcodegen/QrSegmentAdvanced.java create mode 100644 java/javase/.gitignore create mode 100644 java/javase/build.gradle create mode 100644 java/javase/src/io/nayuki/qrcodegen/QrCodeJavaSE.java create mode 100644 java/settings.gradle create mode 100644 java/svg/.gitignore create mode 100644 java/svg/build.gradle create mode 100644 java/svg/src/io/nayuki/qrcodegen/QrCodeJavaSVG.java diff --git a/java/.gitignore b/java/.gitignore new file mode 100644 index 0000000..e7058b2 --- /dev/null +++ b/java/.gitignore @@ -0,0 +1,26 @@ +# Built application files +/build +/classes + +# Local configuration file (sdk path, etc) +local.properties +gradle.properties + +# Gradle generated files +.gradle/ + +# Signing files +.signing/ + +# User-specific configurations +/.idea +*.iml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db \ No newline at end of file diff --git a/java/advanced/.gitignore b/java/advanced/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/java/advanced/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/java/advanced/build.gradle b/java/advanced/build.gradle new file mode 100644 index 0000000..52f6b41 --- /dev/null +++ b/java/advanced/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(':common') +} \ No newline at end of file diff --git a/java/advanced/resources/qr_kanji_to_unicode.bin b/java/advanced/resources/qr_kanji_to_unicode.bin new file mode 100644 index 0000000000000000000000000000000000000000..684f6a122958af3f6af67374c7ef311fb2347718 GIT binary patch literal 16384 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 G@ZSI(#5pej literal 0 HcmV?d00001 diff --git a/java/advanced/src/io/nayuki/qrcodegen/QrSegmentAdvanced.java b/java/advanced/src/io/nayuki/qrcodegen/QrSegmentAdvanced.java new file mode 100644 index 0000000..310e608 --- /dev/null +++ b/java/advanced/src/io/nayuki/qrcodegen/QrSegmentAdvanced.java @@ -0,0 +1,313 @@ +/* + * QR Code generator library - Optional advanced logic (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/qr-code-generator-library + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.qrcodegen; + +import io.nayuki.qrcodegen.QrSegment.Mode; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + + +public final class QrSegmentAdvanced { + + /*---- Optimal list of segments encoder ----*/ + + /** + * Returns a new mutable list of zero or more segments to represent the specified Unicode text string. + * The resulting list optimally minimizes the total encoded bit length, subjected to the constraints given + * by the specified {error correction level, minimum version number, maximum version number}, plus the additional + * constraint that the segment modes {NUMERIC, ALPHANUMERIC, BYTE} can be used but KANJI cannot be used. + *

This function can be viewed as a significantly more sophisticated and slower replacement + * for {@link QrSegment#makeSegments(String)}, but requiring more input parameters in a way + * that overlaps with {@link QrCode#encodeSegments(List, QrCode.Ecc, int, int, int, boolean)}.

+ * + * @param text the text to be encoded, which can be any Unicode string + * @param ecl the error correction level to use + * @param minVersion the minimum allowed version of the QR symbol (at least 1) + * @param maxVersion the maximum allowed version of the QR symbol (at most 40) + * @return a list of segments containing the text, minimizing the bit length with respect to the constraints + * @throws NullPointerException if the data or error correction level is {@code null} + * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 is violated, + * or if the data is too long to fit in a QR Code at maxVersion at the ECL + */ + public static List makeSegmentsOptimally(String text, QrCode.Ecc ecl, int minVersion, int maxVersion) { + // Check arguments + Objects.requireNonNull(text); + Objects.requireNonNull(ecl); + if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40)) + throw new IllegalArgumentException("Invalid value"); + + // Iterate through version numbers, and make tentative segments + List segs = null; + for (int version = minVersion; version <= maxVersion; version++) { + if (version == minVersion || version == 10 || version == 27) + segs = makeSegmentsOptimally(text, version); + + // Check if the segments fit + int dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; + int dataUsedBits = QrSegment.getTotalBits(segs, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + return segs; + } + throw new IllegalArgumentException("Data too long"); + } + + + // Returns a list of segments that is optimal for the given text at the given version number. + private static List makeSegmentsOptimally(String text, int version) { + byte[] data = text.getBytes(StandardCharsets.UTF_8); + int[][] bitCosts = computeBitCosts(data, version); + Mode[] charModes = computeCharacterModes(data, version, bitCosts); + return splitIntoSegments(data, charModes); + } + + + private static int[][] computeBitCosts(byte[] data, int version) { + // Segment header sizes, measured in 1/6 bits + int bytesCost = (4 + Mode.BYTE.numCharCountBits(version)) * 6; + int alphnumCost = (4 + Mode.ALPHANUMERIC.numCharCountBits(version)) * 6; + int numberCost = (4 + Mode.NUMERIC.numCharCountBits(version)) * 6; + + // result[mode][len] is the number of 1/6 bits to encode the first len characters of the text, ending in the mode + int[][] result = new int[3][data.length + 1]; + Arrays.fill(result[1], Integer.MAX_VALUE / 2); + Arrays.fill(result[2], Integer.MAX_VALUE / 2); + result[0][0] = bytesCost; + result[1][0] = alphnumCost; + result[2][0] = numberCost; + + // Calculate the cost table using dynamic programming + for (int i = 0; i < data.length; i++) { + // Encode a character + int j = i + 1; + char c = (char) data[i]; + result[0][j] = result[0][i] + 48; // 8 bits per byte + if (isAlphanumeric(c)) + result[1][j] = result[1][i] + 33; // 5.5 bits per alphanumeric char + if (isNumeric(c)) + result[2][j] = result[2][i] + 20; // 3.33 bits per digit + + // Switch modes, rounding up fractional bits + result[0][j] = Math.min((Math.min(result[1][j], result[2][j]) + 5) / 6 * 6 + bytesCost, result[0][j]); + result[1][j] = Math.min((Math.min(result[2][j], result[0][j]) + 5) / 6 * 6 + alphnumCost, result[1][j]); + result[2][j] = Math.min((Math.min(result[0][j], result[1][j]) + 5) / 6 * 6 + numberCost, result[2][j]); + } + return result; + } + + + private static Mode[] computeCharacterModes(byte[] data, int version, int[][] bitCosts) { + // Segment header sizes, measured in 1/6 bits + int bytesCost = (4 + Mode.BYTE.numCharCountBits(version)) * 6; + int alphnumCost = (4 + Mode.ALPHANUMERIC.numCharCountBits(version)) * 6; + int numberCost = (4 + Mode.NUMERIC.numCharCountBits(version)) * 6; + + // Infer the mode used for last character by taking the minimum + Mode curMode; + int end = bitCosts[0].length - 1; + if (bitCosts[0][end] <= Math.min(bitCosts[1][end], bitCosts[2][end])) + curMode = Mode.BYTE; + else if (bitCosts[1][end] <= bitCosts[2][end]) + curMode = Mode.ALPHANUMERIC; + else + curMode = Mode.NUMERIC; + + // Work backwards to calculate optimal encoding mode for each character + Mode[] result = new Mode[data.length]; + if (data.length == 0) + return result; + result[data.length - 1] = curMode; + for (int i = data.length - 2; i >= 0; i--) { + char c = (char) data[i]; + if (curMode == Mode.NUMERIC) { + if (isNumeric(c)) + curMode = Mode.NUMERIC; + else if (isAlphanumeric(c) && (bitCosts[1][i] + 33 + 5) / 6 * 6 + numberCost == bitCosts[2][i + 1]) + curMode = Mode.ALPHANUMERIC; + else + curMode = Mode.BYTE; + } else if (curMode == Mode.ALPHANUMERIC) { + if (isNumeric(c) && (bitCosts[2][i] + 20 + 5) / 6 * 6 + alphnumCost == bitCosts[1][i + 1]) + curMode = Mode.NUMERIC; + else if (isAlphanumeric(c)) + curMode = Mode.ALPHANUMERIC; + else + curMode = Mode.BYTE; + } else if (curMode == Mode.BYTE) { + if (isNumeric(c) && (bitCosts[2][i] + 20 + 5) / 6 * 6 + bytesCost == bitCosts[0][i + 1]) + curMode = Mode.NUMERIC; + else if (isAlphanumeric(c) && (bitCosts[1][i] + 33 + 5) / 6 * 6 + bytesCost == bitCosts[0][i + 1]) + curMode = Mode.ALPHANUMERIC; + else + curMode = Mode.BYTE; + } else + throw new AssertionError(); + result[i] = curMode; + } + return result; + } + + + private static List splitIntoSegments(byte[] data, Mode[] charModes) { + List result = new ArrayList<>(); + if (data.length == 0) + return result; + + // Accumulate run of modes + Mode curMode = charModes[0]; + int start = 0; + for (int i = 1; i < data.length; i++) { + if (charModes[i] != curMode) { + if (curMode == Mode.BYTE) + result.add(QrSegment.makeBytes(Arrays.copyOfRange(data, start, i))); + else { + String temp = new String(data, start, i - start, StandardCharsets.US_ASCII); + if (curMode == Mode.NUMERIC) + result.add(QrSegment.makeNumeric(temp)); + else if (curMode == Mode.ALPHANUMERIC) + result.add(QrSegment.makeAlphanumeric(temp)); + else + throw new AssertionError(); + } + curMode = charModes[i]; + start = i; + } + } + + // Final segment + if (curMode == Mode.BYTE) + result.add(QrSegment.makeBytes(Arrays.copyOfRange(data, start, data.length))); + else { + String temp = new String(data, start, data.length - start, StandardCharsets.US_ASCII); + if (curMode == Mode.NUMERIC) + result.add(QrSegment.makeNumeric(temp)); + else if (curMode == Mode.ALPHANUMERIC) + result.add(QrSegment.makeAlphanumeric(temp)); + else + throw new AssertionError(); + } + return result; + } + + + private static boolean isAlphanumeric(char c) { + return isNumeric(c) || 'A' <= c && c <= 'Z' || " $%*+./:-".indexOf(c) != -1; + } + + private static boolean isNumeric(char c) { + return '0' <= c && c <= '9'; + } + + + /*---- Kanji mode segment encoder ----*/ + + /** + * Returns a segment representing the specified string encoded in kanji mode. + *

Note that broadly speaking, the set of encodable characters are {kanji used in Japan, hiragana, katakana, + * Asian punctuation, full-width ASCII}.
+ * In particular, non-encodable characters are {normal ASCII, half-width katakana, more extensive Chinese hanzi}. + * + * @param text the text to be encoded, which must fall in the kanji mode subset of characters + * @return a segment containing the data + * @throws NullPointerException if the string is {@code null} + * @throws IllegalArgumentException if the string contains non-kanji-mode characters + * @see #isEncodableAsKanji(String) + */ + public static QrSegment makeKanjiSegment(String text) { + Objects.requireNonNull(text); + BitBuffer bb = new BitBuffer(); + for (int i = 0; i < text.length(); i++) { + int val = UNICODE_TO_QR_KANJI[text.charAt(i)]; + if (val == -1) + throw new IllegalArgumentException("String contains non-kanji-mode characters"); + bb.appendBits(val, 13); + } + return new QrSegment(Mode.KANJI, text.length(), bb.getBytes(), bb.bitLength()); + } + + + /** + * Tests whether the specified text string can be encoded as a segment in kanji mode. + *

Note that broadly speaking, the set of encodable characters are {kanji used in Japan, hiragana, katakana, + * Asian punctuation, full-width ASCII}.
+ * In particular, non-encodable characters are {normal ASCII, half-width katakana, more extensive Chinese hanzi}. + * + * @param text the string to test for encodability + * @return {@code true} if and only if the string can be encoded in kanji mode + * @throws NullPointerException if the string is {@code null} + * @see #makeKanjiSegment(String) + */ + public static boolean isEncodableAsKanji(String text) { + Objects.requireNonNull(text); + for (int i = 0; i < text.length(); i++) { + if (UNICODE_TO_QR_KANJI[text.charAt(i)] == -1) + return false; + } + return true; + } + + + private static short[] UNICODE_TO_QR_KANJI = new short[65536]; + + static { // Unpack the Shift JIS table into a more computation-friendly form + Arrays.fill(UNICODE_TO_QR_KANJI, (short) -1); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + InputStream is = null; + try { + is = QrSegmentAdvanced.class.getResourceAsStream("qr_kanji_to_unicode.bin"); + byte[] buffer = new byte[8192]; + int read; + while ((read = is.read(buffer)) != -1) { + os.write(buffer, 0, read); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + // Ignore + } + } + final byte[] bytes = os.toByteArray(); + for (int i = 0; i < bytes.length; i += 2) { + int j = ((bytes[i] & 0xFF) << 8) | (bytes[i + 1] & 0xFF); + if (j == 0xFFFF) + continue; + if (UNICODE_TO_QR_KANJI[j] != -1) + throw new AssertionError(); + UNICODE_TO_QR_KANJI[j] = (short) (i / 2); + } + } + +} diff --git a/java/android/.gitignore b/java/android/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/java/android/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/java/android/build.gradle b/java/android/build.gradle new file mode 100644 index 0000000..e357a96 --- /dev/null +++ b/java/android/build.gradle @@ -0,0 +1,18 @@ +sourceSets { + main { + java { + srcDirs = ['src'] + } + } +} + +configurations { providedCompile } + +sourceSets.main.compileClasspath += configurations.providedCompile +sourceSets.test.compileClasspath += configurations.providedCompile +sourceSets.test.runtimeClasspath += configurations.providedCompile + +dependencies { + compile project(':common') + providedCompile 'com.google.android:android:4.1.1.4' +} \ No newline at end of file diff --git a/java/android/src/io/nayuki/qrcodegen/QrCodeAndroid.java b/java/android/src/io/nayuki/qrcodegen/QrCodeAndroid.java new file mode 100644 index 0000000..a6ea001 --- /dev/null +++ b/java/android/src/io/nayuki/qrcodegen/QrCodeAndroid.java @@ -0,0 +1,34 @@ +package io.nayuki.qrcodegen; + +import android.graphics.Bitmap; + + +public class QrCodeAndroid { + + /** + * Returns a new bitmap object representing this QR Code, with the specified module scale and number + * of border modules. For example, the arguments scale=10, border=4 means to pad the QR Code symbol + * with 4 white border modules on all four edges, then use 10*10 pixels to represent each module. + * The resulting image only contains the hex colors FF000000 and FFFFFFFF. + * + * @param scale the module scale factor, which must be positive + * @param border the number of border modules to add, which must be non-negative + * @return a bitmap representing this QR Code, with padding and scaling + * @throws IllegalArgumentException if the scale or border is out of range + */ + public Bitmap toBitmap(QrCode qrCode, int scale, int border, Bitmap.Config config) { + if (scale <= 0 || border < 0) + throw new IllegalArgumentException("Value out of range"); + final int size = qrCode.size; + Bitmap result = Bitmap.createBitmap((size + border * 2) * scale, (size + border * 2) * scale, + config); + for (int y = 0; y < result.getHeight(); y++) { + for (int x = 0; x < result.getWidth(); x++) { + int val = qrCode.getModule(x / scale - border, y / scale - border); // 0 or 1 + result.setPixel(x, y, val == 1 ? 0xFF000000 : 0xFFFFFFFF); + } + } + return result; + } + +} diff --git a/java/build.gradle b/java/build.gradle new file mode 100644 index 0000000..268211f --- /dev/null +++ b/java/build.gradle @@ -0,0 +1,28 @@ +subprojects { + apply plugin: 'maven' + apply plugin: 'java' + + group 'com.github.nayuki.QR-Code-generator' + version '1.0' + + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + + sourceSets { + main { + java { + srcDirs = ['src'] + } + resources { + srcDirs = ['resources'] + } + } + } + + repositories { + mavenLocal() + jcenter() + maven { url 'https://jitpack.io/' } + } + +} \ No newline at end of file diff --git a/java/common/.gitignore b/java/common/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/java/common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/java/common/build.gradle b/java/common/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/java/io/nayuki/qrcodegen/BitBuffer.java b/java/common/src/io/nayuki/qrcodegen/BitBuffer.java similarity index 100% rename from java/io/nayuki/qrcodegen/BitBuffer.java rename to java/common/src/io/nayuki/qrcodegen/BitBuffer.java diff --git a/java/io/nayuki/qrcodegen/QrCode.java b/java/common/src/io/nayuki/qrcodegen/QrCode.java similarity index 91% rename from java/io/nayuki/qrcodegen/QrCode.java rename to java/common/src/io/nayuki/qrcodegen/QrCode.java index 4642fe2..1b715d7 100644 --- a/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/common/src/io/nayuki/qrcodegen/QrCode.java @@ -24,11 +24,10 @@ package io.nayuki.qrcodegen; -import java.awt.image.BufferedImage; import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Objects; - +import java.util.Locale; /** * Represents an immutable square grid of black and white cells for a QR Code symbol, and @@ -52,8 +51,8 @@ public final class QrCode { * @throws IllegalArgumentException if the text fails to fit in the largest version QR Code, which means it is too long */ public static QrCode encodeText(String text, Ecc ecl) { - Objects.requireNonNull(text); - Objects.requireNonNull(ecl); + requireNonNull(text); + requireNonNull(ecl); List segs = QrSegment.makeSegments(text); return encodeSegments(segs, ecl); } @@ -71,10 +70,10 @@ public final class QrCode { * @throws IllegalArgumentException if the data fails to fit in the largest version QR Code, which means it is too long */ public static QrCode encodeBinary(byte[] data, Ecc ecl) { - Objects.requireNonNull(data); - Objects.requireNonNull(ecl); + requireNonNull(data); + requireNonNull(ecl); QrSegment seg = QrSegment.makeBytes(data); - return encodeSegments(Arrays.asList(seg), ecl); + return encodeSegments(Collections.singletonList(seg), ecl); } @@ -113,8 +112,8 @@ public final class QrCode { * < −1 or mask > 7, or if the data is too long to fit in a QR Code at maxVersion at the ECL */ public static QrCode encodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) { - Objects.requireNonNull(segs); - Objects.requireNonNull(ecl); + requireNonNull(segs); + requireNonNull(ecl); if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7) throw new IllegalArgumentException("Invalid value"); @@ -202,10 +201,10 @@ public final class QrCode { */ public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { // Check arguments - Objects.requireNonNull(ecl); + requireNonNull(ecl); if (ver < 1 || ver > 40 || mask < -1 || mask > 7) throw new IllegalArgumentException("Value out of range"); - Objects.requireNonNull(dataCodewords); + requireNonNull(dataCodewords); // Initialize fields version = ver; @@ -233,7 +232,7 @@ public final class QrCode { */ public QrCode(QrCode qr, int mask) { // Check arguments - Objects.requireNonNull(qr); + requireNonNull(qr); if (mask < -1 || mask > 7) throw new IllegalArgumentException("Mask value out of range"); @@ -271,66 +270,7 @@ public final class QrCode { return 0; // Infinite white border } - - /** - * Returns a new image object representing this QR Code, with the specified module scale and number - * of border modules. For example, the arguments scale=10, border=4 means to pad the QR Code symbol - * with 4 white border modules on all four edges, then use 10*10 pixels to represent each module. - * The resulting image only contains the hex colors 000000 and FFFFFF. - * @param scale the module scale factor, which must be positive - * @param border the number of border modules to add, which must be non-negative - * @return an image representing this QR Code, with padding and scaling - * @throws IllegalArgumentException if the scale or border is out of range - */ - public BufferedImage toImage(int scale, int border) { - if (scale <= 0 || border < 0) - throw new IllegalArgumentException("Value out of range"); - BufferedImage result = new BufferedImage((size + border * 2) * scale, (size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); - for (int y = 0; y < result.getHeight(); y++) { - for (int x = 0; x < result.getWidth(); x++) { - int val = getModule(x / scale - border, y / scale - border); // 0 or 1 - result.setRGB(x, y, val == 1 ? 0x000000 : 0xFFFFFF); - } - } - return result; - } - - - /** - * Based on the specified number of border modules to add as padding, this returns a - * string whose contents represents an SVG XML file that depicts this QR Code symbol. - * Note that Unix newlines (\n) are always used, regardless of the platform. - * @param border the number of border modules to add, which must be non-negative - * @return a string representing this QR Code as an SVG document - */ - public String toSvgString(int border) { - if (border < 0) - throw new IllegalArgumentException("Border must be non-negative"); - StringBuilder sb = new StringBuilder(); - sb.append("\n"); - sb.append("\n"); - sb.append(String.format("\n", size + border * 2)); - sb.append("\t\n"); - sb.append("\t\n"); - sb.append("\n"); - return sb.toString(); - } - - - + /*---- Private helper methods for constructor: Drawing function modules ----*/ private void drawFunctionPatterns() { @@ -495,7 +435,7 @@ public final class QrCode { // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire // data area of this QR Code symbol. Function modules need to be marked off before this is called. private void drawCodewords(byte[] data) { - Objects.requireNonNull(data); + requireNonNull(data); if (data.length != getNumRawDataModules(version) / 8) throw new IllegalArgumentException(); @@ -829,7 +769,7 @@ public final class QrCode { * @throws NullPointerException if the data is {@code null} */ public byte[] getRemainder(byte[] data) { - Objects.requireNonNull(data); + requireNonNull(data); // Compute the remainder by performing polynomial division byte[] result = new byte[coefficients.length]; @@ -863,5 +803,11 @@ public final class QrCode { } } + + private static T requireNonNull(T obj) { + if (obj == null) + throw new NullPointerException(); + return obj; + } } diff --git a/java/io/nayuki/qrcodegen/QrSegment.java b/java/common/src/io/nayuki/qrcodegen/QrSegment.java similarity index 99% rename from java/io/nayuki/qrcodegen/QrSegment.java rename to java/common/src/io/nayuki/qrcodegen/QrSegment.java index 59aa5df..3d76979 100644 --- a/java/io/nayuki/qrcodegen/QrSegment.java +++ b/java/common/src/io/nayuki/qrcodegen/QrSegment.java @@ -138,7 +138,7 @@ public final class QrSegment { /** The length of this segment's unencoded data, measured in characters. Always zero or positive. */ public final int numChars; - /** The bits of this segment packed into a byte array in big endian. Accessed through {@link getByte(int)}. Not {@code null}. */ + /** The bits of this segment packed into a byte array in big endian. Accessed through {@link #getByte(int)}. Not {@code null}. */ private final byte[] data; /** The length of this segment's encoded data, measured in bits. Satisfies 0 ≤ {@code bitLength} ≤ {@code data.length} × 8. */ diff --git a/java/demo/.gitignore b/java/demo/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/java/demo/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/java/demo/build.gradle b/java/demo/build.gradle new file mode 100644 index 0000000..f98880e --- /dev/null +++ b/java/demo/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(':common') + compile project(':javase') +} \ No newline at end of file diff --git a/java/demo/src/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java b/java/demo/src/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java new file mode 100644 index 0000000..aaff944 --- /dev/null +++ b/java/demo/src/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java @@ -0,0 +1,143 @@ +/* + * QR Code generator demo (Java) + * + * Run this command-line program with no arguments. The program creates/overwrites a bunch of + * PNG and SVG files in the current working directory to demonstrate the creation of QR Codes. + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/qr-code-generator-library + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.qrcodegen; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + + +public final class QrCodeGeneratorDemo { + + // The main application program. + public static void main(String[] args) throws IOException { + doBasicDemo(); + doVarietyDemo(); + doSegmentDemo(); + } + + + // Creates a single QR Code, then writes it to a PNG file and an SVG file. + private static void doBasicDemo() throws IOException { + String text = "Hello, world!"; // User-supplied Unicode text + QrCode.Ecc errCorLvl = QrCode.Ecc.LOW; // Error correction level + + QrCode qr = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol + + BufferedImage img = QrCodeJavaSE.toImage(qr, 10, 4); // Convert to bitmap image + File imgFile = new File("hello-world-QR.png"); // File path for output + ImageIO.write(img, "png", imgFile); // Write image to file + + String svg = qr.toSvgString(4); // Convert to SVG XML code + try (Writer out = new OutputStreamWriter( + new FileOutputStream("hello-world-QR.svg"), + StandardCharsets.UTF_8)) { + out.write(svg); // Create/overwrite file and write SVG data + } + } + + + // Creates a variety of QR Codes that exercise different features of the library, and writes each one to file. + private static void doVarietyDemo() throws IOException { + QrCode qr; + + // Project Nayuki URL + qr = QrCode.encodeText("https://www.nayuki.io/", QrCode.Ecc.HIGH); + qr = new QrCode(qr, 3); // Change mask, forcing to mask #3 + writePng(QrCodeJavaSE.toImage(qr, 8, 6), "project-nayuki-QR.png"); + + // Numeric mode encoding (3.33 bits per digit) + qr = QrCode.encodeText("314159265358979323846264338327950288419716939937510", QrCode.Ecc.MEDIUM); + writePng(QrCodeJavaSE.toImage(qr, 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(QrCodeJavaSE.toImage(qr, 10, 2), "alphanumeric-QR.png"); + + // Unicode text as UTF-8, and different masks + qr = QrCode.encodeText("こんにちwa、世界! αβγδ", QrCode.Ecc.QUARTILE); + writePng(QrCodeJavaSE.toImage(new QrCode(qr, 0), 10, 3), "unicode-mask0-QR.png"); + writePng(QrCodeJavaSE.toImage(new QrCode(qr, 1), 10, 3), "unicode-mask1-QR.png"); + writePng(QrCodeJavaSE.toImage(new QrCode(qr, 5), 10, 3), "unicode-mask5-QR.png"); + writePng(QrCodeJavaSE.toImage(new QrCode(qr, 7), 10, 3), "unicode-mask7-QR.png"); + + // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) + qr = QrCode.encodeText( + "Alice was beginning to get very tired of sitting by her sister on the bank, " + + "and of having nothing to do: once or twice she had peeped into the book her sister was reading, " + + "but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice " + + "'without pictures or conversations?' So she was considering in her own mind (as well as she could, " + + "for the hot day made her feel very sleepy and stupid), whether the pleasure of making a " + + "daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly " + + "a White Rabbit with pink eyes ran close by her.", QrCode.Ecc.HIGH); + writePng(QrCodeJavaSE.toImage(qr, 6, 10), "alice-wonderland-QR.png"); + } + + + // Creates QR Codes with manually specified segments for better compactness. + private static void doSegmentDemo() throws IOException { + QrCode qr; + List segs; + + // Illustration "silver" + String silver0 = "THE SQUARE ROOT OF 2 IS 1."; + String silver1 = "41421356237309504880168872420969807856967187537694807317667973799"; + qr = QrCode.encodeText(silver0 + silver1, QrCode.Ecc.LOW); + writePng(QrCodeJavaSE.toImage(qr, 10, 3), "sqrt2-monolithic-QR.png"); + + segs = Arrays.asList( + QrSegment.makeAlphanumeric(silver0), + QrSegment.makeNumeric(silver1)); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); + writePng(QrCodeJavaSE.toImage(qr, 10, 3), "sqrt2-segmented-QR.png"); + + // Illustration "golden" + String golden0 = "Golden ratio φ = 1."; + String golden1 = "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374"; + String golden2 = "......"; + qr = QrCode.encodeText(golden0 + golden1 + golden2, QrCode.Ecc.LOW); + writePng(QrCodeJavaSE.toImage(qr, 8, 5), "phi-monolithic-QR.png"); + + segs = Arrays.asList( + QrSegment.makeBytes(golden0.getBytes(StandardCharsets.UTF_8)), + QrSegment.makeNumeric(golden1), + QrSegment.makeAlphanumeric(golden2)); + qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); + writePng(QrCodeJavaSE.toImage(qr, 8, 5), "phi-segmented-QR.png"); + } + + + // Helper function to reduce code duplication. + private static void writePng(BufferedImage img, String filepath) throws IOException { + ImageIO.write(img, "png", new File(filepath)); + } + +} diff --git a/java/gradle/wrapper/gradle-wrapper.jar b/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cbc462c0626b0db1f2325b1879bb0829846af4bc GIT binary patch literal 54208 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ℑGy$n4v0n?3VhZw6gc%P(Y|EU=MIw|D-A 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 z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/java/gradlew.bat b/java/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/java/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java b/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java deleted file mode 100644 index 509a126..0000000 --- a/java/io/nayuki/qrcodegen/QrCodeGeneratorDemo.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * QR Code generator demo (Java) - * - * Run this command-line program with no arguments. The program creates/overwrites a bunch of - * PNG and SVG files in the current working directory to demonstrate the creation of QR Codes. - * - * Copyright (c) Project Nayuki - * https://www.nayuki.io/page/qr-code-generator-library - * - * (MIT License) - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -package io.nayuki.qrcodegen; - -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; -import javax.imageio.ImageIO; - - -public final class QrCodeGeneratorDemo { - - // The main application program. - public static void main(String[] args) throws IOException { - doBasicDemo(); - doVarietyDemo(); - doSegmentDemo(); - } - - - // Creates a single QR Code, then writes it to a PNG file and an SVG file. - private static void doBasicDemo() throws IOException { - String text = "Hello, world!"; // User-supplied Unicode text - QrCode.Ecc errCorLvl = QrCode.Ecc.LOW; // Error correction level - - QrCode qr = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol - - BufferedImage img = qr.toImage(10, 4); // Convert to bitmap image - File imgFile = new File("hello-world-QR.png"); // File path for output - ImageIO.write(img, "png", imgFile); // Write image to file - - String svg = qr.toSvgString(4); // Convert to SVG XML code - try (Writer out = new OutputStreamWriter( - new FileOutputStream("hello-world-QR.svg"), - StandardCharsets.UTF_8)) { - out.write(svg); // Create/overwrite file and write SVG data - } - } - - - // Creates a variety of QR Codes that exercise different features of the library, and writes each one to file. - private static void doVarietyDemo() throws IOException { - QrCode qr; - - // Project Nayuki URL - qr = QrCode.encodeText("https://www.nayuki.io/", QrCode.Ecc.HIGH); - qr = new QrCode(qr, 3); // Change mask, forcing to mask #3 - writePng(qr.toImage(8, 6), "project-nayuki-QR.png"); - - // Numeric mode encoding (3.33 bits per digit) - qr = QrCode.encodeText("314159265358979323846264338327950288419716939937510", QrCode.Ecc.MEDIUM); - writePng(qr.toImage(13, 1), "pi-digits-QR.png"); - - // Alphanumeric mode encoding (5.5 bits per character) - qr = QrCode.encodeText("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", QrCode.Ecc.HIGH); - writePng(qr.toImage(10, 2), "alphanumeric-QR.png"); - - // Unicode text as UTF-8, and different masks - qr = QrCode.encodeText("こんにちwa、世界! αβγδ", QrCode.Ecc.QUARTILE); - writePng(new QrCode(qr, 0).toImage(10, 3), "unicode-mask0-QR.png"); - writePng(new QrCode(qr, 1).toImage(10, 3), "unicode-mask1-QR.png"); - writePng(new QrCode(qr, 5).toImage(10, 3), "unicode-mask5-QR.png"); - writePng(new QrCode(qr, 7).toImage(10, 3), "unicode-mask7-QR.png"); - - // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) - qr = QrCode.encodeText( - "Alice was beginning to get very tired of sitting by her sister on the bank, " - + "and of having nothing to do: once or twice she had peeped into the book her sister was reading, " - + "but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice " - + "'without pictures or conversations?' So she was considering in her own mind (as well as she could, " - + "for the hot day made her feel very sleepy and stupid), whether the pleasure of making a " - + "daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly " - + "a White Rabbit with pink eyes ran close by her.", QrCode.Ecc.HIGH); - writePng(qr.toImage(6, 10), "alice-wonderland-QR.png"); - } - - - // Creates QR Codes with manually specified segments for better compactness. - private static void doSegmentDemo() throws IOException { - QrCode qr; - List segs; - - // Illustration "silver" - String silver0 = "THE SQUARE ROOT OF 2 IS 1."; - String silver1 = "41421356237309504880168872420969807856967187537694807317667973799"; - qr = QrCode.encodeText(silver0 + silver1, QrCode.Ecc.LOW); - writePng(qr.toImage(10, 3), "sqrt2-monolithic-QR.png"); - - segs = Arrays.asList( - QrSegment.makeAlphanumeric(silver0), - QrSegment.makeNumeric(silver1)); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); - writePng(qr.toImage(10, 3), "sqrt2-segmented-QR.png"); - - // Illustration "golden" - String golden0 = "Golden ratio φ = 1."; - String golden1 = "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374"; - String golden2 = "......"; - qr = QrCode.encodeText(golden0 + golden1 + golden2, QrCode.Ecc.LOW); - writePng(qr.toImage(8, 5), "phi-monolithic-QR.png"); - - segs = Arrays.asList( - QrSegment.makeBytes(golden0.getBytes(StandardCharsets.UTF_8)), - QrSegment.makeNumeric(golden1), - QrSegment.makeAlphanumeric(golden2)); - qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW); - writePng(qr.toImage(8, 5), "phi-segmented-QR.png"); - } - - - // Helper function to reduce code duplication. - private static void writePng(BufferedImage img, String filepath) throws IOException { - ImageIO.write(img, "png", new File(filepath)); - } - -} diff --git a/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java b/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java deleted file mode 100644 index 8a65084..0000000 --- a/java/io/nayuki/qrcodegen/QrSegmentAdvanced.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * QR Code generator library - Optional advanced logic (Java) - * - * Copyright (c) Project Nayuki - * https://www.nayuki.io/page/qr-code-generator-library - * - * (MIT License) - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -package io.nayuki.qrcodegen; - -import static io.nayuki.qrcodegen.QrSegment.Mode.ALPHANUMERIC; -import static io.nayuki.qrcodegen.QrSegment.Mode.BYTE; -import static io.nayuki.qrcodegen.QrSegment.Mode.NUMERIC; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; -import java.util.Objects; - - -public final class QrSegmentAdvanced { - - /*---- Optimal list of segments encoder ----*/ - - /** - * Returns a new mutable list of zero or more segments to represent the specified Unicode text string. - * The resulting list optimally minimizes the total encoded bit length, subjected to the constraints given - * by the specified {error correction level, minimum version number, maximum version number}, plus the additional - * constraint that the segment modes {NUMERIC, ALPHANUMERIC, BYTE} can be used but KANJI cannot be used. - *

This function can be viewed as a significantly more sophisticated and slower replacement - * for {@link QrSegment#makeSegments(String)}, but requiring more input parameters in a way - * that overlaps with {@link QrCode#encodeSegments(List,QrCode.Ecc,int,int,int,boolean)}.

- * @param text the text to be encoded, which can be any Unicode string - * @param ecl the error correction level to use - * @param minVersion the minimum allowed version of the QR symbol (at least 1) - * @param maxVersion the maximum allowed version of the QR symbol (at most 40) - * @return a list of segments containing the text, minimizing the bit length with respect to the constraints - * @throws NullPointerException if the data or error correction level is {@code null} - * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 is violated, - * or if the data is too long to fit in a QR Code at maxVersion at the ECL - */ - public static List makeSegmentsOptimally(String text, QrCode.Ecc ecl, int minVersion, int maxVersion) { - // Check arguments - Objects.requireNonNull(text); - Objects.requireNonNull(ecl); - if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40)) - throw new IllegalArgumentException("Invalid value"); - - // Iterate through version numbers, and make tentative segments - List segs = null; - for (int version = minVersion; version <= maxVersion; version++) { - if (version == minVersion || version == 10 || version == 27) - segs = makeSegmentsOptimally(text, version); - - // Check if the segments fit - int dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; - int dataUsedBits = QrSegment.getTotalBits(segs, version); - if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) - return segs; - } - throw new IllegalArgumentException("Data too long"); - } - - - // Returns a list of segments that is optimal for the given text at the given version number. - private static List makeSegmentsOptimally(String text, int version) { - byte[] data = text.getBytes(StandardCharsets.UTF_8); - int[][] bitCosts = computeBitCosts(data, version); - QrSegment.Mode[] charModes = computeCharacterModes(data, version, bitCosts); - return splitIntoSegments(data, charModes); - } - - - private static int[][] computeBitCosts(byte[] data, int version) { - // Segment header sizes, measured in 1/6 bits - int bytesCost = (4 + BYTE .numCharCountBits(version)) * 6; - int alphnumCost = (4 + ALPHANUMERIC.numCharCountBits(version)) * 6; - int numberCost = (4 + NUMERIC .numCharCountBits(version)) * 6; - - // result[mode][len] is the number of 1/6 bits to encode the first len characters of the text, ending in the mode - int[][] result = new int[3][data.length + 1]; - Arrays.fill(result[1], Integer.MAX_VALUE / 2); - Arrays.fill(result[2], Integer.MAX_VALUE / 2); - result[0][0] = bytesCost; - result[1][0] = alphnumCost; - result[2][0] = numberCost; - - // Calculate the cost table using dynamic programming - for (int i = 0; i < data.length; i++) { - // Encode a character - int j = i + 1; - char c = (char)data[i]; - result[0][j] = result[0][i] + 48; // 8 bits per byte - if (isAlphanumeric(c)) - result[1][j] = result[1][i] + 33; // 5.5 bits per alphanumeric char - if (isNumeric(c)) - result[2][j] = result[2][i] + 20; // 3.33 bits per digit - - // Switch modes, rounding up fractional bits - result[0][j] = Math.min((Math.min(result[1][j], result[2][j]) + 5) / 6 * 6 + bytesCost , result[0][j]); - result[1][j] = Math.min((Math.min(result[2][j], result[0][j]) + 5) / 6 * 6 + alphnumCost, result[1][j]); - result[2][j] = Math.min((Math.min(result[0][j], result[1][j]) + 5) / 6 * 6 + numberCost , result[2][j]); - } - return result; - } - - - private static QrSegment.Mode[] computeCharacterModes(byte[] data, int version, int[][] bitCosts) { - // Segment header sizes, measured in 1/6 bits - int bytesCost = (4 + BYTE .numCharCountBits(version)) * 6; - int alphnumCost = (4 + ALPHANUMERIC.numCharCountBits(version)) * 6; - int numberCost = (4 + NUMERIC .numCharCountBits(version)) * 6; - - // Infer the mode used for last character by taking the minimum - QrSegment.Mode curMode; - int end = bitCosts[0].length - 1; - if (bitCosts[0][end] <= Math.min(bitCosts[1][end], bitCosts[2][end])) - curMode = BYTE; - else if (bitCosts[1][end] <= bitCosts[2][end]) - curMode = ALPHANUMERIC; - else - curMode = NUMERIC; - - // Work backwards to calculate optimal encoding mode for each character - QrSegment.Mode[] result = new QrSegment.Mode[data.length]; - if (data.length == 0) - return result; - result[data.length - 1] = curMode; - for (int i = data.length - 2; i >= 0; i--) { - char c = (char)data[i]; - if (curMode == NUMERIC) { - if (isNumeric(c)) - curMode = NUMERIC; - else if (isAlphanumeric(c) && (bitCosts[1][i] + 33 + 5) / 6 * 6 + numberCost == bitCosts[2][i + 1]) - curMode = ALPHANUMERIC; - else - curMode = BYTE; - } else if (curMode == ALPHANUMERIC) { - if (isNumeric(c) && (bitCosts[2][i] + 20 + 5) / 6 * 6 + alphnumCost == bitCosts[1][i + 1]) - curMode = NUMERIC; - else if (isAlphanumeric(c)) - curMode = ALPHANUMERIC; - else - curMode = BYTE; - } else if (curMode == BYTE) { - if (isNumeric(c) && (bitCosts[2][i] + 20 + 5) / 6 * 6 + bytesCost == bitCosts[0][i + 1]) - curMode = NUMERIC; - else if (isAlphanumeric(c) && (bitCosts[1][i] + 33 + 5) / 6 * 6 + bytesCost == bitCosts[0][i + 1]) - curMode = ALPHANUMERIC; - else - curMode = BYTE; - } else - throw new AssertionError(); - result[i] = curMode; - } - return result; - } - - - private static List splitIntoSegments(byte[] data, QrSegment.Mode[] charModes) { - List result = new ArrayList<>(); - if (data.length == 0) - return result; - - // Accumulate run of modes - QrSegment.Mode curMode = charModes[0]; - int start = 0; - for (int i = 1; i < data.length; i++) { - if (charModes[i] != curMode) { - if (curMode == BYTE) - result.add(QrSegment.makeBytes(Arrays.copyOfRange(data, start, i))); - else { - String temp = new String(data, start, i - start, StandardCharsets.US_ASCII); - if (curMode == NUMERIC) - result.add(QrSegment.makeNumeric(temp)); - else if (curMode == ALPHANUMERIC) - result.add(QrSegment.makeAlphanumeric(temp)); - else - throw new AssertionError(); - } - curMode = charModes[i]; - start = i; - } - } - - // Final segment - if (curMode == BYTE) - result.add(QrSegment.makeBytes(Arrays.copyOfRange(data, start, data.length))); - else { - String temp = new String(data, start, data.length - start, StandardCharsets.US_ASCII); - if (curMode == NUMERIC) - result.add(QrSegment.makeNumeric(temp)); - else if (curMode == ALPHANUMERIC) - result.add(QrSegment.makeAlphanumeric(temp)); - else - throw new AssertionError(); - } - return result; - } - - - private static boolean isAlphanumeric(char c) { - return isNumeric(c) || 'A' <= c && c <= 'Z' || " $%*+./:-".indexOf(c) != -1; - } - - private static boolean isNumeric(char c) { - return '0' <= c && c <= '9'; - } - - - /*---- Kanji mode segment encoder ----*/ - - /** - * Returns a segment representing the specified string encoded in kanji mode. - *

Note that broadly speaking, the set of encodable characters are {kanji used in Japan, hiragana, katakana, - * Asian punctuation, full-width ASCII}.
- * In particular, non-encodable characters are {normal ASCII, half-width katakana, more extensive Chinese hanzi}. - * @param text the text to be encoded, which must fall in the kanji mode subset of characters - * @return a segment containing the data - * @throws NullPointerException if the string is {@code null} - * @throws IllegalArgumentException if the string contains non-kanji-mode characters - * @see #isEncodableAsKanji(String) - */ - public static QrSegment makeKanjiSegment(String text) { - Objects.requireNonNull(text); - BitBuffer bb = new BitBuffer(); - for (int i = 0; i < text.length(); i++) { - int val = UNICODE_TO_QR_KANJI[text.charAt(i)]; - if (val == -1) - throw new IllegalArgumentException("String contains non-kanji-mode characters"); - bb.appendBits(val, 13); - } - return new QrSegment(QrSegment.Mode.KANJI, text.length(), bb.getBytes(), bb.bitLength()); - } - - - /** - * Tests whether the specified text string can be encoded as a segment in kanji mode. - *

Note that broadly speaking, the set of encodable characters are {kanji used in Japan, hiragana, katakana, - * Asian punctuation, full-width ASCII}.
- * In particular, non-encodable characters are {normal ASCII, half-width katakana, more extensive Chinese hanzi}. - * @param text the string to test for encodability - * @return {@code true} if and only if the string can be encoded in kanji mode - * @throws NullPointerException if the string is {@code null} - * @see #makeKanjiSegment(String) - */ - public static boolean isEncodableAsKanji(String text) { - Objects.requireNonNull(text); - for (int i = 0; i < text.length(); i++) { - if (UNICODE_TO_QR_KANJI[text.charAt(i)] == -1) - return false; - } - return true; - } - - - // 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=="; - - - private static short[] UNICODE_TO_QR_KANJI = new short[65536]; - - static { // Unpack the Shift JIS table into a more computation-friendly form - 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) { - int j = ((bytes[i] & 0xFF) << 8) | (bytes[i + 1] & 0xFF); - if (j == 0xFFFF) - continue; - if (UNICODE_TO_QR_KANJI[j] != -1) - throw new AssertionError(); - UNICODE_TO_QR_KANJI[j] = (short)(i / 2); - } - } - -} diff --git a/java/javase/.gitignore b/java/javase/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/java/javase/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/java/javase/build.gradle b/java/javase/build.gradle new file mode 100644 index 0000000..52f6b41 --- /dev/null +++ b/java/javase/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(':common') +} \ No newline at end of file diff --git a/java/javase/src/io/nayuki/qrcodegen/QrCodeJavaSE.java b/java/javase/src/io/nayuki/qrcodegen/QrCodeJavaSE.java new file mode 100644 index 0000000..d2691e9 --- /dev/null +++ b/java/javase/src/io/nayuki/qrcodegen/QrCodeJavaSE.java @@ -0,0 +1,32 @@ +package io.nayuki.qrcodegen; + +import java.awt.image.BufferedImage; + +public class QrCodeJavaSE { + + /** + * Returns a new image object representing this QR Code, with the specified module scale and number + * of border modules. For example, the arguments scale=10, border=4 means to pad the QR Code symbol + * with 4 white border modules on all four edges, then use 10*10 pixels to represent each module. + * The resulting image only contains the hex colors 000000 and FFFFFF. + * + * @param scale the module scale factor, which must be positive + * @param border the number of border modules to add, which must be non-negative + * @return an image representing this QR Code, with padding and scaling + * @throws IllegalArgumentException if the scale or border is out of range + */ + public static BufferedImage toImage(QrCode qrCode, int scale, int border) { + if (scale <= 0 || border < 0) + throw new IllegalArgumentException("Value out of range"); + final int size = qrCode.size; + BufferedImage result = new BufferedImage((size + border * 2) * scale, (size + border * 2) * scale, BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < result.getHeight(); y++) { + for (int x = 0; x < result.getWidth(); x++) { + int val = qrCode.getModule(x / scale - border, y / scale - border); // 0 or 1 + result.setRGB(x, y, val == 1 ? 0x000000 : 0xFFFFFF); + } + } + return result; + } + +} diff --git a/java/settings.gradle b/java/settings.gradle new file mode 100644 index 0000000..53b40c3 --- /dev/null +++ b/java/settings.gradle @@ -0,0 +1,7 @@ +include ':common' +include ':advanced' +include ':javase' +include ':android' +include ':svg' +include ':demo' + diff --git a/java/svg/.gitignore b/java/svg/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/java/svg/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/java/svg/build.gradle b/java/svg/build.gradle new file mode 100644 index 0000000..52f6b41 --- /dev/null +++ b/java/svg/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(':common') +} \ No newline at end of file diff --git a/java/svg/src/io/nayuki/qrcodegen/QrCodeJavaSVG.java b/java/svg/src/io/nayuki/qrcodegen/QrCodeJavaSVG.java new file mode 100644 index 0000000..0732aa5 --- /dev/null +++ b/java/svg/src/io/nayuki/qrcodegen/QrCodeJavaSVG.java @@ -0,0 +1,43 @@ +package io.nayuki.qrcodegen; + +import java.util.Locale; + +public class QrCodeJavaSVG { + + /** + * Based on the specified number of border modules to add as padding, this returns a + * string whose contents represents an SVG XML file that depicts this QR Code symbol. + * Note that Unix newlines (\n) are always used, regardless of the platform. + * + * @param border the number of border modules to add, which must be non-negative + * @return a string representing this QR Code as an SVG document + */ + public String toSvgString(QrCode qr, int border) { + if (border < 0) + throw new IllegalArgumentException("Border must be non-negative"); + final int size = qr.size; + + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("\n"); + sb.append(String.format(Locale.US, "\n", size + border * 2)); + sb.append("\t\n"); + sb.append("\t\n"); + sb.append("\n"); + return sb.toString(); + } + +}