1 /* 2 * QR Code generator library (Java) 3 * 4 * Copyright (c) Project Nayuki. (MIT License) 5 * https://www.nayuki.io/page/qr-code-generator-library 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 * this software and associated documentation files (the "Software"), to deal in 9 * the Software without restriction, including without limitation the rights to 10 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 * the Software, and to permit persons to whom the Software is furnished to do so, 12 * subject to the following conditions: 13 * - The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * - The Software is provided "as is", without warranty of any kind, express or 16 * implied, including but not limited to the warranties of merchantability, 17 * fitness for a particular purpose and noninfringement. In no event shall the 18 * authors or copyright holders be liable for any claim, damages or other 19 * liability, whether in an action of contract, tort or otherwise, arising from, 20 * out of or in connection with the Software or the use or other dealings in the 21 * Software. 22 */ 23 24 package io.nayuki.qrcodegen; 25 26 import java.nio.charset.StandardCharsets; 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Objects; 30 import java.util.regex.Pattern; 31 32 33 /** 34 * A segment of character/binary/control data in a QR Code symbol. 35 * Instances of this class are immutable. 36 * <p>The mid-level way to create a segment is to take the payload data and call a 37 * static factory function such as {@link QrSegment#makeNumeric(String)}. The low-level 38 * way to create a segment is to custom-make the bit buffer and call the {@link 39 * QrSegment#QrSegment(Mode,int,BitBuffer) constructor} with appropriate values.</p> 40 * <p>This segment class imposes no length restrictions, but QR Codes have restrictions. 41 * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. 42 * Any segment longer than this is meaningless for the purpose of generating QR Codes. 43 * This class can represent kanji mode segments, but provides no help in encoding them 44 * - see {@link QrSegmentAdvanced} for full kanji support.</p> 45 */ 46 public final class QrSegment { 47 48 /*---- Static factory functions (mid level) ----*/ 49 50 /** 51 * Returns a segment representing the specified binary data 52 * encoded in byte mode. All input byte arrays are acceptable. 53 * <p>Any text string can be converted to UTF-8 bytes ({@code 54 * s.getBytes(StandardCharsets.UTF_8)}) and encoded as a byte mode segment.</p> 55 * @param data the binary data (not {@code null}) 56 * @return a segment (not {@code null}) containing the data 57 * @throws NullPointerException if the array is {@code null} 58 */ makeBytes(byte[] data)59 public static QrSegment makeBytes(byte[] data) { 60 Objects.requireNonNull(data); 61 BitBuffer bb = new BitBuffer(); 62 for (byte b : data) 63 bb.appendBits(b & 0xFF, 8); 64 return new QrSegment(Mode.BYTE, data.length, bb); 65 } 66 67 68 /** 69 * Returns a segment representing the specified string of decimal digits encoded in numeric mode. 70 * @param digits the text (not {@code null}), with only digits from 0 to 9 allowed 71 * @return a segment (not {@code null}) containing the text 72 * @throws NullPointerException if the string is {@code null} 73 * @throws IllegalArgumentException if the string contains non-digit characters 74 */ makeNumeric(String digits)75 public static QrSegment makeNumeric(String digits) { 76 Objects.requireNonNull(digits); 77 if (!isNumeric(digits)) 78 throw new IllegalArgumentException("String contains non-numeric characters"); 79 80 BitBuffer bb = new BitBuffer(); 81 for (int i = 0; i < digits.length(); ) { // Consume up to 3 digits per iteration 82 int n = Math.min(digits.length() - i, 3); 83 bb.appendBits(Integer.parseInt(digits.substring(i, i + n)), n * 3 + 1); 84 i += n; 85 } 86 return new QrSegment(Mode.NUMERIC, digits.length(), bb); 87 } 88 89 90 /** 91 * Returns a segment representing the specified text string encoded in alphanumeric mode. 92 * The characters allowed are: 0 to 9, A to Z (uppercase only), space, 93 * dollar, percent, asterisk, plus, hyphen, period, slash, colon. 94 * @param text the text (not {@code null}), with only certain characters allowed 95 * @return a segment (not {@code null}) containing the text 96 * @throws NullPointerException if the string is {@code null} 97 * @throws IllegalArgumentException if the string contains non-encodable characters 98 */ makeAlphanumeric(String text)99 public static QrSegment makeAlphanumeric(String text) { 100 Objects.requireNonNull(text); 101 if (!isAlphanumeric(text)) 102 throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode"); 103 104 BitBuffer bb = new BitBuffer(); 105 int i; 106 for (i = 0; i <= text.length() - 2; i += 2) { // Process groups of 2 107 int temp = ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45; 108 temp += ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1)); 109 bb.appendBits(temp, 11); 110 } 111 if (i < text.length()) // 1 character remaining 112 bb.appendBits(ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6); 113 return new QrSegment(Mode.ALPHANUMERIC, text.length(), bb); 114 } 115 116 117 /** 118 * Returns a list of zero or more segments to represent the specified Unicode text string. 119 * The result may use various segment modes and switch modes to optimize the length of the bit stream. 120 * @param text the text to be encoded, which can be any Unicode string 121 * @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text 122 * @throws NullPointerException if the text is {@code null} 123 */ makeSegments(String text)124 public static List<QrSegment> makeSegments(String text) { 125 Objects.requireNonNull(text); 126 127 // Select the most efficient segment encoding automatically 128 List<QrSegment> result = new ArrayList<>(); 129 if (text.equals("")); // Leave result empty 130 else if (isNumeric(text)) 131 result.add(makeNumeric(text)); 132 else if (isAlphanumeric(text)) 133 result.add(makeAlphanumeric(text)); 134 else 135 result.add(makeBytes(text.getBytes(StandardCharsets.UTF_8))); 136 return result; 137 } 138 139 140 /** 141 * Returns a segment representing an Extended Channel Interpretation 142 * (ECI) designator with the specified assignment value. 143 * @param assignVal the ECI assignment number (see the AIM ECI specification) 144 * @return a segment (not {@code null}) containing the data 145 * @throws IllegalArgumentException if the value is outside the range [0, 10<sup>6</sup>) 146 */ makeEci(int assignVal)147 public static QrSegment makeEci(int assignVal) { 148 BitBuffer bb = new BitBuffer(); 149 if (assignVal < 0) 150 throw new IllegalArgumentException("ECI assignment value out of range"); 151 else if (assignVal < (1 << 7)) 152 bb.appendBits(assignVal, 8); 153 else if (assignVal < (1 << 14)) { 154 bb.appendBits(2, 2); 155 bb.appendBits(assignVal, 14); 156 } else if (assignVal < 1_000_000) { 157 bb.appendBits(6, 3); 158 bb.appendBits(assignVal, 21); 159 } else 160 throw new IllegalArgumentException("ECI assignment value out of range"); 161 return new QrSegment(Mode.ECI, 0, bb); 162 } 163 164 165 /** 166 * Tests whether the specified string can be encoded as a segment in numeric mode. 167 * A string is encodable iff each character is in the range 0 to 9. 168 * @param text the string to test for encodability (not {@code null}) 169 * @return {@code true} iff each character is in the range 0 to 9. 170 * @throws NullPointerException if the string is {@code null} 171 * @see #makeNumeric(String) 172 */ isNumeric(String text)173 public static boolean isNumeric(String text) { 174 return NUMERIC_REGEX.matcher(text).matches(); 175 } 176 177 178 /** 179 * Tests whether the specified string can be encoded as a segment in alphanumeric mode. 180 * A string is encodable iff each character is in the following set: 0 to 9, A to Z 181 * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. 182 * @param text the string to test for encodability (not {@code null}) 183 * @return {@code true} iff each character is in the alphanumeric mode character set 184 * @throws NullPointerException if the string is {@code null} 185 * @see #makeAlphanumeric(String) 186 */ isAlphanumeric(String text)187 public static boolean isAlphanumeric(String text) { 188 return ALPHANUMERIC_REGEX.matcher(text).matches(); 189 } 190 191 192 193 /*---- Instance fields ----*/ 194 195 /** The mode indicator of this segment. Not {@code null}. */ 196 public final Mode mode; 197 198 /** The length of this segment's unencoded data. Measured in characters for 199 * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. 200 * Always zero or positive. Not the same as the data's bit length. */ 201 public final int numChars; 202 203 // The data bits of this segment. Not null. Accessed through getData(). 204 final BitBuffer data; 205 206 207 /*---- Constructor (low level) ----*/ 208 209 /** 210 * Constructs a QR Code segment with the specified attributes and data. 211 * The character count (numCh) must agree with the mode and the bit buffer length, 212 * but the constraint isn't checked. The specified bit buffer is cloned and stored. 213 * @param md the mode (not {@code null}) 214 * @param numCh the data length in characters or bytes, which is non-negative 215 * @param data the data bits (not {@code null}) 216 * @throws NullPointerException if the mode or data is {@code null} 217 * @throws IllegalArgumentException if the character count is negative 218 */ QrSegment(Mode md, int numCh, BitBuffer data)219 public QrSegment(Mode md, int numCh, BitBuffer data) { 220 mode = Objects.requireNonNull(md); 221 Objects.requireNonNull(data); 222 if (numCh < 0) 223 throw new IllegalArgumentException("Invalid value"); 224 numChars = numCh; 225 this.data = data.clone(); // Make defensive copy 226 } 227 228 229 /*---- Methods ----*/ 230 231 /** 232 * Returns the data bits of this segment. 233 * @return a new copy of the data bits (not {@code null}) 234 */ getData()235 public BitBuffer getData() { 236 return data.clone(); // Make defensive copy 237 } 238 239 240 // Calculates the number of bits needed to encode the given segments at the given version. 241 // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too 242 // many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE. getTotalBits(List<QrSegment> segs, int version)243 static int getTotalBits(List<QrSegment> segs, int version) { 244 Objects.requireNonNull(segs); 245 long result = 0; 246 for (QrSegment seg : segs) { 247 Objects.requireNonNull(seg); 248 int ccbits = seg.mode.numCharCountBits(version); 249 if (seg.numChars >= (1 << ccbits)) 250 return -1; // The segment's length doesn't fit the field's bit width 251 result += 4L + ccbits + seg.data.bitLength(); 252 if (result > Integer.MAX_VALUE) 253 return -1; // The sum will overflow an int type 254 } 255 return (int)result; 256 } 257 258 259 /*---- Constants ----*/ 260 261 // Describes precisely all strings that are encodable in numeric mode. 262 private static final Pattern NUMERIC_REGEX = Pattern.compile("[0-9]*"); 263 264 // Describes precisely all strings that are encodable in alphanumeric mode. 265 private static final Pattern ALPHANUMERIC_REGEX = Pattern.compile("[A-Z0-9 $%*+./:-]*"); 266 267 // The set of all legal characters in alphanumeric mode, where 268 // each character value maps to the index in the string. 269 static final String ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; 270 271 272 273 /*---- Public helper enumeration ----*/ 274 275 /** 276 * Describes how a segment's data bits are interpreted. 277 */ 278 public enum Mode { 279 280 /*-- Constants --*/ 281 282 NUMERIC (0x1, 10, 12, 14), 283 ALPHANUMERIC(0x2, 9, 11, 13), 284 BYTE (0x4, 8, 16, 16), 285 KANJI (0x8, 8, 10, 12), 286 ECI (0x7, 0, 0, 0); 287 288 289 /*-- Fields --*/ 290 291 // The mode indicator bits, which is a uint4 value (range 0 to 15). 292 final int modeBits; 293 294 // Number of character count bits for three different version ranges. 295 private final int[] numBitsCharCount; 296 297 298 /*-- Constructor --*/ 299 Mode(int mode, int... ccbits)300 private Mode(int mode, int... ccbits) { 301 modeBits = mode; 302 numBitsCharCount = ccbits; 303 } 304 305 306 /*-- Method --*/ 307 308 // Returns the bit width of the character count field for a segment in this mode 309 // in a QR Code at the given version number. The result is in the range [0, 16]. numCharCountBits(int ver)310 int numCharCountBits(int ver) { 311 assert QrCode.MIN_VERSION <= ver && ver <= QrCode.MAX_VERSION; 312 return numBitsCharCount[(ver + 7) / 17]; 313 } 314 315 } 316 317 } 318