/// <summary> /// Creates a QR code representing the specified binary data using the specified error correction level. /// <para> /// This function encodes the data in the binary segment mode. The maximum number of /// bytes allowed is 2953. The smallest possible QR code version is automatically chosen. /// The resulting ECC level will be higher than the one specified if it can be achieved without increasing the size (version). /// </para> /// </summary> /// <param name="data">The binary data to encode.</param> /// <param name="ecl">The minimum error correction level to use.</param> /// <returns>The created QR code representing the specified data.</returns> /// <exception cref="ArgumentNullException"><paramref name="data"/> or <paramref name="ecl"/> is <c>null</c>.</exception> /// <exception cref="DataTooLongException">The specified data is too long to fit in the largest QR code size (version) /// at the specified error correction level.</exception> public static QrCode EncodeBinary(byte[] data, Ecc ecl) { Objects.RequireNonNull(data); Objects.RequireNonNull(ecl); QrSegment seg = QrSegment.MakeBytes(data); return(EncodeSegments(new List <QrSegment> { seg }, ecl)); }
/// <summary> /// Creates a segment representing the specified binary data /// encoded in byte mode. All input byte arrays are acceptable. /// <para> /// Any text string can be converted to UTF-8 bytes (using <c>Encoding.UTF8.GetBytes(str)</c>) /// and encoded as a byte mode segment. /// </para> /// </summary> /// <param name="data">The binary data to encode.</param> /// <returns>The created segment containing the specified data.</returns> /// <exception cref="ArgumentNullException"><c>data</c> is <c>null</c>.</exception> public static QrSegment MakeBytes(byte[] data) { Objects.RequireNonNull(data); BitArray ba = new BitArray(0); foreach (byte b in data) { ba.AppendBits(b, 8); } return(new QrSegment(Mode.Byte, data.Length, ba)); }
/// <summary> /// Initializes a QR code segment with the specified attributes and data. /// <para> /// The character count <paramref name="numChars"/> must agree with the mode and the bit array length, /// but the constraint isn't checked. The specified bit array is cloned. /// </para> /// </summary> /// <param name="mode">The segment mode used to encode this segment.</param> /// <param name="numChars">The data length in characters or bytes (depending on the segment mode).</param> /// <param name="data">The data bits.</param> /// <exception cref="ArgumentNullException"><paramref name="mode"/> or <paramref name="data"/> is <c>null</c>.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="numChars"/> is negative.</exception> public QrSegment(Mode mode, int numChars, BitArray data) { EncodingMode = Objects.RequireNonNull(mode); Objects.RequireNonNull(data); if (numChars < 0) { throw new ArgumentOutOfRangeException(nameof(numChars), "Invalid value"); } NumChars = numChars; _data = (BitArray)data.Clone(); // Make defensive copy }
/// <summary> /// Tests whether the specified string can be encoded as a segment in Kanji mode. /// <para> /// Broadly speaking, the set of encodable characters are Kanji used in Japan, /// Hiragana, Katakana, East Asian punctuation, full-width ASCII, Greek, and Cyrillic. /// Examples of non-encodable characters include ordinary ASCII, half-width Katakana, /// more extensive Chinese Hanzi. /// </para> /// </summary> /// <param name="text">The text to test for encodability.</param> /// <returns><c>true</c> iff each character is in the Kanji mode character set.</returns> /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception> public static bool IsEncodableAsKanji(string text) { Objects.RequireNonNull(text); foreach (char t in text) { if (!IsKanji(t)) { return(false); } } return(true); }
/// <summary> /// Appends the content of the specified bit array to the end of this array. /// </summary> /// <param name="bitArray">The BitArray instance that this method extends.</param> /// <param name="otherArray">The bit array to append</param> /// <exception cref="ArgumentNullException">If <c>bitArray</c> is <c>null</c>.</exception> public static void AppendData(this BitArray bitArray, BitArray otherArray) { Objects.RequireNonNull(otherArray); int bitLength = bitArray.Length; bitArray.Length = bitLength + otherArray.Length; for (int i = 0; i < otherArray.Length; i++, bitLength++) // Append bit by bit { if (otherArray[i]) { bitArray.Set(bitLength, true); } } }
/// <summary> /// Creates a list of zero or more segments to represent the specified text string. /// The resulting list optimally minimizes the total encoded bit length, subjected to the constraints /// of the specified error correction level, minimum and maximum version number. /// <para> /// This function potentially uses all four text encoding modes: numeric, alphanumeric, byte (UTF-8), /// and Kanji. It is a more sophisticated but slower replacement for <see cref="MakeSegments"/>. /// </para> /// <para> /// The text to be encoded can contain the full set of Unicode characters (code points). /// </para> /// </summary> /// <param name="text">The text to be encoded.</param> /// <param name="ecl">The error correction level to use.</param> /// <param name="minVersion">The minimum version (size) of the QR code (between 1 and 40).</param> /// <param name="maxVersion">The maximum version (size) of the QR code (between 1 and 40).</param> /// <returns>The created mutable list of segments encoding the specified text with a minimal bit length.</returns> /// <exception cref="ArgumentNullException"><paramref name="text"/> or <paramref name="ecl"/> is <c>null</c>.</exception> /// <exception cref="ArgumentOutOfRangeException">1 ≤ minVersion ≤ maxVersion ≤ 40 is violated.</exception> /// <exception cref="DataTooLongException">The text is too long to fit into the QR code with the given encoding parameters.</exception> public static List <QrSegment> MakeSegmentsOptimally(string text, QrCode.Ecc ecl, int minVersion = QrCode.MinVersion, int maxVersion = QrCode.MaxVersion) { // Check arguments Objects.RequireNonNull(text); Objects.RequireNonNull(ecl); if (minVersion < QrCode.MinVersion || minVersion > maxVersion) { throw new ArgumentOutOfRangeException(nameof(minVersion), "Invalid value"); } if (maxVersion > QrCode.MaxVersion) { throw new ArgumentOutOfRangeException(nameof(maxVersion), "Invalid value"); } // Iterate through version numbers, and make tentative segments List <QrSegment> segs = null; var codePoints = ToCodePoints(text); for (int version = minVersion;; version++) { if (version == minVersion || version == 10 || version == 27) { segs = MakeSegmentsOptimally(codePoints, version); } Debug.Assert(segs != null); // Check if the segments fit int dataCapacityBits = QrCode.GetNumDataCodewords(version, ecl) * 8; int dataUsedBits = GetTotalBits(segs, version); if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) { return(segs); // This version number is found to be suitable } if (version < maxVersion) { continue; } // All versions in the range could not fit the given text var msg = "Segment too long"; if (dataUsedBits != -1) { msg = $"Data length = {dataUsedBits} bits, Max capacity = {dataCapacityBits} bits"; } throw new DataTooLongException(msg); } }
// 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 ArgumentOutOfRangeException(); } // Calculate parameter numbers int numBlocks = NumErrorCorrectionBlocks[ErrorCorrectionLevel.Ordinal, Version]; int blockEccLen = EccCodewordsPerBlock[ErrorCorrectionLevel.Ordinal, Version]; int rawCodewords = GetNumRawDataModules(Version) / 8; int numShortBlocks = numBlocks - rawCodewords % numBlocks; int shortBlockLen = rawCodewords / numBlocks; // Split data into blocks and append ECC to each block byte[][] blocks = new byte[numBlocks][]; ReedSolomonGenerator rs = new ReedSolomonGenerator(blockEccLen); for (int i = 0, k = 0; i < numBlocks; i++) { byte[] dat = CopyOfRange(data, k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)); k += dat.Length; byte[] block = CopyOf(dat, shortBlockLen + 1); byte[] ecc = rs.GetRemainder(dat); Array.Copy(ecc, 0, block, block.Length - blockEccLen, ecc.Length); blocks[i] = block; } // Interleave (not concatenate) the bytes from every block into a single sequence byte[] result = new byte[rawCodewords]; for (int i = 0, k = 0; i < blocks[0].Length; i++) { for (int j = 0; j < blocks.Length; j++) { // Skip the padding byte in short blocks if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) { result[k] = blocks[j][i]; k++; } } } return(result); }
/// <summary> /// Computes the Reed-Solomon error correction codewords for the specified /// sequence of data codewords. /// <para> /// This method does not alter this object's state (as it is immutable). /// </para> /// </summary> /// <param name="data">The sequence of data codewords.</param> /// <returns>The Reed-Solomon error correction codewords, as a byte array.</returns> /// <exception cref="ArgumentNullException">If <c>data</c> is <c>null</c>.</exception> internal byte[] GetRemainder(byte[] data) { Objects.RequireNonNull(data); // Compute the remainder by performing polynomial division byte[] result = new byte[_coefficients.Length]; foreach (byte b in data) { uint factor = (uint)(b ^ result[0]); Array.Copy(result, 1, result, 0, result.Length - 1); result[result.Length - 1] = 0; for (int i = 0; i < result.Length; i++) { result[i] ^= Multiply(_coefficients[i], factor); } } return(result); }
/// <summary> /// Creates a segment encoding the specified text in Kanji mode. /// <para> /// Broadly speaking, the set of encodable characters are Kanji used in Japan, /// Hiragana, Katakana, East Asian punctuation, full-width ASCII, Greek, and Cyrillic. /// Examples of non-encodable characters include ordinary ASCII, half-width Katakana, /// more extensive Chinese Hanzi. /// </para> /// </summary> /// <param name="text">The text to encoding, containing only characters allowed by the Kanji encoding.</param> /// <returns>The created segment respresenting the specified text.</returns> /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="text"/> contains non-encodable characters.</exception> /// <seealso cref="IsEncodableAsKanji"/> public static QrSegment MakeKanji(string text) { Objects.RequireNonNull(text); var ba = new BitArray(0); foreach (char t in text) { ushort val = UnicodeToQrKanji[t]; if (val == 0xffff) { throw new ArgumentOutOfRangeException(nameof(text), "String contains non-kanji-mode characters"); } ba.AppendBits(val, 13); } return(new QrSegment(Mode.Kanji, text.Length, ba)); }
/// <summary> /// Creates a segment representing the specified string of decimal digits. /// The segment is encoded in numeric mode. /// </summary> /// <param name="digits">The text to encode, consisting of digits from 0 to 9 only.</param> /// <returns>The created segment containing the text.</returns> /// <exception cref="ArgumentNullException"><c>digits</c> is <c>null</c>.</exception> /// <exception cref="ArgumentOutOfRangeException"><c>digits</c> contains non-digit characters</exception> public static QrSegment MakeNumeric(string digits) { Objects.RequireNonNull(digits); if (!NumericRegex.IsMatch(digits)) { throw new ArgumentOutOfRangeException(nameof(digits), "String contains non-numeric characters"); } BitArray ba = new BitArray(0); for (int i = 0; i < digits.Length;) { // Consume up to 3 digits per iteration int n = Math.Min(digits.Length - i, 3); ba.AppendBits(uint.Parse(digits.Substring(i, n)), n * 3 + 1); i += n; } return(new QrSegment(Mode.Numeric, digits.Length, ba)); }
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire // data area of this QR code. Function modules need to be marked off before this is called. private void DrawCodewords(byte[] data) { Objects.RequireNonNull(data); if (data.Length != GetNumRawDataModules(Version) / 8) { throw new ArgumentOutOfRangeException(); } int i = 0; // Bit index into the data // Do the funny zigzag scan for (int right = Size - 1; right >= 1; right -= 2) { // Index of right column in each column pair if (right == 6) { right = 5; } for (int vert = 0; vert < Size; vert++) { // Vertical counter for (int j = 0; j < 2; j++) { int x = right - j; // Actual x coordinate bool upward = ((right + 1) & 2) == 0; int y = upward ? Size - 1 - vert : vert; // Actual y coordinate if (_isFunction[y * Size + x] || i >= data.Length * 8) { continue; } _modules[y * Size + x] = GetBit(data[(uint)i >> 3], 7 - (i & 7)); i++; // If this QR code has any remainder bits (0 to 7), they were assigned as // 0/false/white by the constructor and are left unchanged by this method } } } Debug.Assert(i == data.Length * 8); }
// 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 // many characters to fit its length field, or the total bits exceeds int.MaxValue. internal static int GetTotalBits(List <QrSegment> segs, int version) { Objects.RequireNonNull(segs); long result = 0; foreach (QrSegment seg in segs) { Objects.RequireNonNull(seg); int ccbits = seg.EncodingMode.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.Length; if (result > int.MaxValue) { return(-1); // The sum will overflow an int type } } return((int)result); }
/// <summary> /// Creates a QR code representing the specified segments with the specified encoding parameters. /// <para> /// The smallest possible QR code version (size) is used. The range of versions can be /// restricted by the <paramref name="minVersion"/> and <paramref name="maxVersion"/> parameters. /// </para> /// <para> /// If <paramref name="boostEcl"/> is <c>true</c>, the resulting ECC level will be higher than the /// one specified if it can be achieved without increasing the size (version). /// </para> /// <para> /// The QR code mask is usually automatically chosen. It can be explicitly set with the <paramref name="mask"/> /// parameter by using a value between 0 to 7 (inclusive). -1 is for automatic mode (which may be slow). /// </para> /// <para> /// 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 and gives full control over all /// encoding paramters. /// </para> /// </summary> /// <remarks> /// This is a mid-level API; the high-level APIs are <see cref="EncodeText(string, Ecc)"/> /// and <see cref="EncodeBinary(byte[], Ecc)"/>. /// </remarks> /// <param name="segments">The segments to encode.</param> /// <param name="ecl">The minimal or fixed error correction level to use .</param> /// <param name="minVersion">The minimum version (size) of the QR code (between 1 and 40).</param> /// <param name="maxVersion">The maximum version (size) of the QR code (between 1 and 40).</param> /// <param name="mask">The mask number to use (between 0 and 7), or -1 for automatic mask selection.</param> /// <param name="boostEcl">If <c>true</c> the ECC level wil be increased if it can be achieved without increasing the size (version).</param> /// <returns>The created QR code representing the segments.</returns> /// <exception cref="ArgumentNullException"><paramref name="segments"/>, any list element, or <paramref name="ecl"/> is <c>null</c>.</exception> /// <exception cref="ArgumentOutOfRangeException">1 ≤ minVersion ≤ maxVersion ≤ 40 /// or -1 ≤ mask ≤ 7 is violated.</exception> /// <exception cref="DataTooLongException">The segments are too long to fit in the largest QR code size (version) /// at the specified error correction level.</exception> public static QrCode EncodeSegments(List <QrSegment> segments, Ecc ecl, int minVersion = MinVersion, int maxVersion = MaxVersion, int mask = -1, bool boostEcl = true) { Objects.RequireNonNull(segments); Objects.RequireNonNull(ecl); if (minVersion < MinVersion || minVersion > maxVersion) { throw new ArgumentOutOfRangeException(nameof(minVersion), "Invalid value"); } if (maxVersion > MaxVersion) { throw new ArgumentOutOfRangeException(nameof(maxVersion), "Invalid value"); } if (mask < -1 || mask > 7) { throw new ArgumentOutOfRangeException(nameof(mask), "Invalid value"); } // Find the minimal version number to use int version, dataUsedBits; for (version = minVersion; ; version++) { int numDataBits = GetNumDataCodewords(version, ecl) * 8; // Number of data bits available dataUsedBits = QrSegment.GetTotalBits(segments, version); if (dataUsedBits != -1 && dataUsedBits <= numDataBits) { break; // This version number is found to be suitable } if (version >= maxVersion) { // All versions in the range could not fit the given data string msg = "Segment too long"; if (dataUsedBits != -1) { msg = $"Data length = {dataUsedBits} bits, Max capacity = {numDataBits} bits"; } throw new DataTooLongException(msg); } } Debug.Assert(dataUsedBits != -1); // Increase the error correction level while the data still fits in the current version number foreach (Ecc newEcl in Ecc.AllValues) { // From low to high if (boostEcl && dataUsedBits <= GetNumDataCodewords(version, newEcl) * 8) { ecl = newEcl; } } // Concatenate all segments to create the data bit string BitArray ba = new BitArray(0); foreach (QrSegment seg in segments) { ba.AppendBits(seg.EncodingMode.ModeBits, 4); ba.AppendBits((uint)seg.NumChars, seg.EncodingMode.NumCharCountBits(version)); ba.AppendData(seg.GetData()); } Debug.Assert(ba.Length == dataUsedBits); // Add terminator and pad up to a byte if applicable int dataCapacityBits = GetNumDataCodewords(version, ecl) * 8; Debug.Assert(ba.Length <= dataCapacityBits); ba.AppendBits(0, Math.Min(4, dataCapacityBits - ba.Length)); ba.AppendBits(0, (8 - ba.Length % 8) % 8); Debug.Assert(ba.Length % 8 == 0); // Pad with alternating bytes until data capacity is reached for (uint padByte = 0xEC; ba.Length < dataCapacityBits; padByte ^= 0xEC ^ 0x11) { ba.AppendBits(padByte, 8); } // Pack bits into bytes in big endian byte[] dataCodewords = new byte[ba.Length / 8]; for (int i = 0; i < ba.Length; i++) { if (ba.Get(i)) { dataCodewords[i >> 3] |= (byte)(1 << (7 - (i & 7))); } } // Create the QR code object return(new QrCode(version, ecl, dataCodewords, mask)); }