#![allow(deprecated)]
use num::{self, bigint::ToBigUint, BigUint};
use std::rc::Rc;
use crate::{
common::{DecoderRXingResult, ECIStringBuilder, Eci, Result},
pdf417::PDF417RXingResultMetadata,
Exceptions,
};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Mode {
Alpha,
Lower,
Mixed,
Punct,
AlphaShift,
PunctShift,
}
const TEXT_COMPACTION_MODE_LATCH: u32 = 900;
const BYTE_COMPACTION_MODE_LATCH: u32 = 901;
const NUMERIC_COMPACTION_MODE_LATCH: u32 = 902;
const BYTE_COMPACTION_MODE_LATCH_6: u32 = 924;
const ECI_USER_DEFINED: u32 = 925;
const ECI_GENERAL_PURPOSE: u32 = 926;
const ECI_CHARSET: u32 = 927;
const BEGIN_MACRO_PDF417_CONTROL_BLOCK: u32 = 928;
const BEGIN_MACRO_PDF417_OPTIONAL_FIELD: u32 = 923;
const MACRO_PDF417_TERMINATOR: u32 = 922;
const MODE_SHIFT_TO_BYTE_COMPACTION_MODE: u32 = 913;
const MAX_NUMERIC_CODEWORDS: usize = 15;
const MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME: u32 = 0;
const MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT: u32 = 1;
const MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP: u32 = 2;
const MACRO_PDF417_OPTIONAL_FIELD_SENDER: u32 = 3;
const MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE: u32 = 4;
const MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE: u32 = 5;
const MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM: u32 = 6;
const PRE_TEXT_COMPACTION_MODE_LATCH: u32 = TEXT_COMPACTION_MODE_LATCH - 1;
const PL: u32 = 25;
const LL: u32 = 27;
const AS: u32 = 27;
const ML: u32 = 28;
const AL: u32 = 28;
const PS: u32 = 29;
const PAL: u32 = 29;
const PUNCT_CHARS: [char; 29] = [
';', '<', '>', '@', '[', '\\', ']', '_', '`', '~', '!', '\r', '\t', ',', ':', '\n', '-', '.',
'$', '/', '"', '|', '*', '(', ')', '?', '{', '}', '\'',
];
const MIXED_CHARS: [char; 25] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&', '\r', '\t', ',', ':', '#', '-', '.',
'$', '/', '+', '%', '*', '=', '^',
];
use once_cell::sync::Lazy;
static EXP900: Lazy<Vec<BigUint>> = Lazy::new(|| {
const EXP_LEN: usize = 16;
let mut exp900 = Vec::with_capacity(EXP_LEN); exp900.push(ToBigUint::to_biguint(&1).unwrap_or_default());
let nineHundred = ToBigUint::to_biguint(&900).unwrap_or_default();
exp900.push(nineHundred);
let mut i = 2;
while i < EXP_LEN {
exp900.push(&exp900[i - 1] * 900_u32);
i += 1;
}
exp900
});
const NUMBER_OF_SEQUENCE_CODEWORDS: usize = 2;
pub fn decode(codewords: &[u32], ecLevel: &str) -> Result<DecoderRXingResult> {
let mut result = ECIStringBuilder::with_capacity(codewords.len() * 2);
let mut codeIndex = textCompaction(codewords, 1, &mut result)?;
let mut resultMetadata = PDF417RXingResultMetadata::default();
while codeIndex < codewords[0] as usize {
let code = codewords[codeIndex];
codeIndex += 1;
match code {
TEXT_COMPACTION_MODE_LATCH => {
codeIndex = textCompaction(codewords, codeIndex, &mut result)?
}
BYTE_COMPACTION_MODE_LATCH | BYTE_COMPACTION_MODE_LATCH_6 => {
codeIndex = byteCompaction(code, codewords, codeIndex, &mut result)?
}
MODE_SHIFT_TO_BYTE_COMPACTION_MODE => {
result.append_char(char::from_u32(codewords[codeIndex]).ok_or(Exceptions::PARSE)?);
codeIndex += 1;
}
NUMERIC_COMPACTION_MODE_LATCH => {
codeIndex = numericCompaction(codewords, codeIndex, &mut result)?
}
ECI_CHARSET => {
result.append_eci(Eci::from(codewords[codeIndex]));
codeIndex += 1;
}
ECI_GENERAL_PURPOSE =>
{
codeIndex += 2
}
ECI_USER_DEFINED =>
{
codeIndex += 1
}
BEGIN_MACRO_PDF417_CONTROL_BLOCK => {
codeIndex = decodeMacroBlock(codewords, codeIndex, &mut resultMetadata)?
}
BEGIN_MACRO_PDF417_OPTIONAL_FIELD | MACRO_PDF417_TERMINATOR =>
{
return Err(Exceptions::FORMAT)
}
_ => {
codeIndex -= 1;
codeIndex = textCompaction(codewords, codeIndex, &mut result)?;
}
}
}
result = result.build_result();
if result.is_empty() && resultMetadata.getFileId().is_empty() {
return Err(Exceptions::FORMAT);
}
let mut decoderRXingResult = DecoderRXingResult::new(
Vec::new(),
result.to_string(),
Vec::new(),
ecLevel.to_owned(),
);
decoderRXingResult.setOther(Some(Rc::new(resultMetadata)));
Ok(decoderRXingResult)
}
pub fn decodeMacroBlock(
codewords: &[u32],
codeIndex: usize,
resultMetadata: &mut PDF417RXingResultMetadata,
) -> Result<usize> {
let mut codeIndex = codeIndex;
if codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0] as usize {
return Err(Exceptions::FORMAT);
}
let mut segmentIndexArray = [0; NUMBER_OF_SEQUENCE_CODEWORDS];
for seq in segmentIndexArray
.iter_mut()
.take(NUMBER_OF_SEQUENCE_CODEWORDS)
{
*seq = codewords[codeIndex];
codeIndex += 1;
}
let segmentIndexString =
decodeBase900toBase10(&segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS)?;
if segmentIndexString.is_empty() {
resultMetadata.setSegmentIndex(0);
} else if let Ok(parsed_int) = segmentIndexString.parse::<usize>() {
resultMetadata.setSegmentIndex(parsed_int);
} else {
return Err(Exceptions::FORMAT);
}
let mut fileId = String::new();
while codeIndex < codewords[0] as usize
&& codeIndex < codewords.len()
&& codewords[codeIndex] != MACRO_PDF417_TERMINATOR
&& codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD
{
fileId.push_str(&format!("{:0>3}", codewords[codeIndex]));
codeIndex += 1;
}
if fileId.chars().count() == 0 {
return Err(Exceptions::FORMAT);
}
resultMetadata.setFileId(fileId);
let mut optionalFieldsStart = -1_isize;
if codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD {
optionalFieldsStart = codeIndex as isize + 1;
}
while codeIndex < codewords[0] as usize {
match codewords[codeIndex] {
BEGIN_MACRO_PDF417_OPTIONAL_FIELD => {
codeIndex += 1;
match codewords[codeIndex] {
MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME => {
let mut fileName = ECIStringBuilder::default();
codeIndex = textCompaction(codewords, codeIndex + 1, &mut fileName)?;
resultMetadata.setFileName(fileName.to_string());
}
MACRO_PDF417_OPTIONAL_FIELD_SENDER => {
let mut sender = ECIStringBuilder::default();
codeIndex = textCompaction(codewords, codeIndex + 1, &mut sender)?;
resultMetadata.setSender(sender.to_string());
}
MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE => {
let mut addressee = ECIStringBuilder::default();
codeIndex = textCompaction(codewords, codeIndex + 1, &mut addressee)?;
resultMetadata.setAddressee(addressee.to_string());
}
MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT => {
let mut segmentCount = ECIStringBuilder::default();
codeIndex = numericCompaction(codewords, codeIndex + 1, &mut segmentCount)?;
let Ok(parsed_segment_count) = segmentCount.to_string().parse() else {
return Err(Exceptions::FORMAT);
};
resultMetadata.setSegmentCount(parsed_segment_count);
}
MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP => {
let mut timestamp = ECIStringBuilder::default();
codeIndex = numericCompaction(codewords, codeIndex + 1, &mut timestamp)?;
let Ok(parsed_timestamp) = timestamp.to_string().parse() else {
return Err(Exceptions::FORMAT);
};
resultMetadata.setTimestamp(parsed_timestamp);
}
MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM => {
let mut checksum = ECIStringBuilder::default();
codeIndex = numericCompaction(codewords, codeIndex + 1, &mut checksum)?;
let Ok(parsed_checksum) = checksum.to_string().parse() else {
return Err(Exceptions::FORMAT);
};
resultMetadata.setChecksum(parsed_checksum);
}
MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE => {
let mut fileSize = ECIStringBuilder::default();
codeIndex = numericCompaction(codewords, codeIndex + 1, &mut fileSize)?;
let Ok(parsed_file_size) = fileSize.to_string().parse() else {
return Err(Exceptions::FORMAT);
};
resultMetadata.setFileSize(parsed_file_size);
}
_ => return Err(Exceptions::FORMAT),
}
}
MACRO_PDF417_TERMINATOR => {
codeIndex += 1;
resultMetadata.setLastSegment(true);
}
_ => return Err(Exceptions::FORMAT),
}
}
if optionalFieldsStart != -1 {
let mut optionalFieldsLength = codeIndex - optionalFieldsStart as usize;
if resultMetadata.isLastSegment() {
optionalFieldsLength -= 1;
}
resultMetadata.setOptionalData(
codewords[optionalFieldsStart as usize
..(optionalFieldsStart + optionalFieldsLength as isize) as usize]
.to_vec(),
);
}
Ok(codeIndex)
}
fn textCompaction(
codewords: &[u32],
codeIndex: usize,
result: &mut ECIStringBuilder,
) -> Result<usize> {
let mut codeIndex = codeIndex;
let mut textCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2];
let mut byteCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2];
let mut index = 0;
let mut end = false;
let mut subMode = Mode::Alpha;
while (codeIndex < codewords[0] as usize) && !end {
let mut code = codewords[codeIndex];
codeIndex += 1;
match code {
..=PRE_TEXT_COMPACTION_MODE_LATCH => {
textCompactionData[index] = code / 30;
textCompactionData[index + 1] = code % 30;
index += 2;
}
TEXT_COMPACTION_MODE_LATCH => {
textCompactionData[index] = TEXT_COMPACTION_MODE_LATCH;
index += 1;
}
BYTE_COMPACTION_MODE_LATCH
| BYTE_COMPACTION_MODE_LATCH_6
| NUMERIC_COMPACTION_MODE_LATCH
| BEGIN_MACRO_PDF417_CONTROL_BLOCK
| BEGIN_MACRO_PDF417_OPTIONAL_FIELD
| MACRO_PDF417_TERMINATOR => {
codeIndex -= 1;
end = true;
}
MODE_SHIFT_TO_BYTE_COMPACTION_MODE => {
textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE;
code = codewords[codeIndex];
codeIndex += 1;
byteCompactionData[index] = code;
index += 1;
}
ECI_CHARSET => {
subMode = decodeTextCompaction(
&textCompactionData,
&byteCompactionData,
index,
result,
subMode,
)
.ok_or(Exceptions::ILLEGAL_STATE)?;
result.append_eci(Eci::from(codewords[codeIndex]));
codeIndex += 1;
textCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2];
byteCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2];
index = 0;
}
_ => {}
}
}
decodeTextCompaction(
&textCompactionData,
&byteCompactionData,
index,
result,
subMode,
);
Ok(codeIndex)
}
fn decodeTextCompaction(
textCompactionData: &[u32],
byteCompactionData: &[u32],
length: usize,
result: &mut ECIStringBuilder,
startMode: Mode,
) -> Option<Mode> {
let mut subMode = startMode;
let mut priorToShiftMode = startMode;
let mut latchedMode = startMode;
let mut i = 0;
const PRE_PL: u32 = PL - 1;
const PRE_PAL: u32 = PAL - 1;
while i < length {
let subModeCh = textCompactionData[i];
let mut ch = 0 as char;
match subMode {
Mode::Alpha =>
{
match subModeCh {
0..=25 => ch = char::from_u32('A' as u32 + subModeCh)?,
26 => ch = ' ',
LL => {
subMode = Mode::Lower;
latchedMode = subMode;
}
ML => {
subMode = Mode::Mixed;
latchedMode = subMode;
}
PS => {
priorToShiftMode = subMode;
subMode = Mode::PunctShift;
}
MODE_SHIFT_TO_BYTE_COMPACTION_MODE => {
result.append_char(char::from_u32(byteCompactionData[i])?)
}
TEXT_COMPACTION_MODE_LATCH => {
subMode = Mode::Alpha;
latchedMode = subMode;
}
_ => {}
}
}
Mode::Lower =>
{
match subModeCh {
..=25 => ch = char::from_u32('a' as u32 + subModeCh)?,
26 => ch = ' ',
AS => {
priorToShiftMode = subMode;
subMode = Mode::AlphaShift;
}
ML => {
subMode = Mode::Mixed;
latchedMode = subMode;
}
PS => {
priorToShiftMode = subMode;
subMode = Mode::PunctShift;
}
MODE_SHIFT_TO_BYTE_COMPACTION_MODE => {
result.append_char(char::from_u32(byteCompactionData[i])?)
}
TEXT_COMPACTION_MODE_LATCH => {
subMode = Mode::Alpha;
latchedMode = subMode;
}
_ => {}
}
}
Mode::Mixed =>
{
match subModeCh {
0..=PRE_PL => ch = MIXED_CHARS[subModeCh as usize],
PL => {
subMode = Mode::Punct;
latchedMode = subMode;
}
26 => ch = ' ',
LL => {
subMode = Mode::Lower;
latchedMode = subMode;
}
AL | TEXT_COMPACTION_MODE_LATCH => {
subMode = Mode::Alpha;
latchedMode = subMode;
}
PS => {
priorToShiftMode = subMode;
subMode = Mode::PunctShift;
}
MODE_SHIFT_TO_BYTE_COMPACTION_MODE => {
result.append_char(char::from_u32(byteCompactionData[i])?)
}
_ => {}
}
}
Mode::Punct =>
{
match subModeCh {
..=PRE_PAL => ch = PUNCT_CHARS[subModeCh as usize],
PAL | TEXT_COMPACTION_MODE_LATCH => {
subMode = Mode::Alpha;
latchedMode = subMode;
}
MODE_SHIFT_TO_BYTE_COMPACTION_MODE => {
result.append_char(char::from_u32(byteCompactionData[i])?)
}
_ => {}
}
}
Mode::AlphaShift => {
subMode = priorToShiftMode;
match subModeCh {
..=25 => ch = char::from_u32('A' as u32 + subModeCh)?,
26 => ch = ' ',
TEXT_COMPACTION_MODE_LATCH => subMode = Mode::Alpha,
_ => {}
}
}
Mode::PunctShift => {
subMode = priorToShiftMode;
match subModeCh {
..=PRE_PAL => ch = PUNCT_CHARS[subModeCh as usize],
PAL | TEXT_COMPACTION_MODE_LATCH => subMode = Mode::Alpha,
MODE_SHIFT_TO_BYTE_COMPACTION_MODE =>
{
result.append_char(char::from_u32(byteCompactionData[i])?)
}
_ => {}
}
}
}
if ch as u32 != 0 {
result.append_char(ch);
}
i += 1;
}
Some(latchedMode)
}
fn byteCompaction(
mode: u32,
codewords: &[u32],
codeIndex: usize,
result: &mut ECIStringBuilder,
) -> Result<usize> {
let mut end = false;
let mut codeIndex = codeIndex;
while codeIndex < codewords[0] as usize && !end {
while codeIndex < codewords[0] as usize && codewords[codeIndex] == ECI_CHARSET {
codeIndex += 1;
result.append_eci(Eci::from(codewords[codeIndex]));
codeIndex += 1;
}
if codeIndex >= codewords[0] as usize || codewords[codeIndex] >= TEXT_COMPACTION_MODE_LATCH
{
end = true;
} else {
let mut value: u64 = 0;
let mut count = 0;
loop {
value = 900 * value + codewords[codeIndex] as u64;
codeIndex += 1;
count += 1;
if !(count < 5
&& codeIndex < codewords[0] as usize
&& codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH)
{
break;
}
}
if count == 5
&& (mode == BYTE_COMPACTION_MODE_LATCH_6
|| codeIndex < codewords[0] as usize
&& codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH)
{
for i in 0..6 {
result.append_byte((value >> (8 * (5 - i))) as u8);
}
} else {
codeIndex -= count;
while (codeIndex < codewords[0] as usize) && !end {
let code = codewords[codeIndex];
codeIndex += 1;
if code < TEXT_COMPACTION_MODE_LATCH {
result.append_byte(code as u8);
} else if code == ECI_CHARSET {
result.append_eci(Eci::from(codewords[codeIndex]));
codeIndex += 1;
} else {
codeIndex -= 1;
end = true;
}
}
}
}
}
Ok(codeIndex)
}
fn numericCompaction(
codewords: &[u32],
codeIndex: usize,
result: &mut ECIStringBuilder,
) -> Result<usize> {
let mut count = 0;
let mut end = false;
let mut codeIndex = codeIndex;
let mut numericCodewords = [0; MAX_NUMERIC_CODEWORDS];
while codeIndex < codewords[0] as usize && !end {
let code = codewords[codeIndex];
codeIndex += 1;
if codeIndex == codewords[0] as usize {
end = true;
}
match code {
..=PRE_TEXT_COMPACTION_MODE_LATCH => {
numericCodewords[count] = code;
count += 1;
}
TEXT_COMPACTION_MODE_LATCH
| BYTE_COMPACTION_MODE_LATCH
| BYTE_COMPACTION_MODE_LATCH_6
| BEGIN_MACRO_PDF417_CONTROL_BLOCK
| BEGIN_MACRO_PDF417_OPTIONAL_FIELD
| MACRO_PDF417_TERMINATOR
| ECI_CHARSET => {
codeIndex -= 1;
end = true;
}
_ => {}
}
if (count % MAX_NUMERIC_CODEWORDS == 0 || code == NUMERIC_COMPACTION_MODE_LATCH || end)
&& count > 0
{
result.append_string(&decodeBase900toBase10(&numericCodewords, count)?);
count = 0;
}
}
Ok(codeIndex)
}
fn decodeBase900toBase10(codewords: &[u32], count: usize) -> Result<String> {
let mut result = 0.to_biguint().ok_or(Exceptions::ARITHMETIC)?;
for i in 0..count {
result +=
&EXP900[count - i - 1] * (codewords[i].to_biguint().ok_or(Exceptions::ARITHMETIC)?);
}
let resultString = result.to_string();
if !resultString.starts_with('1') {
return Err(Exceptions::FORMAT);
}
Ok(resultString[1..].to_owned())
}