use super::encode::{
BIN_LATCH, FNC1, FNC2, FNC3, LATCH_A, LATCH_B_FROM_A, LATCH_BC, UPPER_SHIFT_A, UPPER_SHIFT_B,
is_corner,
};
use super::rs::rsencode;
use super::tables::codeword_for_pattern;
use super::{DotCodeMeta, MAX_SIZE, MIN_SIZE};
use crate::error::{Error, Result};
use crate::output::{BitMatrix, Encoding};
use crate::segment::Segment;
use crate::symbol::{Symbol, SymbolMeta};
use crate::symbology::Symbology;
use crate::traits::Decode;
#[derive(Debug, Default, Clone, Copy)]
pub struct DotCodeDecoder;
impl DotCodeDecoder {
pub fn new() -> Self {
DotCodeDecoder
}
pub fn decode_matrix(&self, matrix: &BitMatrix) -> Result<Symbol> {
let width = matrix.width();
let height = matrix.height();
if !(MIN_SIZE..=MAX_SIZE).contains(&width) || !(MIN_SIZE..=MAX_SIZE).contains(&height) {
return Err(Error::undecodable("DotCode symbol size out of range"));
}
if (width + height) % 2 != 1 {
return Err(Error::undecodable("DotCode width+height must be odd"));
}
let mut grid = vec![false; width * height];
for y in 0..height {
for x in 0..width {
grid[y * width + x] = matrix.get(x, y);
}
}
let stream = unfold_dotstream(&grid, width, height);
if stream.len() < 2 + 9 {
return Err(Error::undecodable("DotCode symbol too small to hold data"));
}
let mask = ((stream[0] as u8) << 1) | (stream[1] as u8);
let mut codewords = Vec::new();
let mut pos = 2;
while pos + 9 <= stream.len() {
let mut pat = 0u16;
for k in 0..9 {
pat = (pat << 1) | stream[pos + k] as u16;
}
match codeword_for_pattern(pat) {
Some(cw) => {
codewords.push(cw);
pos += 9;
}
None => break,
}
}
if codewords.is_empty() {
return Err(Error::undecodable("DotCode has no codewords"));
}
let total = codewords.len();
let data_length = data_length_for(total)
.ok_or_else(|| Error::undecodable("DotCode codeword count is inconsistent"))?;
let ecc_length = total - data_length;
let masked_data = &codewords[..data_length];
let ecc = &codewords[data_length..];
let mut block = Vec::with_capacity(data_length + 1 + ecc_length);
block.push(mask);
block.extend_from_slice(masked_data);
block.extend(std::iter::repeat_n(0u8, ecc_length));
rsencode(data_length + 1, ecc_length, &mut block);
if &block[data_length + 1..] != ecc {
return Err(Error::ErrorCorrectionFailed);
}
let data = unmask(masked_data, mask);
let segments = codewords_to_segments(&data);
let meta = DotCodeMeta {
width,
height,
mask,
codewords: data,
};
Ok(Symbol::new(
Symbology::DotCode,
segments,
SymbolMeta::DotCode(meta),
))
}
}
impl Decode for DotCodeDecoder {
fn decode(&self, encoding: &Encoding) -> Result<Symbol> {
match encoding {
Encoding::Matrix(m) => self.decode_matrix(m),
Encoding::Linear(_) => Err(Error::Unsupported {
what: "DotCode decode of a linear pattern",
}),
}
}
}
fn data_length_for(total: usize) -> Option<usize> {
(1..=total).find(|&dl| dl + 3 + dl / 2 == total)
}
const MASK_WEIGHTS: [u8; 4] = [0, 3, 7, 17];
fn unmask(masked_data: &[u8], mask: u8) -> Vec<u8> {
let step = MASK_WEIGHTS[mask as usize] as u16;
masked_data
.iter()
.enumerate()
.map(|(j, &m)| {
let weight = (step * j as u16) % 113;
((m as u16 + 113 - weight) % 113) as u8
})
.collect()
}
fn unfold_dotstream(grid: &[bool], width: usize, height: usize) -> Vec<bool> {
let n_dots = (height * width) / 2;
let mut stream = vec![false; n_dots];
let mut pos = 0usize;
if height & 1 == 1 {
for row in 0..height {
for column in 0..width {
if (column + row) & 1 == 0 && !is_corner(column, row, width, height) {
stream[pos] = grid[(height - row - 1) * width + column];
pos += 1;
}
}
}
for &idx in &[
width - 2,
height * width - 2,
width * 2 - 1,
(height - 1) * width - 1,
0,
(height - 1) * width,
] {
stream[pos] = grid[idx];
pos += 1;
}
} else {
for column in 0..width {
for row in 0..height {
if (column + row) & 1 == 0 && !is_corner(column, row, width, height) {
stream[pos] = grid[row * width + column];
pos += 1;
}
}
}
for &idx in &[
(height - 1) * width - 1,
(height - 2) * width,
height * width - 2,
(height - 1) * width + 1,
width - 1,
0,
] {
stream[pos] = grid[idx];
pos += 1;
}
}
stream
}
const GS: u8 = 0x1D;
fn codewords_to_segments(cw: &[u8]) -> Vec<Segment> {
let mut bytes: Vec<u8> = Vec::new();
let mut segments: Vec<Segment> = Vec::new();
let mut mode = b'C';
let mut i = 0usize;
if i < cw.len() && (cw[i] == FNC3 || cw[i] == FNC1) {
i += 1;
}
let flush = |bytes: &mut Vec<u8>, segments: &mut Vec<Segment>| {
if !bytes.is_empty() {
segments.push(Segment::byte(std::mem::take(bytes)));
}
};
while i < cw.len() {
let v = cw[i];
match mode {
b'C' => {
if v < 100 {
bytes.push(b'0' + v / 10);
bytes.push(b'0' + v % 10);
i += 1;
} else if v == 100 {
bytes.push(b'1');
bytes.push(b'7');
for k in 0..3 {
if let Some(&p) = cw.get(i + 1 + k) {
bytes.push(b'0' + p / 10);
bytes.push(b'0' + p % 10);
}
}
bytes.push(b'1');
bytes.push(b'0');
i += 4;
} else if v == LATCH_A {
mode = b'A';
i += 1;
} else if (102..=105).contains(&v) {
let nx = (v - 101) as usize;
i += 1;
for _ in 0..nx {
if let Some(&b) = cw.get(i) {
emit_b(&mut bytes, b);
i += 1;
}
}
} else if v == LATCH_BC {
mode = b'B';
i += 1;
} else if v == FNC1 {
bytes.push(GS);
i += 1;
} else if v == FNC2 {
i = read_eci(cw, i + 1, &mut bytes, &mut segments);
} else if v == UPPER_SHIFT_A {
if let Some(&b) = cw.get(i + 1) {
bytes.push(b + 64);
}
i += 2;
} else if v == UPPER_SHIFT_B {
if let Some(&b) = cw.get(i + 1) {
bytes.push(b + 160);
}
i += 2;
} else if v == BIN_LATCH {
i += 1;
i = decode_binary(cw, i, &mut bytes, &mut segments, &mut mode);
} else {
i += 1;
}
}
b'B' => {
if v <= 95 {
bytes.push(v + 32);
i += 1;
} else if v == 96 {
bytes.push(13);
bytes.push(10);
i += 1;
} else if (97..=100).contains(&v) {
bytes.push(match v {
97 => 9,
98 => 28,
99 => 29,
_ => 30,
});
i += 1;
} else if v == LATCH_A {
if let Some(&b) = cw.get(i + 1) {
emit_a(&mut bytes, b);
}
i += 2;
} else if v == LATCH_B_FROM_A {
mode = b'A';
i += 1;
} else if (103..=105).contains(&v) {
let nx = (v - 101) as usize;
i += 1;
for _ in 0..nx {
if let Some(&p) = cw.get(i) {
bytes.push(b'0' + p / 10);
bytes.push(b'0' + p % 10);
i += 1;
}
}
} else if v == LATCH_BC {
mode = b'C';
i += 1;
} else if v == FNC1 {
bytes.push(GS);
i += 1;
} else if v == FNC2 {
i = read_eci(cw, i + 1, &mut bytes, &mut segments);
} else if v == UPPER_SHIFT_A {
if let Some(&b) = cw.get(i + 1) {
bytes.push(b + 64);
}
i += 2;
} else if v == UPPER_SHIFT_B {
if let Some(&b) = cw.get(i + 1) {
bytes.push(b + 160);
}
i += 2;
} else if v == BIN_LATCH {
i += 1;
i = decode_binary(cw, i, &mut bytes, &mut segments, &mut mode);
} else {
i += 1;
}
}
b'A' => {
if v <= 95 {
emit_a(&mut bytes, v);
i += 1;
} else if (96..=101).contains(&v) {
let nx = (v - 95) as usize;
i += 1;
for _ in 0..nx {
if let Some(&b) = cw.get(i) {
emit_b(&mut bytes, b);
i += 1;
}
}
} else if v == LATCH_B_FROM_A {
mode = b'B';
i += 1;
} else if (103..=105).contains(&v) {
let nx = (v - 101) as usize;
i += 1;
for _ in 0..nx {
if let Some(&p) = cw.get(i) {
bytes.push(b'0' + p / 10);
bytes.push(b'0' + p % 10);
i += 1;
}
}
} else if v == LATCH_BC {
mode = b'C';
i += 1;
} else if v == FNC1 {
bytes.push(GS);
i += 1;
} else if v == FNC2 {
i = read_eci(cw, i + 1, &mut bytes, &mut segments);
} else if v == UPPER_SHIFT_A {
if let Some(&b) = cw.get(i + 1) {
bytes.push(b + 64);
}
i += 2;
} else if v == UPPER_SHIFT_B {
if let Some(&b) = cw.get(i + 1) {
bytes.push(b + 160);
}
i += 2;
} else if v == BIN_LATCH {
i += 1;
i = decode_binary(cw, i, &mut bytes, &mut segments, &mut mode);
} else {
i += 1;
}
}
_ => unreachable!("mode X handled inside decode_binary"),
}
}
flush(&mut bytes, &mut segments);
segments
}
fn emit_a(bytes: &mut Vec<u8>, v: u8) {
if v < 64 {
bytes.push(v + 32);
} else {
bytes.push(v - 64);
}
}
fn emit_b(bytes: &mut Vec<u8>, v: u8) {
if v <= 95 {
bytes.push(v + 32);
} else if v == 96 {
bytes.push(13);
bytes.push(10);
} else {
bytes.push(match v {
97 => 9,
98 => 28,
99 => 29,
_ => 30,
});
}
}
fn read_eci(cw: &[u8], start: usize, bytes: &mut Vec<u8>, segments: &mut Vec<Segment>) -> usize {
let (eci, next) = match cw.get(start) {
Some(&first) if first < 40 => (first as u32, start + 1),
Some(&a) => {
let b = cw.get(start + 1).copied().unwrap_or(0) as u32;
let c = cw.get(start + 2).copied().unwrap_or(0) as u32;
((a as u32 - 40) * 12769 + b * 113 + c + 40, start + 3)
}
None => return start,
};
if !bytes.is_empty() {
segments.push(Segment::byte(std::mem::take(bytes)));
}
segments.push(Segment::eci(eci));
next
}
fn decode_binary(
cw: &[u8],
start: usize,
bytes: &mut Vec<u8>,
segments: &mut Vec<Segment>,
mode: &mut u8,
) -> usize {
let mut i = start;
let mut group: Vec<u8> = Vec::new();
loop {
if i >= cw.len() {
flush_binary(&group, bytes, segments);
*mode = b'C';
return i;
}
let v = cw[i];
if v <= 102 {
group.push(v);
i += 1;
continue;
}
flush_binary(&group, bytes, segments);
group.clear();
if (103..=108).contains(&v) {
let n = (v - 101) as usize;
i += 1;
for _ in 0..n {
if let Some(&p) = cw.get(i) {
bytes.push(b'0' + p / 10);
bytes.push(b'0' + p % 10);
i += 1;
}
}
continue;
}
i += 1;
*mode = match v {
FNC3 => b'A', UPPER_SHIFT_A => b'B', _ => b'C', };
return i;
}
}
fn flush_binary(group: &[u8], bytes: &mut Vec<u8>, segments: &mut Vec<Segment>) {
if group.is_empty() {
return;
}
let mut values: Vec<u16> = Vec::new();
let mut idx = 0;
while idx < group.len() {
let m = (group.len() - idx).min(6);
if m < 2 {
break; }
let mut buf: u64 = 0;
for &c in &group[idx..idx + m] {
buf = buf * 103 + c as u64;
}
let size = m - 1;
let mut lsf = Vec::with_capacity(size);
for _ in 0..size {
lsf.push((buf % 259) as u16);
buf /= 259;
}
lsf.reverse();
values.extend(lsf);
idx += m;
}
let mut k = 0;
while k < values.len() {
let v = values[k];
match v {
0..=255 => {
bytes.push(v as u8);
k += 1;
}
256 => {
let eci = values.get(k + 1).copied().unwrap_or(0) as u32;
flush_eci(eci, bytes, segments);
k += 2;
}
257 => {
let hi = values.get(k + 1).copied().unwrap_or(0) as u32;
let lo = values.get(k + 2).copied().unwrap_or(0) as u32;
flush_eci((hi << 8) | lo, bytes, segments);
k += 3;
}
_ => {
let a = values.get(k + 1).copied().unwrap_or(0) as u32;
let b = values.get(k + 2).copied().unwrap_or(0) as u32;
let c = values.get(k + 3).copied().unwrap_or(0) as u32;
flush_eci((a << 16) | (b << 8) | c, bytes, segments);
k += 4;
}
}
}
}
fn flush_eci(eci: u32, bytes: &mut Vec<u8>, segments: &mut Vec<Segment>) {
if !bytes.is_empty() {
segments.push(Segment::byte(std::mem::take(bytes)));
}
segments.push(Segment::eci(eci));
}