Skip to content

Commit ce0fad9

Browse files
committed
Optimize segments for structured append
1 parent 3546547 commit ce0fad9

File tree

9 files changed

+556
-253
lines changed

9 files changed

+556
-253
lines changed

QrCodeGenerator/BitArrayExtensions.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,45 @@ public static void AppendData(this BitArray bitArray, BitArray otherArray)
9898
}
9999
}
100100

101+
/// <summary>
102+
/// Extracts the specified number of bits at the specified index in this bit array.
103+
/// <para>
104+
/// The bit at index <paramref name="index"/> becomes the most significant bit of the result,
105+
/// The bit at index <paramref name="index"/> + <paramref name="len"/> - 1 becomes the least significant bit.
106+
/// </para>
107+
/// <para>
108+
/// Requires 0 &#x2264; <em>len</em> &#x2264; 31, 0 &#x2264; <em>index</em>, and <em>index + len</em> &#x2264; <em>bit array length</em>.
109+
/// </para>
110+
/// </summary>
111+
/// <param name="bitArray">The BitArray instance that this method extends.</param>
112+
/// <param name="index">The index of the first bit to extract.</param>
113+
/// <param name="len">The number of bits to extract.</param>
114+
/// <returns>The extracted bits as an unsigned integer.</returns>
115+
/// <exception cref="ArgumentOutOfRangeException">Index or length is out of range.</exception>
116+
public static uint ExtractBits(this BitArray bitArray, int index, int len)
117+
{
118+
if (len < 0 || len > 31)
119+
{
120+
throw new ArgumentOutOfRangeException(nameof(len), "'len' out of range");
121+
}
122+
123+
if (index < 0 || index + len > bitArray.Length)
124+
{
125+
throw new ArgumentOutOfRangeException(nameof(index), "'index' out of range");
126+
}
127+
128+
uint result = 0;
129+
for (var i = 0; i < len; i++)
130+
{
131+
result <<= 1;
132+
if (bitArray.Get(index + i))
133+
{
134+
result |= 1;
135+
}
136+
}
137+
138+
return result;
139+
}
101140
}
102141
}
103142

