@@ -87,6 +87,48 @@ public static QrSegment MakeBytes(byte[] data)
8787 }
8888
8989
90+ /// <summary>
91+ /// Creates a segment representing the specified binary data
92+ /// encoded in byte mode. All input byte arrays are acceptable.
93+ /// <para>
94+ /// Any text string can be converted to UTF-8 bytes (using <c>Encoding.UTF8.GetBytes(str)</c>)
95+ /// and encoded as a byte mode segment.
96+ /// </para>
97+ /// </summary>
98+ /// <param name="data">The binary data to encode.</param>
99+ /// <returns>The created segment containing the specified data.</returns>
100+ /// <exception cref="ArgumentNullException"><c>data</c> is <c>null</c>.</exception>
101+ public static QrSegment MakeBytes ( ArraySegment < byte > data )
102+ {
103+ Objects . RequireNonNull ( data ) ;
104+ var ba = new BitArray ( 0 ) ;
105+ foreach ( var b in data )
106+ {
107+ ba . AppendBits ( b , 8 ) ;
108+ }
109+
110+ return new QrSegment ( Mode . Byte , data . Count , ba ) ;
111+ }
112+
113+
114+ /// <summary>
115+ /// Creates a segment for the structured append header.
116+ /// </summary>
117+ /// <param name="parity">The parity value.</param>
118+ /// <param name="position">The position of the code within the sequence of codes (1 based).</param>
119+ /// <param name="total">The total number of codes in the sequence of codes.</param>
120+ /// <returns>The created segment containing the specified header.</returns>
121+ /// <exception cref="ArgumentNullException"><c>data</c> is <c>null</c>.</exception>
122+ private static QrSegment MakeStructuredAppend ( byte parity , int position , int total )
123+ {
124+ var bitArray = new BitArray ( 0 ) ;
125+ bitArray . AppendBits ( ( uint ) position - 1 , 4 ) ;
126+ bitArray . AppendBits ( ( uint ) total - 1 , 4 ) ;
127+ bitArray . AppendBits ( parity , 8 ) ;
128+ return new QrSegment ( Mode . StructuredAppend , 0 , bitArray ) ;
129+ }
130+
131+
90132 /// <summary>
91133 /// Creates a segment representing the specified string of decimal digits.
92134 /// The segment is encoded in numeric mode.
@@ -195,6 +237,77 @@ public static List<QrSegment> MakeSegments(string text)
195237 }
196238
197239
240+ /// <summary>
241+ /// Creates the segments represeting the specified binary data,
242+ /// split into multiple QR codes (Structured Append).
243+ /// <para>
244+ /// The outer list represents the series of QR codes to be created.
245+ /// The inner lists contains the QR segments for each QR code.
246+ /// Each inner list contains at least two segments: the structured append segment and the binary data.
247+ /// </para>
248+ /// <para>
249+ /// The data is split into slices of similar size.
250+ /// </para>
251+ /// <para>
252+ /// If <paramref name="considerUtf8Boundaries"/> is set to <c>true</c>, the provided data is interpreted
253+ /// as UTF-8 encoded text and the data is split only at UTF-8 character boundaries, i.e. no split occurs
254+ /// in the middle of a multi-byte UTF-8 character sequence.
255+ /// </para>
256+ /// <para>
257+ /// The structured append segment specifies the number of QR codes (0-15), the position within
258+ /// those (0-15) and a parity which needs to be the same for all.
259+ /// </para>
260+ /// </summary>
261+ /// <param name="data">The binary data to encode in multiple QR codes</param>
262+ /// <param name="numberOfCodes">The number of codes to create.</param>
263+ /// <param name="considerUtf8Boundaries">If set to <c>true</c>, splits are made only at UTF-8 character boundaries.</param>
264+ /// <returns>A list of list of QR segments.</returns>
265+ public static List < List < QrSegment > > MakeStructuredAppendSegments ( byte [ ] data , int numberOfCodes , bool considerUtf8Boundaries = false )
266+ {
267+ var result = new List < List < QrSegment > > ( ) ;
268+
269+ byte parity = 0 ;
270+ foreach ( var val in data )
271+ {
272+ parity ^= ( byte ) ( val >> 8 ) ;
273+ }
274+
275+ var startOfSlice = 0 ;
276+ var length = ( long ) data . Length ;
277+ for ( int position = 1 ; position <= numberOfCodes ; position += 1 )
278+ {
279+ var endOfSlice = ( int ) ( length * position / numberOfCodes ) ;
280+ if ( considerUtf8Boundaries )
281+ {
282+ endOfSlice = NextCharacterBoundary ( data , endOfSlice ) ;
283+ }
284+ if ( startOfSlice == endOfSlice )
285+ {
286+ continue ;
287+ }
288+
289+ result . Add ( new List < QrSegment >
290+ {
291+ MakeStructuredAppend ( parity , position , numberOfCodes ) ,
292+ MakeBytes ( new ArraySegment < byte > ( data , startOfSlice , endOfSlice - startOfSlice ) )
293+ } ) ;
294+
295+ startOfSlice = endOfSlice ;
296+ }
297+
298+ return result ;
299+ }
300+
301+ private static int NextCharacterBoundary ( byte [ ] data , int startIndex )
302+ {
303+ while ( startIndex < data . Length && ( data [ startIndex ] & 0xC0 ) == 0x80 )
304+ {
305+ startIndex += 1 ;
306+ }
307+ return startIndex ;
308+ }
309+
310+
198311 /// <summary>
199312 /// Creates a segment representing an Extended Channel Interpretation
200313 /// (ECI) designator with the specified assignment value.
@@ -333,9 +446,16 @@ public BitArray GetData()
333446 }
334447
335448
336- // Calculates the number of bits needed to encode the given segments at the given version.
337- // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too
338- // many characters to fit its length field, or the total bits exceeds int.MaxValue.
449+ /// <summary>
450+ /// Calculates the number of bits needed to encode the given segments.
451+ /// <para>
452+ /// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too
453+ /// many characters to fit its length field, or the total bits exceeds int.MaxValue.
454+ /// </para>
455+ /// </summary>
456+ /// <param name="segments">The segements.</param>
457+ /// <param name="version">The version number.</param>
458+ /// <returns>The number of bits, or -1.</returns>
339459 internal static int GetTotalBits ( List < QrSegment > segments , int version )
340460 {
341461 Objects . RequireNonNull ( segments ) ;
@@ -417,6 +537,11 @@ public sealed class Mode
417537 /// <value>ECI encoding mode.</value>
418538 public static readonly Mode Eci = new Mode ( 0x7 , 0 , 0 , 0 ) ;
419539
540+ /// <summary>
541+ /// Structured append encoding mode.
542+ /// </summary>
543+ /// <value>Structured append encoding mode.</value>
544+ public static readonly Mode StructuredAppend = new Mode ( 0x3 , 0 , 0 , 0 ) ;
420545
421546 /// <summary>
422547 /// Mode indicator value.
0 commit comments