diff --git a/.github/workflows/wf-build-release-ci.yml b/.github/workflows/wf-build-release-ci.yml
index 124e9b18..8bf0c577 100644
--- a/.github/workflows/wf-build-release-ci.yml
+++ b/.github/workflows/wf-build-release-ci.yml
@@ -6,7 +6,7 @@ on:
workflow_dispatch:
jobs:
build:
- runs-on: windows-2019
+ runs-on: windows-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
@@ -33,17 +33,17 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: Compiled project
- path: D:\a\qrcoder\qrcoder
+ path: ${{ github.workspace }}
test:
needs: build
- runs-on: windows-2019
+ runs-on: windows-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: Compiled project
- path: D:\a\qrcoder\qrcoder
+ path: ${{ github.workspace }}
- name: Install additional .NET SDKs
uses: actions/setup-dotnet@v4
@@ -139,7 +139,7 @@ jobs:
pack-push-ci:
needs: test
- runs-on: windows-2019
+ runs-on: windows-latest
env:
GH_PKG_SEC: ${{ secrets.GITHUB_TOKEN }}
steps:
@@ -147,7 +147,7 @@ jobs:
uses: actions/download-artifact@v4
with:
name: Compiled project
- path: D:\a\qrcoder\qrcoder
+ path: ${{ github.workspace }}
- name: Install additional .NET SDKs
uses: actions/setup-dotnet@v4
@@ -193,7 +193,7 @@ jobs:
clean:
needs: [build, test, pack-push-ci]
if: always()
- runs-on: windows-2019
+ runs-on: windows-latest
steps:
- name: Delete artifacts
uses: GeekyEggo/delete-artifact@v5
diff --git a/.github/workflows/wf-build-release.yml b/.github/workflows/wf-build-release.yml
index 1b898d11..ad7a5f21 100644
--- a/.github/workflows/wf-build-release.yml
+++ b/.github/workflows/wf-build-release.yml
@@ -7,7 +7,7 @@ on:
required: true
jobs:
build:
- runs-on: windows-2019
+ runs-on: windows-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
@@ -34,17 +34,17 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: Compiled project
- path: D:\a\qrcoder\qrcoder
+ path: ${{ github.workspace }}
test:
needs: build
- runs-on: windows-2019
+ runs-on: windows-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: Compiled project
- path: D:\a\qrcoder\qrcoder
+ path: ${{ github.workspace }}
- name: Install additional .NET SDKs
uses: actions/setup-dotnet@v4
@@ -139,7 +139,7 @@ jobs:
pack-push-release:
needs: test
- runs-on: windows-2019
+ runs-on: windows-latest
env:
GH_PKG_SEC: ${{ secrets.GITHUB_TOKEN }}
steps:
@@ -147,7 +147,7 @@ jobs:
uses: actions/download-artifact@v4
with:
name: Compiled project
- path: D:\a\qrcoder\qrcoder
+ path: ${{ github.workspace }}
- name: Install additional .NET SDKs
uses: actions/setup-dotnet@v4
@@ -187,7 +187,7 @@ jobs:
clean:
needs: [build, test, pack-push-release]
if: always()
- runs-on: windows-2019
+ runs-on: windows-latest
steps:
- name: Delete artifacts
uses: GeekyEggo/delete-artifact@v5
diff --git a/.github/workflows/wf-build-test.yml b/.github/workflows/wf-build-test.yml
index eca43ce8..64cc56d3 100644
--- a/.github/workflows/wf-build-test.yml
+++ b/.github/workflows/wf-build-test.yml
@@ -6,7 +6,7 @@ on:
workflow_dispatch:
jobs:
build:
- runs-on: windows-2019
+ runs-on: windows-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
@@ -33,17 +33,17 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: Compiled project
- path: D:\a\qrcoder\qrcoder
+ path: ${{ github.workspace }}
test:
needs: build
- runs-on: windows-2019
+ runs-on: windows-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: Compiled project
- path: D:\a\qrcoder\qrcoder
+ path: ${{ github.workspace }}
- name: Install additional .NET SDKs
uses: actions/setup-dotnet@v4
@@ -98,7 +98,7 @@ jobs:
clean:
needs: [build, test]
if: always()
- runs-on: windows-2019
+ runs-on: windows-latest
steps:
- name: Delete artifacts
uses: GeekyEggo/delete-artifact@v5
diff --git a/.github/workflows/wf-verify-formatting.yml b/.github/workflows/wf-verify-formatting.yml
index acc21c54..3dbb788e 100644
--- a/.github/workflows/wf-verify-formatting.yml
+++ b/.github/workflows/wf-verify-formatting.yml
@@ -7,7 +7,9 @@ on:
jobs:
format:
- runs-on: windows-2019
+ runs-on: ubuntu-latest
+ env:
+ EnableWindowsTargeting: true
steps:
- uses: actions/checkout@v4
name: Checkout Code
@@ -15,11 +17,7 @@ jobs:
- name: Install additional .NET SDKs
uses: actions/setup-dotnet@v4
with:
- dotnet-version: |
- 2.0.x
- 5.0.x
- 6.0.x
- 8.0.x
+ dotnet-version: 8.0.x
- name: Restore NuGet Packages
run: dotnet restore
diff --git a/QRCoder/QRCodeData.cs b/QRCoder/QRCodeData.cs
index 0c27ff55..7fb8a68b 100644
--- a/QRCoder/QRCodeData.cs
+++ b/QRCoder/QRCodeData.cs
@@ -194,10 +194,12 @@ public void SaveRawData(string filePath, Compression compressMode)
///
/// Gets the number of modules per side from the specified version.
///
- /// The version of the QR code.
+ /// The version of the QR code (1 to 40, or -1 to -4 for Micro QR codes).
/// Returns the number of modules per side.
private static int ModulesPerSideFromVersion(int version)
- => 21 + (version - 1) * 4;
+ => version > 0
+ ? 21 + (version - 1) * 4
+ : 11 + (-version - 1) * 2;
///
/// Releases all resources used by the .
diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs
index 8dcf7928..5847082c 100644
--- a/QRCoder/QRCodeGenerator.cs
+++ b/QRCoder/QRCodeGenerator.cs
@@ -150,6 +150,85 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
return GenerateQrCode(completeBitArray, eccLevel, version);
}
+ ///
+ /// Calculates the Micro QR code data which then can be used in one of the rendering classes to generate a graphical representation.
+ ///
+ /// The payload which shall be encoded in the QR code
+ /// The level of error correction data
+ /// Set fixed Micro QR code target version; must be -1 to -4 representing M1 to M4, or 0 for default.
+ /// Thrown when the payload is too big to be encoded in a QR code.
+ /// Returns the raw QR code data which can be used for rendering.
+ public static QRCodeData GenerateMicroQrCode(string plainText, ECCLevel eccLevel = ECCLevel.Default, int requestedVersion = 0)
+ {
+ if (requestedVersion is < -4 or > 0)
+ throw new ArgumentOutOfRangeException(nameof(requestedVersion), requestedVersion, "Requested version must be -1 to -4 representing M1 to M4, or 0 for default.");
+ ValidateECCLevel(eccLevel);
+ if (eccLevel == ECCLevel.H)
+ throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Micro QR codes does not support error correction level H.");
+ if (eccLevel == ECCLevel.Q && requestedVersion != -4)
+ throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Micro QR codes only supports error correction level Q for version M4.");
+ if (eccLevel != ECCLevel.Default && requestedVersion == -1)
+ throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Please specify ECCLevel.Default for version M1.");
+ if (plainText == null)
+ throw new ArgumentNullException(nameof(plainText));
+
+ var encoding = GetEncodingFromPlaintext(plainText, false);
+ var codedText = PlainTextToBinary(plainText, encoding, EciMode.Default, false, false);
+ var dataInputLength = GetDataLength(encoding, plainText, codedText, false);
+ int version = requestedVersion;
+ int minVersion = CapacityTables.CalculateMinimumMicroVersion(dataInputLength, encoding, eccLevel);
+
+ if (version == 0)
+ {
+ version = minVersion;
+ }
+ else
+ {
+ //Version was passed as fixed version via parameter. Thus let's check if chosen version is valid.
+ if (minVersion < version)
+ {
+ var matchedEncoding = CapacityTables.GetVersionInfo(version).Details
+ .First(x => (x.ErrorCorrectionLevel == eccLevel || (eccLevel == ECCLevel.Default && x.ErrorCorrectionLevel == ECCLevel.L)))
+ .CapacityDict.TryGetValue(encoding, out var maxSizeByte);
+ if (!matchedEncoding)
+ throw new ArgumentOutOfRangeException(nameof(encoding), encoding, "Encoding not supported for this version.");
+ throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte);
+ }
+ }
+ if (version < -1 && eccLevel == ECCLevel.Default)
+ eccLevel = ECCLevel.L;
+
+ var modeIndicatorLength = -version - 1; // 0 for M1, 1 for M2, 2 for M3, 3 for M4
+ var countIndicatorLength = GetCountIndicatorLength(version, encoding);
+ var completeBitArrayLength = modeIndicatorLength + countIndicatorLength + codedText.Length;
+
+ var completeBitArray = new BitArray(completeBitArrayLength);
+
+ // write mode indicator
+ var completeBitArrayIndex = 0;
+ if (version < 0)
+ {
+ var encodingValue =
+ encoding == EncodingMode.Numeric ? 0 :
+ encoding == EncodingMode.Alphanumeric ? 1 :
+ encoding == EncodingMode.Byte ? 2 : 3;
+ completeBitArrayIndex = DecToBin(encodingValue, modeIndicatorLength, completeBitArray, completeBitArrayIndex);
+ }
+ else
+ {
+ completeBitArrayIndex = DecToBin((int)encoding, 4, completeBitArray, completeBitArrayIndex);
+ }
+ // write count indicator
+ completeBitArrayIndex = DecToBin(dataInputLength, countIndicatorLength, completeBitArray, completeBitArrayIndex);
+ // write data
+ for (int i = 0; i < codedText.Length; i++)
+ {
+ completeBitArray[completeBitArrayIndex++] = codedText[i];
+ }
+
+ return GenerateQrCode(completeBitArray, eccLevel, version);
+ }
+
///
/// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation.
///
@@ -223,7 +302,7 @@ private static QRCodeData GenerateQrCode(BitArray bitArray, ECCLevel eccLevel, i
// fills the bit array with a repeating pattern to reach the required length
void PadData()
{
- var dataLength = eccInfo.TotalDataCodewords * 8;
+ var dataLength = eccInfo.TotalDataBits;
var lengthDiff = dataLength - bitArray.Length;
if (lengthDiff > 0)
{
@@ -231,11 +310,23 @@ void PadData()
var index = bitArray.Length;
// extend bit array to required length
bitArray.Length = dataLength;
- // pad with 4 zeros (or less if lengthDiff < 4)
- index += Math.Min(lengthDiff, 4);
+ // compute padding length
+ int padLength = version switch
+ {
+ > 0 => 4,
+ -1 => 3,
+ -2 => 5,
+ -3 => 7,
+ _ => 9
+ };
+ // pad with zeros (or less if not enough room)
+ index += padLength;
// pad to nearest 8 bit boundary
if ((uint)index % 8 != 0)
index += 8 - (int)((uint)index % 8);
+ // for m1 and m3 sizes don't fill last 4 bits with repeating pattern
+ if (version == -1 || version == -3)
+ dataLength -= 4;
// pad with repeating pattern
var repeatingPatternIndex = 0;
while (index < dataLength)
@@ -264,6 +355,7 @@ List CalculateECCBlocks()
void AddCodeWordBlocks(int blockNum, int blocksInGroup, int codewordsInGroup, int offset2, int count, Polynom generatorPolynom)
{
var groupLength = codewordsInGroup * 8;
+ groupLength = groupLength > count ? count : groupLength;
for (var i = 0; i < blocksInGroup; i++)
{
var eccWordList = CalculateECCWords(bitArray, offset2, groupLength, eccInfo, generatorPolynom);
@@ -277,7 +369,13 @@ void AddCodeWordBlocks(int blockNum, int blocksInGroup, int codewordsInGroup, in
int CalculateInterleavedLength()
{
var length = 0;
- for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++)
+ var codewords = Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2);
+ if (version == -1 || version == -3)
+ {
+ codewords--;
+ length += 4;
+ }
+ for (var i = 0; i < codewords; i++)
{
foreach (var codeBlock in codeWordWithECC)
if ((uint)codeBlock.CodeWordsLength / 8 > i)
@@ -298,6 +396,9 @@ BitArray InterleaveData()
{
var data = new BitArray(interleavedLength);
int pos = 0;
+ int codewords = Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2);
+ if (version == -1 || version == -3)
+ codewords--;
for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++)
{
foreach (var codeBlock in codeWordWithECC)
@@ -306,6 +407,10 @@ BitArray InterleaveData()
pos = bitArray.CopyTo(data, (int)((uint)i * 8) + codeBlock.CodeWordsOffset, pos, 8);
}
}
+ if (version == -1 || version == -3)
+ {
+ pos = bitArray.CopyTo(data, (int)((uint)codewords * 8) + codeWordWithECC[0].CodeWordsOffset, pos, 4);
+ }
for (var i = 0; i < eccInfo.ECCPerBlock; i++)
{
foreach (var codeBlock in codeWordWithECC)
@@ -325,14 +430,14 @@ QRCodeData PlaceModules()
using (var blockedModules = new ModulePlacer.BlockedModules(size))
{
ModulePlacer.PlaceFinderPatterns(qr, blockedModules);
- ModulePlacer.ReserveSeperatorAreas(size, blockedModules);
+ ModulePlacer.ReserveSeperatorAreas(version, size, blockedModules);
ModulePlacer.PlaceAlignmentPatterns(qr, AlignmentPatterns.FromVersion(version).PatternPositions, blockedModules);
ModulePlacer.PlaceTimingPatterns(qr, blockedModules);
ModulePlacer.PlaceDarkModule(qr, version, blockedModules);
ModulePlacer.ReserveVersionAreas(size, version, blockedModules);
ModulePlacer.PlaceDataWords(qr, interleavedData, blockedModules);
var maskVersion = ModulePlacer.MaskCode(qr, version, blockedModules, eccLevel);
- GetFormatString(tempBitArray, eccLevel, maskVersion);
+ GetFormatString(tempBitArray, version, eccLevel, maskVersion);
ModulePlacer.PlaceFormat(qr, tempBitArray, true);
}
@@ -348,19 +453,25 @@ QRCodeData PlaceModules()
private static readonly BitArray _getFormatGenerator = new BitArray(new bool[] { true, false, true, false, false, true, true, false, true, true, true });
private static readonly BitArray _getFormatMask = new BitArray(new bool[] { true, false, true, false, true, false, false, false, false, false, true, false, false, true, false });
+ private static readonly BitArray _getFormatMicroMask = new BitArray(new bool[] { true, false, false, false, true, false, false, false, true, false, false, false, true, false, true });
+
///
/// Generates a BitArray containing the format string for a QR code based on the error correction level and mask pattern version.
/// The format string includes the error correction level, mask pattern version, and error correction coding.
///
- /// The to write to, or null to create a new one.
+ /// The to write to, or null to create a new one.
+ /// The version number of the QR Code (1-40, or -1 to -4 for Micro QR codes).
/// The error correction level to be encoded in the format string.
/// The mask pattern version to be encoded in the format string.
/// A BitArray containing the 15-bit format string used in QR code generation.
- private static void GetFormatString(BitArray fStrEcc, ECCLevel level, int maskVersion)
+ private static void GetFormatString(BitArray fStrEcc, int version, ECCLevel level, int maskVersion)
{
fStrEcc.Length = 15;
fStrEcc.SetAll(false);
- WriteEccLevelAndVersion();
+ if (version < 0)
+ WriteMicroEccLevelAndVersion();
+ else
+ WriteEccLevelAndVersion();
// Apply the format generator polynomial to add error correction to the format string.
int index = 0;
@@ -379,10 +490,13 @@ private static void GetFormatString(BitArray fStrEcc, ECCLevel level, int maskVe
// Prefix the error correction bits with the ECC level and version number.
fStrEcc.Length = 10 + 5;
ShiftAwayFromBit0(fStrEcc, (10 - count) + 5);
- WriteEccLevelAndVersion();
+ if (version < 0)
+ WriteMicroEccLevelAndVersion();
+ else
+ WriteEccLevelAndVersion();
// XOR the format string with a predefined mask to add robustness against errors.
- fStrEcc.Xor(_getFormatMask);
+ fStrEcc.Xor(version < 0 ? _getFormatMicroMask : _getFormatMask);
void WriteEccLevelAndVersion()
{
@@ -405,6 +519,49 @@ void WriteEccLevelAndVersion()
// Insert the 3-bit mask version directly after the error correction level bits.
DecToBin(maskVersion, 3, fStrEcc, 2);
}
+
+ void WriteMicroEccLevelAndVersion()
+ {
+ switch (version)
+ {
+ case -1: // M1
+ break;
+ case -2: // M2
+ fStrEcc[level == ECCLevel.L ? 2 : 1] = true; // 001 for L and 010 for M
+ break;
+ case -3: // M3
+ if (level == ECCLevel.L)
+ {
+ fStrEcc[1] = true; // 011 for L
+ fStrEcc[2] = true;
+ }
+ else
+ fStrEcc[0] = true; // 100 for M
+ break;
+ default: // M4
+ fStrEcc[0] = true;
+ if (level == ECCLevel.L) // 101 for L
+ fStrEcc[2] = true;
+ else if (level == ECCLevel.M) // 110 for M
+ fStrEcc[1] = true;
+ else // 111 for Q
+ {
+ fStrEcc[1] = true;
+ fStrEcc[2] = true;
+ }
+ break;
+ }
+
+ // Insert the 2-bit mask version directly after the version / error correction level bits.
+ int microMaskVersion = maskVersion switch
+ {
+ 1 => 0,
+ 4 => 1,
+ 6 => 2,
+ _ => 3
+ };
+ DecToBin(microMaskVersion, 2, fStrEcc, 3);
+ }
}
#if !NETFRAMEWORK || NET45_OR_GREATER
@@ -583,17 +740,52 @@ private static bool IsInRange(char c, char min, char max)
=> (uint)(c - min) <= (uint)(max - min);
///
- /// Calculates the message polynomial from a bit array which represents the encoded data.
+ /// Converts a segment of a BitArray representing QR code data into a polynomial,
+ /// padding the final byte if necessary (for Micro QR variants like M1 or M3).
///
- /// A polynomial representation of the message.
- private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, int count)
+ /// The full bit array representing encoded QR code data.
+ /// Starting position in the bit array.
+ /// Total number of bits to convert into codewords.
+ /// A polynomial representing the message codewords.
+ private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, int bitCount)
{
- var messagePol = new Polynom(count /= 8);
- for (var i = count - 1; i >= 0; i--)
+ // Calculate how many full 8-bit codewords are present
+ var fullBytes = bitCount / 8;
+
+ // Determine if there is a remaining partial byte (e.g., 4 bits for Micro QR M1 and M3 versions)
+ var remainingBits = bitCount % 8;
+
+ if (remainingBits > 0)
{
- messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), i));
+ // Pad the last byte with zero bits to make it a full 8-bit codeword
+ var addlBits = 8 - remainingBits;
+ var minBitArrayLength = offset + bitCount + addlBits;
+
+ // Extend BitArray length if needed to fit the padded bits
+ if (bitArray.Length < minBitArrayLength)
+ bitArray.Length = minBitArrayLength;
+
+ // Pad the remaining bits with false (0) values
+ for (int i = 0; i < addlBits; i++)
+ bitArray[offset + bitCount + i] = false;
+ }
+
+ // Total number of codewords (includes extra for partial byte if present)
+ var polynomLength = fullBytes + (remainingBits > 0 ? 1 : 0);
+
+ // Initialize the polynomial
+ var messagePol = new Polynom(polynomLength);
+
+ // Exponent for polynomial terms starts from highest degree
+ int exponent = polynomLength - 1;
+
+ // Convert each 8-bit segment into a decimal value and add it to the polynomial
+ for (int i = 0; i < polynomLength; i++)
+ {
+ messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), exponent--));
offset += 8;
}
+
return messagePol;
}
@@ -670,7 +862,33 @@ private static int DecToBin(int decNum, int bits, BitArray bitList, int index)
private static int GetCountIndicatorLength(int version, EncodingMode encMode)
{
// Different versions and encoding modes require different lengths of bits to represent the character count efficiently
- if (version < 10)
+ if (version == -1)
+ {
+ return 3;
+ }
+ else if (version == -2)
+ {
+ return encMode == EncodingMode.Numeric ? 4 : 3;
+ }
+ else if (version == -3)
+ {
+ if (encMode == EncodingMode.Numeric)
+ return 5;
+ else if (encMode == EncodingMode.Kanji)
+ return 3;
+ else
+ return 4;
+ }
+ else if (version == -4)
+ {
+ if (encMode == EncodingMode.Numeric)
+ return 6;
+ else if (encMode == EncodingMode.Kanji)
+ return 4;
+ else
+ return 5;
+ }
+ else if (version < 10)
{
if (encMode == EncodingMode.Numeric)
return 10;
diff --git a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs
index fe879623..deb1abb5 100644
--- a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs
+++ b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs
@@ -28,7 +28,7 @@ private static class AlignmentPatterns
private static Dictionary CreateAlignmentPatternTable()
{
var alignmentPatternBaseValues = new int[] { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 };
- var localAlignmentPatternTable = new Dictionary(40);
+ var localAlignmentPatternTable = new Dictionary(40 + 4);
for (var i = 0; i < (7 * 40); i += 7)
{
@@ -56,6 +56,14 @@ private static Dictionary CreateAlignmentPatternTable()
PatternPositions = points
});
}
+
+ // Micro QR codes do not have alignment patterns.
+ var emptyPointList = new List();
+ localAlignmentPatternTable.Add(-1, new AlignmentPattern { Version = -1, PatternPositions = emptyPointList });
+ localAlignmentPatternTable.Add(-2, new AlignmentPattern { Version = -2, PatternPositions = emptyPointList });
+ localAlignmentPatternTable.Add(-3, new AlignmentPattern { Version = -3, PatternPositions = emptyPointList });
+ localAlignmentPatternTable.Add(-4, new AlignmentPattern { Version = -4, PatternPositions = emptyPointList });
+
return localAlignmentPatternTable;
}
}
diff --git a/QRCoder/QRCodeGenerator/CapacityTables.cs b/QRCoder/QRCodeGenerator/CapacityTables.cs
index 59f0d2c9..df885273 100644
--- a/QRCoder/QRCodeGenerator/CapacityTables.cs
+++ b/QRCoder/QRCodeGenerator/CapacityTables.cs
@@ -20,6 +20,7 @@ private static class CapacityTables
/// The index in the capacity table corresponds to one less than the version number.
///
private static readonly List _capacityTable = CreateCapacityTable(_capacityBaseValues);
+ private static readonly List _microCapacityTable = CreateMicroCapacityTable();
///
/// A table containing the error correction capacities and data codeword information for different combinations of QR code versions and error correction levels.
@@ -29,7 +30,7 @@ private static class CapacityTables
///
/// Retrieves the error correction information for a specific QR code version and error correction level.
///
- /// The version of the QR code (1 to 40).
+ /// The version of the QR code (1 to 40, or -1 to -4 for M1 to M4).
/// The desired error correction level (L, M, Q, or H). Do not supply .
///
/// An object containing the total number of data codewords, ECC per block,
@@ -44,25 +45,25 @@ public static ECCInfo GetEccInfo(int version, ECCLevel eccLevel)
/// and encoding mode (Numeric, Alphanumeric, Byte, Kanji), indicating the maximum number of characters
/// that can be stored in a QR code of the specified version under each configuration.
///
- /// The version of the QR code (1 to 40).
+ /// The version of the QR code (1 to 40, or -1 to -4 for M1 to M4).
///
/// A object containing data capacity details for all error correction levels
/// and encoding modes for the specified version.
///
public static VersionInfo GetVersionInfo(int version)
- => _capacityTable[version - 1];
+ => version < 0 ? _microCapacityTable[-version - 1] : _capacityTable[version - 1];
///
/// Retrieves the number of remainder bits required for a specific QR code version.
/// Remainder bits are added to the final bit stream to ensure proper alignment with byte boundaries,
/// as required by the QR code specification.
///
- /// The version of the QR code (1 to 40).
+ /// The version of the QR code (1 to 40, or -1 to -4 for M1 to M4).
///
/// The number of remainder bits (0 to 7) that must be appended to the encoded bit stream.
///
public static int GetRemainderBits(int version)
- => _remainderBits[version - 1];
+ => version < 0 ? 0 : _remainderBits[version - 1];
///
/// Determines the minimum QR code version required to encode a given amount of data with a specific encoding mode and error correction level.
@@ -77,6 +78,7 @@ public static int GetRemainderBits(int version)
///
public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCLevel eccLevel)
{
+ // only iterates through non-micro QR codes
// capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found
foreach (var x in _capacityTable)
{
@@ -99,6 +101,50 @@ public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCL
throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte);
}
+
+ ///
+ /// Determines the minimum Micro QR code version required to encode a given amount of data with a specific encoding mode and error correction level.
+ /// If no suitable version is found, it throws an exception indicating that the data length exceeds the maximum capacity for the given settings.
+ ///
+ /// The length of the data to be encoded.
+ /// The encoding mode (e.g., Numeric, Alphanumeric, Byte).
+ /// The error correction level (e.g., Default, Low, Medium, Quartile, High).
+ /// The minimum version of the QR code (-1 to -4) that can accommodate the given data and settings.
+ ///
+ /// Thrown when the data length exceeds the maximum capacity for the specified encoding mode and error correction level.
+ ///
+ public static int CalculateMinimumMicroVersion(int length, EncodingMode encMode, ECCLevel eccLevel)
+ {
+ // only iterates through non-micro QR codes
+ // capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found
+ foreach (var x in _microCapacityTable)
+ {
+ // find the requested ECC level and encoding mode in the capacity table
+ foreach (var y in x.Details)
+ {
+ // Use ECC level L for Micro QR Code versions 2, 3 and 4 when Default is specified
+ if (y.ErrorCorrectionLevel == eccLevel || (eccLevel == ECCLevel.Default && y.ErrorCorrectionLevel == ECCLevel.L))
+ {
+ // Not all versions support all encoding modes, so check if the encoding mode is supported
+ if (y.CapacityDict.TryGetValue(encMode, out int maxLength) && maxLength >= length)
+ {
+ // if the capacity of the current version is enough, return the version number
+ return x.Version;
+ }
+ }
+ }
+ }
+
+ // if no version was found, throw an exception
+ var maxSizeByte = _microCapacityTable
+ .SelectMany(x => x.Details)
+ .Where(y => (y.ErrorCorrectionLevel == eccLevel || (eccLevel == ECCLevel.Default && y.ErrorCorrectionLevel == ECCLevel.L))
+ && y.CapacityDict.ContainsKey(encMode))
+ .Max(y => y.CapacityDict[encMode]);
+
+ throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte);
+ }
+
///
/// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels.
/// This table is essential for determining how much data can be encoded in a QR code of a specific version and ECC level,
@@ -107,7 +153,7 @@ public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCL
/// A list of ECCInfo structures, each representing the ECC data and capacities for different combinations of QR code versions and ECC levels.
private static List CreateCapacityECCTable(int[] capacityECCBaseValues)
{
- var localCapacityECCTable = new List(160);
+ var localCapacityECCTable = new List(160 + 8);
for (var i = 0; i < (4 * 6 * 40); i += (4 * 6))
{
localCapacityECCTable.AddRange(new[]
@@ -153,6 +199,31 @@ private static List CreateCapacityECCTable(int[] capacityECCBaseValues)
)
});
}
+
+ localCapacityECCTable.AddRange(new ECCInfo[]
+ {
+ // Micro QR Code Version M1 - only supports ECCLevel.Default (none)
+ new ECCInfo(
+ version: -1,
+ errorCorrectionLevel: ECCLevel.Default,
+ totalDataCodewords: 3,
+ totalDataBits: 20,
+ eccPerBlock: 2),
+
+ // Micro QR Code Version M2
+ new ECCInfo(-2, ECCLevel.L, 5, 40, 5),
+ new ECCInfo(-2, ECCLevel.M, 4, 32, 6),
+
+ // Micro QR Code Version M3
+ new ECCInfo(-3, ECCLevel.L, 11, 84, 6),
+ new ECCInfo(-3, ECCLevel.M, 9, 68, 8),
+
+ // Micro QR Code Version M4
+ new ECCInfo(-4, ECCLevel.L, 16, 128, 8),
+ new ECCInfo(-4, ECCLevel.M, 14, 112, 10),
+ new ECCInfo(-4, ECCLevel.Q, 10, 80, 14),
+ });
+
return localCapacityECCTable;
}
@@ -212,5 +283,98 @@ private static List CreateCapacityTable(int[] capacityBaseValues)
}
return localCapacityTable;
}
+
+ ///
+ private static List CreateMicroCapacityTable()
+ {
+ var tbl = new List(4);
+
+ var m1details = new List(1)
+ {
+ new VersionInfoDetails(
+ ECCLevel.Default, // none
+ new Dictionary(1) {
+ { EncodingMode.Numeric, 5 },
+ }
+ )
+ };
+ tbl.Add(new VersionInfo(-1, m1details));
+
+ var m2details = new List(2)
+ {
+ new VersionInfoDetails(
+ ECCLevel.L,
+ new Dictionary(2) {
+ { EncodingMode.Numeric, 10 },
+ { EncodingMode.Alphanumeric, 6 },
+ }
+ ),
+ new VersionInfoDetails(
+ ECCLevel.M,
+ new Dictionary(2) {
+ { EncodingMode.Numeric, 8 },
+ { EncodingMode.Alphanumeric, 5 },
+ }
+ ),
+ };
+ tbl.Add(new VersionInfo(-2, m2details));
+
+ var m3details = new List(2)
+ {
+ new VersionInfoDetails(
+ ECCLevel.L,
+ new Dictionary(4) {
+ { EncodingMode.Numeric, 23 },
+ { EncodingMode.Alphanumeric, 14 },
+ { EncodingMode.Byte, 9 },
+ { EncodingMode.Kanji, 6 },
+ }
+ ),
+ new VersionInfoDetails(
+ ECCLevel.M,
+ new Dictionary(4) {
+ { EncodingMode.Numeric, 18 },
+ { EncodingMode.Alphanumeric, 11 },
+ { EncodingMode.Byte, 7 },
+ { EncodingMode.Kanji, 4 },
+ }
+ ),
+ };
+ tbl.Add(new VersionInfo(-3, m3details));
+
+ var m4details = new List(3)
+ {
+ new VersionInfoDetails(
+ ECCLevel.L,
+ new Dictionary(4) {
+ { EncodingMode.Numeric, 35 },
+ { EncodingMode.Alphanumeric, 21 },
+ { EncodingMode.Byte, 15 },
+ { EncodingMode.Kanji, 9 },
+ }
+ ),
+ new VersionInfoDetails(
+ ECCLevel.M,
+ new Dictionary(4) {
+ { EncodingMode.Numeric, 30 },
+ { EncodingMode.Alphanumeric, 18 },
+ { EncodingMode.Byte, 13 },
+ { EncodingMode.Kanji, 8 },
+ }
+ ),
+ new VersionInfoDetails(
+ ECCLevel.Q,
+ new Dictionary(4) {
+ { EncodingMode.Numeric, 21 },
+ { EncodingMode.Alphanumeric, 13 },
+ { EncodingMode.Byte, 9 },
+ { EncodingMode.Kanji, 5 },
+ }
+ ),
+ };
+ tbl.Add(new VersionInfo(-4, m4details));
+
+ return tbl;
+ }
}
}
diff --git a/QRCoder/QRCodeGenerator/ECCInfo.cs b/QRCoder/QRCodeGenerator/ECCInfo.cs
index 919288ca..9e8b787e 100644
--- a/QRCoder/QRCodeGenerator/ECCInfo.cs
+++ b/QRCoder/QRCodeGenerator/ECCInfo.cs
@@ -24,6 +24,7 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword
Version = version;
ErrorCorrectionLevel = errorCorrectionLevel;
TotalDataCodewords = totalDataCodewords;
+ TotalDataBits = totalDataCodewords * 8;
ECCPerBlock = eccPerBlock;
BlocksInGroup1 = blocksInGroup1;
CodewordsInGroup1 = codewordsInGroup1;
@@ -31,6 +32,27 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword
CodewordsInGroup2 = codewordsInGroup2;
}
+ ///
+ /// Initializes a new instance of the ECCInfo struct with specified properties for Micro QR codes.
+ ///
+ /// The version number of the QR code.
+ /// The error correction level used in the QR code.
+ /// The total number of data codewords for this version and error correction level.
+ /// The total number of data bits for this version and error correction level.
+ /// The number of error correction codewords per block.
+ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int totalDataBits, int eccPerBlock)
+ {
+ Version = version;
+ ErrorCorrectionLevel = errorCorrectionLevel;
+ TotalDataCodewords = totalDataCodewords;
+ TotalDataBits = totalDataBits;
+ ECCPerBlock = eccPerBlock;
+ BlocksInGroup1 = 1;
+ CodewordsInGroup1 = totalDataCodewords;
+ BlocksInGroup2 = 0;
+ CodewordsInGroup2 = 0;
+ }
+
///
/// Gets the version number of the QR code.
///
@@ -46,6 +68,11 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword
///
public int TotalDataCodewords { get; }
+ ///
+ /// Gets the total number of data codewords for this version and error correction level.
+ ///
+ public int TotalDataBits { get; }
+
///
/// Gets the number of error correction codewords per block.
///
diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs b/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs
index 4b46dac6..3df023a3 100644
--- a/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs
+++ b/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs
@@ -78,6 +78,28 @@ public static bool Pattern7(int x, int y)
public static bool Pattern8(int x, int y)
=> (((x + y) % 2) + ((x * y) % 3)) % 2 == 0;
+ ///
+ /// Calculates a penalty score for a Micro QR code to evaluate the effectiveness of a mask pattern.
+ /// A lower score indicates a QR code that is easier for decoders to read accurately.
+ ///
+ /// The QR code data structure to be evaluated.
+ /// The total penalty score of the QR code.
+ public static int ScoreMicro(QRCodeData qrCode)
+ {
+ int size = qrCode.ModuleMatrix.Count;
+ int sum1 = 0;
+ int sum2 = 0;
+ for (int i = 1; i < size; i++)
+ {
+ if (qrCode.ModuleMatrix[size - 1][i])
+ sum1++;
+ if (qrCode.ModuleMatrix[i][size - 1])
+ sum2++;
+ }
+ int total = sum1 < sum2 ? sum1 * 16 + sum2 : sum2 * 16 + sum1;
+ return -total; // negate so that lower is better
+ }
+
///
/// Calculates a penalty score for a QR code to evaluate the effectiveness of a mask pattern.
/// A lower score indicates a QR code that is easier for decoders to read accurately.
diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.cs b/QRCoder/QRCodeGenerator/ModulePlacer.cs
index f452e6b3..774d13aa 100644
--- a/QRCoder/QRCodeGenerator/ModulePlacer.cs
+++ b/QRCoder/QRCodeGenerator/ModulePlacer.cs
@@ -35,11 +35,15 @@ public static void PlaceVersion(QRCodeData qrCode, BitArray versionStr, bool off
///
/// The QR code data structure to modify.
/// The bit array containing the format information.
+ /// Specifies whether an offset should be applied.
public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offset)
{
+ var isMicro = qrCode.Version < 0; // Negative versions indicate Micro QR codes.
var offsetValue = offset ? 4 : 0;
var size = qrCode.ModuleMatrix.Count - offsetValue - offsetValue;
+ // Standard QR Code Format Positions:
+ //
// { x1, y1, x2, y2 } i
// ===============================
// { 8, 0, size - 1, 8 }, // 0
@@ -58,16 +62,53 @@ public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offse
// { 1, 8, 8, size - 2 }, // 13
// { 0, 8, 8, size - 1 } }; // 14
+ // Micro QR Code Format Positions:
+ //
+ // i { x1, y1 }
+ // ===============
+ // 0 { 8, 1 }
+ // 1 { 8, 2 }
+ // 2 { 8, 3 }
+ // 3 { 8, 4 }
+ // 4 { 8, 5 }
+ // 5 { 8, 6 }
+ // 6 { 8, 7 }
+ // 7 { 8, 8 }
+ // 8 { 7, 8 }
+ // 9 { 6, 8 }
+ // 10 { 5, 8 }
+ // 11 { 4, 8 }
+ // 12 { 3, 8 }
+ // 13 { 2, 8 }
+ // 14 { 1, 8 }
+
+ // The bit pattern is considered an entire 'word' and LSB goes in position 0
+ // So, we need to reverse the order of the generated bit pattern, hence the (14 - i) below
+
for (var i = 0; i < 15; i++)
{
- // values computed to follow table above
- var x1 = i < 8 ? 8 : i == 8 ? 7 : 14 - i;
- var y1 = i < 6 ? i : i < 7 ? i + 1 : 8;
- var x2 = i < 8 ? size - 1 - i : 8;
- var y2 = i < 8 ? 8 : size - (15 - i);
-
- qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i];
- qrCode.ModuleMatrix[y2 + offsetValue][x2 + offsetValue] = formatStr[14 - i];
+ int x1, y1, x2, y2;
+
+ if (isMicro)
+ {
+ // Micro QR format positions
+ x1 = i < 8 ? 8 : 14 - i + 1;
+ y1 = i < 8 ? i + 1 : 8;
+
+ // Micro QR only uses one set of format positions, no duplication.
+ qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i];
+ }
+ else
+ {
+ // Standard QR format positions
+ x1 = i < 8 ? 8 : i == 8 ? 7 : 14 - i;
+ y1 = i < 6 ? i : i < 7 ? i + 1 : 8;
+ x2 = i < 8 ? size - 1 - i : 8;
+ y2 = i < 8 ? 8 : size - (15 - i);
+
+ qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i];
+ qrCode.ModuleMatrix[y2 + offsetValue][x2 + offsetValue] = formatStr[14 - i];
+ }
}
}
@@ -98,6 +139,9 @@ public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blocke
var formatStr = new BitArray(15);
for (var maskPattern = 0; maskPattern < 8; maskPattern++)
{
+ if (version < 0 && (maskPattern == 0 || maskPattern == 2 || maskPattern == 3 || maskPattern == 5))
+ continue; // Micro QR codes only support certain mask patterns.
+
var patternFunc = MaskPattern.Patterns[maskPattern];
// Reset the temporary QR code to the current state of the actual QR code.
@@ -110,7 +154,7 @@ public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blocke
}
// Place format information using the current mask pattern.
- GetFormatString(formatStr, eccLevel, maskPattern);
+ GetFormatString(formatStr, version, eccLevel, maskPattern);
ModulePlacer.PlaceFormat(qrTemp, formatStr, false);
// Place version information if applicable.
@@ -137,7 +181,7 @@ public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blocke
}
}
- var score = MaskPattern.Score(qrTemp);
+ var score = version < 0 ? MaskPattern.ScoreMicro(qrTemp) : MaskPattern.Score(qrTemp);
// Select the pattern with the lowest score, indicating better QR code readability.
if (patternScore > score)
@@ -185,35 +229,23 @@ public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModul
// Loop from the rightmost column to the leftmost column, skipping one column each time.
for (var x = size - 1; x >= 0; x -= 2)
{
- // Skip the timing pattern column at position 6.
- if (x == 6)
+ // Skip the timing pattern column at position 6 (for normal QR codes only, not Micro QR codes).
+ if (qrCode.Version > 0 && x == 6)
x = 5;
// Loop through each row in the current column set.
for (var yMod = 1; yMod <= size; yMod++)
{
- int y; // Actual y position to place data in the matrix.
-
// Determine the actual y position based on the current fill direction.
- if (up)
- {
- y = size - yMod; // Calculate y for upward direction.
- // Place data if within data length, current position is not blocked, and leftward column is in bounds.
- if (index < count && !blockedModules.IsBlocked(x, y))
- qrCode.ModuleMatrix[y + 4][x + 4] = data[index++];
- if (index < count && x > 0 && !blockedModules.IsBlocked(x - 1, y))
- qrCode.ModuleMatrix[y + 4][x - 1 + 4] = data[index++];
- }
- else
- {
- y = yMod - 1; // Calculate y for downward direction.
- // Similar checks and data placement for the downward direction.
- if (index < count && !blockedModules.IsBlocked(x, y))
- qrCode.ModuleMatrix[y + 4][x + 4] = data[index++];
- if (index < count && x > 0 && !blockedModules.IsBlocked(x - 1, y))
- qrCode.ModuleMatrix[y + 4][x - 1 + 4] = data[index++];
- }
+ int y = up ? size - yMod : yMod - 1;
+
+ // Place data if within data length and current position is not blocked.
+ if (index < count && !blockedModules.IsBlocked(x, y))
+ qrCode.ModuleMatrix[y + 4][x + 4] = data[index++];
+ if (index < count && x > 0 && !blockedModules.IsBlocked(x - 1, y))
+ qrCode.ModuleMatrix[y + 4][x - 1 + 4] = data[index++];
}
+
// Switch the fill direction after completing each column set.
up = !up;
}
@@ -224,15 +256,21 @@ public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModul
///
/// The size of the QR code matrix.
/// A list of rectangles representing areas that must not be overwritten.
- public static void ReserveSeperatorAreas(int size, BlockedModules blockedModules)
+ public static void ReserveSeperatorAreas(int version, int size, BlockedModules blockedModules)
{
- // Block areas around the finder patterns, which are located near three corners of the QR code.
+ // Block areas around the top-left finder pattern
blockedModules.Add(new Rectangle(7, 0, 1, 8)); // Vertical block near the top left finder pattern
blockedModules.Add(new Rectangle(0, 7, 7, 1)); // Horizontal block near the top left finder pattern
- blockedModules.Add(new Rectangle(0, size - 8, 8, 1)); // Horizontal block near the bottom left finder pattern
- blockedModules.Add(new Rectangle(7, size - 7, 1, 7)); // Vertical block near the bottom left finder pattern
- blockedModules.Add(new Rectangle(size - 8, 0, 1, 8)); // Vertical block near the top right finder pattern
- blockedModules.Add(new Rectangle(size - 7, 7, 7, 1)); // Horizontal block near the top right finder pattern
+
+ if (version > 0) // Non-micro QR codes have 3 finder patterns
+ {
+ // Block areas around the bottom-left finder pattern
+ blockedModules.Add(new Rectangle(0, size - 8, 8, 1)); // Horizontal block near the bottom left finder pattern
+ blockedModules.Add(new Rectangle(7, size - 7, 1, 7)); // Vertical block near the bottom left finder pattern
+ // Block areas around the top-right finder pattern
+ blockedModules.Add(new Rectangle(size - 8, 0, 1, 8)); // Vertical block near the top right finder pattern
+ blockedModules.Add(new Rectangle(size - 7, 7, 7, 1)); // Horizontal block near the top right finder pattern
+ }
}
///
@@ -243,6 +281,13 @@ public static void ReserveSeperatorAreas(int size, BlockedModules blockedModules
/// A list of rectangles representing areas that must not be overwritten.
public static void ReserveVersionAreas(int size, int version, BlockedModules blockedModules)
{
+ if (version < 0) // Micro QR codes
+ {
+ blockedModules.Add(new Rectangle(0, 8, 9, 1));
+ blockedModules.Add(new Rectangle(8, 0, 1, 8));
+ return;
+ }
+
// Reserve areas near the timing patterns for version and format information.
blockedModules.Add(new Rectangle(8, 0, 1, 6)); // Near the top timing pattern
blockedModules.Add(new Rectangle(8, 7, 1, 1)); // Small square near the top left finder pattern
@@ -267,6 +312,9 @@ public static void ReserveVersionAreas(int size, int version, BlockedModules blo
/// A list of rectangles representing areas that must not be overwritten, updated to include the dark module.
public static void PlaceDarkModule(QRCodeData qrCode, int version, BlockedModules blockedModules)
{
+ // Micro QR codes do not have a dark module
+ if (version < 0)
+ return;
// Place the dark module, which is always required to be black.
qrCode.ModuleMatrix[4 * version + 9 + 4][8 + 4] = true;
// Block the dark module area to prevent overwriting during further QR code generation steps.
@@ -283,7 +331,8 @@ public static void PlaceFinderPatterns(QRCodeData qrCode, BlockedModules blocked
var size = qrCode.ModuleMatrix.Count - 8;
// Loop to place three finder patterns in the top-left, top-right, and bottom-left corners of the QR code.
- for (var i = 0; i < 3; i++)
+ var count = qrCode.Version < 0 ? 1 : 3; // Micro QR codes have only one finder pattern.
+ for (var i = 0; i < count; i++)
{
// Calculate the x and y starting positions for each finder pattern based on the index.
var locationX = i == 1 ? size - 7 : 0; // Place at top-right if i is 1, otherwise at left side (top or bottom).
@@ -355,21 +404,41 @@ public static void PlaceAlignmentPatterns(QRCodeData qrCode, List alignme
/// A list of rectangles representing areas that must not be overwritten. Updated with the areas occupied by timing patterns.
public static void PlaceTimingPatterns(QRCodeData qrCode, BlockedModules blockedModules)
{
- var size = qrCode.ModuleMatrix.Count - 8; // Get the size of the QR code matrix.
+ // Get the size of the QR code matrix excluding padding.
+ var size = qrCode.ModuleMatrix.Count - 8;
- // Place timing patterns starting from the 8th module to the size - 8 to avoid overlapping with finder patterns.
- for (var i = 8; i < size - 8; i++)
+ if (qrCode.Version > 0)
{
- if (i % 2 == 0) // Place a dark module every other module to create the alternating pattern.
+ // Place timing patterns starting from the 8th module to the size - 8 to avoid overlapping with finder patterns.
+ for (var i = 8; i < size - 8; i++)
{
- qrCode.ModuleMatrix[6 + 4][i + 4] = true; // Horizontal timing pattern
- qrCode.ModuleMatrix[i + 4][6 + 4] = true; // Vertical timing pattern
+ if (i % 2 == 0) // Place a dark module every other module to create the alternating pattern.
+ {
+ qrCode.ModuleMatrix[6 + 4][i + 4] = true; // Horizontal timing pattern
+ qrCode.ModuleMatrix[i + 4][6 + 4] = true; // Vertical timing pattern
+ }
}
+
+ // Add the areas occupied by the timing patterns to the list of blocked modules.
+ blockedModules.Add(new Rectangle(6, 8, 1, size - 16)); // Horizontal timing pattern area
+ blockedModules.Add(new Rectangle(8, 6, size - 16, 1)); // Vertical timing pattern area
}
+ else // Micro QR codes
+ {
+ // Place timing patterns starting from the 8th module to avoid overlapping with finder patterns.
+ for (var i = 8; i < size; i++)
+ {
+ if (i % 2 == 0) // Place a dark module every other module to create the alternating pattern.
+ {
+ qrCode.ModuleMatrix[4][i + 4] = true; // Horizontal timing pattern
+ qrCode.ModuleMatrix[i + 4][4] = true; // Vertical timing pattern
+ }
+ }
- // Add the areas occupied by the timing patterns to the list of blocked modules.
- blockedModules.Add(new Rectangle(6, 8, 1, size - 16)); // Horizontal timing pattern area
- blockedModules.Add(new Rectangle(8, 6, size - 16, 1)); // Vertical timing pattern area
+ // Add the areas occupied by the timing patterns to the list of blocked modules.
+ blockedModules.Add(new Rectangle(0, 8, 1, size - 8)); // Horizontal timing pattern area
+ blockedModules.Add(new Rectangle(8, 0, size - 8, 1)); // Vertical timing pattern area
+ }
}
}
}
diff --git a/QRCoder/QRCodeGenerator/Polynom.cs b/QRCoder/QRCodeGenerator/Polynom.cs
index 6fb3c302..6cb6115d 100644
--- a/QRCoder/QRCodeGenerator/Polynom.cs
+++ b/QRCoder/QRCodeGenerator/Polynom.cs
@@ -154,8 +154,9 @@ public override string ToString()
{
var sb = new StringBuilder();
- foreach (var polyItem in _polyItems)
+ for (int i = 0; i < Count; i++)
{
+ var polyItem = _polyItems[i];
sb.Append("a^" + polyItem.Coefficient + "*x^" + polyItem.Exponent + " + ");
}
diff --git a/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt b/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt
index 0a75bc99..20c384af 100644
--- a/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt
+++ b/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt
@@ -894,6 +894,7 @@ namespace QRCoder
public QRCoder.QRCodeData CreateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
public QRCoder.QRCodeData CreateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
public void Dispose() { }
+ public static QRCoder.QRCodeData GenerateMicroQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel = -1, int requestedVersion = 0) { }
public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload) { }
public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
public static QRCoder.QRCodeData GenerateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
diff --git a/QRCoderApiTests/net60-windows/QRCoder.approved.txt b/QRCoderApiTests/net60-windows/QRCoder.approved.txt
index 99b5f961..5c4386b0 100644
--- a/QRCoderApiTests/net60-windows/QRCoder.approved.txt
+++ b/QRCoderApiTests/net60-windows/QRCoder.approved.txt
@@ -902,6 +902,7 @@ namespace QRCoder
public QRCoder.QRCodeData CreateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
public QRCoder.QRCodeData CreateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
public void Dispose() { }
+ public static QRCoder.QRCodeData GenerateMicroQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel = -1, int requestedVersion = 0) { }
public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload) { }
public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
public static QRCoder.QRCodeData GenerateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
diff --git a/QRCoderApiTests/net60/QRCoder.approved.txt b/QRCoderApiTests/net60/QRCoder.approved.txt
index 6d269d08..8ee10f23 100644
--- a/QRCoderApiTests/net60/QRCoder.approved.txt
+++ b/QRCoderApiTests/net60/QRCoder.approved.txt
@@ -836,6 +836,7 @@ namespace QRCoder
public QRCoder.QRCodeData CreateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
public QRCoder.QRCodeData CreateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
public void Dispose() { }
+ public static QRCoder.QRCodeData GenerateMicroQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel = -1, int requestedVersion = 0) { }
public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload) { }
public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
public static QRCoder.QRCodeData GenerateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
diff --git a/QRCoderApiTests/netstandard13/QRCoder.approved.txt b/QRCoderApiTests/netstandard13/QRCoder.approved.txt
index 343903e1..4b1075b0 100644
--- a/QRCoderApiTests/netstandard13/QRCoder.approved.txt
+++ b/QRCoderApiTests/netstandard13/QRCoder.approved.txt
@@ -800,6 +800,7 @@ namespace QRCoder
public QRCoder.QRCodeData CreateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
public QRCoder.QRCodeData CreateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
public void Dispose() { }
+ public static QRCoder.QRCodeData GenerateMicroQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel = -1, int requestedVersion = 0) { }
public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload) { }
public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
public static QRCoder.QRCodeData GenerateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { }
diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs
index b2ec4441..2f5921cd 100644
--- a/QRCoderTests/QRGeneratorTests.cs
+++ b/QRCoderTests/QRGeneratorTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -41,6 +42,22 @@ public void validate_antilogtable()
}
#if !NETFRAMEWORK // [Theory] is not supported in xunit < 2.0.0
+ [Theory]
+ [InlineData("54321", ECCLevel.Default, "ZfnO93tpy9jjaACKXue2VsACXxY", 11)] //verified
+ [InlineData("00000000", ECCLevel.M, "lEBc3nKaK0UpMfenT5FTX02Zgfg", 13)] //verified
+ [InlineData("123456789", ECCLevel.L, "gCY4Cj1uLhI/0JjWG1F9kC4S1+I", 13)] //verified
+ [InlineData("abcd56789012345", ECCLevel.L, "kqcKfCCdu1VTjjtsmK4iBav9FTs", 17)] //verified
+ [InlineData("abc", ECCLevel.M, "334sxrtY5KkNZRGj1pBgb87/cFc", 15)] //reads fine, but unable to verify repeating pattern
+ public void validate_micro_qr_code(string input, ECCLevel eccLevel, string expectedHash, int expectedSize)
+ {
+ var qrData = QRCodeGenerator.GenerateMicroQrCode(input, eccLevel);
+ (qrData.ModuleMatrix.Count - 8).ShouldBe(expectedSize); // exclude padding
+ var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray());
+ var hash = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(result));
+ var hashString = Convert.ToBase64String(hash);
+ hashString.TrimEnd('=').ShouldBe(expectedHash);
+ }
+
[Theory]
// version 1 numeric
[InlineData("1", "KWw84nkWZLMh5LqAJ/4s/4mW/08", 21)]