diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 2276c19..1159c36 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -52,6 +52,82 @@ pub struct QrCode { impl QrCode { + pub fn encode_text(text: &[char], ecl: &'static QrCodeEcc) -> QrCode { + let segs: Vec = QrSegment::make_segments(text); + QrCode::encode_segments(&segs, ecl) + } + + + pub fn encode_binary(data: &[u8], ecl: &'static QrCodeEcc) -> QrCode { + let segs: Vec = vec![QrSegment::make_bytes(data)]; + QrCode::encode_segments(&segs, ecl) + } + + + pub fn encode_segments(segs: &[QrSegment], ecl: &'static QrCodeEcc) -> QrCode { + QrCode::encode_segments_advanced(segs, ecl, 1, 40, -1, true) + } + + + pub fn encode_segments_advanced(segs: &[QrSegment], mut ecl: &'static QrCodeEcc, + minversion: u8, maxversion: u8, mask: i8, boostecl: bool) -> QrCode { + assert!(1 <= minversion && minversion <= maxversion && maxversion <= 40 && -1 <= mask && mask <= 7, "Invalid value"); + + // Find the minimal version number to use + let mut version: u8 = minversion; + let mut datausedbits: usize; + loop { + let datacapacitybits: usize = QrCode::get_num_data_codewords(version, ecl) * 8; // Number of data bits available + if let Some(n) = QrSegment::get_total_bits(segs, version) { + if n <= datacapacitybits { + datausedbits = n; + break; // This version number is found to be suitable + } + } + if version >= maxversion { // All versions in the range could not fit the given data + panic!("Data too long"); + } + version += 1; + } + + // Increase the error correction level while the data still fits in the current version number + if boostecl { + if datausedbits <= QrCode::get_num_data_codewords(version, &QrCodeEcc_MEDIUM ) * 8 { ecl = &QrCodeEcc_MEDIUM ; } + if datausedbits <= QrCode::get_num_data_codewords(version, &QrCodeEcc_QUARTILE) * 8 { ecl = &QrCodeEcc_QUARTILE; } + if datausedbits <= QrCode::get_num_data_codewords(version, &QrCodeEcc_HIGH ) * 8 { ecl = &QrCodeEcc_HIGH ; } + } + + // Create the data bit string by concatenating all segments + let datacapacitybits: usize = QrCode::get_num_data_codewords(version, ecl) * 8; + let mut bb: Vec = Vec::new(); + for seg in segs { + append_bits(&mut bb, seg.mode.modebits as u32, 4); + append_bits(&mut bb, seg.numchars as u32, seg.mode.num_char_count_bits(version)); + bb.extend_from_slice(&seg.data); + } + + // Add terminator and pad up to a byte if applicable + let numzerobits = std::cmp::min(4, datacapacitybits - bb.len()) + (bb.len().wrapping_neg() & 7); + append_bits(&mut bb, 0, numzerobits as u8); + + // Pad with alternate bytes until data capacity is reached + let mut padbyte: u32 = 0xEC; + while bb.len() < datacapacitybits { + append_bits(&mut bb, padbyte, 8); + padbyte ^= 0xEC ^ 0x11; + } + assert_eq!(bb.len() % 8, 0, "Assertion error"); + + let mut bytes: Vec = vec![0; (bb.len() + 7) / 8]; + for (i, bit) in bb.iter().enumerate() { + bytes[i >> 3] |= (*bit as u8) << (7 - (i & 7)); + } + + // Create the QR Code symbol + QrCode::encode_codewords(version, ecl, &bytes, mask) + } + + pub fn encode_codewords(ver: u8, ecl: &'static QrCodeEcc, datacodewords: &[u8], mask: i8) -> QrCode { // Check arguments assert!(1 <= ver && ver <= 40 && -1 <= mask && mask <= 7, "Value out of range"); @@ -682,6 +758,8 @@ pub struct QrSegment { impl QrSegment { + /*---- Static factory functions ----*/ + pub fn make_bytes(data: &[u8]) -> QrSegment { let mut bb: Vec = Vec::with_capacity(data.len() * 8); for b in data { @@ -696,9 +774,129 @@ impl QrSegment { } } + + pub fn make_numeric(text: &[char]) -> QrSegment { + let mut bb: Vec = Vec::with_capacity(text.len() * 3 + (text.len() + 2) / 3); + let mut accumdata: u32 = 0; + let mut accumcount: u32 = 0; + for c in text { + assert!('0' <= *c && *c <= '9', "String contains non-numeric characters"); + accumdata = accumdata * 10 + ((*c as u32) - ('0' as u32)); + accumcount += 1; + if accumcount == 3 { + append_bits(&mut bb, accumdata, 10); + accumdata = 0; + accumcount = 0; + } + } + if accumcount > 0 { // 1 or 2 digits remaining + append_bits(&mut bb, accumdata, (accumcount as u8) * 3 + 1); + } + QrSegment { + mode: &QrSegmentMode_NUMERIC, + numchars: text.len(), + data: bb, + } + } + + + pub fn make_alphanumeric(text: &[char]) -> QrSegment { + let mut bb: Vec = Vec::with_capacity(text.len() * 5 + (text.len() + 1) / 2); + let mut accumdata: u32 = 0; + let mut accumcount: u32 = 0; + for c in text { + let i = match QrSegment_ALPHANUMERIC_CHARSET.iter().position(|x| *x == *c) { + None => panic!("String contains unencodable characters in alphanumeric mode"), + Some(j) => j, + }; + accumdata = accumdata * 45 + (i as u32); + accumcount += 1; + if accumcount == 2 { + append_bits(&mut bb, accumdata, 11); + accumdata = 0; + accumcount = 0; + } + } + if accumcount > 0 { // 1 character remaining + append_bits(&mut bb, accumdata, 6); + } + QrSegment { + mode: &QrSegmentMode_ALPHANUMERIC, + numchars: text.len(), + data: bb, + } + } + + + pub fn make_segments(text: &[char]) -> Vec { + if text.is_empty() { + vec![] + } else if QrSegment::is_numeric(text) { + vec![QrSegment::make_numeric(text)] + } else if QrSegment::is_alphanumeric(text) { + vec![QrSegment::make_alphanumeric(text)] + } else { + let s: String = text.iter().cloned().collect(); + vec![QrSegment::make_bytes(s.as_bytes())] + } + } + + + pub fn make_eci(assignval: u32) -> QrSegment { + let mut bb: Vec = Vec::with_capacity(24); + if assignval < (1 << 7) { + append_bits(&mut bb, assignval, 8); + } else if assignval < (1 << 14) { + append_bits(&mut bb, 2, 2); + append_bits(&mut bb, assignval, 14); + } else if assignval < 1_000_000 { + append_bits(&mut bb, 6, 3); + append_bits(&mut bb, assignval, 21); + } else { + panic!("ECI assignment value out of range"); + } + QrSegment { + mode: &QrSegmentMode_ECI, + numchars: 0, + data: bb, + } + } + + + fn get_total_bits(segs: &[QrSegment], version: u8) -> Option { + assert!(1 <= version && version <= 40, "Version number out of range"); + let mut result: usize = 0; + for seg in segs { + let ccbits = seg.mode.num_char_count_bits(version); + if seg.numchars >= 1 << ccbits { + return None; + } + match result.checked_add(4 + (ccbits as usize) + seg.data.len()) { + None => return None, + Some(val) => result = val, + } + } + Some(result) + } + + + fn is_alphanumeric(text: &[char]) -> bool { + text.iter().all(|c| QrSegment_ALPHANUMERIC_CHARSET.contains(c)) + } + + + fn is_numeric(text: &[char]) -> bool { + text.iter().all(|c| '0' <= *c && *c <= '9') + } + } +static QrSegment_ALPHANUMERIC_CHARSET: [char; 45] = ['0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + ' ','$','%','*','+','-','.','/',':']; + + /*---- QrSegmentMode functionality ----*/