extensions/net.sf.basedb.otp/trunk/src/io/nayuki/qrcodegen/QrSegment.java

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