|
|
@ -29,14 +29,12 @@ import java.util.List;
|
|
|
|
import java.util.Objects;
|
|
|
|
import java.util.Objects;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
|
|
|
|
import QrSegment.Mode;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* A segment of character/binary/control data in a QR Code symbol.
|
|
|
|
* A segment of character/binary/control data in a QR Code symbol.
|
|
|
|
* Instances of this class are immutable.
|
|
|
|
* Instances of this class are immutable.
|
|
|
|
* <p>The mid-level way to create a segment is to take the payload data and call a
|
|
|
|
* <p>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
|
|
|
|
* static factory function such as {@link MakeNumericToSegment#makeNumericToSegment(String)}. The low-level
|
|
|
|
* way to create a segment is to custom-make the bit buffer and call the {@link
|
|
|
|
* way to create a segment is to custom-make the bit buffer and call the {@link
|
|
|
|
* QrSegment#QrSegment(Mode,int,BitBuffer) constructor} with appropriate values.</p>
|
|
|
|
* QrSegment#QrSegment(Mode,int,BitBuffer) constructor} with appropriate values.</p>
|
|
|
|
* <p>This segment class imposes no length restrictions, but QR Codes have restrictions.
|
|
|
|
* <p>This segment class imposes no length restrictions, but QR Codes have restrictions.
|
|
|
@ -49,6 +47,51 @@ public final class QrSegment {
|
|
|
|
|
|
|
|
|
|
|
|
/*---- Static factory functions (mid level) ----*/
|
|
|
|
/*---- Static factory functions (mid level) ----*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Returns a segment representing the specified binary data
|
|
|
|
|
|
|
|
* encoded in byte mode. All input byte arrays are acceptable.
|
|
|
|
|
|
|
|
* <p>Any text string can be converted to UTF-8 bytes ({@code
|
|
|
|
|
|
|
|
* s.getBytes(StandardCharsets.UTF_8)}) and encoded as a byte mode segment.</p>
|
|
|
|
|
|
|
|
* @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) {
|
|
|
|
|
|
|
|
MakeBytesToSegment makeBytesToSegment = new MakeBytesToSegment();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return makeBytesToSegment.excuteForBytedata(data);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 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) {
|
|
|
|
|
|
|
|
MakeSegment makeSegment = new MakeNumericToSegment();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return makeSegment.excute(digits);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 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) {
|
|
|
|
|
|
|
|
MakeSegment makeSegment = new MakeAlphaNumericToSegment();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return makeSegment.excute(text);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Returns a list of zero or more segments to represent the specified Unicode text string.
|
|
|
|
* 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.
|
|
|
|
* The result may use various segment modes and switch modes to optimize the length of the bit stream.
|
|
|
@ -56,21 +99,6 @@ public final class QrSegment {
|
|
|
|
* @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text
|
|
|
|
* @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text
|
|
|
|
* @throws NullPointerException if the text is {@code null}
|
|
|
|
* @throws NullPointerException if the text is {@code null}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
public static List<QrSegment> makeSegments(String text) {
|
|
|
|
|
|
|
|
Objects.requireNonNull(text);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Select the most efficient segment encoding automatically
|
|
|
|
|
|
|
|
List<QrSegment> segments = new ArrayList<>();
|
|
|
|
|
|
|
|
if (text.equals("")); // Leave result empty
|
|
|
|
|
|
|
|
else if (NUMERIC_REGEX.matcher(text).matches())
|
|
|
|
|
|
|
|
segments.add(makeNumeric(text));
|
|
|
|
|
|
|
|
else if (ALPHANUMERIC_REGEX.matcher(text).matches())
|
|
|
|
|
|
|
|
segments.add(makeAlphanumeric(text));
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
segments.add(makeBytes(text.getBytes(StandardCharsets.UTF_8)));
|
|
|
|
|
|
|
|
return segments;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static List<QrSegment> makeSegments(String text) {
|
|
|
|
public static List<QrSegment> makeSegments(String text) {
|
|
|
|
Objects.requireNonNull(text);
|
|
|
|
Objects.requireNonNull(text);
|
|
|
|
// Select the most efficient segment encoding automatically
|
|
|
|
// Select the most efficient segment encoding automatically
|
|
|
@ -82,10 +110,12 @@ public final class QrSegment {
|
|
|
|
|
|
|
|
|
|
|
|
return segments;
|
|
|
|
return segments;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Returns a segment representing an Extended Channel Interpretation
|
|
|
|
* Returns a segment representing an Extended Channel Interpretation
|
|
|
|
* (ECI) designator with the specified assignment value.
|
|
|
|
* (ECI) designator with the specified assignment value.
|
|
|
|
* @param assignVal the ECI assignment number (see the AIM ECI specification)
|
|
|
|
* @param assignValue the ECI assignment number (see the AIM ECI specification)
|
|
|
|
* @return a segment (not {@code null}) containing the data
|
|
|
|
* @return a segment (not {@code null}) containing the data
|
|
|
|
* @throws IllegalArgumentException if the value is outside the range [0, 10<sup>6</sup>)
|
|
|
|
* @throws IllegalArgumentException if the value is outside the range [0, 10<sup>6</sup>)
|
|
|
|
*/
|
|
|
|
*/
|
|
|
@ -116,7 +146,7 @@ public final class QrSegment {
|
|
|
|
/** The length of this segment's unencoded data. Measured in characters for
|
|
|
|
/** 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.
|
|
|
|
* 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. */
|
|
|
|
* Always zero or positive. Not the same as the data's bit length. */
|
|
|
|
public final int numChars;
|
|
|
|
public final int numberOfCharacters;
|
|
|
|
|
|
|
|
|
|
|
|
// The data bits of this segment. Not null. Accessed through getData().
|
|
|
|
// The data bits of this segment. Not null. Accessed through getData().
|
|
|
|
final BitBuffer data;
|
|
|
|
final BitBuffer data;
|
|
|
@ -128,8 +158,8 @@ public final class QrSegment {
|
|
|
|
* Constructs a QR Code segment with the specified attributes and data.
|
|
|
|
* 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,
|
|
|
|
* 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.
|
|
|
|
* but the constraint isn't checked. The specified bit buffer is cloned and stored.
|
|
|
|
* @param md the mode (not {@code null})
|
|
|
|
* @param _mode the mode (not {@code null})
|
|
|
|
* @param numCh the data length in characters or bytes, which is non-negative
|
|
|
|
* @param _numberOfCharacters the data length in characters or bytes, which is non-negative
|
|
|
|
* @param data the data bits (not {@code null})
|
|
|
|
* @param data the data bits (not {@code null})
|
|
|
|
* @throws NullPointerException if the mode or data is {@code null}
|
|
|
|
* @throws NullPointerException if the mode or data is {@code null}
|
|
|
|
* @throws IllegalArgumentException if the character count is negative
|
|
|
|
* @throws IllegalArgumentException if the character count is negative
|
|
|
@ -143,6 +173,7 @@ public final class QrSegment {
|
|
|
|
this.data = data.clone(); // Make defensive copy
|
|
|
|
this.data = data.clone(); // Make defensive copy
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*---- Methods ----*/
|
|
|
|
/*---- Methods ----*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
@ -157,49 +188,45 @@ public final class QrSegment {
|
|
|
|
// Calculates the number of bits needed to encode the given segments at the given version.
|
|
|
|
// Calculates the number of bits needed to encode the given segments at the given version.
|
|
|
|
// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too
|
|
|
|
// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too
|
|
|
|
// many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE.
|
|
|
|
// many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE.
|
|
|
|
static int getTotalBits(List<QrSegment> segs, int version) {
|
|
|
|
|
|
|
|
Objects.requireNonNull(segs);
|
|
|
|
|
|
|
|
long result = 0;
|
|
|
|
|
|
|
|
for (QrSegment seg : segs) {
|
|
|
|
|
|
|
|
Objects.requireNonNull(seg);
|
|
|
|
|
|
|
|
int ccbits = seg.mode.numCharCountBits(version);
|
|
|
|
|
|
|
|
if (seg.numChars >= (1 << ccbits))
|
|
|
|
|
|
|
|
return -1; // The segment's length doesn't fit the field's bit width
|
|
|
|
|
|
|
|
result += 4L + ccbits + seg.data.bitLength();
|
|
|
|
|
|
|
|
if (result > Integer.MAX_VALUE)
|
|
|
|
|
|
|
|
return -1; // The sum will overflow an int type
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return (int)result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int getTotalBits(List<QrSegment> segments, int version) {
|
|
|
|
static int getTotalBits(List<QrSegment> segments, int version) {
|
|
|
|
Objects.requireNonNull(segments);
|
|
|
|
Objects.requireNonNull(segments);
|
|
|
|
long TotalBits = 0;
|
|
|
|
long TotalBits = 0;
|
|
|
|
for (QrSegment segment : segments) {
|
|
|
|
for (QrSegment segment : segments) {
|
|
|
|
Objects.requireNonNull(segment);
|
|
|
|
Objects.requireNonNull(segment);
|
|
|
|
int characterCountBits = segment.mode.numCharCountBits(version);
|
|
|
|
int characterCountBits = segment.mode.numCharCountBits(version);
|
|
|
|
if (segment.numberOfCharacters >= (1 << characterCountBits))
|
|
|
|
if (DoesNotSegmentLengthFitTheFieldBitWidth(segment, characterCountBits))
|
|
|
|
return -1; // The segment's length doesn't fit the field's bit width
|
|
|
|
return -1; // The segment's length doesn't fit the field's bit width
|
|
|
|
TotalBits += 4L + characterCountBits + segment.data.bitLength();
|
|
|
|
TotalBits += 4L + characterCountBits + segment.data.bitLength();
|
|
|
|
if (TotalBits > Integer.MAX_VALUE)
|
|
|
|
if (IsTotalBitsOutOfIntegerRange(TotalBits))
|
|
|
|
return -1; // The sum will overflow an int type
|
|
|
|
return -1; // The sum will overflow an int type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (int)TotalBits;
|
|
|
|
return (int)TotalBits;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static boolean IsTotalBitsOutOfIntegerRange(long result) {
|
|
|
|
|
|
|
|
return result > Integer.MAX_VALUE;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static boolean DoesNotSegmentLengthFitTheFieldBitWidth(QrSegment segment, int characterCountBits) {
|
|
|
|
|
|
|
|
return segment.numberOfCharacters >= (1 << characterCountBits);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*---- Constants ----*/
|
|
|
|
/*---- Constants ----*/
|
|
|
|
|
|
|
|
|
|
|
|
/** Describes precisely all strings that are encodable in numeric mode. To test whether a
|
|
|
|
/** Describes precisely all strings that are encodable in numeric mode. To test whether a
|
|
|
|
* string {@code s} is encodable: {@code boolean ok = NUMERIC_REGEX.matcher(s).matches();}.
|
|
|
|
* string {@code s} is encodable: {@code boolean ok = NUMERIC_REGEX.matcher(s).matches();}.
|
|
|
|
* A string is encodable iff each character is in the range 0 to 9.
|
|
|
|
* A string is encodable iff each character is in the range 0 to 9.
|
|
|
|
* @see #makeNumeric(String) */
|
|
|
|
* @see MakeNumericToSegment#makeNumericToSegment(String) */
|
|
|
|
public static final Pattern NUMERIC_REGEX = Pattern.compile("[0-9]*");
|
|
|
|
public static final Pattern NUMERIC_REGEX = Pattern.compile("[0-9]*");
|
|
|
|
|
|
|
|
|
|
|
|
/** Describes precisely all strings that are encodable in alphanumeric mode. To test whether a
|
|
|
|
/** Describes precisely all strings that are encodable in alphanumeric mode. To test whether a
|
|
|
|
* string {@code s} is encodable: {@code boolean ok = ALPHANUMERIC_REGEX.matcher(s).matches();}.
|
|
|
|
* string {@code s} is encodable: {@code boolean ok = ALPHANUMERIC_REGEX.matcher(s).matches();}.
|
|
|
|
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
|
|
|
|
* 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.
|
|
|
|
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
|
|
|
* @see #makeAlphanumeric(String) */
|
|
|
|
* @see MakeAlphaNumericToSegment#makeAlphaNumericToSegment(String) */
|
|
|
|
public static final Pattern ALPHANUMERIC_REGEX = Pattern.compile("[A-Z0-9 $%*+./:-]*");
|
|
|
|
public static final Pattern ALPHANUMERIC_REGEX = Pattern.compile("[A-Z0-9 $%*+./:-]*");
|
|
|
|
|
|
|
|
|
|
|
|
// The set of all legal characters in alphanumeric mode, where
|
|
|
|
// The set of all legal characters in alphanumeric mode, where
|
|
|
|