Skip to content
38 changes: 32 additions & 6 deletions QRCoder/QRCodeGenerator/AlphanumericDataSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,22 @@ public AlphanumericDataSegment(string alphanumericText)
/// <param name="version">The QR code version (1-40, or -1 to -4 for Micro QR)</param>
/// <returns>The total number of bits required for this segment</returns>
public override int GetBitLength(int version)
{
return GetBitLength(Text.Length, version);
}

/// <summary>
/// Calculates the total bit length for encoding alphanumeric text of a given length for a specific QR code version.
/// Includes mode indicator, count indicator, and data bits.
/// </summary>
/// <param name="textLength">The length of the alphanumeric text</param>
/// <param name="version">The QR code version (1-40, or -1 to -4 for Micro QR)</param>
/// <returns>The total number of bits required</returns>
public static int GetBitLength(int textLength, int version)
{
int modeIndicatorLength = 4;
int countIndicatorLength = GetCountIndicatorLength(version, EncodingMode.Alphanumeric);
int dataLength = AlphanumericEncoder.GetBitLength(Text);
int dataLength = AlphanumericEncoder.GetBitLength(textLength);
int length = modeIndicatorLength + countIndicatorLength + dataLength;

return length;
Expand All @@ -45,19 +57,33 @@ public override int GetBitLength(int version)
/// <returns>The next index in the BitArray after the last bit written</returns>
public override int WriteTo(BitArray bitArray, int startIndex, int version)
{
var index = startIndex;
return WriteTo(Text, 0, Text.Length, bitArray, startIndex, version);
}

/// <summary>
/// Writes a portion of alphanumeric text to a BitArray at the specified index.
/// Includes mode indicator, count indicator, and data bits.
/// </summary>
/// <param name="text">The full alphanumeric text</param>
/// <param name="offset">The starting index in the text to encode from</param>
/// <param name="length">The number of characters to encode</param>
/// <param name="bitArray">The target BitArray to write to</param>
/// <param name="bitIndex">The starting index in the BitArray</param>
/// <param name="version">The QR code version (1-40, or -1 to -4 for Micro QR)</param>
/// <returns>The next index in the BitArray after the last bit written</returns>
public static int WriteTo(string text, int offset, int length, BitArray bitArray, int bitIndex, int version)
{
// write mode indicator
index = DecToBin((int)EncodingMode.Alphanumeric, 4, bitArray, index);
bitIndex = DecToBin((int)EncodingMode.Alphanumeric, 4, bitArray, bitIndex);

// write count indicator
int countIndicatorLength = GetCountIndicatorLength(version, EncodingMode.Alphanumeric);
index = DecToBin(Text.Length, countIndicatorLength, bitArray, index);
bitIndex = DecToBin(length, countIndicatorLength, bitArray, bitIndex);

// write data - encode alphanumeric text
index = AlphanumericEncoder.WriteToBitArray(Text, bitArray, index);
bitIndex = AlphanumericEncoder.WriteToBitArray(text, offset, length, bitArray, bitIndex);

return index;
return bitIndex;
}
}
}
21 changes: 10 additions & 11 deletions QRCoder/QRCodeGenerator/AlphanumericEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ private static Dictionary<char, int> CreateAlphanumEncDict(char[] alphanumEncTab
public static bool CanEncodeNonDigit(char c) => IsInRange(c, 'A', 'Z') || Array.IndexOf(_alphanumEncTable, c) >= 0;

/// <summary>
/// Calculates the bit length required to encode the given alphanumeric text.
/// Calculates the bit length required to encode alphanumeric text of a given length.
/// </summary>
/// <param name="plainText">The alphanumeric text to be encoded.</param>
/// <param name="textLength">The length of the alphanumeric text to be encoded.</param>
/// <returns>The number of bits required to encode the text.</returns>
public static int GetBitLength(string plainText)
public static int GetBitLength(int textLength)
{
return (plainText.Length / 2) * 11 + (plainText.Length & 1) * 6;
return (textLength / 2) * 11 + (textLength & 1) * 6;
}

/// <summary>
Expand All @@ -59,25 +59,24 @@ public static int GetBitLength(string plainText)
/// <returns>A BitArray representing the binary data of the encoded alphanumeric text.</returns>
public static BitArray GetBitArray(string plainText)
{
var codeText = new BitArray(GetBitLength(plainText));
WriteToBitArray(plainText, codeText, 0);
var codeText = new BitArray(GetBitLength(plainText.Length));
WriteToBitArray(plainText, 0, plainText.Length, codeText, 0);
return codeText;
}

/// <summary>
/// Writes alphanumeric plain text directly into an existing BitArray at the specified index.
/// Writes a portion of alphanumeric plain text directly into an existing BitArray at the specified index.
/// Alphanumeric encoding packs characters into 11-bit groups for each pair of characters,
/// and 6 bits for a single remaining character if the total count is odd.
/// </summary>
/// <param name="plainText">The alphanumeric text to be encoded, which should only contain characters valid in QR alphanumeric mode.</param>
/// <param name="index">The starting index in the text to encode from.</param>
/// <param name="count">The number of characters to encode.</param>
/// <param name="codeText">The target BitArray to write to.</param>
/// <param name="codeIndex">The starting index in the BitArray where writing should begin.</param>
/// <returns>The next index in the BitArray after the last bit written.</returns>
public static int WriteToBitArray(string plainText, BitArray codeText, int codeIndex)
public static int WriteToBitArray(string plainText, int index, int count, BitArray codeText, int codeIndex)
{
var index = 0;
var count = plainText.Length;

// Process each pair of characters.
while (count >= 2)
{
Expand Down
81 changes: 58 additions & 23 deletions QRCoder/QRCodeGenerator/NumericDataSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,22 @@ public NumericDataSegment(string numericText)
/// <param name="version">The QR code version (1-40, or -1 to -4 for Micro QR)</param>
/// <returns>The total number of bits required for this segment</returns>
public override int GetBitLength(int version)
{
return GetBitLength(Text.Length, version);
}

/// <summary>
/// Calculates the total bit length for encoding numeric text of a given length for a specific QR code version.
/// Includes mode indicator, count indicator, and data bits.
/// </summary>
/// <param name="textLength">The length of the numeric text</param>
/// <param name="version">The QR code version (1-40, or -1 to -4 for Micro QR)</param>
/// <returns>The total number of bits required</returns>
public static int GetBitLength(int textLength, int version)
{
int modeIndicatorLength = 4;
int countIndicatorLength = GetCountIndicatorLength(version, EncodingMode.Numeric);
int dataLength = Text.Length / 3 * 10 + (Text.Length % 3 == 1 ? 4 : Text.Length % 3 == 2 ? 7 : 0);
int dataLength = textLength / 3 * 10 + (textLength % 3 == 1 ? 4 : textLength % 3 == 2 ? 7 : 0);
int length = modeIndicatorLength + countIndicatorLength + dataLength;

return length;
Expand All @@ -45,19 +57,33 @@ public override int GetBitLength(int version)
/// <returns>The next index in the BitArray after the last bit written</returns>
public override int WriteTo(BitArray bitArray, int startIndex, int version)
{
var index = startIndex;
return WriteTo(Text, 0, Text.Length, bitArray, startIndex, version);
}

/// <summary>
/// Writes a portion of numeric text to a BitArray at the specified index.
/// Includes mode indicator, count indicator, and data bits.
/// </summary>
/// <param name="text">The full numeric text</param>
/// <param name="startIndex">The starting index in the text to encode from</param>
/// <param name="length">The number of characters to encode</param>
/// <param name="bitArray">The target BitArray to write to</param>
/// <param name="bitIndex">The starting index in the BitArray</param>
/// <param name="version">The QR code version (1-40, or -1 to -4 for Micro QR)</param>
/// <returns>The next index in the BitArray after the last bit written</returns>
public static int WriteTo(string text, int startIndex, int length, BitArray bitArray, int bitIndex, int version)
{
var index = bitIndex;

// write mode indicator
index = DecToBin((int)EncodingMode.Numeric, 4, bitArray, index);

// write count indicator
int countIndicatorLength = GetCountIndicatorLength(version, EncodingMode.Numeric);
index = DecToBin(Text.Length, countIndicatorLength, bitArray, index);
index = DecToBin(length, countIndicatorLength, bitArray, index);

// write data - encode numeric text
var data = PlainTextToBinaryNumeric(Text);
data.CopyTo(bitArray, 0, index, data.Length);
index += data.Length;
index = PlainTextToBinaryNumeric(text, startIndex, length, bitArray, index);

return index;
}
Expand All @@ -74,10 +100,26 @@ private static BitArray PlainTextToBinaryNumeric(string plainText)
// Calculate the length of the BitArray needed to encode the text.
// Groups of three digits are encoded in 10 bits, remaining groups of two or one digits take 7 or 4 bits respectively.
var bitArray = new BitArray(plainText.Length / 3 * 10 + (plainText.Length % 3 == 1 ? 4 : plainText.Length % 3 == 2 ? 7 : 0));
var index = 0;
PlainTextToBinaryNumeric(plainText, 0, plainText.Length, bitArray, 0);
return bitArray;
}

/// <summary>
/// Converts a portion of numeric plain text into a binary format specifically optimized for QR codes, writing directly to an existing BitArray.
/// Numeric compression groups up to 3 digits into 10 bits, less for remaining digits if they do not complete a group of three.
/// </summary>
/// <param name="plainText">The numeric text to be encoded, which should only contain digit characters.</param>
/// <param name="offset">The starting index in the text to encode from.</param>
/// <param name="length">The number of characters to encode.</param>
/// <param name="bitArray">The target BitArray to write to.</param>
/// <param name="bitIndex">The starting index in the BitArray where bits will be written.</param>
/// <returns>The next index in the BitArray after the last bit written.</returns>
private static int PlainTextToBinaryNumeric(string plainText, int offset, int length, BitArray bitArray, int bitIndex)
{
var endIndex = offset + length;

// Process each group of three digits.
for (int i = 0; i < plainText.Length - 2; i += 3)
for (int i = offset; i < endIndex - 2; i += 3)
{
// Parse the next three characters as a decimal integer.
#if HAS_SPAN
Expand All @@ -86,29 +128,22 @@ private static BitArray PlainTextToBinaryNumeric(string plainText)
var dec = int.Parse(plainText.Substring(i, 3), NumberStyles.None, CultureInfo.InvariantCulture);
#endif
// Convert the decimal to binary and store it in the BitArray.
index = DecToBin(dec, 10, bitArray, index);
bitIndex = DecToBin(dec, 10, bitArray, bitIndex);
offset += 3;
length -= 3;
}

// Handle any remaining digits if the total number is not a multiple of three.
if (plainText.Length % 3 == 2) // Two remaining digits are encoded in 7 bits.
if (length > 0) // Two remaining digits are encoded in 7 bits; one remaining digit is encoded in 4 bits.
{
#if HAS_SPAN
var dec = int.Parse(plainText.AsSpan(plainText.Length / 3 * 3, 2), NumberStyles.None, CultureInfo.InvariantCulture);
var dec = int.Parse(plainText.AsSpan(offset, length), NumberStyles.None, CultureInfo.InvariantCulture);
#else
var dec = int.Parse(plainText.Substring(plainText.Length / 3 * 3, 2), NumberStyles.None, CultureInfo.InvariantCulture);
var dec = int.Parse(plainText.Substring(offset, length), NumberStyles.None, CultureInfo.InvariantCulture);
#endif
index = DecToBin(dec, 7, bitArray, index);
}
else if (plainText.Length % 3 == 1) // One remaining digit is encoded in 4 bits.
{
#if HAS_SPAN
var dec = int.Parse(plainText.AsSpan(plainText.Length / 3 * 3, 1), NumberStyles.None, CultureInfo.InvariantCulture);
#else
var dec = int.Parse(plainText.Substring(plainText.Length / 3 * 3, 1), NumberStyles.None, CultureInfo.InvariantCulture);
#endif
index = DecToBin(dec, 4, bitArray, index);
bitIndex = DecToBin(dec, length == 2 ? 7 : 4, bitArray, bitIndex);
}

return bitArray;
return bitIndex;
}
}