#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum EcLevel {
L1,
L2,
L3,
L4,
L5,
}
impl EcLevel {
pub fn number(self) -> usize {
self as usize + 1
}
pub fn from_number(n: usize) -> Option<Self> {
match n {
1 => Some(EcLevel::L1),
2 => Some(EcLevel::L2),
3 => Some(EcLevel::L3),
4 => Some(EcLevel::L4),
5 => Some(EcLevel::L5),
_ => None,
}
}
pub const ALL: [EcLevel; 5] = [
EcLevel::L1,
EcLevel::L2,
EcLevel::L3,
EcLevel::L4,
EcLevel::L5,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Version(u8);
impl Version {
pub fn new(v: u8) -> Option<Self> {
(1..=13).contains(&v).then_some(Version(v))
}
pub fn number(self) -> u8 {
self.0
}
pub fn macromodules_per_dim(self) -> usize {
1 + (self.0 as usize) * 2
}
pub fn size(self) -> usize {
6 * self.macromodules_per_dim()
}
pub fn total_codewords(self) -> usize {
let mpd = self.macromodules_per_dim();
2 * mpd * mpd
}
pub fn data_codewords(self, ec: EcLevel) -> usize {
GM_DATA_CWS[self.0 as usize - 1][ec.number() - 1] as usize
}
}
#[rustfmt::skip]
pub const GM_DATA_CWS: [[u16; 5]; 13] = [
[ 0, 15, 13, 11, 9 ], [ 45, 40, 35, 30, 25 ], [ 89, 79, 69, 59, 49 ], [ 146, 130, 114, 98, 81 ], [ 218, 194, 170, 146, 121 ], [ 305, 271, 237, 203, 169 ], [ 405, 360, 315, 270, 225 ], [ 521, 463, 405, 347, 289 ], [ 650, 578, 506, 434, 361 ], [ 794, 706, 618, 530, 441 ], [ 953, 847, 741, 635, 529 ], [ 1125, 1000, 875, 750, 625 ], [ 1313, 1167, 1021, 875, 729 ], ];
#[rustfmt::skip]
pub const GM_N1: [u8; 13] = [
18, 50, 98, 81, 121, 113, 113, 116, 121, 126, 118, 125, 122,
];
#[rustfmt::skip]
pub const GM_B1: [u8; 13] = [
1, 1, 1, 2, 2, 2, 2, 3, 2, 7, 5, 10, 6,
];
#[rustfmt::skip]
pub const GM_B2: [u8; 13] = [
0, 0, 0, 0, 0, 1, 2, 2, 4, 0, 4, 0, 6,
];
#[rustfmt::skip]
pub const GM_E1B3E2: [[[u8; 3]; 5]; 13] = [
[ [ 0, 0, 0 ], [ 3, 1, 0 ], [ 5, 1, 0 ], [ 7, 1, 0 ], [ 9, 1, 0 ] ], [ [ 5, 1, 0 ], [ 10, 1, 0 ], [ 15, 1, 0 ], [ 20, 1, 0 ], [ 25, 1, 0 ] ], [ [ 9, 1, 0 ], [ 19, 1, 0 ], [ 29, 1, 0 ], [ 39, 1, 0 ], [ 49, 1, 0 ] ], [ [ 8, 2, 0 ], [ 16, 2, 0 ], [ 24, 2, 0 ], [ 32, 2, 0 ], [ 41, 1, 40 ] ], [ [ 12, 2, 0 ], [ 24, 2, 0 ], [ 36, 2, 0 ], [ 48, 2, 0 ], [ 61, 1, 60 ] ], [ [ 11, 3, 0 ], [ 23, 1, 22 ], [ 34, 2, 33 ], [ 45, 3, 0 ], [ 57, 1, 56 ] ], [ [ 12, 1, 11 ], [ 23, 2, 22 ], [ 34, 3, 33 ], [ 45, 4, 0 ], [ 57, 1, 56 ] ], [ [ 12, 2, 11 ], [ 23, 5, 0 ], [ 35, 3, 34 ], [ 47, 1, 46 ], [ 58, 4, 57 ] ], [ [ 12, 6, 0 ], [ 24, 6, 0 ], [ 36, 6, 0 ], [ 48, 6, 0 ], [ 61, 1, 60 ] ], [ [ 13, 4, 12 ], [ 26, 1, 25 ], [ 38, 5, 37 ], [ 51, 2, 50 ], [ 63, 7, 0 ] ], [ [ 12, 6, 11 ], [ 24, 4, 23 ], [ 36, 2, 35 ], [ 47, 9, 0 ], [ 59, 7, 58 ] ], [ [ 13, 5, 12 ], [ 25, 10, 0 ], [ 38, 5, 37 ], [ 50, 10, 0 ], [ 63, 5, 62 ] ], [ [ 13, 1, 12 ], [ 25, 3, 24 ], [ 37, 5, 36 ], [ 49, 7, 48 ], [ 61, 9, 60 ] ], ];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Block {
pub total: usize,
pub ec: usize,
}
impl Block {
pub fn data(self) -> usize {
self.total - self.ec
}
}
pub fn blocks(version: Version, ec: EcLevel) -> Vec<Block> {
let idx = version.number() as usize - 1;
let eidx = ec.number() - 1;
let n1 = GM_N1[idx] as usize;
let n2 = n1 - 1;
let b1 = GM_B1[idx] as usize;
let b2 = GM_B2[idx] as usize;
let e1 = GM_E1B3E2[idx][eidx][0] as usize;
let b3 = GM_E1B3E2[idx][eidx][1] as usize;
let e2 = GM_E1B3E2[idx][eidx][2] as usize;
let num_blocks = b1 + b2;
let mut out = Vec::with_capacity(num_blocks);
for i in 0..num_blocks {
let total = if i < b1 { n1 } else { n2 };
let ec = if i < b3 { e1 } else { e2 };
out.push(Block { total, ec });
}
out
}
pub fn smallest_version_for(data_cw: usize, ec: EcLevel) -> Option<Version> {
(1..=13u8).find_map(|v| {
let ver = Version(v);
let cap = ver.data_codewords(ec);
(cap > 0 && cap >= data_cw).then_some(ver)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn geometry_matches_zint_tables() {
let totals = [
18, 50, 98, 162, 242, 338, 450, 578, 722, 882, 1058, 1250, 1458,
];
for v in 1..=13u8 {
let ver = Version::new(v).unwrap();
assert_eq!(
ver.total_codewords(),
totals[v as usize - 1],
"total codewords for v{v}"
);
assert_eq!(ver.size(), 6 + 12 * v as usize, "size for v{v}");
for ec in EcLevel::ALL {
let blocks = blocks(ver, ec);
if ver.data_codewords(ec) == 0 {
continue; }
let total: usize = blocks.iter().map(|b| b.total).sum();
let data: usize = blocks.iter().map(|b| b.data()).sum();
assert_eq!(total, ver.total_codewords(), "block total v{v} {ec:?}");
assert_eq!(data, ver.data_codewords(ec), "block data v{v} {ec:?}");
assert!(
blocks.iter().all(|b| b.ec <= b.total),
"ec exceeds block v{v} {ec:?}"
);
}
}
}
}