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
|
||||
photo_view: a13ca2fc387a3fb1276126959e092c44d0029987
|
||||
pointycastle: bbd8569f68a7fccbdf0b92d0b44a9219c126c8dd
|
||||
qr: 5fa01fcccd6121b906dc7df4fffa9fa22ca94f75
|
||||
qr: 7b1e9665ca976f484e7975356cf26fc7a0ccf02e
|
||||
qr_flutter: d5e7206396105d643113618290bbcc755d05f492
|
||||
restart_app: 12339f63bf8e9631e619c4f9f6b4e013fa324715
|
||||
x25519: ecb1d357714537bba6e276ef45f093846d4beaee
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
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/input_too_long_exception.dart';
|
||||
export 'src/mode.dart';
|
||||
export 'src/qr_code.dart';
|
||||
export 'src/qr_image.dart';
|
||||
|
|
|
|||
|
|
@ -1,78 +1,34 @@
|
|||
/// A growable sequence of bits.
|
||||
///
|
||||
/// Used internally to construct the data bit stream for a QR code.
|
||||
class QrBitBuffer extends Iterable<bool> {
|
||||
final _buffer = <int>[];
|
||||
import 'dart:collection';
|
||||
|
||||
class QrBitBuffer extends Object with ListMixin<bool> {
|
||||
final List<int> _buffer;
|
||||
int _length = 0;
|
||||
|
||||
QrBitBuffer();
|
||||
QrBitBuffer() : _buffer = <int>[];
|
||||
|
||||
@override
|
||||
int get length => _length;
|
||||
void operator []=(int index, bool value) =>
|
||||
throw UnsupportedError('cannot change');
|
||||
|
||||
@override
|
||||
Iterator<bool> get iterator => _QrBitBufferIterator(this);
|
||||
|
||||
bool operator [](int index) {
|
||||
final bufIndex = index ~/ 8;
|
||||
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];
|
||||
|
||||
void put(int number, int length) {
|
||||
if (length == 0) return;
|
||||
|
||||
var bitIndex = _length;
|
||||
final endBitIndex = bitIndex + length;
|
||||
|
||||
// Ensure capacity
|
||||
final neededBytes = (endBitIndex + 7) >> 3; // (endBitIndex + 7) ~/ 8
|
||||
while (_buffer.length < neededBytes) {
|
||||
_buffer.add(0);
|
||||
for (var i = 0; i < length; i++) {
|
||||
final bit = ((number >> (length - i - 1)) & 1) == 1;
|
||||
putBit(bit);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
@ -87,28 +43,4 @@ class QrBitBuffer extends Iterable<bool> {
|
|||
|
||||
_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 'bit_buffer.dart';
|
||||
import 'eci.dart';
|
||||
import 'mode.dart';
|
||||
import 'mode.dart' as qr_mode;
|
||||
|
||||
/// A piece of data to be encoded in a QR code.
|
||||
///
|
||||
/// Use [toDatums] to parse a string into optimal segments.
|
||||
abstract class QrDatum {
|
||||
QrMode get mode;
|
||||
int get mode;
|
||||
int get length;
|
||||
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 {
|
||||
@override
|
||||
final QrMode mode = QrMode.byte;
|
||||
final int mode = qr_mode.mode8bitByte;
|
||||
final Uint8List _data;
|
||||
|
||||
factory QrByte(String input) =>
|
||||
|
|
@ -64,20 +34,13 @@ class QrByte implements QrDatum {
|
|||
}
|
||||
}
|
||||
|
||||
/// Encodes numeric data (digits 0-9).
|
||||
///
|
||||
/// Compresses 3 digits into 10 bits.
|
||||
/// Most efficient mode for decimal numbers.
|
||||
/// Encodes numbers (0-9) 10 bits per 3 digits.
|
||||
class QrNumeric implements QrDatum {
|
||||
static final RegExp validationRegex = RegExp(r'^[0-9]+$');
|
||||
|
||||
factory QrNumeric.fromString(String numberString) {
|
||||
if (!validationRegex.hasMatch(numberString)) {
|
||||
throw ArgumentError.value(
|
||||
numberString,
|
||||
'numberString',
|
||||
'string can only contain digits 0-9',
|
||||
);
|
||||
throw ArgumentError('string can only contain digits 0-9');
|
||||
}
|
||||
final newList = Uint8List(numberString.length);
|
||||
var count = 0;
|
||||
|
|
@ -92,7 +55,7 @@ class QrNumeric implements QrDatum {
|
|||
final Uint8List _data;
|
||||
|
||||
@override
|
||||
final QrMode mode = QrMode.numeric;
|
||||
final int mode = qr_mode.modeNumber;
|
||||
|
||||
@override
|
||||
void write(QrBitBuffer buffer) {
|
||||
|
|
@ -119,10 +82,7 @@ class QrNumeric implements QrDatum {
|
|||
int get length => _data.length;
|
||||
}
|
||||
|
||||
/// Encodes alphanumeric data (uppercase letters, digits, and specific symbols).
|
||||
///
|
||||
/// Supported characters: 0-9, A-Z, space, $, %, *, +, -, ., /, :
|
||||
/// Compresses 2 characters into 11 bits.
|
||||
/// Encodes numbers (0-9) 10 bits per 3 digits.
|
||||
class QrAlphaNumeric implements QrDatum {
|
||||
static const alphaNumTable = r'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
||||
// Note: '-' anywhere in this string is a range character.
|
||||
|
|
@ -142,10 +102,9 @@ class QrAlphaNumeric implements QrDatum {
|
|||
|
||||
factory QrAlphaNumeric.fromString(String alphaNumeric) {
|
||||
if (!alphaNumeric.contains(validationRegex)) {
|
||||
throw ArgumentError.value(
|
||||
alphaNumeric,
|
||||
'alphaNumeric',
|
||||
'String does not contain valid ALPHA-NUM character set',
|
||||
throw ArgumentError(
|
||||
'String does not contain valid ALPHA-NUM '
|
||||
'character set: $alphaNumeric',
|
||||
);
|
||||
}
|
||||
return QrAlphaNumeric._(alphaNumeric);
|
||||
|
|
@ -154,7 +113,7 @@ class QrAlphaNumeric implements QrDatum {
|
|||
QrAlphaNumeric._(this._string);
|
||||
|
||||
@override
|
||||
final QrMode mode = QrMode.alphaNumeric;
|
||||
final int mode = qr_mode.modeAlphaNum;
|
||||
|
||||
@override
|
||||
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.
|
||||
///
|
||||
/// Recover capacity:
|
||||
/// * [low] : ~7%
|
||||
/// * [medium] : ~15%
|
||||
/// * [quartile] : ~25%
|
||||
/// * [high] : ~30%
|
||||
enum QrErrorCorrectLevel {
|
||||
// NOTE: the order here MATTERS.
|
||||
// The index maps to the QR standard.
|
||||
// ignore: avoid_classes_with_only_static_members
|
||||
class QrErrorCorrectLevel {
|
||||
static const int L = 1;
|
||||
static const int M = 0;
|
||||
static const int Q = 3;
|
||||
static const int H = 2;
|
||||
|
||||
/// Level M (Medium) ~15% error correction.
|
||||
medium(15),
|
||||
// thesee *are* in order of lowest to highest quality...I think
|
||||
// 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.
|
||||
low(7),
|
||||
|
||||
/// Level H (High) ~30% error correction.
|
||||
high(30),
|
||||
|
||||
/// Level Q (Quartile) ~25% error correction.
|
||||
quartile(25);
|
||||
|
||||
final int recoveryRate;
|
||||
|
||||
const QrErrorCorrectLevel(this.recoveryRate);
|
||||
static String getName(int level) => switch (level) {
|
||||
L => 'Low',
|
||||
M => 'Medium',
|
||||
Q => 'Quartile',
|
||||
H => 'High',
|
||||
_ => throw ArgumentError('level $level not supported'),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,8 @@
|
|||
enum QrMaskPattern {
|
||||
pattern000(_check000),
|
||||
pattern001(_check001),
|
||||
pattern010(_check010),
|
||||
pattern011(_check011),
|
||||
pattern100(_check100),
|
||||
pattern101(_check101),
|
||||
pattern110(_check110),
|
||||
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;
|
||||
const int pattern000 = 0;
|
||||
const int pattern001 = 1;
|
||||
const int pattern010 = 2;
|
||||
const int pattern011 = 3;
|
||||
const int pattern100 = 4;
|
||||
const int pattern101 = 5;
|
||||
const int pattern110 = 6;
|
||||
const int pattern111 = 7;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ import 'dart:typed_data';
|
|||
final Uint8List _logTable = _createLogTable();
|
||||
final Uint8List _expTable = _createExpTable();
|
||||
|
||||
int glog(int n) =>
|
||||
(n >= 1) ? _logTable[n] : throw ArgumentError.value(n, 'n', 'must be >= 1');
|
||||
int glog(int n) => (n >= 1) ? _logTable[n] : throw ArgumentError('glog($n)');
|
||||
|
||||
int gexp(int n) => _expTable[n % 255];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,55 +1,4 @@
|
|||
/// The encoding mode of a QR code segment.
|
||||
enum QrMode {
|
||||
/// Numeric mode (0-9). Most efficient.
|
||||
numeric(1),
|
||||
|
||||
/// 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
const int modeNumber = 1 << 0;
|
||||
const int modeAlphaNum = 1 << 1;
|
||||
const int mode8bitByte = 1 << 2;
|
||||
const int modeKanji = 1 << 3;
|
||||
|
|
|
|||
|
|
@ -28,75 +28,36 @@ class QrPolynomial {
|
|||
int get length => _values.length;
|
||||
|
||||
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++) {
|
||||
final v1 = _values[i];
|
||||
if (v1 == 0) continue;
|
||||
final log1 = qr_math.glog(v1);
|
||||
for (var j = 0; j < e.length; j++) {
|
||||
final v2 = e[j];
|
||||
if (v2 == 0) continue;
|
||||
foo[i + j] ^= qr_math.gexp(log1 + qr_math.glog(v2));
|
||||
foo[i + j] ^= qr_math.gexp(qr_math.glog(this[i]) + qr_math.glog(e[j]));
|
||||
}
|
||||
}
|
||||
|
||||
return QrPolynomial._internal(foo);
|
||||
return QrPolynomial(foo, 0);
|
||||
}
|
||||
|
||||
QrPolynomial mod(QrPolynomial e) {
|
||||
if (length - e.length < 0) {
|
||||
// ignore: avoid_returning_this
|
||||
return this;
|
||||
}
|
||||
|
||||
// Use a copy of _values that we will mutate
|
||||
// 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 ratio = qr_math.glog(this[0]) - qr_math.glog(e[0]);
|
||||
|
||||
final values = Uint8List.fromList(_values);
|
||||
final value = Uint8List(length);
|
||||
|
||||
for (var i = 0; i < values.length - e.length + 1; i++) {
|
||||
final v = values[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);
|
||||
}
|
||||
for (var i = 0; i < length; i++) {
|
||||
value[i] = this[i];
|
||||
}
|
||||
|
||||
// The result is the remainder, which is the last e.length - 1 coefficients?
|
||||
// Wait, the degree of remainder is less than degree of divisor (e).
|
||||
// e.length is e.degree + 1.
|
||||
// So remainder length is e.length - 1.
|
||||
for (var i = 0; i < e.length; i++) {
|
||||
value[i] ^= qr_math.gexp(qr_math.glog(e[i]) + ratio);
|
||||
}
|
||||
|
||||
// Find where the remainder starts.
|
||||
// In the loop above, we zeroed out terms from 0 to
|
||||
// `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);
|
||||
// recursive call
|
||||
return QrPolynomial(value, 0).mod(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,61 +5,66 @@ import 'package:meta/meta.dart';
|
|||
|
||||
import 'bit_buffer.dart';
|
||||
import 'byte.dart';
|
||||
import 'eci.dart';
|
||||
import 'error_correct_level.dart';
|
||||
import 'input_too_long_exception.dart';
|
||||
import 'math.dart' as qr_math;
|
||||
|
||||
import 'mode.dart' as qr_mode;
|
||||
import 'polynomial.dart';
|
||||
import 'rs_block.dart';
|
||||
|
||||
class QrCode {
|
||||
final int typeNumber;
|
||||
final QrErrorCorrectLevel errorCorrectLevel;
|
||||
final int errorCorrectLevel;
|
||||
final int moduleCount;
|
||||
List<int>? _dataCache;
|
||||
final _dataList = <QrDatum>[];
|
||||
|
||||
QrCode(this.typeNumber, this.errorCorrectLevel)
|
||||
: 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.checkValidIndex(
|
||||
errorCorrectLevel,
|
||||
QrErrorCorrectLevel.levels,
|
||||
'errorCorrectLevel',
|
||||
);
|
||||
}
|
||||
|
||||
factory QrCode.fromData({
|
||||
required String data,
|
||||
required QrErrorCorrectLevel errorCorrectLevel,
|
||||
required int errorCorrectLevel,
|
||||
}) {
|
||||
final datumList = QrDatum.toDatums(data);
|
||||
|
||||
final typeNumber = _calculateTypeNumberFromData(
|
||||
errorCorrectLevel,
|
||||
datumList,
|
||||
);
|
||||
|
||||
final qrCode = QrCode(typeNumber, errorCorrectLevel);
|
||||
for (final datum in datumList) {
|
||||
qrCode._addToList(datum);
|
||||
final QrDatum datum;
|
||||
// Automatically determine mode here
|
||||
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);
|
||||
}
|
||||
|
||||
final typeNumber = _calculateTypeNumberFromData(errorCorrectLevel, datum);
|
||||
|
||||
final qrCode = QrCode(typeNumber, errorCorrectLevel).._addToList(datum);
|
||||
return qrCode;
|
||||
}
|
||||
|
||||
factory QrCode.fromUint8List({
|
||||
required Uint8List data,
|
||||
required QrErrorCorrectLevel errorCorrectLevel,
|
||||
required int errorCorrectLevel,
|
||||
}) {
|
||||
final datum = QrByte.fromUint8List(data);
|
||||
final typeNumber = _calculateTypeNumberFromData(errorCorrectLevel, [datum]);
|
||||
return QrCode(typeNumber, errorCorrectLevel).._addToList(datum);
|
||||
final typeNumber = _calculateTypeNumberFromData(
|
||||
errorCorrectLevel,
|
||||
QrByte.fromUint8List(data),
|
||||
);
|
||||
return QrCode(typeNumber, errorCorrectLevel)
|
||||
.._addToList(QrByte.fromUint8List(data));
|
||||
}
|
||||
|
||||
static int _calculateTotalDataBits(
|
||||
int typeNumber,
|
||||
QrErrorCorrectLevel errorCorrectLevel,
|
||||
) {
|
||||
static int _calculateTotalDataBits(int typeNumber, int errorCorrectLevel) {
|
||||
final rsBlocks = QrRsBlock.getRSBlocks(typeNumber, errorCorrectLevel);
|
||||
var totalDataBits = 0;
|
||||
for (var rsBlock in rsBlocks) {
|
||||
|
|
@ -68,35 +73,26 @@ class QrCode {
|
|||
return totalDataBits;
|
||||
}
|
||||
|
||||
static int _calculateTypeNumberFromData(
|
||||
QrErrorCorrectLevel errorCorrectLevel,
|
||||
List<QrDatum> data,
|
||||
) {
|
||||
static int _calculateTypeNumberFromData(int errorCorrectLevel, QrDatum data) {
|
||||
for (var typeNumber = 1; typeNumber <= 40; typeNumber++) {
|
||||
final totalDataBits = _calculateTotalDataBits(
|
||||
typeNumber,
|
||||
errorCorrectLevel,
|
||||
);
|
||||
|
||||
final buffer = QrBitBuffer();
|
||||
for (final datum in data) {
|
||||
buffer
|
||||
..put(datum.mode.value, 4)
|
||||
..put(datum.length, datum.mode.getLengthBits(typeNumber));
|
||||
datum.write(buffer);
|
||||
}
|
||||
final buffer = QrBitBuffer()
|
||||
..put(data.mode, 4)
|
||||
..put(data.length, _lengthInBits(data.mode, typeNumber));
|
||||
data.write(buffer);
|
||||
|
||||
if (buffer.length <= totalDataBits) return typeNumber;
|
||||
}
|
||||
|
||||
// If we reach here, the data is too long for any QR Code version.
|
||||
final buffer = QrBitBuffer();
|
||||
for (final datum in data) {
|
||||
buffer
|
||||
..put(datum.mode.value, 4)
|
||||
..put(datum.length, datum.mode.getLengthBits(40));
|
||||
datum.write(buffer);
|
||||
}
|
||||
final buffer = QrBitBuffer()
|
||||
..put(data.mode, 4)
|
||||
..put(data.length, _lengthInBits(data.mode, 40));
|
||||
data.write(buffer);
|
||||
|
||||
final maxBits = _calculateTotalDataBits(40, errorCorrectLevel);
|
||||
|
||||
|
|
@ -104,9 +100,19 @@ class QrCode {
|
|||
}
|
||||
|
||||
void addData(String data) {
|
||||
for (final datum in QrDatum.toDatums(data)) {
|
||||
_addToList(datum);
|
||||
final QrDatum 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));
|
||||
|
|
@ -121,8 +127,6 @@ class QrCode {
|
|||
void addAlphaNumeric(String alphaNumeric) =>
|
||||
_addToList(QrAlphaNumeric.fromString(alphaNumeric));
|
||||
|
||||
void addECI(int eciValue) => _addToList(QrEci(eciValue));
|
||||
|
||||
void _addToList(QrDatum data) {
|
||||
_dataList.add(data);
|
||||
_dataCache = null;
|
||||
|
|
@ -138,7 +142,7 @@ const int _pad1 = 0x11;
|
|||
|
||||
List<int> _createData(
|
||||
int typeNumber,
|
||||
QrErrorCorrectLevel errorCorrectLevel,
|
||||
int errorCorrectLevel,
|
||||
List<QrDatum> dataList,
|
||||
) {
|
||||
final rsBlocks = QrRsBlock.getRSBlocks(typeNumber, errorCorrectLevel);
|
||||
|
|
@ -148,8 +152,8 @@ List<int> _createData(
|
|||
for (var i = 0; i < dataList.length; i++) {
|
||||
final data = dataList[i];
|
||||
buffer
|
||||
..put(data.mode.value, 4)
|
||||
..put(data.length, data.mode.getLengthBits(typeNumber));
|
||||
..put(data.mode, 4)
|
||||
..put(data.length, _lengthInBits(data.mode, typeNumber));
|
||||
data.write(buffer);
|
||||
}
|
||||
|
||||
|
|
@ -160,10 +164,6 @@ List<int> _createData(
|
|||
errorCorrectLevel,
|
||||
);
|
||||
|
||||
if (buffer.length > totalDataBits) {
|
||||
throw InputTooLongException(buffer.length, totalDataBits);
|
||||
}
|
||||
|
||||
// HUH?
|
||||
// èIí[ÉRÅ[Éh
|
||||
if (buffer.length + 4 <= totalDataBits) {
|
||||
|
|
@ -244,6 +244,39 @@ List<int> _createBytes(QrBitBuffer buffer, List<QrRsBlock> rsBlocks) {
|
|||
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) {
|
||||
var a = QrPolynomial([1], 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,72 +1,34 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'error_correct_level.dart';
|
||||
import 'mask_pattern.dart';
|
||||
import 'mask_pattern.dart' as qr_mask_pattern;
|
||||
import 'qr_code.dart';
|
||||
import 'util.dart' as qr_util;
|
||||
|
||||
/// Renders the encoded data from a [QrCode] in a portable format.
|
||||
class QrImage {
|
||||
static const _pixelUnassigned = 0;
|
||||
static const _pixelLight = 1;
|
||||
static const _pixelDark = 2;
|
||||
|
||||
final int moduleCount;
|
||||
final int typeNumber;
|
||||
final QrErrorCorrectLevel errorCorrectLevel;
|
||||
final int errorCorrectLevel;
|
||||
final int maskPattern;
|
||||
|
||||
final Uint8List _data;
|
||||
final _modules = <List<bool?>>[];
|
||||
|
||||
/// Generates a QrImage with the best mask pattern encoding [qrCode].
|
||||
factory QrImage(QrCode qrCode) {
|
||||
// Create a template with invariant patterns
|
||||
final template = QrImage._template(qrCode);
|
||||
final moduleCount = template.moduleCount;
|
||||
final dataSize = moduleCount * moduleCount;
|
||||
var minLostPoint = 0.0;
|
||||
QrImage? bestImage;
|
||||
|
||||
// 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++) {
|
||||
// Copy pre-placed data to working buffer
|
||||
workingBuffer.setRange(0, dataSize, dataMap);
|
||||
|
||||
final testImage = QrImage._fromData(qrCode, i, workingBuffer)
|
||||
// Apply mask (XOR)
|
||||
.._applyMask(QrMaskPattern.values[i], template._data);
|
||||
|
||||
final testImage = QrImage._test(qrCode, i);
|
||||
final lostPoint = _lostPoint(testImage);
|
||||
|
||||
if (lostPoint < minLostPoint) {
|
||||
if (i == 0 || minLostPoint > lostPoint) {
|
||||
minLostPoint = lostPoint;
|
||||
bestMaskPattern = i;
|
||||
// Copy working buffer to bestData
|
||||
bestData ??= Uint8List(dataSize);
|
||||
bestData.setRange(0, dataSize, workingBuffer);
|
||||
bestImage = testImage;
|
||||
}
|
||||
}
|
||||
|
||||
final finalImage = QrImage._fromData(qrCode, bestMaskPattern, bestData!)
|
||||
// Final setup with correct format info (not test, so actual pixels)
|
||||
.._setupTypeInfo(bestMaskPattern, false);
|
||||
if (finalImage.typeNumber >= 7) {
|
||||
finalImage._setupTypeNumber(false);
|
||||
}
|
||||
|
||||
return finalImage;
|
||||
return QrImage.withMaskPattern(qrCode, bestImage!.maskPattern);
|
||||
}
|
||||
|
||||
/// Generates a specific image for the [qrCode] and [maskPattern].
|
||||
|
|
@ -74,75 +36,35 @@ class QrImage {
|
|||
: assert(maskPattern >= 0 && maskPattern <= 7),
|
||||
moduleCount = qrCode.moduleCount,
|
||||
typeNumber = qrCode.typeNumber,
|
||||
errorCorrectLevel = qrCode.errorCorrectLevel,
|
||||
_data = Uint8List(qrCode.moduleCount * qrCode.moduleCount) {
|
||||
errorCorrectLevel = qrCode.errorCorrectLevel {
|
||||
_makeImpl(maskPattern, qrCode.dataCache, false);
|
||||
}
|
||||
|
||||
/// Internal constructor for template creation
|
||||
QrImage._template(QrCode qrCode)
|
||||
QrImage._test(QrCode qrCode, this.maskPattern)
|
||||
: moduleCount = qrCode.moduleCount,
|
||||
typeNumber = qrCode.typeNumber,
|
||||
errorCorrectLevel = qrCode.errorCorrectLevel,
|
||||
maskPattern = 0, // Irrelevant
|
||||
_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);
|
||||
}
|
||||
errorCorrectLevel = qrCode.errorCorrectLevel {
|
||||
_makeImpl(maskPattern, qrCode.dataCache, true);
|
||||
}
|
||||
|
||||
/// Internal constructor for testing phase
|
||||
QrImage._fromData(QrCode qrCode, this.maskPattern, this._data)
|
||||
: moduleCount = qrCode.moduleCount,
|
||||
typeNumber = qrCode.typeNumber,
|
||||
errorCorrectLevel = qrCode.errorCorrectLevel;
|
||||
|
||||
@visibleForTesting
|
||||
List<List<bool?>> get qrModules {
|
||||
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;
|
||||
}
|
||||
List<List<bool?>> get qrModules => _modules;
|
||||
|
||||
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) {
|
||||
if (row < 0 || moduleCount <= row) {
|
||||
throw RangeError.range(row, 0, moduleCount - 1, 'row');
|
||||
if (row < 0 || moduleCount <= row || col < 0 || moduleCount <= col) {
|
||||
throw ArgumentError('$row , $col');
|
||||
}
|
||||
if (col < 0 || moduleCount <= 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;
|
||||
return _modules[row][col]!;
|
||||
}
|
||||
|
||||
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();
|
||||
_setupPositionProbePattern(0, 0);
|
||||
_setupPositionProbePattern(moduleCount - 7, 0);
|
||||
|
|
@ -158,13 +80,6 @@ class QrImage {
|
|||
_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) {
|
||||
for (var r = -1; r <= 7; r++) {
|
||||
if (row + r <= -1 || moduleCount <= row + r) continue;
|
||||
|
|
@ -175,9 +90,9 @@ class QrImage {
|
|||
if ((0 <= r && r <= 6 && (c == 0 || c == 6)) ||
|
||||
(0 <= c && c <= 6 && (r == 0 || r == 6)) ||
|
||||
(2 <= r && r <= 4 && 2 <= c && c <= 4)) {
|
||||
_set(row + r, col + c, true);
|
||||
_modules[row + r][col + c] = true;
|
||||
} else {
|
||||
_set(row + r, col + c, false);
|
||||
_modules[row + r][col + c] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -191,16 +106,16 @@ class QrImage {
|
|||
final row = pos[i];
|
||||
final col = pos[j];
|
||||
|
||||
if (_data[row * moduleCount + col] != _pixelUnassigned) {
|
||||
if (_modules[row][col] != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var r = -2; r <= 2; r++) {
|
||||
for (var c = -2; c <= 2; c++) {
|
||||
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 {
|
||||
_set(row + r, col + c, false);
|
||||
_modules[row + r][col + c] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,22 +125,22 @@ class QrImage {
|
|||
|
||||
void _setupTimingPattern() {
|
||||
for (var r = 8; r < moduleCount - 8; r++) {
|
||||
if (_data[r * moduleCount + 6] != _pixelUnassigned) {
|
||||
if (_modules[r][6] != null) {
|
||||
continue;
|
||||
}
|
||||
_set(r, 6, r.isEven);
|
||||
_modules[r][6] = r.isEven;
|
||||
}
|
||||
|
||||
for (var c = 8; c < moduleCount - 8; c++) {
|
||||
if (_data[6 * moduleCount + c] != _pixelUnassigned) {
|
||||
if (_modules[6][c] != null) {
|
||||
continue;
|
||||
}
|
||||
_set(6, c, c.isEven);
|
||||
_modules[6][c] = c.isEven;
|
||||
}
|
||||
}
|
||||
|
||||
void _setupTypeInfo(int maskPattern, bool test) {
|
||||
final data = (errorCorrectLevel.index << 3) | maskPattern;
|
||||
final data = (errorCorrectLevel << 3) | maskPattern;
|
||||
final bits = qr_util.bchTypeInfo(data);
|
||||
|
||||
int i;
|
||||
|
|
@ -236,11 +151,11 @@ class QrImage {
|
|||
mod = !test && ((bits >> i) & 1) == 1;
|
||||
|
||||
if (i < 6) {
|
||||
_set(i, 8, mod);
|
||||
_modules[i][8] = mod;
|
||||
} else if (i < 8) {
|
||||
_set(i + 1, 8, mod);
|
||||
_modules[i + 1][8] = mod;
|
||||
} 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;
|
||||
|
||||
if (i < 8) {
|
||||
_set(8, moduleCount - i - 1, mod);
|
||||
_modules[8][moduleCount - i - 1] = mod;
|
||||
} else if (i < 9) {
|
||||
_set(8, 15 - i - 1 + 1, mod);
|
||||
_modules[8][15 - i - 1 + 1] = mod;
|
||||
} else {
|
||||
_set(8, 15 - i - 1, mod);
|
||||
_modules[8][15 - i - 1] = mod;
|
||||
}
|
||||
}
|
||||
|
||||
// fixed module
|
||||
_set(moduleCount - 8, 8, !test);
|
||||
_modules[moduleCount - 8][8] = !test;
|
||||
}
|
||||
|
||||
void _setupTypeNumber(bool test) {
|
||||
|
|
@ -266,12 +181,12 @@ class QrImage {
|
|||
|
||||
for (var i = 0; i < 18; i++) {
|
||||
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++) {
|
||||
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 (var c = 0; c < 2; c++) {
|
||||
if (_data[row * moduleCount + (col - c)] == _pixelUnassigned) {
|
||||
if (_modules[row][col - c] == null) {
|
||||
var dark = false;
|
||||
|
||||
if (byteIndex < data.length) {
|
||||
dark = ((data[byteIndex] >> bitIndex) & 1) == 1;
|
||||
}
|
||||
|
||||
final mask = QrMaskPattern.values[maskPattern].check(row, col - c);
|
||||
final mask = _mask(maskPattern, row, col - c);
|
||||
|
||||
if (mask) {
|
||||
dark = !dark;
|
||||
}
|
||||
|
||||
_set(row, col - c, dark);
|
||||
_modules[row][col - c] = dark;
|
||||
bitIndex--;
|
||||
|
||||
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);
|
||||
bitIndex--;
|
||||
|
||||
if (bitIndex == -1) {
|
||||
byteIndex++;
|
||||
bitIndex = 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _mask(int maskPattern, int i, int j) => switch (maskPattern) {
|
||||
qr_mask_pattern.pattern000 => (i + j).isEven,
|
||||
qr_mask_pattern.pattern001 => i.isEven,
|
||||
qr_mask_pattern.pattern010 => j % 3 == 0,
|
||||
qr_mask_pattern.pattern011 => (i + j) % 3 == 0,
|
||||
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'),
|
||||
};
|
||||
|
||||
double _lostPoint(QrImage qrImage) {
|
||||
final moduleCount = qrImage.moduleCount;
|
||||
final data = qrImage._data;
|
||||
|
||||
var lostPoint = 0.0;
|
||||
int row, col;
|
||||
|
||||
// Cache data length for faster access (though it's final)
|
||||
// Accessing local vars is faster.
|
||||
|
||||
// Level 1
|
||||
for (var row = 0; row < moduleCount; row++) {
|
||||
for (var col = 0; col < moduleCount; col++) {
|
||||
// LEVEL1
|
||||
for (row = 0; row < moduleCount; row++) {
|
||||
for (col = 0; col < moduleCount; col++) {
|
||||
var sameCount = 0;
|
||||
final currentIdx = row * moduleCount + col;
|
||||
final isDark = data[currentIdx] == QrImage._pixelDark;
|
||||
final dark = qrImage.isDark(row, col);
|
||||
|
||||
// Check all 8 neighbors
|
||||
// Top row
|
||||
if (row > 0) {
|
||||
final upIdx = currentIdx - moduleCount;
|
||||
if (col > 0 && (data[upIdx - 1] == QrImage._pixelDark) == isDark) {
|
||||
sameCount++;
|
||||
for (var r = -1; r <= 1; r++) {
|
||||
if (row + r < 0 || moduleCount <= row + r) {
|
||||
continue;
|
||||
}
|
||||
if ((data[upIdx] == QrImage._pixelDark) == isDark) sameCount++;
|
||||
if (col < moduleCount - 1 &&
|
||||
(data[upIdx + 1] == QrImage._pixelDark) == isDark) {
|
||||
sameCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Middle row (left/right)
|
||||
if (col > 0 && (data[currentIdx - 1] == QrImage._pixelDark) == isDark) {
|
||||
sameCount++;
|
||||
}
|
||||
if (col < moduleCount - 1 &&
|
||||
(data[currentIdx + 1] == QrImage._pixelDark) == isDark) {
|
||||
sameCount++;
|
||||
}
|
||||
for (var c = -1; c <= 1; c++) {
|
||||
if (col + c < 0 || moduleCount <= col + c) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bottom row
|
||||
if (row < moduleCount - 1) {
|
||||
final downIdx = currentIdx + moduleCount;
|
||||
if (col > 0 && (data[downIdx - 1] == QrImage._pixelDark) == isDark) {
|
||||
sameCount++;
|
||||
}
|
||||
if ((data[downIdx] == QrImage._pixelDark) == isDark) sameCount++;
|
||||
if (col < moduleCount - 1 &&
|
||||
(data[downIdx + 1] == QrImage._pixelDark) == isDark) {
|
||||
sameCount++;
|
||||
if (r == 0 && c == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dark == qrImage.isDark(row + r, col + c)) {
|
||||
sameCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -445,58 +286,58 @@ double _lostPoint(QrImage qrImage) {
|
|||
}
|
||||
}
|
||||
|
||||
// Level 2: 2x2 blocks of same color
|
||||
for (var row = 0; row < moduleCount - 1; row++) {
|
||||
for (var col = 0; col < moduleCount - 1; col++) {
|
||||
final idx = row * moduleCount + col;
|
||||
final p00 = data[idx];
|
||||
final p01 = data[idx + 1];
|
||||
final p10 = data[idx + moduleCount];
|
||||
final p11 = data[idx + moduleCount + 1];
|
||||
|
||||
if (p00 == p01 && p00 == p10 && p00 == p11) {
|
||||
// LEVEL2
|
||||
for (row = 0; row < moduleCount - 1; row++) {
|
||||
for (col = 0; col < moduleCount - 1; col++) {
|
||||
var count = 0;
|
||||
if (qrImage.isDark(row, col)) count++;
|
||||
if (qrImage.isDark(row + 1, col)) count++;
|
||||
if (qrImage.isDark(row, col + 1)) count++;
|
||||
if (qrImage.isDark(row + 1, col + 1)) count++;
|
||||
if (count == 0 || count == 4) {
|
||||
lostPoint += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Level 3: 1:1:3:1:1 pattern
|
||||
// Dark, Light, Dark, Dark, Dark, Light, Dark
|
||||
for (var row = 0; row < moduleCount; row++) {
|
||||
for (var col = 0; col < moduleCount - 6; col++) {
|
||||
final idx = row * moduleCount + col;
|
||||
if (data[idx] == QrImage._pixelDark &&
|
||||
data[idx + 1] == QrImage._pixelLight &&
|
||||
data[idx + 2] == QrImage._pixelDark &&
|
||||
data[idx + 3] == QrImage._pixelDark &&
|
||||
data[idx + 4] == QrImage._pixelDark &&
|
||||
data[idx + 5] == QrImage._pixelLight &&
|
||||
data[idx + 6] == QrImage._pixelDark) {
|
||||
// LEVEL3
|
||||
for (row = 0; row < moduleCount; row++) {
|
||||
for (col = 0; col < moduleCount - 6; col++) {
|
||||
if (qrImage.isDark(row, col) &&
|
||||
!qrImage.isDark(row, col + 1) &&
|
||||
qrImage.isDark(row, col + 2) &&
|
||||
qrImage.isDark(row, col + 3) &&
|
||||
qrImage.isDark(row, col + 4) &&
|
||||
!qrImage.isDark(row, col + 5) &&
|
||||
qrImage.isDark(row, col + 6)) {
|
||||
lostPoint += 40;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check cols
|
||||
for (var col = 0; col < moduleCount; col++) {
|
||||
for (var row = 0; row < moduleCount - 6; row++) {
|
||||
final idx = row * moduleCount + col;
|
||||
if (data[idx] == QrImage._pixelDark &&
|
||||
data[idx + moduleCount] == QrImage._pixelLight &&
|
||||
data[idx + 2 * moduleCount] == QrImage._pixelDark &&
|
||||
data[idx + 3 * moduleCount] == QrImage._pixelDark &&
|
||||
data[idx + 4 * moduleCount] == QrImage._pixelDark &&
|
||||
data[idx + 5 * moduleCount] == QrImage._pixelLight &&
|
||||
data[idx + 6 * moduleCount] == QrImage._pixelDark) {
|
||||
for (col = 0; col < moduleCount; col++) {
|
||||
for (row = 0; row < moduleCount - 6; row++) {
|
||||
if (qrImage.isDark(row, col) &&
|
||||
!qrImage.isDark(row + 1, col) &&
|
||||
qrImage.isDark(row + 2, col) &&
|
||||
qrImage.isDark(row + 3, col) &&
|
||||
qrImage.isDark(row + 4, col) &&
|
||||
!qrImage.isDark(row + 5, col) &&
|
||||
qrImage.isDark(row + 6, col)) {
|
||||
lostPoint += 40;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Level 4: Dark ratio
|
||||
// LEVEL4
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ class QrRsBlock {
|
|||
|
||||
QrRsBlock._(this.totalCount, this.dataCount);
|
||||
|
||||
static List<QrRsBlock> getRSBlocks(
|
||||
int typeNumber,
|
||||
QrErrorCorrectLevel errorCorrectLevel,
|
||||
) {
|
||||
static List<QrRsBlock> getRSBlocks(int typeNumber, int errorCorrectLevel) {
|
||||
final rsBlock = _getRsBlockTable(typeNumber, errorCorrectLevel);
|
||||
|
||||
final length = rsBlock.length ~/ 3;
|
||||
|
|
@ -32,12 +29,15 @@ class QrRsBlock {
|
|||
|
||||
List<int> _getRsBlockTable(
|
||||
int typeNumber,
|
||||
QrErrorCorrectLevel errorCorrectLevel,
|
||||
int errorCorrectLevel,
|
||||
) => switch (errorCorrectLevel) {
|
||||
QrErrorCorrectLevel.low => _rsBlockTable[(typeNumber - 1) * 4 + 0],
|
||||
QrErrorCorrectLevel.medium => _rsBlockTable[(typeNumber - 1) * 4 + 1],
|
||||
QrErrorCorrectLevel.quartile => _rsBlockTable[(typeNumber - 1) * 4 + 2],
|
||||
QrErrorCorrectLevel.high => _rsBlockTable[(typeNumber - 1) * 4 + 3],
|
||||
QrErrorCorrectLevel.L => _rsBlockTable[(typeNumber - 1) * 4 + 0],
|
||||
QrErrorCorrectLevel.M => _rsBlockTable[(typeNumber - 1) * 4 + 1],
|
||||
QrErrorCorrectLevel.Q => _rsBlockTable[(typeNumber - 1) * 4 + 2],
|
||||
QrErrorCorrectLevel.H => _rsBlockTable[(typeNumber - 1) * 4 + 3],
|
||||
_ => throw ArgumentError(
|
||||
'bad rs block @ typeNumber: $typeNumber/errorCorrectLevel:$errorCorrectLevel',
|
||||
),
|
||||
};
|
||||
|
||||
const List<List<int>> _rsBlockTable = [
|
||||
|
|
|
|||
|
|
@ -12,13 +12,9 @@ dependencies:
|
|||
meta: ^1.7.0
|
||||
|
||||
dev_dependencies:
|
||||
args: ^2.1.0
|
||||
benchmark_harness: ^2.0.0
|
||||
build_runner: ^2.2.1
|
||||
build_web_compilers: ^4.1.4
|
||||
dart_flutter_team_lints: ^3.0.0
|
||||
path: ^1.9.1
|
||||
stream_transform: ^2.0.0
|
||||
test: ^1.21.6
|
||||
test_process: ^2.1.1
|
||||
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/src/byte.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
|
|
@ -6,11 +7,11 @@ void main() {
|
|||
final qr = QrAlphaNumeric.fromString(
|
||||
r'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:',
|
||||
);
|
||||
expect(qr.mode, QrMode.alphaNumeric);
|
||||
expect(qr.mode, 2);
|
||||
expect(qr.length, 45);
|
||||
final buffer = QrBitBuffer();
|
||||
qr.write(buffer);
|
||||
expect(buffer, hasLength(248));
|
||||
expect(buffer.length, 248);
|
||||
expect(
|
||||
buffer.map<String>((e) => e ? '1' : '0').join(),
|
||||
'00000000001'
|
||||
|
|
@ -41,21 +42,21 @@ void main() {
|
|||
|
||||
test('single alphanumeric', () {
|
||||
final qr = QrAlphaNumeric.fromString(r'$');
|
||||
expect(qr.mode, QrMode.alphaNumeric);
|
||||
expect(qr.mode, 2);
|
||||
expect(qr.length, 1);
|
||||
final buffer = QrBitBuffer();
|
||||
qr.write(buffer);
|
||||
expect(buffer, hasLength(6));
|
||||
expect(buffer.length, 6);
|
||||
expect(buffer.map<String>((e) => e ? '1' : '0').join(), '100101');
|
||||
});
|
||||
|
||||
test('double (even) alphanumeric', () {
|
||||
final qr = QrAlphaNumeric.fromString('3Z');
|
||||
expect(qr.mode, QrMode.alphaNumeric);
|
||||
expect(qr.mode, 2);
|
||||
expect(qr.length, 2);
|
||||
final buffer = QrBitBuffer();
|
||||
qr.write(buffer);
|
||||
expect(buffer, hasLength(11), reason: 'n*5+1 = 11');
|
||||
expect(buffer.length, 11, reason: 'n*5+1 = 11');
|
||||
expect(
|
||||
buffer
|
||||
.getRange(0, 11)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:qr/qr.dart';
|
||||
import 'package:qr/src/byte.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
|
|
|
|||
|
|
@ -11,32 +11,36 @@ import 'qr_code_test_data_with_mask.dart';
|
|||
void main() {
|
||||
test('simple', () {
|
||||
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 modules = qr.qrModules;
|
||||
expect(
|
||||
modules.map(_encodeBoolListToString),
|
||||
qrCodeTestData[typeNumber.toString()][quality.index.toString()],
|
||||
);
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
expect(
|
||||
_encodeBoolListToString(modules[i]),
|
||||
qrCodeTestData[typeNumber.toString()][quality.toString()][i],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('fromData', () {
|
||||
for (var quality in QrErrorCorrectLevel.values) {
|
||||
for (var quality in QrErrorCorrectLevel.levels) {
|
||||
final qr = QrImage(
|
||||
QrCode.fromData(data: 'shanna!', errorCorrectLevel: quality),
|
||||
);
|
||||
final modules = qr.qrModules;
|
||||
expect(
|
||||
modules.map(_encodeBoolListToString),
|
||||
qrCodeTestData['1'][quality.index.toString()],
|
||||
);
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
expect(
|
||||
_encodeBoolListToString(modules[i]),
|
||||
qrCodeTestData['1'][quality.toString()][i],
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('fromUint8List', () {
|
||||
for (var quality in QrErrorCorrectLevel.values) {
|
||||
for (var quality in QrErrorCorrectLevel.levels) {
|
||||
final qr = QrImage(
|
||||
QrCode.fromUint8List(
|
||||
data: Uint8List.fromList([115, 104, 97, 110, 110, 97, 33]),
|
||||
|
|
@ -44,53 +48,67 @@ void main() {
|
|||
),
|
||||
);
|
||||
final modules = qr.qrModules;
|
||||
expect(
|
||||
modules.map(_encodeBoolListToString),
|
||||
qrCodeTestData['1'][quality.index.toString()],
|
||||
);
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
expect(
|
||||
_encodeBoolListToString(modules[i]),
|
||||
qrCodeTestData['1'][quality.toString()][i],
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('WHEN mask pattern is provided, SHOULD make a masked QR Code', () {
|
||||
for (var mask = 0; mask <= 7; mask++) {
|
||||
final qr = QrImage.withMaskPattern(
|
||||
QrCode(1, QrErrorCorrectLevel.low)..addData('shanna!'),
|
||||
QrCode(1, QrErrorCorrectLevel.L)..addData('shanna!'),
|
||||
mask,
|
||||
);
|
||||
final modules = qr.qrModules;
|
||||
expect(
|
||||
modules.map(_encodeBoolListToString),
|
||||
qrCodeTestDataWithMask[mask.toString()],
|
||||
);
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
expect(
|
||||
_encodeBoolListToString(modules[i]),
|
||||
qrCodeTestDataWithMask[mask.toString()][i],
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('WHEN provided mask pattern is smaller than 0, '
|
||||
'SHOULD throw an AssertionError', () {
|
||||
expect(() {
|
||||
QrImage.withMaskPattern(
|
||||
QrCode(1, QrErrorCorrectLevel.low)..addData('shanna!'),
|
||||
-1,
|
||||
);
|
||||
}, throwsA(isA<AssertionError>()));
|
||||
});
|
||||
test(
|
||||
'''
|
||||
WHEN provided mask pattern is smaller than 0,
|
||||
SHOULD throw an AssertionError
|
||||
''',
|
||||
() {
|
||||
expect(() {
|
||||
QrImage.withMaskPattern(
|
||||
QrCode(1, QrErrorCorrectLevel.L)..addData('shanna!'),
|
||||
-1,
|
||||
);
|
||||
}, throwsA(isA<AssertionError>()));
|
||||
},
|
||||
);
|
||||
|
||||
test('WHEN provided mask pattern is bigger than 7, '
|
||||
'SHOULD throw an AssertionError', () {
|
||||
expect(() {
|
||||
QrImage.withMaskPattern(
|
||||
QrCode(1, QrErrorCorrectLevel.high)..addData('shanna!'),
|
||||
8,
|
||||
);
|
||||
}, throwsA(isA<AssertionError>()));
|
||||
});
|
||||
test(
|
||||
'''
|
||||
WHEN provided mask pattern is bigger than 7,
|
||||
SHOULD throw an AssertionError
|
||||
''',
|
||||
() {
|
||||
expect(() {
|
||||
QrImage.withMaskPattern(
|
||||
QrCode(1, QrErrorCorrectLevel.L)..addData('shanna!'),
|
||||
8,
|
||||
);
|
||||
}, throwsA(isA<AssertionError>()));
|
||||
},
|
||||
);
|
||||
group('QrCode.fromData Automatic Mode Detection', () {
|
||||
// Numeric Mode
|
||||
test('should use Numeric Mode for numbers', () {
|
||||
// 9 numeric chars fit version 1 (H level).
|
||||
final qr = QrCode.fromData(
|
||||
data: '123456789',
|
||||
errorCorrectLevel: QrErrorCorrectLevel.high,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.H,
|
||||
);
|
||||
expect(qr.typeNumber, 1);
|
||||
});
|
||||
|
|
@ -101,7 +119,7 @@ void main() {
|
|||
// version 2 (H level, 16 chars).
|
||||
final qr = QrCode.fromData(
|
||||
data: 'HELLO WORLD A',
|
||||
errorCorrectLevel: QrErrorCorrectLevel.high,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.H,
|
||||
);
|
||||
expect(qr.typeNumber, 2);
|
||||
});
|
||||
|
|
@ -112,7 +130,7 @@ void main() {
|
|||
// '機械学習' (12 bytes) fits version 2 (H level, 16 bytes).
|
||||
final qr = QrCode.fromData(
|
||||
data: '機械学習',
|
||||
errorCorrectLevel: QrErrorCorrectLevel.high,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.H,
|
||||
);
|
||||
expect(qr.typeNumber, 2);
|
||||
});
|
||||
|
|
@ -122,7 +140,7 @@ void main() {
|
|||
// Numeric Mode
|
||||
test('should use Numeric Mode for numbers', () {
|
||||
// 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);
|
||||
});
|
||||
|
||||
|
|
@ -130,7 +148,7 @@ void main() {
|
|||
test('should use Alphanumeric Mode', () {
|
||||
// 13 alphanumeric characters exceed version 1 (7 chars) but fit
|
||||
// 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);
|
||||
});
|
||||
|
||||
|
|
@ -138,7 +156,7 @@ void main() {
|
|||
test('should use Byte Mode for non-alphanumeric characters', () {
|
||||
// Kanji characters are UTF-8 encoded.
|
||||
// '機械学習' (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);
|
||||
});
|
||||
});
|
||||
|
|
@ -150,7 +168,7 @@ void main() {
|
|||
|
||||
final qrCode = QrCode.fromData(
|
||||
data: largeData,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.low,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.L,
|
||||
);
|
||||
|
||||
expect(qrCode.typeNumber, 40);
|
||||
|
|
@ -164,20 +182,12 @@ void main() {
|
|||
expect(
|
||||
() => QrCode.fromData(
|
||||
data: excessivelyLargeData,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.low,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.L,
|
||||
),
|
||||
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) =>
|
||||
|
|
|
|||
|
|
@ -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/src/byte.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('all digits 1 through 0', () {
|
||||
final qr = QrNumeric.fromString('1234567890');
|
||||
expect(qr.mode, QrMode.numeric);
|
||||
expect(qr.mode, 1);
|
||||
expect(qr.length, 10);
|
||||
final buffer = QrBitBuffer();
|
||||
qr.write(buffer);
|
||||
expect(buffer, hasLength(34));
|
||||
expect(buffer.length, 34);
|
||||
expect(
|
||||
buffer
|
||||
.getRange(0, 10)
|
||||
|
|
@ -53,11 +54,11 @@ void main() {
|
|||
|
||||
test('single numeric', () {
|
||||
final qr = QrNumeric.fromString('5');
|
||||
expect(qr.mode, QrMode.numeric);
|
||||
expect(qr.mode, 1);
|
||||
expect(qr.length, 1);
|
||||
final buffer = QrBitBuffer();
|
||||
qr.write(buffer);
|
||||
expect(buffer, hasLength(4));
|
||||
expect(buffer.length, 4);
|
||||
expect(
|
||||
buffer
|
||||
.getRange(0, 4)
|
||||
|
|
@ -72,11 +73,11 @@ void main() {
|
|||
|
||||
test('double numeric', () {
|
||||
final qr = QrNumeric.fromString('37');
|
||||
expect(qr.mode, QrMode.numeric);
|
||||
expect(qr.mode, 1);
|
||||
expect(qr.length, 2);
|
||||
final buffer = QrBitBuffer();
|
||||
qr.write(buffer);
|
||||
expect(buffer, hasLength(7), reason: 'n*3+1 = 7');
|
||||
expect(buffer.length, 7, reason: 'n*3+1 = 7');
|
||||
expect(
|
||||
buffer
|
||||
.getRange(0, 7)
|
||||
|
|
@ -91,11 +92,11 @@ void main() {
|
|||
|
||||
test('triple (even) numeric', () {
|
||||
final qr = QrNumeric.fromString('371');
|
||||
expect(qr.mode, QrMode.numeric);
|
||||
expect(qr.mode, 1);
|
||||
expect(qr.length, 3);
|
||||
final buffer = QrBitBuffer();
|
||||
qr.write(buffer);
|
||||
expect(buffer, hasLength(10), reason: 'n*3+1 = 10');
|
||||
expect(buffer.length, 10, reason: 'n*3+1 = 10');
|
||||
expect(
|
||||
buffer
|
||||
.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