From f325bfe638a6c88b0294de31dc225fd391311519 Mon Sep 17 00:00:00 2001 From: Nayuki Minase Date: Mon, 18 Apr 2016 18:40:14 +0000 Subject: [PATCH] Moved {encodeText(), encodeBinary(), encodeSegments()} into class QrCode in Python and JavaScript implementations, for consistency with Java implementation. --- javascript/qrcodegen-demo.js | 2 +- javascript/qrcodegen.js | 168 +++++++++++++++++------------------ python/qrcodegen-demo.py | 20 ++--- python/qrcodegen.py | 146 +++++++++++++++--------------- 4 files changed, 167 insertions(+), 169 deletions(-) diff --git a/javascript/qrcodegen-demo.js b/javascript/qrcodegen-demo.js index 46c501f..94b911d 100644 --- a/javascript/qrcodegen-demo.js +++ b/javascript/qrcodegen-demo.js @@ -42,7 +42,7 @@ function redrawQrCode() { var ecl = getInputErrorCorrectionLevel(); var text = document.getElementById("text-input").value; var segs = qrcodegen.QrSegment.makeSegments(text); - var qr = qrcodegen.encodeSegments(segs, ecl); + var qr = qrcodegen.QrCode.encodeSegments(segs, ecl); // Get scale and border var scale = parseInt(document.getElementById("scale-input").value, 10); diff --git a/javascript/qrcodegen.js b/javascript/qrcodegen.js index 1aae9ee..7254be3 100644 --- a/javascript/qrcodegen.js +++ b/javascript/qrcodegen.js @@ -27,11 +27,11 @@ /* * Module "qrcodegen". Public members inside this namespace: - * - Function encodeText(str text, QrCode.Ecc ecl) -> QrCode - * - Function encodeBinary(list data, QrCode.Ecc ecl) -> QrCode - * - Function encodeSegments(list segs, QrCode.Ecc ecl, - * int minVersion=1, int maxVersion=40, mask=-1, boostEcl=true) -> QrCode * - Class QrCode: + * - Function encodeText(str text, QrCode.Ecc ecl) -> QrCode + * - Function encodeBinary(list data, QrCode.Ecc ecl) -> QrCode + * - Function encodeSegments(list segs, QrCode.Ecc ecl, + * int minVersion=1, int maxVersion=40, mask=-1, boostEcl=true) -> QrCode * - Constructor QrCode(QrCode qr, int mask) * - Constructor QrCode(list bytes, int mask, int version, QrCode.Ecc ecl) * - Method getVersion() -> int @@ -60,87 +60,6 @@ */ var qrcodegen = new function() { - /*---- Public static factory functions for QrCode ----*/ - - /* - * Returns a QR Code symbol representing the given Unicode text string at the given error correction level. - * As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer Unicode - * code points (not UTF-16 code units). The smallest possible QR Code version is automatically chosen for the output. - */ - this.encodeText = function(text, ecl) { - var segs = this.QrSegment.makeSegments(text); - return this.encodeSegments(segs, ecl); - }; - - - /* - * Returns a QR Code symbol representing the given binary data string at the given error correction level. - * This function always encodes using the binary segment mode, not any text mode. The maximum number of - * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. - */ - this.encodeBinary = function(data, ecl) { - var seg = this.QrSegment.makeBytes(data); - return this.encodeSegments([seg], ecl); - }; - - - /* - * 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. - */ - 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"; - - // 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); - bb.appendBits(seg.getNumChars(), seg.getMode().numCharCountBits(version)); - bb.appendData(seg); - }); - - // Add terminator and pad up to a byte if applicable - bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength())); - bb.appendBits(0, (8 - bb.bitLength() % 8) % 8); - - // Pad with alternate bytes until data capacity is reached - for (var padByte = 0xEC; bb.bitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) - bb.appendBits(padByte, 8); - if (bb.bitLength() % 8 != 0) - throw "Assertion error"; - - // Create the QR Code symbol - return new this.QrCode(bb.getBytes(), mask, version, ecl); - }; - - - /*---- QR Code symbol class ----*/ /* @@ -594,6 +513,85 @@ var qrcodegen = new function() { }; + /*---- Public static factory functions for QrCode ----*/ + + /* + * Returns a QR Code symbol representing the given Unicode text string at the given error correction level. + * As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer Unicode + * code points (not UTF-16 code units). The smallest possible QR Code version is automatically chosen for the output. + */ + this.QrCode.encodeText = function(text, ecl) { + var segs = qrcodegen.QrSegment.makeSegments(text); + return this.encodeSegments(segs, ecl); + }; + + + /* + * Returns a QR Code symbol representing the given binary data string at the given error correction level. + * This function always encodes using the binary segment mode, not any text mode. The maximum number of + * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + */ + this.QrCode.encodeBinary = function(data, ecl) { + var seg = qrcodegen.QrSegment.makeBytes(data); + return this.encodeSegments([seg], ecl); + }; + + /* + * 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. + */ + this.QrCode.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"; + + // 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 = qrcodegen.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.Ecc.MEDIUM, this.Ecc.QUARTILE, this.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); + bb.appendBits(seg.getNumChars(), seg.getMode().numCharCountBits(version)); + bb.appendData(seg); + }); + + // Add terminator and pad up to a byte if applicable + bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength())); + bb.appendBits(0, (8 - bb.bitLength() % 8) % 8); + + // Pad with alternate bytes until data capacity is reached + for (var padByte = 0xEC; bb.bitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + bb.appendBits(padByte, 8); + if (bb.bitLength() % 8 != 0) + throw "Assertion error"; + + // Create the QR Code symbol + return new this(bb.getBytes(), mask, version, ecl); + }; + + /*---- Private static helper functions ----*/ var QrCode = {}; // Private object to assign properties to diff --git a/python/qrcodegen-demo.py b/python/qrcodegen-demo.py index 81be66d..b8a1542 100644 --- a/python/qrcodegen-demo.py +++ b/python/qrcodegen-demo.py @@ -39,7 +39,7 @@ def do_basic_demo(): """Creates a single QR Code, then prints it to the console.""" text = u"Hello, world!" # User-supplied Unicode text errcorlvl = qrcodegen.QrCode.Ecc.LOW # Error correction level - qr = qrcodegen.encode_text(text, errcorlvl) + qr = qrcodegen.QrCode.encode_text(text, errcorlvl) print_qr(qr) @@ -47,27 +47,27 @@ def do_variety_demo(): """Creates a variety of QR Codes that exercise different features of the library, and prints each one to the console.""" # Project Nayuki URL - qr = qrcodegen.encode_text("https://www.nayuki.io/", qrcodegen.QrCode.Ecc.HIGH) + qr = qrcodegen.QrCode.encode_text("https://www.nayuki.io/", qrcodegen.QrCode.Ecc.HIGH) qr = qrcodegen.QrCode(qrcode=qr, mask=3) # Change mask, forcing to mask #3 print_qr(qr) # Numeric mode encoding (3.33 bits per digit) - qr = qrcodegen.encode_text("314159265358979323846264338327950288419716939937510", qrcodegen.QrCode.Ecc.MEDIUM) + qr = qrcodegen.QrCode.encode_text("314159265358979323846264338327950288419716939937510", qrcodegen.QrCode.Ecc.MEDIUM) print_qr(qr) # Alphanumeric mode encoding (5.5 bits per character) - qr = qrcodegen.encode_text("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", qrcodegen.QrCode.Ecc.HIGH) + qr = qrcodegen.QrCode.encode_text("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", qrcodegen.QrCode.Ecc.HIGH) print_qr(qr) # Unicode text as UTF-8, and different masks - qr = qrcodegen.encode_text(u"\u3053\u3093\u306B\u3061\u0077\u0061\u3001\u4E16\u754C\uFF01\u0020\u03B1\u03B2\u03B3\u03B4", qrcodegen.QrCode.Ecc.QUARTILE) + qr = qrcodegen.QrCode.encode_text(u"\u3053\u3093\u306B\u3061\u0077\u0061\u3001\u4E16\u754C\uFF01\u0020\u03B1\u03B2\u03B3\u03B4", qrcodegen.QrCode.Ecc.QUARTILE) print_qr(qrcodegen.QrCode(qrcode=qr, mask=0)) print_qr(qrcodegen.QrCode(qrcode=qr, mask=1)) print_qr(qrcodegen.QrCode(qrcode=qr, mask=5)) print_qr(qrcodegen.QrCode(qrcode=qr, mask=7)) # Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) - qr = qrcodegen.encode_text("Alice was beginning to get very tired of sitting by her sister on the bank, " + qr = qrcodegen.QrCode.encode_text("Alice was beginning to get very tired of sitting by her sister on the bank, " "and of having nothing to do: once or twice she had peeped into the book her sister was reading, " "but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice " "'without pictures or conversations?' So she was considering in her own mind (as well as she could, " @@ -83,27 +83,27 @@ def do_segment_demo(): # Illustration "silver" silver0 = "THE SQUARE ROOT OF 2 IS 1." silver1 = "41421356237309504880168872420969807856967187537694807317667973799" - qr = qrcodegen.encode_text(silver0 + silver1, qrcodegen.QrCode.Ecc.LOW) + qr = qrcodegen.QrCode.encode_text(silver0 + silver1, qrcodegen.QrCode.Ecc.LOW) print_qr(qr) segs = [ qrcodegen.QrSegment.make_alphanumeric(silver0), qrcodegen.QrSegment.make_numeric(silver1)] - qr = qrcodegen.encode_segments(segs, qrcodegen.QrCode.Ecc.LOW) + qr = qrcodegen.QrCode.encode_segments(segs, qrcodegen.QrCode.Ecc.LOW) print_qr(qr) # Illustration "golden" golden0 = u"Golden ratio \u03C6 = 1." golden1 = u"6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374" golden2 = u"......" - qr = qrcodegen.encode_text(golden0 + golden1 + golden2, qrcodegen.QrCode.Ecc.LOW) + qr = qrcodegen.QrCode.encode_text(golden0 + golden1 + golden2, qrcodegen.QrCode.Ecc.LOW) print_qr(qr) segs = [ qrcodegen.QrSegment.make_bytes(golden0.encode("UTF-8")), qrcodegen.QrSegment.make_numeric(golden1), qrcodegen.QrSegment.make_alphanumeric(golden2)] - qr = qrcodegen.encode_segments(segs, qrcodegen.QrCode.Ecc.LOW) + qr = qrcodegen.QrCode.encode_segments(segs, qrcodegen.QrCode.Ecc.LOW) print_qr(qr) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index ab7f467..bd44b27 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -27,11 +27,11 @@ 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 segs, QrCode.Ecc ecl, - int minversion=1, int maxversion=40, mask=-1, boostecl=true) -> QrCode - Class QrCode: +- Function encode_text(str text, QrCode.Ecc ecl) -> QrCode +- Function encode_binary(bytes data, QrCode.Ecc ecl) -> QrCode + - Function encode_segments(list segs, QrCode.Ecc ecl, + int minversion=1, int maxversion=40, mask=-1, boostecl=true) -> QrCode - Constructor QrCode(QrCode qr, int mask) - Constructor QrCode(bytes bytes, int mask, int version, QrCode.Ecc ecl) - Method get_version() -> int @@ -59,81 +59,81 @@ Public members inside this module "qrcodegen": """ -# ---- Public static factory functions for QrCode ---- - -def encode_text(text, ecl): - """Returns a QR Code symbol representing the given Unicode text string at the given error correction level. - As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer Unicode - code points (not UTF-16 code units). The smallest possible QR Code version is automatically chosen for the output.""" - segs = QrSegment.make_segments(text) - return encode_segments(segs, ecl) - - -def encode_binary(data, ecl): - """Returns a QR Code symbol representing the given binary data string at the given error correction level. - This function always encodes using the binary segment mode, not any text mode. The maximum number of - bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.""" - if not isinstance(data, bytes): - raise TypeError("Binary array expected") - return QrCode.encode_segments([QrSegment.make_bytes(data)], ecl) - - -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.""" - - 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 range(minversion, maxversion + 1): - datacapacitybits = QrCode._get_num_data_codewords(version, ecl) * 8 # Number of data bits available - 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) - bb.append_bits(seg.get_num_chars(), seg.get_mode().num_char_count_bits(version)) - bb.append_all(seg) - - # Add terminator and pad up to a byte if applicable - bb.append_bits(0, min(4, datacapacitybits - bb.bit_length())) - bb.append_bits(0, -bb.bit_length() % 8) - - # Pad with alternate bytes until data capacity is reached - for padbyte in itertools.cycle((0xEC, 0x11)): - if bb.bit_length() >= datacapacitybits: - break - bb.append_bits(padbyte, 8) - assert bb.bit_length() % 8 == 0 - - # Create the QR Code symbol - return QrCode(None, bb.get_bytes(), mask, version, ecl) - - - # ---- QR Code symbol class ---- class QrCode(object): """Represents an immutable square grid of black or white cells for a QR Code symbol. This class covers the QR Code model 2 specification, supporting all versions (sizes) from 1 to 40, all 4 error correction levels.""" + # ---- Public static factory functions ---- + + def encode_text(text, ecl): + """Returns a QR Code symbol representing the given Unicode text string at the given error correction level. + As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer Unicode + code points (not UTF-16 code units). The smallest possible QR Code version is automatically chosen for the output.""" + segs = QrSegment.make_segments(text) + return QrCode.encode_segments(segs, ecl) + + + def encode_binary(data, ecl): + """Returns a QR Code symbol representing the given binary data string at the given error correction level. + This function always encodes using the binary segment mode, not any text mode. The maximum number of + bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.""" + if not isinstance(data, bytes): + raise TypeError("Binary array expected") + return QrCode.encode_segments([QrSegment.make_bytes(data)], ecl) + + + @staticmethod + 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.""" + + 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 range(minversion, maxversion + 1): + datacapacitybits = QrCode._get_num_data_codewords(version, ecl) * 8 # Number of data bits available + 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) + bb.append_bits(seg.get_num_chars(), seg.get_mode().num_char_count_bits(version)) + bb.append_all(seg) + + # Add terminator and pad up to a byte if applicable + bb.append_bits(0, min(4, datacapacitybits - bb.bit_length())) + bb.append_bits(0, -bb.bit_length() % 8) + + # Pad with alternate bytes until data capacity is reached + for padbyte in itertools.cycle((0xEC, 0x11)): + if bb.bit_length() >= datacapacitybits: + break + bb.append_bits(padbyte, 8) + assert bb.bit_length() % 8 == 0 + + # Create the QR Code symbol + return QrCode(None, bb.get_bytes(), mask, version, ecl) + + # ---- Constructor ---- def __init__(self, qrcode=None, datacodewords=None, mask=None, version=None, errcorlvl=None):