using System; using Gma.QrCodeNet.Encoding.DataEncodation; namespace Gma.QrCodeNet.Encoding.Versions; internal static class VersionControl { private const int NumBitsModeIndicator = 4; private const string DefaultEncoding = QRCodeConstantVariable.DefaultEncoding; private static readonly int[] VERSION_GROUP = new int[] { 9, 26, 40 }; /// /// Determine which version to use /// /// Number of bits for encoded content /// Encoding name for EightBitByte /// VersionDetail and ECI internal static VersionControlStruct InitialSetup(int dataBitsLength, ErrorCorrectionLevel level, string encodingName) { int totalDataBits = dataBitsLength; bool containECI = false; BitList eciHeader = new(); if (encodingName is not DefaultEncoding and not QRCodeConstantVariable.UTF8Encoding) { ECISet eciSet = new(ECISet.AppendOption.NameToValue); int eciValue = eciSet.GetECIValueByName(encodingName); totalDataBits += ECISet.NumOfECIHeaderBits(eciValue); eciHeader = eciSet.GetECIHeader(encodingName); containECI = true; } // Determine which version group it belong to int searchGroup = DynamicSearchIndicator(totalDataBits, level); int[] charCountIndicator = CharCountIndicatorTable.GetCharCountIndicatorSet(); totalDataBits += (NumBitsModeIndicator + charCountIndicator[searchGroup]); int lowerSearchBoundary = searchGroup == 0 ? 1 : (VERSION_GROUP[searchGroup - 1] + 1); int higherSearchBoundary = VERSION_GROUP[searchGroup]; // Binary search to find proper version int versionNum = BinarySearch(totalDataBits, level, lowerSearchBoundary, higherSearchBoundary); VersionControlStruct vcStruct = FillVCStruct(versionNum, level); vcStruct.IsContainECI = containECI; vcStruct.ECIHeader = eciHeader; return vcStruct; } private static VersionControlStruct FillVCStruct(int versionNum, ErrorCorrectionLevel level) { if (versionNum is < 1 or > 40) { throw new InvalidOperationException($"Unexpected version number: {versionNum}"); } VersionControlStruct vcStruct = new(); int version = versionNum; QRCodeVersion versionData = VersionTable.GetVersionByNum(versionNum); int numTotalBytes = versionData.TotalCodewords; ErrorCorrectionBlocks ecBlocks = versionData.GetECBlocksByLevel(level); int numDataBytes = numTotalBytes - ecBlocks.NumErrorCorrectionCodewards; int numECBlocks = ecBlocks.NumBlocks; VersionDetail vcDetail = new(version, numTotalBytes, numDataBytes, numECBlocks); vcStruct.VersionDetail = vcDetail; return vcStruct; } /// /// Decide which version group it belong to /// /// Number of bits for bitlist where it contain DataBits encode from input content and ECI header /// Error correction level /// Version group index for VERSION_GROUP private static int DynamicSearchIndicator(int numBits, ErrorCorrectionLevel level) { int[] charCountIndicator = CharCountIndicatorTable.GetCharCountIndicatorSet(); int loopLength = VERSION_GROUP.Length; for (int i = 0; i < loopLength; i++) { int totalBits = numBits + NumBitsModeIndicator + charCountIndicator[i]; QRCodeVersion version = VersionTable.GetVersionByNum(VERSION_GROUP[i]); int numECCodewords = version.GetECBlocksByLevel(level).NumErrorCorrectionCodewards; int dataCodewords = version.TotalCodewords - numECCodewords; if (totalBits <= dataCodewords * 8) { return i; } } throw new InputOutOfBoundaryException($"QRCode do not have enough space for {(numBits + NumBitsModeIndicator + charCountIndicator[2])} bits"); } /// /// Use number of data bits(header + eci header + data bits from EncoderBase) to search for proper version to use /// between min and max boundary. /// Boundary define by DynamicSearchIndicator method. /// private static int BinarySearch(int numDataBits, ErrorCorrectionLevel level, int lowerVersionNum, int higherVersionNum) { int middleVersionNumber; while (lowerVersionNum <= higherVersionNum) { middleVersionNumber = (lowerVersionNum + higherVersionNum) / 2; QRCodeVersion version = VersionTable.GetVersionByNum(middleVersionNumber); int numECCodewords = version.GetECBlocksByLevel(level).NumErrorCorrectionCodewards; int dataCodewords = version.TotalCodewords - numECCodewords; if (dataCodewords << 3 == numDataBits) { return middleVersionNumber; } if (dataCodewords << 3 > numDataBits) { higherVersionNum = middleVersionNumber - 1; } else { lowerVersionNum = middleVersionNumber + 1; } } return lowerVersionNum; } }