public static List <QrSegment> MakeSegmentsOptimally(string text, Ecc ecl, int minVersion, int maxVersion)
        {
            Utils.CheckNull(text, nameof(text));

            if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION))
            {
                throw new ArgumentException("Invalid value");
            }

            List <QrSegment> segs = null;
            var codePoints        = ToCodePoints(text);

            for (int version = minVersion; ; version++)
            {
                if (version == minVersion || version == 10 || version == 27)
                {
                    segs = MakeSegmentsOptimally(codePoints, version);
                }

                var dataCapacityBits = QrCode.GetNumDataCodewords(version, ecl) * 8;
                var dataUsedBits     = QrSegment.GetTotalBits(segs, version);
                if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
                {
                    return(segs);
                }
                if (version >= maxVersion)
                {
                    var msg = "Segment too long";
                    if (dataUsedBits != -1)
                    {
                        msg = String.Format("Data length = {0} bits, Max capacity = {1} bits", dataUsedBits, dataCapacityBits);
                    }
                    throw new DataTooLongException(msg);
                }
            }
        }
Example #2
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));
        }
Example #3
0
        public static QrCode EncodeSegments(List <QrSegment> segs, Ecc ecl, int minVersion, int maxVersion, int mask, bool boostEcl)
        {
            Utils.CheckNull(segs, nameof(segs));

            if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7)
            {
                throw new ArgumentException("Invalid value");
            }

            int version, dataUsedBits;

            for (version = minVersion; ; version++)
            {
                var capacityBits = GetNumDataCodewords(version, ecl) * 8;
                dataUsedBits = QrSegment.GetTotalBits(segs, version);
                if (dataUsedBits != -1 && dataUsedBits <= capacityBits)
                {
                    break;
                }

                if (version >= maxVersion)
                {
                    var msg = "Segment too long";
                    if (dataUsedBits != -1)
                    {
                        msg = string.Format("Data length = {0} bits, Max capacity = {1} bits", dataUsedBits, capacityBits);
                    }
                    throw new DataTooLongException(msg);
                }
            }

            foreach (var newEcl in new Ecc[] { Ecc.Low, Ecc.Medium, Ecc.Quartitle, Ecc.High })
            {
                if (boostEcl && dataUsedBits <= GetNumDataCodewords(version, newEcl) * 8)
                {
                    ecl = newEcl;
                }
            }

            var bb = new BitBuffer();

            foreach (var seg in segs)
            {
                bb.AppendBits(seg.Mode.ModeBits, 4);
                bb.AppendBits(seg.NumChars, seg.Mode.NumCharCountBits(version));
                bb.AppendData(seg.Data);
            }

            var dataCapacityBits = GetNumDataCodewords(version, ecl) * 8;

            bb.AppendBits(0, Math.Min(4, dataCapacityBits - bb.Length));
            bb.AppendBits(0, (8 - bb.Length % 8) % 8);

            for (var padByte = 0xEC; bb.Length < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
            {
                bb.AppendBits(padByte, 8);
            }

            var dataCodewords = new byte[bb.Length / 8];

            for (var i = 0; i < bb.Length; i++)
            {
                dataCodewords[i >> 3] = (byte)(dataCodewords[i >> 3] | bb.GetBit(i) << (7 - (i & 7)));
            }

            // Create the QR Code object
            return(new QrCode(version, ecl, dataCodewords, mask));
        }