Revamped QrCode.encodeSegments() to add parameters to make a much richer API, in all language versions; updated JavaScript demo script to handle new semantics.

This commit is contained in:
Nayuki Minase 2016-04-16 03:53:58 +00:00
parent ca7e7a60a7
commit 5692e951dd
9 changed files with 205 additions and 113 deletions

View File

@ -55,34 +55,34 @@ qrcodegen::QrCode qrcodegen::QrCode::encodeBinary(const std::vector<uint8_t> &da
}
qrcodegen::QrCode qrcodegen::QrCode::encodeSegments(const std::vector<QrSegment> &segs, const Ecc &ecl) {
// Find the minimal version number to use
int version, dataCapacityBits;
for (version = 1; ; version++) { // Increment until the data fits in the QR Code
if (version > 40) // All versions could not fit the given data
throw "Data too long";
dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
qrcodegen::QrCode qrcodegen::QrCode::encodeSegments(const std::vector<QrSegment> &segs, const Ecc &ecl,
int minVersion, int maxVersion, int mask, bool boostEcl) {
if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7)
throw "Invalid value";
// Calculate the total number of bits needed at this version number
// to encode all the segments (i.e. segment metadata and payloads)
int dataUsedBits = 0;
for (size_t i = 0; i < segs.size(); i++) {
const QrSegment &seg(segs.at(i));
if (seg.numChars < 0)
throw "Assertion error";
int ccbits = seg.mode.numCharCountBits(version);
if (seg.numChars >= (1 << ccbits)) {
// Segment length value doesn't fit in the length field's bit-width, so fail immediately
goto continueOuter;
}
dataUsedBits += 4 + ccbits + seg.bitLength;
}
if (dataUsedBits <= dataCapacityBits)
// Find the minimal version number to use
int version, dataUsedBits;
for (version = minVersion; ; version++) {
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
dataUsedBits = QrSegment::getTotalBits(segs, version);
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
break; // This version number is found to be suitable
continueOuter:;
if (version >= maxVersion) // All versions in the range could not fit the given data
throw "Data too long";
}
if (dataUsedBits == -1)
throw "Assertion error";
// Increase the error correction level while the data still fits in the current version number
const Ecc *newEcl = &ecl;
if (boostEcl) {
if (dataUsedBits <= getNumDataCodewords(version, Ecc::MEDIUM ) * 8) newEcl = &Ecc::MEDIUM ;
if (dataUsedBits <= getNumDataCodewords(version, Ecc::QUARTILE) * 8) newEcl = &Ecc::QUARTILE;
if (dataUsedBits <= getNumDataCodewords(version, Ecc::HIGH ) * 8) newEcl = &Ecc::HIGH ;
}
// Create the data bit string by concatenating all segments
int dataCapacityBits = getNumDataCodewords(version, *newEcl) * 8;
BitBuffer bb;
for (size_t i = 0; i < segs.size(); i++) {
const QrSegment &seg(segs.at(i));
@ -102,7 +102,7 @@ qrcodegen::QrCode qrcodegen::QrCode::encodeSegments(const std::vector<QrSegment>
throw "Assertion error";
// Create the QR Code symbol
return QrCode(version, ecl, bb.getBytes(), -1);
return QrCode(version, *newEcl, bb.getBytes(), mask);
}

View File

@ -83,13 +83,14 @@ public:
/*
* Returns a QR Code symbol representing the given data segments at the given error
* correction level. The smallest possible QR Code version is automatically chosen for the output.
* Returns a QR Code symbol representing the specified data segments with the specified encoding parameters.
* The smallest possible QR Code version within the specified range is automatically chosen for the output.
* This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and binary) to encode text more efficiently. This
* function is considered to be lower level than simply encoding text or binary data.
* between modes (such as alphanumeric and binary) to encode text more efficiently.
* This function is considered to be lower level than simply encoding text or binary data.
*/
static QrCode encodeSegments(const std::vector<QrSegment> &segs, const Ecc &ecl);
static QrCode encodeSegments(const std::vector<QrSegment> &segs, const Ecc &ecl,
int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters

View File

@ -22,6 +22,7 @@
* Software.
*/
#include <cstddef>
#include "BitBuffer.hpp"
#include "QrSegment.hpp"
@ -128,6 +129,22 @@ qrcodegen::QrSegment::QrSegment(const Mode &md, int numCh, const std::vector<uin
}
int qrcodegen::QrSegment::getTotalBits(const std::vector<QrSegment> &segs, int version) {
if (version < 1 || version > 40)
throw "Version number out of range";
int result = 0;
for (size_t i = 0; i < segs.size(); i++) {
const QrSegment &seg(segs.at(i));
int ccbits = seg.mode.numCharCountBits(version);
// Fail if segment length value doesn't fit in the length field's bit-width
if (seg.numChars >= (1 << ccbits))
return -1;
result += 4 + ccbits + seg.bitLength;
}
return result;
}
bool qrcodegen::QrSegment::isAlphanumeric(const char *text) {
for (; *text != '\0'; text++) {
char c = *text;

View File

@ -151,6 +151,10 @@ public:
QrSegment(const Mode &md, int numCh, const std::vector<uint8_t> &b, int bitLen);
// Package-private helper function.
static int getTotalBits(const std::vector<QrSegment> &segs, int version);
/*---- Constant ----*/
private:

View File

@ -76,49 +76,66 @@ public final class QrCode {
/**
* Returns a QR Code symbol representing the specified data segments at the specified error
* correction level. The smallest possible QR Code version is automatically chosen for the output.
* Returns a QR Code symbol representing the specified data segments at the specified error correction
* level or higher. The smallest possible QR Code version is automatically chosen for the output.
* <p>This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and binary) to encode text more efficiently. This
* function is considered to be lower level than simply encoding text or binary data.</p>
* @param segs the segments to encode
* @param ecl the error correction level to use
* @param ecl the error correction level to use (will be boosted)
* @return a QR Code representing the segments
* @throws NullPointerException if the list of segments, a segment, or the error correction level is {@code null}
* @throws IllegalArgumentException if the data fails to fit in the largest version QR Code, which means it is too long
* @throws IllegalArgumentException if the data is too long to fit in the largest version QR Code at the ECL
*/
public static QrCode encodeSegments(List<QrSegment> segs, Ecc ecl) {
return encodeSegments(segs, ecl, 1, 40, -1, true);
}
/**
* Returns a QR Code symbol representing the specified data segments with the specified encoding parameters.
* The smallest possible QR Code version within the specified range is automatically chosen for the output.
* <p>This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and binary) to encode text more efficiently.
* This function is considered to be lower level than simply encoding text or binary data.</p>
* @param segs the segments to encode
* @param ecl the error correction level to use (may be boosted)
* @param minVersion the minimum allowed version of the QR symbol (at least 1)
* @param maxVersion the maximum allowed version of the QR symbol (at most 40)
* @param mask the mask pattern to use, which is either -1 for automatic choice or from 0 to 7 for fixed choice
* @param boostEcl increases the error correction level if it can be done without increasing the version number
* @return a QR Code representing the segments
* @throws NullPointerException if the list of segments, a segment, or the error correction level is {@code null}
* @throws IllegalArgumentException if 1 &le; minVersion &le; maxVersion &le; 40 is violated, or if mask
* &lt; &minus;1 or mask > 7, or if the data is too long to fit in a QR Code at maxVersion at the ECL
*/
public static QrCode encodeSegments(List<QrSegment> segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) {
if (segs == null || ecl == null)
throw new NullPointerException();
if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7)
throw new IllegalArgumentException("Invalid value");
// Find the minimal version number to use
int version, dataCapacityBits;
outer:
for (version = 1; ; version++) { // Increment until the data fits in the QR Code
if (version > 40) // All versions could not fit the given data
throw new IllegalArgumentException("Data too long");
dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
// Calculate the total number of bits needed at this version number
// to encode all the segments (i.e. segment metadata and payloads)
int dataUsedBits = 0;
for (QrSegment seg : segs) {
if (seg == null)
throw new NullPointerException();
if (seg.numChars < 0)
throw new AssertionError();
int ccbits = seg.mode.numCharCountBits(version);
if (seg.numChars >= (1 << ccbits)) {
// Segment length value doesn't fit in the length field's bit-width, so fail immediately
continue outer;
}
dataUsedBits += 4 + ccbits + seg.bitLength;
}
if (dataUsedBits <= dataCapacityBits)
int version, dataUsedBits;
for (version = minVersion; ; version++) {
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
dataUsedBits = QrSegment.getTotalBits(segs, version);
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
break; // This version number is found to be suitable
if (version >= maxVersion) // All versions in the range could not fit the given data
throw new IllegalArgumentException("Data too long");
}
if (dataUsedBits == -1)
throw new AssertionError();
// Increase the error correction level while the data still fits in the current version number
for (Ecc newEcl : Ecc.values()) {
if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8)
ecl = newEcl;
}
// Create the data bit string by concatenating all segments
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
BitBuffer bb = new BitBuffer();
for (QrSegment seg : segs) {
bb.appendBits(seg.mode.modeBits, 4);
@ -137,7 +154,7 @@ public final class QrCode {
throw new AssertionError();
// Create the QR Code symbol
return new QrCode(version, ecl, bb.getBytes(), -1);
return new QrCode(version, ecl, bb.getBytes(), mask);
}
@ -732,7 +749,8 @@ public final class QrCode {
* Represents the error correction level used in a QR Code symbol.
*/
public enum Ecc {
// Constants declared in ascending order of error protection.
// These enum constants must be declared in ascending order of error protection,
// for the sake of the implicit ordinal() method and values() function.
LOW(1), MEDIUM(0), QUARTILE(3), HIGH(2);
// In the range 0 to 3 (unsigned 2-bit integer).

View File

@ -186,6 +186,27 @@ public final class QrSegment {
}
// Package-private helper function.
static int getTotalBits(List<QrSegment> segs, int version) {
if (segs == null)
throw new NullPointerException();
if (version < 1 || version > 40)
throw new IllegalArgumentException("Version number out of range");
int result = 0;
for (QrSegment seg : segs) {
if (seg == null)
throw new NullPointerException();
int ccbits = seg.mode.numCharCountBits(version);
// Fail if segment length value doesn't fit in the length field's bit-width
if (seg.numChars >= (1 << ccbits))
return -1;
result += 4 + ccbits + seg.bitLength;
}
return result;
}
/*---- Constants ----*/
/** Can test whether a string is encodable in numeric mode (such as by using {@link #makeNumeric(String)}). */

View File

@ -88,7 +88,8 @@ function redrawQrCode() {
segs.forEach(function(seg) {
databits += 4 + seg.getMode().numCharCountBits(qr.getVersion()) + seg.getBits().length;
});
stats += ", data bits = " + databits + ".";
stats += ", error correction = level " + "LMQH".charAt(qr.getErrorCorrectionLevel().ordinal) + ", ";
stats += "data bits = " + databits + ".";
var elem = document.getElementById("statistics-output");
while (elem.firstChild != null)
elem.removeChild(elem.firstChild);

View File

@ -29,7 +29,8 @@
* Module "qrcodegen". Public members inside this namespace:
* - Function encodeText(str text, QrCode.Ecc ecl) -> QrCode
* - Function encodeBinary(list<int> data, QrCode.Ecc ecl) -> QrCode
* - Function encodeSegments(list<QrSegment> segs, QrCode.Ecc ecl) -> QrCode
* - Function encodeSegments(list<QrSegment> segs, QrCode.Ecc ecl,
* int minVersion=1, int maxVersion=40, mask=-1, boostEcl=true) -> QrCode
* - Class QrCode:
* - Constructor QrCode(QrCode qr, int mask)
* - Constructor QrCode(list<int> bytes, int mask, int version, QrCode.Ecc ecl)
@ -84,40 +85,39 @@ var qrcodegen = new function() {
/*
* Returns a QR Code symbol representing the given data segments at the given error
* correction level. The smallest possible QR Code version is automatically chosen for the output.
* Returns a QR Code symbol representing the specified data segments with the specified encoding parameters.
* The smallest possible QR Code version within the specified range is automatically chosen for the output.
* This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and binary) to encode text more efficiently. This
* function is considered to be lower level than simply encoding text or binary data.
* between modes (such as alphanumeric and binary) to encode text more efficiently.
* This function is considered to be lower level than simply encoding text or binary data.
*/
this.encodeSegments = function(segs, ecl) {
// Find the minimal version number to use
var version, dataCapacityBits;
outer:
for (version = 1; ; version++) { // Increment until the data fits in the QR Code
if (version > 40) // All versions could not fit the given data
throw "Data too long";
dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available
this.encodeSegments = function(segs, ecl, minVersion, maxVersion, mask, boostEcl) {
if (minVersion == undefined) minVersion = 1;
if (maxVersion == undefined) maxVersion = 40;
if (mask == undefined) mask = -1;
if (boostEcl == undefined) boostEcl = true;
if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7)
throw "Invalid value";
// Calculate the total number of bits needed at this version number
// to encode all the segments (i.e. segment metadata and payloads)
var dataUsedBits = 0;
for (var i = 0; i < segs.length; i++) {
var seg = segs[i];
if (seg.numChars < 0)
throw "Assertion error";
var ccbits = seg.getMode().numCharCountBits(version);
if (seg.getNumChars() >= (1 << ccbits)) {
// Segment length value doesn't fit in the length field's bit-width, so fail immediately
continue outer;
}
dataUsedBits += 4 + ccbits + seg.getBits().length;
}
if (dataUsedBits <= dataCapacityBits)
// Find the minimal version number to use
var version, dataUsedBits;
for (version = minVersion; ; version++) {
var dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available
dataUsedBits = this.QrSegment.getTotalBits(segs, version);
if (dataUsedBits != null && dataUsedBits <= dataCapacityBits)
break; // This version number is found to be suitable
if (version >= maxVersion) // All versions in the range could not fit the given data
throw "Data too long";
}
// Increase the error correction level while the data still fits in the current version number
[this.QrCode.Ecc.MEDIUM, this.QrCode.Ecc.QUARTILE, this.QrCode.Ecc.HIGH].forEach(function(newEcl) {
if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8)
ecl = newEcl;
});
// Create the data bit string by concatenating all segments
var dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
var bb = new BitBuffer();
segs.forEach(function(seg) {
bb.appendBits(seg.getMode().getModeBits(), 4);
@ -136,7 +136,7 @@ var qrcodegen = new function() {
throw "Assertion error";
// Create the QR Code symbol
return new this.QrCode(bb.getBytes(), -1, version, ecl);
return new this.QrCode(bb.getBytes(), mask, version, ecl);
};
@ -775,6 +775,21 @@ var qrcodegen = new function() {
return [this.makeBytes(toUtf8ByteArray(text))];
};
// Package-private helper function.
this.QrSegment.getTotalBits = function(segs, version) {
if (version < 1 || version > 40)
throw "Version number out of range";
var result = 0;
segs.forEach(function(seg) {
var ccbits = seg.getMode().numCharCountBits(version);
// Fail if segment length value doesn't fit in the length field's bit-width
if (seg.getNumChars() >= (1 << ccbits))
return null;
result += 4 + ccbits + seg.getBits().length;
});
return result;
};
/*-- Constants --*/
var QrSegment = {}; // Private object to assign properties to

View File

@ -29,7 +29,8 @@ import itertools, re, sys
Public members inside this module "qrcodegen":
- Function encode_text(str text, QrCode.Ecc ecl) -> QrCode
- Function encode_binary(bytes data, QrCode.Ecc ecl) -> QrCode
- Function encode_segments(list<QrSegment> segs, QrCode.Ecc ecl) -> QrCode
- Function encode_segments(list<QrSegment> segs, QrCode.Ecc ecl,
int minversion=1, int maxversion=40, mask=-1, boostecl=true) -> QrCode
- Class QrCode:
- Constructor QrCode(QrCode qr, int mask)
- Constructor QrCode(bytes bytes, int mask, int version, QrCode.Ecc ecl)
@ -77,35 +78,34 @@ def encode_binary(data, ecl):
return QrCode.encode_segments([QrSegment.make_bytes(data)], ecl)
def encode_segments(segs, ecl):
"""Returns a QR Code symbol representing the given data segments at the given error
correction level. The smallest possible QR Code version is automatically chosen for the output.
def encode_segments(segs, ecl, minversion=1, maxversion=40, mask=-1, boostecl=True):
"""Returns a QR Code symbol representing the specified data segments with the specified encoding parameters.
The smallest possible QR Code version within the specified range is automatically chosen for the output.
This function allows the user to create a custom sequence of segments that switches
between modes (such as alphanumeric and binary) to encode text more efficiently. This
function is considered to be lower level than simply encoding text or binary data."""
between modes (such as alphanumeric and binary) to encode text more efficiently.
This function is considered to be lower level than simply encoding text or binary data."""
if not 1 <= minversion <= maxversion <= 40 or not -1 <= mask <= 7:
raise ValueError("Invalid value")
# Find the minimal version number to use
for version in itertools.count(1): # Increment until the data fits in the QR Code
if version > 40: # All versions could not fit the given data
raise ValueError("Data too long")
for version in range(minversion, maxversion + 1):
datacapacitybits = QrCode._get_num_data_codewords(version, ecl) * 8 # Number of data bits available
# Calculate the total number of bits needed at this version number
# to encode all the segments (i.e. segment metadata and payloads)
datausedbits = 0
for seg in segs:
if seg.get_num_chars() < 0:
raise AssertionError()
ccbits = seg.get_mode().num_char_count_bits(version)
if seg.get_num_chars() >= (1 << ccbits):
# Segment length value doesn't fit in the length field's bit-width, so fail immediately
break
datausedbits += 4 + ccbits + len(seg.get_bits())
else: # If the loop above did not break
if datausedbits <= datacapacitybits:
datausedbits = QrSegment.get_total_bits(segs, version)
if datausedbits is not None and datausedbits <= datacapacitybits:
break # This version number is found to be suitable
if version >= maxversion: # All versions in the range could not fit the given data
raise ValueError("Data too long")
if datausedbits is None:
raise AssertionError()
# Increase the error correction level while the data still fits in the current version number
for newecl in (QrCode.Ecc.MEDIUM, QrCode.Ecc.QUARTILE, QrCode.Ecc.HIGH):
if boostecl and datausedbits <= QrCode._get_num_data_codewords(version, newecl) * 8:
ecl = newecl
# Create the data bit string by concatenating all segments
datacapacitybits = QrCode._get_num_data_codewords(version, ecl) * 8
bb = _BitBuffer()
for seg in segs:
bb.append_bits(seg.get_mode().get_mode_bits(), 4)
@ -124,7 +124,7 @@ def encode_segments(segs, ecl):
assert bb.bit_length() % 8 == 0
# Create the QR Code symbol
return QrCode(datacodewords=bb.get_bytes(), mask=-1, version=version, errcorlvl=ecl)
return QrCode(None, bb.get_bytes(), mask, version, ecl)
@ -686,6 +686,21 @@ class QrSegment(object):
return list(self._bitdata) # Defensive copy
# Package-private helper function.
@staticmethod
def get_total_bits(segs, version):
if not 1 <= version <= 40:
raise ValueError("Version number out of range")
result = 0
for seg in segs:
ccbits = seg.get_mode().num_char_count_bits(version)
# Fail if segment length value doesn't fit in the length field's bit-width
if seg.get_num_chars() >= (1 << ccbits):
return None
result += 4 + ccbits + len(seg.get_bits())
return result
# -- Constants --
# Can test whether a string is encodable in numeric mode (such as by using make_numeric())