diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs
index eec9c884..09b681aa 100644
--- a/QRCoder/QRCodeGenerator.cs
+++ b/QRCoder/QRCodeGenerator.cs
@@ -1,8 +1,10 @@
#if HAS_SPAN
using System.Buffers;
#endif
+using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Text;
namespace QRCoder;
@@ -109,14 +111,39 @@ public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload, ECCLev
public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1)
{
eccLevel = ValidateECCLevel(eccLevel);
+ // Create data segment from plain text
+ var segment = CreateDataSegment(plainText, forceUtf8, utf8BOM, eciMode);
+ // Determine the appropriate version based on segment bit length
+ int version = DetermineVersion(segment, eccLevel, requestedVersion);
+ // Build the complete bit array for the determined version
+ var completeBitArray = BuildBitArrayFromSegment(segment, version);
+ return GenerateQrCode(completeBitArray, eccLevel, version);
+ }
+
+ ///
+ /// Creates a data segment from plain text, encoding it appropriately.
+ ///
+ private static DataSegment CreateDataSegment(string plainText, bool forceUtf8, bool utf8BOM, EciMode eciMode)
+ {
var encoding = GetEncodingFromPlaintext(plainText, forceUtf8);
var codedText = PlainTextToBinary(plainText, encoding, eciMode, utf8BOM, forceUtf8);
var dataInputLength = GetDataLength(encoding, plainText, codedText, forceUtf8);
- int version = requestedVersion;
- int minVersion = CapacityTables.CalculateMinimumVersion(dataInputLength + (eciMode != EciMode.Default ? 2 : 0), encoding, eccLevel);
- if (version == -1)
+ return new DataSegment(encoding, dataInputLength, codedText, eciMode);
+ }
+
+ ///
+ /// Determines the appropriate QR code version based on the data segment and error correction level.
+ /// Validates that the data fits within the requested version, or finds the minimum version if not specified.
+ ///
+ private static int DetermineVersion(DataSegment segment, ECCLevel eccLevel, int version)
+ {
+ if (!CapacityTables.TryCalculateMinimumVersion(segment, eccLevel, out var minVersion))
{
- version = minVersion;
+ return Throw(eccLevel, segment.EncodingMode, version == -1 ? 40 : version);
+ }
+ else if (version == -1)
+ {
+ return minVersion;
}
else
{
@@ -124,19 +151,30 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
if (minVersion > version)
{
// Use a throw-helper to avoid allocating a closure
- Throw(eccLevel, encoding, version);
-
- static void Throw(ECCLevel eccLevel, EncodingMode encoding, int version)
- {
- var maxSizeByte = CapacityTables.GetVersionInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding];
- throw new Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte);
- }
+ return Throw(eccLevel, segment.EncodingMode, version);
}
+ return version;
}
- var modeIndicatorLength = eciMode != EciMode.Default ? 16 : 4;
- var countIndicatorLength = GetCountIndicatorLength(version, encoding);
- var completeBitArrayLength = modeIndicatorLength + countIndicatorLength + codedText.Length;
+ static int Throw(ECCLevel eccLevel, EncodingMode encoding, int version)
+ {
+ var maxSizeByte = CapacityTables.GetVersionInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding];
+ throw new Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte);
+ }
+ }
+
+ ///
+ /// Builds a complete BitArray from a data segment for a specific QR code version.
+ ///
+ private static BitArray BuildBitArrayFromSegment(DataSegment segment, int version)
+ {
+ // todo in subsequent PR: eliminate these local variables and directly access the struct
+ var eciMode = segment.EciMode;
+ var encoding = segment.EncodingMode;
+ int dataInputLength = segment.CharacterCount;
+ var codedText = segment.Data;
+ int completeBitArrayLength = segment.GetBitLength(version);
+ int countIndicatorLength = GetCountIndicatorLength(version, segment.EncodingMode);
var completeBitArray = new BitArray(completeBitArrayLength);
@@ -156,7 +194,7 @@ static void Throw(ECCLevel eccLevel, EncodingMode encoding, int version)
completeBitArray[completeBitArrayIndex++] = codedText[i];
}
- return GenerateQrCode(completeBitArray, eccLevel, version);
+ return completeBitArray;
}
///
@@ -366,6 +404,7 @@ List CalculateECCBlocks()
void AddCodeWordBlocks(int blockNum, int blocksInGroup, int codewordsInGroup, int offset2, int count, Polynom generatorPolynom)
{
+ _ = blockNum;
var groupLength = codewordsInGroup * 8;
groupLength = groupLength > count ? count : groupLength;
for (var i = 0; i < blocksInGroup; i++)
diff --git a/QRCoder/QRCodeGenerator/CapacityTables.cs b/QRCoder/QRCodeGenerator/CapacityTables.cs
index f4c6b94c..9146976e 100644
--- a/QRCoder/QRCodeGenerator/CapacityTables.cs
+++ b/QRCoder/QRCodeGenerator/CapacityTables.cs
@@ -116,6 +116,55 @@ static void Throw(EncodingMode encMode, ECCLevel eccLevel)
}
}
+ ///
+ /// Attempts to determine the minimum QR code version required to encode a data segment with a specific error correction level.
+ /// This method accounts for the version-dependent size of mode and count indicators when calculating the total bit length.
+ ///
+ /// The data segment to be encoded (includes encoding mode, character count, and data bits).
+ /// The error correction level (e.g., Low, Medium, Quartile, High).
+ /// When this method returns, contains the minimum version number (1-40) that can accommodate the data segment if a suitable version was found; otherwise, 0.
+ /// if a suitable QR code version was found; otherwise, .
+ public static bool TryCalculateMinimumVersion(DataSegment segment, ECCLevel eccLevel, out int version)
+ {
+ // Versions 1-9: Count indicator length is constant within this range
+ var segmentBitLength = segment.GetBitLength(1);
+ for (version = 1; version <= 9; version++)
+ {
+ var eccInfo = GetEccInfo(version, eccLevel);
+ // Check if this version has enough capacity for the segment's total bits
+ if (eccInfo.TotalDataBits >= segmentBitLength)
+ {
+ return true;
+ }
+ }
+
+ // Versions 10-26: Count indicator length is constant within this range
+ segmentBitLength = segment.GetBitLength(10);
+ for (version = 10; version <= 26; version++)
+ {
+ var eccInfo = GetEccInfo(version, eccLevel);
+ // Check if this version has enough capacity for the segment's total bits
+ if (eccInfo.TotalDataBits >= segmentBitLength)
+ {
+ return true;
+ }
+ }
+
+ // Versions 27-40: Count indicator length is constant within this range
+ segmentBitLength = segment.GetBitLength(27);
+ for (version = 27; version <= 40; version++)
+ {
+ var eccInfo = GetEccInfo(version, eccLevel);
+ // Check if this version has enough capacity for the segment's total bits
+ if (eccInfo.TotalDataBits >= segmentBitLength)
+ {
+ return true;
+ }
+ }
+
+ version = 0;
+ return false;
+ }
///
/// Determines the minimum Micro QR code version required to encode a given amount of data with a specific encoding mode and error correction level.
diff --git a/QRCoder/QRCodeGenerator/DataSegment.cs b/QRCoder/QRCodeGenerator/DataSegment.cs
new file mode 100644
index 00000000..0769d26b
--- /dev/null
+++ b/QRCoder/QRCodeGenerator/DataSegment.cs
@@ -0,0 +1,55 @@
+namespace QRCoder;
+
+public partial class QRCodeGenerator
+{
+ ///
+ /// Represents a data segment for QR code encoding, containing the encoding mode, character count, and encoded data.
+ ///
+ private readonly struct DataSegment
+ {
+ ///
+ /// The encoding mode for this segment (Numeric, Alphanumeric, Byte, etc.)
+ ///
+ public readonly EncodingMode EncodingMode;
+
+ ///
+ /// The character count (or byte count for byte mode)
+ ///
+ public readonly int CharacterCount;
+
+ ///
+ /// The encoded data as a BitArray
+ ///
+ public readonly BitArray Data;
+
+ ///
+ /// Whether this segment includes an ECI mode indicator
+ ///
+ public bool HasEciMode => EciMode != EciMode.Default;
+
+ ///
+ /// The ECI mode value (only valid if HasEciMode is true)
+ ///
+ public readonly EciMode EciMode;
+
+ public DataSegment(EncodingMode encodingMode, int characterCount, BitArray data, EciMode eciMode)
+ {
+ EncodingMode = encodingMode;
+ CharacterCount = characterCount;
+ Data = data;
+ EciMode = eciMode;
+ }
+
+ ///
+ /// Calculates the total bit length for this segment when encoded for a specific QR code version.
+ ///
+ /// The QR code version (1-40, or -1 to -4 for Micro QR)
+ /// The total number of bits required for this segment including mode indicator, count indicator, and data
+ public int GetBitLength(int version)
+ {
+ int modeIndicatorLength = HasEciMode ? 16 : 4;
+ int countIndicatorLength = GetCountIndicatorLength(version, EncodingMode);
+ return modeIndicatorLength + countIndicatorLength + Data.Length;
+ }
+ }
+}