namespace Gma.QrCodeNet.Encoding.DataEncodation; public sealed class ECISet { /// /// ISO/IEC 18004:2006 Chapter 6.4.2 Mode indicator = 0111 Page 23 /// private const int ECIMode = 7; private const int ECIIndicatorNumBits = 4; private Dictionary? _nameToValue; private Dictionary? _valueToName; /// /// Initialize ECI Set. /// /// AppendOption is enum under ECISet /// Use NameToValue during Encode. ValueToName during Decode internal ECISet(AppendOption option) { Initialize(option); } public enum AppendOption { NameToValue, ValueToName, Both } /// /// Length indicator for number of ECI codewords /// /// ISO/IEC 18004:2006 Chapter 6.4.2 Page 24. /// 1 codeword length = 0. Any additional codeword add 1 to front. Eg: 3 = 110 /// Bits required for each one is: /// one = 1, two = 2, three = 3 private enum ECICodewordsLength { One = 0, Two = 2, Three = 6 } /// ISO/IEC 18004:2006E ECI Designator Page 24 /// Range: 0 ~ 999999 /// Number of Codewords(Byte) for ECI Assignment Value private static int NumOfCodewords(int eCIValue) { if (eCIValue is >= 0 and <= 127) { return 1; } else if (eCIValue is > 127 and <= 16383) { return 2; } else if (eCIValue is > 16383 and <= 999999) { return 3; } else { throw new ArgumentOutOfRangeException($"{nameof(eCIValue)} should be in range: 0 to 999999."); } } /// ISO/IEC 18004:2006E ECI Designator Page 24 /// Range: 0 ~ 999999 /// Number of bits for ECI Assignment Value private static int NumOfAssignmentBits(int eCIValue) => NumOfCodewords(eCIValue) * 8; private void AppendECI(string name, int value, AppendOption option) { switch (option) { case AppendOption.NameToValue: _nameToValue?.Add(name, value); break; case AppendOption.ValueToName: _valueToName?.Add(value, name); break; case AppendOption.Both: _nameToValue?.Add(name, value); _valueToName?.Add(value, name); break; default: throw new InvalidOperationException($"There is no such {nameof(AppendOption)}."); } } private void Initialize(AppendOption option) { switch (option) { case AppendOption.NameToValue: _nameToValue = new Dictionary(); break; case AppendOption.ValueToName: _valueToName = new Dictionary(); break; case AppendOption.Both: _nameToValue = new Dictionary(); _valueToName = new Dictionary(); break; default: throw new InvalidOperationException($"There is no such {nameof(AppendOption)}."); } // ECI table. Source 01 URL: http://strokescribe.com/en/ECI.html // ECI table. Source 02 URL: http://lab.must.or.kr/Extended-Channel-Interpretations-ECI-Encoding.ashx // ToDo. Fill up remaining missing table. AppendECI("iso-8859-1", 1, option); AppendECI("IBM437", 2, option); // AppendECI("iso-8859-1", 3, option); //ECI value 1 is default encoding. AppendECI("iso-8859-2", 4, option); AppendECI("iso-8859-3", 5, option); AppendECI("iso-8859-4", 6, option); AppendECI("iso-8859-5", 7, option); AppendECI("iso-8859-6", 8, option); AppendECI("iso-8859-7", 9, option); AppendECI("iso-8859-8", 10, option); AppendECI("iso-8859-9", 11, option); AppendECI("windows-874", 13, option); AppendECI("iso-8859-13", 15, option); AppendECI("iso-8859-15", 17, option); AppendECI("shift_jis", 20, option); AppendECI("utf-8", 26, option); } /// ISO/IEC 18004:2006E ECI Designator Page 24 /// Range: 0 ~ 999999 /// Number of bits for ECI Header internal static int NumOfECIHeaderBits(int eCIValue) => NumOfAssignmentBits(eCIValue) + 4; internal int GetECIValueByName(string encodingName) { if (_nameToValue is null) { Initialize(AppendOption.NameToValue); } if (_nameToValue!.TryGetValue(encodingName, out int eCIValue)) { return eCIValue; } else { throw new ArgumentOutOfRangeException($"ECI does not contain encoding: {encodingName}."); } } internal string GetECINameByValue(int eCIValue) { if (_valueToName is null) { Initialize(AppendOption.ValueToName); } if (_valueToName!.TryGetValue(eCIValue, out var eCIName)) { return eCIName; } else { throw new ArgumentOutOfRangeException($"ECI does not contain value: {eCIValue}."); } } /// ECI table in Dictionary collection public Dictionary? GetECITable() { if (_nameToValue is null) { Initialize(AppendOption.NameToValue); } return _nameToValue; } public bool ContainsECIName(string encodingName) { if (_nameToValue is null) { Initialize(AppendOption.NameToValue); } return _nameToValue!.ContainsKey(encodingName); } public bool ContainsECIValue(int eciValue) { if (_valueToName is null) { Initialize(AppendOption.ValueToName); } return _valueToName!.ContainsKey(eciValue); } /// ISO/IEC 18004:2006 Chapter 6.4.2 Page 24. internal BitList GetECIHeader(string encodingName) { int eciValue = GetECIValueByName(encodingName); BitList dataBits = new() { { ECIMode, ECIIndicatorNumBits } }; int eciAssignmentByte = NumOfCodewords(eciValue); // Number of bits = Num codewords indicator + codeword value = Number of codewords * 8 // Chapter 6.4.2.1 ECI Designator ISOIEC 18004:2006 Page 24 int eciAssignmentBits; switch (eciAssignmentByte) { case 1: // Indicator = 0. Page 24. Chapter 6.4.2.1 dataBits.Add((int)ECICodewordsLength.One, 1); eciAssignmentBits = (eciAssignmentByte * 8) - 1; break; case 2: // Indicator = 10. Page 24. Chapter 6.4.2.1 dataBits.Add((int)ECICodewordsLength.Two, 2); eciAssignmentBits = (eciAssignmentByte * 8) - 2; break; case 3: // Indicator = 110. Page 24. Chapter 6.4.2.1 dataBits.Add((int)ECICodewordsLength.Three, 3); eciAssignmentBits = (eciAssignmentByte * 8) - 3; break; default: throw new InvalidOperationException("Assignment Codewords should be either 1, 2 or 3."); } dataBits.Add(eciValue, eciAssignmentBits); return dataBits; } }