145 lines
4.6 KiB
C#
145 lines
4.6 KiB
C#
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 };
|
|
|
|
/// <summary>
|
|
/// Determine which version to use
|
|
/// </summary>
|
|
/// <param name="dataBitsLength">Number of bits for encoded content</param>
|
|
/// <param name="encodingName">Encoding name for EightBitByte</param>
|
|
/// <returns>VersionDetail and ECI</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decide which version group it belong to
|
|
/// </summary>
|
|
/// <param name="numBits">Number of bits for bitlist where it contain DataBits encode from input content and ECI header</param>
|
|
/// <param name="level">Error correction level</param>
|
|
/// <returns>Version group index for VERSION_GROUP</returns>
|
|
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");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|