downgrade qr
This commit is contained in:
parent
785dccdf9c
commit
33111edeb2
23 changed files with 360 additions and 1656 deletions
|
|
@ -13,7 +13,7 @@ no_screenshot: 9ca2a492ff12e5179583a1fa015bf0843382b866
|
||||||
optional: 71c638891ce4f2aff35c7387727989f31f9d877d
|
optional: 71c638891ce4f2aff35c7387727989f31f9d877d
|
||||||
photo_view: a13ca2fc387a3fb1276126959e092c44d0029987
|
photo_view: a13ca2fc387a3fb1276126959e092c44d0029987
|
||||||
pointycastle: bbd8569f68a7fccbdf0b92d0b44a9219c126c8dd
|
pointycastle: bbd8569f68a7fccbdf0b92d0b44a9219c126c8dd
|
||||||
qr: 5fa01fcccd6121b906dc7df4fffa9fa22ca94f75
|
qr: 7b1e9665ca976f484e7975356cf26fc7a0ccf02e
|
||||||
qr_flutter: d5e7206396105d643113618290bbcc755d05f492
|
qr_flutter: d5e7206396105d643113618290bbcc755d05f492
|
||||||
restart_app: 12339f63bf8e9631e619c4f9f6b4e013fa324715
|
restart_app: 12339f63bf8e9631e619c4f9f6b4e013fa324715
|
||||||
x25519: ecb1d357714537bba6e276ef45f093846d4beaee
|
x25519: ecb1d357714537bba6e276ef45f093846d4beaee
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
export 'src/bit_buffer.dart';
|
export 'src/bit_buffer.dart';
|
||||||
export 'src/byte.dart';
|
|
||||||
export 'src/eci.dart';
|
|
||||||
export 'src/ecivalue.dart';
|
|
||||||
export 'src/error_correct_level.dart';
|
export 'src/error_correct_level.dart';
|
||||||
export 'src/input_too_long_exception.dart';
|
export 'src/input_too_long_exception.dart';
|
||||||
export 'src/mode.dart';
|
|
||||||
export 'src/qr_code.dart';
|
export 'src/qr_code.dart';
|
||||||
export 'src/qr_image.dart';
|
export 'src/qr_image.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,78 +1,34 @@
|
||||||
/// A growable sequence of bits.
|
import 'dart:collection';
|
||||||
///
|
|
||||||
/// Used internally to construct the data bit stream for a QR code.
|
class QrBitBuffer extends Object with ListMixin<bool> {
|
||||||
class QrBitBuffer extends Iterable<bool> {
|
final List<int> _buffer;
|
||||||
final _buffer = <int>[];
|
|
||||||
int _length = 0;
|
int _length = 0;
|
||||||
|
|
||||||
QrBitBuffer();
|
QrBitBuffer() : _buffer = <int>[];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get length => _length;
|
void operator []=(int index, bool value) =>
|
||||||
|
throw UnsupportedError('cannot change');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Iterator<bool> get iterator => _QrBitBufferIterator(this);
|
|
||||||
|
|
||||||
bool operator [](int index) {
|
bool operator [](int index) {
|
||||||
final bufIndex = index ~/ 8;
|
final bufIndex = index ~/ 8;
|
||||||
return ((_buffer[bufIndex] >> (7 - index % 8)) & 1) == 1;
|
return ((_buffer[bufIndex] >> (7 - index % 8)) & 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get length => _length;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set length(int value) => throw UnsupportedError('Cannot change');
|
||||||
|
|
||||||
int getByte(int index) => _buffer[index];
|
int getByte(int index) => _buffer[index];
|
||||||
|
|
||||||
void put(int number, int length) {
|
void put(int number, int length) {
|
||||||
if (length == 0) return;
|
for (var i = 0; i < length; i++) {
|
||||||
|
final bit = ((number >> (length - i - 1)) & 1) == 1;
|
||||||
var bitIndex = _length;
|
putBit(bit);
|
||||||
final endBitIndex = bitIndex + length;
|
|
||||||
|
|
||||||
// Ensure capacity
|
|
||||||
final neededBytes = (endBitIndex + 7) >> 3; // (endBitIndex + 7) ~/ 8
|
|
||||||
while (_buffer.length < neededBytes) {
|
|
||||||
_buffer.add(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimization for byte-aligned writes of 8 bits (common case)
|
|
||||||
if (length == 8 && (bitIndex & 7) == 0 && number >= 0 && number <= 255) {
|
|
||||||
_buffer[bitIndex >> 3] = number;
|
|
||||||
_length = endBitIndex;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic chunked write
|
|
||||||
var bitsLeft = length;
|
|
||||||
|
|
||||||
while (bitsLeft > 0) {
|
|
||||||
final bufIndex = bitIndex >> 3;
|
|
||||||
final leftBitIndex = bitIndex & 7;
|
|
||||||
final available = 8 - leftBitIndex;
|
|
||||||
final bitsToWrite = bitsLeft < available ? bitsLeft : available;
|
|
||||||
|
|
||||||
// Extract the 'bitsToWrite' most significant bits from 'number'
|
|
||||||
// Shift number right to move target bits to bottom
|
|
||||||
// Mask them
|
|
||||||
// Then allocate them to the byte buffer
|
|
||||||
|
|
||||||
final shift = bitsLeft - bitsToWrite;
|
|
||||||
final bits = (number >> shift) & ((1 << bitsToWrite) - 1);
|
|
||||||
|
|
||||||
// Setup position in byte.
|
|
||||||
// We want to write 'bits' starting at 'leftBitIndex'.
|
|
||||||
// So we shift 'bits' left by (available - bitsToWrite)?
|
|
||||||
// No, `leftBitIndex` is 0-7. 0 is MSB (0x80).
|
|
||||||
// If leftBitIndex is 0, we write starting at 0x80.
|
|
||||||
// If bitsToWrite is 8, we write 0xFF.
|
|
||||||
// If 4 bits, we write 0xF0.
|
|
||||||
// formula: bits << (8 - leftBitIndex - bitsToWrite)
|
|
||||||
|
|
||||||
final posShift = 8 - leftBitIndex - bitsToWrite;
|
|
||||||
_buffer[bufIndex] |= bits << posShift;
|
|
||||||
|
|
||||||
bitsLeft -= bitsToWrite;
|
|
||||||
bitIndex += bitsToWrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
_length = endBitIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void putBit(bool bit) {
|
void putBit(bool bit) {
|
||||||
|
|
@ -87,28 +43,4 @@ class QrBitBuffer extends Iterable<bool> {
|
||||||
|
|
||||||
_length++;
|
_length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<bool> getRange(int start, int end) {
|
|
||||||
final list = <bool>[];
|
|
||||||
for (var i = start; i < end; i++) {
|
|
||||||
list.add(this[i]);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _QrBitBufferIterator implements Iterator<bool> {
|
|
||||||
final QrBitBuffer _buffer;
|
|
||||||
int _currentIndex = -1;
|
|
||||||
|
|
||||||
_QrBitBufferIterator(this._buffer);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get current => _buffer[_currentIndex];
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool moveNext() {
|
|
||||||
_currentIndex++;
|
|
||||||
return _currentIndex < _buffer.length;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,47 +2,17 @@ import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'bit_buffer.dart';
|
import 'bit_buffer.dart';
|
||||||
import 'eci.dart';
|
import 'mode.dart' as qr_mode;
|
||||||
import 'mode.dart';
|
|
||||||
|
|
||||||
/// A piece of data to be encoded in a QR code.
|
|
||||||
///
|
|
||||||
/// Use [toDatums] to parse a string into optimal segments.
|
|
||||||
abstract class QrDatum {
|
abstract class QrDatum {
|
||||||
QrMode get mode;
|
int get mode;
|
||||||
int get length;
|
int get length;
|
||||||
void write(QrBitBuffer buffer);
|
void write(QrBitBuffer buffer);
|
||||||
|
|
||||||
/// Parses [data] into a list of [QrDatum] segments, optimizing for the
|
|
||||||
/// most efficient encoding modes (Numeric, Alphanumeric, Byte).
|
|
||||||
///
|
|
||||||
/// Automatically handles UTF-8 characters by using [QrEci] and [QrByte]
|
|
||||||
/// segments if necessary.
|
|
||||||
static List<QrDatum> toDatums(String data) {
|
|
||||||
if (QrNumeric.validationRegex.hasMatch(data)) {
|
|
||||||
return [QrNumeric.fromString(data)];
|
|
||||||
}
|
|
||||||
if (QrAlphaNumeric.validationRegex.hasMatch(data)) {
|
|
||||||
return [QrAlphaNumeric.fromString(data)];
|
|
||||||
}
|
|
||||||
// Default to byte mode for other characters
|
|
||||||
// Check if we need ECI (if there are chars > 255)
|
|
||||||
// Actually, standard ISO-8859-1 is 0-255.
|
|
||||||
// Emojis and other UTF-8 chars will definitely trigger this.
|
|
||||||
final hasNonLatin1 = data.codeUnits.any((c) => c > 255);
|
|
||||||
if (hasNonLatin1) {
|
|
||||||
return [QrEci(26), QrByte(data)]; // UTF-8
|
|
||||||
}
|
|
||||||
return [QrByte(data)];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents data encoded in Byte mode (8-bit).
|
|
||||||
///
|
|
||||||
/// Supports ISO-8859-1 and UTF-8 (when preceded by an ECI segment).
|
|
||||||
class QrByte implements QrDatum {
|
class QrByte implements QrDatum {
|
||||||
@override
|
@override
|
||||||
final QrMode mode = QrMode.byte;
|
final int mode = qr_mode.mode8bitByte;
|
||||||
final Uint8List _data;
|
final Uint8List _data;
|
||||||
|
|
||||||
factory QrByte(String input) =>
|
factory QrByte(String input) =>
|
||||||
|
|
@ -64,20 +34,13 @@ class QrByte implements QrDatum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes numeric data (digits 0-9).
|
/// Encodes numbers (0-9) 10 bits per 3 digits.
|
||||||
///
|
|
||||||
/// Compresses 3 digits into 10 bits.
|
|
||||||
/// Most efficient mode for decimal numbers.
|
|
||||||
class QrNumeric implements QrDatum {
|
class QrNumeric implements QrDatum {
|
||||||
static final RegExp validationRegex = RegExp(r'^[0-9]+$');
|
static final RegExp validationRegex = RegExp(r'^[0-9]+$');
|
||||||
|
|
||||||
factory QrNumeric.fromString(String numberString) {
|
factory QrNumeric.fromString(String numberString) {
|
||||||
if (!validationRegex.hasMatch(numberString)) {
|
if (!validationRegex.hasMatch(numberString)) {
|
||||||
throw ArgumentError.value(
|
throw ArgumentError('string can only contain digits 0-9');
|
||||||
numberString,
|
|
||||||
'numberString',
|
|
||||||
'string can only contain digits 0-9',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
final newList = Uint8List(numberString.length);
|
final newList = Uint8List(numberString.length);
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
|
@ -92,7 +55,7 @@ class QrNumeric implements QrDatum {
|
||||||
final Uint8List _data;
|
final Uint8List _data;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final QrMode mode = QrMode.numeric;
|
final int mode = qr_mode.modeNumber;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(QrBitBuffer buffer) {
|
void write(QrBitBuffer buffer) {
|
||||||
|
|
@ -119,10 +82,7 @@ class QrNumeric implements QrDatum {
|
||||||
int get length => _data.length;
|
int get length => _data.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes alphanumeric data (uppercase letters, digits, and specific symbols).
|
/// Encodes numbers (0-9) 10 bits per 3 digits.
|
||||||
///
|
|
||||||
/// Supported characters: 0-9, A-Z, space, $, %, *, +, -, ., /, :
|
|
||||||
/// Compresses 2 characters into 11 bits.
|
|
||||||
class QrAlphaNumeric implements QrDatum {
|
class QrAlphaNumeric implements QrDatum {
|
||||||
static const alphaNumTable = r'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
static const alphaNumTable = r'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
||||||
// Note: '-' anywhere in this string is a range character.
|
// Note: '-' anywhere in this string is a range character.
|
||||||
|
|
@ -142,10 +102,9 @@ class QrAlphaNumeric implements QrDatum {
|
||||||
|
|
||||||
factory QrAlphaNumeric.fromString(String alphaNumeric) {
|
factory QrAlphaNumeric.fromString(String alphaNumeric) {
|
||||||
if (!alphaNumeric.contains(validationRegex)) {
|
if (!alphaNumeric.contains(validationRegex)) {
|
||||||
throw ArgumentError.value(
|
throw ArgumentError(
|
||||||
alphaNumeric,
|
'String does not contain valid ALPHA-NUM '
|
||||||
'alphaNumeric',
|
'character set: $alphaNumeric',
|
||||||
'String does not contain valid ALPHA-NUM character set',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return QrAlphaNumeric._(alphaNumeric);
|
return QrAlphaNumeric._(alphaNumeric);
|
||||||
|
|
@ -154,7 +113,7 @@ class QrAlphaNumeric implements QrDatum {
|
||||||
QrAlphaNumeric._(this._string);
|
QrAlphaNumeric._(this._string);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final QrMode mode = QrMode.alphaNumeric;
|
final int mode = qr_mode.modeAlphaNum;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(QrBitBuffer buffer) {
|
void write(QrBitBuffer buffer) {
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import 'bit_buffer.dart';
|
|
||||||
import 'byte.dart';
|
|
||||||
|
|
||||||
import 'mode.dart';
|
|
||||||
|
|
||||||
/// Extended Channel Interpretation (ECI) mode data.
|
|
||||||
///
|
|
||||||
/// Use this to specify a different character encoding for the following data.
|
|
||||||
class QrEci implements QrDatum {
|
|
||||||
final int value;
|
|
||||||
|
|
||||||
factory QrEci(int value) {
|
|
||||||
if (value < 0 || value > 999999) {
|
|
||||||
throw RangeError.range(value, 0, 999999, 'value');
|
|
||||||
}
|
|
||||||
return QrEci._(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
QrEci._(this.value);
|
|
||||||
|
|
||||||
@override
|
|
||||||
QrMode get mode => QrMode.eci;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get length => 0; // ECI segments do not have a length field
|
|
||||||
|
|
||||||
@override
|
|
||||||
void write(QrBitBuffer buffer) {
|
|
||||||
if (value < 128) {
|
|
||||||
// 0xxxxxxx
|
|
||||||
buffer.put(value, 8);
|
|
||||||
} else if (value < 16384) {
|
|
||||||
// 10xxxxxx xxxxxxxx
|
|
||||||
buffer.put(0x8000 | value, 16);
|
|
||||||
} else {
|
|
||||||
// 110xxxxx xxxxxxxx xxxxxxxx
|
|
||||||
buffer.put(0xC00000 | value, 24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
/// ECI value for QR Codes.
|
|
||||||
///
|
|
||||||
/// This extension type provides constants for common ECI values.
|
|
||||||
///
|
|
||||||
/// See: https://github.com/zxing/zxing/blob/master/core/src/main/java/com/google/zxing/common/CharacterSetECI.java
|
|
||||||
extension type const QrEciValue(int value) implements int {
|
|
||||||
/// ISO-8859-1 (Latin-1). Default encoding.
|
|
||||||
static const iso8859_1 = QrEciValue(3);
|
|
||||||
|
|
||||||
/// ISO-8859-2 (Latin-2).
|
|
||||||
static const iso8859_2 = QrEciValue(4);
|
|
||||||
|
|
||||||
/// ISO-8859-3 (Latin-3).
|
|
||||||
static const iso8859_3 = QrEciValue(5);
|
|
||||||
|
|
||||||
/// ISO-8859-4 (Latin-4).
|
|
||||||
static const iso8859_4 = QrEciValue(6);
|
|
||||||
|
|
||||||
/// ISO-8859-5 (Latin/Cyrillic).
|
|
||||||
static const iso8859_5 = QrEciValue(7);
|
|
||||||
|
|
||||||
/// ISO-8859-6 (Latin/Arabic).
|
|
||||||
static const iso8859_6 = QrEciValue(8);
|
|
||||||
|
|
||||||
/// ISO-8859-7 (Latin/Greek).
|
|
||||||
static const iso8859_7 = QrEciValue(9);
|
|
||||||
|
|
||||||
/// ISO-8859-8 (Latin/Hebrew).
|
|
||||||
static const iso8859_8 = QrEciValue(10);
|
|
||||||
|
|
||||||
/// ISO-8859-9 (Latin-5).
|
|
||||||
static const iso8859_9 = QrEciValue(11);
|
|
||||||
|
|
||||||
/// ISO-8859-10 (Latin-6).
|
|
||||||
static const iso8859_10 = QrEciValue(12);
|
|
||||||
|
|
||||||
/// ISO-8859-11 (Latin/Thai).
|
|
||||||
static const iso8859_11 = QrEciValue(13);
|
|
||||||
|
|
||||||
/// ISO-8859-13 (Latin-7).
|
|
||||||
static const iso8859_13 = QrEciValue(15);
|
|
||||||
|
|
||||||
/// ISO-8859-14 (Latin-8).
|
|
||||||
static const iso8859_14 = QrEciValue(16);
|
|
||||||
|
|
||||||
/// ISO-8859-15 (Latin-9).
|
|
||||||
static const iso8859_15 = QrEciValue(17);
|
|
||||||
|
|
||||||
/// ISO-8859-16 (Latin-10).
|
|
||||||
static const iso8859_16 = QrEciValue(18);
|
|
||||||
|
|
||||||
/// Shift JIS.
|
|
||||||
static const shiftJis = QrEciValue(20);
|
|
||||||
|
|
||||||
/// Windows-1250 (Latin-2).
|
|
||||||
static const windows1250 = QrEciValue(21);
|
|
||||||
|
|
||||||
/// Windows-1251 (Cyrillic).
|
|
||||||
static const windows1251 = QrEciValue(22);
|
|
||||||
|
|
||||||
/// Windows-1252 (Latin-1).
|
|
||||||
static const windows1252 = QrEciValue(23);
|
|
||||||
|
|
||||||
/// Windows-1256 (Arabic).
|
|
||||||
static const windows1256 = QrEciValue(24);
|
|
||||||
|
|
||||||
/// UTF-16 (Big Endian).
|
|
||||||
static const utf16BE = QrEciValue(25);
|
|
||||||
|
|
||||||
/// UTF-8.
|
|
||||||
static const utf8 = QrEciValue(26);
|
|
||||||
|
|
||||||
/// US-ASCII.
|
|
||||||
static const ascii = QrEciValue(27);
|
|
||||||
|
|
||||||
/// Big5.
|
|
||||||
static const big5 = QrEciValue(28);
|
|
||||||
|
|
||||||
/// GB 2312.
|
|
||||||
static const gb2312 = QrEciValue(29);
|
|
||||||
|
|
||||||
/// EUC-KR.
|
|
||||||
static const eucKr = QrEciValue(30);
|
|
||||||
|
|
||||||
/// GBK.
|
|
||||||
static const gbk = QrEciValue(31);
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +1,20 @@
|
||||||
/// QR Code error correction level.
|
// ignore: avoid_classes_with_only_static_members
|
||||||
///
|
class QrErrorCorrectLevel {
|
||||||
/// Recover capacity:
|
static const int L = 1;
|
||||||
/// * [low] : ~7%
|
static const int M = 0;
|
||||||
/// * [medium] : ~15%
|
static const int Q = 3;
|
||||||
/// * [quartile] : ~25%
|
static const int H = 2;
|
||||||
/// * [high] : ~30%
|
|
||||||
enum QrErrorCorrectLevel {
|
|
||||||
// NOTE: the order here MATTERS.
|
|
||||||
// The index maps to the QR standard.
|
|
||||||
|
|
||||||
/// Level M (Medium) ~15% error correction.
|
// thesee *are* in order of lowest to highest quality...I think
|
||||||
medium(15),
|
// all I know for sure: you can create longer messages w/ item N than N+1
|
||||||
|
// I assume this correcsponds to more error correction for N+1
|
||||||
|
static const List<int> levels = [L, M, Q, H];
|
||||||
|
|
||||||
/// Level L (Low) ~7% error correction.
|
static String getName(int level) => switch (level) {
|
||||||
low(7),
|
L => 'Low',
|
||||||
|
M => 'Medium',
|
||||||
/// Level H (High) ~30% error correction.
|
Q => 'Quartile',
|
||||||
high(30),
|
H => 'High',
|
||||||
|
_ => throw ArgumentError('level $level not supported'),
|
||||||
/// Level Q (Quartile) ~25% error correction.
|
};
|
||||||
quartile(25);
|
|
||||||
|
|
||||||
final int recoveryRate;
|
|
||||||
|
|
||||||
const QrErrorCorrectLevel(this.recoveryRate);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,8 @@
|
||||||
enum QrMaskPattern {
|
const int pattern000 = 0;
|
||||||
pattern000(_check000),
|
const int pattern001 = 1;
|
||||||
pattern001(_check001),
|
const int pattern010 = 2;
|
||||||
pattern010(_check010),
|
const int pattern011 = 3;
|
||||||
pattern011(_check011),
|
const int pattern100 = 4;
|
||||||
pattern100(_check100),
|
const int pattern101 = 5;
|
||||||
pattern101(_check101),
|
const int pattern110 = 6;
|
||||||
pattern110(_check110),
|
const int pattern111 = 7;
|
||||||
pattern111(_check111);
|
|
||||||
|
|
||||||
final bool Function(int i, int j) _check;
|
|
||||||
|
|
||||||
const QrMaskPattern(this._check);
|
|
||||||
|
|
||||||
bool check(int i, int j) => _check(i, j);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _check000(int i, int j) => (i + j).isEven;
|
|
||||||
bool _check001(int i, int j) => i.isEven;
|
|
||||||
bool _check010(int i, int j) => j % 3 == 0;
|
|
||||||
bool _check011(int i, int j) => (i + j) % 3 == 0;
|
|
||||||
bool _check100(int i, int j) => ((i ~/ 2) + (j ~/ 3)).isEven;
|
|
||||||
bool _check101(int i, int j) => ((i * j) % 2 + (i * j) % 3) == 0;
|
|
||||||
bool _check110(int i, int j) => (((i * j) % 2) + ((i * j) % 3)).isEven;
|
|
||||||
bool _check111(int i, int j) => (((i * j) % 3) + ((i + j) % 2)).isEven;
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import 'dart:typed_data';
|
||||||
final Uint8List _logTable = _createLogTable();
|
final Uint8List _logTable = _createLogTable();
|
||||||
final Uint8List _expTable = _createExpTable();
|
final Uint8List _expTable = _createExpTable();
|
||||||
|
|
||||||
int glog(int n) =>
|
int glog(int n) => (n >= 1) ? _logTable[n] : throw ArgumentError('glog($n)');
|
||||||
(n >= 1) ? _logTable[n] : throw ArgumentError.value(n, 'n', 'must be >= 1');
|
|
||||||
|
|
||||||
int gexp(int n) => _expTable[n % 255];
|
int gexp(int n) => _expTable[n % 255];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,4 @@
|
||||||
/// The encoding mode of a QR code segment.
|
const int modeNumber = 1 << 0;
|
||||||
enum QrMode {
|
const int modeAlphaNum = 1 << 1;
|
||||||
/// Numeric mode (0-9). Most efficient.
|
const int mode8bitByte = 1 << 2;
|
||||||
numeric(1),
|
const int modeKanji = 1 << 3;
|
||||||
|
|
||||||
/// Alphanumeric mode (0-9, A-Z, space, %, *, +, -, ., /, :).
|
|
||||||
alphaNumeric(2),
|
|
||||||
|
|
||||||
/// Byte mode (8-bit data).
|
|
||||||
byte(4),
|
|
||||||
|
|
||||||
/// Kanji mode (Shift-JIS).
|
|
||||||
kanji(8),
|
|
||||||
|
|
||||||
/// Extended Channel Interpretation (ECI) mode.
|
|
||||||
eci(7);
|
|
||||||
|
|
||||||
final int value;
|
|
||||||
|
|
||||||
const QrMode(this.value);
|
|
||||||
|
|
||||||
int getLengthBits(int type) {
|
|
||||||
if (this == eci) return 0;
|
|
||||||
if (type < 1 || type > 40) throw RangeError.range(type, 1, 40, 'type');
|
|
||||||
|
|
||||||
if (type < 10) {
|
|
||||||
// 1 - 9
|
|
||||||
return switch (this) {
|
|
||||||
numeric => 10,
|
|
||||||
alphaNumeric => 9,
|
|
||||||
byte => 8,
|
|
||||||
kanji => 8,
|
|
||||||
eci => 0,
|
|
||||||
};
|
|
||||||
} else if (type < 27) {
|
|
||||||
// 10 - 26
|
|
||||||
return switch (this) {
|
|
||||||
numeric => 12,
|
|
||||||
alphaNumeric => 11,
|
|
||||||
byte => 16,
|
|
||||||
kanji => 10,
|
|
||||||
eci => 0,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// 27 - 40
|
|
||||||
return switch (this) {
|
|
||||||
numeric => 14,
|
|
||||||
alphaNumeric => 13,
|
|
||||||
byte => 16,
|
|
||||||
kanji => 12,
|
|
||||||
eci => 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -28,75 +28,36 @@ class QrPolynomial {
|
||||||
int get length => _values.length;
|
int get length => _values.length;
|
||||||
|
|
||||||
QrPolynomial multiply(QrPolynomial e) {
|
QrPolynomial multiply(QrPolynomial e) {
|
||||||
final foo = Uint8List(length + e.length - 1);
|
final List<int> foo = Uint8List(length + e.length - 1);
|
||||||
|
|
||||||
for (var i = 0; i < length; i++) {
|
for (var i = 0; i < length; i++) {
|
||||||
final v1 = _values[i];
|
|
||||||
if (v1 == 0) continue;
|
|
||||||
final log1 = qr_math.glog(v1);
|
|
||||||
for (var j = 0; j < e.length; j++) {
|
for (var j = 0; j < e.length; j++) {
|
||||||
final v2 = e[j];
|
foo[i + j] ^= qr_math.gexp(qr_math.glog(this[i]) + qr_math.glog(e[j]));
|
||||||
if (v2 == 0) continue;
|
|
||||||
foo[i + j] ^= qr_math.gexp(log1 + qr_math.glog(v2));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QrPolynomial._internal(foo);
|
return QrPolynomial(foo, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
QrPolynomial mod(QrPolynomial e) {
|
QrPolynomial mod(QrPolynomial e) {
|
||||||
if (length - e.length < 0) {
|
if (length - e.length < 0) {
|
||||||
|
// ignore: avoid_returning_this
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a copy of _values that we will mutate
|
final ratio = qr_math.glog(this[0]) - qr_math.glog(e[0]);
|
||||||
// We only need the part that will remain after modulo?
|
|
||||||
// Actually, standard polynomial division.
|
|
||||||
// We can work on a copy of `this._values` and zero out leading terms.
|
|
||||||
|
|
||||||
final values = Uint8List.fromList(_values);
|
final value = Uint8List(length);
|
||||||
|
|
||||||
for (var i = 0; i < values.length - e.length + 1; i++) {
|
for (var i = 0; i < length; i++) {
|
||||||
final v = values[i];
|
value[i] = this[i];
|
||||||
if (v == 0) continue;
|
|
||||||
|
|
||||||
final ratio = qr_math.glog(v) - qr_math.glog(e[0]);
|
|
||||||
|
|
||||||
for (var j = 0; j < e.length; j++) {
|
|
||||||
final eVal = e[j];
|
|
||||||
if (eVal == 0) continue;
|
|
||||||
values[i + j] ^= qr_math.gexp(qr_math.glog(eVal) + ratio);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The result is the remainder, which is the last e.length - 1 coefficients?
|
for (var i = 0; i < e.length; i++) {
|
||||||
// Wait, the degree of remainder is less than degree of divisor (e).
|
value[i] ^= qr_math.gexp(qr_math.glog(e[i]) + ratio);
|
||||||
// e.length is e.degree + 1.
|
}
|
||||||
// So remainder length is e.length - 1.
|
|
||||||
|
|
||||||
// Find where the remainder starts.
|
// recursive call
|
||||||
// In the loop above, we zeroed out terms from 0 to
|
return QrPolynomial(value, 0).mod(e);
|
||||||
// `values.length - e.length`.
|
|
||||||
// So the remainder starts at values.length - e.length + 1?
|
|
||||||
// No, we iterated i from 0 to diff.
|
|
||||||
// The loop eliminates the term at `i`.
|
|
||||||
// The last `i` is `values.length - e.length`.
|
|
||||||
// After that, the terms from `0` to `values.length - e.length` should be 0.
|
|
||||||
// The remainder is at the end.
|
|
||||||
|
|
||||||
// Note: The original implementation used `offset` to skip leading zeros.
|
|
||||||
// `offset` increased when `values[offset] == 0`.
|
|
||||||
// My loop enforces `values[i]` becomes 0 (arithmetically, though likely not
|
|
||||||
// exactly 0 due to XOR, wait XOR equal things is 0).
|
|
||||||
|
|
||||||
// Let's manually increment offset to match original logic if needed,
|
|
||||||
// or just slice the end.
|
|
||||||
// The remainder should fit in e.length - 1.
|
|
||||||
|
|
||||||
// We can just return the tail.
|
|
||||||
// But we need to handle leading zeros in the result too?
|
|
||||||
// `QrPolynomial` constructor handles leading zeros.
|
|
||||||
|
|
||||||
return QrPolynomial(values.sublist(values.length - e.length + 1), 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,61 +5,66 @@ import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import 'bit_buffer.dart';
|
import 'bit_buffer.dart';
|
||||||
import 'byte.dart';
|
import 'byte.dart';
|
||||||
import 'eci.dart';
|
|
||||||
import 'error_correct_level.dart';
|
import 'error_correct_level.dart';
|
||||||
import 'input_too_long_exception.dart';
|
import 'input_too_long_exception.dart';
|
||||||
import 'math.dart' as qr_math;
|
import 'math.dart' as qr_math;
|
||||||
|
import 'mode.dart' as qr_mode;
|
||||||
import 'polynomial.dart';
|
import 'polynomial.dart';
|
||||||
import 'rs_block.dart';
|
import 'rs_block.dart';
|
||||||
|
|
||||||
class QrCode {
|
class QrCode {
|
||||||
final int typeNumber;
|
final int typeNumber;
|
||||||
final QrErrorCorrectLevel errorCorrectLevel;
|
final int errorCorrectLevel;
|
||||||
final int moduleCount;
|
final int moduleCount;
|
||||||
List<int>? _dataCache;
|
List<int>? _dataCache;
|
||||||
final _dataList = <QrDatum>[];
|
final _dataList = <QrDatum>[];
|
||||||
|
|
||||||
QrCode(this.typeNumber, this.errorCorrectLevel)
|
QrCode(this.typeNumber, this.errorCorrectLevel)
|
||||||
: moduleCount = typeNumber * 4 + 17 {
|
: moduleCount = typeNumber * 4 + 17 {
|
||||||
// The typeNumber is now calculated internally by the factories,
|
|
||||||
// so this check is only needed if QrCode is instantiated directly.
|
|
||||||
// However, the factories ensure a valid typeNumber is passed.
|
|
||||||
// Keeping it for direct instantiation safety.
|
|
||||||
RangeError.checkValueInInterval(typeNumber, 1, 40, 'typeNumber');
|
RangeError.checkValueInInterval(typeNumber, 1, 40, 'typeNumber');
|
||||||
|
RangeError.checkValidIndex(
|
||||||
|
errorCorrectLevel,
|
||||||
|
QrErrorCorrectLevel.levels,
|
||||||
|
'errorCorrectLevel',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory QrCode.fromData({
|
factory QrCode.fromData({
|
||||||
required String data,
|
required String data,
|
||||||
required QrErrorCorrectLevel errorCorrectLevel,
|
required int errorCorrectLevel,
|
||||||
}) {
|
}) {
|
||||||
final datumList = QrDatum.toDatums(data);
|
final QrDatum datum;
|
||||||
|
// Automatically determine mode here
|
||||||
final typeNumber = _calculateTypeNumberFromData(
|
if (QrNumeric.validationRegex.hasMatch(data)) {
|
||||||
errorCorrectLevel,
|
// Numeric mode for numbers only
|
||||||
datumList,
|
datum = QrNumeric.fromString(data);
|
||||||
);
|
} else if (QrAlphaNumeric.validationRegex.hasMatch(data)) {
|
||||||
|
// Alphanumeric mode for alphanumeric characters only
|
||||||
final qrCode = QrCode(typeNumber, errorCorrectLevel);
|
datum = QrAlphaNumeric.fromString(data);
|
||||||
for (final datum in datumList) {
|
} else {
|
||||||
qrCode._addToList(datum);
|
// Default to byte mode for other characters
|
||||||
|
datum = QrByte(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final typeNumber = _calculateTypeNumberFromData(errorCorrectLevel, datum);
|
||||||
|
|
||||||
|
final qrCode = QrCode(typeNumber, errorCorrectLevel).._addToList(datum);
|
||||||
return qrCode;
|
return qrCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory QrCode.fromUint8List({
|
factory QrCode.fromUint8List({
|
||||||
required Uint8List data,
|
required Uint8List data,
|
||||||
required QrErrorCorrectLevel errorCorrectLevel,
|
required int errorCorrectLevel,
|
||||||
}) {
|
}) {
|
||||||
final datum = QrByte.fromUint8List(data);
|
final typeNumber = _calculateTypeNumberFromData(
|
||||||
final typeNumber = _calculateTypeNumberFromData(errorCorrectLevel, [datum]);
|
errorCorrectLevel,
|
||||||
return QrCode(typeNumber, errorCorrectLevel).._addToList(datum);
|
QrByte.fromUint8List(data),
|
||||||
|
);
|
||||||
|
return QrCode(typeNumber, errorCorrectLevel)
|
||||||
|
.._addToList(QrByte.fromUint8List(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _calculateTotalDataBits(
|
static int _calculateTotalDataBits(int typeNumber, int errorCorrectLevel) {
|
||||||
int typeNumber,
|
|
||||||
QrErrorCorrectLevel errorCorrectLevel,
|
|
||||||
) {
|
|
||||||
final rsBlocks = QrRsBlock.getRSBlocks(typeNumber, errorCorrectLevel);
|
final rsBlocks = QrRsBlock.getRSBlocks(typeNumber, errorCorrectLevel);
|
||||||
var totalDataBits = 0;
|
var totalDataBits = 0;
|
||||||
for (var rsBlock in rsBlocks) {
|
for (var rsBlock in rsBlocks) {
|
||||||
|
|
@ -68,35 +73,26 @@ class QrCode {
|
||||||
return totalDataBits;
|
return totalDataBits;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _calculateTypeNumberFromData(
|
static int _calculateTypeNumberFromData(int errorCorrectLevel, QrDatum data) {
|
||||||
QrErrorCorrectLevel errorCorrectLevel,
|
|
||||||
List<QrDatum> data,
|
|
||||||
) {
|
|
||||||
for (var typeNumber = 1; typeNumber <= 40; typeNumber++) {
|
for (var typeNumber = 1; typeNumber <= 40; typeNumber++) {
|
||||||
final totalDataBits = _calculateTotalDataBits(
|
final totalDataBits = _calculateTotalDataBits(
|
||||||
typeNumber,
|
typeNumber,
|
||||||
errorCorrectLevel,
|
errorCorrectLevel,
|
||||||
);
|
);
|
||||||
|
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer()
|
||||||
for (final datum in data) {
|
..put(data.mode, 4)
|
||||||
buffer
|
..put(data.length, _lengthInBits(data.mode, typeNumber));
|
||||||
..put(datum.mode.value, 4)
|
data.write(buffer);
|
||||||
..put(datum.length, datum.mode.getLengthBits(typeNumber));
|
|
||||||
datum.write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer.length <= totalDataBits) return typeNumber;
|
if (buffer.length <= totalDataBits) return typeNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we reach here, the data is too long for any QR Code version.
|
// If we reach here, the data is too long for any QR Code version.
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer()
|
||||||
for (final datum in data) {
|
..put(data.mode, 4)
|
||||||
buffer
|
..put(data.length, _lengthInBits(data.mode, 40));
|
||||||
..put(datum.mode.value, 4)
|
data.write(buffer);
|
||||||
..put(datum.length, datum.mode.getLengthBits(40));
|
|
||||||
datum.write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
final maxBits = _calculateTotalDataBits(40, errorCorrectLevel);
|
final maxBits = _calculateTotalDataBits(40, errorCorrectLevel);
|
||||||
|
|
||||||
|
|
@ -104,9 +100,19 @@ class QrCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
void addData(String data) {
|
void addData(String data) {
|
||||||
for (final datum in QrDatum.toDatums(data)) {
|
final QrDatum datum;
|
||||||
_addToList(datum);
|
// Automatically determine mode here, just like QrCode.fromData
|
||||||
|
if (QrNumeric.validationRegex.hasMatch(data)) {
|
||||||
|
// Numeric mode for numbers only
|
||||||
|
datum = QrNumeric.fromString(data);
|
||||||
|
} else if (QrAlphaNumeric.validationRegex.hasMatch(data)) {
|
||||||
|
// Alphanumeric mode for alphanumeric characters only
|
||||||
|
datum = QrAlphaNumeric.fromString(data);
|
||||||
|
} else {
|
||||||
|
// Default to byte mode for other characters
|
||||||
|
datum = QrByte(data);
|
||||||
}
|
}
|
||||||
|
_addToList(datum);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addByteData(ByteData data) => _addToList(QrByte.fromByteData(data));
|
void addByteData(ByteData data) => _addToList(QrByte.fromByteData(data));
|
||||||
|
|
@ -121,8 +127,6 @@ class QrCode {
|
||||||
void addAlphaNumeric(String alphaNumeric) =>
|
void addAlphaNumeric(String alphaNumeric) =>
|
||||||
_addToList(QrAlphaNumeric.fromString(alphaNumeric));
|
_addToList(QrAlphaNumeric.fromString(alphaNumeric));
|
||||||
|
|
||||||
void addECI(int eciValue) => _addToList(QrEci(eciValue));
|
|
||||||
|
|
||||||
void _addToList(QrDatum data) {
|
void _addToList(QrDatum data) {
|
||||||
_dataList.add(data);
|
_dataList.add(data);
|
||||||
_dataCache = null;
|
_dataCache = null;
|
||||||
|
|
@ -138,7 +142,7 @@ const int _pad1 = 0x11;
|
||||||
|
|
||||||
List<int> _createData(
|
List<int> _createData(
|
||||||
int typeNumber,
|
int typeNumber,
|
||||||
QrErrorCorrectLevel errorCorrectLevel,
|
int errorCorrectLevel,
|
||||||
List<QrDatum> dataList,
|
List<QrDatum> dataList,
|
||||||
) {
|
) {
|
||||||
final rsBlocks = QrRsBlock.getRSBlocks(typeNumber, errorCorrectLevel);
|
final rsBlocks = QrRsBlock.getRSBlocks(typeNumber, errorCorrectLevel);
|
||||||
|
|
@ -148,8 +152,8 @@ List<int> _createData(
|
||||||
for (var i = 0; i < dataList.length; i++) {
|
for (var i = 0; i < dataList.length; i++) {
|
||||||
final data = dataList[i];
|
final data = dataList[i];
|
||||||
buffer
|
buffer
|
||||||
..put(data.mode.value, 4)
|
..put(data.mode, 4)
|
||||||
..put(data.length, data.mode.getLengthBits(typeNumber));
|
..put(data.length, _lengthInBits(data.mode, typeNumber));
|
||||||
data.write(buffer);
|
data.write(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,10 +164,6 @@ List<int> _createData(
|
||||||
errorCorrectLevel,
|
errorCorrectLevel,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (buffer.length > totalDataBits) {
|
|
||||||
throw InputTooLongException(buffer.length, totalDataBits);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HUH?
|
// HUH?
|
||||||
// èIí[ÉRÅ[Éh
|
// èIí[ÉRÅ[Éh
|
||||||
if (buffer.length + 4 <= totalDataBits) {
|
if (buffer.length + 4 <= totalDataBits) {
|
||||||
|
|
@ -244,6 +244,39 @@ List<int> _createBytes(QrBitBuffer buffer, List<QrRsBlock> rsBlocks) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _lengthInBits(int mode, int type) {
|
||||||
|
if (1 <= type && type < 10) {
|
||||||
|
// 1 - 9
|
||||||
|
return switch (mode) {
|
||||||
|
qr_mode.modeNumber => 10,
|
||||||
|
qr_mode.modeAlphaNum => 9,
|
||||||
|
qr_mode.mode8bitByte => 8,
|
||||||
|
qr_mode.modeKanji => 8,
|
||||||
|
_ => throw ArgumentError('mode:$mode'),
|
||||||
|
};
|
||||||
|
} else if (type < 27) {
|
||||||
|
// 10 - 26
|
||||||
|
return switch (mode) {
|
||||||
|
qr_mode.modeNumber => 12,
|
||||||
|
qr_mode.modeAlphaNum => 11,
|
||||||
|
qr_mode.mode8bitByte => 16,
|
||||||
|
qr_mode.modeKanji => 10,
|
||||||
|
_ => throw ArgumentError('mode:$mode'),
|
||||||
|
};
|
||||||
|
} else if (type < 41) {
|
||||||
|
// 27 - 40
|
||||||
|
return switch (mode) {
|
||||||
|
qr_mode.modeNumber => 14,
|
||||||
|
qr_mode.modeAlphaNum => 13,
|
||||||
|
qr_mode.mode8bitByte => 16,
|
||||||
|
qr_mode.modeKanji => 12,
|
||||||
|
_ => throw ArgumentError('mode:$mode'),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw ArgumentError('type:$type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QrPolynomial _errorCorrectPolynomial(int errorCorrectLength) {
|
QrPolynomial _errorCorrectPolynomial(int errorCorrectLength) {
|
||||||
var a = QrPolynomial([1], 0);
|
var a = QrPolynomial([1], 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,34 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import 'error_correct_level.dart';
|
import 'mask_pattern.dart' as qr_mask_pattern;
|
||||||
import 'mask_pattern.dart';
|
|
||||||
import 'qr_code.dart';
|
import 'qr_code.dart';
|
||||||
import 'util.dart' as qr_util;
|
import 'util.dart' as qr_util;
|
||||||
|
|
||||||
/// Renders the encoded data from a [QrCode] in a portable format.
|
/// Renders the encoded data from a [QrCode] in a portable format.
|
||||||
class QrImage {
|
class QrImage {
|
||||||
static const _pixelUnassigned = 0;
|
|
||||||
static const _pixelLight = 1;
|
|
||||||
static const _pixelDark = 2;
|
|
||||||
|
|
||||||
final int moduleCount;
|
final int moduleCount;
|
||||||
final int typeNumber;
|
final int typeNumber;
|
||||||
final QrErrorCorrectLevel errorCorrectLevel;
|
final int errorCorrectLevel;
|
||||||
final int maskPattern;
|
final int maskPattern;
|
||||||
|
|
||||||
final Uint8List _data;
|
final _modules = <List<bool?>>[];
|
||||||
|
|
||||||
/// Generates a QrImage with the best mask pattern encoding [qrCode].
|
/// Generates a QrImage with the best mask pattern encoding [qrCode].
|
||||||
factory QrImage(QrCode qrCode) {
|
factory QrImage(QrCode qrCode) {
|
||||||
// Create a template with invariant patterns
|
var minLostPoint = 0.0;
|
||||||
final template = QrImage._template(qrCode);
|
QrImage? bestImage;
|
||||||
final moduleCount = template.moduleCount;
|
|
||||||
final dataSize = moduleCount * moduleCount;
|
|
||||||
|
|
||||||
// Step 1: Clone template to working buffer and place data (no mask)
|
|
||||||
final dataMap = Uint8List(dataSize)..setRange(0, dataSize, template._data);
|
|
||||||
|
|
||||||
// Create a temporary QrImage to use its _placeData method
|
|
||||||
// We pass 0 as maskPattern, but we will modify _placeData to NOT mask.
|
|
||||||
QrImage._fromData(qrCode, 0, dataMap)._placeData(qrCode.dataCache);
|
|
||||||
|
|
||||||
final workingBuffer = Uint8List(dataSize);
|
|
||||||
var minLostPoint = double.maxFinite;
|
|
||||||
var bestMaskPattern = 0;
|
|
||||||
Uint8List? bestData; // We need to store the best result.
|
|
||||||
|
|
||||||
// Step 2: Try all 8 masks
|
|
||||||
for (var i = 0; i < 8; i++) {
|
for (var i = 0; i < 8; i++) {
|
||||||
// Copy pre-placed data to working buffer
|
final testImage = QrImage._test(qrCode, i);
|
||||||
workingBuffer.setRange(0, dataSize, dataMap);
|
|
||||||
|
|
||||||
final testImage = QrImage._fromData(qrCode, i, workingBuffer)
|
|
||||||
// Apply mask (XOR)
|
|
||||||
.._applyMask(QrMaskPattern.values[i], template._data);
|
|
||||||
|
|
||||||
final lostPoint = _lostPoint(testImage);
|
final lostPoint = _lostPoint(testImage);
|
||||||
|
|
||||||
if (lostPoint < minLostPoint) {
|
if (i == 0 || minLostPoint > lostPoint) {
|
||||||
minLostPoint = lostPoint;
|
minLostPoint = lostPoint;
|
||||||
bestMaskPattern = i;
|
bestImage = testImage;
|
||||||
// Copy working buffer to bestData
|
|
||||||
bestData ??= Uint8List(dataSize);
|
|
||||||
bestData.setRange(0, dataSize, workingBuffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final finalImage = QrImage._fromData(qrCode, bestMaskPattern, bestData!)
|
return QrImage.withMaskPattern(qrCode, bestImage!.maskPattern);
|
||||||
// Final setup with correct format info (not test, so actual pixels)
|
|
||||||
.._setupTypeInfo(bestMaskPattern, false);
|
|
||||||
if (finalImage.typeNumber >= 7) {
|
|
||||||
finalImage._setupTypeNumber(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a specific image for the [qrCode] and [maskPattern].
|
/// Generates a specific image for the [qrCode] and [maskPattern].
|
||||||
|
|
@ -74,75 +36,35 @@ class QrImage {
|
||||||
: assert(maskPattern >= 0 && maskPattern <= 7),
|
: assert(maskPattern >= 0 && maskPattern <= 7),
|
||||||
moduleCount = qrCode.moduleCount,
|
moduleCount = qrCode.moduleCount,
|
||||||
typeNumber = qrCode.typeNumber,
|
typeNumber = qrCode.typeNumber,
|
||||||
errorCorrectLevel = qrCode.errorCorrectLevel,
|
errorCorrectLevel = qrCode.errorCorrectLevel {
|
||||||
_data = Uint8List(qrCode.moduleCount * qrCode.moduleCount) {
|
|
||||||
_makeImpl(maskPattern, qrCode.dataCache, false);
|
_makeImpl(maskPattern, qrCode.dataCache, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal constructor for template creation
|
QrImage._test(QrCode qrCode, this.maskPattern)
|
||||||
QrImage._template(QrCode qrCode)
|
|
||||||
: moduleCount = qrCode.moduleCount,
|
: moduleCount = qrCode.moduleCount,
|
||||||
typeNumber = qrCode.typeNumber,
|
typeNumber = qrCode.typeNumber,
|
||||||
errorCorrectLevel = qrCode.errorCorrectLevel,
|
errorCorrectLevel = qrCode.errorCorrectLevel {
|
||||||
maskPattern = 0, // Irrelevant
|
_makeImpl(maskPattern, qrCode.dataCache, true);
|
||||||
_data = Uint8List(qrCode.moduleCount * qrCode.moduleCount) {
|
|
||||||
// Setup invariant parts with test=true (reserving space)
|
|
||||||
_resetModules();
|
|
||||||
_setupPositionProbePattern(0, 0);
|
|
||||||
_setupPositionProbePattern(moduleCount - 7, 0);
|
|
||||||
_setupPositionProbePattern(0, moduleCount - 7);
|
|
||||||
_setupPositionAdjustPattern();
|
|
||||||
_setupTimingPattern();
|
|
||||||
// Type info and Type number are invariant if test=true (all light)
|
|
||||||
_setupTypeInfo(0, true);
|
|
||||||
if (typeNumber >= 7) {
|
|
||||||
_setupTypeNumber(true);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal constructor for testing phase
|
|
||||||
QrImage._fromData(QrCode qrCode, this.maskPattern, this._data)
|
|
||||||
: moduleCount = qrCode.moduleCount,
|
|
||||||
typeNumber = qrCode.typeNumber,
|
|
||||||
errorCorrectLevel = qrCode.errorCorrectLevel;
|
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
List<List<bool?>> get qrModules {
|
List<List<bool?>> get qrModules => _modules;
|
||||||
final list = <List<bool?>>[];
|
|
||||||
for (var r = 0; r < moduleCount; r++) {
|
|
||||||
final row = List<bool?>.filled(moduleCount, null);
|
|
||||||
for (var c = 0; c < moduleCount; c++) {
|
|
||||||
final v = _data[r * moduleCount + c];
|
|
||||||
row[c] = v == _pixelUnassigned ? null : (v == _pixelDark);
|
|
||||||
}
|
|
||||||
list.add(row);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resetModules() {
|
void _resetModules() {
|
||||||
_data.fillRange(0, _data.length, _pixelUnassigned);
|
_modules.clear();
|
||||||
|
for (var row = 0; row < moduleCount; row++) {
|
||||||
|
_modules.add(List<bool?>.filled(moduleCount, null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDark(int row, int col) {
|
bool isDark(int row, int col) {
|
||||||
if (row < 0 || moduleCount <= row) {
|
if (row < 0 || moduleCount <= row || col < 0 || moduleCount <= col) {
|
||||||
throw RangeError.range(row, 0, moduleCount - 1, 'row');
|
throw ArgumentError('$row , $col');
|
||||||
}
|
}
|
||||||
if (col < 0 || moduleCount <= col) {
|
return _modules[row][col]!;
|
||||||
throw RangeError.range(col, 0, moduleCount - 1, 'col');
|
|
||||||
}
|
|
||||||
return _data[row * moduleCount + col] == _pixelDark;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _set(int row, int col, bool value) {
|
|
||||||
_data[row * moduleCount + col] = value ? _pixelDark : _pixelLight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _makeImpl(int maskPattern, List<int> dataCache, bool test) {
|
void _makeImpl(int maskPattern, List<int> dataCache, bool test) {
|
||||||
// If not testing, we do full setup.
|
|
||||||
// If testing (template), this method is NOT called directly, but manually
|
|
||||||
// in _template.
|
|
||||||
// However, withMaskPattern calls this.
|
|
||||||
_resetModules();
|
_resetModules();
|
||||||
_setupPositionProbePattern(0, 0);
|
_setupPositionProbePattern(0, 0);
|
||||||
_setupPositionProbePattern(moduleCount - 7, 0);
|
_setupPositionProbePattern(moduleCount - 7, 0);
|
||||||
|
|
@ -158,13 +80,6 @@ class QrImage {
|
||||||
_mapData(dataCache, maskPattern);
|
_mapData(dataCache, maskPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... (existing constructors)
|
|
||||||
|
|
||||||
// Refactored _mapData to JUST call _placeData then _applyMask?
|
|
||||||
// No, original _mapData did both.
|
|
||||||
|
|
||||||
// Implemented below...
|
|
||||||
|
|
||||||
void _setupPositionProbePattern(int row, int col) {
|
void _setupPositionProbePattern(int row, int col) {
|
||||||
for (var r = -1; r <= 7; r++) {
|
for (var r = -1; r <= 7; r++) {
|
||||||
if (row + r <= -1 || moduleCount <= row + r) continue;
|
if (row + r <= -1 || moduleCount <= row + r) continue;
|
||||||
|
|
@ -175,9 +90,9 @@ class QrImage {
|
||||||
if ((0 <= r && r <= 6 && (c == 0 || c == 6)) ||
|
if ((0 <= r && r <= 6 && (c == 0 || c == 6)) ||
|
||||||
(0 <= c && c <= 6 && (r == 0 || r == 6)) ||
|
(0 <= c && c <= 6 && (r == 0 || r == 6)) ||
|
||||||
(2 <= r && r <= 4 && 2 <= c && c <= 4)) {
|
(2 <= r && r <= 4 && 2 <= c && c <= 4)) {
|
||||||
_set(row + r, col + c, true);
|
_modules[row + r][col + c] = true;
|
||||||
} else {
|
} else {
|
||||||
_set(row + r, col + c, false);
|
_modules[row + r][col + c] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,16 +106,16 @@ class QrImage {
|
||||||
final row = pos[i];
|
final row = pos[i];
|
||||||
final col = pos[j];
|
final col = pos[j];
|
||||||
|
|
||||||
if (_data[row * moduleCount + col] != _pixelUnassigned) {
|
if (_modules[row][col] != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var r = -2; r <= 2; r++) {
|
for (var r = -2; r <= 2; r++) {
|
||||||
for (var c = -2; c <= 2; c++) {
|
for (var c = -2; c <= 2; c++) {
|
||||||
if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) {
|
if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) {
|
||||||
_set(row + r, col + c, true);
|
_modules[row + r][col + c] = true;
|
||||||
} else {
|
} else {
|
||||||
_set(row + r, col + c, false);
|
_modules[row + r][col + c] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -210,22 +125,22 @@ class QrImage {
|
||||||
|
|
||||||
void _setupTimingPattern() {
|
void _setupTimingPattern() {
|
||||||
for (var r = 8; r < moduleCount - 8; r++) {
|
for (var r = 8; r < moduleCount - 8; r++) {
|
||||||
if (_data[r * moduleCount + 6] != _pixelUnassigned) {
|
if (_modules[r][6] != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_set(r, 6, r.isEven);
|
_modules[r][6] = r.isEven;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var c = 8; c < moduleCount - 8; c++) {
|
for (var c = 8; c < moduleCount - 8; c++) {
|
||||||
if (_data[6 * moduleCount + c] != _pixelUnassigned) {
|
if (_modules[6][c] != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_set(6, c, c.isEven);
|
_modules[6][c] = c.isEven;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setupTypeInfo(int maskPattern, bool test) {
|
void _setupTypeInfo(int maskPattern, bool test) {
|
||||||
final data = (errorCorrectLevel.index << 3) | maskPattern;
|
final data = (errorCorrectLevel << 3) | maskPattern;
|
||||||
final bits = qr_util.bchTypeInfo(data);
|
final bits = qr_util.bchTypeInfo(data);
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
|
|
@ -236,11 +151,11 @@ class QrImage {
|
||||||
mod = !test && ((bits >> i) & 1) == 1;
|
mod = !test && ((bits >> i) & 1) == 1;
|
||||||
|
|
||||||
if (i < 6) {
|
if (i < 6) {
|
||||||
_set(i, 8, mod);
|
_modules[i][8] = mod;
|
||||||
} else if (i < 8) {
|
} else if (i < 8) {
|
||||||
_set(i + 1, 8, mod);
|
_modules[i + 1][8] = mod;
|
||||||
} else {
|
} else {
|
||||||
_set(moduleCount - 15 + i, 8, mod);
|
_modules[moduleCount - 15 + i][8] = mod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,16 +164,16 @@ class QrImage {
|
||||||
mod = !test && ((bits >> i) & 1) == 1;
|
mod = !test && ((bits >> i) & 1) == 1;
|
||||||
|
|
||||||
if (i < 8) {
|
if (i < 8) {
|
||||||
_set(8, moduleCount - i - 1, mod);
|
_modules[8][moduleCount - i - 1] = mod;
|
||||||
} else if (i < 9) {
|
} else if (i < 9) {
|
||||||
_set(8, 15 - i - 1 + 1, mod);
|
_modules[8][15 - i - 1 + 1] = mod;
|
||||||
} else {
|
} else {
|
||||||
_set(8, 15 - i - 1, mod);
|
_modules[8][15 - i - 1] = mod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixed module
|
// fixed module
|
||||||
_set(moduleCount - 8, 8, !test);
|
_modules[moduleCount - 8][8] = !test;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setupTypeNumber(bool test) {
|
void _setupTypeNumber(bool test) {
|
||||||
|
|
@ -266,12 +181,12 @@ class QrImage {
|
||||||
|
|
||||||
for (var i = 0; i < 18; i++) {
|
for (var i = 0; i < 18; i++) {
|
||||||
final mod = !test && ((bits >> i) & 1) == 1;
|
final mod = !test && ((bits >> i) & 1) == 1;
|
||||||
_set(i ~/ 3, i % 3 + moduleCount - 8 - 3, mod);
|
_modules[i ~/ 3][i % 3 + moduleCount - 8 - 3] = mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < 18; i++) {
|
for (var i = 0; i < 18; i++) {
|
||||||
final mod = !test && ((bits >> i) & 1) == 1;
|
final mod = !test && ((bits >> i) & 1) == 1;
|
||||||
_set(i % 3 + moduleCount - 8 - 3, i ~/ 3, mod);
|
_modules[i % 3 + moduleCount - 8 - 3][i ~/ 3] = mod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,20 +201,20 @@ class QrImage {
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
for (var c = 0; c < 2; c++) {
|
for (var c = 0; c < 2; c++) {
|
||||||
if (_data[row * moduleCount + (col - c)] == _pixelUnassigned) {
|
if (_modules[row][col - c] == null) {
|
||||||
var dark = false;
|
var dark = false;
|
||||||
|
|
||||||
if (byteIndex < data.length) {
|
if (byteIndex < data.length) {
|
||||||
dark = ((data[byteIndex] >> bitIndex) & 1) == 1;
|
dark = ((data[byteIndex] >> bitIndex) & 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final mask = QrMaskPattern.values[maskPattern].check(row, col - c);
|
final mask = _mask(maskPattern, row, col - c);
|
||||||
|
|
||||||
if (mask) {
|
if (mask) {
|
||||||
dark = !dark;
|
dark = !dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
_set(row, col - c, dark);
|
_modules[row][col - c] = dark;
|
||||||
bitIndex--;
|
bitIndex--;
|
||||||
|
|
||||||
if (bitIndex == -1) {
|
if (bitIndex == -1) {
|
||||||
|
|
@ -319,123 +234,49 @@ class QrImage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _placeData(List<int> data) {
|
|
||||||
var inc = -1;
|
|
||||||
var row = moduleCount - 1;
|
|
||||||
var bitIndex = 7;
|
|
||||||
var byteIndex = 0;
|
|
||||||
|
|
||||||
for (var col = moduleCount - 1; col > 0; col -= 2) {
|
|
||||||
if (col == 6) col--;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
for (var c = 0; c < 2; c++) {
|
|
||||||
if (_data[row * moduleCount + (col - c)] == _pixelUnassigned) {
|
|
||||||
var dark = false;
|
|
||||||
|
|
||||||
if (byteIndex < data.length) {
|
|
||||||
dark = ((data[byteIndex] >> bitIndex) & 1) == 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_set(row, col - c, dark);
|
bool _mask(int maskPattern, int i, int j) => switch (maskPattern) {
|
||||||
bitIndex--;
|
qr_mask_pattern.pattern000 => (i + j).isEven,
|
||||||
|
qr_mask_pattern.pattern001 => i.isEven,
|
||||||
if (bitIndex == -1) {
|
qr_mask_pattern.pattern010 => j % 3 == 0,
|
||||||
byteIndex++;
|
qr_mask_pattern.pattern011 => (i + j) % 3 == 0,
|
||||||
bitIndex = 7;
|
qr_mask_pattern.pattern100 => ((i ~/ 2) + (j ~/ 3)).isEven,
|
||||||
}
|
qr_mask_pattern.pattern101 => (i * j) % 2 + (i * j) % 3 == 0,
|
||||||
}
|
qr_mask_pattern.pattern110 => ((i * j) % 2 + (i * j) % 3).isEven,
|
||||||
}
|
qr_mask_pattern.pattern111 => ((i * j) % 3 + (i + j) % 2).isEven,
|
||||||
|
_ => throw ArgumentError('bad maskPattern:$maskPattern'),
|
||||||
row += inc;
|
};
|
||||||
|
|
||||||
if (row < 0 || moduleCount <= row) {
|
|
||||||
row -= inc;
|
|
||||||
inc = -inc;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _applyMask(QrMaskPattern maskPattern, Uint8List templateData) {
|
|
||||||
var inc = -1;
|
|
||||||
var row = moduleCount - 1;
|
|
||||||
|
|
||||||
for (var col = moduleCount - 1; col > 0; col -= 2) {
|
|
||||||
if (col == 6) col--;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
for (var c = 0; c < 2; c++) {
|
|
||||||
if (templateData[row * moduleCount + (col - c)] == _pixelUnassigned) {
|
|
||||||
final mask = maskPattern.check(row, col - c);
|
|
||||||
if (mask) {
|
|
||||||
_data[row * moduleCount + (col - c)] ^= _pixelDark ^ _pixelLight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
row += inc;
|
|
||||||
|
|
||||||
if (row < 0 || moduleCount <= row) {
|
|
||||||
row -= inc;
|
|
||||||
inc = -inc;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _lostPoint(QrImage qrImage) {
|
double _lostPoint(QrImage qrImage) {
|
||||||
final moduleCount = qrImage.moduleCount;
|
final moduleCount = qrImage.moduleCount;
|
||||||
final data = qrImage._data;
|
|
||||||
var lostPoint = 0.0;
|
var lostPoint = 0.0;
|
||||||
|
int row, col;
|
||||||
|
|
||||||
// Cache data length for faster access (though it's final)
|
// LEVEL1
|
||||||
// Accessing local vars is faster.
|
for (row = 0; row < moduleCount; row++) {
|
||||||
|
for (col = 0; col < moduleCount; col++) {
|
||||||
// Level 1
|
|
||||||
for (var row = 0; row < moduleCount; row++) {
|
|
||||||
for (var col = 0; col < moduleCount; col++) {
|
|
||||||
var sameCount = 0;
|
var sameCount = 0;
|
||||||
final currentIdx = row * moduleCount + col;
|
final dark = qrImage.isDark(row, col);
|
||||||
final isDark = data[currentIdx] == QrImage._pixelDark;
|
|
||||||
|
|
||||||
// Check all 8 neighbors
|
for (var r = -1; r <= 1; r++) {
|
||||||
// Top row
|
if (row + r < 0 || moduleCount <= row + r) {
|
||||||
if (row > 0) {
|
continue;
|
||||||
final upIdx = currentIdx - moduleCount;
|
|
||||||
if (col > 0 && (data[upIdx - 1] == QrImage._pixelDark) == isDark) {
|
|
||||||
sameCount++;
|
|
||||||
}
|
|
||||||
if ((data[upIdx] == QrImage._pixelDark) == isDark) sameCount++;
|
|
||||||
if (col < moduleCount - 1 &&
|
|
||||||
(data[upIdx + 1] == QrImage._pixelDark) == isDark) {
|
|
||||||
sameCount++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middle row (left/right)
|
for (var c = -1; c <= 1; c++) {
|
||||||
if (col > 0 && (data[currentIdx - 1] == QrImage._pixelDark) == isDark) {
|
if (col + c < 0 || moduleCount <= col + c) {
|
||||||
sameCount++;
|
continue;
|
||||||
}
|
|
||||||
if (col < moduleCount - 1 &&
|
|
||||||
(data[currentIdx + 1] == QrImage._pixelDark) == isDark) {
|
|
||||||
sameCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom row
|
if (r == 0 && c == 0) {
|
||||||
if (row < moduleCount - 1) {
|
continue;
|
||||||
final downIdx = currentIdx + moduleCount;
|
}
|
||||||
if (col > 0 && (data[downIdx - 1] == QrImage._pixelDark) == isDark) {
|
|
||||||
|
if (dark == qrImage.isDark(row + r, col + c)) {
|
||||||
sameCount++;
|
sameCount++;
|
||||||
}
|
}
|
||||||
if ((data[downIdx] == QrImage._pixelDark) == isDark) sameCount++;
|
|
||||||
if (col < moduleCount - 1 &&
|
|
||||||
(data[downIdx + 1] == QrImage._pixelDark) == isDark) {
|
|
||||||
sameCount++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -445,58 +286,58 @@ double _lostPoint(QrImage qrImage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Level 2: 2x2 blocks of same color
|
// LEVEL2
|
||||||
for (var row = 0; row < moduleCount - 1; row++) {
|
for (row = 0; row < moduleCount - 1; row++) {
|
||||||
for (var col = 0; col < moduleCount - 1; col++) {
|
for (col = 0; col < moduleCount - 1; col++) {
|
||||||
final idx = row * moduleCount + col;
|
var count = 0;
|
||||||
final p00 = data[idx];
|
if (qrImage.isDark(row, col)) count++;
|
||||||
final p01 = data[idx + 1];
|
if (qrImage.isDark(row + 1, col)) count++;
|
||||||
final p10 = data[idx + moduleCount];
|
if (qrImage.isDark(row, col + 1)) count++;
|
||||||
final p11 = data[idx + moduleCount + 1];
|
if (qrImage.isDark(row + 1, col + 1)) count++;
|
||||||
|
if (count == 0 || count == 4) {
|
||||||
if (p00 == p01 && p00 == p10 && p00 == p11) {
|
|
||||||
lostPoint += 3;
|
lostPoint += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Level 3: 1:1:3:1:1 pattern
|
// LEVEL3
|
||||||
// Dark, Light, Dark, Dark, Dark, Light, Dark
|
for (row = 0; row < moduleCount; row++) {
|
||||||
for (var row = 0; row < moduleCount; row++) {
|
for (col = 0; col < moduleCount - 6; col++) {
|
||||||
for (var col = 0; col < moduleCount - 6; col++) {
|
if (qrImage.isDark(row, col) &&
|
||||||
final idx = row * moduleCount + col;
|
!qrImage.isDark(row, col + 1) &&
|
||||||
if (data[idx] == QrImage._pixelDark &&
|
qrImage.isDark(row, col + 2) &&
|
||||||
data[idx + 1] == QrImage._pixelLight &&
|
qrImage.isDark(row, col + 3) &&
|
||||||
data[idx + 2] == QrImage._pixelDark &&
|
qrImage.isDark(row, col + 4) &&
|
||||||
data[idx + 3] == QrImage._pixelDark &&
|
!qrImage.isDark(row, col + 5) &&
|
||||||
data[idx + 4] == QrImage._pixelDark &&
|
qrImage.isDark(row, col + 6)) {
|
||||||
data[idx + 5] == QrImage._pixelLight &&
|
|
||||||
data[idx + 6] == QrImage._pixelDark) {
|
|
||||||
lostPoint += 40;
|
lostPoint += 40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cols
|
for (col = 0; col < moduleCount; col++) {
|
||||||
for (var col = 0; col < moduleCount; col++) {
|
for (row = 0; row < moduleCount - 6; row++) {
|
||||||
for (var row = 0; row < moduleCount - 6; row++) {
|
if (qrImage.isDark(row, col) &&
|
||||||
final idx = row * moduleCount + col;
|
!qrImage.isDark(row + 1, col) &&
|
||||||
if (data[idx] == QrImage._pixelDark &&
|
qrImage.isDark(row + 2, col) &&
|
||||||
data[idx + moduleCount] == QrImage._pixelLight &&
|
qrImage.isDark(row + 3, col) &&
|
||||||
data[idx + 2 * moduleCount] == QrImage._pixelDark &&
|
qrImage.isDark(row + 4, col) &&
|
||||||
data[idx + 3 * moduleCount] == QrImage._pixelDark &&
|
!qrImage.isDark(row + 5, col) &&
|
||||||
data[idx + 4 * moduleCount] == QrImage._pixelDark &&
|
qrImage.isDark(row + 6, col)) {
|
||||||
data[idx + 5 * moduleCount] == QrImage._pixelLight &&
|
|
||||||
data[idx + 6 * moduleCount] == QrImage._pixelDark) {
|
|
||||||
lostPoint += 40;
|
lostPoint += 40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Level 4: Dark ratio
|
// LEVEL4
|
||||||
var darkCount = 0;
|
var darkCount = 0;
|
||||||
for (var i = 0; i < data.length; i++) {
|
|
||||||
if (data[i] == QrImage._pixelDark) darkCount++;
|
for (col = 0; col < moduleCount; col++) {
|
||||||
|
for (row = 0; row < moduleCount; row++) {
|
||||||
|
if (qrImage.isDark(row, col)) {
|
||||||
|
darkCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ratio = (100 * darkCount / moduleCount / moduleCount - 50).abs() / 5;
|
final ratio = (100 * darkCount / moduleCount / moduleCount - 50).abs() / 5;
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,7 @@ class QrRsBlock {
|
||||||
|
|
||||||
QrRsBlock._(this.totalCount, this.dataCount);
|
QrRsBlock._(this.totalCount, this.dataCount);
|
||||||
|
|
||||||
static List<QrRsBlock> getRSBlocks(
|
static List<QrRsBlock> getRSBlocks(int typeNumber, int errorCorrectLevel) {
|
||||||
int typeNumber,
|
|
||||||
QrErrorCorrectLevel errorCorrectLevel,
|
|
||||||
) {
|
|
||||||
final rsBlock = _getRsBlockTable(typeNumber, errorCorrectLevel);
|
final rsBlock = _getRsBlockTable(typeNumber, errorCorrectLevel);
|
||||||
|
|
||||||
final length = rsBlock.length ~/ 3;
|
final length = rsBlock.length ~/ 3;
|
||||||
|
|
@ -32,12 +29,15 @@ class QrRsBlock {
|
||||||
|
|
||||||
List<int> _getRsBlockTable(
|
List<int> _getRsBlockTable(
|
||||||
int typeNumber,
|
int typeNumber,
|
||||||
QrErrorCorrectLevel errorCorrectLevel,
|
int errorCorrectLevel,
|
||||||
) => switch (errorCorrectLevel) {
|
) => switch (errorCorrectLevel) {
|
||||||
QrErrorCorrectLevel.low => _rsBlockTable[(typeNumber - 1) * 4 + 0],
|
QrErrorCorrectLevel.L => _rsBlockTable[(typeNumber - 1) * 4 + 0],
|
||||||
QrErrorCorrectLevel.medium => _rsBlockTable[(typeNumber - 1) * 4 + 1],
|
QrErrorCorrectLevel.M => _rsBlockTable[(typeNumber - 1) * 4 + 1],
|
||||||
QrErrorCorrectLevel.quartile => _rsBlockTable[(typeNumber - 1) * 4 + 2],
|
QrErrorCorrectLevel.Q => _rsBlockTable[(typeNumber - 1) * 4 + 2],
|
||||||
QrErrorCorrectLevel.high => _rsBlockTable[(typeNumber - 1) * 4 + 3],
|
QrErrorCorrectLevel.H => _rsBlockTable[(typeNumber - 1) * 4 + 3],
|
||||||
|
_ => throw ArgumentError(
|
||||||
|
'bad rs block @ typeNumber: $typeNumber/errorCorrectLevel:$errorCorrectLevel',
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const List<List<int>> _rsBlockTable = [
|
const List<List<int>> _rsBlockTable = [
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,9 @@ dependencies:
|
||||||
meta: ^1.7.0
|
meta: ^1.7.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
args: ^2.1.0
|
|
||||||
benchmark_harness: ^2.0.0
|
|
||||||
build_runner: ^2.2.1
|
build_runner: ^2.2.1
|
||||||
build_web_compilers: ^4.1.4
|
build_web_compilers: ^4.1.4
|
||||||
dart_flutter_team_lints: ^3.0.0
|
dart_flutter_team_lints: ^3.0.0
|
||||||
path: ^1.9.1
|
|
||||||
stream_transform: ^2.0.0
|
stream_transform: ^2.0.0
|
||||||
test: ^1.21.6
|
test: ^1.21.6
|
||||||
test_process: ^2.1.1
|
|
||||||
web: ^1.1.0
|
web: ^1.1.0
|
||||||
|
|
|
||||||
|
|
@ -1,564 +0,0 @@
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:qr/qr.dart';
|
|
||||||
import 'package:qr/src/mode.dart' as qr_mode;
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('QrEci', () {
|
|
||||||
test('validates value range', () {
|
|
||||||
expect(() => QrEci(-1), throwsArgumentError);
|
|
||||||
expect(() => QrEci(1000000), throwsArgumentError);
|
|
||||||
expect(QrEci(0).value, 0);
|
|
||||||
expect(QrEci(999999).value, 999999);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('constants', () {
|
|
||||||
expect(QrEciValue.iso8859_1, 3);
|
|
||||||
expect(QrEciValue.iso8859_2, 4);
|
|
||||||
expect(QrEciValue.iso8859_3, 5);
|
|
||||||
expect(QrEciValue.iso8859_4, 6);
|
|
||||||
expect(QrEciValue.iso8859_5, 7);
|
|
||||||
expect(QrEciValue.iso8859_6, 8);
|
|
||||||
expect(QrEciValue.iso8859_7, 9);
|
|
||||||
expect(QrEciValue.iso8859_8, 10);
|
|
||||||
expect(QrEciValue.iso8859_9, 11);
|
|
||||||
expect(QrEciValue.iso8859_10, 12);
|
|
||||||
expect(QrEciValue.iso8859_11, 13);
|
|
||||||
expect(QrEciValue.iso8859_13, 15);
|
|
||||||
expect(QrEciValue.iso8859_14, 16);
|
|
||||||
expect(QrEciValue.iso8859_15, 17);
|
|
||||||
expect(QrEciValue.iso8859_16, 18);
|
|
||||||
expect(QrEciValue.shiftJis, 20);
|
|
||||||
expect(QrEciValue.windows1250, 21);
|
|
||||||
expect(QrEciValue.windows1251, 22);
|
|
||||||
expect(QrEciValue.windows1252, 23);
|
|
||||||
expect(QrEciValue.windows1256, 24);
|
|
||||||
expect(QrEciValue.utf16BE, 25);
|
|
||||||
expect(QrEciValue.utf8, 26);
|
|
||||||
expect(QrEciValue.ascii, 27);
|
|
||||||
expect(QrEciValue.big5, 28);
|
|
||||||
expect(QrEciValue.gb2312, 29);
|
|
||||||
expect(QrEciValue.eucKr, 30);
|
|
||||||
expect(QrEciValue.gbk, 31);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('properties', () {
|
|
||||||
final eci = QrEci(123);
|
|
||||||
expect(eci.mode, qr_mode.QrMode.eci);
|
|
||||||
expect(eci.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('encodes 0-127 (8 bits)', () {
|
|
||||||
_testEci(0, [0x00]); // 00000000
|
|
||||||
_testEci(65, [0x41]); // 01000001
|
|
||||||
_testEci(127, [0x7F]); // 01111111
|
|
||||||
});
|
|
||||||
|
|
||||||
test('encodes 128-16383 (16 bits)', () {
|
|
||||||
// 128 -> 10 000000 10000000 -> 0x80 0x80
|
|
||||||
_testEci(128, [0x80, 0x80]);
|
|
||||||
// 16383 -> 10 111111 11111111 -> 0xBF 0xFF
|
|
||||||
_testEci(16383, [0xBF, 0xFF]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('encodes 16384-999999 (24 bits)', () {
|
|
||||||
// 16384 -> 110 00000 01000000 00000000 -> 0xC0 0x40 0x00
|
|
||||||
_testEci(16384, [0xC0, 0x40, 0x00]);
|
|
||||||
// 999999 -> 11110100001000111111 -> 0F 42 3F
|
|
||||||
// 999999 = 0xF423F
|
|
||||||
// 110 01111 01000010 00111111 -> 0xCF 0x42 0x3F
|
|
||||||
_testEci(999999, [0xCF, 0x42, 0x3F]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('validates emoji', () {
|
|
||||||
final code = QrCode(1, QrErrorCorrectLevel.low)..addData('🙃');
|
|
||||||
|
|
||||||
// Validate bitstream structure:
|
|
||||||
// Header: Mode 7 (0111) + Value 26 (00011010) + Mode 4 (0100) + Length 4 (00000100)
|
|
||||||
// 0111 0001 1010 0100 0000 0100 -> 0x71 0xA4 0x04
|
|
||||||
// Data: F0 9F 99 83
|
|
||||||
// Terminator: 0000
|
|
||||||
// Padding to byte: 0000 (since 60 bits + 4 bits = 64 bits = 8 bytes)
|
|
||||||
// Pad Bytes: 0xEC, 0x11... (to fill 19 bytes)
|
|
||||||
final expectedData = [
|
|
||||||
0x71, 0xA4, 0x04, // Header
|
|
||||||
0xF0, 0x9F, 0x99, 0x83, // '🙃' in UTF-8
|
|
||||||
0x00, // Terminator + Bit Padding to byte boundary
|
|
||||||
// Padding Codewords (0xEC, 0x11 alternating) to fill 19 bytes capacity
|
|
||||||
0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Verify the full data cache (19 Data Codewords for Version 1-L)
|
|
||||||
expect(code.dataCache.sublist(0, 19), expectedData);
|
|
||||||
|
|
||||||
final image = QrImage(code);
|
|
||||||
expect(image.moduleCount, 21); // Version 1 is 21x21
|
|
||||||
expect(_getModules(image), _expectedEmojiModules);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _testEci(int value, List<int> expectedBytes) {
|
|
||||||
final buffer = QrBitBuffer();
|
|
||||||
QrEci(value).write(buffer);
|
|
||||||
|
|
||||||
expect(buffer, hasLength(expectedBytes.length * 8));
|
|
||||||
for (var i = 0; i < expectedBytes.length; i++) {
|
|
||||||
expect(buffer.getByte(i), expectedBytes[i], reason: 'Byte $i mismatch');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<bool> _getModules(QrImage image) {
|
|
||||||
final modules = <bool>[];
|
|
||||||
for (var i = 0; i < image.moduleCount; i++) {
|
|
||||||
for (var j = 0; j < image.moduleCount; j++) {
|
|
||||||
modules.add(image.isDark(i, j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _expectedEmojiModules = [
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true, // Row 0
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true, // Row 1
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true, // Row 2
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true, // Row 3
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true, // Row 4
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true, // Row 5
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true, // Row 6
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false, // Row 7
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false, // Row 8
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false, // Row 9
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false, // Row 10
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false, // Row 11
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false, // Row 12
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false, // Row 13
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false, // Row 14
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true, // Row 15
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true, // Row 16
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false, // Row 17
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false, // Row 18
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true, // Row 19
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false, // Row 20
|
|
||||||
];
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:qr/qr.dart';
|
import 'package:qr/qr.dart';
|
||||||
|
import 'package:qr/src/byte.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
@ -6,11 +7,11 @@ void main() {
|
||||||
final qr = QrAlphaNumeric.fromString(
|
final qr = QrAlphaNumeric.fromString(
|
||||||
r'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:',
|
r'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:',
|
||||||
);
|
);
|
||||||
expect(qr.mode, QrMode.alphaNumeric);
|
expect(qr.mode, 2);
|
||||||
expect(qr.length, 45);
|
expect(qr.length, 45);
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer();
|
||||||
qr.write(buffer);
|
qr.write(buffer);
|
||||||
expect(buffer, hasLength(248));
|
expect(buffer.length, 248);
|
||||||
expect(
|
expect(
|
||||||
buffer.map<String>((e) => e ? '1' : '0').join(),
|
buffer.map<String>((e) => e ? '1' : '0').join(),
|
||||||
'00000000001'
|
'00000000001'
|
||||||
|
|
@ -41,21 +42,21 @@ void main() {
|
||||||
|
|
||||||
test('single alphanumeric', () {
|
test('single alphanumeric', () {
|
||||||
final qr = QrAlphaNumeric.fromString(r'$');
|
final qr = QrAlphaNumeric.fromString(r'$');
|
||||||
expect(qr.mode, QrMode.alphaNumeric);
|
expect(qr.mode, 2);
|
||||||
expect(qr.length, 1);
|
expect(qr.length, 1);
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer();
|
||||||
qr.write(buffer);
|
qr.write(buffer);
|
||||||
expect(buffer, hasLength(6));
|
expect(buffer.length, 6);
|
||||||
expect(buffer.map<String>((e) => e ? '1' : '0').join(), '100101');
|
expect(buffer.map<String>((e) => e ? '1' : '0').join(), '100101');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('double (even) alphanumeric', () {
|
test('double (even) alphanumeric', () {
|
||||||
final qr = QrAlphaNumeric.fromString('3Z');
|
final qr = QrAlphaNumeric.fromString('3Z');
|
||||||
expect(qr.mode, QrMode.alphaNumeric);
|
expect(qr.mode, 2);
|
||||||
expect(qr.length, 2);
|
expect(qr.length, 2);
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer();
|
||||||
qr.write(buffer);
|
qr.write(buffer);
|
||||||
expect(buffer, hasLength(11), reason: 'n*5+1 = 11');
|
expect(buffer.length, 11, reason: 'n*5+1 = 11');
|
||||||
expect(
|
expect(
|
||||||
buffer
|
buffer
|
||||||
.getRange(0, 11)
|
.getRange(0, 11)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:qr/qr.dart';
|
import 'package:qr/qr.dart';
|
||||||
|
import 'package:qr/src/byte.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
||||||
|
|
@ -11,32 +11,36 @@ import 'qr_code_test_data_with_mask.dart';
|
||||||
void main() {
|
void main() {
|
||||||
test('simple', () {
|
test('simple', () {
|
||||||
for (var typeNumber = 1; typeNumber <= 40; typeNumber++) {
|
for (var typeNumber = 1; typeNumber <= 40; typeNumber++) {
|
||||||
for (var quality in QrErrorCorrectLevel.values) {
|
for (var quality in QrErrorCorrectLevel.levels) {
|
||||||
final qr = QrImage(QrCode(typeNumber, quality)..addData('shanna!'));
|
final qr = QrImage(QrCode(typeNumber, quality)..addData('shanna!'));
|
||||||
final modules = qr.qrModules;
|
final modules = qr.qrModules;
|
||||||
|
for (var i = 0; i < modules.length; i++) {
|
||||||
expect(
|
expect(
|
||||||
modules.map(_encodeBoolListToString),
|
_encodeBoolListToString(modules[i]),
|
||||||
qrCodeTestData[typeNumber.toString()][quality.index.toString()],
|
qrCodeTestData[typeNumber.toString()][quality.toString()][i],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fromData', () {
|
test('fromData', () {
|
||||||
for (var quality in QrErrorCorrectLevel.values) {
|
for (var quality in QrErrorCorrectLevel.levels) {
|
||||||
final qr = QrImage(
|
final qr = QrImage(
|
||||||
QrCode.fromData(data: 'shanna!', errorCorrectLevel: quality),
|
QrCode.fromData(data: 'shanna!', errorCorrectLevel: quality),
|
||||||
);
|
);
|
||||||
final modules = qr.qrModules;
|
final modules = qr.qrModules;
|
||||||
|
for (var i = 0; i < modules.length; i++) {
|
||||||
expect(
|
expect(
|
||||||
modules.map(_encodeBoolListToString),
|
_encodeBoolListToString(modules[i]),
|
||||||
qrCodeTestData['1'][quality.index.toString()],
|
qrCodeTestData['1'][quality.toString()][i],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fromUint8List', () {
|
test('fromUint8List', () {
|
||||||
for (var quality in QrErrorCorrectLevel.values) {
|
for (var quality in QrErrorCorrectLevel.levels) {
|
||||||
final qr = QrImage(
|
final qr = QrImage(
|
||||||
QrCode.fromUint8List(
|
QrCode.fromUint8List(
|
||||||
data: Uint8List.fromList([115, 104, 97, 110, 110, 97, 33]),
|
data: Uint8List.fromList([115, 104, 97, 110, 110, 97, 33]),
|
||||||
|
|
@ -44,53 +48,67 @@ void main() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final modules = qr.qrModules;
|
final modules = qr.qrModules;
|
||||||
|
for (var i = 0; i < modules.length; i++) {
|
||||||
expect(
|
expect(
|
||||||
modules.map(_encodeBoolListToString),
|
_encodeBoolListToString(modules[i]),
|
||||||
qrCodeTestData['1'][quality.index.toString()],
|
qrCodeTestData['1'][quality.toString()][i],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('WHEN mask pattern is provided, SHOULD make a masked QR Code', () {
|
test('WHEN mask pattern is provided, SHOULD make a masked QR Code', () {
|
||||||
for (var mask = 0; mask <= 7; mask++) {
|
for (var mask = 0; mask <= 7; mask++) {
|
||||||
final qr = QrImage.withMaskPattern(
|
final qr = QrImage.withMaskPattern(
|
||||||
QrCode(1, QrErrorCorrectLevel.low)..addData('shanna!'),
|
QrCode(1, QrErrorCorrectLevel.L)..addData('shanna!'),
|
||||||
mask,
|
mask,
|
||||||
);
|
);
|
||||||
final modules = qr.qrModules;
|
final modules = qr.qrModules;
|
||||||
|
for (var i = 0; i < modules.length; i++) {
|
||||||
expect(
|
expect(
|
||||||
modules.map(_encodeBoolListToString),
|
_encodeBoolListToString(modules[i]),
|
||||||
qrCodeTestDataWithMask[mask.toString()],
|
qrCodeTestDataWithMask[mask.toString()][i],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('WHEN provided mask pattern is smaller than 0, '
|
test(
|
||||||
'SHOULD throw an AssertionError', () {
|
'''
|
||||||
|
WHEN provided mask pattern is smaller than 0,
|
||||||
|
SHOULD throw an AssertionError
|
||||||
|
''',
|
||||||
|
() {
|
||||||
expect(() {
|
expect(() {
|
||||||
QrImage.withMaskPattern(
|
QrImage.withMaskPattern(
|
||||||
QrCode(1, QrErrorCorrectLevel.low)..addData('shanna!'),
|
QrCode(1, QrErrorCorrectLevel.L)..addData('shanna!'),
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}, throwsA(isA<AssertionError>()));
|
}, throwsA(isA<AssertionError>()));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('WHEN provided mask pattern is bigger than 7, '
|
test(
|
||||||
'SHOULD throw an AssertionError', () {
|
'''
|
||||||
|
WHEN provided mask pattern is bigger than 7,
|
||||||
|
SHOULD throw an AssertionError
|
||||||
|
''',
|
||||||
|
() {
|
||||||
expect(() {
|
expect(() {
|
||||||
QrImage.withMaskPattern(
|
QrImage.withMaskPattern(
|
||||||
QrCode(1, QrErrorCorrectLevel.high)..addData('shanna!'),
|
QrCode(1, QrErrorCorrectLevel.L)..addData('shanna!'),
|
||||||
8,
|
8,
|
||||||
);
|
);
|
||||||
}, throwsA(isA<AssertionError>()));
|
}, throwsA(isA<AssertionError>()));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
group('QrCode.fromData Automatic Mode Detection', () {
|
group('QrCode.fromData Automatic Mode Detection', () {
|
||||||
// Numeric Mode
|
// Numeric Mode
|
||||||
test('should use Numeric Mode for numbers', () {
|
test('should use Numeric Mode for numbers', () {
|
||||||
// 9 numeric chars fit version 1 (H level).
|
// 9 numeric chars fit version 1 (H level).
|
||||||
final qr = QrCode.fromData(
|
final qr = QrCode.fromData(
|
||||||
data: '123456789',
|
data: '123456789',
|
||||||
errorCorrectLevel: QrErrorCorrectLevel.high,
|
errorCorrectLevel: QrErrorCorrectLevel.H,
|
||||||
);
|
);
|
||||||
expect(qr.typeNumber, 1);
|
expect(qr.typeNumber, 1);
|
||||||
});
|
});
|
||||||
|
|
@ -101,7 +119,7 @@ void main() {
|
||||||
// version 2 (H level, 16 chars).
|
// version 2 (H level, 16 chars).
|
||||||
final qr = QrCode.fromData(
|
final qr = QrCode.fromData(
|
||||||
data: 'HELLO WORLD A',
|
data: 'HELLO WORLD A',
|
||||||
errorCorrectLevel: QrErrorCorrectLevel.high,
|
errorCorrectLevel: QrErrorCorrectLevel.H,
|
||||||
);
|
);
|
||||||
expect(qr.typeNumber, 2);
|
expect(qr.typeNumber, 2);
|
||||||
});
|
});
|
||||||
|
|
@ -112,7 +130,7 @@ void main() {
|
||||||
// '機械学習' (12 bytes) fits version 2 (H level, 16 bytes).
|
// '機械学習' (12 bytes) fits version 2 (H level, 16 bytes).
|
||||||
final qr = QrCode.fromData(
|
final qr = QrCode.fromData(
|
||||||
data: '機械学習',
|
data: '機械学習',
|
||||||
errorCorrectLevel: QrErrorCorrectLevel.high,
|
errorCorrectLevel: QrErrorCorrectLevel.H,
|
||||||
);
|
);
|
||||||
expect(qr.typeNumber, 2);
|
expect(qr.typeNumber, 2);
|
||||||
});
|
});
|
||||||
|
|
@ -122,7 +140,7 @@ void main() {
|
||||||
// Numeric Mode
|
// Numeric Mode
|
||||||
test('should use Numeric Mode for numbers', () {
|
test('should use Numeric Mode for numbers', () {
|
||||||
// 9 numeric characters fit version 1 (H level).
|
// 9 numeric characters fit version 1 (H level).
|
||||||
final qr = QrCode(1, QrErrorCorrectLevel.low)..addData('123456789');
|
final qr = QrCode(1, QrErrorCorrectLevel.H)..addData('123456789');
|
||||||
expect(qr.typeNumber, 1);
|
expect(qr.typeNumber, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -130,7 +148,7 @@ void main() {
|
||||||
test('should use Alphanumeric Mode', () {
|
test('should use Alphanumeric Mode', () {
|
||||||
// 13 alphanumeric characters exceed version 1 (7 chars) but fit
|
// 13 alphanumeric characters exceed version 1 (7 chars) but fit
|
||||||
// version 2 (H level, 16 chars).
|
// version 2 (H level, 16 chars).
|
||||||
final qr = QrCode(2, QrErrorCorrectLevel.high)..addData('HELLO WORLD A');
|
final qr = QrCode(2, QrErrorCorrectLevel.H)..addData('HELLO WORLD A');
|
||||||
expect(qr.typeNumber, 2);
|
expect(qr.typeNumber, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -138,7 +156,7 @@ void main() {
|
||||||
test('should use Byte Mode for non-alphanumeric characters', () {
|
test('should use Byte Mode for non-alphanumeric characters', () {
|
||||||
// Kanji characters are UTF-8 encoded.
|
// Kanji characters are UTF-8 encoded.
|
||||||
// '機械学習' (12 bytes) fits version 2 (H level, 16 bytes).
|
// '機械学習' (12 bytes) fits version 2 (H level, 16 bytes).
|
||||||
final qr = QrCode(2, QrErrorCorrectLevel.high)..addData('機械学習');
|
final qr = QrCode(2, QrErrorCorrectLevel.H)..addData('機械学習');
|
||||||
expect(qr.typeNumber, 2);
|
expect(qr.typeNumber, 2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -150,7 +168,7 @@ void main() {
|
||||||
|
|
||||||
final qrCode = QrCode.fromData(
|
final qrCode = QrCode.fromData(
|
||||||
data: largeData,
|
data: largeData,
|
||||||
errorCorrectLevel: QrErrorCorrectLevel.low,
|
errorCorrectLevel: QrErrorCorrectLevel.L,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(qrCode.typeNumber, 40);
|
expect(qrCode.typeNumber, 40);
|
||||||
|
|
@ -164,20 +182,12 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
() => QrCode.fromData(
|
() => QrCode.fromData(
|
||||||
data: excessivelyLargeData,
|
data: excessivelyLargeData,
|
||||||
errorCorrectLevel: QrErrorCorrectLevel.low,
|
errorCorrectLevel: QrErrorCorrectLevel.L,
|
||||||
),
|
),
|
||||||
throwsA(isA<InputTooLongException>()),
|
throwsA(isA<InputTooLongException>()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('QrCode.addData size checks', () {
|
|
||||||
test('should throw if data exceeds capacity for fixed version', () {
|
|
||||||
final code = QrCode(1, QrErrorCorrectLevel.low)..addData('|' * 30);
|
|
||||||
|
|
||||||
expect(() => code.dataCache, throwsA(isA<InputTooLongException>()));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _encodeBoolListToString(List<bool?> source) =>
|
String _encodeBoolListToString(List<bool?> source) =>
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import 'package:qr/src/byte.dart';
|
|
||||||
import 'package:qr/src/eci.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('QrDatum.toDatums', () {
|
|
||||||
test('Numeric', () {
|
|
||||||
final datums = QrDatum.toDatums('123456');
|
|
||||||
expect(datums, hasLength(1));
|
|
||||||
expect(datums.first, isA<QrNumeric>());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('AlphaNumeric', () {
|
|
||||||
final datums = QrDatum.toDatums('HELLO WORLD');
|
|
||||||
expect(datums, hasLength(1));
|
|
||||||
expect(datums.first, isA<QrAlphaNumeric>());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Byte (Latin-1)', () {
|
|
||||||
final datums = QrDatum.toDatums('Hello World!');
|
|
||||||
expect(datums, hasLength(1));
|
|
||||||
expect(datums.first, isA<QrByte>());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Byte (UTF-8 with ECI)', () {
|
|
||||||
final datums = QrDatum.toDatums('Hello 🌍');
|
|
||||||
expect(datums, hasLength(2));
|
|
||||||
expect(datums[0], isA<QrEci>());
|
|
||||||
expect((datums[0] as QrEci).value, 26);
|
|
||||||
expect(datums[1], isA<QrByte>());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Complex Emoji (UTF-8 with ECI)', () {
|
|
||||||
// Woman + Medium Skin Tone + ZWJ + Heart + VS16 + ZWJ + Kiss Mark + ZWJ
|
|
||||||
// + Man + Dark Brown Skin Tone
|
|
||||||
const complexEmoji =
|
|
||||||
'\u{1F469}\u{1F3FD}\u{200D}\u{2764}\u{FE0F}\u{200D}'
|
|
||||||
'\u{1F48B}\u{200D}\u{1F468}\u{1F3FE}';
|
|
||||||
final datums = QrDatum.toDatums(complexEmoji);
|
|
||||||
expect(datums, hasLength(2));
|
|
||||||
expect(datums[0], isA<QrEci>());
|
|
||||||
expect((datums[0] as QrEci).value, 26);
|
|
||||||
expect(datums[1], isA<QrByte>());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import 'package:qr/qr.dart';
|
import 'package:qr/qr.dart';
|
||||||
|
import 'package:qr/src/byte.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('all digits 1 through 0', () {
|
test('all digits 1 through 0', () {
|
||||||
final qr = QrNumeric.fromString('1234567890');
|
final qr = QrNumeric.fromString('1234567890');
|
||||||
expect(qr.mode, QrMode.numeric);
|
expect(qr.mode, 1);
|
||||||
expect(qr.length, 10);
|
expect(qr.length, 10);
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer();
|
||||||
qr.write(buffer);
|
qr.write(buffer);
|
||||||
expect(buffer, hasLength(34));
|
expect(buffer.length, 34);
|
||||||
expect(
|
expect(
|
||||||
buffer
|
buffer
|
||||||
.getRange(0, 10)
|
.getRange(0, 10)
|
||||||
|
|
@ -53,11 +54,11 @@ void main() {
|
||||||
|
|
||||||
test('single numeric', () {
|
test('single numeric', () {
|
||||||
final qr = QrNumeric.fromString('5');
|
final qr = QrNumeric.fromString('5');
|
||||||
expect(qr.mode, QrMode.numeric);
|
expect(qr.mode, 1);
|
||||||
expect(qr.length, 1);
|
expect(qr.length, 1);
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer();
|
||||||
qr.write(buffer);
|
qr.write(buffer);
|
||||||
expect(buffer, hasLength(4));
|
expect(buffer.length, 4);
|
||||||
expect(
|
expect(
|
||||||
buffer
|
buffer
|
||||||
.getRange(0, 4)
|
.getRange(0, 4)
|
||||||
|
|
@ -72,11 +73,11 @@ void main() {
|
||||||
|
|
||||||
test('double numeric', () {
|
test('double numeric', () {
|
||||||
final qr = QrNumeric.fromString('37');
|
final qr = QrNumeric.fromString('37');
|
||||||
expect(qr.mode, QrMode.numeric);
|
expect(qr.mode, 1);
|
||||||
expect(qr.length, 2);
|
expect(qr.length, 2);
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer();
|
||||||
qr.write(buffer);
|
qr.write(buffer);
|
||||||
expect(buffer, hasLength(7), reason: 'n*3+1 = 7');
|
expect(buffer.length, 7, reason: 'n*3+1 = 7');
|
||||||
expect(
|
expect(
|
||||||
buffer
|
buffer
|
||||||
.getRange(0, 7)
|
.getRange(0, 7)
|
||||||
|
|
@ -91,11 +92,11 @@ void main() {
|
||||||
|
|
||||||
test('triple (even) numeric', () {
|
test('triple (even) numeric', () {
|
||||||
final qr = QrNumeric.fromString('371');
|
final qr = QrNumeric.fromString('371');
|
||||||
expect(qr.mode, QrMode.numeric);
|
expect(qr.mode, 1);
|
||||||
expect(qr.length, 3);
|
expect(qr.length, 3);
|
||||||
final buffer = QrBitBuffer();
|
final buffer = QrBitBuffer();
|
||||||
qr.write(buffer);
|
qr.write(buffer);
|
||||||
expect(buffer, hasLength(10), reason: 'n*3+1 = 10');
|
expect(buffer.length, 10, reason: 'n*3+1 = 10');
|
||||||
expect(
|
expect(
|
||||||
buffer
|
buffer
|
||||||
.getRange(0, 10)
|
.getRange(0, 10)
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import 'package:qr/qr.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('Generate QR with Emoji', () {
|
|
||||||
const emojiString = '👩🏽❤️💋👨🏾';
|
|
||||||
final qr = QrCode.fromData(
|
|
||||||
data: emojiString,
|
|
||||||
errorCorrectLevel: QrErrorCorrectLevel.low,
|
|
||||||
);
|
|
||||||
expect(qr.typeNumber, 2);
|
|
||||||
expect(qr.typeNumber, greaterThan(0));
|
|
||||||
// Verify we have multiple segments (ECI + Byte)
|
|
||||||
// iterate over modules or check internal structure if possible
|
|
||||||
// (but it's private)
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Generate QR with Complex Emoji (ZWJ support)', () {
|
|
||||||
// Woman + Medium Skin Tone + ZWJ + Heart + VS16 + ZWJ + Kiss Mark + ZWJ
|
|
||||||
// + Man + Dark Brown Skin Tone
|
|
||||||
const complexEmoji =
|
|
||||||
'\u{1F469}\u{1F3FD}\u{200D}\u{2764}\u{FE0F}\u{200D}'
|
|
||||||
'\u{1F48B}\u{200D}\u{1F468}\u{1F3FE}';
|
|
||||||
|
|
||||||
final qr = QrCode.fromData(
|
|
||||||
data: complexEmoji,
|
|
||||||
errorCorrectLevel: QrErrorCorrectLevel.low,
|
|
||||||
);
|
|
||||||
expect(qr.typeNumber, greaterThan(0));
|
|
||||||
// Verify it didn't throw and created a valid QR structure
|
|
||||||
// The exact type number depends on the overhead of ECI + Byte mode
|
|
||||||
|
|
||||||
// 4 segments:
|
|
||||||
// 1. ECI (26 for UTF-8)
|
|
||||||
// 2. Byte Data (the emoji bytes)
|
|
||||||
|
|
||||||
// We can't easily peek into _dataList, but we can verify the module count
|
|
||||||
// implies it's not empty
|
|
||||||
expect(qr.moduleCount, greaterThan(21));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
@Tags(['require-zbar'])
|
|
||||||
library;
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import 'package:test_process/test_process.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
late Directory tempDir;
|
|
||||||
|
|
||||||
setUpAll(() {
|
|
||||||
tempDir = Directory.systemTemp.createTempSync('qr_tool_test');
|
|
||||||
});
|
|
||||||
|
|
||||||
tearDownAll(() {
|
|
||||||
tempDir.deleteSync(recursive: true);
|
|
||||||
});
|
|
||||||
|
|
||||||
final configurations = [
|
|
||||||
(version: null, correction: null),
|
|
||||||
(version: 40, correction: 'H'),
|
|
||||||
];
|
|
||||||
|
|
||||||
final inputs = [
|
|
||||||
'123456',
|
|
||||||
'HELLO WORLD',
|
|
||||||
'Hello 👋 World 🌍',
|
|
||||||
'👩🏽❤️💋👨🏾',
|
|
||||||
'👩🏽❤️💋👨🏾',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (final config in configurations) {
|
|
||||||
for (final input in inputs) {
|
|
||||||
test(
|
|
||||||
'Generate QR with config $config and input "$input"',
|
|
||||||
() async {
|
|
||||||
final bmpPath = p.join(
|
|
||||||
tempDir.path,
|
|
||||||
'test_${config.hashCode}_${input.hashCode}.bmp',
|
|
||||||
);
|
|
||||||
final args = [
|
|
||||||
'tool/write_qr.dart',
|
|
||||||
'-o',
|
|
||||||
bmpPath,
|
|
||||||
if (config.version != null) ...['-v', config.version.toString()],
|
|
||||||
if (config.correction != null) ...['-c', config.correction!],
|
|
||||||
'--scale',
|
|
||||||
'10',
|
|
||||||
input,
|
|
||||||
];
|
|
||||||
|
|
||||||
final process = await TestProcess.start('dart', args);
|
|
||||||
await process.shouldExit(0);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
File(bmpPath).existsSync(),
|
|
||||||
isTrue,
|
|
||||||
reason: 'BMP file should be created',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Validate with zbarimg
|
|
||||||
// zbarimg output format: QR-Code:content
|
|
||||||
final zbar = await TestProcess.start('zbarimg', ['--quiet', bmpPath]);
|
|
||||||
await zbar.shouldExit(0);
|
|
||||||
final output = (await zbar.stdout.rest.toList()).join('\n').trim();
|
|
||||||
|
|
||||||
if (output != 'QR-Code:$input') {
|
|
||||||
print('zbarimg failed to match input.');
|
|
||||||
print('Input: $input');
|
|
||||||
print('Output: "$output"');
|
|
||||||
}
|
|
||||||
expect(output, 'QR-Code:$input');
|
|
||||||
},
|
|
||||||
timeout: const Timeout(Duration(seconds: 20)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test('Generate QR with Version 1 (numeric input)', () async {
|
|
||||||
const input = '123456';
|
|
||||||
final bmpPath = p.join(tempDir.path, 'test_v1_numeric.bmp');
|
|
||||||
final args = [
|
|
||||||
'tool/write_qr.dart',
|
|
||||||
'-o',
|
|
||||||
bmpPath,
|
|
||||||
'-v',
|
|
||||||
'1',
|
|
||||||
'-c',
|
|
||||||
'L',
|
|
||||||
'--scale',
|
|
||||||
'10',
|
|
||||||
input,
|
|
||||||
];
|
|
||||||
|
|
||||||
final process = await TestProcess.start('dart', args);
|
|
||||||
await process.shouldExit(0);
|
|
||||||
expect(
|
|
||||||
File(bmpPath).existsSync(),
|
|
||||||
isTrue,
|
|
||||||
reason: 'BMP file should be created',
|
|
||||||
);
|
|
||||||
|
|
||||||
final zbar = await TestProcess.start('zbarimg', ['--quiet', bmpPath]);
|
|
||||||
await zbar.shouldExit(0);
|
|
||||||
final output = (await zbar.stdout.rest.toList()).join('\n').trim();
|
|
||||||
|
|
||||||
if (output != 'QR-Code:$input') {
|
|
||||||
print('zbarimg failed to match input.');
|
|
||||||
print('Input: $input');
|
|
||||||
print('Output: "$output"');
|
|
||||||
}
|
|
||||||
expect(output, 'QR-Code:$input');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Error case: Missing output argument', () async {
|
|
||||||
final process = await TestProcess.start('dart', [
|
|
||||||
'tool/write_qr.dart',
|
|
||||||
'content',
|
|
||||||
]);
|
|
||||||
await process.shouldExit(1);
|
|
||||||
final output = await process.stdout.next;
|
|
||||||
expect(
|
|
||||||
output,
|
|
||||||
contains('Error: Invalid argument(s): Option output is mandatory.'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Error case: Invalid version', () async {
|
|
||||||
final bmpPath = p.join(tempDir.path, 'invalid_version.bmp');
|
|
||||||
final process = await TestProcess.start('dart', [
|
|
||||||
'tool/write_qr.dart',
|
|
||||||
'-o',
|
|
||||||
bmpPath,
|
|
||||||
'-v',
|
|
||||||
'41',
|
|
||||||
'content',
|
|
||||||
]);
|
|
||||||
await process.shouldExit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Error case: Invalid correction', () async {
|
|
||||||
final bmpPath = p.join(tempDir.path, 'invalid_correction.bmp');
|
|
||||||
final process = await TestProcess.start('dart', [
|
|
||||||
'tool/write_qr.dart',
|
|
||||||
'-o',
|
|
||||||
bmpPath,
|
|
||||||
'-c',
|
|
||||||
'X',
|
|
||||||
'content',
|
|
||||||
]);
|
|
||||||
await process.shouldExit(1); // ArgParser error
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Error case: Input too long for version (explicit version)', () async {
|
|
||||||
const input =
|
|
||||||
'This string is definitely too long for Version 1 with '
|
|
||||||
'High error correction level.';
|
|
||||||
final bmpPath = p.join(tempDir.path, 'too_long.bmp');
|
|
||||||
final process = await TestProcess.start('dart', [
|
|
||||||
'tool/write_qr.dart',
|
|
||||||
'-o',
|
|
||||||
bmpPath,
|
|
||||||
'-v',
|
|
||||||
'1',
|
|
||||||
'-c',
|
|
||||||
'H', // High error correction reduces capacity
|
|
||||||
input,
|
|
||||||
]);
|
|
||||||
await process.shouldExit(1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue