diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index beb3d576..e64b9159 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -125,6 +125,10 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo /// private static DataSegment CreateDataSegment(string plainText, bool forceUtf8, bool utf8BOM, EciMode eciMode) { + // Fast path: Use optimized Latin1 segment if conditions allow + if (!forceUtf8 && !utf8BOM && eciMode == EciMode.Default && OptimizedLatin1DataSegment.CanEncode(plainText)) + return new OptimizedLatin1DataSegment(plainText); + var encoding = GetEncodingFromPlaintext(plainText, forceUtf8); // Use specialized segment classes based on encoding mode diff --git a/QRCoder/QRCodeGenerator/OptimizedDataSegment.cs b/QRCoder/QRCodeGenerator/OptimizedDataSegment.cs new file mode 100644 index 00000000..a7fee75d --- /dev/null +++ b/QRCoder/QRCodeGenerator/OptimizedDataSegment.cs @@ -0,0 +1,337 @@ +#pragma warning disable IDE0018 // Inline variable declaration -- false positive + +namespace QRCoder; + +public partial class QRCodeGenerator +{ + /// + /// Data segment that optimizes encoding by analyzing character patterns and switching between + /// encoding modes (Numeric, Alphanumeric, Byte) to minimize the total bit length. + /// This implements the QR Code optimization algorithm from ISO/IEC 18004:2015 Annex J.2. + /// It does not support Kanji mode. + /// + private sealed class OptimizedLatin1DataSegment : DataSegment + { + /// + /// Checks if the given string can be encoded using optimized Latin-1 encoding. + /// Returns true if all characters are within the ISO-8859-1 range (0x00-0xFF). + /// + /// The text to check + /// True if the text can be encoded as ISO-8859-1, false otherwise + public static bool CanEncode(string plainText) => IsValidISO(plainText); + + /// + /// Gets the encoding mode (used only for DataTooLongException) + /// + public override EncodingMode EncodingMode => EncodingMode.Byte; + + /// + /// Initializes a new instance of the OptimizedDataSegment class. + /// + /// The text to encode with optimized mode switching + public OptimizedLatin1DataSegment(string plainText) + : base(plainText) + { + } + + /// + /// 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 + public override int GetBitLength(int version) + { + if (string.IsNullOrEmpty(Text)) + return 0; + + var totalBits = 0; + var mode = SelectInitialMode(Text, 0, version); + var startPos = 0; + + do + { + // Find the extent of the current mode + EncodingMode nextMode; + var segmentEnd = mode switch + { + EncodingMode.Byte => ProcessByteMode(Text, startPos, version, out nextMode), + EncodingMode.Alphanumeric => ProcessAlphanumericMode(Text, startPos, version, out nextMode), + EncodingMode.Numeric => ProcessNumericMode(Text, startPos, version, out nextMode), + _ => throw new InvalidOperationException("Unsupported encoding mode") + }; + + var segmentLength = segmentEnd - startPos; + totalBits += mode switch + { + EncodingMode.Numeric => NumericDataSegment.GetBitLength(segmentLength, version), + EncodingMode.Alphanumeric => AlphanumericDataSegment.GetBitLength(segmentLength, version), + EncodingMode.Byte => GetByteBitLength(segmentLength, version), + _ => throw new InvalidOperationException("Unsupported encoding mode") + }; + + // Move to the next segment + startPos = segmentEnd; + mode = nextMode; + } + while (startPos < Text.Length); + + return totalBits; + } + + /// + /// Calculates the bit length for a byte mode segment. + /// + private static int GetByteBitLength(int textLength, int version) + { + int modeIndicatorLength = 4; + int countIndicatorLength = GetCountIndicatorLength(version, EncodingMode.Byte); + int dataLength = textLength * 8; // ISO-8859-1 encoding + return modeIndicatorLength + countIndicatorLength + dataLength; + } + + /// + /// Writes this data segment to an existing BitArray at the specified index. + /// + /// The target BitArray to write to + /// The starting index in the BitArray + /// The QR code version (1-40, or -1 to -4 for Micro QR) + /// The next index in the BitArray after the last bit written + public override int WriteTo(BitArray bitArray, int startIndex, int version) + { + if (string.IsNullOrEmpty(Text)) + return startIndex; + + var bitIndex = startIndex; + var mode = SelectInitialMode(Text, 0, version); + var startPos = 0; + + do + { + // Find the extent of the current mode + EncodingMode nextMode; + var segmentEnd = mode switch + { + EncodingMode.Byte => ProcessByteMode(Text, startPos, version, out nextMode), + EncodingMode.Alphanumeric => ProcessAlphanumericMode(Text, startPos, version, out nextMode), + EncodingMode.Numeric => ProcessNumericMode(Text, startPos, version, out nextMode), + _ => throw new InvalidOperationException("Unsupported encoding mode") + }; + + var segmentLength = segmentEnd - startPos; + bitIndex = mode switch + { + EncodingMode.Numeric => NumericDataSegment.WriteTo(Text, startPos, segmentLength, bitArray, bitIndex, version), + EncodingMode.Alphanumeric => AlphanumericDataSegment.WriteTo(Text, startPos, segmentLength, bitArray, bitIndex, version), + EncodingMode.Byte => WriteByteSegment(Text, startPos, segmentLength, bitArray, bitIndex, version), + _ => throw new InvalidOperationException("Unsupported encoding mode") + }; + + // Move to the next segment + startPos = segmentEnd; + mode = nextMode; + } + while (startPos < Text.Length); + + return bitIndex; + } + + /// + /// Writes a byte mode segment to the BitArray. + /// + private static int WriteByteSegment(string text, int offset, int length, BitArray bitArray, int bitIndex, int version) + { + // write mode indicator + bitIndex = DecToBin((int)EncodingMode.Byte, 4, bitArray, bitIndex); + + // write count indicator + int countIndicatorLength = GetCountIndicatorLength(version, EncodingMode.Byte); + bitIndex = DecToBin(length, countIndicatorLength, bitArray, bitIndex); + + // write data - encode as ISO-8859-1 + for (int i = 0; i < length; i++) + { + bitIndex = DecToBin(text[offset + i], 8, bitArray, bitIndex); + } + + return bitIndex; + } + + // Selects the initial encoding mode based on the first character(s) of the input. + // Implements rules from ISO/IEC 18004:2015 Annex J.2 section a. + private static EncodingMode SelectInitialMode(string text, int startPos, int version) + { + var c = text[startPos]; + + // Rule a.1: If initial input data is in the exclusive subset of the Byte character set, select Byte mode + if (!IsAlphanumeric(c)) + return EncodingMode.Byte; + + // Rule a.4: If initial data is numeric, AND if there are less than [4,4,5] characters followed by data from the + // exclusive subset of the Byte character set, THEN select Byte mode + if (IsNumeric(c)) + { + var numericCount = CountConsecutive(text, startPos, IsNumeric); + var threshold = GetBreakpoint(version, 4, 4, 5); + if (numericCount < threshold) + { + var nextPos = startPos + numericCount; + if (nextPos < text.Length && !IsAlphanumericNonDigit(text[nextPos])) + return EncodingMode.Byte; + } + // ELSE IF there are less than [7-9] characters followed by data from the exclusive subset of the Alphanumeric character set + // THEN select Alphanumeric mode ELSE select Numeric mode + threshold = GetBreakpoint(version, 7, 8, 9); + if (numericCount < threshold) + { + var nextPos = startPos + numericCount; + if (nextPos < text.Length && IsAlphanumericNonDigit(text[nextPos])) + return EncodingMode.Alphanumeric; + } + return EncodingMode.Numeric; + } + + // Rule a.3: If initial input data is in the exclusive subset of the Alphanumeric character set AND if there are + // less than [6-8] characters followed by data from the remainder of the Byte character set, THEN select Byte mode + var alphanumericCount = CountConsecutive(text, startPos, IsAlphanumeric); + var alphaThreshold = GetBreakpoint(version, 6, 7, 8); + if (alphanumericCount < alphaThreshold) + { + var nextPos = startPos + alphanumericCount; + if (nextPos < text.Length && !IsAlphanumeric(text[nextPos])) + return EncodingMode.Byte; + } + return EncodingMode.Alphanumeric; + } + + // Processes text in Byte mode and determines when to switch to another mode. + // Implements rules from ISO/IEC 18004:2015 Annex J.2 section b. + private static int ProcessByteMode(string text, int startPos, int version, out EncodingMode nextMode) + { + var pos = startPos; + + var numericThreshold = GetBreakpoint(version, 6, 8, 9); + var alphaThreshold = GetBreakpoint(version, 11, 15, 16); + while (pos < text.Length) + { + var c = text[pos]; + + // Rule b.3: If a sequence of at least [6,8,9] Numeric characters occurs before more data from the exclusive subset of the Byte character set, switch to Numeric mode + var numericCount = CountConsecutive(text, pos, IsNumeric); + if (numericCount >= numericThreshold) + { + nextMode = EncodingMode.Numeric; + return pos; + } + + // Rule b.2: If a sequence of at least [11,15,16] character from the exclusive subset of the Alphanumeric character set occurs before more data from the exclusive subset of the Byte character set, switch to Alphanumeric mode + var alphanumericCount = CountConsecutive(text, pos, IsAlphanumeric); + if (alphanumericCount >= alphaThreshold) + { + nextMode = EncodingMode.Alphanumeric; + return pos; + } + + // Continue in Byte mode + pos++; + } + + nextMode = EncodingMode.Byte; + return pos; + } + + // Processes text in Alphanumeric mode and determines when to switch to another mode. + // Implements rules from ISO/IEC 18004:2015 Annex J.2 section c. + private static int ProcessAlphanumericMode(string text, int startPos, int version, out EncodingMode nextMode) + { + var pos = startPos; + + var threshold = GetBreakpoint(version, 13, 15, 17); + while (pos < text.Length) + { + var c = text[pos]; + + // Rule c.2: If one or more characters from the exclusive subset of the Byte character set occurs, switch to Byte mode + if (!IsAlphanumeric(c)) + { + nextMode = EncodingMode.Byte; + return pos; + } + + // Rule c.3: If a sequence of at least [13,15,17] Numeric characters occurs before more data from the exclusive subset of the Alphanumeric character set, switch to Numeric mode + var numericCount = CountConsecutive(text, pos, IsNumeric); + if (numericCount >= threshold) + { + nextMode = EncodingMode.Numeric; + return pos; + } + + // Continue in Alphanumeric mode + pos++; + } + + nextMode = EncodingMode.Alphanumeric; + return pos; + } + + // Processes text in Numeric mode and determines when to switch to another mode. + // Implements rules from ISO/IEC 18004:2015 Annex J.2 section d. + private static int ProcessNumericMode(string text, int startPos, int version, out EncodingMode nextMode) + { + var pos = startPos; + + while (pos < text.Length) + { + var c = text[pos]; + + // Rule d.2: If one or more characters from the exclusive subset of the Byte character set occurs, switch to Byte mode + // Rule d.3: If one or more characters from the exclusive subset of the Alphanumeric character set occurs, switch to Alphanumeric mode + + // Replaced by using the more intelligent intial mode logic: + if (!IsNumeric(c)) + { + nextMode = SelectInitialMode(text, pos, version); + return pos; + } + + // Continue in Numeric mode + pos++; + } + + nextMode = EncodingMode.Numeric; + return pos; + } + + // Gets the appropriate breakpoint value based on QR code version. + // ISO/IEC 18004:2015 Annex J.2 specifies different thresholds for different version ranges: + // - Versions 1-9: Use v1_9 value + // - Versions 10-26: Use v10_26 value + // - Versions 27-40: Use v27_40 value + private static int GetBreakpoint(int version, int v1_9, int v10_26, int v27_40) + { + if (version < 10) + return v1_9; + else if (version < 27) + return v10_26; + else + return v27_40; + } + + // Counts consecutive characters matching a predicate starting from a position. + private static int CountConsecutive(string text, int startPos, Func predicate) + { + var count = 0; + for (var i = startPos; i < text.Length && predicate(text[i]); i++) + count++; + return count; + } + + // Checks if a character is numeric (0-9). + private static bool IsNumeric(char c) => IsInRange(c, '0', '9'); + + // Checks if a character is alphanumeric (can be encoded in alphanumeric mode). + private static bool IsAlphanumeric(char c) => IsNumeric(c) || IsAlphanumericNonDigit(c); + + // Checks if a non-digit character can be encoded in alphanumeric mode. + private static bool IsAlphanumericNonDigit(char c) => AlphanumericEncoder.CanEncodeNonDigit(c); + } +} diff --git a/QRCoderTests/QRGeneratorTests.can_encode_byte_mixed_mode.approved.txt b/QRCoderTests/QRGeneratorTests.can_encode_byte_mixed_mode.approved.txt new file mode 100644 index 00000000..112afafe --- /dev/null +++ b/QRCoderTests/QRGeneratorTests.can_encode_byte_mixed_mode.approved.txt @@ -0,0 +1,41 @@ + + + + + ██████████████ ██ ██ ████████ ██ ██████████████ + ██ ██ ██ ████ ██ ████ ██████ ██ ██ + ██ ██████ ██ ████ ██ ██████████ ██████ ██ ██████ ██ + ██ ██████ ██ ██████ ██████ ████ ██ ██ ██████ ██ + ██ ██████ ██ ████ ██████ ████████ ██ ██ ██████ ██ + ██ ██ ██████ ██████ ████████ ██ ██ + ██████████████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██████████████ + ████ ██ ████ ██████████ + ████ ██████ ██ ██ ██ ██ ██ ██████ ██ ████████ + ████ ████ ████ ████ ██ ██ ██ ████ + ██████ ██████ ██████ ████ ██ ██ ██████ + ██ ██ ██ ██ ██████ ██ ██ ██ ████ ██ ██ + ████ ██ ██ ██ ██ ████ ██ ██ ████████████ ████ ██ + ██ ████ ██ ████ ██████ ██ ██████ ████ ██ ██████ + ██ ██ ██████████ ██████ ████████ ████ ██ ██████ ██████ + ██ ██████ ██████ ████ ████ ██████ ██ ████ + ██ ██████ ████ ████ ████████████████████ ██ + ██ ██ ████ ██████ ████ ██ ██████ ██ ████ + ██ ██████████ ████ ██ ████████████ ██ ████ ██████ + ████████ ██████ ██ ██ ████ ██ ████ ██ + ██ ██ ██████ ████████ ██ ████ ████████ ██ ██ + ██ ████ ██ ██ ██ ██ ██ ██████████████ + ██ ██████████ ████ ████████████ ██ ██████████ ██ + ████ ██ ██ ██████ ████ ██ ████ ██ ██ + ██████████████ ██ ██ ██ ██ ████ ██████████████████ + ████ ██████ ████████ ██ ██████ + ██████████████ ██ ██ ██████ ██ ████ ██ ██ + ██ ██ ██████ ██ ██ ██ ██ ██ ██ + ██ ██████ ██ ██ ██ ████ ████ ██████ ██████████ ████ + ██ ██████ ██ ████ ██ ████ ██████ ██ ████████ + ██ ██████ ██ ████ ██████ ██████ ██ ██ ██ ██ + ██ ██ ████ ██ ██ ██████████ ██████ ████ ██ + ██████████████ ██ ██ ██ ████ ████ ██████ ██ ██ ████ + + + + \ No newline at end of file diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 1fd197b5..8ecb3699 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -479,6 +479,26 @@ public void can_encode_byte_long() result.ShouldBe("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111110111011110011001011001111110100001111110001011011111110000000010000010100110100001000000101000011110000111111001011010000010000000010111010111101110010100000011111010001101111101110111010111010000000010111010011000001001010001010111110101001011000010101010111010000000010111010010010100000010111111111110111100111110111110010111010000000010000010101101111011000001111000111010110101111011100010000010000000011111110101010101010101010101010101010101010101010101011111110000000000000000111101010100111001111000101000000100010001010000000000000000000111010110101000110101000101111110110110001001111011111001110000000000010000110011101010010010100001010100101100100010010110001000000000010101111110100010001000110001000001000011010011111111001111110000000000000000010001011101010110001111111001100110111001010011100010000000000100011110110110010110101011001111001011101010111100110011110000000000010100111000110101110011010111100111011110110101000111101100000000011001111110001100000000100111110001111000001001100101000001110000000010111101011110000000010010001110111000001000111000001010100010000000001111011000010001111111101100110011111111011000110111111011010000000010101000011010011011000011101001001000110111110111000011010100000000000111011100011011110110111001101101110010000001100111101010110000000010100000111100001011000111111011011000111110110001001011110100000000001010110011101010101001111011101100111011111001010001001001010000000000000101111001011101111110110000010000001111000111000110010000000000001001111001000111100111001110011111001001100011010110000100110000000010110101011011000101000101000011110001010010101001100100010000000000000000011001000001111000110110100000110010111001111010101101010000000000001100010101111011011001101111101001001010000111001110110000000000001011011011010110100000010111100001100000101000000111011110010000000001101100000010111011001010011100100110110010100100001100100010000000011101111110001001000011001111111101110001111000011011111111110000000011101000101001110011011101011000100111100011100110011000100000000000010001010100100000010111100111010110100110100001001001010100110000000011111000100011010000010110111000111101011110100100011000100010000000011011111110011000110110000001111101100001111010010011111101110000000001010101001000011110111110101010011101100111100001011111001100000000011010111110111000010100100001101000011110001101111111111011010000000000000000100110010011101011101001101111010011011010001000010000000000010100110111001000010100011110011010000101100010111111100001110000000001110100011110001111010101000100101111011111000001011111000000000000001100111100001001010100101111100110101101000011010111010000110000000001011100001110010110101011111011100100111010101000001100000000000000011001110000011111001111001000000110100000001011111010010111010000000011100001100011100011001100100110101110111111101101001110010100000000000100110100100000110000100100110001110101001110101011010101010000000000101100010101110101110111101101111010101010101101100110010010000000000001010001100111001111111100011001100100100001011111111110010000000011110000101001111010010101001011101110010111110101010010001000000000000010010100001011100111111111111100110000100011010110110111010000000011100000111111100101001011010010101001001010101011001100000000000000011110111111100000101010110101000111110110101001100110011111010000000011001100110010000100110010100000001101101011100000011000011000000000000111110110001011010010100000010111011011001000101111011010110000000011101001111111010110001001000111111001111011001101000000000110000000011110010010100111111101010101111100010000111001110011111101010000000000000000100100001011110000101000111100001011000110011000100100000000011111110000011111010110111011010100110101110011111101010100110000000010000010011011000101110101001000110100011001111000001000110110000000010111010101001000000000101101111100111100110000110111111101000000000010111010110001111001110110110000111100111011100010011011110010000000010111010100101010100001111011100001110010100011001100000011010000000010000010000100100101000100000010001001011001111000001010000010000000011111110001100100110010101000111001011000110010111001000111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); } + [Fact] + public void can_encode_byte_mixed_mode() + { + var gen = new QRCodeGenerator(); + // create QR code with these data segments + // 1. Alphanumeric: "HTTPS://GITHUB.COM/S" + // 2. Byte: "hane32/QRCoder/commit" + // 3. Alphanumeric: "/F7C9426ECBB" + // 4. Byte: "a" + // 5. Numeric: "8614681" + // 6. Alphanumeric: "C954CEAA50113C26555C7" + var qrData = gen.CreateQrCode("HTTPS://GITHUB.COM/Shane32/QRCoder/commit/F7C9426ECBBa8614681C954CEAA50113C26555C7", ECCLevel.L); + qrData.Version.ShouldBe(4); // without mixed mode it would be version 5 + var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray()); + result.ShouldBe("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111010000010001111001011111110000000010000010100110010001101110100000100000000101110101100100011111011101011101000000001011101011101110110000100010111010000000010111010011011100011110100101110100000000100000101110011100001111001000001000000001111111010101010101010101011111110000000000000000001101001100111110000000000000000110011100010100101001011100101111000000000011000110000110110100010100110000000000011101110111000000011010101110000000000000010001000100101110001001010110101000000000110101010010110101001111110011010000000010001101011011101001110001101011100000000101011111011101111001101011101110000000001011100001110110110011100000101100000000010001110110011000011111111110000100000000010001000110011100110101110100110000000001001111101100100111111010110011100000000011110000111010001011000001000110100000000010010111011110100110111100010010000000001000000110001010001001010111111100000000000101111101100111111010011111000100000000001101010111000011001001100010010000000001111111010010010100011001111111110000000000000000110111000011110010001110000000000111111100101000111000101101010000000000001000001011100001010010001000100010000000010111010100101101101110011111011000000000101110100110001011011100010001111000000001011101001101110011101000010101000000000010000010110100010111110111000110100000000111111101010100011001101110101011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + var asciiRenderer = new AsciiQRCode(qrData); + var asciiArt = asciiRenderer.GetGraphic(1); + asciiArt.ShouldMatchApproved(x => x.NoDiff()); + } + [Fact] public void can_encode_byte() { @@ -771,6 +791,18 @@ public void micro_qr_should_auto_select_m4_for_ecc_level_q() (qrData.ModuleMatrix.Count - 8).ShouldBe(17); // M4 size is 17x17 qrData.Version.ShouldBe(-4); } + + [Fact] + public void multi_mode_does_not_compress_longer_than_original() + { + var gen = new QRCodeGenerator(); + var str = "111111a111111a111111a111111a111111a111111"; + var s = new string('a', 119 - str.Length) + str; + var qrData = gen.CreateQrCode(s, ECCLevel.H, requestedVersion: 10); + qrData.Version.ShouldBe(10); + var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray()); + result.ShouldBe("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111001101011000111000001000011110110101011110011111110000000010000010111000101000001110100111010111100010010100100000100000000101110101110010100011001010010100000011101110111001011101000000001011101010010011011111101101010110100101010100010010111010000000010111010110110110111110000111110100101101010100100101110100000000100000101000001011110011111000110011011000100110001000001000000001111111010101010101010101010101010101010101010101011111110000000000000000000000101111011010100011101100001001100000000000000000000001001111001010111000001101111100000001010111010010111110000000000010010000000101001000100010111101001001010100000101000010000000001100011110100001111001010110000100010011101100011011010100000000101010010000011101110110111000001010000010011000000011010000000000001111110010011110000110011010000010010101110110010110110000000001011100001000001000011100111111010001010101010101010000100000000010111101101010111110100001000011001010111011100110110101000000001100010100000000111101100111110110100000100110000001110100000000000000011100101010100000000100001000100101011101100111101100000000110110000010010000000010001101011100010100010101010100001000000001100111011010111111101011010011010010101100111001101101010000000000010001000000010111000101101110101000001000100000001001000000000100000111001011111000101101101000001001010101011001010011000000000101110000100011000001110110000101000101010101010101000110000000011001010110101000111010010010001100101011101110011011010100000000000110010000011111110100011011011110000110001000000011000000000001000001110010100010100101001011111010011101010110010110110000000001011100001000001001010111100111100001010001010101010101100000000110011111101101111101100111111100011010110011100111110011000000000001100010011011011000001110001110101001100010001000110100000000010001010100111100111111010101010001000111010101110101101100000000010110001000111011010101101000101010010101010100100010011000000001100111111000011101001001111111011110101110111011111101110000000000011001000110110101000010001011100000011000100110100101000000000101101110111011000001110101100000110001110101011011101011000000000111110110010110101111011000110011011001010100000101000010000000011000010011100111110010010011010110010011101100101101010100000000100110011111101101111000110011011111000010001001011001010000000000000011000010110000011101101000000011010101010110111010110000000001101101001001101110010110001100100010010101000001010000100000000111000101010001111001100101110101010100111011001011010101000000001101100001111011010000001110110111011000100010010110010100000000001100110110111110011011001000000000110101010101101110101100000000100111001110111000010101111111001101000101010000010100001000000000010001111111001110111011100101011101001110110010010101010000000011111001001101010011101001100100110000001001100100100101000000000001100111011100001010101010000010001001010111011011101011000000000100010001101101101100101111010011001001010101000101000010000000010100110111110100101101001010010111110011101110110101010100000000111110000011000110111100011111011100000010011001101001010000000000000001000111101010100011011111000010010101110111111110110000000000000000111010000011010010100011110010010101010110001000100000000111111101111111111011010001010111111111111011101101010101000000001000001010110011001111011110001101000010100010001000100100000000010111010001111100101000000111110000101101010101111111001100000000101110100110100010110011110001100100100100010100100010000000000001011101011111001010110110001001001111011100111001010101110000000010000010001101100011111001011000000000011000100101010000000000000111111100011101101000010100111111111010110101011110110001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + } } public static class ExtensionMethods