use unicode_segmentation::UnicodeSegmentation;
use crate::{
EncodeHints, Exceptions,
common::{
BitArray, BitFieldBaseType, CharacterSet, Eci, Result,
reedsolomon::{PredefinedGenericGF, ReedSolomonEncoder, get_predefined_genericgf},
},
qrcode::common::{ErrorCorrectionLevel, Mode, Version, VersionRef},
};
use super::{BlockPair, MinimalEncoder, QRCode, mask_util, matrix_util};
use crate::common::cpp_essentials::ByteMatrix;
static SHIFT_JIS_CHARSET: CharacterSet = CharacterSet::Shift_JIS;
const ALPHANUMERIC_TABLE: [i8; 96] = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, ];
pub const DEFAULT_BYTE_MODE_ENCODING: CharacterSet = CharacterSet::ISO8859_1;
pub fn calculateMaskPenalty(matrix: &ByteMatrix) -> u32 {
mask_util::applyMaskPenaltyRule1(matrix)
+ mask_util::applyMaskPenaltyRule2(matrix)
+ mask_util::applyMaskPenaltyRule3(matrix)
+ mask_util::applyMaskPenaltyRule4(matrix)
}
pub fn encode(content: &str, ecLevel: ErrorCorrectionLevel) -> Result<QRCode> {
encode_with_hints(content, ecLevel, &EncodeHints::default())
}
pub fn encode_with_hints(
content: &str,
ec_level: ErrorCorrectionLevel,
hints: &EncodeHints,
) -> Result<QRCode> {
let version;
let mut header_and_data_bits;
let mode;
let has_gs1_format_hint = matches!(hints.Gs1Format, Some(true));
let has_compaction_hint = if let Some(vb) = &hints.QrCompact {
vb.parse::<bool>().unwrap_or_default()
} else {
false
};
let mut encoding = None; let mut has_encoding_hint = hints.CharacterSet.is_some();
if has_encoding_hint {
if let Some(v) = &hints.CharacterSet {
encoding = Some(CharacterSet::get_character_set_by_name(v).ok_or(Exceptions::WRITER)?)
}
}
if has_compaction_hint {
mode = Mode::BYTE;
let priority_encoding = encoding; let rn = MinimalEncoder::encode_with_details(
content,
None,
priority_encoding,
has_gs1_format_hint,
ec_level,
)?;
header_and_data_bits = BitArray::new();
rn.getBits(&mut header_and_data_bits)?;
version = rn.getVersion();
} else {
let encoding = if let Some(encoding) = encoding {
encoding
} else if let Ok(_encs) = DEFAULT_BYTE_MODE_ENCODING.encode(content) {
DEFAULT_BYTE_MODE_ENCODING
} else {
has_encoding_hint = true;
CharacterSet::UTF8
};
mode = chooseModeWithEncoding(content, encoding);
let mut header_bits = BitArray::new();
if mode == Mode::BYTE && has_encoding_hint {
appendECI(encoding.into(), &mut header_bits)?;
}
if has_gs1_format_hint {
appendModeInfo(Mode::FNC1_FIRST_POSITION, &mut header_bits)?;
}
appendModeInfo(mode, &mut header_bits)?;
let mut data_bits = BitArray::new();
appendBytes(content, mode, &mut data_bits, encoding)?;
if hints.QrVersion.is_some() {
let versionNumber = if let Some(v) = &hints.QrVersion {
v.parse::<u32>().unwrap_or_default()
} else {
0
};
version = Version::getVersionForNumber(versionNumber)?;
let bitsNeeded = calculateBitsNeeded(mode, &header_bits, &data_bits, version);
if !willFit(bitsNeeded, version, &ec_level) {
return Err(Exceptions::writer_with(
"Data too big for requested version",
));
}
} else {
version = recommendVersion(&ec_level, mode, &header_bits, &data_bits)?;
}
header_and_data_bits = BitArray::new();
header_and_data_bits.appendBitArray(header_bits);
let num_letters = if mode == Mode::BYTE {
data_bits.getSizeInBytes()
} else {
content.graphemes(true).count()
};
appendLengthInfo(num_letters as u32, version, mode, &mut header_and_data_bits)?;
header_and_data_bits.appendBitArray(data_bits);
}
let ec_blocks = version.getECBlocksForLevel(ec_level);
let num_data_bytes = version.getTotalCodewords() - ec_blocks.getTotalECCodewords();
terminateBits(num_data_bytes, &mut header_and_data_bits)?;
let final_bits = interleaveWithECBytes(
&header_and_data_bits,
version.getTotalCodewords(),
num_data_bytes,
ec_blocks.getNumBlocks(),
)?;
let mut qrCode = QRCode::new();
qrCode.setECLevel(ec_level);
qrCode.setMode(mode);
qrCode.setVersion(version);
let dimension = version.getDimensionForVersion();
let mut matrix = ByteMatrix::new(dimension, dimension);
let mut mask_pattern = -1;
if let Some(v) = &hints.QrMaskPattern {
let hint_mask_pattern = v.parse::<i32>().unwrap_or(-1);
mask_pattern = if QRCode::isValidMaskPattern(hint_mask_pattern) {
hint_mask_pattern
} else {
-1
};
}
if mask_pattern == -1 {
mask_pattern = chooseMaskPattern(&final_bits, &ec_level, version, &mut matrix)? as i32;
}
qrCode.setMaskPattern(mask_pattern);
matrix_util::buildMatrix(&final_bits, &ec_level, version, mask_pattern, &mut matrix)?;
qrCode.setMatrix(matrix);
Ok(qrCode)
}
fn recommendVersion(
ec_level: &ErrorCorrectionLevel,
mode: Mode,
header_bits: &BitArray,
data_bits: &BitArray,
) -> Result<VersionRef> {
let provisional_bits_needed = calculateBitsNeeded(
mode,
header_bits,
data_bits,
Version::getVersionForNumber(1)?,
);
let provisional_version = chooseVersion(provisional_bits_needed, ec_level)?;
let bits_needed = calculateBitsNeeded(mode, header_bits, data_bits, provisional_version);
chooseVersion(bits_needed, ec_level)
}
fn calculateBitsNeeded(
mode: Mode,
header_bits: &BitArray,
data_bits: &BitArray,
version: VersionRef,
) -> u32 {
(header_bits.get_size() + mode.getCharacterCountBits(version) as usize + data_bits.get_size())
as u32
}
pub fn getAlphanumericCode(code: u32) -> i8 {
let code = code as usize;
if code < ALPHANUMERIC_TABLE.len() {
ALPHANUMERIC_TABLE[code]
} else {
-1
}
}
pub fn chooseMode(content: &str) -> Mode {
chooseModeWithEncoding(content, CharacterSet::ISO8859_1)
}
fn chooseModeWithEncoding(content: &str, encoding: CharacterSet) -> Mode {
if SHIFT_JIS_CHARSET == encoding && isOnlyDoubleByteKanji(content) {
return Mode::KANJI;
}
let mut has_numeric = false;
let mut has_alphanumeric = false;
for c in content.chars() {
if c.is_ascii_digit() {
has_numeric = true;
} else if getAlphanumericCode(c as u32) != -1 {
has_alphanumeric = true;
} else {
return Mode::BYTE;
}
}
if has_alphanumeric {
return Mode::ALPHANUMERIC;
}
if has_numeric {
return Mode::NUMERIC;
}
Mode::BYTE
}
pub fn isOnlyDoubleByteKanji(content: &str) -> bool {
let bytes = if let Ok(byt) = SHIFT_JIS_CHARSET.encode(content) {
byt
} else {
return false;
};
let length = bytes.len();
if length % 2 != 0 {
return false;
}
let mut i = 0;
while i < length {
let byte1 = bytes[i];
if !(0x81..=0x9F).contains(&byte1) && !(0xE0..=0xEB).contains(&byte1) {
return false;
}
i += 2;
}
true
}
fn chooseMaskPattern(
bits: &BitArray,
ec_level: &ErrorCorrectionLevel,
version: VersionRef,
matrix: &mut ByteMatrix,
) -> Result<u32> {
let mut min_penalty = u32::MAX; let mut best_mask_pattern = -1;
for maskPattern in 0..QRCode::NUM_MASK_PATTERNS {
let mut matrix = matrix.clone();
matrix_util::buildMatrix(bits, ec_level, version, maskPattern, &mut matrix)?;
let penalty = calculateMaskPenalty(&matrix);
if penalty < min_penalty {
min_penalty = penalty;
best_mask_pattern = maskPattern;
}
}
Ok(best_mask_pattern as u32)
}
fn chooseVersion(numInputBits: u32, ecLevel: &ErrorCorrectionLevel) -> Result<VersionRef> {
for versionNum in 1..=40 {
let version = Version::getVersionForNumber(versionNum)?;
if willFit(numInputBits, version, ecLevel) {
return Ok(version);
}
}
Err(Exceptions::writer_with(format!(
"data too big {numInputBits}/{ecLevel:?}"
)))
}
pub fn willFit(numInputBits: u32, version: VersionRef, ecLevel: &ErrorCorrectionLevel) -> bool {
let num_bytes = version.getTotalCodewords();
let ec_blocks = version.getECBlocksForLevel(*ecLevel);
let num_ec_bytes = ec_blocks.getTotalECCodewords();
let num_data_bytes = num_bytes - num_ec_bytes;
let total_input_bytes = numInputBits.div_ceil(8);
num_data_bytes >= total_input_bytes
}
pub fn terminateBits(num_data_bytes: u32, bits: &mut BitArray) -> Result<()> {
let capacity = num_data_bytes * 8;
if bits.get_size() > capacity as usize {
return Err(Exceptions::writer_with(format!(
"data bits cannot fit in the QR Code{capacity} > "
)));
}
for _i in 0..4 {
if bits.get_size() >= capacity as usize {
break;
}
bits.appendBit(false);
}
let num_bits_in_last_byte = bits.get_size() & 0x07;
if num_bits_in_last_byte > 0 {
for _i in num_bits_in_last_byte..8 {
bits.appendBit(false);
}
}
let num_padding_bytes = num_data_bytes as isize - bits.getSizeInBytes() as isize;
for i in 0..num_padding_bytes {
if i >= num_padding_bytes {
break;
}
bits.appendBits(if (i & 0x01) == 0 { 0xEC } else { 0x11 }, 8)?;
}
if bits.get_size() != capacity as usize {
return Err(Exceptions::writer_with("Bits size does not equal capacity"));
}
Ok(())
}
pub fn getNumDataBytesAndNumECBytesForBlockID(
num_total_bytes: u32,
num_data_bytes: u32,
num_rsblocks: u32,
block_id: u32,
) -> Result<(u32, u32)> {
if block_id >= num_rsblocks {
return Err(Exceptions::writer_with("Block ID too large"));
}
let num_rs_blocks_in_group2 = num_total_bytes % num_rsblocks;
let num_rs_blocks_in_group1 = num_rsblocks - num_rs_blocks_in_group2;
let num_total_bytes_in_group1 = num_total_bytes / num_rsblocks;
let num_total_bytes_in_group2 = num_total_bytes_in_group1 + 1;
let num_data_bytes_in_group1 = num_data_bytes / num_rsblocks;
let num_data_bytes_in_group2 = num_data_bytes_in_group1 + 1;
let num_ec_bytes_in_group1 = num_total_bytes_in_group1 - num_data_bytes_in_group1;
let numEcBytesInGroup2 = num_total_bytes_in_group2 - num_data_bytes_in_group2;
if num_ec_bytes_in_group1 != numEcBytesInGroup2 {
return Err(Exceptions::writer_with("EC bytes mismatch"));
}
if num_rsblocks != num_rs_blocks_in_group1 + num_rs_blocks_in_group2 {
return Err(Exceptions::writer_with("RS blocks mismatch"));
}
if num_total_bytes
!= ((num_data_bytes_in_group1 + num_ec_bytes_in_group1) * num_rs_blocks_in_group1)
+ ((num_data_bytes_in_group2 + numEcBytesInGroup2) * num_rs_blocks_in_group2)
{
return Err(Exceptions::writer_with("total bytes mismatch"));
}
Ok(if block_id < num_rs_blocks_in_group1 {
(num_data_bytes_in_group1, num_ec_bytes_in_group1)
} else {
(num_data_bytes_in_group2, numEcBytesInGroup2)
})
}
pub fn interleaveWithECBytes(
bits: &BitArray,
num_total_bytes: u32,
num_data_bytes: u32,
num_rsblocks: u32,
) -> Result<BitArray> {
if bits.getSizeInBytes() as u32 != num_data_bytes {
return Err(Exceptions::writer_with(
"Number of bits and data bytes does not match",
));
}
let mut data_bytes_offset = 0;
let mut max_num_data_bytes = 0;
let mut max_num_ec_bytes = 0;
let mut blocks = Vec::new();
for i in 0..num_rsblocks {
let (numDataBytesInBlock, numEcBytesInBlock) = getNumDataBytesAndNumECBytesForBlockID(
num_total_bytes,
num_data_bytes,
num_rsblocks,
i,
)?;
let size = numDataBytesInBlock;
let mut dataBytes = vec![0u8; size as usize];
bits.toBytes(8 * data_bytes_offset, &mut dataBytes, 0, size as usize);
let ec_bytes = generateECBytes(&dataBytes, numEcBytesInBlock as usize)?;
blocks.push(BlockPair::new(dataBytes, ec_bytes.clone()));
max_num_data_bytes = max_num_data_bytes.max(size);
max_num_ec_bytes = max_num_ec_bytes.max(ec_bytes.len());
data_bytes_offset += numDataBytesInBlock as usize;
}
if num_data_bytes != data_bytes_offset as u32 {
return Err(Exceptions::writer_with("Data bytes does not match offset"));
}
let mut result = BitArray::new();
for i in 0..max_num_data_bytes as usize {
for block in &blocks {
let data_bytes = block.getDataBytes();
if i < data_bytes.len() {
result.appendBits(data_bytes[i] as BitFieldBaseType, 8)?;
}
}
}
for i in 0..max_num_ec_bytes {
for block in &blocks {
let ec_bytes = block.getErrorCorrectionBytes();
if i < ec_bytes.len() {
result.appendBits(ec_bytes[i] as BitFieldBaseType, 8)?;
}
}
}
if num_total_bytes != result.getSizeInBytes() as u32 {
return Err(Exceptions::writer_with(format!(
"Interleaving error: {} and {} differ.",
num_total_bytes,
result.getSizeInBytes()
)));
}
Ok(result)
}
pub fn generateECBytes(dataBytes: &[u8], num_ec_bytes_in_block: usize) -> Result<Vec<u8>> {
let num_data_bytes = dataBytes.len();
let mut to_encode = vec![0; num_data_bytes + num_ec_bytes_in_block];
for i in 0..num_data_bytes {
to_encode[i] = dataBytes[i] as i32;
}
ReedSolomonEncoder::new(get_predefined_genericgf(
PredefinedGenericGF::QrCodeField256,
))?
.encode(&mut to_encode, num_ec_bytes_in_block)?;
let mut ecBytes = vec![0u8; num_ec_bytes_in_block];
for i in 0..num_ec_bytes_in_block {
ecBytes[i] = to_encode[num_data_bytes + i] as u8;
}
Ok(ecBytes)
}
pub fn appendModeInfo(mode: Mode, bits: &mut BitArray) -> Result<()> {
bits.appendBits(mode.getBits() as BitFieldBaseType, 4)
}
pub fn appendLengthInfo(
num_letters: u32,
version: VersionRef,
mode: Mode,
bits: &mut BitArray,
) -> Result<()> {
let numBits = mode.getCharacterCountBits(version);
if num_letters >= (1 << numBits) {
return Err(Exceptions::writer_with(format!(
"{} is bigger than {}",
num_letters,
((1 << numBits) - 1)
)));
}
bits.appendBits(num_letters as BitFieldBaseType, numBits as usize)
}
pub fn appendBytes(
content: &str,
mode: Mode,
bits: &mut BitArray,
encoding: CharacterSet,
) -> Result<()> {
match mode {
Mode::NUMERIC => appendNumericBytes(content, bits),
Mode::ALPHANUMERIC => appendAlphanumericBytes(content, bits),
Mode::BYTE => append8BitBytes(content, bits, encoding),
Mode::KANJI => appendKanjiBytes(content, bits),
_ => Err(Exceptions::writer_with(format!("Invalid mode: {mode:?}"))),
}
}
pub fn appendNumericBytes(content: &str, bits: &mut BitArray) -> Result<()> {
let length = content.len();
let mut i = 0;
let content_byte_cache: Vec<u8> = content.chars().map(|c| c as u8).collect();
while i < length {
let num1 = content_byte_cache[i] - b'0';
if i + 2 < length {
let num2 = content_byte_cache[i + 1] - b'0';
let num3 = content_byte_cache[i + 2] - b'0';
bits.appendBits(
num1 as BitFieldBaseType * 100
+ num2 as BitFieldBaseType * 10
+ num3 as BitFieldBaseType,
10,
)?;
i += 3;
} else if i + 1 < length {
let num2 = content_byte_cache[i + 1] - b'0';
bits.appendBits(num1 as BitFieldBaseType * 10 + num2 as BitFieldBaseType, 7)?;
i += 2;
} else {
bits.appendBits(num1 as BitFieldBaseType, 4)?;
i += 1;
}
}
Ok(())
}
pub fn appendAlphanumericBytes(content: &str, bits: &mut BitArray) -> Result<()> {
let length = content.len();
let content_byte_cache: Vec<u32> = content.chars().map(|c| c as u32).collect();
let mut i = 0;
while i < length {
let code1 = getAlphanumericCode(content_byte_cache[i]);
if code1 == -1 {
return Err(Exceptions::WRITER);
}
if i + 1 < length {
let code2 = getAlphanumericCode(content_byte_cache[i + 1]);
if code2 == -1 {
return Err(Exceptions::WRITER);
}
bits.appendBits((code1 as i16 * 45 + code2 as i16) as BitFieldBaseType, 11)?;
i += 2;
} else {
bits.appendBits(code1 as BitFieldBaseType, 6)?;
i += 1;
}
}
Ok(())
}
pub fn append8BitBytes(content: &str, bits: &mut BitArray, encoding: CharacterSet) -> Result<()> {
let bytes = encoding
.encode(content)
.map_err(|e| Exceptions::writer_with(format!("error {e}")))?;
for b in bytes {
bits.appendBits(b as BitFieldBaseType, 8)?;
}
Ok(())
}
pub fn appendKanjiBytes(content: &str, bits: &mut BitArray) -> Result<()> {
let sjis = &SHIFT_JIS_CHARSET;
let bytes = sjis
.encode(content)
.map_err(|e| Exceptions::writer_with(format!("error {e}")))?;
if bytes.len() % 2 != 0 {
return Err(Exceptions::writer_with("Kanji byte size not even"));
}
let max_i = bytes.len() - 1; let mut i = 0;
while i < max_i {
let byte1 = bytes[i]; let byte2 = bytes[i + 1]; let code: u16 = ((byte1 as u16) << 8u16) | byte2 as u16;
let mut subtracted: i32 = -1;
if (0x8140..=0x9ffc).contains(&code) {
subtracted = code as i32 - 0x8140;
} else if (0xe040..=0xebbf).contains(&code) {
subtracted = code as i32 - 0xc140;
}
if subtracted == -1 {
return Err(Exceptions::writer_with("Invalid byte sequence"));
}
let encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff);
bits.appendBits(encoded as BitFieldBaseType, 13)?;
i += 2;
}
Ok(())
}
fn appendECI(eci: Eci, bits: &mut BitArray) -> Result<()> {
bits.appendBits(Mode::ECI.getBits() as BitFieldBaseType, 4)?;
bits.appendBits(eci as BitFieldBaseType, 8)
}