Beispiel #1
0
        /// <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));
        }
Beispiel #2
0
        /// <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));
        }
Beispiel #3
0
        /// <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 &#x2264; minVersion &#x2264; maxVersion &#x2264; 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);
            }
        }
Beispiel #7
0
        // 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));
        }
Beispiel #10
0
        /// <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));
        }
Beispiel #11
0
        // 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);
        }
Beispiel #12
0
        // 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);
        }
Beispiel #13
0
        /// <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 &#x2264; minVersion &#x2264; maxVersion &#x2264; 40
        /// or -1 &#x2264; mask &#x2264; 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));
        }