public static List <QrSegment> MakeSegmentsOptimally(string text, Ecc ecl, int minVersion, int maxVersion) { Utils.CheckNull(text, nameof(text)); if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION)) { throw new ArgumentException("Invalid value"); } List <QrSegment> segs = null; var codePoints = ToCodePoints(text); for (int version = minVersion; ; version++) { if (version == minVersion || version == 10 || version == 27) { segs = MakeSegmentsOptimally(codePoints, version); } var dataCapacityBits = QrCode.GetNumDataCodewords(version, ecl) * 8; var dataUsedBits = QrSegment.GetTotalBits(segs, version); if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) { return(segs); } if (version >= maxVersion) { var msg = "Segment too long"; if (dataUsedBits != -1) { msg = String.Format("Data length = {0} bits, Max capacity = {1} bits", dataUsedBits, dataCapacityBits); } throw new DataTooLongException(msg); } } }
/// <summary> /// Creates a QR code representing the specified segments with the specified encoding parameters. /// <para> /// The smallest possible QR code version (size) is used. The range of versions can be /// restricted by the <paramref name="minVersion"/> and <paramref name="maxVersion"/> parameters. /// </para> /// <para> /// If <paramref name="boostEcl"/> is <c>true</c>, the resulting ECC level will be higher than the /// one specified if it can be achieved without increasing the size (version). /// </para> /// <para> /// The QR code mask is usually automatically chosen. It can be explicitly set with the <paramref name="mask"/> /// parameter by using a value between 0 to 7 (inclusive). -1 is for automatic mode (which may be slow). /// </para> /// <para> /// 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 and gives full control over all /// encoding paramters. /// </para> /// </summary> /// <remarks> /// This is a mid-level API; the high-level APIs are <see cref="EncodeText(string, Ecc)"/> /// and <see cref="EncodeBinary(byte[], Ecc)"/>. /// </remarks> /// <param name="segments">The segments to encode.</param> /// <param name="ecl">The minimal or fixed error correction level to use .</param> /// <param name="minVersion">The minimum version (size) of the QR code (between 1 and 40).</param> /// <param name="maxVersion">The maximum version (size) of the QR code (between 1 and 40).</param> /// <param name="mask">The mask number to use (between 0 and 7), or -1 for automatic mask selection.</param> /// <param name="boostEcl">If <c>true</c> the ECC level wil be increased if it can be achieved without increasing the size (version).</param> /// <returns>The created QR code representing the segments.</returns> /// <exception cref="ArgumentNullException"><paramref name="segments"/>, any list element, or <paramref name="ecl"/> is <c>null</c>.</exception> /// <exception cref="ArgumentOutOfRangeException">1 ≤ minVersion ≤ maxVersion ≤ 40 /// or -1 ≤ mask ≤ 7 is violated.</exception> /// <exception cref="DataTooLongException">The segments are too long to fit in the largest QR code size (version) /// at the specified error correction level.</exception> public static QrCode EncodeSegments(List <QrSegment> segments, Ecc ecl, int minVersion = MinVersion, int maxVersion = MaxVersion, int mask = -1, bool boostEcl = true) { Objects.RequireNonNull(segments); 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(segments, 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 segments) { 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)); }
public static QrCode EncodeSegments(List <QrSegment> segs, Ecc ecl, int minVersion, int maxVersion, int mask, bool boostEcl) { Utils.CheckNull(segs, nameof(segs)); if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) { throw new ArgumentException("Invalid value"); } int version, dataUsedBits; for (version = minVersion; ; version++) { var capacityBits = GetNumDataCodewords(version, ecl) * 8; dataUsedBits = QrSegment.GetTotalBits(segs, version); if (dataUsedBits != -1 && dataUsedBits <= capacityBits) { break; } if (version >= maxVersion) { var msg = "Segment too long"; if (dataUsedBits != -1) { msg = string.Format("Data length = {0} bits, Max capacity = {1} bits", dataUsedBits, capacityBits); } throw new DataTooLongException(msg); } } foreach (var newEcl in new Ecc[] { Ecc.Low, Ecc.Medium, Ecc.Quartitle, Ecc.High }) { if (boostEcl && dataUsedBits <= GetNumDataCodewords(version, newEcl) * 8) { ecl = newEcl; } } var bb = new BitBuffer(); foreach (var seg in segs) { bb.AppendBits(seg.Mode.ModeBits, 4); bb.AppendBits(seg.NumChars, seg.Mode.NumCharCountBits(version)); bb.AppendData(seg.Data); } var dataCapacityBits = GetNumDataCodewords(version, ecl) * 8; bb.AppendBits(0, Math.Min(4, dataCapacityBits - bb.Length)); bb.AppendBits(0, (8 - bb.Length % 8) % 8); for (var padByte = 0xEC; bb.Length < dataCapacityBits; padByte ^= 0xEC ^ 0x11) { bb.AppendBits(padByte, 8); } var dataCodewords = new byte[bb.Length / 8]; for (var i = 0; i < bb.Length; i++) { dataCodewords[i >> 3] = (byte)(dataCodewords[i >> 3] | bb.GetBit(i) << (7 - (i & 7))); } // Create the QR Code object return(new QrCode(version, ecl, dataCodewords, mask)); }