/// <summary>
        /// Creates a segment representing the specified text string.
        /// The segment is encoded in alphanumeric mode.
        /// <para>
        /// Allowed characters are: 0 to 9, A to Z (uppercase only), space,
        /// dollar, percent, asterisk, plus, hyphen, period, slash, colon.
        /// </para>
        /// </summary>
        /// <param name="text">The text to encode, consisting of allowed characters only.</param>
        /// <returns>The created segment containing the text.</returns>
        /// <exception cref="ArgumentNullException"><c>text</c> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><c>text</c> contains non-encodable characters.</exception>
        public static QrSegment MakeAlphanumeric(string text)
        {
            Objects.RequireNonNull(text);
            if (!AlphanumericRegex.IsMatch(text))
            {
                throw new ArgumentOutOfRangeException(nameof(text), "String contains unencodable characters in alphanumeric mode");
            }

            BitArray ba = new BitArray(0);
            int      i;

            for (i = 0; i <= text.Length - 2; i += 2)
            {
                // Process groups of 2
                uint temp = (uint)AlphanumericCharset.IndexOf(text[i]) * 45;
                temp += (uint)AlphanumericCharset.IndexOf(text[i + 1]);
                ba.AppendBits(temp, 11);
            }
            if (i < text.Length)  // 1 character remaining
            {
                ba.AppendBits((uint)AlphanumericCharset.IndexOf(text[i]), 6);
            }

            return(new QrSegment(Mode.Alphanumeric, text.Length, ba));
        }
Exemple #2
0
        // Returns a new array representing the optimal mode per code point based on the given text and version.
        private static Mode[] ComputeCharacterModes(int[] codePoints, int version)
        {
            if (codePoints.Length == 0)
            {
                throw new ArgumentOutOfRangeException(nameof(codePoints));
            }

            Mode[] modeTypes = { Mode.Byte, Mode.Alphanumeric, Mode.Numeric, Mode.Kanji }; // Do not modify
            int    numModes  = modeTypes.Length;

            // Segment header sizes, measured in 1/6 bits
            var headCosts = new int[numModes];

            for (var i = 0; i < numModes; i++)
            {
                headCosts[i] = (4 + modeTypes[i].NumCharCountBits(version)) * 6;
            }

            // charModes[i][j] represents the mode to encode the code point at
            // index i such that the final segment ends in modeTypes[j] and the
            // total number of bits is minimized over all possible choices
            var charModes = new Mode[codePoints.Length, numModes];

            // At the beginning of each iteration of the loop below,
            // prevCosts[j] is the exact minimum number of 1/6 bits needed to
            // encode the entire string prefix of length i, and end in modeTypes[j]
            var prevCosts = (int[])headCosts.Clone();

            // Calculate costs using dynamic programming
            for (var i = 0; i < codePoints.Length; i++)
            {
                int c        = codePoints[i];
                var curCosts = new int[numModes];
                {
                    // Always extend a byte mode segment
                    curCosts[0]     = prevCosts[0] + CountUtf8Bytes(c) * 8 * 6;
                    charModes[i, 0] = modeTypes[0];
                }
                // Extend a segment if possible
                if (AlphanumericCharset.IndexOf((char)c) != -1)
                {
                    // Is alphanumeric
                    curCosts[1]     = prevCosts[1] + 33; // 5.5 bits per alphanumeric char
                    charModes[i, 1] = modeTypes[1];
                }

                if ('0' <= c && c <= '9')
                {
                    // Is numeric
                    curCosts[2]     = prevCosts[2] + 20; // 3.33 bits per digit
                    charModes[i, 2] = modeTypes[2];
                }

                if (IsKanji(c))
                {
                    curCosts[3]     = prevCosts[3] + 78; // 13 bits per Shift JIS char
                    charModes[i, 3] = modeTypes[3];
                }

                // Start new segment at the end to switch modes
                for (var j = 0; j < numModes; j++)
                {
                    // To mode
                    for (var k = 0; k < numModes; k++)
                    {
                        // From mode
                        int newCost = (curCosts[k] + 5) / 6 * 6 + headCosts[j];
                        if (charModes[i, k] == null || charModes[i, j] != null && newCost >= curCosts[j])
                        {
                            continue;
                        }
                        curCosts[j]     = newCost;
                        charModes[i, j] = modeTypes[k];
                    }
                }

                prevCosts = curCosts;
            }

            // Find optimal ending mode
            Mode curMode = null;

            for (int i = 0, minCost = 0; i < numModes; i++)
            {
                if (curMode != null && prevCosts[i] >= minCost)
                {
                    continue;
                }
                minCost = prevCosts[i];
                curMode = modeTypes[i];
            }

            // Get optimal mode for each code point by tracing backwards
            var result = new Mode[codePoints.Length];

            for (int i = result.Length - 1; i >= 0; i--)
            {
                for (var j = 0; j < numModes; j++)
                {
                    if (modeTypes[j] != curMode)
                    {
                        continue;
                    }
                    curMode   = charModes[i, j];
                    result[i] = curMode;
                    break;
                }
            }

            return(result);
        }