Instances of this class represent an immutable square grid of black and white cells.
+ * The class provides static factory functions to create a QR Code from text or binary data.
+ * The class covers the QR Code Model 2 specification, supporting all versions (sizes)
+ * from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
+ *
Ways to create a QR Code object:
+ *
+ *
High level: Take the payload data and call {@link QrCode#encodeText(String,Ecc)}
+ * or {@link QrCode#encodeBinary(byte[],Ecc)}.
+ *
Mid level: Custom-make the list of {@link QrSegment segments}
+ * and call {@link QrCode#encodeSegments(List,Ecc)} or
+ * {@link QrCode#encodeSegments(List,Ecc,int,int,int,boolean)}
+ *
Low level: Custom-make the array of data codeword bytes (including segment headers and
+ * final padding, excluding error correction codewords), supply the appropriate version number,
+ * and call the {@link QrCode#QrCode(int,Ecc,byte[],int) constructor}.
+ *
+ *
(Note that all ways require supplying the desired error correction level.)
+ * @see QrSegment
+ */
public final class QrCode {
- /*---- Public static factory functions ----*/
+ /*---- Static factory functions (high level) ----*/
+ /**
+ * Returns a QR Code representing the specified Unicode text string at the specified error correction level.
+ * As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer
+ * Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible
+ * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the
+ * ecl argument if it can be done without increasing the version.
+ * @param text the text to be encoded (not {@code null}), which can be any Unicode string
+ * @param ecl the error correction level to use (not {@code null}) (boostable)
+ * @return a QR Code (not {@code null}) representing the text
+ * @throws NullPointerException if the text or error correction level is {@code null}
+ * @throws IllegalArgumentException if the text fails to fit in the
+ * largest version QR Code at the ECL, which means it is too long
+ */
public static QrCode encodeText(String text, Ecc ecl) {
Objects.requireNonNull(text);
Objects.requireNonNull(ecl);
@@ -41,6 +75,18 @@ public final class QrCode {
}
+ /**
+ * Returns a QR Code representing the specified binary data at the specified error correction level.
+ * This function always encodes using the binary segment mode, not any text mode. The maximum number of
+ * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
+ * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
+ * @param data the binary data to encode (not {@code null})
+ * @param ecl the error correction level to use (not {@code null}) (boostable)
+ * @return a QR Code (not {@code null}) representing the data
+ * @throws NullPointerException if the data or error correction level is {@code null}
+ * @throws IllegalArgumentException if the data fails to fit in the
+ * largest version QR Code at the ECL, which means it is too long
+ */
public static QrCode encodeBinary(byte[] data, Ecc ecl) {
Objects.requireNonNull(data);
Objects.requireNonNull(ecl);
@@ -49,11 +95,51 @@ public final class QrCode {
}
+ /*---- Static factory functions (mid level) ----*/
+
+ /**
+ * Returns a QR Code representing the specified segments at the specified error correction
+ * level. The smallest possible QR Code version is automatically chosen for the output. The ECC level
+ * of the result may be higher than the ecl argument if it can be done without increasing the version.
+ *
This function allows the user to create a custom sequence of segments that switches
+ * between modes (such as alphanumeric and byte) to encode text in less space.
+ * This is a mid-level API; the high-level API is {@link #encodeText(String,Ecc)}
+ * and {@link #encodeBinary(byte[],Ecc)}.
+ * @param segs the segments to encode
+ * @param ecl the error correction level to use (not {@code null}) (boostable)
+ * @return a QR Code (not {@code null}) representing the segments
+ * @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null}
+ * @throws IllegalArgumentException if the segments fail to fit in the
+ * largest version QR Code at the ECL, which means they are too long
+ */
public static QrCode encodeSegments(List segs, Ecc ecl) {
return encodeSegments(segs, ecl, MIN_VERSION, MAX_VERSION, -1, true);
}
+ /**
+ * Returns a QR Code representing the specified segments with the specified encoding parameters.
+ * The smallest possible QR Code version within the specified range is automatically
+ * chosen for the output. Iff boostEcl is {@code true}, then the ECC level of the
+ * result may be higher than the ecl argument if it can be done without increasing
+ * the version. The mask number is either between 0 to 7 (inclusive) to force that
+ * mask, or −1 to automatically choose an appropriate mask (which may be slow).
+ *
This function allows the user to create a custom sequence of segments that switches
+ * between modes (such as alphanumeric and byte) to encode text in less space.
+ * This is a mid-level API; the high-level API is {@link #encodeText(String,Ecc)}
+ * and {@link #encodeBinary(byte[],Ecc)}.
+ * @param segs the segments to encode
+ * @param ecl the error correction level to use (not {@code null}) (boostable)
+ * @param minVersion the minimum allowed version of the QR Code (at least 1)
+ * @param maxVersion the maximum allowed version of the QR Code (at most 40)
+ * @param mask the mask number to use (between 0 and 7 (inclusive)), or −1 for automatic mask
+ * @param boostEcl increases the ECC level as long as it doesn't increase the version number
+ * @return a QR Code (not {@code null}) representing the segments
+ * @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null}
+ * @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40
+ * or −1 ≤ mask ≤ 7 is violated; or if the segments fail to
+ * fit in the maxVersion QR Code at the ECL, which means they are too long
+ */
public static QrCode encodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) {
Objects.requireNonNull(segs);
Objects.requireNonNull(ecl);
@@ -98,7 +184,7 @@ public final class QrCode {
for (int padByte = 0xEC; bb.bitLength < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
bb.appendBits(padByte, 8);
- // Create the QR Code symbol
+ // Create the QR Code object
return new QrCode(version, ecl, bb.getBytes(), mask);
}
@@ -106,20 +192,41 @@ public final class QrCode {
/*---- Instance fields ----*/
+ /** The version number of this QR Code, which is between 1 and 40 (inclusive).
+ * This determines the size of this barcode. */
public final int version;
+ /** The width and height of this QR Code, measured in modules, between
+ * 21 and 177 (inclusive). This is equal to version × 4 + 17. */
public final int size;
+ /** The error correction level used in this QR Code, which is not {@code null}. */
public final Ecc errorCorrectionLevel;
+ /** The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).
+ *
Even if a QR Code is created with automatic masking requested (mask =
+ * −1), the resulting object still has a mask value between 0 and 7. */
public final int mask;
private final int[] modules;
- /*---- Constructor ----*/
+ /*---- Constructor (low level) ----*/
+ /**
+ * Constructs a QR Code with the specified version number,
+ * error correction level, data codeword bytes, and mask number.
+ *
This is a low-level API that most users should not use directly. A mid-level
+ * API is the {@link #encodeSegments(List,Ecc,int,int,int,boolean)} function.
+ * @param ver the version number to use, which must be in the range 1 to 40 (inclusive)
+ * @param ecl the error correction level to use
+ * @param dataCodewords the bytes representing segments to encode (without ECC)
+ * @param mask the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice
+ * @throws NullPointerException if the byte array or error correction level is {@code null}
+ * @throws IllegalArgumentException if the version or mask value is out of range,
+ * or if the data is the wrong length for the specified version and error correction level
+ */
public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) {
// Check arguments and initialize fields
if (ver < MIN_VERSION || ver > MAX_VERSION)
@@ -145,12 +252,13 @@ public final class QrCode {
/*---- Public instance methods ----*/
/**
- * Returns the color of the module (pixel) at the specified coordinates, which is either
- * false for white or true for black. The top left corner has the coordinates (x=0, y=0).
- * If the specified coordinates are out of bounds, then false (white) is returned.
- * @param x the x coordinate, where 0 is the left edge and size−1 is the right edge
- * @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge
- * @return the module's color, which is either false (white) or true (black)
+ * Returns the color of the module (pixel) at the specified coordinates, which is {@code false}
+ * for white or {@code true} for black. The top left corner has the coordinates (x=0, y=0).
+ * If the specified coordinates are out of bounds, then {@code false} (white) is returned.
+ * @param x the x coordinate, where 0 is the left edge and size−1 is the right edge
+ * @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge
+ * @return {@code true} if the coordinates are in bounds and the module
+ * at that location is black, or {@code false} (white) otherwise
*/
public boolean getModule(int x, int y) {
if (0 <= x && x < size && 0 <= y && y < size) {
@@ -162,13 +270,13 @@ public final class QrCode {
/**
- * 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.
+ * Returns a raster image depicting this QR Code, with the specified module scale and border modules.
+ *
For example, toImage(scale=10, border=4) means to pad the QR Code with 4 white
+ * border modules on all four sides, and 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 scale the side length (measured in pixels, must be positive) of each module
* @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
+ * @return a new image representing this QR Code, with padding and scaling
* @throws IllegalArgumentException if the scale or border is out of range, or if
* {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE
*/
@@ -190,10 +298,10 @@ public final class QrCode {
/**
- * Returns a string of SVG XML code representing an image of this QR Code symbol with the specified
- * number of border modules. Note that Unix newlines (\n) are always used, regardless of the platform.
+ * Returns a string of SVG code for an image depicting this QR Code, with the specified number
+ * of border modules. The string always uses Unix newlines (\n), 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
+ * @return a string representing this QR Code as an SVG XML document
* @throws IllegalArgumentException if the border is negative
*/
public String toSvgString(int border) {
@@ -331,7 +439,7 @@ public final class QrCode {
}
- // A messy helper function for the constructors. This QR Code must be in an unmasked state when this
+ // A messy helper function for the constructor. This QR Code must be in an unmasked state when this
// method is called. The given argument is the requested mask, which is -1 for auto or 0 to 7 for fixed.
// This method applies and returns the actual mask chosen, from 0 to 7.
private int handleConstructorMasking(int[][] masks, int mask) {
@@ -434,7 +542,7 @@ public final class QrCode {
- /*---- Private static helper functions ----*/
+ /*---- Private helper functions ----*/
// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
// QR Code of the given version number and error correction level, with remainder bits discarded.
@@ -454,7 +562,10 @@ public final class QrCode {
/*---- Constants and tables ----*/
+ /** The minimum version number (1) supported in the QR Code Model 2 standard. */
public static final int MIN_VERSION = 1;
+
+ /** The maximum version number (40) supported in the QR Code Model 2 standard. */
public static final int MAX_VERSION = 40;
@@ -487,10 +598,16 @@ public final class QrCode {
/*---- Public helper enumeration ----*/
+ /**
+ * The error correction level in a QR Code symbol.
+ */
public enum Ecc {
- // These enum constants must be declared in ascending order of error protection,
- // for the sake of the implicit ordinal() method and values() function.
- LOW(1), MEDIUM(0), QUARTILE(3), HIGH(2);
+ // 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;
diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java
index ff31a35..ee6a6a2 100644
--- a/src/io/nayuki/fastqrcodegen/QrSegment.java
+++ b/src/io/nayuki/fastqrcodegen/QrSegment.java
@@ -32,8 +32,17 @@ import java.util.Objects;
public final class QrSegment {
- /*---- Static factory functions ----*/
-
+ /*---- Static factory functions (mid level) ----*/
+
+ /**
+ * 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 static QrSegment makeBytes(byte[] data) {
Objects.requireNonNull(data);
int[] bits = new int[(data.length + 3) / 4];
@@ -43,6 +52,13 @@ public final class QrSegment {
}
+ /**
+ * 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 static QrSegment makeNumeric(String digits) {
Objects.requireNonNull(digits);
BitBuffer bb = new BitBuffer();
@@ -66,6 +82,15 @@ public final class QrSegment {
}
+ /**
+ * 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 static QrSegment makeAlphanumeric(String text) {
Objects.requireNonNull(text);
BitBuffer bb = new BitBuffer();
@@ -89,6 +114,13 @@ public final class QrSegment {
}
+ /**
+ * 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.
+ * @param text the text to be encoded, which can be any Unicode string
+ * @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);
@@ -105,6 +137,13 @@ public final class QrSegment {
}
+ /**
+ * Returns a segment representing an Extended Channel Interpretation
+ * (ECI) designator with the specified assignment value.
+ * @param assignVal the ECI assignment number (see the AIM ECI specification)
+ * @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 (0 <= assignVal && assignVal < (1 << 7))
@@ -144,17 +183,32 @@ public final class QrSegment {
/*---- Instance fields ----*/
+ /** The mode indicator of this segment. Not {@code null}. */
public final Mode mode;
+ /** The length of this segment's unencoded data. Measured in characters for
+ * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
+ * Always zero or positive. Not the same as the data's bit length. */
public final int numChars;
+ // The data bits of this segment. Not null. Accessed through getData().
final int[] data;
final int bitLength;
- /*---- Constructor ----*/
+ /*---- Constructor (low level) ----*/
+ /**
+ * Constructs a QR Code segment with the specified attributes and data.
+ * The character count (numCh) must agree with the mode and the bit buffer length,
+ * but the constraint isn't checked. The specified bit buffer is cloned and stored.
+ * @param md the mode (not {@code null})
+ * @param numCh the data length in characters or bytes, which is non-negative
+ * @param data the data bits (not {@code null})
+ * @throws NullPointerException if the mode or data is {@code null}
+ * @throws IllegalArgumentException if the character count is negative
+ */
public QrSegment(Mode md, int numCh, int[] data, int bitLen) {
mode = Objects.requireNonNull(md);
this.data = Objects.requireNonNull(data);
@@ -204,6 +258,9 @@ public final class QrSegment {
/*---- Public helper enumeration ----*/
+ /**
+ * Describes how a segment's data bits are interpreted.
+ */
public enum Mode {
/*-- Constants --*/
@@ -217,8 +274,10 @@ public final class QrSegment {
/*-- Fields --*/
+ // The mode indicator bits, which is a uint4 value (range 0 to 15).
final int modeBits;
+ // Number of character count bits for three different version ranges.
private final int[] numBitsCharCount;
@@ -232,6 +291,8 @@ public final class QrSegment {
/*-- 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) {
return numBitsCharCount[(ver + 7) / 17];
}
From f6e7b1760271928de92ed932592ae5d657ed2e72 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 06:25:12 +0000
Subject: [PATCH 45/85] Tweaked small pieces of code, synchronizing from the
parent "QR Code generator library" project.
---
src/io/nayuki/fastqrcodegen/BitBuffer.java | 2 ++
src/io/nayuki/fastqrcodegen/QrCode.java | 1 +
src/io/nayuki/fastqrcodegen/QrSegment.java | 11 +++++++----
3 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java
index a2d573e..f187e9f 100644
--- a/src/io/nayuki/fastqrcodegen/BitBuffer.java
+++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java
@@ -87,6 +87,8 @@ final class BitBuffer {
public void appendBits(int val, int len) {
if (len < 0 || len > 31 || val >>> len != 0)
throw new IllegalArgumentException("Value out of range");
+ if (Integer.MAX_VALUE - bitLength < len)
+ throw new IllegalStateException("Maximum length reached");
if (bitLength + len + 1 > data.length << 5)
data = Arrays.copyOf(data, data.length * 2);
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index 42fdaec..4366d5f 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -381,6 +381,7 @@ public final class QrCode {
// Returns a new byte string representing the given data with the appropriate error correction
// codewords appended to it, based on this object's version and error correction level.
private byte[] addEccAndInterleave(byte[] data) {
+ Objects.requireNonNull(data);
if (data.length != getNumDataCodewords(version, errorCorrectionLevel))
throw new IllegalArgumentException();
diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java
index ee6a6a2..9f91ac4 100644
--- a/src/io/nayuki/fastqrcodegen/QrSegment.java
+++ b/src/io/nayuki/fastqrcodegen/QrSegment.java
@@ -146,12 +146,14 @@ public final class QrSegment {
*/
public static QrSegment makeEci(int assignVal) {
BitBuffer bb = new BitBuffer();
- if (0 <= assignVal && assignVal < (1 << 7))
+ if (assignVal < 0)
+ throw new IllegalArgumentException("ECI assignment value out of range");
+ else if (assignVal < (1 << 7))
bb.appendBits(assignVal, 8);
- else if ((1 << 7) <= assignVal && assignVal < (1 << 14)) {
+ else if (assignVal < (1 << 14)) {
bb.appendBits(2, 2);
bb.appendBits(assignVal, 14);
- } else if ((1 << 14) <= assignVal && assignVal < 1000000) {
+ } else if (assignVal < 1_000_000) {
bb.appendBits(6, 3);
bb.appendBits(assignVal, 21);
} else
@@ -284,7 +286,7 @@ public final class QrSegment {
/*-- Constructor --*/
private Mode(int mode, int... ccbits) {
- this.modeBits = mode;
+ modeBits = mode;
numBitsCharCount = ccbits;
}
@@ -294,6 +296,7 @@ public final class QrSegment {
// 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];
}
From b669311c2eed9c9afe5f8d6f54ca0da551cab927 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 06:33:33 +0000
Subject: [PATCH 46/85] Added Javadoc and informal comments to some members,
which differ from the parent project.
---
src/io/nayuki/fastqrcodegen/BitBuffer.java | 5 ++++
src/io/nayuki/fastqrcodegen/QrCode.java | 3 +++
src/io/nayuki/fastqrcodegen/QrSegment.java | 29 ++++++++++++++++++++++
3 files changed, 37 insertions(+)
diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java
index f187e9f..7031914 100644
--- a/src/io/nayuki/fastqrcodegen/BitBuffer.java
+++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java
@@ -65,6 +65,11 @@ final class BitBuffer {
}
+ /**
+ * Returns an array representing this buffer's bits packed into bytes
+ * in big endian. The current bit length must be a multiple of 8.
+ * @return a new byte array (not {@code null}) representing this bit sequence
+ */
public byte[] getBytes() {
if (bitLength % 8 != 0)
throw new IllegalStateException("Data is not a whole number of bytes");
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index 4366d5f..a1e71e0 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -208,6 +208,7 @@ public final class QrCode {
* −1), the resulting object still has a mask value between 0 and 7. */
public final int mask;
+ // The modules of this QR Code. Immutable after constructor finishes. Accessed through getModule().
private final int[] modules;
@@ -363,6 +364,8 @@ public final class QrCode {
}
+ // Sets the module at the given coordinates to the given color.
+ // Only used by the constructor. Coordinates must be in bounds.
private void setModule(int x, int y, int black) {
assert 0 <= x && x < size;
assert 0 <= y && y < size;
diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java
index 9f91ac4..cf0d655 100644
--- a/src/io/nayuki/fastqrcodegen/QrSegment.java
+++ b/src/io/nayuki/fastqrcodegen/QrSegment.java
@@ -30,6 +30,18 @@ import java.util.List;
import java.util.Objects;
+/**
+ * A segment of character/binary/control data in a QR Code symbol.
+ * Instances of this class are immutable.
+ *
The mid-level way to create a segment is to take the payload data and call a
+ * static factory function such as {@link QrSegment#makeNumeric(String)}. The low-level
+ * way to create a segment is to custom-make the bit buffer and call the {@link
+ * QrSegment#QrSegment(Mode,int,int[],int) constructor} with appropriate values.
+ *
This segment class imposes no length restrictions, but QR Codes have restrictions.
+ * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
+ * Any segment longer than this is meaningless for the purpose of generating QR Codes.
+ * This class can represent kanji mode segments, but provides no help in encoding them.
+ */
public final class QrSegment {
/*---- Static factory functions (mid level) ----*/
@@ -162,6 +174,14 @@ public final class QrSegment {
}
+ /**
+ * Tests whether the specified string can be encoded as a segment in numeric mode.
+ * A string is encodable iff each character is in the range 0 to 9.
+ * @param text the string to test for encodability (not {@code null})
+ * @return {@code true} iff each character is in the range 0 to 9.
+ * @throws NullPointerException if the string is {@code null}
+ * @see #makeNumeric(String)
+ */
public static boolean isNumeric(String text) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
@@ -172,6 +192,15 @@ public final class QrSegment {
}
+ /**
+ * Tests whether the specified string can be encoded as a segment in alphanumeric mode.
+ * A string is encodable iff each character is in the following set: 0 to 9, A to Z
+ * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
+ * @param text the string to test for encodability (not {@code null})
+ * @return {@code true} iff each character is in the alphanumeric mode character set
+ * @throws NullPointerException if the string is {@code null}
+ * @see #makeAlphanumeric(String)
+ */
public static boolean isAlphanumeric(String text) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
From 61241872480da6e6ab4306c8d5c16bb36031bc91 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 06:38:54 +0000
Subject: [PATCH 47/85] Refactored QrTemplate to use QrCode.getBit().
---
src/io/nayuki/fastqrcodegen/QrCode.java | 2 +-
src/io/nayuki/fastqrcodegen/QrTemplate.java | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index a1e71e0..dcd958f 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -559,7 +559,7 @@ public final class QrCode {
// Returns 0 or 1 based on the i'th bit of x.
- private static int getBit(int x, int i) {
+ static int getBit(int x, int i) {
return (x >>> i) & 1;
}
diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java
index 41306d1..9ecbbbd 100644
--- a/src/io/nayuki/fastqrcodegen/QrTemplate.java
+++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java
@@ -173,7 +173,7 @@ final class QrTemplate {
// Draw two copies
for (int i = 0; i < 18; i++) {
- int bit = (bits >>> i) & 1;
+ int bit = QrCode.getBit(bits, i);
int a = size - 11 + i % 3;
int b = i / 3;
darkenFunctionModule(a, b, bit);
@@ -260,7 +260,7 @@ final class QrTemplate {
assert 0 <= x && x < size;
assert 0 <= y && y < size;
int i = y * size + x;
- return (grid[i >>> 5] >>> i) & 1;
+ return QrCode.getBit(grid[i >>> 5], i);
}
From 3e6381cea83f5c3a2c5f93beed59a3439981aba0 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 06:50:45 +0000
Subject: [PATCH 48/85] Renamed loop variables and swapped commutative
arguments for clarity.
---
src/io/nayuki/fastqrcodegen/QrTemplate.java | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java
index 9ecbbbd..49aad5d 100644
--- a/src/io/nayuki/fastqrcodegen/QrTemplate.java
+++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java
@@ -185,10 +185,10 @@ final class QrTemplate {
// Draws a 9*9 finder pattern including the border separator,
// with the center module at (x, y). Modules can be out of bounds.
private void drawFinderPattern(int x, int y) {
- for (int i = -4; i <= 4; i++) {
- for (int j = -4; j <= 4; j++) {
- int dist = Math.max(Math.abs(i), Math.abs(j)); // Chebyshev/infinity norm
- int xx = x + j, yy = y + i;
+ for (int dy = -4; dy <= 4; dy++) {
+ for (int dx = -4; dx <= 4; dx++) {
+ int dist = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm
+ int xx = x + dx, yy = y + dy;
if (0 <= xx && xx < size && 0 <= yy && yy < size)
darkenFunctionModule(xx, yy, (dist != 2 && dist != 4) ? 1 : 0);
}
@@ -199,9 +199,9 @@ final class QrTemplate {
// Draws a 5*5 alignment pattern, with the center module
// at (x, y). All modules must be in bounds.
private void drawAlignmentPattern(int x, int y) {
- for (int i = -2; i <= 2; i++) {
- for (int j = -2; j <= 2; j++)
- darkenFunctionModule(x + j, y + i, (Math.max(Math.abs(i), Math.abs(j)) != 1) ? 1 : 0);
+ for (int dy = -2; dy <= 2; dy++) {
+ for (int dx = -2; dx <= 2; dx++)
+ darkenFunctionModule(x + dx, y + dy, (Math.max(Math.abs(dx), Math.abs(dy)) != 1) ? 1 : 0);
}
}
From eb072ba84e1bd0a29413d598bf8f40303f835746 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 06:52:30 +0000
Subject: [PATCH 49/85] Simplified small pieces of logic.
---
src/io/nayuki/fastqrcodegen/QrTemplate.java | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java
index 49aad5d..6c81748 100644
--- a/src/io/nayuki/fastqrcodegen/QrTemplate.java
+++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java
@@ -125,9 +125,7 @@ final class QrTemplate {
int numAlign = alignPatPos.length;
for (int i = 0; i < numAlign; i++) {
for (int j = 0; j < numAlign; j++) {
- if (i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)
- continue; // Skip the three finder corners
- else
+ if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0))
drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
}
}
@@ -201,7 +199,7 @@ final class QrTemplate {
private void drawAlignmentPattern(int x, int y) {
for (int dy = -2; dy <= 2; dy++) {
for (int dx = -2; dx <= 2; dx++)
- darkenFunctionModule(x + dx, y + dy, (Math.max(Math.abs(dx), Math.abs(dy)) != 1) ? 1 : 0);
+ darkenFunctionModule(x + dx, y + dy, Math.abs(Math.max(Math.abs(dx), Math.abs(dy)) - 1));
}
}
From 83a3c3b957714ddd38d8e5a2e941a996a913381c Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 06:52:37 +0000
Subject: [PATCH 50/85] Added and updated comments.
---
src/io/nayuki/fastqrcodegen/QrTemplate.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java
index 6c81748..931d9b0 100644
--- a/src/io/nayuki/fastqrcodegen/QrTemplate.java
+++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java
@@ -90,7 +90,7 @@ final class QrTemplate {
final int[][] masks;
final int[] dataOutputBitIndexes;
- private int[] isFunction; // Discarded at end of constructor
+ private int[] isFunction; // Discarded when constructor finishes
private QrTemplate(int ver) {
@@ -125,6 +125,7 @@ final class QrTemplate {
int numAlign = alignPatPos.length;
for (int i = 0; i < numAlign; i++) {
for (int j = 0; j < numAlign; j++) {
+ // Don't draw on the three finder corners
if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0))
drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
}
@@ -262,6 +263,8 @@ final class QrTemplate {
}
+ // Marks the module at the given coordinates as a function module.
+ // Also either sets that module black or keeps its color unchanged.
private void darkenFunctionModule(int x, int y, int enable) {
assert 0 <= x && x < size;
assert 0 <= y && y < size;
From b967ae4aaec57079e486ec0a67b6c5ef20cb54d1 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 07:00:35 +0000
Subject: [PATCH 51/85] Added and updated some strict overflow checks.
---
src/io/nayuki/fastqrcodegen/BitBuffer.java | 6 ++++--
src/io/nayuki/fastqrcodegen/QrSegment.java | 4 +++-
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java
index 7031914..4a2faf1 100644
--- a/src/io/nayuki/fastqrcodegen/BitBuffer.java
+++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java
@@ -92,7 +92,7 @@ final class BitBuffer {
public void appendBits(int val, int len) {
if (len < 0 || len > 31 || val >>> len != 0)
throw new IllegalArgumentException("Value out of range");
- if (Integer.MAX_VALUE - bitLength < len)
+ if (len > Integer.MAX_VALUE - bitLength)
throw new IllegalStateException("Maximum length reached");
if (bitLength + len + 1 > data.length << 5)
@@ -118,12 +118,14 @@ final class BitBuffer {
Objects.requireNonNull(vals);
if (len == 0)
return;
- if (len < 0 || len > vals.length * 32)
+ if (len < 0 || len > vals.length * 32L)
throw new IllegalArgumentException("Value out of range");
int wholeWords = len / 32;
int tailBits = len % 32;
if (tailBits > 0 && vals[wholeWords] << tailBits != 0)
throw new IllegalArgumentException("Last word must have low bits clear");
+ if (len > Integer.MAX_VALUE - bitLength)
+ throw new IllegalStateException("Maximum length reached");
while (bitLength + len > data.length * 32)
data = Arrays.copyOf(data, data.length * 2);
diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java
index cf0d655..bf7d5ef 100644
--- a/src/io/nayuki/fastqrcodegen/QrSegment.java
+++ b/src/io/nayuki/fastqrcodegen/QrSegment.java
@@ -57,6 +57,8 @@ public final class QrSegment {
*/
public static QrSegment makeBytes(byte[] data) {
Objects.requireNonNull(data);
+ if (data.length * 8L > Integer.MAX_VALUE)
+ throw new IllegalArgumentException("Data too long");
int[] bits = new int[(data.length + 3) / 4];
for (int i = 0; i < data.length; i++)
bits[i >>> 2] |= (data[i] & 0xFF) << (~i << 3);
@@ -243,7 +245,7 @@ public final class QrSegment {
public QrSegment(Mode md, int numCh, int[] data, int bitLen) {
mode = Objects.requireNonNull(md);
this.data = Objects.requireNonNull(data);
- if (numCh < 0 || bitLen < 0 || bitLen > data.length * 32)
+ if (numCh < 0 || bitLen < 0 || bitLen > data.length * 32L)
throw new IllegalArgumentException("Invalid value");
numChars = numCh;
bitLength = bitLen;
From 72d350e3a0d278e256f1e1f13623c910a1f7cdc3 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 07:04:53 +0000
Subject: [PATCH 52/85] Added and updated comments.
---
src/io/nayuki/fastqrcodegen/BitBuffer.java | 8 ++++++++
src/io/nayuki/fastqrcodegen/QrSegment.java | 2 ++
2 files changed, 10 insertions(+)
diff --git a/src/io/nayuki/fastqrcodegen/BitBuffer.java b/src/io/nayuki/fastqrcodegen/BitBuffer.java
index 4a2faf1..18e1027 100644
--- a/src/io/nayuki/fastqrcodegen/BitBuffer.java
+++ b/src/io/nayuki/fastqrcodegen/BitBuffer.java
@@ -114,6 +114,14 @@ final class BitBuffer {
}
+ /**
+ * Appends the specified sequence of bits to this buffer.
+ * Requires 0 ≤ len ≤ 32 × vals.length.
+ * @param vals the sequence of bits to append (not {@code null})
+ * @param len the number of prefix bits to read from the array
+ * @throws IllegalStateException if appending the data
+ * would make bitLength exceed Integer.MAX_VALUE
+ */
public void appendBits(int[] vals, int len) {
Objects.requireNonNull(vals);
if (len == 0)
diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java
index bf7d5ef..677bcda 100644
--- a/src/io/nayuki/fastqrcodegen/QrSegment.java
+++ b/src/io/nayuki/fastqrcodegen/QrSegment.java
@@ -227,6 +227,7 @@ public final class QrSegment {
// The data bits of this segment. Not null. Accessed through getData().
final int[] data;
+ // Requires 0 <= bitLength <= data.length * 32.
final int bitLength;
@@ -239,6 +240,7 @@ public final class QrSegment {
* @param md the mode (not {@code null})
* @param numCh the data length in characters or bytes, which is non-negative
* @param data the data bits (not {@code null})
+ * @param bitLen the number of valid prefix bits in the data array
* @throws NullPointerException if the mode or data is {@code null}
* @throws IllegalArgumentException if the character count is negative
*/
From 5657ba274b21da3e4bba9f3bfb26a9044b448b92 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sun, 7 Oct 2018 20:47:04 +0000
Subject: [PATCH 53/85] Added package summary Javadoc comment.
---
src/io/nayuki/fastqrcodegen/package-info.java | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 src/io/nayuki/fastqrcodegen/package-info.java
diff --git a/src/io/nayuki/fastqrcodegen/package-info.java b/src/io/nayuki/fastqrcodegen/package-info.java
new file mode 100644
index 0000000..e70dc6c
--- /dev/null
+++ b/src/io/nayuki/fastqrcodegen/package-info.java
@@ -0,0 +1,48 @@
+/**
+ * Generates QR Codes from text strings and byte arrays.
+ *
+ *
This library generates QR Code symbols, and its design is optimized for speed. It contrasts with another QR library by the same author which is slow but which optimizes for clarity and conciseness. The functionality of this library and its API are nearly identical to the slow library, but it runs anywhere from 1.5× to 6× as fast.
import java.util.List;
+ *import io.nayuki.fastqrcodegen.*;
+ *
+ *List<QrSegment> segs = QrSegment.makeSegments("3141592653589793238462643383");
+ *QrCode qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, 5, 5, 2, false);
+ *for (int y = 0; y < qr.size; y++) {
+ * for (int x = 0; x < qr.size; x++) {
+ * (... paint qr.getModule(x, y) ...)
+ * }
+ *}
+ */
+package io.nayuki.fastqrcodegen;
From badeca0dc9c0eb84ed72585448cb090f7510b192 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Sat, 13 Oct 2018 21:22:52 +0000
Subject: [PATCH 54/85] Imported QrSegmentAdvanced from the parent project,
changed a constant to be package-private, updated readme.
---
Readme.markdown | 2 +
src/io/nayuki/fastqrcodegen/QrSegment.java | 2 +-
.../fastqrcodegen/QrSegmentAdvanced.java | 419 ++++++++++++++++++
3 files changed, 422 insertions(+), 1 deletion(-)
create mode 100644 src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java
diff --git a/Readme.markdown b/Readme.markdown
index 9e7380f..bd70143 100644
--- a/Readme.markdown
+++ b/Readme.markdown
@@ -20,6 +20,8 @@ Core features:
* Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard
* Output formats: Raw modules/pixels of the QR symbol, SVG XML string, `BufferedImage` raster bitmap
* Encodes numeric and special-alphanumeric text in less space than general text
+* Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes
+* Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts
* Open source code under the permissive MIT License
Manual parameters:
diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java
index 677bcda..86cce95 100644
--- a/src/io/nayuki/fastqrcodegen/QrSegment.java
+++ b/src/io/nayuki/fastqrcodegen/QrSegment.java
@@ -276,7 +276,7 @@ public final class QrSegment {
/*---- Constants ----*/
- private static final int[] ALPHANUMERIC_MAP;
+ static final int[] ALPHANUMERIC_MAP;
static {
final String ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
diff --git a/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java b/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java
new file mode 100644
index 0000000..6cbdb7b
--- /dev/null
+++ b/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java
@@ -0,0 +1,419 @@
+/*
+ * Fast QR Code generator library
+ *
+ * Copyright (c) Project Nayuki. (MIT License)
+ * https://www.nayuki.io/page/fast-qr-code-generator-library
+ *
+ * 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.fastqrcodegen;
+
+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;
+import io.nayuki.fastqrcodegen.QrSegment.Mode;
+
+
+/**
+ * Splits text into optimal segments and encodes kanji segments.
+ * Provides static functions only; not instantiable.
+ * @see QrSegment
+ * @see QrCode
+ */
+public final class QrSegmentAdvanced {
+
+ /*---- Optimal list of segments encoder ----*/
+
+ /**
+ * Returns a 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
+ * in the specified {error correction level, minimum version number, maximum version number}.
+ *
This function can utilize all four text encoding modes: numeric, alphanumeric, byte (UTF-8),
+ * and kanji. This can be considered as a sophisticated but slower replacement for {@link
+ * QrSegment#makeSegments(String)}. This requires more input parameters because it searches a
+ * range of versions, like {@link QrCode#encodeSegments(List,QrCode.Ecc,int,int,int,boolean)}.
+ * @param text the text to be encoded (not {@code null}), which can be any Unicode string
+ * @param ecl the error correction level to use (not {@code null})
+ * @param minVersion the minimum allowed version of the QR Code (at least 1)
+ * @param maxVersion the maximum allowed version of the QR Code (at most 40)
+ * @return a new mutable list (not {@code null}) of segments (not {@code null})
+ * containing the text, minimizing the bit length with respect to the constraints
+ * @throws NullPointerException if the text 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 ECL
+ */
+ public static List makeSegmentsOptimally(String text, QrCode.Ecc ecl, int minVersion, int maxVersion) {
+ // Check arguments
+ Objects.requireNonNull(text);
+ Objects.requireNonNull(ecl);
+ if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION))
+ throw new IllegalArgumentException("Invalid value");
+
+ // Iterate through version numbers, and make tentative segments
+ List segs = null;
+ int[] codePoints = toCodePoints(text);
+ for (int version = minVersion; version <= maxVersion; version++) {
+ if (version == minVersion || version == 10 || version == 27)
+ segs = makeSegmentsOptimally(codePoints, version);
+ assert segs != null;
+
+ // 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 new list of segments that is optimal for the given text at the given version number.
+ private static List makeSegmentsOptimally(int[] codePoints, int version) {
+ if (codePoints.length == 0)
+ return new ArrayList<>();
+ Mode[] charModes = computeCharacterModes(codePoints, version);
+ return splitIntoSegments(codePoints, charModes);
+ }
+
+
+ // Returns a new array representing the optimal mode per code point based on the given text and version.
+ private static Mode[] computeCharacterModes(int[] codePoints, int version) {
+ if (codePoints.length == 0)
+ throw new IllegalArgumentException();
+ final Mode[] modeTypes = {Mode.BYTE, Mode.ALPHANUMERIC, Mode.NUMERIC, Mode.KANJI}; // Do not modify
+ final int numModes = modeTypes.length;
+
+ // Segment header sizes, measured in 1/6 bits
+ final int[] headCosts = new int[numModes];
+ for (int i = 0; i < numModes; i++)
+ headCosts[i] = (4 + modeTypes[i].numCharCountBits(version)) * 6;
+
+ // charModes[i][j] represents the mode to encode the code point at
+ // index i such that the final segment ends in modeTypes[j] and the
+ // total number of bits is minimized over all possible choices
+ Mode[][] charModes = new Mode[codePoints.length][numModes];
+
+ // At the beginning of each iteration of the loop below,
+ // prevCosts[j] is the exact minimum number of 1/6 bits needed to
+ // encode the entire string prefix of length i, and end in modeTypes[j]
+ int[] prevCosts = headCosts.clone();
+
+ // Calculate costs using dynamic programming
+ for (int i = 0; i < codePoints.length; i++) {
+ int c = codePoints[i];
+ int[] curCosts = new int[numModes];
+ { // Always extend a byte mode segment
+ curCosts[0] = prevCosts[0] + countUtf8Bytes(c) * 8 * 6;
+ charModes[i][0] = modeTypes[0];
+ }
+ // Extend a segment if possible
+ if (QrSegment.ALPHANUMERIC_MAP[c] != -1) { // Is alphanumeric
+ curCosts[1] = prevCosts[1] + 33; // 5.5 bits per alphanumeric char
+ charModes[i][1] = modeTypes[1];
+ }
+ if ('0' <= c && c <= '9') { // Is numeric
+ curCosts[2] = prevCosts[2] + 20; // 3.33 bits per digit
+ charModes[i][2] = modeTypes[2];
+ }
+ if (isKanji(c)) {
+ curCosts[3] = prevCosts[3] + 78; // 13 bits per Shift JIS char
+ charModes[i][3] = modeTypes[3];
+ }
+
+ // Start new segment at the end to switch modes
+ for (int j = 0; j < numModes; j++) { // To mode
+ for (int k = 0; k < numModes; k++) { // From mode
+ int newCost = (curCosts[k] + 5) / 6 * 6 + headCosts[j];
+ if (charModes[i][k] != null && (charModes[i][j] == null || newCost < curCosts[j])) {
+ curCosts[j] = newCost;
+ charModes[i][j] = modeTypes[k];
+ }
+ }
+ }
+
+ prevCosts = curCosts;
+ }
+
+ // Find optimal ending mode
+ Mode curMode = null;
+ for (int i = 0, minCost = 0; i < numModes; i++) {
+ if (curMode == null || prevCosts[i] < minCost) {
+ minCost = prevCosts[i];
+ curMode = modeTypes[i];
+ }
+ }
+
+ // Get optimal mode for each code point by tracing backwards
+ Mode[] result = new Mode[charModes.length];
+ for (int i = result.length - 1; i >= 0; i--) {
+ for (int j = 0; j < numModes; j++) {
+ if (modeTypes[j] == curMode) {
+ curMode = charModes[i][j];
+ result[i] = curMode;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+
+ // Returns a new list of segments based on the given text and modes, such that
+ // consecutive code points in the same mode are put into the same segment.
+ private static List splitIntoSegments(int[] codePoints, Mode[] charModes) {
+ if (codePoints.length == 0)
+ throw new IllegalArgumentException();
+ List result = new ArrayList<>();
+
+ // Accumulate run of modes
+ Mode curMode = charModes[0];
+ int start = 0;
+ for (int i = 1; ; i++) {
+ if (i < codePoints.length && charModes[i] == curMode)
+ continue;
+ String s = new String(codePoints, start, i - start);
+ if (curMode == Mode.BYTE)
+ result.add(QrSegment.makeBytes(s.getBytes(StandardCharsets.UTF_8)));
+ else if (curMode == Mode.NUMERIC)
+ result.add(QrSegment.makeNumeric(s));
+ else if (curMode == Mode.ALPHANUMERIC)
+ result.add(QrSegment.makeAlphanumeric(s));
+ else if (curMode == Mode.KANJI)
+ result.add(makeKanji(s));
+ else
+ throw new AssertionError();
+ if (i >= codePoints.length)
+ return result;
+ curMode = charModes[i];
+ start = i;
+ }
+ }
+
+
+ // Returns a new array of Unicode code points (effectively
+ // UTF-32 / UCS-4) representing the given UTF-16 string.
+ private static int[] toCodePoints(String s) {
+ int[] result = s.codePoints().toArray();
+ for (int c : result) {
+ if (Character.isSurrogate((char)c))
+ throw new IllegalArgumentException("Invalid UTF-16 string");
+ }
+ return result;
+ }
+
+
+ // Returns the number of UTF-8 bytes needed to encode the given Unicode code point.
+ private static int countUtf8Bytes(int cp) {
+ if (cp < 0) throw new IllegalArgumentException("Invalid code point");
+ else if (cp < 0x80) return 1;
+ else if (cp < 0x800) return 2;
+ else if (cp < 0x10000) return 3;
+ else if (cp < 0x110000) return 4;
+ else throw new IllegalArgumentException("Invalid code point");
+ }
+
+
+
+ /*---- Kanji mode segment encoder ----*/
+
+ /**
+ * Returns a segment representing the specified text string encoded in kanji mode.
+ * Broadly speaking, the set of encodable characters are {kanji used in Japan,
+ * hiragana, katakana, East Asian punctuation, full-width ASCII, Greek, Cyrillic}.
+ * Examples of non-encodable characters include {ordinary ASCII, half-width katakana,
+ * more extensive Chinese hanzi}.
+ * @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
+ * @see #isEncodableAsKanji(String)
+ */
+ public static QrSegment makeKanji(String text) {
+ Objects.requireNonNull(text);
+ BitBuffer bb = new BitBuffer();
+ text.chars().forEachOrdered(c -> {
+ int val = UNICODE_TO_QR_KANJI[c];
+ if (val == -1)
+ throw new IllegalArgumentException("String contains non-kanji-mode characters");
+ bb.appendBits(val, 13);
+ });
+ return new QrSegment(Mode.KANJI, text.length(), bb.data, bb.bitLength);
+ }
+
+
+ /**
+ * Tests whether the specified string can be encoded as a segment in kanji mode.
+ * Broadly speaking, the set of encodable characters are {kanji used in Japan,
+ * hiragana, katakana, East Asian punctuation, full-width ASCII, Greek, Cyrillic}.
+ * Examples of non-encodable characters include {ordinary ASCII, half-width katakana,
+ * more extensive Chinese hanzi}.
+ * @param text the string to test for encodability (not {@code null})
+ * @return {@code true} iff each character is in the kanji mode character set
+ * @throws NullPointerException if the string is {@code null}
+ * @see #makeKanji(String)
+ */
+ public static boolean isEncodableAsKanji(String text) {
+ Objects.requireNonNull(text);
+ return text.chars().allMatch(
+ c -> isKanji((char)c));
+ }
+
+
+ private static boolean isKanji(int c) {
+ return c < UNICODE_TO_QR_KANJI.length && UNICODE_TO_QR_KANJI[c] != -1;
+ }
+
+
+ // Data derived from ftp://ftp.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/SHIFTJIS.TXT
+ private static final String PACKED_QR_KANJI_TO_UNICODE =
+ "MAAwATAC/wz/DjD7/xr/G/8f/wEwmzCcALT/QACo/z7/4/8/MP0w/jCdMJ4wA07dMAUwBjAHMPwgFSAQ/w8AXDAcIBb/XCAmICUgGCAZIBwgHf8I/wkwFDAV/zv/Pf9b/10wCDAJMAowCzAMMA0wDjAPMBAwEf8LIhIAsQDX//8A9/8dImD/HP8eImYiZyIeIjQmQiZA" +
+ "ALAgMiAzIQP/5f8EAKIAo/8F/wP/Bv8K/yAApyYGJgUlyyXPJc4lxyXGJaEloCWzJbIlvSW8IDswEiGSIZAhkSGTMBP/////////////////////////////IggiCyKGIocigiKDIioiKf////////////////////8iJyIoAKwh0iHUIgAiA///////////////////" +
+ "//////////8iICKlIxIiAiIHImEiUiJqImsiGiI9Ih0iNSIrIiz//////////////////yErIDAmbyZtJmogICAhALb//////////yXv/////////////////////////////////////////////////xD/Ef8S/xP/FP8V/xb/F/8Y/xn///////////////////8h" +
+ "/yL/I/8k/yX/Jv8n/yj/Kf8q/yv/LP8t/y7/L/8w/zH/Mv8z/zT/Nf82/zf/OP85/zr///////////////////9B/0L/Q/9E/0X/Rv9H/0j/Sf9K/0v/TP9N/07/T/9Q/1H/Uv9T/1T/Vf9W/1f/WP9Z/1r//////////zBBMEIwQzBEMEUwRjBHMEgwSTBKMEswTDBN" +
+ "ME4wTzBQMFEwUjBTMFQwVTBWMFcwWDBZMFowWzBcMF0wXjBfMGAwYTBiMGMwZDBlMGYwZzBoMGkwajBrMGwwbTBuMG8wcDBxMHIwczB0MHUwdjB3MHgweTB6MHswfDB9MH4wfzCAMIEwgjCDMIQwhTCGMIcwiDCJMIowizCMMI0wjjCPMJAwkTCSMJP/////////////" +
+ "////////////////////////MKEwojCjMKQwpTCmMKcwqDCpMKowqzCsMK0wrjCvMLAwsTCyMLMwtDC1MLYwtzC4MLkwujC7MLwwvTC+ML8wwDDBMMIwwzDEMMUwxjDHMMgwyTDKMMswzDDNMM4wzzDQMNEw0jDTMNQw1TDWMNcw2DDZMNow2zDcMN0w3jDf//8w4DDh" +
+ "MOIw4zDkMOUw5jDnMOgw6TDqMOsw7DDtMO4w7zDwMPEw8jDzMPQw9TD2/////////////////////wORA5IDkwOUA5UDlgOXA5gDmQOaA5sDnAOdA54DnwOgA6EDowOkA6UDpgOnA6gDqf////////////////////8DsQOyA7MDtAO1A7YDtwO4A7kDugO7A7wDvQO+" +
+ "A78DwAPBA8MDxAPFA8YDxwPIA8n/////////////////////////////////////////////////////////////////////////////////////////////////////////////BBAEEQQSBBMEFAQVBAEEFgQXBBgEGQQaBBsEHAQdBB4EHwQgBCEEIgQjBCQEJQQm" +
+ "BCcEKAQpBCoEKwQsBC0ELgQv////////////////////////////////////////BDAEMQQyBDMENAQ1BFEENgQ3BDgEOQQ6BDsEPAQ9//8EPgQ/BEAEQQRCBEMERARFBEYERwRIBEkESgRLBEwETQROBE///////////////////////////////////yUAJQIlDCUQ" +
+ "JRglFCUcJSwlJCU0JTwlASUDJQ8lEyUbJRclIyUzJSslOyVLJSAlLyUoJTclPyUdJTAlJSU4JUL/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "/////////////////////////////////////06cVRZaA5Y/VMBhG2MoWfaQIoR1gxx6UGCqY+FuJWXthGaCppv1aJNXJ2WhYnFbm1nQhnuY9H1ifb6bjmIWfJ+It1uJXrVjCWaXaEiVx5eNZ09O5U8KT01PnVBJVvJZN1nUWgFcCWDfYQ9hcGYTaQVwunVPdXB5+32t" +
+ "fe+Aw4QOiGOLApBVkHpTO06VTqVX34CykMF4704AWPFuopA4ejKDKIKLnC9RQVNwVL1U4VbgWftfFZjybeuA5IUt////////lmKWcJagl/tUC1PzW4dwz3+9j8KW6FNvnVx6uk4ReJOB/G4mVhhVBGsdhRqcO1nlU6ltZnTclY9WQk6RkEuW8oNPmQxT4VW2WzBfcWYg" +
+ "ZvNoBGw4bPNtKXRbdsh6Tpg0gvGIW4pgku1tsnWrdsqZxWCmiwGNipWyaY5TrVGG//9XElgwWURbtF72YChjqWP0bL9vFHCOcRRxWXHVcz9+AYJ2gtGFl5BgkludG1hpZbxsWnUlUflZLlllX4Bf3GK8ZfpqKmsna7Rzi3/BiVadLJ0OnsRcoWyWg3tRBFxLYbaBxmh2" +
+ "cmFOWU/6U3hgaW4pek+X804LUxZO7k9VTz1PoU9zUqBT71YJWQ9awVu2W+F50WaHZ5xntmtMbLNwa3PCeY15vno8e4eCsYLbgwSDd4Pvg9OHZoqyVimMqI/mkE6XHoaKT8Rc6GIRcll1O4Hlgr2G/ozAlsWZE5nVTstPGonjVt5YSljKXvtf62AqYJRgYmHQYhJi0GU5" +
+ "////////m0FmZmiwbXdwcHVMdoZ9dYKlh/mVi5aOjJ1R8VK+WRZUs1uzXRZhaGmCba94jYTLiFeKcpOnmrhtbJmohtlXo2f/hs6SDlKDVodUBF7TYuFkuWg8aDhru3NyeLp6a4maidKNa48DkO2Vo5aUl2lbZlyzaX2YTZhOY5t7IGor//9qf2i2nA1vX1JyVZ1gcGLs" +
+ "bTtuB27RhFuJEI9EThScOVP2aRtqOpeEaCpRXHrDhLKR3JOMVludKGgigwWEMXylUgiCxXTmTn5Pg1GgW9JSClLYUudd+1WaWCpZ5luMW5hb215yXnlgo2EfYWNhvmPbZWJn0WhTaPprPmtTbFdvIm+Xb0V0sHUYduN3C3r/e6F8IX3pfzZ/8ICdgmaDnomzisyMq5CE" +
+ "lFGVk5WRlaKWZZfTmSiCGE44VCtcuF3Mc6l2THc8XKl/640LlsGYEZhUmFhPAU8OU3FVnFZoV/pZR1sJW8RckF4MXn5fzGPuZzpl12XiZx9oy2jE////////al9eMGvFbBdsfXV/eUhbY3oAfQBfvYmPihiMtI13jsyPHZjimg6bPE6AUH1RAFmTW5xiL2KAZOxrOnKg" +
+ "dZF5R3+ph/uKvItwY6yDypegVAlUA1WraFRqWIpweCdndZ7NU3RbooEahlCQBk4YTkVOx08RU8pUOFuuXxNgJWVR//9nPWxCbHJs43B4dAN6dnquewh9Gnz+fWZl53JbU7tcRV3oYtJi4GMZbiCGWooxjd2S+G8BeaabWk6oTqtOrE+bT6BQ0VFHevZRcVH2U1RTIVN/" +
+ "U+tVrFiDXOFfN19KYC9gUGBtYx9lWWpLbMFywnLtd++A+IEFggiFTpD3k+GX/5lXmlpO8FHdXC1mgWltXEBm8ml1c4loUHyBUMVS5FdHXf6TJmWkayNrPXQ0eYF5vXtLfcqCuYPMiH+JX4s5j9GR0VQfkoBOXVA2U+VTOnLXc5Z36YLmjq+ZxpnImdJRd2Eahl5VsHp6" +
+ "UHZb05BHloVOMmrbkedcUVxI////////Y5h6n2yTl3SPYXqqcYqWiHyCaBd+cGhRk2xS8lQbhauKE3+kjs2Q4VNmiIh5QU/CUL5SEVFEVVNXLXPqV4tZUV9iX4RgdWF2YWdhqWOyZDplbGZvaEJuE3Vmej18+31MfZl+S39rgw6DSobNigiKY4tmjv2YGp2PgriPzpvo" +
+ "//9Sh2IfZINvwJaZaEFQkWsgbHpvVHp0fVCIQIojZwhO9lA5UCZQZVF8UjhSY1WnVw9YBVrMXvphsmH4YvNjcmkcailyfXKscy54FHhvfXl3DICpiYuLGYzijtKQY5N1lnqYVZoTnnhRQ1OfU7Nee18mbhtukHOEc/59Q4I3igCK+pZQTk5QC1PkVHxW+lnRW2Rd8V6r" +
+ "XydiOGVFZ69uVnLQfMqItIChgOGD8IZOioeN6JI3lseYZ58TTpROkk8NU0hUSVQ+Wi9fjF+hYJ9op2qOdFp4gYqeiqSLd5GQTl6byU6kT3xPr1AZUBZRSVFsUp9SuVL+U5pT41QR////////VA5ViVdRV6JZfVtUW11bj13lXedd9154XoNeml63XxhgUmFMYpdi2GOn" +
+ "ZTtmAmZDZvRnbWghaJdpy2xfbSptaW4vbp11MnaHeGx6P3zgfQV9GH1efbGAFYADgK+AsYFUgY+CKoNSiEyIYYsbjKKM/JDKkXWScXg/kvyVpJZN//+YBZmZmtidO1JbUqtT91QIWNVi92/gjGqPX565UUtSO1RKVv16QJF3nWCe0nNEbwmBcHURX/1g2pqoctuPvGtk" +
+ "mANOylbwV2RYvlpaYGhhx2YPZgZoOWixbfd11X06gm6bQk6bT1BTyVUGXW9d5l3uZ/tsmXRzeAKKUJOWiN9XUF6nYytQtVCsUY1nAFTJWF5Zu1uwX2liTWOhaD1rc24IcH2Rx3KAeBV4JnltZY59MIPciMGPCZabUmRXKGdQf2qMoVG0V0KWKlg6aYqAtFSyXQ5X/HiV" +
+ "nfpPXFJKVItkPmYoZxRn9XqEe1Z9IpMvaFybrXs5UxlRilI3////////W99i9mSuZOZnLWu6hamW0XaQm9ZjTJMGm6t2v2ZSTglQmFPCXHFg6GSSZWNoX3Hmc8p1I3uXfoKGlYuDjNuReJkQZaxmq2uLTtVO1E86T39SOlP4U/JV41bbWOtZy1nJWf9bUFxNXgJeK1/X" +
+ "YB1jB2UvW1xlr2W9ZehnnWti//9re2wPc0V5SXnBfPh9GX0rgKKBAoHziZaKXoppimaKjIrujMeM3JbMmPxrb06LTzxPjVFQW1db+mFIYwFmQmshbstsu3I+dL111HjBeTqADIAzgeqElI+ebFCef18Pi1idK3r6jvhbjZbrTgNT8Vf3WTFayVukYIluf28Gdb6M6luf" +
+ "hQB74FByZ/SCnVxhhUp+HoIOUZlcBGNojWZlnHFueT59F4AFix2OypBuhseQqlAfUvpcOmdTcHxyNZFMkciTK4LlW8JfMWD5TjtT1luIYktnMWuKculz4HougWuNo5FSmZZRElPXVGpb/2OIajl9rJcAVtpTzlRo////////W5dcMV3eT+5hAWL+bTJ5wHnLfUJ+TX/S" +
+ "ge2CH4SQiEaJcouQjnSPL5AxkUuRbJbGkZxOwE9PUUVTQV+TYg5n1GxBbgtzY34mkc2Sg1PUWRlbv23ReV1+LnybWH5xn1H6iFOP8E/KXPtmJXeseuOCHJn/UcZfqmXsaW9riW3z//9ulm9kdv59FF3hkHWRh5gGUeZSHWJAZpFm2W4aXrZ90n9yZviFr4X3ivhSqVPZ" +
+ "WXNej1+QYFWS5JZkULdRH1LdUyBTR1PsVOhVRlUxVhdZaFm+WjxbtVwGXA9cEVwaXoReil7gX3Bif2KEYttjjGN3ZgdmDGYtZnZnfmiiah9qNWy8bYhuCW5YcTxxJnFndcd3AXhdeQF5ZXnweuB7EXynfTmAloPWhIuFSYhdiPOKH4o8ilSKc4xhjN6RpJJmk36UGJac" +
+ "l5hOCk4ITh5OV1GXUnBXzlg0WMxbIl44YMVk/mdhZ1ZtRHK2dXN6Y4S4i3KRuJMgVjFX9Jj+////////Yu1pDWuWce1+VIB3gnKJ5pjfh1WPsVw7TzhP4U+1VQdaIFvdW+lfw2FOYy9lsGZLaO5pm214bfF1M3W5dx95XnnmfTOB44KvhaqJqoo6jquPm5Aykd2XB066" +
+ "TsFSA1h1WOxcC3UaXD2BTooKj8WWY5dteyWKz5gIkWJW81Oo//+QF1Q5V4JeJWOobDRwindhfIt/4IhwkEKRVJMQkxiWj3RemsRdB11pZXBnoo2olttjbmdJaRmDxZgXlsCI/m+EZHpb+E4WcCx1XWYvUcRSNlLiWdNfgWAnYhBlP2V0Zh9mdGjyaBZrY24FcnJ1H3bb" +
+ "fL6AVljwiP2Jf4qgipOKy5AdkZKXUpdZZYl6DoEGlrteLWDcYhplpWYUZ5B383pNfE1+PoEKjKyNZI3hjl94qVIHYtljpWRCYpiKLXqDe8CKrJbqfXaCDIdJTtlRSFNDU2Bbo1wCXBZd3WImYkdksGgTaDRsyW1FbRdn029ccU5xfWXLen97rX3a////////fkp/qIF6" +
+ "ghuCOYWmim6Mzo31kHiQd5KtkpGVg5uuUk1VhG84cTZRaHmFflWBs3zOVkxYUVyoY6pm/mb9aVpy2XWPdY55DnlWed98l30gfUSGB4o0ljuQYZ8gUOdSdVPMU+JQCVWqWO5ZT3I9W4tcZFMdYONg82NcY4NjP2O7//9kzWXpZvld42nNaf1vFXHlTol16Xb4epN8333P" +
+ "fZyAYYNJg1iEbIS8hfuIxY1wkAGQbZOXlxyaElDPWJdhjoHThTWNCJAgT8NQdFJHU3Ngb2NJZ19uLI2zkB9P11xejMplz32aU1KIllF2Y8NbWFtrXApkDWdRkFxO1lkaWSpscIpRVT5YFVmlYPBiU2fBgjVpVZZAmcSaKE9TWAZb/oAQXLFeL1+FYCBhS2I0Zv9s8G7e" +
+ "gM6Bf4LUiIuMuJAAkC6Wip7bm9tO41PwWSd7LJGNmEyd+W7dcCdTU1VEW4ViWGKeYtNsom/vdCKKF5Q4b8GK/oM4UeeG+FPq////////U+lPRpBUj7BZaoExXf166o+/aNqMN3L4nEhqPYqwTjlTWFYGV2ZixWOiZeZrTm3hbltwrXfteu97qn27gD2AxobLipWTW1bj" +
+ "WMdfPmWtZpZqgGu1dTeKx1Akd+VXMF8bYGVmemxgdfR6Gn9ugfSHGJBFmbN7yXVcevl7UYTE//+QEHnpepKDNlrhd0BOLU7yW5lf4GK9Zjxn8WzohmuId4o7kU6S85nQahdwJnMqgueEV4yvTgFRRlHLVYtb9V4WXjNegV8UXzVfa1+0YfJjEWaiZx1vbnJSdTp3OoB0" +
+ "gTmBeId2ir+K3I2FjfOSmpV3mAKc5VLFY1d29GcVbIhzzYzDk66Wc20lWJxpDmnMj/2TmnXbkBpYWmgCY7Rp+09Dbyxn2I+7hSZ9tJNUaT9vcFdqWPdbLH0scipUCpHjnbROrU9OUFxQdVJDjJ5USFgkW5peHV6VXq1e918fYIxitWM6Y9Bor2xAeId5jnoLfeCCR4oC" +
+ "iuaORJAT////////kLiRLZHYnw5s5WRYZOJldW70doR7G5Bpk9FuulTyX7lkpI9Nj+2SRFF4WGtZKVxVXpdt+36PdRyMvI7imFtwuU8da79vsXUwlvtRTlQQWDVYV1msXGBfkmWXZ1xuIXZ7g9+M7ZAUkP2TTXgleDpSql6mVx9ZdGASUBJRWlGs//9RzVIAVRBYVFhY" +
+ "WVdblVz2XYtgvGKVZC1ncWhDaLxo33bXbdhub22bcG9xyF9Tddh5d3tJe1R7UnzWfXFSMIRjhWmF5IoOiwSMRo4PkAOQD5QZlnaYLZowldhQzVLVVAxYAlwOYadknm0ed7N65YD0hASQU5KFXOCdB1M/X5dfs22ccnl3Y3m/e+Rr0nLsiq1oA2phUfh6gWk0XEqc9oLr" +
+ "W8WRSXAeVnhcb2DHZWZsjIxakEGYE1RRZseSDVlIkKNRhU5NUeqFmYsOcFhjepNLaWKZtH4EdXdTV2lgjt+W42xdToxcPF8Qj+lTAozRgImGeV7/ZeVOc1Fl////////WYJcP5fuTvtZil/Nio1v4XmweWJb54RxcytxsV50X/Vje2SaccN8mE5DXvxOS1fcVqJgqW/D" +
+ "fQ2A/YEzgb+PsomXhqRd9GKKZK2Jh2d3bOJtPnQ2eDRaRn91gq2ZrE/zXsNi3WOSZVdnb3bDckyAzIC6jymRTVANV/lakmiF//9pc3Fkcv2Mt1jyjOCWapAZh3955HfnhClPL1JlU1pizWfPbMp2fXuUfJWCNoWEj+tm3W8gcgZ+G4OrmcGeplH9e7F4cnu4gId7SGro" +
+ "XmGAjHVRdWBRa5Jibox2epGXmupPEH9wYpx7T5WlnOlWelhZhuSWvE80UiRTSlPNU9teBmQsZZFnf2w+bE5ySHKvc+11VH5BgiyF6Yype8SRxnFpmBKY72M9Zml1anbkeNCFQ4buUypTUVQmWYNeh198YLJiSWJ5YqtlkGvUbMx1snaueJF52H3Lf3eApYirirmMu5B/" +
+ "l16Y22oLfDhQmVw+X65nh2vYdDV3CX+O////////nztnynoXUzl1i5rtX2aBnYPxgJhfPF/FdWJ7RpA8aGdZ61qbfRB2fossT/VfamoZbDdvAnTieWiIaIpVjHle32PPdcV50oLXkyiS8oSchu2cLVTBX2xljG1ccBWMp4zTmDtlT3T2Tg1O2FfgWStaZlvMUaheA16c" +
+ "YBZidmV3//9lp2ZubW5yNnsmgVCBmoKZi1yMoIzmjXSWHJZET65kq2tmgh6EYYVqkOhcAWlTmKiEeoVXTw9Sb1+pXkVnDXmPgXmJB4mGbfVfF2JVbLhOz3Jpm5JSBlQ7VnRYs2GkYm5xGllufIl83n0blvBlh4BeThlPdVF1WEBeY15zXwpnxE4mhT2ViZZbfHOYAVD7" +
+ "WMF2VninUiV3pYURe4ZQT1kJckd7x33oj7qP1JBNT79SyVopXwGXrU/dgheS6lcDY1VraXUriNyPFHpCUt9Yk2FVYgpmrmvNfD+D6VAjT/hTBVRGWDFZSVudXPBc710pXpZisWNnZT5luWcL////////bNVs4XD5eDJ+K4DegrOEDITshwKJEooqjEqQppLSmP2c851s" +
+ "Tk9OoVCNUlZXSlmoXj1f2F/ZYj9mtGcbZ9Bo0lGSfSGAqoGoiwCMjIy/kn6WMlQgmCxTF1DVU1xYqGSyZzRyZ3dmekaR5lLDbKFrhlgAXkxZVGcsf/tR4XbG//9kaXjom1Seu1fLWblmJ2eaa85U6WnZXlWBnGeVm6pn/pxSaF1Opk/jU8hiuWcrbKuPxE+tfm2ev04H" +
+ "YWJugG8rhRNUc2cqm0Vd83uVXKxbxoccbkqE0XoUgQhZmXyNbBF3IFLZWSJxIXJfd9uXJ51haQtaf1oYUaVUDVR9Zg5234/3kpic9Fnqcl1uxVFNaMl9v33sl2KeumR4aiGDAlmEW19r23MbdvJ9soAXhJlRMmcontl27mdiUv+ZBVwkYjt8foywVU9gtn0LlYBTAU5f" +
+ "UbZZHHI6gDaRzl8ld+JThF95fQSFrIozjo2XVmfzha6UU2EJYQhsuXZS////////iu2POFUvT1FRKlLHU8tbpV59YKBhgmPWZwln2m5nbYxzNnM3dTF5UIjVipiQSpCRkPWWxIeNWRVOiE9ZTg6KiY8/mBBQrV58WZZbuV64Y9pj+mTBZtxpSmnYbQtutnGUdSh6r3+K" +
+ "gACESYTJiYGLIY4KkGWWfZkKYX5ikWsy//9sg210f8x//G3Af4WHuoj4Z2WDsZg8lvdtG31hhD2Rak5xU3VdUGsEb+uFzYYtiadSKVQPXGVnTmiodAZ0g3XiiM+I4ZHMluKWeF+Lc4d6y4ROY6B1ZVKJbUFunHQJdVl4a3ySloZ63J+NT7ZhbmXFhlxOhk6uUNpOIVHM" +
+ "W+5lmWiBbbxzH3ZCd616HHzngm+K0pB8kc+WdZgYUpt90VArU5hnl23LcdB0M4HojyqWo5xXnp90YFhBbZl9L5heTuRPNk+LUbdSsV26YBxzsnk8gtOSNJa3lvaXCp6Xn2Jmpmt0UhdSo3DIiMJeyWBLYZBvI3FJfD599IBv////////hO6QI5MsVEKbb2rTcImMwo3v" +
+ "lzJStFpBXspfBGcXaXxplG1qbw9yYnL8e+2AAYB+h0uQzlFtnpN5hICLkzKK1lAtVIyKcWtqjMSBB2DRZ6Cd8k6ZTpicEIprhcGFaGkAbn54l4FV////////////////////////////////////////////////////////////////////////////////////////" +
+ "/////////////////////////////18MThBOFU4qTjFONk48Tj9OQk5WTlhOgk6FjGtOioISXw1Ojk6eTp9OoE6iTrBOs062Ts5OzU7ETsZOwk7XTt5O7U7fTvdPCU9aTzBPW09dT1dPR092T4hPj0+YT3tPaU9wT5FPb0+GT5ZRGE/UT99Pzk/YT9tP0U/aT9BP5E/l" +
+ "UBpQKFAUUCpQJVAFTxxP9lAhUClQLE/+T+9QEVAGUENQR2cDUFVQUFBIUFpQVlBsUHhQgFCaUIVQtFCy////////UMlQylCzUMJQ1lDeUOVQ7VDjUO5Q+VD1UQlRAVECURZRFVEUURpRIVE6UTdRPFE7UT9RQFFSUUxRVFFievhRaVFqUW5RgFGCVthRjFGJUY9RkVGT" +
+ "UZVRllGkUaZRolGpUapRq1GzUbFRslGwUbVRvVHFUclR21HghlVR6VHt//9R8FH1Uf5SBFILUhRSDlInUipSLlIzUjlST1JEUktSTFJeUlRSalJ0UmlSc1J/Un1SjVKUUpJScVKIUpGPqI+nUqxSrVK8UrVSwVLNUtdS3lLjUuaY7VLgUvNS9VL4UvlTBlMIdThTDVMQ" +
+ "Uw9TFVMaUyNTL1MxUzNTOFNAU0ZTRU4XU0lTTVHWU15TaVNuWRhTe1N3U4JTllOgU6ZTpVOuU7BTtlPDfBKW2VPfZvxx7lPuU+hT7VP6VAFUPVRAVCxULVQ8VC5UNlQpVB1UTlSPVHVUjlRfVHFUd1RwVJJUe1SAVHZUhFSQVIZUx1SiVLhUpVSsVMRUyFSo////////" +
+ "VKtUwlSkVL5UvFTYVOVU5lUPVRRU/VTuVO1U+lTiVTlVQFVjVUxVLlVcVUVVVlVXVThVM1VdVZlVgFSvVYpVn1V7VX5VmFWeVa5VfFWDValVh1WoVdpVxVXfVcRV3FXkVdRWFFX3VhZV/lX9VhtV+VZOVlBx31Y0VjZWMlY4//9Wa1ZkVi9WbFZqVoZWgFaKVqBWlFaP" +
+ "VqVWrla2VrRWwla8VsFWw1bAVshWzlbRVtNW11buVvlXAFb/VwRXCVcIVwtXDVcTVxhXFlXHVxxXJlc3VzhXTlc7V0BXT1dpV8BXiFdhV39XiVeTV6BXs1ekV6pXsFfDV8ZX1FfSV9NYClfWV+NYC1gZWB1YclghWGJYS1hwa8BYUlg9WHlYhVi5WJ9Yq1i6WN5Yu1i4" +
+ "WK5YxVjTWNFY11jZWNhY5VjcWORY31jvWPpY+Vj7WPxY/VkCWQpZEFkbaKZZJVksWS1ZMlk4WT560llVWVBZTllaWVhZYllgWWdZbFlp////////WXhZgVmdT15Pq1mjWbJZxlnoWdxZjVnZWdpaJVofWhFaHFoJWhpaQFpsWklaNVo2WmJaalqaWrxavlrLWsJavVrj" +
+ "Wtda5lrpWtZa+lr7WwxbC1sWWzJa0FsqWzZbPltDW0VbQFtRW1VbWltbW2VbaVtwW3NbdVt4ZYhbeluA//9bg1umW7hbw1vHW8lb1FvQW+Rb5lviW95b5VvrW/Bb9lvzXAVcB1wIXA1cE1wgXCJcKFw4XDlcQVxGXE5cU1xQXE9bcVxsXG5OYlx2XHlcjFyRXJRZm1yr" +
+ "XLtctly8XLdcxVy+XMdc2VzpXP1c+lztXYxc6l0LXRVdF11cXR9dG10RXRRdIl0aXRldGF1MXVJdTl1LXWxdc112XYddhF2CXaJdnV2sXa5dvV2QXbddvF3JXc1d013SXdZd213rXfJd9V4LXhpeGV4RXhteNl43XkReQ15AXk5eV15UXl9eYl5kXkdedV52XnqevF5/" +
+ "XqBewV7CXshe0F7P////////XtZe417dXtpe217iXuFe6F7pXuxe8V7zXvBe9F74Xv5fA18JX11fXF8LXxFfFl8pXy1fOF9BX0hfTF9OXy9fUV9WX1dfWV9hX21fc193X4Nfgl9/X4pfiF+RX4dfnl+ZX5hfoF+oX61fvF/WX/tf5F/4X/Ff3WCzX/9gIWBg//9gGWAQ" +
+ "YClgDmAxYBtgFWArYCZgD2A6YFpgQWBqYHdgX2BKYEZgTWBjYENgZGBCYGxga2BZYIFgjWDnYINgmmCEYJtglmCXYJJgp2CLYOFguGDgYNNgtF/wYL1gxmC1YNhhTWEVYQZg9mD3YQBg9GD6YQNhIWD7YPFhDWEOYUdhPmEoYSdhSmE/YTxhLGE0YT1hQmFEYXNhd2FY" +
+ "YVlhWmFrYXRhb2FlYXFhX2FdYVNhdWGZYZZhh2GsYZRhmmGKYZFhq2GuYcxhymHJYfdhyGHDYcZhumHLf3lhzWHmYeNh9mH6YfRh/2H9Yfxh/mIAYghiCWINYgxiFGIb////////Yh5iIWIqYi5iMGIyYjNiQWJOYl5iY2JbYmBiaGJ8YoJiiWJ+YpJik2KWYtRig2KU" +
+ "Ytdi0WK7Ys9i/2LGZNRiyGLcYsxiymLCYsdim2LJYwxi7mLxYydjAmMIYu9i9WNQYz5jTWQcY09jlmOOY4Bjq2N2Y6Njj2OJY59jtWNr//9jaWO+Y+ljwGPGY+NjyWPSY/ZjxGQWZDRkBmQTZCZkNmUdZBdkKGQPZGdkb2R2ZE5lKmSVZJNkpWSpZIhkvGTaZNJkxWTH" +
+ "ZLtk2GTCZPFk54IJZOBk4WKsZONk72UsZPZk9GTyZPplAGT9ZRhlHGUFZSRlI2UrZTRlNWU3ZTZlOHVLZUhlVmVVZU1lWGVeZV1lcmV4ZYJlg4uKZZtln2WrZbdlw2XGZcFlxGXMZdJl22XZZeBl4WXxZ3JmCmYDZftnc2Y1ZjZmNGYcZk9mRGZJZkFmXmZdZmRmZ2Zo" +
+ "Zl9mYmZwZoNmiGaOZolmhGaYZp1mwWa5Zslmvma8////////ZsRmuGbWZtpm4GY/ZuZm6WbwZvVm92cPZxZnHmcmZyeXOGcuZz9nNmdBZzhnN2dGZ15nYGdZZ2NnZGeJZ3BnqWd8Z2pnjGeLZ6ZnoWeFZ7dn72e0Z+xns2fpZ7hn5GfeZ91n4mfuZ7lnzmfGZ+dqnGge" +
+ "aEZoKWhAaE1oMmhO//9os2graFloY2h3aH9on2iPaK1olGidaJtog2quaLlodGi1aKBoumkPaI1ofmkBaMppCGjYaSJpJmjhaQxozWjUaOdo1Wk2aRJpBGjXaONpJWj5aOBo72koaSppGmkjaSFoxml5aXdpXGl4aWtpVGl+aW5pOWl0aT1pWWkwaWFpXmldaYFpammy" +
+ "aa5p0Gm/acFp02m+ac5b6GnKad1pu2nDaadqLmmRaaBpnGmVabRp3mnoagJqG2n/awpp+WnyaedqBWmxah5p7WoUaetqCmoSasFqI2oTakRqDGpyajZqeGpHamJqWWpmakhqOGoiapBqjWqgaoRqomqj////////apeGF2q7asNqwmq4arNqrGreatFq32qqatpq6mr7" +
+ "awWGFmr6axJrFpsxax9rOGs3dtxrOZjua0drQ2tJa1BrWWtUa1trX2tha3hreWt/a4BrhGuDa41rmGuVa55rpGuqa6trr2uya7Frs2u3a7xrxmvLa9Nr32vsa+tr82vv//+evmwIbBNsFGwbbCRsI2xebFVsYmxqbIJsjWyabIFsm2x+bGhsc2ySbJBsxGzxbNNsvWzX" +
+ "bMVs3WyubLFsvmy6bNts72zZbOptH4hNbTZtK209bThtGW01bTNtEm0MbWNtk21kbVpteW1ZbY5tlW/kbYVt+W4VbgpttW3HbeZtuG3Gbext3m3Mbeht0m3Fbfpt2W3kbdVt6m3ubi1ubm4ubhlucm5fbj5uI25rbitudm5Nbh9uQ246bk5uJG7/bh1uOG6CbqpumG7J" +
+ "brdu0269bq9uxG6ybtRu1W6PbqVuwm6fb0FvEXBMbuxu+G7+bz9u8m8xbu9vMm7M////////bz5vE273b4Zvem94b4FvgG9vb1tv829tb4JvfG9Yb45vkW/Cb2Zvs2+jb6FvpG+5b8Zvqm/fb9Vv7G/Ub9hv8W/ub9twCXALb/pwEXABcA9v/nAbcBpvdHAdcBhwH3Aw" +
+ "cD5wMnBRcGNwmXCScK9w8XCscLhws3CucN9wy3Dd//9w2XEJcP1xHHEZcWVxVXGIcWZxYnFMcVZxbHGPcftxhHGVcahxrHHXcblxvnHScclx1HHOceBx7HHncfVx/HH5cf9yDXIQchtyKHItcixyMHIycjtyPHI/ckByRnJLclhydHJ+coJygXKHcpJylnKicqdyuXKy" +
+ "csNyxnLEcs5y0nLicuBy4XL5cvdQD3MXcwpzHHMWcx1zNHMvcylzJXM+c05zT57Yc1dzanNoc3BzeHN1c3tzenPIc7NzznO7c8Bz5XPuc950onQFdG90JXP4dDJ0OnRVdD90X3RZdEF0XHRpdHB0Y3RqdHZ0fnSLdJ50p3TKdM901HPx////////dOB043TndOl07nTy" +
+ "dPB08XT4dPd1BHUDdQV1DHUOdQ11FXUTdR51JnUsdTx1RHVNdUp1SXVbdUZ1WnVpdWR1Z3VrdW11eHV2dYZ1h3V0dYp1iXWCdZR1mnWddaV1o3XCdbN1w3W1db11uHW8dbF1zXXKddJ12XXjdd51/nX///91/HYBdfB1+nXydfN2C3YNdgl2H3YndiB2IXYidiR2NHYw" +
+ "djt2R3ZIdkZ2XHZYdmF2YnZodml2anZndmx2cHZydnZ2eHZ8doB2g3aIdot2jnaWdpN2mXaadrB2tHa4drl2unbCds121nbSdt524Xbldud26oYvdvt3CHcHdwR3KXckdx53JXcmdxt3N3c4d0d3Wndod2t3W3dld393fnd5d453i3eRd6B3nnewd7Z3uXe/d7x3vXe7" +
+ "d8d3zXfXd9p33Hfjd+53/HgMeBJ5JnggeSp4RXiOeHR4hnh8eJp4jHijeLV4qniveNF4xnjLeNR4vni8eMV4ynjs////////eOd42nj9ePR5B3kSeRF5GXkseSt5QHlgeVd5X3laeVV5U3l6eX95inmdeaefS3mqea55s3m5ebp5yXnVeed57HnheeN6CHoNehh6GXog" +
+ "eh95gHoxejt6Pno3ekN6V3pJemF6Ynppn516cHp5en16iHqXepV6mHqWeql6yHqw//96tnrFesR6v5CDesd6ynrNes961XrTetl62nrdeuF64nrmeu168HsCew97CnsGezN7GHsZex57NXsoezZ7UHt6ewR7TXsLe0x7RXt1e2V7dHtne3B7cXtse257nXuYe597jXuc" +
+ "e5p7i3uSe497XXuZe8t7wXvMe897tHvGe9176XwRfBR75nvlfGB8AHwHfBN783v3fBd8DXv2fCN8J3wqfB98N3wrfD18THxDfFR8T3xAfFB8WHxffGR8VnxlfGx8dXyDfJB8pHytfKJ8q3yhfKh8s3yyfLF8rny5fL18wHzFfMJ82HzSfNx84ps7fO988nz0fPZ8+n0G" +
+ "////////fQJ9HH0VfQp9RX1LfS59Mn0/fTV9Rn1zfVZ9Tn1yfWh9bn1PfWN9k32JfVt9j319fZt9un2ufaN9tX3Hfb19q349faJ9r33cfbh9n32wfdh93X3kfd59+33yfeF+BX4KfiN+IX4SfjF+H34Jfgt+In5GfmZ+O341fjl+Q343//9+Mn46fmd+XX5Wfl5+WX5a" +
+ "fnl+an5pfnx+e36DfdV+fY+ufn9+iH6Jfox+kn6QfpN+lH6Wfo5+m36cfzh/On9Ff0x/TX9Of1B/UX9Vf1R/WH9ff2B/aH9pf2d/eH+Cf4Z/g3+If4d/jH+Uf55/nX+af6N/r3+yf7l/rn+2f7iLcX/Ff8Z/yn/Vf9R/4X/mf+l/83/5mNyABoAEgAuAEoAYgBmAHIAh" +
+ "gCiAP4A7gEqARoBSgFiAWoBfgGKAaIBzgHKAcIB2gHmAfYB/gISAhoCFgJuAk4CagK1RkICsgNuA5YDZgN2AxIDagNaBCYDvgPGBG4EpgSOBL4FL////////louBRoE+gVOBUYD8gXGBboFlgWaBdIGDgYiBioGAgYKBoIGVgaSBo4FfgZOBqYGwgbWBvoG4gb2BwIHC" +
+ "gbqByYHNgdGB2YHYgciB2oHfgeCB54H6gfuB/oIBggKCBYIHggqCDYIQghaCKYIrgjiCM4JAglmCWIJdglqCX4Jk//+CYoJogmqCa4IugnGCd4J4gn6CjYKSgquCn4K7gqyC4YLjgt+C0oL0gvOC+oOTgwOC+4L5gt6DBoLcgwmC2YM1gzSDFoMygzGDQIM5g1CDRYMv" +
+ "gyuDF4MYg4WDmoOqg5+DooOWgyODjoOHg4qDfIO1g3ODdYOgg4mDqIP0hBOD64POg/2EA4PYhAuDwYP3hAeD4IPyhA2EIoQgg72EOIUGg/uEbYQqhDyFWoSEhHeEa4SthG6EgoRphEaELIRvhHmENYTKhGKEuYS/hJ+E2YTNhLuE2oTQhMGExoTWhKGFIYT/hPSFF4UY" +
+ "hSyFH4UVhRSE/IVAhWOFWIVI////////hUGGAoVLhVWFgIWkhYiFkYWKhaiFbYWUhZuF6oWHhZyFd4V+hZCFyYW6hc+FuYXQhdWF3YXlhdyF+YYKhhOGC4X+hfqGBoYihhqGMIY/hk1OVYZUhl+GZ4ZxhpOGo4aphqqGi4aMhraGr4bEhsaGsIbJiCOGq4bUht6G6Ybs" +
+ "//+G34bbhu+HEocGhwiHAIcDhvuHEYcJhw2G+YcKhzSHP4c3hzuHJYcphxqHYIdfh3iHTIdOh3SHV4doh26HWYdTh2OHaogFh6KHn4eCh6+Hy4e9h8CH0JbWh6uHxIezh8eHxoe7h++H8ofgiA+IDYf+h/aH94gOh9KIEYgWiBWIIoghiDGINog5iCeIO4hEiEKIUohZ" +
+ "iF6IYohriIGIfoieiHWIfYi1iHKIgoiXiJKIroiZiKKIjYikiLCIv4ixiMOIxIjUiNiI2YjdiPmJAoj8iPSI6IjyiQSJDIkKiROJQ4keiSWJKokriUGJRIk7iTaJOIlMiR2JYIle////////iWaJZIltiWqJb4l0iXeJfomDiYiJiomTiZiJoYmpiaaJrImvibKJuom9" +
+ "ib+JwInaidyJ3YnnifSJ+IoDihaKEIoMihuKHYolijaKQYpbilKKRopIinyKbYpsimKKhYqCioSKqIqhipGKpYqmipqKo4rEis2KworaiuuK84rn//+K5IrxixSK4IriiveK3orbiwyLB4saiuGLFosQixeLIIszl6uLJosriz6LKItBi0yLT4tOi0mLVotbi1qLa4tf" +
+ "i2yLb4t0i32LgIuMi46LkouTi5aLmYuajDqMQYw/jEiMTIxOjFCMVYxijGyMeIx6jIKMiYyFjIqMjYyOjJSMfIyYYh2MrYyqjL2MsoyzjK6MtozIjMGM5IzjjNqM/Yz6jPuNBI0FjQqNB40PjQ2NEJ9OjROMzY0UjRaNZ41tjXGNc42BjZmNwo2+jbqNz43ajdaNzI3b" +
+ "jcuN6o3rjd+N4438jgiOCY3/jh2OHo4Qjh+OQo41jjCONI5K////////jkeOSY5MjlCOSI5ZjmSOYI4qjmOOVY52jnKOfI6BjoeOhY6EjouOio6TjpGOlI6ZjqqOoY6sjrCOxo6xjr6OxY7IjsuO247jjvyO+47rjv6PCo8FjxWPEo8ZjxOPHI8fjxuPDI8mjzOPO485" +
+ "j0WPQo8+j0yPSY9Gj06PV49c//+PYo9jj2SPnI+fj6OPrY+vj7eP2o/lj+KP6o/vkIeP9JAFj/mP+pARkBWQIZANkB6QFpALkCeQNpA1kDmP+JBPkFCQUZBSkA6QSZA+kFaQWJBekGiQb5B2lqiQcpCCkH2QgZCAkIqQiZCPkKiQr5CxkLWQ4pDkYkiQ25ECkRKRGZEy" +
+ "kTCRSpFWkViRY5FlkWmRc5FykYuRiZGCkaKRq5GvkaqRtZG0kbqRwJHBkcmRy5HQkdaR35HhkduR/JH1kfaSHpH/khSSLJIVkhGSXpJXkkWSSZJkkkiSlZI/kkuSUJKckpaSk5KbklqSz5K5kreS6ZMPkvqTRJMu////////kxmTIpMakyOTOpM1kzuTXJNgk3yTbpNW" +
+ "k7CTrJOtk5STuZPWk9eT6JPlk9iTw5Pdk9CTyJPklBqUFJQTlAOUB5QQlDaUK5Q1lCGUOpRBlFKURJRblGCUYpRelGqSKZRwlHWUd5R9lFqUfJR+lIGUf5WClYeVipWUlZaVmJWZ//+VoJWolaeVrZW8lbuVuZW+lcpv9pXDlc2VzJXVldSV1pXcleGV5ZXiliGWKJYu" +
+ "li+WQpZMlk+WS5Z3llyWXpZdll+WZpZylmyWjZaYlpWWl5aqlqeWsZaylrCWtJa2lriWuZbOlsuWyZbNiU2W3JcNltWW+ZcElwaXCJcTlw6XEZcPlxaXGZcklyqXMJc5lz2XPpdEl0aXSJdCl0mXXJdgl2SXZpdoUtKXa5dxl3mXhZd8l4GXepeGl4uXj5eQl5yXqJem" +
+ "l6OXs5e0l8OXxpfIl8uX3Jftn0+X8nrfl/aX9ZgPmAyYOJgkmCGYN5g9mEaYT5hLmGuYb5hw////////mHGYdJhzmKqYr5ixmLaYxJjDmMaY6ZjrmQOZCZkSmRSZGJkhmR2ZHpkkmSCZLJkumT2ZPplCmUmZRZlQmUuZUZlSmUyZVZmXmZiZpZmtma6ZvJnfmduZ3ZnY" +
+ "mdGZ7ZnumfGZ8pn7mfiaAZoPmgWZ4poZmiuaN5pFmkKaQJpD//+aPppVmk2aW5pXml+aYpplmmSaaZprmmqarZqwmryawJrPmtGa05rUmt6a35rimuOa5prvmuua7pr0mvGa95r7mwabGJsamx+bIpsjmyWbJ5somymbKpsumy+bMptEm0ObT5tNm06bUZtYm3Sbk5uD" +
+ "m5GblpuXm5+boJuom7SbwJvKm7mbxpvPm9Gb0pvjm+Kb5JvUm+GcOpvym/Gb8JwVnBScCZwTnAycBpwInBKcCpwEnC6cG5wlnCScIZwwnEecMpxGnD6cWpxgnGecdpx4nOec7JzwnQmdCJzrnQOdBp0qnSadr50jnR+dRJ0VnRKdQZ0/nT6dRp1I////////nV2dXp1k" +
+ "nVGdUJ1ZnXKdiZ2Hnaudb516nZqdpJ2pnbKdxJ3BnbuduJ26ncadz53Cndmd0534nead7Z3vnf2eGp4bnh6edZ55nn2egZ6InouejJ6SnpWekZ6dnqWeqZ64nqqerZdhnsyezp7PntCe1J7cnt6e3Z7gnuWe6J7v//+e9J72nvee+Z77nvye/Z8Hnwh2t58VnyGfLJ8+" +
+ "n0qfUp9Un2OfX59gn2GfZp9nn2yfap93n3Kfdp+Vn5yfoFgvaceQWXRkUdxxmf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
+ "/////////////////////////////////////////////w==";
+
+
+ private static short[] UNICODE_TO_QR_KANJI = new short[1 << 16];
+
+ 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) {
+ char c = (char)(((bytes[i] & 0xFF) << 8) | (bytes[i + 1] & 0xFF));
+ if (c == 0xFFFF)
+ continue;
+ assert UNICODE_TO_QR_KANJI[c] == -1;
+ UNICODE_TO_QR_KANJI[c] = (short)(i / 2);
+ }
+ }
+
+
+
+ /*---- Miscellaneous ----*/
+
+ private QrSegmentAdvanced() {} // Not instantiable
+
+}
From 2f4b0e8fd8749341c064bc73ab7d2f6fd4bf07a7 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Mon, 5 Nov 2018 04:37:27 +0000
Subject: [PATCH 55/85] Tweaked drawFormatBits() and drawDummyFormatBits() to
use end-exclusive range in second copy for clarity.
---
src/io/nayuki/fastqrcodegen/QrCode.java | 2 +-
src/io/nayuki/fastqrcodegen/QrTemplate.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index dcd958f..091bec5 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -356,7 +356,7 @@ public final class QrCode {
setModule(14 - i, 8, getBit(bits, i));
// Draw second copy
- for (int i = 0; i <= 7; i++)
+ for (int i = 0; i < 8; i++)
setModule(size - 1 - i, 8, getBit(bits, i));
for (int i = 8; i < 15; i++)
setModule(8, size - 15 + i, getBit(bits, i));
diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java
index 931d9b0..3ccfb40 100644
--- a/src/io/nayuki/fastqrcodegen/QrTemplate.java
+++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java
@@ -149,7 +149,7 @@ final class QrTemplate {
darkenFunctionModule(14 - i, 8, 0);
// Draw second copy
- for (int i = 0; i <= 7; i++)
+ for (int i = 0; i < 8; i++)
darkenFunctionModule(size - 1 - i, 8, 0);
for (int i = 8; i < 15; i++)
darkenFunctionModule(8, size - 15 + i, 0);
From df729db98bb9a4a7dbf1b18bb30891d634e820a4 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Mon, 5 Nov 2018 04:37:57 +0000
Subject: [PATCH 56/85] Added "data too long" exception class, changed code to
make use of it, updated Javadoc comments.
---
.../fastqrcodegen/DataTooLongException.java | 57 +++++++++++++++++++
src/io/nayuki/fastqrcodegen/QrCode.java | 19 ++++---
.../fastqrcodegen/QrCodeGeneratorWorker.java | 4 +-
.../fastqrcodegen/QrSegmentAdvanced.java | 15 +++--
4 files changed, 80 insertions(+), 15 deletions(-)
create mode 100644 src/io/nayuki/fastqrcodegen/DataTooLongException.java
diff --git a/src/io/nayuki/fastqrcodegen/DataTooLongException.java b/src/io/nayuki/fastqrcodegen/DataTooLongException.java
new file mode 100644
index 0000000..c6e4444
--- /dev/null
+++ b/src/io/nayuki/fastqrcodegen/DataTooLongException.java
@@ -0,0 +1,57 @@
+/*
+ * Fast QR Code generator library
+ *
+ * Copyright (c) Project Nayuki. (MIT License)
+ * https://www.nayuki.io/page/fast-qr-code-generator-library
+ *
+ * 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.fastqrcodegen;
+
+
+/**
+ * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include:
+ *
+ *
Decrease the error correction level if it was greater than {@code Ecc.LOW}.
+ *
If the advanced {@code encodeSegments()} function with 6 arguments or the
+ * {@code makeSegmentsOptimally()} function was called, then increase the maxVersion argument
+ * if it was less than {@link QrCode#MAX_VERSION}. (This advice does not apply to the other
+ * factory functions because they search all versions up to {@code QrCode.MAX_VERSION}.)
+ *
Split the text data into better or optimal segments in order to reduce the number of
+ * bits required. (See {@link QrSegmentAdvanced#makeSegmentsOptimally(String,QrCode.Ecc,int,int)
+ * QrSegmentAdvanced.makeSegmentsOptimally()}.)
+ *
Change the text or binary data to be shorter.
+ *
Change the text to fit the character set of a particular segment mode (e.g. alphanumeric).
+ *
Propagate the error upward to the caller/user.
+ *
+ * @see QrCode#encodeText(String, QrCode.Ecc)
+ * @see QrCode#encodeBinary(byte[], QrCode.Ecc)
+ * @see QrCode#encodeSegments(java.util.List, QrCode.Ecc)
+ * @see QrCode#encodeSegments(java.util.List, QrCode.Ecc, int, int, int, boolean)
+ * @see QrSegmentAdvanced#makeSegmentsOptimally(String, QrCode.Ecc, int, int)
+ */
+public class DataTooLongException extends IllegalArgumentException {
+
+ public DataTooLongException() {}
+
+
+ public DataTooLongException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index 091bec5..c24aae4 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -64,7 +64,7 @@ public final class QrCode {
* @param ecl the error correction level to use (not {@code null}) (boostable)
* @return a QR Code (not {@code null}) representing the text
* @throws NullPointerException if the text or error correction level is {@code null}
- * @throws IllegalArgumentException if the text fails to fit in the
+ * @throws DataTooLongException if the text fails to fit in the
* largest version QR Code at the ECL, which means it is too long
*/
public static QrCode encodeText(String text, Ecc ecl) {
@@ -84,7 +84,7 @@ public final class QrCode {
* @param ecl the error correction level to use (not {@code null}) (boostable)
* @return a QR Code (not {@code null}) representing the data
* @throws NullPointerException if the data or error correction level is {@code null}
- * @throws IllegalArgumentException if the data fails to fit in the
+ * @throws DataTooLongException if the data fails to fit in the
* largest version QR Code at the ECL, which means it is too long
*/
public static QrCode encodeBinary(byte[] data, Ecc ecl) {
@@ -109,7 +109,7 @@ public final class QrCode {
* @param ecl the error correction level to use (not {@code null}) (boostable)
* @return a QR Code (not {@code null}) representing the segments
* @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null}
- * @throws IllegalArgumentException if the segments fail to fit in the
+ * @throws DataTooLongException if the segments fail to fit in the
* largest version QR Code at the ECL, which means they are too long
*/
public static QrCode encodeSegments(List segs, Ecc ecl) {
@@ -137,8 +137,9 @@ public final class QrCode {
* @return a QR Code (not {@code null}) representing the segments
* @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null}
* @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40
- * or −1 ≤ mask ≤ 7 is violated; or if the segments fail to
- * fit in the maxVersion QR Code at the ECL, which means they are too long
+ * or −1 ≤ mask ≤ 7 is violated
+ * @throws DataTooLongException if the segments fail to fit in
+ * the maxVersion QR Code at the ECL, which means they are too long
*/
public static QrCode encodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) {
Objects.requireNonNull(segs);
@@ -153,8 +154,12 @@ public final class QrCode {
dataUsedBits = QrSegment.getTotalBits(segs, version);
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
break; // This version number is found to be suitable
- if (version >= maxVersion) // All versions in the range could not fit the given data
- throw new IllegalArgumentException("Data too long");
+ if (version >= maxVersion) { // All versions in the range could not fit the given data
+ String msg = "Segment too long";
+ if (dataUsedBits != -1)
+ msg = String.format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits);
+ throw new DataTooLongException(msg);
+ }
}
assert dataUsedBits != -1;
diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java
index eb9dd36..fc0c9d1 100644
--- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java
+++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java
@@ -91,9 +91,7 @@ public final class QrCodeGeneratorWorker {
System.out.println(qr.getModule(x, y) ? 1 : 0);
}
- } catch (IllegalArgumentException e) {
- if (!e.getMessage().equals("Data too long"))
- throw e;
+ } catch (DataTooLongException e) {
System.out.println(-1);
}
System.out.flush();
diff --git a/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java b/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java
index 6cbdb7b..3cd5ae5 100644
--- a/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java
+++ b/src/io/nayuki/fastqrcodegen/QrSegmentAdvanced.java
@@ -57,8 +57,8 @@ public final class QrSegmentAdvanced {
* @return a new mutable list (not {@code null}) of segments (not {@code null})
* containing the text, minimizing the bit length with respect to the constraints
* @throws NullPointerException if the text 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 ECL
+ * @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) {
// Check arguments
@@ -70,7 +70,7 @@ public final class QrSegmentAdvanced {
// Iterate through version numbers, and make tentative segments
List segs = null;
int[] codePoints = toCodePoints(text);
- for (int version = minVersion; version <= maxVersion; version++) {
+ for (int version = minVersion; ; version++) {
if (version == minVersion || version == 10 || version == 27)
segs = makeSegmentsOptimally(codePoints, version);
assert segs != null;
@@ -79,9 +79,14 @@ public final class QrSegmentAdvanced {
int dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
int dataUsedBits = QrSegment.getTotalBits(segs, version);
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
- return segs;
+ return segs; // This version number is found to be suitable
+ if (version >= maxVersion) { // All versions in the range could not fit the given text
+ String msg = "Segment too long";
+ if (dataUsedBits != -1)
+ msg = String.format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits);
+ throw new DataTooLongException(msg);
+ }
}
- throw new IllegalArgumentException("Data too long");
}
From 8551314425dac9130a99c016d91360c72de63d7b Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Mon, 5 Nov 2018 04:38:11 +0000
Subject: [PATCH 57/85] Added/updated/deleted some comments.
---
src/io/nayuki/fastqrcodegen/QrCode.java | 4 ++++
src/io/nayuki/fastqrcodegen/QrSegment.java | 6 +++---
src/io/nayuki/fastqrcodegen/QrTemplate.java | 5 ++---
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index c24aae4..32b2acf 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -197,6 +197,8 @@ public final class QrCode {
/*---- Instance fields ----*/
+ // Public immutable scalar parameters:
+
/** The version number of this QR Code, which is between 1 and 40 (inclusive).
* This determines the size of this barcode. */
public final int version;
@@ -213,6 +215,8 @@ public final class QrCode {
* −1), the resulting object still has a mask value between 0 and 7. */
public final int mask;
+ // Private grid of modules/pixels:
+
// The modules of this QR Code. Immutable after constructor finishes. Accessed through getModule().
private final int[] modules;
diff --git a/src/io/nayuki/fastqrcodegen/QrSegment.java b/src/io/nayuki/fastqrcodegen/QrSegment.java
index 86cce95..7ac0234 100644
--- a/src/io/nayuki/fastqrcodegen/QrSegment.java
+++ b/src/io/nayuki/fastqrcodegen/QrSegment.java
@@ -40,7 +40,8 @@ import java.util.Objects;
*
This segment class imposes no length restrictions, but QR Codes have restrictions.
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
- * This class can represent kanji mode segments, but provides no help in encoding them.
+ * This class can represent kanji mode segments, but provides no help in encoding them
+ * - see {@link QrSegmentAdvanced} for full kanji support.
Available in 7 programming languages, all with nearly equal functionality: Java, JavaScript, TypeScript, Python, C++, C, Rust
- *
Significantly shorter code but more documentation comments compared to competing libraries
*
Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard
*
Output formats: Raw modules/pixels of the QR symbol, SVG XML string, {@code BufferedImage} raster bitmap
*
Encodes numeric and special-alphanumeric text in less space than general text
+ *
Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes
+ *
Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts
+ *
Detects finder-like penalty patterns more accurately than other implementations
*
Open source code under the permissive MIT License
*
*
Manual parameters:
From 8dabf86641301b1de0fb91e083fea5a6c529814f Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Mon, 14 Oct 2019 00:19:03 +0000
Subject: [PATCH 72/85] Renamed some method parameters to completely avoid
variable shadowing.
---
src/io/nayuki/fastqrcodegen/QrCode.java | 34 ++++++++++++-------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index b629391..6f43d93 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -231,16 +231,16 @@ public final class QrCode {
* @param ver the version number to use, which must be in the range 1 to 40 (inclusive)
* @param ecl the error correction level to use
* @param dataCodewords the bytes representing segments to encode (without ECC)
- * @param mask the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice
+ * @param msk the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice
* @throws NullPointerException if the byte array or error correction level is {@code null}
* @throws IllegalArgumentException if the version or mask value is out of range,
* or if the data is the wrong length for the specified version and error correction level
*/
- public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) {
+ public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int msk) {
// Check arguments and initialize fields
if (ver < MIN_VERSION || ver > MAX_VERSION)
throw new IllegalArgumentException("Version value out of range");
- if (mask < -1 || mask > 7)
+ if (msk < -1 || msk > 7)
throw new IllegalArgumentException("Mask value out of range");
version = ver;
size = ver * 4 + 17;
@@ -253,7 +253,7 @@ public final class QrCode {
// Compute ECC, draw modules, do masking
byte[] allCodewords = addEccAndInterleave(dataCodewords);
drawCodewords(tpl.dataOutputBitIndexes, allCodewords);
- this.mask = handleConstructorMasking(tpl.masks, mask);
+ this.mask = handleConstructorMasking(tpl.masks, msk);
}
@@ -345,9 +345,9 @@ public final class QrCode {
// Draws two copies of the format bits (with its own error correction code)
// based on the given mask and this object's error correction level field.
- private void drawFormatBits(int mask) {
+ private void drawFormatBits(int msk) {
// Calculate error correction code and pack bits
- int data = errorCorrectionLevel.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3
+ int data = errorCorrectionLevel.formatBits << 3 | msk; // errCorrLvl is uint2, mask is uint3
int rem = data;
for (int i = 0; i < 10; i++)
rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
@@ -440,35 +440,35 @@ public final class QrCode {
// before masking. Due to the arithmetic of XOR, calling applyMask() with
// the same mask value a second time will undo the mask. A final well-formed
// QR Code needs exactly one (not zero, two, etc.) mask applied.
- private void applyMask(int[] mask) {
- if (mask.length != modules.length)
+ private void applyMask(int[] msk) {
+ if (msk.length != modules.length)
throw new IllegalArgumentException();
- for (int i = 0; i < mask.length; i++)
- modules[i] ^= mask[i];
+ for (int i = 0; i < msk.length; i++)
+ modules[i] ^= msk[i];
}
// A messy helper function for the constructor. This QR Code must be in an unmasked state when this
// method is called. The 'mask' argument is the requested mask, which is -1 for auto or 0 to 7 for fixed.
// This method applies and returns the actual mask chosen, from 0 to 7.
- private int handleConstructorMasking(int[][] masks, int mask) {
- if (mask == -1) { // Automatically choose best mask
+ private int handleConstructorMasking(int[][] masks, int msk) {
+ if (msk == -1) { // Automatically choose best mask
int minPenalty = Integer.MAX_VALUE;
for (int i = 0; i < 8; i++) {
applyMask(masks[i]);
drawFormatBits(i);
int penalty = getPenaltyScore();
if (penalty < minPenalty) {
- mask = i;
+ msk = i;
minPenalty = penalty;
}
applyMask(masks[i]); // Undoes the mask due to XOR
}
}
- assert 0 <= mask && mask <= 7;
- applyMask(masks[mask]); // Apply the final choice of mask
- drawFormatBits(mask); // Overwrite old format bits
- return mask; // The caller shall assign this value to the final-declared field
+ assert 0 <= msk && msk <= 7;
+ applyMask(masks[msk]); // Apply the final choice of mask
+ drawFormatBits(msk); // Overwrite old format bits
+ return msk; // The caller shall assign this value to the final-declared field
}
From db2d52116a9e587785a80b1ba05825009db1fdfa Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Wed, 28 Jul 2021 18:45:28 +0000
Subject: [PATCH 73/85] Simplified QrCode.getPenalty().
---
src/io/nayuki/fastqrcodegen/QrCode.java | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index 6f43d93..f7fa2ef 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -484,7 +484,6 @@ public final class QrCode {
int runColor = 0;
int runX = 0;
Arrays.fill(runHistory, 0);
- int padRun = size; // Add white border to initial run
int curRow = 0;
int nextRow = 0;
for (int x = 0; x < size; x++, index++, downIndex++) {
@@ -496,8 +495,7 @@ public final class QrCode {
else if (runX > 5)
result++;
} else {
- finderPenaltyAddHistory(runX + padRun, runHistory);
- padRun = 0;
+ finderPenaltyAddHistory(runX, runHistory);
if (runColor == 0)
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
runColor = c;
@@ -512,7 +510,7 @@ public final class QrCode {
result += PENALTY_N2;
}
}
- result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory) * PENALTY_N3;
+ result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3;
}
// Iterate over single columns
@@ -520,7 +518,6 @@ public final class QrCode {
int runColor = 0;
int runY = 0;
Arrays.fill(runHistory, 0);
- int padRun = size; // Add white border to initial run
for (int y = 0, index = x; y < size; y++, index += size) {
int c = getBit(modules[index >>> 5], index);
if (c == runColor) {
@@ -530,15 +527,14 @@ public final class QrCode {
else if (runY > 5)
result++;
} else {
- finderPenaltyAddHistory(runY + padRun, runHistory);
- padRun = 0;
+ finderPenaltyAddHistory(runY, runHistory);
if (runColor == 0)
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
runColor = c;
runY = 1;
}
}
- result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory) * PENALTY_N3;
+ result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3;
}
// Balance of black and white modules
@@ -587,7 +583,9 @@ public final class QrCode {
// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore().
- private static void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) {
+ private void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) {
+ if (runHistory[0] == 0)
+ currentRunLength += size; // Add white border to initial run
System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1);
runHistory[0] = currentRunLength;
}
From 219d04a2477ea9c022c201c96d97bfbad8ac7586 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Wed, 28 Jul 2021 18:47:30 +0000
Subject: [PATCH 74/85] Simplified code to remove unnecessary `this`, also
improving consistency with other field assignments, enabled by a local
variable renaming in commit 8dabf8664130.
---
src/io/nayuki/fastqrcodegen/QrCode.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index f7fa2ef..afa8aaa 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -253,7 +253,7 @@ public final class QrCode {
// Compute ECC, draw modules, do masking
byte[] allCodewords = addEccAndInterleave(dataCodewords);
drawCodewords(tpl.dataOutputBitIndexes, allCodewords);
- this.mask = handleConstructorMasking(tpl.masks, msk);
+ mask = handleConstructorMasking(tpl.masks, msk);
}
From e4761348282ced2e90c7d67da43a255d852859f5 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Wed, 28 Jul 2021 18:50:30 +0000
Subject: [PATCH 75/85] Changed the words "white"->"light" and "black"->"dark"
in comments and local variables, in order to match the vocabulary in the QR
Code specification document.
---
src/io/nayuki/fastqrcodegen/QrCode.java | 38 ++++++++++-----------
src/io/nayuki/fastqrcodegen/QrTemplate.java | 4 +--
2 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index afa8aaa..f6e801d 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -32,7 +32,7 @@ import java.util.Objects;
/**
* A QR Code symbol, which is a type of two-dimension barcode.
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
- *
Instances of this class represent an immutable square grid of black and white cells.
+ *
Instances of this class represent an immutable square grid of dark and light cells.
* The class provides static factory functions to create a QR Code from text or binary data.
* The class covers the QR Code Model 2 specification, supporting all versions (sizes)
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
@@ -262,12 +262,12 @@ public final class QrCode {
/**
* Returns the color of the module (pixel) at the specified coordinates, which is {@code false}
- * for white or {@code true} for black. The top left corner has the coordinates (x=0, y=0).
- * If the specified coordinates are out of bounds, then {@code false} (white) is returned.
+ * for light or {@code true} for dark. The top left corner has the coordinates (x=0, y=0).
+ * If the specified coordinates are out of bounds, then {@code false} (light) is returned.
* @param x the x coordinate, where 0 is the left edge and size−1 is the right edge
* @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge
* @return {@code true} if the coordinates are in bounds and the module
- * at that location is black, or {@code false} (white) otherwise
+ * at that location is dark, or {@code false} (light) otherwise
*/
public boolean getModule(int x, int y) {
if (0 <= x && x < size && 0 <= y && y < size) {
@@ -280,7 +280,7 @@ public final class QrCode {
/**
* Returns a raster image depicting this QR Code, with the specified module scale and border modules.
- *
For example, toImage(scale=10, border=4) means to pad the QR Code with 4 white
+ *
For example, toImage(scale=10, border=4) means to pad the QR Code with 4 light
* border modules on all four sides, and use 10×10 pixels to represent each module.
* The resulting image only contains the hex colors 000000 and FFFFFF.
* @param scale the side length (measured in pixels, must be positive) of each module
@@ -368,19 +368,19 @@ public final class QrCode {
setModule(size - 1 - i, 8, getBit(bits, i));
for (int i = 8; i < 15; i++)
setModule(8, size - 15 + i, getBit(bits, i));
- setModule(8, size - 8, 1); // Always black
+ setModule(8, size - 8, 1); // Always dark
}
// Sets the module at the given coordinates to the given color.
// Only used by the constructor. Coordinates must be in bounds.
- private void setModule(int x, int y, int black) {
+ private void setModule(int x, int y, int dark) {
assert 0 <= x && x < size;
assert 0 <= y && y < size;
- assert black == 0 || black == 1;
+ assert dark == 0 || dark == 1;
int i = y * size + x;
modules[i >>> 5] &= ~(1 << i);
- modules[i >>> 5] |= black << i;
+ modules[i >>> 5] |= dark << i;
}
@@ -476,7 +476,7 @@ public final class QrCode {
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
private int getPenaltyScore() {
int result = 0;
- int black = 0;
+ int dark = 0;
int[] runHistory = new int[7];
// Iterate over adjacent pairs of rows
@@ -501,7 +501,7 @@ public final class QrCode {
runColor = c;
runX = 1;
}
- black += c;
+ dark += c;
if (downIndex < end) {
curRow = ((curRow << 1) | c) & 3;
nextRow = ((nextRow << 1) | getBit(modules[downIndex >>> 5], downIndex)) & 3;
@@ -537,10 +537,10 @@ public final class QrCode {
result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3;
}
- // Balance of black and white modules
- int total = size * size; // Note that size is odd, so black/total != 1/2
- // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)%
- int k = (Math.abs(black * 20 - total * 10) + total - 1) / total - 1;
+ // Balance of dark and light modules
+ int total = size * size; // Note that size is odd, so dark/total != 1/2
+ // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)%
+ int k = (Math.abs(dark * 20 - total * 10) + total - 1) / total - 1;
result += k * PENALTY_N4;
return result;
}
@@ -559,7 +559,7 @@ public final class QrCode {
}
- // Can only be called immediately after a white run is added, and
+ // Can only be called immediately after a light run is added, and
// returns either 0, 1, or 2. A helper function for getPenaltyScore().
private int finderPenaltyCountPatterns(int[] runHistory) {
int n = runHistory[1];
@@ -572,11 +572,11 @@ public final class QrCode {
// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore().
private int finderPenaltyTerminateAndCount(int currentRunColor, int currentRunLength, int[] runHistory) {
- if (currentRunColor == 1) { // Terminate black run
+ if (currentRunColor == 1) { // Terminate dark run
finderPenaltyAddHistory(currentRunLength, runHistory);
currentRunLength = 0;
}
- currentRunLength += size; // Add white border to final run
+ currentRunLength += size; // Add light border to final run
finderPenaltyAddHistory(currentRunLength, runHistory);
return finderPenaltyCountPatterns(runHistory);
}
@@ -585,7 +585,7 @@ public final class QrCode {
// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore().
private void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) {
if (runHistory[0] == 0)
- currentRunLength += size; // Add white border to initial run
+ currentRunLength += size; // Add light border to initial run
System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1);
runHistory[0] = currentRunLength;
}
diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java
index b774cce..50ffc41 100644
--- a/src/io/nayuki/fastqrcodegen/QrTemplate.java
+++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java
@@ -107,7 +107,7 @@ final class QrTemplate {
darkenFunctionModule(size - 1 - i, 8, 0);
for (int i = 8; i < 15; i++)
darkenFunctionModule(8, size - 15 + i, 0);
- darkenFunctionModule(8, size - 8, 1); // Always black
+ darkenFunctionModule(8, size - 8, 1); // Always dark
}
@@ -221,7 +221,7 @@ final class QrTemplate {
// Marks the module at the given coordinates as a function module.
- // Also either sets that module black or keeps its color unchanged.
+ // Also either sets that module dark or keeps its color unchanged.
private void darkenFunctionModule(int x, int y, int enable) {
assert 0 <= x && x < size;
assert 0 <= y && y < size;
From 2515a4213cce689f853f8cc9ca0d8ba78e796f5e Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Wed, 28 Jul 2021 19:00:32 +0000
Subject: [PATCH 76/85] Moved QrCode.toImage() out of the library and into the
runnable demo program, slightly adapted some code, updated documentation
comments.
---
Readme.markdown | 4 +-
src/io/nayuki/fastqrcodegen/QrCode.java | 29 --------
.../fastqrcodegen/QrCodeGeneratorDemo.java | 66 ++++++++++++++-----
src/io/nayuki/fastqrcodegen/package-info.java | 4 +-
4 files changed, 53 insertions(+), 50 deletions(-)
diff --git a/Readme.markdown b/Readme.markdown
index 1f83e40..c14bc8a 100644
--- a/Readme.markdown
+++ b/Readme.markdown
@@ -18,7 +18,7 @@ Features
Core features:
* Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard
-* Output formats: Raw modules/pixels of the QR symbol, SVG XML string, `BufferedImage` raster bitmap
+* Output formats: Raw modules/pixels of the QR symbol, SVG XML string
* Encodes numeric and special-alphanumeric text in less space than general text
* Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes
* Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts
@@ -44,7 +44,7 @@ Examples
// Simple operation
QrCode qr0 = QrCode.encodeText("Hello, world!", QrCode.Ecc.MEDIUM);
- BufferedImage img = qr0.toImage(4, 10);
+ BufferedImage img = toImage(qr0, 4, 10); // See QrCodeGeneratorDemo
ImageIO.write(img, "png", new File("qr-code.png"));
// Manual operation
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index f6e801d..66abd48 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -23,7 +23,6 @@
package io.nayuki.fastqrcodegen;
-import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -278,34 +277,6 @@ public final class QrCode {
}
- /**
- * Returns a raster image depicting this QR Code, with the specified module scale and border modules.
- *
For example, toImage(scale=10, border=4) means to pad the QR Code with 4 light
- * border modules on all four sides, and use 10×10 pixels to represent each module.
- * The resulting image only contains the hex colors 000000 and FFFFFF.
- * @param scale the side length (measured in pixels, must be positive) of each module
- * @param border the number of border modules to add, which must be non-negative
- * @return a new image representing this QR Code, with padding and scaling
- * @throws IllegalArgumentException if the scale or border is out of range, or if
- * {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE
- */
- public BufferedImage toImage(int scale, int border) {
- if (scale <= 0 || border < 0)
- throw new IllegalArgumentException("Value out of range");
- if (border > Integer.MAX_VALUE / 2 || size + border * 2L > Integer.MAX_VALUE / scale)
- throw new IllegalArgumentException("Scale or border too large");
-
- 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++) {
- boolean color = getModule(x / scale - border, y / scale - border);
- result.setRGB(x, y, color ? 0x000000 : 0xFFFFFF);
- }
- }
- return result;
- }
-
-
/**
* Returns a string of SVG code for an image depicting this QR Code, with the specified number
* of border modules. The string always uses Unix newlines (\n), regardless of the platform.
diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
index f57836c..792b42d 100644
--- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
+++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
@@ -33,6 +33,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import javax.imageio.ImageIO;
@@ -57,7 +58,7 @@ public final class QrCodeGeneratorDemo {
QrCode qr = QrCode.encodeText(text, errCorLvl); // Make the QR Code symbol
- BufferedImage img = qr.toImage(10, 4); // Convert to bitmap image
+ BufferedImage img = 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
@@ -74,15 +75,15 @@ public final class QrCodeGeneratorDemo {
// Numeric mode encoding (3.33 bits per digit)
qr = QrCode.encodeText("314159265358979323846264338327950288419716939937510", QrCode.Ecc.MEDIUM);
- writePng(qr.toImage(13, 1), "pi-digits-QR.png");
+ writePng(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(qr.toImage(10, 2), "alphanumeric-QR.png");
+ writePng(toImage(qr, 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");
+ writePng(toImage(qr, 10, 3), "unicode-QR.png");
// Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland)
qr = QrCode.encodeText(
@@ -93,7 +94,7 @@ public final class QrCodeGeneratorDemo {
+ "for the hot day made her feel very sleepy and stupid), whether the pleasure of making a "
+ "daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly "
+ "a White Rabbit with pink eyes ran close by her.", QrCode.Ecc.HIGH);
- writePng(qr.toImage(6, 10), "alice-wonderland-QR.png");
+ writePng(toImage(qr, 6, 10), "alice-wonderland-QR.png");
}
@@ -106,32 +107,32 @@ public final class QrCodeGeneratorDemo {
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");
+ writePng(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(qr.toImage(10, 3), "sqrt2-segmented-QR.png");
+ writePng(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(qr.toImage(8, 5), "phi-monolithic-QR.png");
+ writePng(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(qr.toImage(8, 5), "phi-segmented-QR.png");
+ writePng(toImage(qr, 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");
+ writePng(toImage(qr, 9, 4), "madoka-utf8-QR.png");
int[] kanjiChars = { // Kanji mode encoding (13 bits per character)
0x0035, 0x1002, 0x0FC0, 0x0AED, 0x0AD7,
@@ -146,7 +147,7 @@ public final class QrCodeGeneratorDemo {
bb.appendBits(c, 13);
segs = Arrays.asList(new QrSegment(QrSegment.Mode.KANJI, kanjiChars.length, bb.data, bb.bitLength));
qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW);
- writePng(qr.toImage(9, 4), "madoka-kanji-QR.png");
+ writePng(toImage(qr, 9, 4), "madoka-kanji-QR.png");
}
@@ -158,26 +159,57 @@ public final class QrCodeGeneratorDemo {
// 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");
+ writePng(toImage(qr, 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");
+ writePng(toImage(qr, 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");
+ writePng(toImage(qr, 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");
+ writePng(toImage(qr, 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");
+ writePng(toImage(qr, 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");
+ writePng(toImage(qr, 10, 3), "unicode-mask7-QR.png");
}
/*---- Utilities ----*/
+ /**
+ * Returns a raster image depicting the specified QR Code, with the specified module scale and border modules.
+ *
For example, toImage(qr, scale=10, border=4) means to pad the QR Code with 4 light
+ * border modules on all four sides, and use 10×10 pixels to represent each module.
+ * The resulting image only contains the hex colors 000000 and FFFFFF.
+ * @param qr the QR Code to render (not {@code null})
+ * @param scale the side length (measured in pixels, must be positive) of each module
+ * @param border the number of border modules to add, which must be non-negative
+ * @return a new image representing the QR Code, with padding and scaling
+ * @throws NullPointerException if the QR Code is {@code null}
+ * @throws IllegalArgumentException if the scale or border is out of range, or if
+ * {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE
+ */
+ private static BufferedImage toImage(QrCode qr, int scale, int border) {
+ Objects.requireNonNull(qr);
+ if (scale <= 0 || border < 0)
+ throw new IllegalArgumentException("Value out of range");
+ if (border > Integer.MAX_VALUE / 2 || qr.size + border * 2L > Integer.MAX_VALUE / scale)
+ throw new IllegalArgumentException("Scale or border too large");
+
+ BufferedImage result = new BufferedImage((qr.size + border * 2) * scale, (qr.size + border * 2) * scale, BufferedImage.TYPE_INT_RGB);
+ for (int y = 0; y < result.getHeight(); y++) {
+ for (int x = 0; x < result.getWidth(); x++) {
+ boolean color = qr.getModule(x / scale - border, y / scale - border);
+ result.setRGB(x, y, color ? 0x000000 : 0xFFFFFF);
+ }
+ }
+ return result;
+ }
+
+
// 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/src/io/nayuki/fastqrcodegen/package-info.java b/src/io/nayuki/fastqrcodegen/package-info.java
index 50ddb0f..220b8b2 100644
--- a/src/io/nayuki/fastqrcodegen/package-info.java
+++ b/src/io/nayuki/fastqrcodegen/package-info.java
@@ -9,7 +9,7 @@
*
Core features:
*
*
Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard
- *
Output formats: Raw modules/pixels of the QR symbol, SVG XML string, {@code BufferedImage} raster bitmap
+ *
Output formats: Raw modules/pixels of the QR symbol, SVG XML string
*
Encodes numeric and special-alphanumeric text in less space than general text
*
Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes
*
Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts
import java.util.List;
From 1954c534bf59c133bc7481aad866390b622775fe Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Wed, 28 Jul 2021 19:02:39 +0000
Subject: [PATCH 77/85] Moved QrCode.toSvgString() out of the library and into
the runnable demo program, slightly adapted some code, updated documentation
comments.
---
Readme.markdown | 2 +-
src/io/nayuki/fastqrcodegen/QrCode.java | 34 ----------------
.../fastqrcodegen/QrCodeGeneratorDemo.java | 39 ++++++++++++++++++-
src/io/nayuki/fastqrcodegen/package-info.java | 2 +-
4 files changed, 40 insertions(+), 37 deletions(-)
diff --git a/Readme.markdown b/Readme.markdown
index c14bc8a..dfcff4c 100644
--- a/Readme.markdown
+++ b/Readme.markdown
@@ -18,7 +18,7 @@ Features
Core features:
* Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard
-* Output formats: Raw modules/pixels of the QR symbol, SVG XML string
+* Output format: Raw modules/pixels of the QR symbol
* Encodes numeric and special-alphanumeric text in less space than general text
* Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes
* Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts
diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java
index 66abd48..75e5e0f 100644
--- a/src/io/nayuki/fastqrcodegen/QrCode.java
+++ b/src/io/nayuki/fastqrcodegen/QrCode.java
@@ -277,40 +277,6 @@ public final class QrCode {
}
- /**
- * Returns a string of SVG code for an image depicting this QR Code, with the specified number
- * of border modules. The string always uses Unix newlines (\n), 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 XML document
- * @throws IllegalArgumentException if the border is negative
- */
- public String toSvgString(int border) {
- if (border < 0)
- throw new IllegalArgumentException("Border must be non-negative");
- long brd = border;
- StringBuilder sb = new StringBuilder()
- .append("\n")
- .append("\n")
- .append(String.format("\n",
- size + brd * 2))
- .append("\t\n")
- .append("\t\n")
- .append("\n")
- .toString();
- }
-
-
/*---- Private helper methods for constructor: Drawing function modules ----*/
diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
index 792b42d..5e9e2a0 100644
--- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
+++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
@@ -62,7 +62,7 @@ public final class QrCodeGeneratorDemo {
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 = toSvgString(qr, 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));
@@ -215,4 +215,41 @@ public final class QrCodeGeneratorDemo {
ImageIO.write(img, "png", new File(filepath));
}
+
+ /**
+ * Returns a string of SVG code for an image depicting the specified QR Code, with the specified
+ * number of border modules. The string always uses Unix newlines (\n), regardless of the platform.
+ * @param qr the QR Code to render (not {@code null})
+ * @param border the number of border modules to add, which must be non-negative
+ * @return a string representing the QR Code as an SVG XML document
+ * @throws NullPointerException if the QR Code is {@code null}
+ * @throws IllegalArgumentException if the border is negative
+ */
+ private static String toSvgString(QrCode qr, int border) {
+ Objects.requireNonNull(qr);
+ if (border < 0)
+ throw new IllegalArgumentException("Border must be non-negative");
+ long brd = border;
+ StringBuilder sb = new StringBuilder()
+ .append("\n")
+ .append("\n")
+ .append(String.format("\n",
+ qr.size + brd * 2))
+ .append("\t\n")
+ .append("\t\n")
+ .append("\n")
+ .toString();
+ }
+
}
diff --git a/src/io/nayuki/fastqrcodegen/package-info.java b/src/io/nayuki/fastqrcodegen/package-info.java
index 220b8b2..0ab23a6 100644
--- a/src/io/nayuki/fastqrcodegen/package-info.java
+++ b/src/io/nayuki/fastqrcodegen/package-info.java
@@ -9,7 +9,7 @@
*
Core features:
*
*
Supports encoding all 40 versions (sizes) and all 4 error correction levels, as per the QR Code Model 2 standard
- *
Output formats: Raw modules/pixels of the QR symbol, SVG XML string
+ *
Output format: Raw modules/pixels of the QR symbol
*
Encodes numeric and special-alphanumeric text in less space than general text
*
Encodes Japanese Unicode text in kanji mode to save a lot of space compared to UTF-8 bytes
*
Computes optimal segment mode switching for text with mixed numeric/alphanumeric/general/kanji parts
From 07725617d7b7302341fe1be6ba8c8609b91d5ae1 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Wed, 28 Jul 2021 19:03:31 +0000
Subject: [PATCH 78/85] Removed the test worker program, because this is not
core functionality and is hard to explain.
---
.../fastqrcodegen/QrCodeGeneratorWorker.java | 100 ------------------
1 file changed, 100 deletions(-)
delete mode 100644 src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java
diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java
deleted file mode 100644
index 5bf9b30..0000000
--- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorWorker.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * QR Code generator test worker
- *
- * This program reads data and encoding parameters from standard input and writes
- * QR Code bitmaps to standard output. The I/O format is one integer per line.
- * Run with no command line arguments. The program is intended for automated
- * batch testing of end-to-end functionality of this QR Code generator library.
- *
- * Copyright (c) Project Nayuki. (MIT License)
- * https://www.nayuki.io/page/fast-qr-code-generator-library
- *
- * 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.fastqrcodegen;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Scanner;
-
-
-public final class QrCodeGeneratorWorker {
-
- public static void main(String[] args) {
- // Set up input stream and start loop
- try (Scanner input = new Scanner(System.in, "US-ASCII")) {
- input.useDelimiter("\r\n|\n|\r");
- while (processCase(input));
- }
- }
-
-
- private static boolean processCase(Scanner input) {
- // Read data length or exit
- int length = input.nextInt();
- if (length == -1)
- return false;
- if (length > Short.MAX_VALUE)
- throw new RuntimeException();
-
- // Read data bytes
- 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)
- throw new RuntimeException();
- data[i] = (byte)b;
- isAscii &= b < 128;
- }
-
- // Read encoding parameters
- int errCorLvl = input.nextInt();
- int minVersion = input.nextInt();
- int maxVersion = input.nextInt();
- int mask = input.nextInt();
- int boostEcl = input.nextInt();
- if (!(0 <= errCorLvl && errCorLvl <= 3) || !(-1 <= mask && mask <= 7) || (boostEcl >>> 1) != 0
- || !(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION))
- throw new RuntimeException();
-
- // Make segments for encoding
- List segs;
- if (isAscii)
- segs = QrSegment.makeSegments(new String(data, StandardCharsets.US_ASCII));
- else
- segs = 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);
- // Print grid of modules
- System.out.println(qr.version);
- for (int y = 0; y < qr.size; y++) {
- for (int x = 0; x < qr.size; x++)
- System.out.println(qr.getModule(x, y) ? 1 : 0);
- }
-
- } catch (DataTooLongException e) {
- System.out.println(-1);
- }
- System.out.flush();
- return true;
- }
-
-}
From 8640ddf8a53f81b72f7dfa08e2e764e86732f160 Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Wed, 28 Jul 2021 19:04:14 +0000
Subject: [PATCH 79/85] Added spaces around multiplication operators in
QrTemplate.getAlignmentPatternPositions(), for consistency with other code.
---
src/io/nayuki/fastqrcodegen/QrTemplate.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java
index 50ffc41..b841da2 100644
--- a/src/io/nayuki/fastqrcodegen/QrTemplate.java
+++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java
@@ -241,7 +241,7 @@ final class QrTemplate {
else {
int numAlign = version / 7 + 2;
int step = (version == 32) ? 26 :
- (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2;
+ (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2;
int[] result = new int[numAlign];
result[0] = 6;
for (int i = result.length - 1, pos = size - 7; i >= 1; i--, pos -= step)
From 02d182ebc2c57968762bd612660dade7f0aedc1f Mon Sep 17 00:00:00 2001
From: Project Nayuki
Date: Wed, 28 Jul 2021 19:09:20 +0000
Subject: [PATCH 80/85] Added parameters for custom module colors when
rendering to BufferedImage, changed some demo code to use non-black/white
colors.
---
.../fastqrcodegen/QrCodeGeneratorDemo.java | 27 ++++++++++++-------
1 file changed, 17 insertions(+), 10 deletions(-)
diff --git a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
index 5e9e2a0..33c2ff4 100644
--- a/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
+++ b/src/io/nayuki/fastqrcodegen/QrCodeGeneratorDemo.java
@@ -132,7 +132,7 @@ public final class QrCodeGeneratorDemo {
// Illustration "Madoka": kanji, kana, Cyrillic, full-width Latin, Greek characters
String madoka = "「魔法少女まどか☆マギカ」って、 ИАИ desu κα?";
qr = QrCode.encodeText(madoka, QrCode.Ecc.LOW);
- writePng(toImage(qr, 9, 4), "madoka-utf8-QR.png");
+ writePng(toImage(qr, 9, 4, 0xFFFFE0, 0x303080), "madoka-utf8-QR.png");
int[] kanjiChars = { // Kanji mode encoding (13 bits per character)
0x0035, 0x1002, 0x0FC0, 0x0AED, 0x0AD7,
@@ -147,7 +147,7 @@ public final class QrCodeGeneratorDemo {
bb.appendBits(c, 13);
segs = Arrays.asList(new QrSegment(QrSegment.Mode.KANJI, kanjiChars.length, bb.data, bb.bitLength));
qr = QrCode.encodeSegments(segs, QrCode.Ecc.LOW);
- writePng(toImage(qr, 9, 4), "madoka-kanji-QR.png");
+ writePng(toImage(qr, 9, 4, 0xE0F0FF, 0x404040), "madoka-kanji-QR.png");
}
@@ -159,9 +159,9 @@ public final class QrCodeGeneratorDemo {
// 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(toImage(qr, 8, 6), "project-nayuki-automask-QR.png");
+ writePng(toImage(qr, 8, 6, 0xE0FFE0, 0x206020), "project-nayuki-automask-QR.png");
qr = QrCode.encodeSegments(segs, QrCode.Ecc.HIGH, QrCode.MIN_VERSION, QrCode.MAX_VERSION, 3, true); // Force mask 3
- writePng(toImage(qr, 8, 6), "project-nayuki-mask3-QR.png");
+ writePng(toImage(qr, 8, 6, 0xFFE0E0, 0x602020), "project-nayuki-mask3-QR.png");
// Chinese text as UTF-8
segs = QrSegment.makeSegments("維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫");
@@ -179,20 +179,27 @@ public final class QrCodeGeneratorDemo {
/*---- Utilities ----*/
+ private static BufferedImage toImage(QrCode qr, int scale, int border) {
+ return toImage(qr, scale, border, 0xFFFFFF, 0x000000);
+ }
+
+
/**
- * Returns a raster image depicting the specified QR Code, with the specified module scale and border modules.
- *
For example, toImage(qr, scale=10, border=4) means to pad the QR Code with 4 light
- * border modules on all four sides, and use 10×10 pixels to represent each module.
- * The resulting image only contains the hex colors 000000 and FFFFFF.
+ * Returns a raster image depicting the specified QR Code, with
+ * the specified module scale, border modules, and module colors.
+ *