diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd28d2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vs/ +*.user +bin/ +obj/ diff --git a/dotnet/QrCodeGenerator.sln b/dotnet/QrCodeGenerator.sln new file mode 100644 index 0000000..0b529a3 --- /dev/null +++ b/dotnet/QrCodeGenerator.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.271 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QrCodeGenerator", "QrCodeGenerator\QrCodeGenerator.csproj", "{36B0822E-28D7-43C7-BAF7-AF578E31E978}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QrCodeGeneratorTest", "QrCodeGeneratorTest\QrCodeGeneratorTest.csproj", "{81307EE7-7915-4D2D-8F71-495F239FD266}" + ProjectSection(ProjectDependencies) = postProject + {36B0822E-28D7-43C7-BAF7-AF578E31E978} = {36B0822E-28D7-43C7-BAF7-AF578E31E978} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {36B0822E-28D7-43C7-BAF7-AF578E31E978}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36B0822E-28D7-43C7-BAF7-AF578E31E978}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36B0822E-28D7-43C7-BAF7-AF578E31E978}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36B0822E-28D7-43C7-BAF7-AF578E31E978}.Release|Any CPU.Build.0 = Release|Any CPU + {81307EE7-7915-4D2D-8F71-495F239FD266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81307EE7-7915-4D2D-8F71-495F239FD266}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81307EE7-7915-4D2D-8F71-495F239FD266}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81307EE7-7915-4D2D-8F71-495F239FD266}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {256EFBB5-C259-4422-BE66-29B9FF4BF92F} + EndGlobalSection +EndGlobal diff --git a/dotnet/QrCodeGenerator/BitArrayExtensions.cs b/dotnet/QrCodeGenerator/BitArrayExtensions.cs new file mode 100644 index 0000000..460534e --- /dev/null +++ b/dotnet/QrCodeGenerator/BitArrayExtensions.cs @@ -0,0 +1,104 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +using System; +using System.Collections; + +namespace Io.Nayuki.QrCodeGen +{ + /// + /// Provides extension methods for the class. + /// + public static class BitArrayExtensions + { + + /// + /// Appends the specified number of low-order bits of the specified value to this + /// bit array. Requires 0 ≤ len ≤ 31 and 0 ≤ val < 2len. + /// + /// this bit array + /// the value to append + /// the number of low-order bits in the value to take + /// Thrown if the value or number of bits is out of range + /// Thrown if appending the data would make bit length exceed + public static void AppendBits(this BitArray bitArray, uint val, int len) + { + if (len < 0 || len > 31 || (val >> len) != 0) + { + throw new ArgumentOutOfRangeException(nameof(len), "'len' out of range"); + } + + if (len < 0 || len > 31 || (val >> len) != 0) + { + throw new ArgumentOutOfRangeException(nameof(val), "'val' out of range"); + } + + int bitLength = bitArray.Length; + if (int.MaxValue - bitLength < len) + { + throw new InvalidOperationException("Maximum length reached"); + } + + bitArray.Length = bitLength + len; + uint mask = 1U << (len - 1); + for (int i = bitLength; i < bitLength + len; i++) // Append bit by bit + { + if ((val & mask) != 0) + { + bitArray.Set(i, true); + } + + mask >>= 1; + } + } + + + /// + /// Appends the content of the specified bit array to this array. + /// + /// this bit array + /// the bit array whose data to append (not null) + /// Thrown if the bit array is null + /// Thrown if appending the data would make the bit length exceed + public static void AppendData(this BitArray bitArray, BitArray otherArray) + { + Objects.RequireNonNull(otherArray); + int bitLength = bitArray.Length; + if (int.MaxValue - bitLength < otherArray.Length) + { + throw new InvalidOperationException("Maximum length reached"); + } + + bitArray.Length = bitLength + otherArray.Length; + for (int i = 0; i < otherArray.Length; i++, bitLength++) // Append bit by bit + { + if (otherArray[i]) + { + bitArray.Set(bitLength, true); + } + } + } + + } +} + diff --git a/dotnet/QrCodeGenerator/DataTooLongException.cs b/dotnet/QrCodeGenerator/DataTooLongException.cs new file mode 100644 index 0000000..94919a1 --- /dev/null +++ b/dotnet/QrCodeGenerator/DataTooLongException.cs @@ -0,0 +1,58 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ +using System; +using System.Collections.Generic; + +namespace Io.Nayuki.QrCodeGen +{ + /// + /// Thrown when the supplied data does not fit any QR Code version. + /// + /// + /// Ways to handle this exception include: + /// + /// + /// + /// + /// + /// + /// + public class DataTooLongException : ArgumentException + { + public DataTooLongException(string message) + : base(message) + { } + } +} diff --git a/dotnet/QrCodeGenerator/Objects.cs b/dotnet/QrCodeGenerator/Objects.cs new file mode 100644 index 0000000..8a928b8 --- /dev/null +++ b/dotnet/QrCodeGenerator/Objects.cs @@ -0,0 +1,39 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +using System; + +namespace Io.Nayuki.QrCodeGen +{ + internal class Objects + { + internal static T RequireNonNull(T arg) + { + if (arg == null) + { + throw new ArgumentNullException(); + } + return arg; + } + } +} diff --git a/dotnet/QrCodeGenerator/QrCode.cs b/dotnet/QrCodeGenerator/QrCode.cs new file mode 100644 index 0000000..7137203 --- /dev/null +++ b/dotnet/QrCodeGenerator/QrCode.cs @@ -0,0 +1,1021 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Io.Nayuki.QrCodeGen +{ + /// + /// A QR Code symbol, which is a type of two-dimension barcode. + /// + /// + /// Invented by Denso Wave and described in the ISO/IEC 18004 standard. + /// araInstances of this class represent an immutable square grid of black and white cells. + /// The class provides static factory functions to create a QR Code from text or binary data. + /// The class covers the QR Code Model 2 specification, supporting all versions (sizes) + /// from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + /// Ways to create a QR Code object: + ///
    + ///
  • High level: Take the payload data and call + /// or .
  • + ///
  • Mid level: Custom-make the list of + /// and call or + ///
  • + ///
  • Low level: Custom-make the array of data codeword bytes (including segment headers and + /// final padding, excluding error correction codewords), supply the appropriate version number, + /// and call the .
  • + ///
+ /// (Note that all ways require supplying the desired error correction level.) + ///
+ /// + public class QrCode + { + #region Static factory functions (high level) + + /// + /// Returns a QR Code representing the specified Unicode text string at the specified error correction level. + /// + /// + /// As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer + /// Unicode code points(not UTF-16 code units) if the low error correction level is used.The smallest possible + /// QR Code version is automatically chosen for the output.The ECC level of the result may be higher than the + /// ecl argument if it can be done without increasing the version. + /// + /// the text to be encoded (not null), which can be any Unicode string + /// the error correction level to use (not null) (boostable) + /// a QR Code (not null) representing the text + /// Thrown if the text or error correction level is null + /// Thrown if the text fails to fit in the + /// largest version QR Code at the ECL, which means it is too long + public static QrCode EncodeText(string text, Ecc ecl) + { + Objects.RequireNonNull(text); + Objects.RequireNonNull(ecl); + List segs = QrSegment.MakeSegments(text); + return EncodeSegments(segs, ecl); + } + + /// + /// Returns a QR Code representing the specified binary data at the specified error correction level. + /// + /// + /// This function always encodes using the binary segment mode, not any text mode. The maximum number of + /// bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + /// The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. + /// + /// the binary data to encode (not null) + /// the error correction level to use (not null) (boostable) + /// a QR Code (not null) representing the data + /// Thrown if the data or error correction level is null + /// Thrown if the data fails to fit in the + /// largest version QR Code at the ECL, which means it is too long + public static QrCode EncodeBinary(byte[] data, Ecc ecl) + { + Objects.RequireNonNull(data); + Objects.RequireNonNull(ecl); + QrSegment seg = QrSegment.MakeBytes(data); + return EncodeSegments(new List { seg }, ecl); + } + + #endregion + + + #region Static factory functions (mid level) + + /// + /// Returns a QR Code representing the specified segments at the specified error correction level. + /// + /// + /// The smallest possible QR Code version is automatically chosen for the output. The ECC level + /// of the result may be higher than the ecl argument if it can be done without increasing the version. + /// This function allows the user to create a custom sequence of segments that switches + /// between modes (such as alphanumeric and byte) to encode text in less space. + /// This is a mid-level API; the high-level API is + /// and + /// + /// the segments to encode + /// the error correction level to use (not null) (boostable) + /// a QR Code (not null) representing the segments + /// Thrown if the data or error correction level is null + /// Thrown if the segments fail to fit in the + /// largest version QR Code at the ECL, which means it is too long + public static QrCode EncodeSegments(List segs, Ecc ecl) + { + return EncodeSegments(segs, ecl, MinVersion, MaxVersion, -1, true); + } + + /// + /// Returns a QR Code representing the specified segments with the specified encoding parameters. + /// + /// + /// The smallest possible QR Code version within the specified range is automatically + /// chosen for the output. Iff boostEcl is true, then the ECC level of the + /// result may be higher than the ecl argument if it can be done without increasing + /// the version. The mask number is either between 0 to 7 (inclusive) to force that + /// mask, or −1 to automatically choose an appropriate mask (which may be slow). + /// This function allows the user to create a custom sequence of segments that switches + /// between modes (such as alphanumeric and byte) to encode text in less space. + /// This is a mid-level API; the high-level API is + /// and . + /// + /// the segments to encode + /// the error correction level to use (not null) (boostable) + /// the minimum allowed version of the QR Code (at least 1) + /// the maximum allowed version of the QR Code (at most 40) + /// the mask number to use (between 0 and 7 (inclusive)), or −1 for automatic mask + /// increases the ECC level as long as it doesn't increase the version number + /// a QR Code (not null) representing the segments + /// Thrown if the list of segments, any segment, or the error correction level is null + /// Thrown if 1 ≤ minVersion ≤ maxVersion ≤ 40 + /// or −1 ≤ mask ≤ 7 is violated + /// Thrown DataTooLongException if the segments fail to fit in + /// the maxVersion QR Code at the ECL, which means they are too long + public static QrCode EncodeSegments(List segs, Ecc ecl, int minVersion, int maxVersion, int mask, bool boostEcl) + { + Objects.RequireNonNull(segs); + Objects.RequireNonNull(ecl); + if (minVersion < MinVersion || minVersion > maxVersion) + { + throw new ArgumentOutOfRangeException(nameof(minVersion), "Invalid value"); + } + if (maxVersion > MaxVersion) + { + throw new ArgumentOutOfRangeException(nameof(maxVersion), "Invalid value"); + } + if (mask < -1 || mask > 7) + { + throw new ArgumentOutOfRangeException(nameof(mask), "Invalid value"); + } + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) + { + int numDataBits = GetNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = QrSegment.GetTotalBits(segs, version); + if (dataUsedBits != -1 && dataUsedBits <= numDataBits) + { + break; // This version number is found to be suitable + } + + if (version >= maxVersion) + { // All versions in the range could not fit the given data + string msg = "Segment too long"; + if (dataUsedBits != -1) + { + msg = $"Data length = {dataUsedBits} bits, Max capacity = {numDataBits} bits"; + } + + throw new DataTooLongException(msg); + } + } + Debug.Assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the current version number + foreach (Ecc newEcl in Ecc.AllValues) + { // From low to high + if (boostEcl && dataUsedBits <= GetNumDataCodewords(version, newEcl) * 8) + { + ecl = newEcl; + } + } + + // Concatenate all segments to create the data bit string + BitArray ba = new BitArray(0); + foreach (QrSegment seg in segs) + { + ba.AppendBits(seg.EncodingMode.ModeBits, 4); + ba.AppendBits((uint)seg.NumChars, seg.EncodingMode.NumCharCountBits(version)); + ba.AppendData(seg.GetData()); + } + Debug.Assert(ba.Length == dataUsedBits); + + // Add terminator and pad up to a byte if applicable + int dataCapacityBits = GetNumDataCodewords(version, ecl) * 8; + Debug.Assert(ba.Length <= dataCapacityBits); + ba.AppendBits(0, Math.Min(4, dataCapacityBits - ba.Length)); + ba.AppendBits(0, (8 - ba.Length % 8) % 8); + Debug.Assert(ba.Length % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint padByte = 0xEC; ba.Length < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + { + ba.AppendBits(padByte, 8); + } + + // Pack bits into bytes in big endian + byte[] dataCodewords = new byte[ba.Length / 8]; + for (int i = 0; i < ba.Length; i++) + { + if (ba.Get(i)) + { + dataCodewords[i >> 3] |= (byte)(1 << (7 - (i & 7))); + } + } + + // Create the QR Code object + return new QrCode(version, ecl, dataCodewords, mask); + } + + #endregion + + + #region Public immutable properties + + /// + /// The version number of this QR Code, which is between 1 and 40 (inclusive). + /// This determines the size of this barcode. + /// + public int Version { get; } + + /// + /// The width and height of this QR Code, measured in modules, between + /// 21 and 177 (inclusive). This is equal to version × 4 + 17. + /// + public int Size { get; } + + /// + /// The error correction level used in this QR Code, which is not null. + /// + public Ecc ErrorCorrectionLevel { get; } + + /// + /// The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + /// + /// Even if a QR Code is created with automatic masking requested (mask = + /// −1), the resulting object still has a mask value between 0 and 7. + /// + public int Mask { get; } + + #endregion + + + #region Private grids of modules/pixels, with dimensions of size * size + + // The modules of this QR Code (false = white, true = black). + // Immutable after constructor finishes. Accessed through getModule(). + private readonly bool[,] _modules; + + // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. + private readonly bool[,] _isFunction; + + #endregion + + + #region Constructor (low level) + + /// + /// Constructs a QR Code with the specified version number, + /// error correction level, data codeword bytes, and mask number. + /// + /// + /// This is a low-level API that most users should not use directly. A mid-level + /// API is the function. + /// + /// the version number to use, which must be in the range 1 to 40 (inclusive) + /// the error correction level to use + /// the bytes representing segments to encode (without ECC) + /// the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice + /// Thrown if the byte array or error correction level is null + /// Thrown if the version or mask value is out of range, + /// or if the data is the wrong length for the specified version and error correction level + public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) + { + // Check arguments and initialize fields + if (ver < MinVersion || ver > MaxVersion) + { + throw new ArgumentOutOfRangeException(nameof(ver), "Version value out of range"); + } + + if (mask < -1 || mask > 7) + { + throw new ArgumentOutOfRangeException(nameof(mask), "Mask value out of range"); + } + + Version = ver; + Size = ver * 4 + 17; + Objects.RequireNonNull(ecl); + ErrorCorrectionLevel = ecl; + Objects.RequireNonNull(dataCodewords); + _modules = new bool[Size, Size]; // Initially all white + _isFunction = new bool[Size, Size]; + + // Compute ECC, draw modules, do masking + DrawFunctionPatterns(); + byte[] allCodewords = AddEccAndInterleave(dataCodewords); + DrawCodewords(allCodewords); + Mask = HandleConstructorMasking(mask); + _isFunction = null; + } + + + #endregion + + + #region Public methods + + /// + /// Returns the color of the module (pixel) at the specified coordinates, which is false + /// for white or true for black. + /// + /// + /// The top left corner has the coordinates (x=0, y=0). + /// If the specified coordinates are out of bounds, then false + /// (white) is returned. + /// + /// the x coordinate, where 0 is the left edge and size−1 is the right edge + /// the y coordinate, where 0 is the top edge and size−1 is the bottom edge + /// true if the coordinates are in bounds and the module + /// at that location is black, or false (white) otherwise + public bool GetModule(int x, int y) + { + return 0 <= x && x < Size && 0 <= y && y < Size && _modules[y, x]; + } + + #endregion + + + #region Private helper methods for constructor: Drawing function modules + + // Reads this object's version field, and draws and marks all function modules. + private void DrawFunctionPatterns() + { + // Draw horizontal and vertical timing patterns + for (int i = 0; i < Size; i++) + { + SetFunctionModule(6, i, i % 2 == 0); + SetFunctionModule(i, 6, i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + DrawFinderPattern(3, 3); + DrawFinderPattern(Size - 4, 3); + DrawFinderPattern(3, Size - 4); + + // Draw numerous alignment patterns + int[] alignPatPos = GetAlignmentPatternPositions(); + int numAlign = alignPatPos.Length; + for (int i = 0; i < numAlign; i++) + { + for (int j = 0; j < numAlign; j++) + { + // Don't draw on the three finder corners + if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)) + { + DrawAlignmentPattern(alignPatPos[i], alignPatPos[j]); + } + } + } + + // Draw configuration data + DrawFormatBits(0); // Dummy mask value; overwritten later in the constructor + DrawVersion(); + } + + + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. + private void DrawFormatBits(uint mask) + { + // Calculate error correction code and pack bits + uint data = (ErrorCorrectionLevel.FormatBits << 3) | mask; // errCorrLvl is uint2, mask is uint3 + uint rem = data; + for (int i = 0; i < 10; i++) + { + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + } + + uint bits = ((data << 10) | rem) ^ 0x5412; // uint15 + Debug.Assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + { + SetFunctionModule(8, i, GetBit(bits, i)); + } + + SetFunctionModule(8, 7, GetBit(bits, 6)); + SetFunctionModule(8, 8, GetBit(bits, 7)); + SetFunctionModule(7, 8, GetBit(bits, 8)); + for (int i = 9; i < 15; i++) + { + SetFunctionModule(14 - i, 8, GetBit(bits, i)); + } + + // Draw second copy + for (int i = 0; i < 8; i++) + { + SetFunctionModule(Size - 1 - i, 8, GetBit(bits, i)); + } + + for (int i = 8; i < 15; i++) + { + SetFunctionModule(8, Size - 15 + i, GetBit(bits, i)); + } + + SetFunctionModule(8, Size - 8, true); // Always black + } + + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field, iff 7 <= version <= 40. + private void DrawVersion() + { + if (Version < 7) + { + return; + } + + // Calculate error correction code and pack bits + uint rem = (uint)Version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + { + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + } + + uint bits = ((uint)Version << 12) | rem; // uint18 + Debug.Assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 18; i++) + { + bool bit = GetBit(bits, i); + int a = Size - 11 + i % 3; + int b = i / 3; + SetFunctionModule(a, b, bit); + SetFunctionModule(b, a, bit); + } + } + + // Draws a 9*9 finder pattern including the border separator, + // with the center module at (x, y). Modules can be out of bounds. + private void DrawFinderPattern(int x, int y) + { + for (int dy = -4; dy <= 4; dy++) + { + for (int dx = -4; dx <= 4; dx++) + { + int dist = Math.Max(Math.Abs(dx), Math.Abs(dy)); // Chebyshev/infinity norm + int xx = x + dx, yy = y + dy; + if (0 <= xx && xx < Size && 0 <= yy && yy < Size) + { + SetFunctionModule(xx, yy, dist != 2 && dist != 4); + } + } + } + } + + + // Draws a 5*5 alignment pattern, with the center module + // at (x, y). All modules must be in bounds. + private void DrawAlignmentPattern(int x, int y) + { + for (int dy = -2; dy <= 2; dy++) + { + for (int dx = -2; dx <= 2; dx++) + { + SetFunctionModule(x + dx, y + dy, Math.Max(Math.Abs(dx), Math.Abs(dy)) != 1); + } + } + } + + + // Sets the color of a module and marks it as a function module. + // Only used by the constructor. Coordinates must be in bounds. + private void SetFunctionModule(int x, int y, bool isBlack) + { + _modules[y, x] = isBlack; + _isFunction[y, x] = true; + } + + #endregion + + + #region Private helper methods for constructor: Codewords and masking + + // Returns a new byte string representing the given data with the appropriate error correction + // codewords appended to it, based on this object's version and error correction level. + private byte[] AddEccAndInterleave(byte[] data) + { + Objects.RequireNonNull(data); + if (data.Length != GetNumDataCodewords(Version, ErrorCorrectionLevel)) + { + throw new ArgumentOutOfRangeException(); + } + + // Calculate parameter numbers + int numBlocks = NumErrorCorrectionBlocks[ErrorCorrectionLevel.Ordinal, Version]; + int blockEccLen = EccCodewordsPerBlock[ErrorCorrectionLevel.Ordinal, Version]; + int rawCodewords = GetNumRawDataModules(Version) / 8; + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockLen = rawCodewords / numBlocks; + + // Split data into blocks and append ECC to each block + byte[][] blocks = new byte[numBlocks][]; + ReedSolomonGenerator rs = new ReedSolomonGenerator(blockEccLen); + for (int i = 0, k = 0; i < numBlocks; i++) + { + byte[] dat = CopyOfRange(data, k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)); + k += dat.Length; + byte[] block = CopyOf(dat, shortBlockLen + 1); + byte[] ecc = rs.GetRemainder(dat); + Array.Copy(ecc, 0, block, block.Length - blockEccLen, ecc.Length); + blocks[i] = block; + } + + // Interleave (not concatenate) the bytes from every block into a single sequence + byte[] result = new byte[rawCodewords]; + for (int i = 0, k = 0; i < blocks[0].Length; i++) + { + for (int j = 0; j < blocks.Length; j++) + { + // Skip the padding byte in short blocks + if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) + { + result[k] = blocks[j][i]; + k++; + } + } + } + return result; + } + + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code. Function modules need to be marked off before this is called. + private void DrawCodewords(byte[] data) + { + Objects.RequireNonNull(data); + if (data.Length != GetNumRawDataModules(Version) / 8) + { + throw new ArgumentOutOfRangeException(); + } + + int i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = Size - 1; right >= 1; right -= 2) + { + // Index of right column in each column pair + if (right == 6) + { + right = 5; + } + + for (int vert = 0; vert < Size; vert++) + { + // Vertical counter + for (int j = 0; j < 2; j++) + { + int x = right - j; // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + int y = upward ? Size - 1 - vert : vert; // Actual y coordinate + if (!_isFunction[y, x] && i < data.Length * 8) + { + _modules[y, x] = GetBit(data[((uint)i) >> 3], 7 - (i & 7)); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/white by the constructor and are left unchanged by this method + } + } + } + Debug.Assert(i == data.Length * 8); + } + + + // XORs the codeword modules in this QR Code with the given mask pattern. + // The function modules must be marked and the codeword bits must be drawn + // before masking. Due to the arithmetic of XOR, calling applyMask() with + // the same mask value a second time will undo the mask. A final well-formed + // QR Code needs exactly one (not zero, two, etc.) mask applied. + private void ApplyMask(uint mask) + { + if (mask > 7) + { + throw new ArgumentOutOfRangeException(nameof(mask), "Mask value out of range"); + } + + for (int y = 0; y < Size; y++) + { + for (int x = 0; x < Size; x++) + { + bool invert; + switch (mask) + { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: Debug.Assert(false); return; + } + _modules[y, x] ^= invert & !_isFunction[y, x]; + } + } + } + + + // A messy helper function for the constructor. This QR Code must be in an unmasked state when this + // method is called. The given argument is the requested mask, which is -1 for auto or 0 to 7 for fixed. + // This method applies and returns the actual mask chosen, from 0 to 7. + private int HandleConstructorMasking(int mask) + { + if (mask == -1) + { + // Automatically choose best mask + int minPenalty = int.MaxValue; + for (uint i = 0; i < 8; i++) + { + ApplyMask(i); + DrawFormatBits(i); + int penalty = GetPenaltyScore(); + if (penalty < minPenalty) + { + mask = (int)i; + minPenalty = penalty; + } + ApplyMask(i); // Undoes the mask due to XOR + } + } + Debug.Assert(0 <= mask && mask <= 7); + ApplyMask((uint)mask); // Apply the final choice of mask + DrawFormatBits((uint)mask); // Overwrite old format bits + return mask; // The caller shall assign this value to the final-declared field + } + + + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + private int GetPenaltyScore() + { + int result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < Size; y++) + { + int[] runHistory = new int[7]; + bool color = false; + int runX = 0; + for (int x = 0; x < Size; x++) + { + if (_modules[y, x] == color) + { + runX++; + if (runX == 5) + { + result += PenaltyN1; + } + else if (runX > 5) + { + result++; + } + } + else + { + AddRunToHistory(runX, runHistory); + if (!color && HasFinderLikePattern(runHistory)) + { + result += PenaltyN3; + } + + color = _modules[y, x]; + runX = 1; + } + } + AddRunToHistory(runX, runHistory); + if (color) + { + AddRunToHistory(0, runHistory); // Dummy run of white + } + + if (HasFinderLikePattern(runHistory)) + { + result += PenaltyN3; + } + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < Size; x++) + { + int[] runHistory = new int[7]; + bool color = false; + int runY = 0; + for (int y = 0; y < Size; y++) + { + if (_modules[y, x] == color) + { + runY++; + if (runY == 5) + { + result += PenaltyN1; + } + else if (runY > 5) + { + result++; + } + } + else + { + AddRunToHistory(runY, runHistory); + if (!color && HasFinderLikePattern(runHistory)) + { + result += PenaltyN3; + } + + color = _modules[y, x]; + runY = 1; + } + } + AddRunToHistory(runY, runHistory); + if (color) + { + AddRunToHistory(0, runHistory); // Dummy run of white + } + + if (HasFinderLikePattern(runHistory)) + { + result += PenaltyN3; + } + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < Size - 1; y++) + { + for (int x = 0; x < Size - 1; x++) + { + bool color = _modules[y, x]; + if (color == _modules[y, x + 1] && + color == _modules[y + 1, x] && + color == _modules[y + 1, x + 1]) + { + result += PenaltyN2; + } + } + } + + // Balance of black and white modules + int black = 0; + for (int y = 0; y < Size; y++) + { + for (int x = 0; x < Size; x++) + + { + if (_modules[y, x]) + { + black++; + } + } + } + int total = Size * Size; // Note that size is odd, so black/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% + int k = (Math.Abs(black * 20 - total * 10) + total - 1) / total - 1; + result += k * PenaltyN4; + return result; + } + + + #endregion + + + #region Private helper functions + + // Returns an ascending list of positions of alignment patterns for this version number. + // Each position is in the range [0,177), and are used on both the x and y axes. + // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. + private int[] GetAlignmentPatternPositions() + { + if (Version == 1) + { + return new int[] { }; + } + else + { + int numAlign = Version / 7 + 2; + int step; + if (Version == 32) // Special snowflake + { + step = 26; + } + else // step = ceil[(size - 13) / (numAlign*2 - 2)] * 2 + { + step = (Version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; + } + + int[] result = new int[numAlign]; + result[0] = 6; + for (int i = result.Length - 1, pos = Size - 7; i >= 1; i--, pos -= step) + { + result[i] = pos; + } + + return result; + } + } + + // Returns the number of data bits that can be stored in a QR Code of the given version number, after + // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. + // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. + private static int GetNumRawDataModules(int ver) + { + if (ver < MinVersion || ver > MaxVersion) + { + throw new ArgumentOutOfRangeException(nameof(ver), "Version number out of range"); + } + + int size = ver * 4 + 17; + int result = size * size; // Number of modules in the whole QR Code square + result -= 8 * 8 * 3; // Subtract the three finders with separators + result -= 15 * 2 + 1; // Subtract the format information and black module + result -= (size - 16) * 2; // Subtract the timing patterns (excluding finders) + // The five lines above are equivalent to: int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) + { + int numAlign = ver / 7 + 2; + result -= (numAlign - 1) * (numAlign - 1) * 25; // Subtract alignment patterns not overlapping with timing patterns + result -= (numAlign - 2) * 2 * 20; // Subtract alignment patterns that overlap with timing patterns + // The two lines above are equivalent to: result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + { + result -= 6 * 3 * 2; // Subtract version information + } + } + return result; + } + + + // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any + // QR Code of the given version number and error correction level, with remainder bits discarded. + // This stateless pure function could be implemented as a (40*4)-cell lookup table. + private static int GetNumDataCodewords(int ver, Ecc ecl) + { + return GetNumRawDataModules(ver) / 8 + - EccCodewordsPerBlock[ecl.Ordinal, ver] + * NumErrorCorrectionBlocks[ecl.Ordinal, ver]; + } + + + // Inserts the given value to the front of the given array, which shifts over the + // existing values and deletes the last value. A helper function for GetPenaltyScore(). + private static void AddRunToHistory(int run, int[] history) + { + Array.Copy(history, 0, history, 1, history.Length - 1); + history[0] = run; + } + + + // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and + // surrounded by at least 4 on either or both ends. A helper function for GetPenaltyScore(). + // Must only be called immediately after a run of white modules has ended. + private static bool HasFinderLikePattern(int[] runHistory) + { + int n = runHistory[1]; + return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n + && runHistory[3] == n * 3 && Math.Max(runHistory[0], runHistory[6]) >= n * 4; + } + + + private static byte[] CopyOfRange(byte[] original, int from, int to) + { + byte[] result = new byte[to - from]; + Array.Copy(original, from, result, 0, to - from); + return result; + } + + + private static byte[] CopyOf(byte[] original, int newLength) + { + byte[] result = new byte[newLength]; + Array.Copy(original, result, Math.Min(original.Length, newLength)); + return result; + } + + + // Returns true iff the i'th bit of x is set to 1. + private static bool GetBit(uint x, int i) + { + return ((x >> i) & 1) != 0; + } + + #endregion + + + #region Constants and tables + + /// + /// The minimum version number (1) supported in the QR Code Model 2 standard. + /// + public static readonly int MinVersion = 1; + + /// + /// The maximum version number (40) supported in the QR Code Model 2 standard. + /// + public static readonly int MaxVersion = 40; + + + // For use in getPenaltyScore(), when evaluating which mask is best. + private static readonly int PenaltyN1 = 3; + private static readonly int PenaltyN2 = 3; + private static readonly int PenaltyN3 = 40; + private static readonly int PenaltyN4 = 10; + + + private static readonly byte[,] EccCodewordsPerBlock = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + { 255, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, // Low + { 255, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 }, // Medium + { 255, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, // Quartile + { 255, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, // High + }; + + private static readonly byte[,] NumErrorCorrectionBlocks = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + { 255, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }, // Low + { 255, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium + { 255, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile + { 255, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 }, // High + }; + + #endregion + + + #region Public helper enumeration + + /// + /// The error correction level in a QR Code symbol. + /// + public sealed class Ecc + { + /// + /// The QR Code can tolerate about 7% erroneous codewords. + /// + public static readonly Ecc Low = new Ecc(0, 1); + + /// + /// The QR Code can tolerate about 15% erroneous codewords. + /// + public static readonly Ecc Medium = new Ecc(1, 0); + + /// + /// The QR Code can tolerate about 25% erroneous codewords. + /// + public static readonly Ecc Quartile = new Ecc(2, 3); + + /// + /// The QR Code can tolerate about 30% erroneous codewords. + /// + public static readonly Ecc High = new Ecc(3, 2); + + + internal static Ecc[] AllValues = { Low, Medium, Quartile, High }; + + /// + /// Ordinal number of error correction level (in the range 0 to 3). + /// + /// + /// Higher number represent a higher amount of error tolerance. + /// + public int Ordinal { get; } + + // In the range 0 to 3 (unsigned 2-bit integer). + internal uint FormatBits { get; } + + // Constructor. + private Ecc(int ordinal, uint fb) + { + Ordinal = ordinal; + FormatBits = fb; + } + } + + + #endregion + } +} diff --git a/dotnet/QrCodeGenerator/QrCodeGenerator.csproj b/dotnet/QrCodeGenerator/QrCodeGenerator.csproj new file mode 100644 index 0000000..37a22d2 --- /dev/null +++ b/dotnet/QrCodeGenerator/QrCodeGenerator.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + Io.Nayuki.QrCodeGen + Io.Nayuki.QrCodeGen + 1.4.1 + Project Nayuki + QR Code Generator for .NET + This project aims to be the best, clearest QR Code generator library in multiple languages. The primary goals are flexible options and absolute correctness. Secondary goals are compact implementation size and good documentation comments + Copyright (c) Project Nayuki. (MIT License) + https://github.com/nayuki/QR-Code-generator + https://www.nayuki.io/page/qr-code-generator-library + https://opensource.org/licenses/MIT + qr code, qrcode + Initial release for .NET + + + diff --git a/dotnet/QrCodeGenerator/QrSegment.cs b/dotnet/QrCodeGenerator/QrSegment.cs new file mode 100644 index 0000000..046aa13 --- /dev/null +++ b/dotnet/QrCodeGenerator/QrSegment.cs @@ -0,0 +1,379 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; + +namespace Io.Nayuki.QrCodeGen +{ + /// + /// A segment of character/binary/control data in a QR Code symbol. + /// + /// + /// Instances of this class are immutable. + /// The mid-level way to create a segment is to take the payload data and call a + /// static factory function such as . The low-level + /// way to create a segment is to custom-make the bit buffer and call the + /// with appropriate values. + /// This segment class imposes no length restrictions, but QR Codes have restrictions. + /// Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + /// Any segment longer than this is meaningless for the purpose of generating QR Codes. + /// This class can represent kanji mode segments, but provides no help in encoding them + /// - see for full kanji support. + /// + public class QrSegment + { + #region Static factory functions (mid level) + + /// + /// Returns a segment representing the specified binary data + /// encoded in byte mode. All input byte arrays are acceptable. + /// + /// + /// Any text string can be converted to UTF-8 bytes (Encoding.UTF8.GetBytes(s)) + /// and encoded as a byte mode segment. + /// + /// the binary data (not null) + /// a segment (not null) containing the data + /// Thrown if the array is null + public static QrSegment MakeBytes(byte[] data) + { + Objects.RequireNonNull(data); + BitArray ba = new BitArray(0); + foreach (byte b in data) + { + ba.AppendBits(b, 8); + } + + return new QrSegment(Mode.Byte, data.Length, ba); + } + + + /// + /// Returns a segment representing the specified string of decimal digits encoded in numeric mode. + /// + /// the text (not null), with only digits from 0 to 9 allowed + /// a segment (not null) containing the text + /// Thrown if the string is null + /// Thrown if the string contains non-digit characters + public static QrSegment MakeNumeric(string digits) + { + Objects.RequireNonNull(digits); + if (!NumericRegex.IsMatch(digits)) + { + throw new ArgumentOutOfRangeException(nameof(digits), "String contains non-numeric characters"); + } + + BitArray ba = new BitArray(0); + for (int i = 0; i < digits.Length;) + { + // Consume up to 3 digits per iteration + int n = Math.Min(digits.Length - i, 3); + ba.AppendBits(uint.Parse(digits.Substring(i, n)), n * 3 + 1); + i += n; + } + return new QrSegment(Mode.Numeric, digits.Length, ba); + } + + + /// + /// Returns a segment representing the specified text string encoded in alphanumeric mode. + /// The characters allowed are: 0 to 9, A to Z(uppercase only), space, + /// dollar, percent, asterisk, plus, hyphen, period, slash, colon. + /// + /// the text (not null), with only certain characters allowed + /// a segment (not null) containing the text + /// Thrown if the string is null + /// Thrown iif the string contains non-encodable characters + public static QrSegment MakeAlphanumeric(string text) + { + Objects.RequireNonNull(text); + if (!AlphanumericRegex.IsMatch(text)) + { + throw new ArgumentOutOfRangeException(nameof(text), "String contains unencodable characters in alphanumeric mode"); + } + + BitArray ba = new BitArray(0); + int i; + for (i = 0; i <= text.Length - 2; i += 2) + { + // Process groups of 2 + uint temp = (uint)AlphanumericCharset.IndexOf(text[i]) * 45; + temp += (uint)AlphanumericCharset.IndexOf(text[i + 1]); + ba.AppendBits(temp, 11); + } + if (i < text.Length) // 1 character remaining + { + ba.AppendBits((uint)AlphanumericCharset.IndexOf(text[i]), 6); + } + + return new QrSegment(Mode.Alphanumeric, text.Length, ba); + } + + + /// + /// Returns a list of zero or more segments to represent the specified Unicode text string. + /// The result may use various segment modes and switch modes to optimize the length of the bit stream. + /// + /// the text to be encoded, which can be any Unicode string + /// a new mutable list (not null) of segments (not null) containing the text + /// Thrown if the text is null + public static List MakeSegments(string text) + { + Objects.RequireNonNull(text); + + // Select the most efficient segment encoding automatically + List result = new List(); + if (text == "") + { + // Leave result empty + } + else if (NumericRegex.IsMatch(text)) + { + result.Add(MakeNumeric(text)); + } + else if (AlphanumericRegex.IsMatch(text)) + { + result.Add(MakeAlphanumeric(text)); + } + else + { + result.Add(MakeBytes(Encoding.UTF8.GetBytes(text))); + } + + return result; + } + + + /// + /// Returns a segment representing an Extended Channel Interpretation + /// (ECI) designator with the specified assignment value. + /// + /// the ECI assignment number (see the AIM ECI specification) + /// a segment (not null) containing the data + /// Thrown if the value is outside the range [0, 106) + public static QrSegment MakeEci(int assignVal) + { + BitArray ba = new BitArray(0); + if (assignVal < 0) + { + throw new ArgumentOutOfRangeException(nameof(assignVal), "ECI assignment value out of range"); + } + + if (assignVal < (1 << 7)) + { + ba.AppendBits((uint)assignVal, 8); + } + else if (assignVal < (1 << 14)) + { + ba.AppendBits(2, 2); + ba.AppendBits((uint)assignVal, 14); + } + else if (assignVal < 1_000_000) + { + ba.AppendBits(6, 3); + ba.AppendBits((uint)assignVal, 21); + } + else + { + throw new ArgumentOutOfRangeException(nameof(assignVal), "ECI assignment value out of range"); + } + + return new QrSegment(Mode.Eci, 0, ba); + } + + #endregion + + + #region Instance fields + + /// + /// The mode indicator of this segment. Not null. + /// + public Mode EncodingMode { get; } + + /// + /// The length of this segment's unencoded data. Measured in characters for + /// numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + /// Always zero or positive. Not the same as the data's bit length. + /// + public int NumChars { get; } + + // The data bits of this segment. Not null. Accessed through GetData(). + private readonly BitArray _data; + + #endregion + + + #region Constructor (low level) + + /// + /// Constructs a QR Code segment with the specified attributes and data. + /// The character count(numCh) must agree with the mode and the bit buffer length, + /// but the constraint isn't checked. The specified bit buffer is cloned and stored. + /// + /// the mode (not null) + /// the data length in characters or bytes, which is non-negative + /// the data bits (not null) + /// Thrown if the mode or data is null + /// Thrown if the character count is negative + public QrSegment(Mode md, int numCh, BitArray data) + { + EncodingMode = Objects.RequireNonNull(md); + Objects.RequireNonNull(data); + if (numCh < 0) + { + throw new ArgumentOutOfRangeException(nameof(numCh), "Invalid value"); + } + + NumChars = numCh; + _data = (BitArray)data.Clone(); // Make defensive copy + } + + #endregion + + + #region Methods + + /// + /// Returns the data bits of this segment. + /// + /// a new copy of the data bits(notnull) + public BitArray GetData() + { + return (BitArray)_data.Clone(); // Make defensive copy + } + + + // Calculates the number of bits needed to encode the given segments at the given version. + // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too + // many characters to fit its length field, or the total bits exceeds int.MaxValue. + internal static int GetTotalBits(List segs, int version) + { + Objects.RequireNonNull(segs); + long result = 0; + foreach (QrSegment seg in segs) + { + Objects.RequireNonNull(seg); + int ccbits = seg.EncodingMode.NumCharCountBits(version); + if (seg.NumChars >= (1 << ccbits)) + { + return -1; // The segment's length doesn't fit the field's bit width + } + + result += 4L + ccbits + seg._data.Length; + if (result > int.MaxValue) + { + return -1; // The sum will overflow an int type + } + } + return (int)result; + } + + #endregion + + + #region Constants + + /// + /// Describes precisely all strings that are encodable in numeric mode. + /// + /// + /// To test whether a string s is encodable: + /// + /// bool ok = NumericRegex.IsMatch(s); + /// + /// A string is encodable iff each character is in the range 0 to 9. + /// + /// + public static readonly Regex NumericRegex = new Regex("^[0-9]*$", RegexOptions.Compiled); + + /// + /// Describes precisely all strings that are encodable in alphanumeric mode. + /// + /// + /// To test whether a string s is encodable: + /// + /// bool ok = AlphanumericRegex.IsMatch(s); + /// + /// A string is encodable iff each character is in the following set: 0 to 9, A to Z + /// (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + /// + /// + public static readonly Regex AlphanumericRegex = new Regex("^[A-Z0-9 $%*+./:-]*$", RegexOptions.Compiled); + + + // The set of all legal characters in alphanumeric mode, where + // each character value maps to the index in the string. + static readonly string AlphanumericCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + + #endregion + + + #region Public helper enumeration + + /// + /// Describes how a segment's data bits are interpreted. + /// + public sealed class Mode + { + public static readonly Mode Numeric = new Mode(0x1, 10, 12, 14); + + public static readonly Mode Alphanumeric = new Mode(0x2, 9, 11, 13); + + public static readonly Mode Byte = new Mode(0x4, 8, 16, 16); + + public static readonly Mode Kanji = new Mode(0x8, 8, 10, 12); + + public static readonly Mode Eci = new Mode(0x7, 0, 0, 0); + + // The mode indicator bits, which is a uint4 value (range 0 to 15). + internal uint ModeBits { get; } + + // Number of character count bits for three different version ranges. + internal int[] NumBitsCharCount { get; } + + // Returns the bit width of the character count field for a segment in this mode + // in a QR Code at the given version number. The result is in the range [0, 16]. + internal int NumCharCountBits(int ver) + { + Debug.Assert(QrCode.MinVersion <= ver && ver <= QrCode.MaxVersion); + return NumBitsCharCount[(ver + 7) / 17]; + } + + private Mode(uint modeBits, params int[] numBitsCharCount) + { + ModeBits = modeBits; + NumBitsCharCount = numBitsCharCount; + } + } + + #endregion + + } + +} diff --git a/dotnet/QrCodeGenerator/ReedSolomonGenerator.cs b/dotnet/QrCodeGenerator/ReedSolomonGenerator.cs new file mode 100644 index 0000000..366f506 --- /dev/null +++ b/dotnet/QrCodeGenerator/ReedSolomonGenerator.cs @@ -0,0 +1,144 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + + +using System; +using System.Diagnostics; + +namespace Io.Nayuki.QrCodeGen +{ + /// + /// Computes the Reed-Solomon error correction codewords for a sequence of data codewords at a given degree. + /// + /// + /// Objects are immutable, and the state only depends on the degree. + /// This class exists because each data block in a QR Code shares the same the divisor polynomial. + /// + internal class ReedSolomonGenerator + { + #region Fields + + // Coefficients of the divisor polynomial, stored from highest to lowest power, excluding the leading term which + // is always 1. For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + private readonly byte[] _coefficients; + + #endregion + + + #region Constructors + + /// + /// Constructs a Reed-Solomon ECC generator for the specified degree. This could be implemented + /// as a lookup table over all possible parameter values, instead of as an algorithm. + /// + /// the divisor polynomial degree, which must be between 1 and 255 (inclusive) + /// Thrown if degree < 1 or degree > 255 + internal ReedSolomonGenerator(int degree) + { + if (degree < 1 || degree > 255) + { + throw new ArgumentOutOfRangeException(nameof(degree), "Degree out of range"); + } + + // Start with the monomial x^0 + _coefficients = new byte[degree]; + _coefficients[degree - 1] = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint root = 1; + for (int i = 0; i < degree; i++) + { + // Multiply the current product by (x - r^i) + for (int j = 0; j < _coefficients.Length; j++) + { + _coefficients[j] = Multiply(_coefficients[j], root); + if (j + 1 < _coefficients.Length) + { + _coefficients[j] ^= _coefficients[j + 1]; + } + } + root = Multiply(root, 0x02); + } + } + + #endregion + + + #region Methods + + /// + /// Computes and returns the Reed-Solomon error correction codewords for the specified + /// sequence of data codewords. + /// + /// + /// The returned object is always a new byte array. + /// This method does not alter this object's state (because it is immutable). + /// + /// the sequence of data codewords + /// the Reed-Solomon error correction codewords + /// Thrown if the data is null + internal byte[] GetRemainder(byte[] data) + { + Objects.RequireNonNull(data); + + // Compute the remainder by performing polynomial division + byte[] result = new byte[_coefficients.Length]; + foreach (byte b in data) + { + uint factor = (uint)(b ^ result[0]); + Array.Copy(result, 1, result, 0, result.Length - 1); + result[result.Length - 1] = 0; + for (int i = 0; i < result.Length; i++) + { + result[i] ^= Multiply(_coefficients[i], factor); + } + } + return result; + } + + #endregion + + + #region Static functions + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. + private static byte Multiply(uint x, uint y) + { + Debug.Assert((x >> 8) == 0 && (y >> 8) == 0); + // Russian peasant multiplication + uint z = 0; + for (int i = 7; i >= 0; i--) + { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + Debug.Assert((z >> 8) == 0); + return (byte)z; + } + + #endregion + } +} diff --git a/dotnet/QrCodeGeneratorTest/BitArrayExtensionsTest.cs b/dotnet/QrCodeGeneratorTest/BitArrayExtensionsTest.cs new file mode 100644 index 0000000..8d8fef2 --- /dev/null +++ b/dotnet/QrCodeGeneratorTest/BitArrayExtensionsTest.cs @@ -0,0 +1,79 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +using System; +using System.Collections; +using Xunit; + +namespace Io.Nayuki.QrCodeGen.Test +{ + public class BitArrayExtensionsTest + { + [Fact] + private void AppendInt1() + { + var ba = new BitArray(0); + ba.AppendBits(18, 6); + + Assert.Equal(6, ba.Length); + + Assert.False(ba[0]); + Assert.True(ba[1]); + Assert.False(ba[2]); + Assert.False(ba[3]); + Assert.True(ba[4]); + Assert.False(ba[5]); + } + + [Fact] + private void AppendInt2() + { + var ba = new BitArray(0); + ba.AppendBits(18, 6); + + ba.AppendBits(3, 2); + + Assert.Equal(8, ba.Length); + + Assert.False(ba[0]); + Assert.True(ba[1]); + Assert.False(ba[2]); + Assert.False(ba[3]); + Assert.True(ba[4]); + Assert.False(ba[5]); + Assert.True(ba[6]); + Assert.True(ba[7]); + } + + [Fact] + private void AppendExtraBits() + { + var ba = new BitArray(0); + + Assert.Throws(() => + { + ba.AppendBits(128, 4); + }); + } + } +} diff --git a/dotnet/QrCodeGeneratorTest/QrCodeGeneratorTest.csproj b/dotnet/QrCodeGeneratorTest/QrCodeGeneratorTest.csproj new file mode 100644 index 0000000..db8bf84 --- /dev/null +++ b/dotnet/QrCodeGeneratorTest/QrCodeGeneratorTest.csproj @@ -0,0 +1,41 @@ + + + + netcoreapp2.2 + + false + + Io.Nayuki.QrCodeGen.Test + + Io.Nayuki.QrCodeGen.Test + + 1.4.1 + + Project Nayuki + + Project Nayuki + + QR Code Generator for .NET + + Unit tests for QR Code Generator + + Copyright (c) Project Nayuki. (MIT License) + + https://opensource.org/licenses/MIT + + https://www.nayuki.io/page/qr-code-generator-library + + https://github.com/nayuki/QR-Code-generator + + + + + + + + + + + + + diff --git a/dotnet/QrCodeGeneratorTest/QrCodeTest.cs b/dotnet/QrCodeGeneratorTest/QrCodeTest.cs new file mode 100644 index 0000000..9e65547 --- /dev/null +++ b/dotnet/QrCodeGeneratorTest/QrCodeTest.cs @@ -0,0 +1,930 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +using System.Text; +using Xunit; +using static Io.Nayuki.QrCodeGen.QrCode; + +namespace Io.Nayuki.QrCodeGen.Test +{ + public class QrCodeTest + { + + private static readonly string[] Modules0 = { + "XXXXXXX X XXXXXXX", + "X X XX X X X", + "X XXX X X X XXX X", + "X XXX X XX X X XXX X", + "X XXX X X X XXX X", + "X X XXXX X X", + "XXXXXXX X X X XXXXXXX", + " X X ", + " XXXX XX XX X ", + " X XX XX XXX X X ", + " XXX XX XXXXXXXX ", + "X X X X X X X X XX ", + "X XXXX XX ", + " X X XX X ", + "XXXXXXX X X X X ", + "X X XXXXXXXX X X ", + "X XXX X XX XXX XX XXX", + "X XXX X XXXX X X XX", + "X XXX X X X XXXX ", + "X X X X X XX ", + "XXXXXXX X X X XXX" + }; + + private const string Text0 = "98323"; + + [Fact] + private void TestCode0() + { + var qrCode = EncodeText(Text0, Ecc.Medium); + Assert.Same(Ecc.High, qrCode.ErrorCorrectionLevel); + Assert.Equal(21, qrCode.Size); + Assert.Equal(4, qrCode.Mask); + Assert.Equal(Modules0, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules1 = { + "XXXXXXX X X XXX X XXXXXXX", + "X X X X XXX XX X X", + "X XXX X XXXX X X X X XXX X", + "X XXX X X XX XX XX X XXX X", + "X XXX X X XXX X X XXX X", + "X X X X X XX X X", + "XXXXXXX X X X X X X X XXXXXXX", + " X XX XX X ", + " XX X XX XXX X X X XXXXX", + " XXX XXXXXXXXX X XXXX XXX", + " XXXX XXXX XXXXXXXXXXXX XX", + "XXX XXX X XX X X X ", + "X X XX X XXXX XX XXXXX X XX", + " XX XX X XX X XX XXXX", + "X X X X XX X X X XX XXX", + "X X XX XXX X XXX X XX", + "X XXXXX XX X X XXXXXX XX", + " XXX XXX X XX XX X XX ", + "X XXX X XXXX XXX", + " XXX X X XX X XXX XXX ", + "X XX XX X XXX XXX XXXXX ", + " XX X XX XXX X XX", + "XXXXXXX X X XX XX X XXXXX", + "X X X X XX X X XX X ", + "X XXX X X XXXXXX XXXXX X", + "X XXX X X X X X X XX ", + "X XXX X X X X X X XX X X", + "X X X X XXX XX XX X X ", + "XXXXXXX XX X XX XX X X XX" + }; + + private const string Text1 = "The quick brown fox jumps"; + + [Fact] + private void TestCode1() + { + var qrCode = EncodeText(Text1, Ecc.Quartile); + Assert.Same(Ecc.Quartile, qrCode.ErrorCorrectionLevel); + Assert.Equal(29, qrCode.Size); + Assert.Equal(0, qrCode.Mask); + Assert.Equal(Modules1, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules2 = { + "XXXXXXX XXXXX X XX X X XXX X XXX X X XX XXXXXXX", + "X X XX XXXXX XX XXXX X XX XXX XXX X XX X X", + "X XXX X X X XXXXX XX XX XXX X XX XXX X XXX X XXX X", + "X XXX X XX X X X X XXX X XX XX X X XX XX X X XXX X", + "X XXX X X X XX X XX X X XXXXXX X XXXX X X XX X XXX X", + "X X XXXX XXX XX XX X X XX X XX XX X XXXX X X", + "XXXXXXX X X X X X X X X X X X X X X X X X X X X X X X XXXXXXX", + " X X XXXXX XXX XXXX X XXXX XXX XXX X ", + "X X XXX XXX XXXXX X XXXXX XXX X X X XX XXXXX X", + "XXXX X X X X XX X X XX X XXXX XXXX X ", + " X XX X X X X X XXXX X XXX X X XXX X X X X X X XX ", + "X XXX X XX X XXX X X X X X X XXX XX XXXX X XX ", + "XX X XXX XXXXXXX XX XXX X XXXX X X X XX XXX X X X X ", + "X X X XXXX X X XX XX X XX XXXXX XX XX X XX ", + " X XX XX X XXXX XXX XXXX X X X XXX XX X XX XXX X ", + "X XX X XXXX X X X X X X X XXXX XX XXXX X ", + "XX X X XXX X XX XXXXX X X X XXXX X XXXXXXXXX X XXX", + "X X XXXXX XX XXX X XXX X X XXXX X XXX XX X ", + "X XXX XX XXX X XX XXX XXX X X X XXXXX XXXX XX ", + "XX X X XX XXX XXXX XX XX XX XXX XX XX ", + " XXX X X X X X X X XX X XXXXXXXXX XX X XX XX XX X ", + "X X X X X XXX XXX XXXX XXXX X X XXX X X XX XX X X ", + " XX X X XXX XXXXXX XX X X XX X XXX X XX XX X ", + " XX X XXXX X XX X X XX X X XXXX X XXXXXX X X X ", + "XX XXXX X XXX XX X X XXXX XXXX X XX XX X XXXXX", + " X XX XXXXXXXXXXX XXX X XX X XXX X X XXX XX XX XX XXX X", + "XXXXX X X X XXX X XX XX XXX XX X XX X X X ", + "X X X X X X X X XXXXX X X XX X XXX X XX X X X", + " X XXXXXX X XXX X X XXXXX XX X XX XXXX XXXXX XXX", + " XXX X XXX XX XX XXX X XX X XXXXX XXX XX XX", + "XXXXX X X X X XX XX X XX X X XXXX XXXX XXXX X X X X ", + " X X X XX X X X XXXXX X XXXXX XX X XXX X X ", + " XXXXXX XX XXXXX XX X XXXXX X X XX XXX XXXXX XXX", + "X X X X XX X X X XXXXX XXXX X XXXXXXXX X XX XX XX ", + " X XXXXX X XXXXX X X X XXX X XX X XXX X ", + "X XXX X XXX X XXXXX X XX XXX X XX X X XXX XX XXXX", + " XX X XX X X XXXXXXX XX X XX X X XXXXXXXX X XX XX X", + "XX X X XX X X X XX XX XX XX XX XX XX X X X X XX XX", + " X XX X X XXXXX XXX XX X XX XXX XXXXX X XX X X XX X", + "XX X XX XXXXX XXX X X X X XX XXXXX XXX XX ", + " XXXX X XX XXX XXXXXX XX XXXXXX X X X XXX X X XX X", + "XXXX X XX XXX X XXX X X X X XX X XX XX X X XXX XX X ", + " XXXXXX XXX X XX X XX XX X XXX X X XX XXXX ", + " XXXX XXX XX XXX X X XXXX X XXX X X X X XXX", + " XX XXXX X X X X XX X X XXXXXX X X XXXX XXXX XXX XXX X", + "XX XX XXX XX XXXXXXX X X XXXX X X X XX X XXXX ", + " XX XXX X XXXX X X X X XXX XX X X X XXX XX XX XX ", + " XX X X XXX XXXX X X XXXXX XXXX X XXXX X X XX XX ", + "XXX XX X XX XXXX X XX XX XXXXX X X XX X X ", + "XX XX X XX XXXX X X XX XX XXXX X XX XX XX X X XX X ", + " XXXXXX X X XXXX XXXXXX X X XX XXXX XX XX ", + "XXX X XXX X XX XX XX XXX XXXXX XXX X XXX XX X X X XXX ", + "XXXX XXX XXX X X X XXXXX X X XX XXXX XXXXX X X", + " XXXX XXXX X XXXXXX X XX X X XXXXX X X X X ", + "XXXXXXX X X XXX X X X X X X XXX XXXX XXXXXX XXX X XX ", + "X X XX XX XXXXX X X X XX X X XXXX X XXX XX X X ", + "X XXX X X X XX X XX XX XXXXX X XXX X XXX XXXXX XX", + "X XXX X X X XX XX XX XXX X XX XXXX X X X X", + "X XXX X X X X XX XXX X XXX X X XXXX XX XX X XXX ", + "X X X XXX X XXXX XX X XXXX XXX XXXXX XX XX XX XX ", + "XXXXXXX X X X X X X X XX X XXX XX X XXX" + }; + + private const string Text2 = "kVtnmdZMDB wbhQ4Y0L0D 6dxWYgeDO7 6XEq8JBGFD dbA5ruetw0 zIevtZZkJL UnEcrObNuS COOscwe4PD PL2lKGcbqk uXnmfUX00E l4FsUfvkiU O8bje4GTce C85HiEHDha EoObmX7Hef VEipzaCPV7 XpBy5cgYRZ VzlrmMTRSW f0U7Dt0x5j Mb5uk2JcA6 MFov2hnHQR"; + + [Fact] + private void TestCode2() + { + var qrCode = EncodeText(Text2, Ecc.Medium); + Assert.Same(Ecc.Medium, qrCode.ErrorCorrectionLevel); + Assert.Equal(61, qrCode.Size); + Assert.Equal(4, qrCode.Mask); + Assert.Equal(Modules2, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules3 = { + "XXXXXXX X X XXXX XXXXXXX", + "X X X X X X X", + "X XXX X X X X XXX X XXX X", + "X XXX X X X XX XX X XXX X", + "X XXX X X X X X XXX X", + "X X X XX X X X", + "XXXXXXX X X X X X XXXXXXX", + " X X X ", + " X X XXXX XX XX XXX XX X", + "XXX X XX X X X XXX ", + " X X XXXX XXXX XXX ", + " X X XX XX X XXXXXXXX", + " XXXXX X XX X XX ", + " X XX X X XX XX XX ", + "X XX XX XXX X XXX XX ", + " X X X XX XXX XX XX X ", + "XX X X X XXXX XXXXX X X", + " XX XX XX XX XX", + "XXXXXXX XXX X XX X X XX ", + "X X X XX XXXX X XX", + "X XXX X XXXX X XXXXX XX", + "X XXX X X XX XX XXXX ", + "X XXX X XXX XX X X", + "X X X X X X X XX ", + "XXXXXXX X X X X XXX" + }; + + private const string Text3 = "😀 € éñ 💩"; + + [Fact] + private void TestCode3() + { + var qrCode = EncodeBinary(Encoding.UTF8.GetBytes(Text3), Ecc.Low); + Assert.Same(Ecc.Quartile, qrCode.ErrorCorrectionLevel); + Assert.Equal(25, qrCode.Size); + Assert.Equal(7, qrCode.Mask); + Assert.Equal(Modules3, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules4 = { + "XXXXXXX XX X X XXX XX XXX XXX X X XX XXXXXXX", + "X X XXX X XXX X X XX XXX X X X X X X", + "X XXX X XX XX XX XX X XXX XXXX X XXXXXX X XXX X XXX X", + "X XXX X XX X X XX X XX X X X X X X XXX X", + "X XXX X XXX X X XX XXXXXX XX X X X X X X XXX X", + "X X X XX XXX X XX X XX XX XXXXXXXXXX X X", + "XXXXXXX X X X X X X X X X X X X X X X X X X X X X XXXXXXX", + " X X X X X X X X XXXX XX X X XXX ", + " XXXX XXX XX XXXXX XXXXXX XXX X X XX X ", + " X X X X XX XXX X X X X X XX X", + "XXXXXXXXXXX XXXX X X X XX X X XXX XXX XX XXX XX ", + "X X X X X X XX XXX XX X XX XXX X X XX XX ", + " X X X XXX XX XXXXXXXXXXX X XXXXX XXX X X XXX", + " XX XX XXX X X XX XXX XXXX XX X XX XXX XXX", + " XX X XX X X X X X XX XXX XXX XXXX X X XX", + " X X XX X XXXXX XXXXX XXX XX X XX XXX XXXXX X", + "X XXX XXXX XXX X XXXXXX XX XXX XXX XX XXXX ", + " X X X X XX X XX X X X X X X X XX X", + " XX X XXXXX X X X X X XXX XXX XXX XXX XXX XX ", + " XX X X X XXX XXXXXX X X XX XXX X XX XX ", + "X XX X XX X X XXXXXXXXX XXXXX XXX XXX XXX", + "XXXX X XX XX XX X X X X XXX XX X XX XX XXX XX X ", + " XX XXXX XXX X XXX XX XXX XXX XXXX X XX ", + " X XX X XX XX X X XXXXXXX XX XX X XXX XX X XX ", + "XXXXXXXXXX X X X X X XX XX XXX XXXXX XX XXX", + " XX XX X XXXX X XX XX X XX X X X X X X XX X", + " XX XXXXXXX XX XX X XXXXX X XXX XX XXXXXXX X ", + " XX X XX X XXXX X XXXXXX X X X X X X XX X ", + "X X X XX XXXXX X X XXXX X X XX X X XXX X XX XX", + "XXXXX X X X X X XX XX XXX X X XXXX XX X", + " XX XXXXXXX X XX X X XXXXXXX X XXX XXX XXXX XXXXXXX ", + " XXX X XX XXX XX XXX XXX X XXXXX X XXX XX XX ", + "XXX XX XXXXX X X XX XX XXX XX XXXXXXX XX XXX", + " X X XX X XXXX X X X X X X X XXX XXX", + " XXX XXX XX X XXXX XX X X XX X XX X XX X XX", + " X XX XX XXXXXXXX XXXX XX XX X X X X XXX X X", + "X X X XXXXX X X XXXXXXXX XX X X X XXXXXX XX ", + "XXX XX X X X X X XX XX XXXX X X XXX X X X", + " XX XX XX X X X XXX XXX X XX XXX XXXX XXX ", + "X XXX XX XX XXX XX XX XXX X XXX X XXX XX XX ", + " X X X XXX X X XXXX X XX XXXX XXXXXXX XXX X XXX", + " XX X X X X X XX X XXX X X XX X X XXXX XXX XXX", + " XX XXX XXX XXXX X X X X XX X X XX XXXX XX", + "X XX XX X XX XXXXXXX XX XX X XXX X XX XXX X X", + " X XX XX XXXXX XX X XX X XX X X XX XX XXX XX ", + " X XXX X XX XX XXX X XXX X X X X X X X X", + "X X XXX XXX XX XXX X XXX X XX XXX XXX XXX ", + "XXXXX XXXXX XXXXX X XXX XXXXXX XXX XX XX ", + " X XXXX XX X X XXXXXXXX XXXXX XXXX XXXXXXX XXX", + " XX X X XXX X X X X XX XX X X XXX", + "XXXXXXX XXXX XXX XX X X XXXX X XXXX X X X XX", + "X X X X X XXXXX XX XXXX X X X XX XX X XX X", + "X XXX X X XXX X X X X XXXXXXXXX X X XXX XXXXXXXX ", + "X XXX X XX XXXXX X XXXXX XX X X X XX X XXX", + "X XXX X XXX X X X XX XXXXXXX XXX XX XXX XX XXX ", + "X X XXXXX X XXXXX X XX X XX X XX X XX XX X", + "XXXXXXX XX X X XX X XX X XXXXXX XX X XXXX XXXX" + }; + + private const string Text4 = "ABCD22340"; + + [Fact] + private void TestCode4() + { + var segments = QrSegment.MakeSegments(Text4); + var qrCode = EncodeSegments(segments, Ecc.High, 10, 40, 4, false); + Assert.Same(Ecc.High, qrCode.ErrorCorrectionLevel); + Assert.Equal(57, qrCode.Size); + Assert.Equal(4, qrCode.Mask); + Assert.Equal(Modules4, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules5 = { + "XXXXXXX X X XXXXXXX", + "X X X X XXX X X X", + "X XXX X X XXX X XXX X", + "X XXX X XX XXX X X XXX X", + "X XXX X X XXXX X XXX X", + "X X X X X X X", + "XXXXXXX X X X X X XXXXXXX", + " X XXX X ", + "X XX XXX X X XX X X XX", + " XX X XXXX X XX XX ", + "XXX XX XX XXX X X ", + "X X X X X X X X X", + " X XX XX X X X X X X", + " X X XXXX X X XX XXX", + " XXXX X X X X X XX XXX X", + "X X XXXX XXX XXXXX X ", + " XX XXXXXX X XXXXXX X ", + " X X XX XX X X ", + "XXXXXXX XXXXX X X X X ", + "X X XXX X X X X XX ", + "X XXX X X XXX XXXXXXX XX", + "X XXX X XX XX X X X X", + "X XXX X XX XXX XXXX XX X ", + "X X X XXX XX ", + "XXXXXXX X X XX XXX X X" + }; + + private const string Text5 = "314159265358979323846264338327950288419716939937510"; + + [Fact] + private void TestCode5() + { + var qrCode = EncodeText(Text5, Ecc.Medium); + Assert.Same(Ecc.Medium, qrCode.ErrorCorrectionLevel); + Assert.Equal(25, qrCode.Size); + Assert.Equal(3, qrCode.Mask); + Assert.Equal(Modules5, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules6 = { + "XXXXXXX X XX XX XXXXXX XXXXXXX", + "X X XX XX XXX X XXX X X X", + "X XXX X X XXX X XXXXX X X XXX X", + "X XXX X X XXX X X X XX X XXX X", + "X XXX X X XX XXX X X X XXX X", + "X X X XXX XXXXXXX XX XXX X X", + "XXXXXXX X X X X X X X X X X X XXXXXXX", + " X X X X X X XX ", + " X XXXX XXXXX XX X X X X XXXXX ", + " XX XX X XX XX XX X ", + " XXXXX XXXX X X XX X XX X X XX", + " X XXXXX XXX XX XXX XXX X X ", + "X XX XXXXXXX XX XX X XX XXX XX", + "XXX X X X XX XXX X XXX X X ", + " XX X XX X XXXXXXX X XXX XXX XXX XX", + "XX XX X X X XXX X X X XXXX X", + "X X XXXXXX X XXXXXX X XXXXX X X XXX", + " X XXX X X XXX X X X X X X", + " X XX XX X X XXX X XXXXX X ", + "X X XX X X X X XXXXXX", + " XXXXXXXXX X XXX X X X X XX X ", + "XX X X X X XXXX X X XXXXX ", + "XXX XXXX X X XX XXXXX X XXX X", + "XXX XX XXXX XXX XXXX X X XX XXXXXXX ", + "XXX X X X XXXX XXX XX XXX X XXX", + " XX XXX XXX X X X X XXXXX ", + "XX XX X XXX X X XX X XX X X", + " XX XXXX X XX X XXXX XXXX ", + "XXXXXXX X XXX XX X XXXXX XXXXXXXX ", + " X X X XXX XXXXX XXX XX ", + "XXXXXXX X XX X XXX X XX X X X", + "X X X X XX X X X X X XXX", + "X XXX X X X X XXX X XX XXXXXXX X", + "X XXX X XX X XXX X X X XXXX X ", + "X XXX X XXX X X X X X XX XX", + "X X XXXX X XX XX X X XX X", + "XXXXXXX XXX XX XXXX X XXXX XXX" + }; + + private const string Text6 = "DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/"; + + [Fact] + private void TestCode6() + { + var qrCode = EncodeText(Text6, Ecc.High); + Assert.Same(Ecc.High, qrCode.ErrorCorrectionLevel); + Assert.Equal(37, qrCode.Size); + Assert.Equal(1, qrCode.Mask); + Assert.Equal(Modules6, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules7 = { + "XXXXXXX XXXX X X X X XXXXXXX", + "X X X XXX XXXX X X", + "X XXX X X XX XX XXXXX X XXX X", + "X XXX X X X X X X X XXX X", + "X XXX X X XX XX XXX X XXX X", + "X X XX XX X X XX X X", + "XXXXXXX X X X X X X X X X XXXXXXX", + " X XXX XX XX XX ", + " XXXXXXX X X XX XX XX X", + " XXXX X X XXXXXX X XX X ", + "X XX X XX X X X X X XXX X", + "X XX X X XXXX XXXXXX X XX ", + " XXXX XX X XX XXX XXX XXXXX", + "XXX X X X XX XXX X X XX X ", + "XX XXXXX X X XXX X X X XXXX X", + "XX X XXXXX X X X X X X XXX", + " X X X XX X XXX ", + "XX XX X XXX XX X X XX X X XXX", + " XX XXX X XX XX X X X XX XX X", + "X X X X XXX XX X XX X XX", + " X XX X X X XX X X X XX XX", + "XX X XX XXXXXX XXXXXXX X X X", + "X X XXXXXX X XXX X X XX X XXX", + "X XXXX XX XXX XXX XX X XXX X XX", + "X XXX XXXX X XXXX XXXXXX X ", + " XXX X XXX X X X X", + "XXXXXXX XXX X X X X X X XX XX", + "X X X X X XX X X X X ", + "X XXX X XX X X X XXXXXXXXX XXX", + "X XXX X XX X XXXXX X X XX X X", + "X XXX X X XX X X XX X XXX ", + "X X X X X XX X X XX ", + "XXXXXXX XX XXX X XX XXX X X " + }; + + private const string Text7 = "こんにちwa、世界! αβγδ"; + + [Fact] + private void TestCode7() + { + var qrCode = EncodeText(Text7, Ecc.Quartile); + Assert.Same(Ecc.Quartile, qrCode.ErrorCorrectionLevel); + Assert.Equal(33, qrCode.Size); + Assert.Equal(2, qrCode.Mask); + Assert.Equal(Modules7, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules8 = { + "XXXXXXX X X XX X XXX X XX X X X X X XXXX X X XX X XX XXXXXX XX X XXX X XX X XX XXX XX X XX XXXXXXX", + "X X X XXX X XX X X XXXX X XXXX XX XX X X X XXXX XX XX XX X XXXX XX XXXXXX XX X X X", + "X XXX X XXX X X XX X X XXXXX X XXXXX X XX X X X XXXX XX XXX X XX X X X X XX X XX X XXX X", + "X XXX X XX XX X XXX X X XXX X XXX X X X X XXXXXXX XXXXXXXX XX X X XXXXX XX XXX XXXX X XXX X", + "X XXX X X XXX XXXX XXX XXXXX XXX X X XXXX X XX X XXXXXXXX X XX X XXXX XXXXXXX XX X XX XX X X XXX X", + "X X XX XX XX X XXXX X X XX X X X X XX XX X XXX XX XXX X X X XX X XXX XX XXX X X", + "XXXXXXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X XXXXXXX", + " X XXX X X X X X XX X XXX X XXX XX X X XX X X X XXX X X XXXX X X XXXXXXXX ", + " XXX X X XX X XX XX X X XXXXXX X X XXX X X XXXXX X XX X X X X XXXXXXXXX X XXX XX XXXX XXX XXX", + " XXXX XXX XXXXX XX X X X XX X X XX XXX XXXXXX XX XX X X XXX XXXX XXXXX X XXX X XX X X XX ", + "XX X XXX X X XX XXX X XX XXX X XXX XX X X X X XX XX X XXXX X XXXX X X XXXX X X XX XX", + " X XXX XXXXX X X XXX X XX X XX XXXX XX XX XX XX XXX X X XXX XX XX XXX X XX XXX X XXX X X X XXXX XXXXXXX X", + "X XXXXXX X XXX XX X XX XXXXXXX XXXXXXX XXXXX X X X X XX XX X XXXX XX X X X X XX X XX X X XXX XXXX ", + "XX XXX XX XXXX X XXXXX XX XXX XXX XX XX X X XX XX XXXX XX XXXX XX XXX XX X XX X XX XX X XX XX XX X ", + "XX XXXX X X XX XXXXX XXX X XXX XXXXX X XXX XXXXX X XXXXXX X XX X X XXX XXXX X X X XX X X X XX", + "X XX XXX X XX XX X XXXXXXX X XX X XX X XXXX XX X X XX XXXXX XX XX XX XXXXXX X XX X XXXXXXX XX", + "X X XX X XXX X XXXXXX X X XXXX X XXX XX X XX X XXX X XX X X XX XXX XXX X XXX X XX X XX X XXXX", + " X XX XXX XXXXXXXXXX X X XXXX XXXXX XXX XX X XX XX X XXX X X X XXXXXX XXX XX X XXXXX X X XXX X ", + "XX XXXX X XXXX X XXX XX X X X XX XXXX XXXX XXX XXX X X XXXX XXX XX XXXX X X XXXX X XXX X X XX", + " X XXX X XXXXX XXXX XX X XXXXX X XX XXXXX X XXXX XX XX XX XX X XXX XXXX X X XXXXXXXX X ", + " XX XX XXXX XXX XX X X X X X X X XX XX X X X XXXX XXXXX X X XX XXX X XX X XX XXXX X X X XX X", + "X X XX X XX X XX X X X XXXXX XXX X X XX XX X XX X XX X XXX X X X X XXX XXX X XXX X X X X X ", + " X X X XX X X XX XX X X X X X X XX X X XXXX X XX XX X X X X X XXXX XX X XXXX X X X XX XXXX", + "X X XX XX XXX XXXXXX XXXXX XXXXXXX X X X XX X XXXXX X X XXXX X X XXXX X X X XX X X XXX XX XX", + "X XX XXXXX XX XX XX XXXX XXXXX XXX XXX XX XX X X XX X XX XX X XX X XXX X XX XX X X XXXXX ", + "XX X X XX X X XX X X XXX X XX X XX XXXX XX XX X X X X XX X XXXX XX XX X XXX XX XX X XX X X ", + " X XXXX XXXXX XX XX X XXXX XXX XX XXX X X X X XXX X XXXXX XXX X XX XX XXXX X X XXXXXX X X XX", + " XX XX XXXXXXX X XX X XXXXX XX X X X XXXXXX XX X XXX XX XXXX X XXXXX X XXXX X XXX ", + " X XXXXX X XXXX X X X XXXXX XX XXX X X XXX X XXXXXXXXX XX X X XX X XXXX XXXXX X X X X X XXX XXXXX XXX", + " XX X X XXXXX X XXX XX X X XXX XX XX X X X X XXXXX XXX XX XX XX X XX XXXX X X XX XX ", + " X XX X X X X XX XXXX X X X X XXXX X X X X X X X X XXXX XX X XXX XX XX XX X XXXXX X X XXX XX X X X X X X X", + "XXX X X XX XX X X XX X X X X XX X X X X XXX X XXXXX XXXXX XX X X X XXXX X X XXXXXXXX X X", + " X XXXXXXX XXX XX XX X XXXXXXXXX XX XX X X XXX XXXXXXX XXX X X XXX X X X XXXXX X X XXX XX X XX XXXXXXXXX ", + "X XX X XXXXXXX XX X XXXXX X X X XXXX X X X XXXX XXXXX XX X X XXXXXX X XXXX X XXXXXX X X XXX X XX ", + "X XX XX X X XXX XX XX XXXX XX XX X XXX X XX X X XX XX X X X XX XX XX XX X X XXXX XXX XX XX", + " XXXX XXXXX XXXXXXXXX X X X XXXX X X XX XX XXXXX X X X XXX X XXX XXXXXX X XXX XXXX X ", + "XX X XXXX X XXXX XX XXX X XX XXXXX X X XXX X X X X X X XX XXXX XX X XXXXXX X X X X X XX X XX ", + "X XXXX X X X XXX X X X X XX XXXXXX X X X XX X X X XXXXX X X X XX X XXXXXX X X X X XXX XXX ", + "XXX X XX XX X X XX XXXXXXX X X XXXXX X XXXXX X X X X XXXX X XX X X XX X XXXX X X X X XX XX", + "XX X XXXX X X XX XX X XXX XX X XX XXXXX X X X X XXXX XXXX XXX XXX X X X XXXX XX XXXX XXX XX X ", + "X X XX X XXX X XX X X XXX XXXXXXX X XXXXXX XX XX XX XX X X XXXXX X X X XX X XX X X X XXXXX X X", + "XX XXX XX X X X X X XX X X XX XXXX X XXX XXXXXXX XX XX XXXX XX X XXXXXX X XX XXX X X X XX X X ", + "XX X XXX X X X X XXXXX X XX XX X X XX X X XX XX XX X X X X XX X XX X X XXX X X X X X", + " X XXX XX X X X X X X X X X XX X X X X XXXXXX XX XX XXXXX X XXX X XX X XXXX X XX X XXX X ", + "XX XXXXXXX X XXX X X X X XX XX XX XXXXX XXX XXX XX X XX XX X X XX X XXXX XXX XX X X X X XXXXX XXXX", + "X X X XXX X XX XX X X XXXX X X X X XXX X XXXXX X XXX X X X XX X X X X XXX X X X XXX XX XX ", + " X X X XX XX XXXX XXXXXXX XXXX XX X XX X XXX X XXX X XXXX X X XXXXXX XXXXX X X X XXXX XX X X XXX X", + " XX X XX X X X X XXXX XXXX XXX XX XXXX XXX XXX XX X X XX XXX XX X XX X XXX XX X XXXX XX XXX ", + "X X XXXXX XX XX XX X XX XXXXX X X XX X XXXX XX XXXXX X X X XXX XX X X X XX X XXXX XXX X", + "X XXXX X X XXXXX X X XX XXXX X XXXX X X XXXXXX XX XX XX XX X X X XX XXXX X X XX X XX X X ", + " XX X X XXX XX XXXX X X XXX XX X XXX X X XXX XXXX X XXXX XXX X X X X XX XXX X X XXXXX", + "XXXX XX XX XXX X X XX X XX XX XX X X XX XXXXX XX XX X X XXXXX XX X X XXX X X X X XXX XX X", + " X XX X XXX X XX XX XX XX XXXX XXXX XXXX XX X XXX XXXX XXX XXX X X X X X X XX X XXXXX XX XXX XXXX", + "XX XXX XX X X X XX X X X XX XXX X X X XXXX XX XX X X X XX XXXX X X XX ", + " X XXXXXX X X X XXX XX X XXX X XX XXX XXX X X X X XXX X X XX X X XXX X X X X X XX", + " XX XX XXX X XX XXX X X X XXXX X XXXX XX XXXX X X X XXXXXXX XXX XXXX XXX X XXX X XXXX X X XXXXXXXX XX X X", + "X XXXXXX XX X XXXXX X XXXXXXXX XX XXXX X XXXXXXX X XX X XXXXX X XXX X XX XX XXXXXXX ", + " X XX XXX XX X X X X XXX X X X XX XXXXXXX X X XX XXX XX XX X X X XX X XX X XX X X X X ", + " XX X X XXX X X X XXXXXXXX X X XX XXXXXXX XXXXX XX X XXXXXXX XX X X X X XX X XXX X XX X XX X X X XXXXX", + " XX X X X XX X X X XXX XX XX X X X X X X XXX XX XX XXX XXXX X XXXX X X XXXX XXX X X ", + " XXXXXXXX X XXXXXX XX XXXXXX XXX XX XX X XX XXXXXXXXX XX X XXXXXX X XXXXXXXXX XX XX XX XX XXXXXXX X", + "X X XXXXX XXXXXX XX XX X X XXXXXX X X XX XXXXX X XX XXX XXX XX X X X XX XXX XXX XX X X XX X XX X ", + " X X X XX X XXX X XXXXXX X X X XXXX XX X X XX XXX X X X X X XXX X XX X X X XXXX X X XX X XX", + "XXXXX XX XXX X X X X X X X X X X XX XXX XXXXXXXXX XXXX X X X XXX XXXX X X XXXX XX X XXXXX XXX X ", + "XX XX XX XX X X X XXX XXX X X X XX XX X X X X XXX X XXX XX X XXXXXX XXX X XXXX XXX ", + "XX X X XXX XXX XXXX X XXXXX XX X XXX XXXX XXXX XX X X X XX XXX X XXX X XXX XXX XX XX XXXXXX XX X ", + " X XXXXX XX XX X X XXXXXX XX XX XX XX X XX X X X X XX XX XX X XX X XX X X X XX X X X X X XX XX", + "XXX XX XX X XXX X XXX XXXX XX XXX XXX XXXX X X XXXXXX X X X XXXXX X XXXX X X XXXXX X XXXX XX X XX XX", + " XXX X XX XX X XXX X XXX XXXX XXX X XXX X X XXX X XX XXXX XXX XX XXXX XX XX XX X X XXX X XX X", + " X X XX X XXXX X X XX X X X X X X XXX XX X X X XXXXX X X XXX X X X XX XXXX X XXX XXX XXX ", + " XX X XXXX XXXXXXX X X X XX X XX XX X XXX X XXXXX XXX XXX XX XX XXX X XXXX X XXX X X X X XX", + " X XX X XXX X XXXX XX X X XXXX X XX X XXXXX X XXX XXX XX XXXXX X XXX X X X XXXX X X X X", + " X X XX X X XX X X X X XXX X X XX X X X XXXXXX X XXX XX XX XXXX XXXX X XX XXXX XXX X XXXX", + "XX X X XXX X X X XXX XX X X X XXXXXX X X X X X X XXXXX XX X X XX X X X X X X X XXXX XX ", + " X XX XX X X X X XXX XXXXX XXX X X XX X X X XXXXX X X X XXXXX X XXX X XXX XXXX X X XXX", + "X X XXX XX X XX XX X XXX X XXX X XXXXXX X XXXXXXX X XXXXX XX XX X X XXXX X XXXX XXX X X XX", + " XXXX X X X X XX XXX XXX X XX XXXX X X X X XXXX X X XX X X X XXXXX X XXX XXX", + " X X X XXXXXXX XX XXX XX XXX X XXXX XXX X X XXX X XXX XXX X X XXXX XX XXX X X X XX X XX XX X XXX X ", + " XXX X XX X XXX XX X X X X X XX XXXXXX XXXXXX X X XX X XX XXX X XX X X XXXX X X X X X X", + "XX XXX X X X X X X X X XXXX XXX X X X X X XX X XXXX X XX XX XX XXXX XX X X X XXXX XX XX X ", + "XXXXX X X XXX X XXX XXX X XX XXXXX X X X XXXXXX XXX XXX XXX XX XXX XXX XX XX X X X XX X X XX XX X X", + "XX X XX X XX X XXXXX X X XXXX XXXXX X XXXXXXXX X XXXXXXX XX XXXX XX XX XXXX X X X XXX XX X ", + " X XX XX X XXXXX X X XXX XXXX XXX XX XX X X X X XXXX XX X XX X X X X X X X X X", + " XX XX XX XXX XX X X X X XX XX XX XXX XXX X XXXX X X X XXX XXX XXX X XX XXXXXX XXX X X XXXX XX X X XX", + "X XXXXXXX X XXX X X XXXXX X X X XX X X X XXXXXXXXXXXX X X X X XXXX X XXXXXX X X X XX XX XXXXXXXXX", + "X XXX X X X XXXX XX XX XXX XX XX XXX XX X X X X X X XX XXXX X X X XX XX X X X XX X X XX ", + " XX X XX XXXXX X XXXX XX X X XX X X X X X X X X X X XXXXXXXXXXX XX X X X X X X XXXX X X X X XX X", + "X X X XX XX X XX X X X X X X XX XX XX XXX XX XXX XX XXX X XXXX X X XXXXXX X XX X XXX X X", + "X XXXXXXX XX XX X X X XXXXX XX XXX X X XXXX XXXXXXXXXXX X XXX XXXXX X XXXXXX X X X X XXX XX XXX XXXXX XXX", + " X XXX X X XX XXX X X XX XXX XX XX X X X XXX X XXX XXX XX XX X XX XX X XX XX ", + " XXX XXXXX XX X XXXX X XX X XX XX X X XXXX X X XXX X XXXX X XX XXX X X XXX X XXX X X X XX X XX", + "X X XXXXXX X XX XX XX X XX XXXXXXX X X X X X XX XX XXX XXXX XX XXXX X XXX X X XXXX X X XX X", + " X X XXX X XXX XXX XXXXXX X XX XXX XXX XXXXXX X XX X X XX XXX X XXX X XX XX X X XX X XX X XX X XXX X", + " X X XX XX X XX X X X XXXXXX X X X XX XXX XX XXXXX XX X XX X X XXX XX X X XXX X X XXX ", + "XXX XX X X XX X X X X XX XX XX XX XXX X XX X X XXXX X XX XXXXX X XX XXXXX X XXXXXX X XXXXX XX", + "XXX XX XX XXX X XX XX XX XX XXXX XXXXXXXXXX XX X X XX XX XX X XX X XX X X X XXXX XX X XX XXX XX X", + " X XXX X X X X X X X X X X X XX XXX XX X XXXX XX XXX X XX XXX X XXXXX X X XX XXX X ", + " X XXXX XXX X X XX X XXX X XX XXX XX XX XXXX X XX X XXX XX XXX X XXX XX XX X XX XXX X ", + "XXX XX XX XX XXXX X XX X XXX X X X X X XXXX XX XXXX X XX XXX XX X X X X X X XX X XX X X XXXX XX", + " X XXX XX XX X X XXXX X X XX X X XX X XXX X XXXX X XX X XX X XX X X X X X XXXXXX X XX ", + "XX XXXXXXX XX X XXXXXXX X XXXXX X XXX X X XX X XXX X XX XX XXX X X X X X XX X XXXX XXXX ", + "X X XX XXXXX X XX X X XXXXXXXX XXXXX X XX X XXX XXXXXXX X X X XXX X X X X XX X XX ", + "XXX X X X X XX X XXX XXX X X XX X XXX X X X XX X XX XX X XXX XXX XXX X XXXXXX X X XX X X", + " XXX XX XXXX X XX XXX X X XXX X X XX X XXXXXXX XX X XX X X X X XXXXX X X XXX XX X XXXXXX X X ", + "XX XXXXX XX XX X X XX X X X X XX XX XX X XXX X XX XX X X X XXX XXXXX XXXXX X XXX XXXX X XX X XXX X", + " X X XX X X XXXX XX XXXX X XX X XX X X X XX X XX XXX X X X X XX XXXXX X XX XXXX X X X XX ", + "XXX XX X XX XXX X XX X X XX X X X XXX XX XXXXX XX XX X X XXX X XX XX XX X X XXXXXX XXXXXX XX", + "X XXXX XXXXXX XX X XX X X X X X X XXXXXXXX X XXXX XXX X X XXXX XX XXX X XX X X X XXXX XX X XX X X XX X X XX", + "X X X X XXX X XX XX X X XX XXX XX X XXX XX XXX XX XXXXX X X XX XX XXXX X X X XX X XXX ", + "X X X X X X X XXX X X XXXX X X X X XX X XX XXXX XXXXXXXXX X XXX X X X X X X XXX XX ", + "XX X XXXXXX XXX X X XX XXXXXXX X XX XX X XXX X XX X XXXX XXX XX X X X X X XXXX X X X XXX XXX", + "X XXXX XX XX X XX XX X XXXX X XX X XX X X XX XXX XX XX XX X X XX X XX X X XXX X ", + " XXX X X X X XXX XXXXXX X X XXXXX XXXX XXXXXXXX XX XX X XX X XXXXXX X XX X XX X XXX XX XXXXXX X X", + " X XXXX XXXXXXXX XXXX XXXXXXXXXXX XXX X XX X XXX X XXX XX XX XXX X XX X X X XX XXX XX XX XXX ", + "XXXXXXX XX XX XXX X X X X XX XX XX X X XXX XX X X X X XX XXXXX X X X XXXXX X XXXXXXX X X X XX XX", + "X X X X X X XXX XX X X X X X XXX XX X X X X X XX X XX X XX X XXXX XX X XX XXX X X XX", + "X XXX X XX X X XX X X X XXXXXX X XX X XX XX XXXXXXXX X X X XXXXXXXX XX X X XXXXXXX ", + "X XXX X XX X XX XX XX X X XXX X XX X XXX XX X X XX XX XX XXXX XX X X XX XX XX X XX X XX XX XX X X", + "X XXX X XXXXXXX XXXXXX X XXX X X X XXX XXXXX X XXXXXX X XXXXXX X X XX X X XXXX X XXX X X XX X X", + "X X XX X XXX X X XX X X XXX XXX X X XXX XXXXXX X X X X X XX X X XX XXXXXXX XXXX X X XXXXXX XX X", + "XXXXXXX X X X X X X XX XXXX X X X XX X X X X XXXXX X X X X X X XX XX XXX XXXXX XXXX" + }; + + private const string Text8 = "Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice 'without pictures or conversations?' So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her."; + + [Fact] + private void TestCode8() + { + var qrCode = EncodeText(Text8, Ecc.High); + Assert.Same(Ecc.High, qrCode.ErrorCorrectionLevel); + Assert.Equal(121, qrCode.Size); + Assert.Equal(2, qrCode.Mask); + Assert.Equal(Modules8, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules9 = { + "XXXXXXX XXXXXX XXX XXXXXXX", + "X X XXXX X XXXXX X X", + "X XXX X X X X X X X XXX X", + "X XXX X X XXX X XX XX X XXX X", + "X XXX X XX X XXX X XXX X", + "X X X X XXX X X X X", + "XXXXXXX X X X X X X X XXXXXXX", + " X XX X XXX ", + " X XXXXX XX XXXXXXXX XXXXX ", + " X XX XX X XXX XX X XX", + " X XXX XX X XX X XX X X X X", + " X XX XXX X X XX X XX ", + " X XX X X X XXXXX XX X ", + "X XXX XXX X X XX X X X", + "XXXXX X X XX X XX XX X", + "XX XXX X X X X XXX XX X X X", + " XX X X X XX X X XX XX ", + " XX X X X XX X X XX X X", + "XX XXX X X X XX X X", + " X XXX XXX XXXX XX X", + "XXXX XX X X X XXXXX X", + " X XXX X XXX XX X", + "XXXXXXX XXX X XX X X XXX X", + "X X X X X X XX XX X", + "X XXX X XX XXXXXXX ", + "X XXX X X X X XX XXX X ", + "X XXX X XX X XX XX X X XXX", + "X X XX X X X X X ", + "XXXXXXX XXXXXXX X XXXX X" + }; + + private const string Text9 = "https://www.nayuki.io/"; + + [Fact] + private void TestCode9() + { + var segments = QrSegment.MakeSegments(Text9); + var qrCode = EncodeSegments(segments, Ecc.High, 1, 40, -1, true); + Assert.Same(Ecc.High, qrCode.ErrorCorrectionLevel); + Assert.Equal(29, qrCode.Size); + Assert.Equal(1, qrCode.Mask); + Assert.Equal(Modules9, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules10 = { + "XXXXXXX X XX X X XXXXXXX", + "X X XX XXXX XXX X X", + "X XXX X X XXX X X X XXX X", + "X XXX X XXX XXXXXXX X XXX X", + "X XXX X X X X X X X X XXX X", + "X X X X XX X X", + "XXXXXXX X X X X X X X XXXXXXX", + " XX X XXX ", + " XX XXX X X XX X ", + "XX X X XXXX X XX X X", + "XXXX XXXX XXX XX X XXX ", + " X XXX X X X", + " X X XX XXX X XXXX X XXXX", + "X XXX X X X XX X XX XX X", + " X XXX XX X X X XXX XXX XX", + " X XX X XXX XXXXXXX XX XX", + "X XX XXXX XXXXX X XX XXX XX", + " X X XXX X XX X XX ", + "X X XX XXXX XXX X XX X ", + " X XXX X X X X XXXXXX X", + " X XXXX X XX X XXXXX XXX", + " XX X X XX XX XX", + "XXXXXXX X XXXXXX XX X X XX ", + "X X X XXX X X ", + "X XXX X XX XXX XXXXXXX X", + "X XXX X XX X X XXXX ", + "X XXX X X XX XX X X X X", + "X X X X XX XXX XX X ", + "XXXXXXX X X X XXXX X X " + }; + + private const string Text10 = "https://www.nayuki.io/"; + + [Fact] + private void TestCode10() + { + var segments = QrSegment.MakeSegments(Text10); + var qrCode = EncodeSegments(segments, Ecc.High, 1, 40, 3, true); + Assert.Same(Ecc.High, qrCode.ErrorCorrectionLevel); + Assert.Equal(29, qrCode.Size); + Assert.Equal(3, qrCode.Mask); + Assert.Equal(Modules10, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules11 = { + "XXXXXXX X XX X XX XXX X XXX X XXXXXXX", + "X X X XXX XX XXXX XX X X XX X XXX X X", + "X XXX X X X XX X XXXX XXXXXXXX X XX X XXX X", + "X XXX X X XXX XXXX XX X XXX XX XXXX X X XXX X", + "X XXX X X XXX XX XXXXXXX XX XXXX X X XXX X", + "X X XXXXX X XX XXXXXXXXX XX X X", + "XXXXXXX X X X X X X X X X X X X X X X X X XXXXXXX", + " XXXXX XX X XXX XXXXX XX ", + "X X X X XX XX XXXXXX X XXX XX X X ", + " XXX X XX XX XXX X XXX XX XXXX XXXXX X XX", + "XXXX XX X XX X X X X X XXX XXX XXX XXX ", + " X XX X XX XX XXX XXXX XX XXXX X XXX X ", + "X XXX X X X XXXX XXX XXX XXX X ", + " XX XX X X XX XXX X XX XXX X X X X", + " X X XX X XX XX X XX XX XX XX ", + "X X XXXXXX XX X XX XX XXXX X XX XX X X X", + "XXXXXXXXXXXXXX X XX X X X X X X XX", + "X X X X XXXX XXXXX XX XXX XX X X X XX", + "XXX X XXXXX X X XX XX X XX X XXX ", + " X X X XXX X XXX X XXXXX XXX X X X XXX X", + "XX XX X X XX XX X X XX", + " X XXX X XX X XX XXXX XX X XXX XXX XXXXX", + " X XXXXXX XX XX XXXXXXXXX XX XXXX XXXXX ", + "XXXXX X X XX XX X XX XX XXX XXXXXX X X ", + "XXX X X X XX XXXX XXX X X X XX XX XX X X XX", + " XXX X XXX X XXX X XX XXX X XX X X X ", + "XXX XXXXX XX XX XXX XXXXXX X XX XXXXXXXX ", + "X X X X XX XXX XXX X XXXXX XXX X XXX X", + " XXXX XX X XX X XXXX X X XXXX X XXX XX XX", + " X XXXX XXXX X X X XXX XXX XXXX X X XX", + "X XXX X X XXX XXXX XX X X XX X XX X X X ", + " XXXXX XX XX XXXX X XX X XX XX X XXXX ", + " XXXX XXXX X X X X X X XX X XX X XX XXX ", + "XX X X X X X XXXXXXX XXXX XXX XXXX X X X X", + " X XXX X XX X XXX X X XXX X XX XXXX ", + " X XX XX XX XX XXXX XXX XXXXXXX X XX XX ", + " X X X X XX XX X X X XXX X X XX X X ", + " X X X X XXX XXXX XX XX X X X XX XXXXXX ", + " X XXXXXX X X XXXX X X XXX ", + " XXX X XX X X XXXX X X X XXXX X X X ", + "XXX XXX X X XXX XXXXXXX X X XX XXXXXXX XX", + " X X X XX X X XX X X XX X X X X", + "XXXXXXX X XX XXXXXX XX X XXX X XX X X X XX", + "X X XX X XX X X X X X X X XX XXXX ", + "X XXX X XX X X XX X XXXXX XXX X XXXXXXX X ", + "X XXX X X XX XX X X XXX XXX XX XXXXXX XXX ", + "X XXX X XXX XX XX XXX XXX XX X XX XX XX ", + "X X XXXXX XX XX XXX XX X XXX X XXX ", + "XXXXXXX X X X X X XXXX XX XX X X XXX XX" + }; + + private const string Text11 = "維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫"; + + [Fact] + private void TestCode11() + { + var segments = QrSegment.MakeSegments(Text11); + var qrCode = EncodeSegments(segments, Ecc.Medium, 1, 40, 0, true); + Assert.Same(Ecc.Medium, qrCode.ErrorCorrectionLevel); + Assert.Equal(49, qrCode.Size); + Assert.Equal(0, qrCode.Mask); + Assert.Equal(Modules11, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules12 = { + "XXXXXXX XX XX X XXX XXX XX XXX X X XXXXXXX", + "X X XX XXX X XXX X XX XXXXXX X X", + "X XXX X XX XX X XX X X X XX XX X XXX X", + "X XXX X X X XX XX X XXX X X X XXX X", + "X XXX X X X XX XX XXXXXX XXX X X XXX X", + "X X XXX X XXXX X X X X X X XX X X X", + "XXXXXXX X X X X X X X X X X X X X X X X X XXXXXXX", + " X XX X XXX XX X XX X X XX ", + "X X XX XX XX X X XXXXXX X X XX X X X", + " X X XXX XX XXXX X X XX X XXX X XXXX X", + "X X XXXX XX X XXXXXX XX X XX XX X X ", + " XX XX XX X X XX XX X XX XXX X XXX ", + "XX X X X X XX X X XX XXX XXXX X X X X ", + " X XX X XXXX X X XXXX X XXX XX XX XXXXXXXXX", + " XXXXXX XX XX X X XXXX X XX X X XX XX X ", + "XX X X XX X X XXXXX XX X XX XX XXXXXXX", + "X X X X X X X XXXX XXX X XXX XXX XXX X X X", + "XXXXXX XXXX X XX X XXX XX XXX XXX XXX XXX X X", + "X XXXXX X XXXXXXX X XX X X X X XX XXXX XX X ", + " X X XX XX X XX X XXX XXXX XX XXX", + "X XXXX X X X X X X X X X XX XX X XXX X X", + " X XXX XX XXX X XX X X XX XXX XXX X X", + " XXXXXXXX XX XXX X XXXXXX X XX X XXXXXXXX X ", + "X X X XXXX XXX X X X X X XXX XX X ", + "X XXX X XXX X XX XX X X XX XXX XXX X X XX X", + " XX X XX XX XXXXX XXX X X X XXX XX ", + "X XXXXXXXX XXX X XXXXXX X X X XXXXXXX X ", + "XX X X XX XX XX X X X X X XX XXX", + " X XXX XXXX X X XXXXXX X XXXX X XX X X", + " X XXX X XX X XX X XXX X X X XXXXXX X", + "XXX XXXXXXX XXX X XXXX X X XX XXXX X ", + " X X XX XX XX X XX X XX XX X XX X ", + " X X XX X XXXXXX XXXXXXXX XX X XX X ", + "X X XXX X X X X XX X X X XXXXXXXXX", + " X XX XXXXX X XXXXX X X X XX X X XX ", + " XXXX XX XX X XX XX X X X X X XXX XXX ", + " XXXXXXX XXX X XXX X X XXXX X X ", + " XXXXX X XXXX XX X XXX XXXXXX XXXX XX X X ", + " X XX X XXXX X XXXX X X X X X X X X X X ", + " XXX XX X X X X XXXXXXXXX X X XXX ", + "XXX X XXXXXX X XX XXXXXX X XXX XXXXXX X", + " XXXX X XXXXX X XXXXXX X XX XXXXX", + "XXXXXXX X XXX X X X X X XX X XX XX X XX X", + "X X XX XX XXXX XXXX XXXXXXX X X X ", + "X XXX X XX XXXXXXXXX XX XXXX X XXXXX ", + "X XXX X XXX XXX X XX XX XXX X X X X ", + "X XXX X X XXX XX X X X XX X XX XX XX ", + "X X XX X XX XXX X XX X XXXX XX XXX X ", + "XXXXXXX XXXX XXX X X XX XX XX XXX X X" + }; + + private const string Text12 = "維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫"; + + [Fact] + private void TestCode12() + { + var segments = QrSegment.MakeSegments(Text12); + var qrCode = EncodeSegments(segments, Ecc.Medium, 1, 40, 1, true); + Assert.Same(Ecc.Medium, qrCode.ErrorCorrectionLevel); + Assert.Equal(49, qrCode.Size); + Assert.Equal(1, qrCode.Mask); + Assert.Equal(Modules12, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules13 = { + "XXXXXXX X XX X XXX XXX XX XXX X X XXXXXXX", + "X X XXX XX XXX XX X XX X XXXX X X", + "X XXX X XXXX XXX X XX XX X XXX X XX X XXX X", + "X XXX X X X X XXX X X X X X X XXX X", + "X XXX X XXXXXX XXXXXXXXX X X X XXX X", + "X X XX XXXXXX X X X X XXX X X X", + "XXXXXXX X X X X X X X X X X X X X X X X X XXXXXXX", + " XX X X XX XX X XXX X X X XX ", + "X X XX X X XXXXXXX X XX X X XXXX XXX ", + "XXX X X XX X XXXX X XX X X XX ", + "XX XXX XXXX X XX X X X X XX X X X X ", + "X XX XXXXX X X XXX X XXXXXX XXXX", + "XX X X X X XX X X XX XXX XXXX X X X X ", + "XX XX X XX X X XXXXXX X X X X XX XXX XX XXXXX ", + " X XXX XXX X XXX X X X XX X X ", + " XXXX XXX X X X XX X XX X XX X X X ", + "XX XXX XXXXX XXXX X X X XX XXX XXXXX", + " XXXXX XXXX XX XXX XXX XX XXXXXXX X X X ", + "X XXXXX X XXXXXXX X XX X X X X XX XXXX XX X ", + "X X XX XX X X XXX X XX XX X XXX XX ", + "XXX X XXX XXXX XXX XXXXX X XXXX X XXXXX", + "X X X XX XX X X XX XXX XXXX X X ", + " XXXXXX XXX X XX XXXXXXX X XXXXX XXXXXXXX ", + " X X XXXXX XXX X X X X XXX XX XX", + "X XXX X XXX X XX XX X X XX XXX XXX X X XX X", + "XXX X XX X XXXX XXX X XX XXX XX X", + "XX XXXXXXXXXX XXXXX XXXXXXX XXXX X XXXXXX X ", + " XX X XX XXXX XX XX X XXX X X XX X ", + " X XXXX XXXXXX X X XXXXX X X XXX XXXXX", + "XX XXX X XXX X XXX X X X X X X XX XXX ", + "XXX XXXXXXX XXX X XXXX X X XX XXXX X ", + "X X X XX X XX X XX XX X XXXX X X", + " X XX X XX X XXX X X X XXX X X XXX X X ", + " X X XXX XX XXX X XX X X X X X X ", + " XXXX XX X XXX XXXXXX X XXXXXXX X X XX X ", + "XXXXX XX X X X XX X X X X XXXX XXX X", + " XXXXXXX XXX X XXX X X XXXX X X ", + "XXXXXX X X XX XXX X XX XX XXX X XX X X X X", + " X XXX X XXX X X XXX XXXX X X X X ", + " XXX X XXXX XXXXX X X X X X XXX X XX XX", + "XXX XX X X X X XXXXXXXX X XXX XXXXXXXXX", + " XXXXX X XXXX X X XXXX XX XX XXXX ", + "XXXXXXX XXX X X X X X XX X XX XX X XX X", + "X X XXX XXXXXXX XXXXXXXXXX X X X X X", + "X XXX X XX XX XXXXXXXX XX X X XXXXX XX ", + "X XXX X X X X XX XX XX XX X XXX X", + "X XXX X XXX X X X XXX XXXX XXX X ", + "X X XX XX XX XXXX XXX XX X X X X", + "XXXXXXX XXXX XXX X X XX XX XX XXX X X" + }; + + private const string Text13 = "維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫"; + + [Fact] + private void TestCode13() + { + var segments = QrSegment.MakeSegments(Text13); + var qrCode = EncodeSegments(segments, Ecc.Medium, 1, 40, 5, true); + Assert.Same(Ecc.Medium, qrCode.ErrorCorrectionLevel); + Assert.Equal(49, qrCode.Size); + Assert.Equal(5, qrCode.Mask); + Assert.Equal(Modules13, TestHelper.ToStringArray(qrCode)); + } + + private static readonly string[] Modules14 = { + "XXXXXXX X XX X XX XXX X XXX X XXXXXXX", + "X X XXXXXXXX X X X XXX X X XXXXX X X", + "X XXX X XX X XXXX X XXX XX XXX X XX X XXX X", + "X XXX X X XXX XXXX XX X XXX XX XXXX X X XXX X", + "X XXX X XXX X XXXXXXXX XXXXXX X X XXX X", + "X X X X XX XX XXX XXX XX XXX XX X X", + "XXXXXXX X X X X X X X X X X X X X X X X X XXXXXXX", + " X X XX X X XX XX X X X ", + "X X XX X X X X X XXXXXX X XXXX X X X ", + " XXX X XX XX XXX X XXX XX XXXX XXXXX X XX", + "X XXXXXXX XXXX XXX X XX XXX XXX X X XXX XXX ", + " XXXXX XXXXX X XXXXX X XXXX X XXX XXX XX ", + "X XXX X X X XXXX XXX XXX XXX X ", + " X X X X XX X XX X XXX XX X XXX XXX X", + " XXXXXX X XXXX X XX X X X X XXXX X X ", + "X X XXXXXX XX X XX XX XXXX X XX XX X X X", + "X XX XX XX XX X X XX XX XX XX X XXX", + "X XX X XXX X XX X XXXXXX XX XX X X", + "XXX X XXXXX X X XX XX X XX X XXX ", + " XXX XXX XXX XX XXX XXXXX XX XXX XXX X", + "XXXXXXX X X XX X X X X X XXXX XX XX X X", + " X XXX X XX X XX XXXX XX X XXX XXX XXXXX", + " XX XXXXXXXXXX X X XXXXXXX X X XXX X XXXXX X ", + "XX XX X XX X X XXXX XX XXXXX XX X X X ", + "XXX X X X XX XXXX XXX X X X XX XX XX X X XX", + " XXXX X X X X X X X XXXXX XX X X X XX ", + "XX XXXXX X X XXXXXXXXX X XX X X XXXXXXX ", + "X X X X XX XXX XXX X XXXXX XXX X XXX X", + " XX X X XX XX XXX XXX X XXXXXX XXX", + " X XX X XXX X XXX XX XX XX XXXXX X XXXX X", + "X XXX X X XXX XXXX XX X X XX X XX X X X ", + " XX X XXXX X XX XX X XX X XXXX XX XXX X ", + " XXX X XXX X XX XX XXX X XX X XX X XXX ", + "XX X X X X X XXXXXXX XXXX XXX XXXX X X X X", + " X X X X XXXX XX XXX XX X X XX ", + " X X X X X X X XXXXX X XX XXX XX X X ", + " X X X X XX XX X X X XXX X X XX X X ", + " XX X XXXX XX X X XXX X XXXXX X XX X ", + " X XXX XXXX X XX XX X X XX X XX X X X X ", + " XXX X XX X X XXXX X X X XXXX X X X ", + "XXX X X XX X X XXXXXXXX XX X XXXXXXXXXXXX XXX", + " X XX XX X X XX XX X X X X XXX", + "XXXXXXX X XX XXXXXX XX X XXX X XX X X X XX", + "X X XX X XX X X XX XX XX XX X ", + "X XXX X X XXXXX XXXXX XXX XX XXXXXXXX ", + "X XXX X XX XX XX X X XXX XXX XX XXXXXX XXX ", + "X XXX X X X X X X X XXXXXX XX XXXXXXX X ", + "X X X XX X X XXXXXX X XXXX XX XXX ", + "XXXXXXX X X X X X XXXX XX XX X X XXX XX" + }; + + private const string Text14 = "維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫"; + + [Fact] + private void TestCode14() + { + var segments = QrSegment.MakeSegments(Text14); + var qrCode = EncodeSegments(segments, Ecc.Medium, 1, 40, 7, true); + Assert.Same(Ecc.Medium, qrCode.ErrorCorrectionLevel); + Assert.Equal(49, qrCode.Size); + Assert.Equal(7, qrCode.Mask); + Assert.Equal(Modules14, TestHelper.ToStringArray(qrCode)); + } + + + } +} diff --git a/dotnet/QrCodeGeneratorTest/QrSegmentEncodingTest.cs b/dotnet/QrCodeGeneratorTest/QrSegmentEncodingTest.cs new file mode 100644 index 0000000..5f33238 --- /dev/null +++ b/dotnet/QrCodeGeneratorTest/QrSegmentEncodingTest.cs @@ -0,0 +1,158 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Io.Nayuki.QrCodeGen.Test +{ + public class QrSegmentEncodingTest + { + + private static readonly string TextNumeric = "83930"; + + private static readonly int BitLengthNumeric = 17; + + private static readonly byte[] BitsNumeric = { 139, 243, 0 }; + + private static readonly string TextAlphanumeric = "$%*+-./ 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + private static readonly int BitLengthAlphanumeric = 242; + + private static readonly byte[] BitsAlphanumeric = { + 43, 63,240, 245, 223, 12, 64, 232, + 162, 147, 168, 116,228, 172, 40, 21, + 170, 67, 243, 58, 211, 175, 81, 76, + 109, 33, 107, 218, 193, 225, 2 + }; + + private static readonly string TextUtf8 = "😐ö€"; + + private static readonly int BitLengthUtf8 = 72; + + private static readonly byte[] BitsUtf8 = { 15, 249, 25, 9, 195, 109, 71, 65, 53 }; + + [Fact] + void NumericEncoding() + { + QrSegment segment = QrSegment.MakeNumeric(TextNumeric); + Assert.Equal(segment.EncodingMode, QrSegment.Mode.Numeric); + Assert.Equal(TextNumeric.Length, segment.NumChars); + + BitArray data = segment.GetData(); + Assert.Equal(BitLengthNumeric, data.Length); + + Assert.Equal(BitsNumeric, BitArrayToByteArray(data)); + } + + [Fact] + void RejectNonNumeric() + { + Assert.Throws(() => QrSegment.MakeNumeric("abc")); + } + + [Fact] + void AlphanumericEncoding() + { + QrSegment segment = QrSegment.MakeAlphanumeric(TextAlphanumeric); + Assert.Equal(segment.EncodingMode, QrSegment.Mode.Alphanumeric); + Assert.Equal(TextAlphanumeric.Length, segment.NumChars); + + BitArray data = segment.GetData(); + Assert.Equal(BitLengthAlphanumeric, data.Length); + + Assert.Equal(BitsAlphanumeric, BitArrayToByteArray(data)); + } + + [Fact] + void RejectNonAlphanumeric() + { + Assert.Throws(() => QrSegment.MakeAlphanumeric("abc,def")); + } + + [Fact] + void AutoNumericEncoding() + { + List segments = QrSegment.MakeSegments(TextNumeric); + Assert.Single(segments); + + QrSegment segment = segments[0]; + Assert.Equal(segment.EncodingMode, QrSegment.Mode.Numeric); + Assert.Equal(TextNumeric.Length, segment.NumChars); + + BitArray data = segment.GetData(); + Assert.Equal(BitLengthNumeric, data.Length); + + Assert.Equal(BitsNumeric, BitArrayToByteArray(data)); + } + + [Fact] + void AutoAlphanumericEncoding() + { + List segments = QrSegment.MakeSegments(TextAlphanumeric); + Assert.Single(segments); + + QrSegment segment = segments[0]; + Assert.Equal(segment.EncodingMode, QrSegment.Mode.Alphanumeric); + Assert.Equal(TextAlphanumeric.Length, segment.NumChars); + + BitArray data = segment.GetData(); + Assert.Equal(BitLengthAlphanumeric, data.Length); + + Assert.Equal(BitsAlphanumeric, BitArrayToByteArray(data)); + } + + [Fact] + void Utf8Encoding() + { + List segments = QrSegment.MakeSegments(TextUtf8); + Assert.Single(segments); + QrSegment segment = segments[0]; + Assert.Equal(segment.EncodingMode, QrSegment.Mode.Byte); + Assert.Equal(Encoding.UTF8.GetBytes(TextUtf8).Length, segment.NumChars); + + BitArray data = segment.GetData(); + Assert.Equal(BitLengthUtf8, data.Length); + + Assert.Equal(BitsUtf8, BitArrayToByteArray(data)); + } + + [Fact] + void EmptyTest() + { + List segments = QrSegment.MakeSegments(""); + Assert.Empty(segments); + } + + private static byte[] BitArrayToByteArray(BitArray buffer) + { + int len = buffer.Length; + byte[] result = new byte[(len + 7) / 8]; + buffer.CopyTo(result, 0); + return result; + } + } +} diff --git a/dotnet/QrCodeGeneratorTest/QrSegmentRegexTest.cs b/dotnet/QrCodeGeneratorTest/QrSegmentRegexTest.cs new file mode 100644 index 0000000..3413ed6 --- /dev/null +++ b/dotnet/QrCodeGeneratorTest/QrSegmentRegexTest.cs @@ -0,0 +1,75 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +using Xunit; + +namespace Io.Nayuki.QrCodeGen.Test +{ + public class QrSegmentRegexTest + { + [Fact] + void IsNumeric() + { + Assert.Matches(QrSegment.NumericRegex, "1234"); + } + + [Fact] + void EmptyIsNumeric() + { + Assert.Matches(QrSegment.NumericRegex, ""); + } + + [Fact] + void TextIsNotNumeric() + { + Assert.DoesNotMatch(QrSegment.NumericRegex, "123a"); + } + + [Fact] + void WhitespaceIsNotNumeric() + { + Assert.DoesNotMatch(QrSegment.NumericRegex, "123\n345"); + } + + [Fact] + void ValidAlphanumeric() + { + Assert.Matches(QrSegment.AlphanumericRegex, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./"); + } + + [Fact] + void EmptyIsAlphanumeric() + { + Assert.Matches(QrSegment.AlphanumericRegex, ""); + } + + [Fact] + void InvalidAlphanumeric() + { + Assert.DoesNotMatch(QrSegment.AlphanumericRegex, ","); + Assert.DoesNotMatch(QrSegment.AlphanumericRegex, "^"); + Assert.DoesNotMatch(QrSegment.AlphanumericRegex, "("); + Assert.DoesNotMatch(QrSegment.AlphanumericRegex, "a"); + } + } +} diff --git a/dotnet/QrCodeGeneratorTest/TestHelper.cs b/dotnet/QrCodeGeneratorTest/TestHelper.cs new file mode 100644 index 0000000..3c327e7 --- /dev/null +++ b/dotnet/QrCodeGeneratorTest/TestHelper.cs @@ -0,0 +1,47 @@ +/* + * QR Code generator library (.NET) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + + +namespace Io.Nayuki.QrCodeGen.Test +{ + internal class TestHelper + { + internal static string[] ToStringArray(QrCode qrCode) + { + int size = qrCode.Size; + string[] result = new string[size]; + + for (int y = 0; y < size; y++) + { + char[] row = new char[size]; + for (int x = 0; x < size; x++) + { + row[x] = qrCode.GetModule(x, y) ? 'X' : ' '; + } + result[y] = new string(row); + } + + return result; + } + } +}