/// <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)); }
// 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); }