QrCodeGenerator/Graphics.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ internal byte[] ToBmpBitmap(int border, int scale, int foreground, int backgroun
170170
// Background - Light
171171
buf[58] = (byte)background; // blue
172172
buf[59] = (byte)(background >> 8); // green
173-
buf[60] = (byte)(background >> 16); // red;
173+
buf[60] = (byte)(background >> 16); // red
174174

175175
var scaledBorder = border * scale;
176176

@@ -247,9 +247,9 @@ internal byte[] ToBmpBitmap(int border, int scale, int foreground, int backgroun
247247
}
248248

249249
// Append a SVG/XAML path for the QR code to the provided string builder
250-
private void CreatePath(StringBuilder path, bool[,] modules, int border)
250+
private static void CreatePath(StringBuilder path, bool[,] modules, int border)
251251
{
252-
// Simple algorithms to reduce the number of rectangles for drawing the QR code
252+
// Simple algorithm to reduce the number of rectangles for drawing the QR code
253253
// and reduce SVG/XAML size.
254254
var size = modules.GetLength(0);
255255
for (var y = 0; y < size; y++)
@@ -297,7 +297,7 @@ private static void DrawLargestRectangle(StringBuilder path, bool[,] modules, in
297297
// append path command
298298
if (x != 0 || y != 0)
299299
{
300-
path.Append(" ");
300+
path.Append(' ');
301301
}
302302

303303
// Different locales use different minus signs.

QrCodeGenerator/QrCode.cs

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
using System.Collections.Generic;
3131
using System.Diagnostics;
3232
using System.Linq;
33-
using System.Text;
3433

3534
namespace Net.Codecrete.QrCodeGenerator
3635
{
@@ -101,9 +100,14 @@ public static QrCode EncodeText(string text, Ecc ecl)
101100
/// If multiple QR codes are required, <em>Structured Append</em> data is included to link the QR codes.
102101
/// </para>
103102
/// <para>
104-
/// The text is split at character boundaries even though the underlying UTF-8 encoding requires
105-
/// multiple bytes for some characters. This increases compatibility with QR code scanners assuming
106-
/// that each individual QR codes contains a valid UTF-8 string.
103+
/// Each QR code might use multiple segments with different encoding modes
104+
/// to maximize the amount of text that can be stored in each QR code.
105+
/// </para>
106+
/// <para>
107+
/// Each QR code will contain a valid string as it is ensured that splitting only occurs
108+
/// at character boundaries and not in the middle of a multi-byte encoding of a character.
109+
/// This increases compatibility with QR code scanners that incorrectly assume that each
110+
/// individual QR code in the series contains a valid UTF-8 string.
107111
/// </para>
108112
/// </summary>
109113
/// <param name="text">The text to be encoded. The full range of Unicode characters may be used.</param>
@@ -118,36 +122,17 @@ public static List<QrCode> EncodeTextInMultipleCodes(string text, Ecc ecl, int v
118122
Objects.RequireNonNull(text);
119123
Objects.RequireNonNull(ecl);
120124

121-
var textBytes = Encoding.UTF8.GetBytes(text);
122-
var numCharCountBits = QrSegment.Mode.Byte.NumCharCountBits(version);
123-
var numDataCodewords = GetNumDataCodewords(version, ecl);
124-
125-
if ((numCharCountBits + 7) / 8 + textBytes.Length <= numDataCodewords)
126-
{
127-
// text is short enough to fit into a single QR code
128-
var segment = QrSegment.MakeBytes(textBytes);
129-
var qrCode = EncodeSegments(new List<QrSegment> { segment }, ecl, minVersion: version, maxVersion: version, boostEcl: boostEcl);
125+
// Test if text fits in a single QR code
126+
try {
127+
var segments = QrSegmentAdvanced.MakeSegmentsOptimally(text, ecl, version, version);
128+
var qrCode = EncodeSegments(segments, ecl, minVersion: version, maxVersion: version, boostEcl: boostEcl);
130129
return new List<QrCode> { qrCode };
131-
}
132-
133-
var overhead = (2 * 4 + numCharCountBits + 16 + 7) / 8; // 2 mode indicators + char count + structured append + byte alignment
134-
var maxSliceSize = numDataCodewords - overhead;
135-
int numSlices = (textBytes.Length + maxSliceSize - 1) / maxSliceSize;
136-
137-
var segments = QrSegment.MakeStructuredAppendSegments(textBytes, numSlices, considerUtf8Boundaries: true);
138-
if (segments.Max(s => s[1].NumChars) > maxSliceSize)
139-
{
140-
numSlices++;
141-
segments = QrSegment.MakeStructuredAppendSegments(textBytes, numSlices, considerUtf8Boundaries: true);
142-
}
143-
144-
if (numSlices > 16)
130+
} catch (DataTooLongException)
145131
{
146-
throw new DataTooLongException("Text is too long to fit in 16 QR codes with the given version and ECL");
132+
// Continue with multiple QR codes
147133
}
148134

149-
int balancedSliceSize = (textBytes.Length + numSlices - 1) / numSlices;
150-
return segments
135+
return QrSegmentAdvanced.MakeSegmentsForMultipleCodes(text, ecl, version)
151136
.Select(segmentList => EncodeSegments(segmentList, ecl, minVersion: version, maxVersion: version, boostEcl: boostEcl))
152137
.ToList();
153138
}
@@ -753,7 +738,7 @@ private byte[] AddEccAndInterleave(byte[] data)
753738
Objects.RequireNonNull(data);
754739
if (data.Length != GetNumDataCodewords(Version, ErrorCorrectionLevel))
755740
{
756-
throw new ArgumentOutOfRangeException();
741+
throw new ArgumentOutOfRangeException(nameof(data), "Length of data does not match version and ecl");
757742
}
758743

759744
// Calculate parameter numbers
@@ -801,7 +786,7 @@ private void DrawCodewords(byte[] data)
801786
Objects.RequireNonNull(data);
802787
if (data.Length != GetNumRawDataModules(Version) / 8)
803788
{
804-
throw new ArgumentOutOfRangeException();
789+
throw new ArgumentOutOfRangeException(nameof(data), "data length does not match version");
805790
}
806791

807792
var i = 0; // Bit index into the data
@@ -865,7 +850,7 @@ private void ApplyMask(uint mask)
865850
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
866851
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
867852
}
868-
_modules[y, x] ^= invert & !_isFunction[y, x];
853+
_modules[y, x] ^= invert && !_isFunction[y, x];
869854
}
870855
}
871856
}

0 commit comments

Comments
 (0)