Files
Toolkit2/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControl.cs
T
2024-04-13 11:41:33 +01:00

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