15317bbafSopenharmony_ci/* 25317bbafSopenharmony_ci * Fast QR Code generator library 35317bbafSopenharmony_ci * 45317bbafSopenharmony_ci * Copyright (c) Project Nayuki. (MIT License) 55317bbafSopenharmony_ci * https://www.nayuki.io/page/fast-qr-code-generator-library 65317bbafSopenharmony_ci * 75317bbafSopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a copy of 85317bbafSopenharmony_ci * this software and associated documentation files (the "Software"), to deal in 95317bbafSopenharmony_ci * the Software without restriction, including without limitation the rights to 105317bbafSopenharmony_ci * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 115317bbafSopenharmony_ci * the Software, and to permit persons to whom the Software is furnished to do so, 125317bbafSopenharmony_ci * subject to the following conditions: 135317bbafSopenharmony_ci * - The above copyright notice and this permission notice shall be included in 145317bbafSopenharmony_ci * all copies or substantial portions of the Software. 155317bbafSopenharmony_ci * - The Software is provided "as is", without warranty of any kind, express or 165317bbafSopenharmony_ci * implied, including but not limited to the warranties of merchantability, 175317bbafSopenharmony_ci * fitness for a particular purpose and noninfringement. In no event shall the 185317bbafSopenharmony_ci * authors or copyright holders be liable for any claim, damages or other 195317bbafSopenharmony_ci * liability, whether in an action of contract, tort or otherwise, arising from, 205317bbafSopenharmony_ci * out of or in connection with the Software or the use or other dealings in the 215317bbafSopenharmony_ci * Software. 225317bbafSopenharmony_ci */ 235317bbafSopenharmony_ci 245317bbafSopenharmony_cipackage io.nayuki.fastqrcodegen; 255317bbafSopenharmony_ci 265317bbafSopenharmony_ciimport java.nio.charset.StandardCharsets; 275317bbafSopenharmony_ciimport java.util.ArrayList; 285317bbafSopenharmony_ciimport java.util.Arrays; 295317bbafSopenharmony_ciimport java.util.List; 305317bbafSopenharmony_ciimport java.util.Objects; 315317bbafSopenharmony_ci 325317bbafSopenharmony_ci 335317bbafSopenharmony_ci/** 345317bbafSopenharmony_ci * A segment of character/binary/control data in a QR Code symbol. 355317bbafSopenharmony_ci * Instances of this class are immutable. 365317bbafSopenharmony_ci * <p>The mid-level way to create a segment is to take the payload data and call a 375317bbafSopenharmony_ci * static factory function such as {@link QrSegment#makeNumeric(String)}. The low-level 385317bbafSopenharmony_ci * way to create a segment is to custom-make the bit buffer and call the {@link 395317bbafSopenharmony_ci * QrSegment#QrSegment(Mode,int,int[],int) constructor} with appropriate values.</p> 405317bbafSopenharmony_ci * <p>This segment class imposes no length restrictions, but QR Codes have restrictions. 415317bbafSopenharmony_ci * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. 425317bbafSopenharmony_ci * Any segment longer than this is meaningless for the purpose of generating QR Codes. 435317bbafSopenharmony_ci * This class can represent kanji mode segments, but provides no help in encoding them 445317bbafSopenharmony_ci * - see {@link QrSegmentAdvanced} for full kanji support.</p> 455317bbafSopenharmony_ci */ 465317bbafSopenharmony_cipublic final class QrSegment { 475317bbafSopenharmony_ci 485317bbafSopenharmony_ci /*---- Static factory functions (mid level) ----*/ 495317bbafSopenharmony_ci 505317bbafSopenharmony_ci /** 515317bbafSopenharmony_ci * Returns a segment representing the specified binary data 525317bbafSopenharmony_ci * encoded in byte mode. All input byte arrays are acceptable. 535317bbafSopenharmony_ci * <p>Any text string can be converted to UTF-8 bytes ({@code 545317bbafSopenharmony_ci * s.getBytes(StandardCharsets.UTF_8)}) and encoded as a byte mode segment.</p> 555317bbafSopenharmony_ci * @param data the binary data (not {@code null}) 565317bbafSopenharmony_ci * @return a segment (not {@code null}) containing the data 575317bbafSopenharmony_ci * @throws NullPointerException if the array is {@code null} 585317bbafSopenharmony_ci */ 595317bbafSopenharmony_ci public static QrSegment makeBytes(byte[] data) { 605317bbafSopenharmony_ci Objects.requireNonNull(data); 615317bbafSopenharmony_ci if (data.length * 8L > Integer.MAX_VALUE) 625317bbafSopenharmony_ci throw new IllegalArgumentException("Data too long"); 635317bbafSopenharmony_ci int[] bits = new int[(data.length + 3) / 4]; 645317bbafSopenharmony_ci for (int i = 0; i < data.length; i++) 655317bbafSopenharmony_ci bits[i >>> 2] |= (data[i] & 0xFF) << (~i << 3); 665317bbafSopenharmony_ci return new QrSegment(Mode.BYTE, data.length, bits, data.length * 8); 675317bbafSopenharmony_ci } 685317bbafSopenharmony_ci 695317bbafSopenharmony_ci 705317bbafSopenharmony_ci /** 715317bbafSopenharmony_ci * Returns a segment representing the specified string of decimal digits encoded in numeric mode. 725317bbafSopenharmony_ci * @param digits the text (not {@code null}), with only digits from 0 to 9 allowed 735317bbafSopenharmony_ci * @return a segment (not {@code null}) containing the text 745317bbafSopenharmony_ci * @throws NullPointerException if the string is {@code null} 755317bbafSopenharmony_ci * @throws IllegalArgumentException if the string contains non-digit characters 765317bbafSopenharmony_ci */ 775317bbafSopenharmony_ci public static QrSegment makeNumeric(String digits) { 785317bbafSopenharmony_ci Objects.requireNonNull(digits); 795317bbafSopenharmony_ci BitBuffer bb = new BitBuffer(); 805317bbafSopenharmony_ci int accumData = 0; 815317bbafSopenharmony_ci int accumCount = 0; 825317bbafSopenharmony_ci for (int i = 0; i < digits.length(); i++) { 835317bbafSopenharmony_ci char c = digits.charAt(i); 845317bbafSopenharmony_ci if (c < '0' || c > '9') 855317bbafSopenharmony_ci throw new IllegalArgumentException("String contains non-numeric characters"); 865317bbafSopenharmony_ci accumData = accumData * 10 + (c - '0'); 875317bbafSopenharmony_ci accumCount++; 885317bbafSopenharmony_ci if (accumCount == 3) { 895317bbafSopenharmony_ci bb.appendBits(accumData, 10); 905317bbafSopenharmony_ci accumData = 0; 915317bbafSopenharmony_ci accumCount = 0; 925317bbafSopenharmony_ci } 935317bbafSopenharmony_ci } 945317bbafSopenharmony_ci if (accumCount > 0) // 1 or 2 digits remaining 955317bbafSopenharmony_ci bb.appendBits(accumData, accumCount * 3 + 1); 965317bbafSopenharmony_ci return new QrSegment(Mode.NUMERIC, digits.length(), bb.data, bb.bitLength); 975317bbafSopenharmony_ci } 985317bbafSopenharmony_ci 995317bbafSopenharmony_ci 1005317bbafSopenharmony_ci /** 1015317bbafSopenharmony_ci * Returns a segment representing the specified text string encoded in alphanumeric mode. 1025317bbafSopenharmony_ci * The characters allowed are: 0 to 9, A to Z (uppercase only), space, 1035317bbafSopenharmony_ci * dollar, percent, asterisk, plus, hyphen, period, slash, colon. 1045317bbafSopenharmony_ci * @param text the text (not {@code null}), with only certain characters allowed 1055317bbafSopenharmony_ci * @return a segment (not {@code null}) containing the text 1065317bbafSopenharmony_ci * @throws NullPointerException if the string is {@code null} 1075317bbafSopenharmony_ci * @throws IllegalArgumentException if the string contains non-encodable characters 1085317bbafSopenharmony_ci */ 1095317bbafSopenharmony_ci public static QrSegment makeAlphanumeric(String text) { 1105317bbafSopenharmony_ci Objects.requireNonNull(text); 1115317bbafSopenharmony_ci BitBuffer bb = new BitBuffer(); 1125317bbafSopenharmony_ci int accumData = 0; 1135317bbafSopenharmony_ci int accumCount = 0; 1145317bbafSopenharmony_ci for (int i = 0; i < text.length(); i++) { 1155317bbafSopenharmony_ci char c = text.charAt(i); 1165317bbafSopenharmony_ci if (c >= ALPHANUMERIC_MAP.length || ALPHANUMERIC_MAP[c] == -1) 1175317bbafSopenharmony_ci throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode"); 1185317bbafSopenharmony_ci accumData = accumData * 45 + ALPHANUMERIC_MAP[c]; 1195317bbafSopenharmony_ci accumCount++; 1205317bbafSopenharmony_ci if (accumCount == 2) { 1215317bbafSopenharmony_ci bb.appendBits(accumData, 11); 1225317bbafSopenharmony_ci accumData = 0; 1235317bbafSopenharmony_ci accumCount = 0; 1245317bbafSopenharmony_ci } 1255317bbafSopenharmony_ci } 1265317bbafSopenharmony_ci if (accumCount > 0) // 1 character remaining 1275317bbafSopenharmony_ci bb.appendBits(accumData, 6); 1285317bbafSopenharmony_ci return new QrSegment(Mode.ALPHANUMERIC, text.length(), bb.data, bb.bitLength); 1295317bbafSopenharmony_ci } 1305317bbafSopenharmony_ci 1315317bbafSopenharmony_ci 1325317bbafSopenharmony_ci /** 1335317bbafSopenharmony_ci * Returns a list of zero or more segments to represent the specified Unicode text string. 1345317bbafSopenharmony_ci * The result may use various segment modes and switch modes to optimize the length of the bit stream. 1355317bbafSopenharmony_ci * @param text the text to be encoded, which can be any Unicode string 1365317bbafSopenharmony_ci * @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text 1375317bbafSopenharmony_ci * @throws NullPointerException if the text is {@code null} 1385317bbafSopenharmony_ci */ 1395317bbafSopenharmony_ci public static List<QrSegment> makeSegments(String text) { 1405317bbafSopenharmony_ci Objects.requireNonNull(text); 1415317bbafSopenharmony_ci 1425317bbafSopenharmony_ci // Select the most efficient segment encoding automatically 1435317bbafSopenharmony_ci List<QrSegment> result = new ArrayList<>(); 1445317bbafSopenharmony_ci if (text.equals("")); // Leave result empty 1455317bbafSopenharmony_ci else if (isNumeric(text)) 1465317bbafSopenharmony_ci result.add(makeNumeric(text)); 1475317bbafSopenharmony_ci else if (isAlphanumeric(text)) 1485317bbafSopenharmony_ci result.add(makeAlphanumeric(text)); 1495317bbafSopenharmony_ci else 1505317bbafSopenharmony_ci result.add(makeBytes(text.getBytes(StandardCharsets.UTF_8))); 1515317bbafSopenharmony_ci return result; 1525317bbafSopenharmony_ci } 1535317bbafSopenharmony_ci 1545317bbafSopenharmony_ci 1555317bbafSopenharmony_ci /** 1565317bbafSopenharmony_ci * Returns a segment representing an Extended Channel Interpretation 1575317bbafSopenharmony_ci * (ECI) designator with the specified assignment value. 1585317bbafSopenharmony_ci * @param assignVal the ECI assignment number (see the AIM ECI specification) 1595317bbafSopenharmony_ci * @return a segment (not {@code null}) containing the data 1605317bbafSopenharmony_ci * @throws IllegalArgumentException if the value is outside the range [0, 10<sup>6</sup>) 1615317bbafSopenharmony_ci */ 1625317bbafSopenharmony_ci public static QrSegment makeEci(int assignVal) { 1635317bbafSopenharmony_ci BitBuffer bb = new BitBuffer(); 1645317bbafSopenharmony_ci if (assignVal < 0) 1655317bbafSopenharmony_ci throw new IllegalArgumentException("ECI assignment value out of range"); 1665317bbafSopenharmony_ci else if (assignVal < (1 << 7)) 1675317bbafSopenharmony_ci bb.appendBits(assignVal, 8); 1685317bbafSopenharmony_ci else if (assignVal < (1 << 14)) { 1695317bbafSopenharmony_ci bb.appendBits(2, 2); 1705317bbafSopenharmony_ci bb.appendBits(assignVal, 14); 1715317bbafSopenharmony_ci } else if (assignVal < 1_000_000) { 1725317bbafSopenharmony_ci bb.appendBits(6, 3); 1735317bbafSopenharmony_ci bb.appendBits(assignVal, 21); 1745317bbafSopenharmony_ci } else 1755317bbafSopenharmony_ci throw new IllegalArgumentException("ECI assignment value out of range"); 1765317bbafSopenharmony_ci return new QrSegment(Mode.ECI, 0, bb.data, bb.bitLength); 1775317bbafSopenharmony_ci } 1785317bbafSopenharmony_ci 1795317bbafSopenharmony_ci 1805317bbafSopenharmony_ci /** 1815317bbafSopenharmony_ci * Tests whether the specified string can be encoded as a segment in numeric mode. 1825317bbafSopenharmony_ci * A string is encodable iff each character is in the range 0 to 9. 1835317bbafSopenharmony_ci * @param text the string to test for encodability (not {@code null}) 1845317bbafSopenharmony_ci * @return {@code true} iff each character is in the range 0 to 9. 1855317bbafSopenharmony_ci * @throws NullPointerException if the string is {@code null} 1865317bbafSopenharmony_ci * @see #makeNumeric(String) 1875317bbafSopenharmony_ci */ 1885317bbafSopenharmony_ci public static boolean isNumeric(String text) { 1895317bbafSopenharmony_ci for (int i = 0; i < text.length(); i++) { 1905317bbafSopenharmony_ci char c = text.charAt(i); 1915317bbafSopenharmony_ci if (c < '0' || c > '9') 1925317bbafSopenharmony_ci return false; 1935317bbafSopenharmony_ci } 1945317bbafSopenharmony_ci return true; 1955317bbafSopenharmony_ci } 1965317bbafSopenharmony_ci 1975317bbafSopenharmony_ci 1985317bbafSopenharmony_ci /** 1995317bbafSopenharmony_ci * Tests whether the specified string can be encoded as a segment in alphanumeric mode. 2005317bbafSopenharmony_ci * A string is encodable iff each character is in the following set: 0 to 9, A to Z 2015317bbafSopenharmony_ci * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. 2025317bbafSopenharmony_ci * @param text the string to test for encodability (not {@code null}) 2035317bbafSopenharmony_ci * @return {@code true} iff each character is in the alphanumeric mode character set 2045317bbafSopenharmony_ci * @throws NullPointerException if the string is {@code null} 2055317bbafSopenharmony_ci * @see #makeAlphanumeric(String) 2065317bbafSopenharmony_ci */ 2075317bbafSopenharmony_ci public static boolean isAlphanumeric(String text) { 2085317bbafSopenharmony_ci for (int i = 0; i < text.length(); i++) { 2095317bbafSopenharmony_ci char c = text.charAt(i); 2105317bbafSopenharmony_ci if (c >= ALPHANUMERIC_MAP.length || ALPHANUMERIC_MAP[c] == -1) 2115317bbafSopenharmony_ci return false; 2125317bbafSopenharmony_ci } 2135317bbafSopenharmony_ci return true; 2145317bbafSopenharmony_ci } 2155317bbafSopenharmony_ci 2165317bbafSopenharmony_ci 2175317bbafSopenharmony_ci 2185317bbafSopenharmony_ci /*---- Instance fields ----*/ 2195317bbafSopenharmony_ci 2205317bbafSopenharmony_ci /** The mode indicator of this segment. Not {@code null}. */ 2215317bbafSopenharmony_ci public final Mode mode; 2225317bbafSopenharmony_ci 2235317bbafSopenharmony_ci /** The length of this segment's unencoded data. Measured in characters for 2245317bbafSopenharmony_ci * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. 2255317bbafSopenharmony_ci * Always zero or positive. Not the same as the data's bit length. */ 2265317bbafSopenharmony_ci public final int numChars; 2275317bbafSopenharmony_ci 2285317bbafSopenharmony_ci // The data bits of this segment. Not null. 2295317bbafSopenharmony_ci final int[] data; 2305317bbafSopenharmony_ci 2315317bbafSopenharmony_ci // Requires 0 <= bitLength <= data.length * 32. 2325317bbafSopenharmony_ci final int bitLength; 2335317bbafSopenharmony_ci 2345317bbafSopenharmony_ci 2355317bbafSopenharmony_ci /*---- Constructor (low level) ----*/ 2365317bbafSopenharmony_ci 2375317bbafSopenharmony_ci /** 2385317bbafSopenharmony_ci * Constructs a QR Code segment with the specified attributes and data. 2395317bbafSopenharmony_ci * The character count (numCh) must agree with the mode and the bit buffer length, 2405317bbafSopenharmony_ci * but the constraint isn't checked. The specified bit buffer is cloned and stored. 2415317bbafSopenharmony_ci * @param md the mode (not {@code null}) 2425317bbafSopenharmony_ci * @param numCh the data length in characters or bytes, which is non-negative 2435317bbafSopenharmony_ci * @param data the data bits (not {@code null}) 2445317bbafSopenharmony_ci * @param bitLen the number of valid prefix bits in the data array 2455317bbafSopenharmony_ci * @throws NullPointerException if the mode or data is {@code null} 2465317bbafSopenharmony_ci * @throws IllegalArgumentException if the character count is negative 2475317bbafSopenharmony_ci */ 2485317bbafSopenharmony_ci public QrSegment(Mode md, int numCh, int[] data, int bitLen) { 2495317bbafSopenharmony_ci mode = Objects.requireNonNull(md); 2505317bbafSopenharmony_ci this.data = Objects.requireNonNull(data); 2515317bbafSopenharmony_ci if (numCh < 0 || bitLen < 0 || bitLen > data.length * 32L) 2525317bbafSopenharmony_ci throw new IllegalArgumentException("Invalid value"); 2535317bbafSopenharmony_ci numChars = numCh; 2545317bbafSopenharmony_ci bitLength = bitLen; 2555317bbafSopenharmony_ci } 2565317bbafSopenharmony_ci 2575317bbafSopenharmony_ci 2585317bbafSopenharmony_ci // Calculates the number of bits needed to encode the given segments at the given version. 2595317bbafSopenharmony_ci // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too 2605317bbafSopenharmony_ci // many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE. 2615317bbafSopenharmony_ci static int getTotalBits(List<QrSegment> segs, int version) { 2625317bbafSopenharmony_ci Objects.requireNonNull(segs); 2635317bbafSopenharmony_ci long result = 0; 2645317bbafSopenharmony_ci for (QrSegment seg : segs) { 2655317bbafSopenharmony_ci Objects.requireNonNull(seg); 2665317bbafSopenharmony_ci int ccbits = seg.mode.numCharCountBits(version); 2675317bbafSopenharmony_ci if (seg.numChars >= (1 << ccbits)) 2685317bbafSopenharmony_ci return -1; // The segment's length doesn't fit the field's bit width 2695317bbafSopenharmony_ci result += 4L + ccbits + seg.bitLength; 2705317bbafSopenharmony_ci if (result > Integer.MAX_VALUE) 2715317bbafSopenharmony_ci return -1; // The sum will overflow an int type 2725317bbafSopenharmony_ci } 2735317bbafSopenharmony_ci return (int)result; 2745317bbafSopenharmony_ci } 2755317bbafSopenharmony_ci 2765317bbafSopenharmony_ci 2775317bbafSopenharmony_ci /*---- Constants ----*/ 2785317bbafSopenharmony_ci 2795317bbafSopenharmony_ci static final int[] ALPHANUMERIC_MAP; 2805317bbafSopenharmony_ci 2815317bbafSopenharmony_ci static { 2825317bbafSopenharmony_ci final String ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; 2835317bbafSopenharmony_ci int maxCh = -1; 2845317bbafSopenharmony_ci for (int i = 0; i < ALPHANUMERIC_CHARSET.length(); i++) 2855317bbafSopenharmony_ci maxCh = Math.max(ALPHANUMERIC_CHARSET.charAt(i), maxCh); 2865317bbafSopenharmony_ci ALPHANUMERIC_MAP = new int[maxCh + 1]; 2875317bbafSopenharmony_ci Arrays.fill(ALPHANUMERIC_MAP, -1); 2885317bbafSopenharmony_ci for (int i = 0; i < ALPHANUMERIC_CHARSET.length(); i++) 2895317bbafSopenharmony_ci ALPHANUMERIC_MAP[ALPHANUMERIC_CHARSET.charAt(i)] = i; 2905317bbafSopenharmony_ci } 2915317bbafSopenharmony_ci 2925317bbafSopenharmony_ci 2935317bbafSopenharmony_ci 2945317bbafSopenharmony_ci /*---- Public helper enumeration ----*/ 2955317bbafSopenharmony_ci 2965317bbafSopenharmony_ci /** 2975317bbafSopenharmony_ci * Describes how a segment's data bits are interpreted. 2985317bbafSopenharmony_ci */ 2995317bbafSopenharmony_ci public enum Mode { 3005317bbafSopenharmony_ci 3015317bbafSopenharmony_ci /*-- Constants --*/ 3025317bbafSopenharmony_ci 3035317bbafSopenharmony_ci NUMERIC (0x1, 10, 12, 14), 3045317bbafSopenharmony_ci ALPHANUMERIC(0x2, 9, 11, 13), 3055317bbafSopenharmony_ci BYTE (0x4, 8, 16, 16), 3065317bbafSopenharmony_ci KANJI (0x8, 8, 10, 12), 3075317bbafSopenharmony_ci ECI (0x7, 0, 0, 0); 3085317bbafSopenharmony_ci 3095317bbafSopenharmony_ci 3105317bbafSopenharmony_ci /*-- Fields --*/ 3115317bbafSopenharmony_ci 3125317bbafSopenharmony_ci // The mode indicator bits, which is a uint4 value (range 0 to 15). 3135317bbafSopenharmony_ci final int modeBits; 3145317bbafSopenharmony_ci 3155317bbafSopenharmony_ci // Number of character count bits for three different version ranges. 3165317bbafSopenharmony_ci private final int[] numBitsCharCount; 3175317bbafSopenharmony_ci 3185317bbafSopenharmony_ci 3195317bbafSopenharmony_ci /*-- Constructor --*/ 3205317bbafSopenharmony_ci 3215317bbafSopenharmony_ci private Mode(int mode, int... ccbits) { 3225317bbafSopenharmony_ci modeBits = mode; 3235317bbafSopenharmony_ci numBitsCharCount = ccbits; 3245317bbafSopenharmony_ci } 3255317bbafSopenharmony_ci 3265317bbafSopenharmony_ci 3275317bbafSopenharmony_ci /*-- Method --*/ 3285317bbafSopenharmony_ci 3295317bbafSopenharmony_ci // Returns the bit width of the character count field for a segment in this mode 3305317bbafSopenharmony_ci // in a QR Code at the given version number. The result is in the range [0, 16]. 3315317bbafSopenharmony_ci int numCharCountBits(int ver) { 3325317bbafSopenharmony_ci assert QrCode.MIN_VERSION <= ver && ver <= QrCode.MAX_VERSION; 3335317bbafSopenharmony_ci return numBitsCharCount[(ver + 7) / 17]; 3345317bbafSopenharmony_ci } 3355317bbafSopenharmony_ci 3365317bbafSopenharmony_ci } 3375317bbafSopenharmony_ci 3385317bbafSopenharmony_ci} 339