Skip to content

Commit 5be3878

Browse files
committed
Add CLI arguments. Fix run switching byte-to-numeric
1 parent a001d54 commit 5be3878

File tree

5 files changed

+152
-43
lines changed

5 files changed

+152
-43
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"kind": "bin"
2020
}
2121
},
22-
"args": [],
22+
"args": ["[package]\nname = \"qr-generator\"\nversion = \"0.1.0\"\nedition = \"2021\""],
2323
"cwd": "${workspaceFolder}"
2424
},
2525
{

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ edition = "2021"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9+
clap = { version = "4.4.8", features = ["derive"] }
910
qr-generator = { version = "0.1.0", path = "qr-generator" }

qr-generator/src/encoder.rs

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,18 @@ struct DistToNextType {
2323
alpha_numeric: Option<usize>,
2424
kanji: Option<usize>,
2525
byte: Option<usize>,
26-
end: usize
26+
end: usize,
27+
}
28+
impl DistToNextType {
29+
fn run_length(&self) -> usize {
30+
[self.numeric, self.alpha_numeric, self.kanji, self.byte, Some(self.end)].iter().filter_map(|d| {
31+
if let Some(num) = d {
32+
if *num == 0 { None } else { *d }
33+
} else {
34+
None
35+
}
36+
}).min().unwrap()
37+
}
2738
}
2839

2940
pub struct Encoder<'a> {
@@ -73,9 +84,9 @@ impl<'a> Encoder<'a> {
7384
.peekable();
7485
while input_iter.peek().is_some() {
7586
let (next_encoding, mut bit_run, char_count) = match current_encoding {
76-
EncodingModes::Numeric => Self::encode_numeric_run(&mut input_iter),
77-
EncodingModes::AlphaNumeric => self.encode_alphanumeric_run(&mut input_iter),
78-
EncodingModes::Byte => self.encode_byte_run(&mut input_iter),
87+
EncodingModes::Numeric => Self::encode_numeric_run(&mut input_iter, dynamic_mode)?,
88+
EncodingModes::AlphaNumeric => self.encode_alphanumeric_run(&mut input_iter, dynamic_mode)?,
89+
EncodingModes::Byte => self.encode_byte_run(&mut input_iter, dynamic_mode)?,
7990
_ => unreachable!(),
8091
};
8192
self.output_data
@@ -210,7 +221,8 @@ impl<'a> Encoder<'a> {
210221

211222
fn encode_numeric_run<'b, Input>(
212223
input: &mut Peekable<Input>,
213-
) -> (EncodingModes, BitVec<u8, Msb0>, usize)
224+
dynamic: bool
225+
) -> Result<(EncodingModes, BitVec<u8, Msb0>, usize), EncodingError>
214226
where
215227
Input: Iterator<Item = (char, &'b DistToNextType)>,
216228
{
@@ -238,18 +250,22 @@ impl<'a> Encoder<'a> {
238250
}
239251

240252
let next_mode = if let Some(&(c, _)) = input.peek() {
253+
if !dynamic {
254+
return Err(EncodingError::new("Need to change mode, but not dynamic"));
255+
}
241256
Self::char_type(c)
242257
} else {
243258
// Doesn't matter
244259
EncodingModes::Numeric
245260
};
246-
(next_mode, encoded_numbers, char_count)
261+
Ok((next_mode, encoded_numbers, char_count))
247262
}
248263

249264
fn encode_alphanumeric_run<'b, Input>(
250265
&self,
251266
input: &mut Peekable<Input>,
252-
) -> (EncodingModes, BitVec<u8, Msb0>, usize)
267+
dynamic: bool
268+
) -> Result<(EncodingModes, BitVec<u8, Msb0>, usize), EncodingError>
253269
where
254270
Input: Iterator<Item = (char, &'b DistToNextType)>,
255271
{
@@ -259,11 +275,12 @@ impl<'a> Encoder<'a> {
259275
(10..=26) => 15,
260276
(27..) => 17,
261277
};
278+
let should_switch_down = c.is_ascii_digit()
279+
&& distances.alpha_numeric.unwrap_or(usize::MAX)
280+
.min(distances.byte.unwrap_or(usize::MAX))
281+
.min(distances.end) >= min_dist_to_non_num;
262282
Self::is_qr_alphanumeric(c)
263-
&& (!c.is_ascii_digit()
264-
|| distances.alpha_numeric.unwrap_or(usize::MAX)
265-
.min(distances.byte.unwrap_or(usize::MAX)
266-
.min(distances.end)) < min_dist_to_non_num)
283+
&& (!should_switch_down || !dynamic)
267284
});
268285
let mut char_count = 0usize;
269286
let mut encoded_alphanums = bitvec![u8, Msb0;];
@@ -287,44 +304,55 @@ impl<'a> Encoder<'a> {
287304
}
288305

289306
let next_mode = if let Some(&(c, _)) = input.peek() {
307+
if !dynamic {
308+
return Err(EncodingError::new("Need to change mode, but not dynamic"))
309+
}
290310
Self::char_type(c)
291311
} else {
292312
// Doesn't matter
293313
EncodingModes::Numeric
294314
};
295-
(next_mode, encoded_alphanums, char_count)
315+
Ok((next_mode, encoded_alphanums, char_count))
296316
}
297317

298318
fn encode_byte_run<'b, Input>(
299319
&self,
300320
input: &mut Peekable<Input>,
301-
) -> (EncodingModes, BitVec<u8, Msb0>, usize)
321+
dynamic: bool
322+
) -> Result<(EncodingModes, BitVec<u8, Msb0>, usize), EncodingError>
302323
where
303324
Input: Iterator<Item = (char, &'b DistToNextType)>,
304325
{
305-
let bytes = input.peeking_take_while(|&(c, distances)| {
306-
let min_dist_alphanum_to_byte = match self.size_estimate {
307-
(0..=9) => 11,
308-
(10..=26) => 15,
309-
(27..) => 16,
310-
};
311-
let min_dist_num_to_byte = match self.size_estimate {
312-
(0..=9) => 6,
313-
(10..=26) => 8,
314-
(27..) => 9,
315-
};
316-
let min_dist_num_to_alphanum = match self.size_estimate {
317-
(0..=9) => 6,
318-
(10..=26) => 7,
319-
(27..) => 8,
320-
};
321-
322-
let change_to_alphanum = Self::is_qr_alphanumeric(c)
323-
&& distances.byte.unwrap_or(usize::MAX).min(distances.end) >= min_dist_alphanum_to_byte;
324-
let change_to_numeric = c.is_ascii_digit()
326+
let min_dist_alphanum_to_byte = match self.size_estimate {
327+
(0..=9) => 11,
328+
(10..=26) => 15,
329+
(27..) => 16,
330+
};
331+
let min_dist_num_to_byte = match self.size_estimate {
332+
(0..=9) => 6,
333+
(10..=26) => 8,
334+
(27..) => 9,
335+
};
336+
let min_dist_num_to_alphanum = match self.size_estimate {
337+
(0..=9) => 6,
338+
(10..=26) => 7,
339+
(27..) => 8,
340+
};
341+
let change_to_alphanum = |c, distances: &DistToNextType| -> bool {
342+
Self::is_qr_alphanumeric(c)
343+
&& distances.byte.unwrap_or(usize::MAX).min(distances.end) >= min_dist_alphanum_to_byte
344+
};
345+
let change_to_numeric = |c: char, distances: &DistToNextType| -> bool {
346+
// TODO: needs to depend on run length as well!
347+
c.is_ascii_digit()
325348
&& (distances.byte.unwrap_or(usize::MAX).min(distances.end) >= min_dist_num_to_byte
326-
|| distances.alpha_numeric.unwrap_or(usize::MAX).min(distances.end) >= min_dist_num_to_alphanum);
327-
!(change_to_alphanum || change_to_numeric)
349+
|| distances.alpha_numeric.unwrap_or(usize::MAX).min(distances.run_length()).min(distances.end) >= min_dist_num_to_alphanum)
350+
};
351+
352+
let bytes = input.peeking_take_while(|&(c, distances)| {
353+
let should_change_to_alphanum = change_to_alphanum(c, distances);
354+
let should_change_to_numeric = change_to_numeric(c, distances);
355+
!(should_change_to_alphanum || should_change_to_numeric) || !dynamic
328356
});
329357

330358
let mut byte_count = 0usize;
@@ -342,13 +370,22 @@ impl<'a> Encoder<'a> {
342370
}
343371
}
344372

345-
let next_mode = if let Some(&(c, _)) = input.peek() {
346-
Self::char_type(c)
373+
let next_mode = if let Some(&(c, distances)) = input.peek() {
374+
if !dynamic {
375+
return Err(EncodingError::new("Need to change mode, but not dynamic"))
376+
}
377+
if change_to_alphanum(c, distances) {
378+
EncodingModes::AlphaNumeric
379+
} else if change_to_numeric(c, distances) {
380+
EncodingModes::Numeric
381+
} else {
382+
unreachable!()
383+
}
347384
} else {
348385
// Doesn't matter
349386
EncodingModes::Byte
350387
};
351-
(next_mode, encoded_bytes, byte_count)
388+
Ok((next_mode, encoded_bytes, byte_count))
352389
}
353390

354391
fn sequence_preamble(&self, encoding: EncodingModes, char_count: usize) -> BitVec<u8, Msb0> {

src/cli.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
pub use clap::{Parser, ValueEnum};
2+
use qr_generator::{EncodingModes, CorrectionLevels};
3+
4+
macro_rules! bidir_from {
5+
( $owned:ident, $foreign:ident; $( $variant:ident ),+ ) => {
6+
impl From<$foreign> for $owned {
7+
fn from(other: $foreign) -> $owned {
8+
match other {
9+
$(
10+
$foreign::$variant => $owned::$variant
11+
),+
12+
}
13+
}
14+
}
15+
impl From<$owned> for $foreign {
16+
fn from(other: $owned) -> $foreign {
17+
match other {
18+
$(
19+
$owned::$variant => $foreign::$variant
20+
),+
21+
}
22+
}
23+
}
24+
};
25+
}
26+
27+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
28+
pub enum CliEncoding {
29+
#[value(alias("n"), help("(abbrev: n) Data must consist only of digits 0-9. Most compact"))]
30+
Numeric,
31+
#[value(alias("a"), help("(abbrev: a) Data must consist only of 0-9, A-Z (uppercase), $%*+-./:, and <space>"))]
32+
AlphaNumeric,
33+
#[value(alias("b"), help("(abbrev: b) Data will be encoded as bytes. Least compact"))]
34+
Byte,
35+
#[value(skip)] //, alias("k"), help("(abbrev: k) Data will be encoded as Kanji from Shift-JIS"))]
36+
Kanji,
37+
#[value(alias("d"), help("(abbrev: d) Default. Data encoding will change as necessary to obtain the smallest possible code"))]
38+
Dynamic
39+
}
40+
bidir_from!(CliEncoding, EncodingModes; Numeric, AlphaNumeric, Byte, Dynamic, Kanji);
41+
42+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
43+
pub enum CliCorrectionLevels {
44+
#[value(alias("d"), help("(abbrev: d) Only valid for M1 Micro QR Codes"))]
45+
DetectionOnly,
46+
L,
47+
M,
48+
Q,
49+
H,
50+
}
51+
bidir_from!(CliCorrectionLevels, CorrectionLevels; DetectionOnly, L, M, Q, H);
52+
53+
#[derive(Parser)]
54+
#[command(author, about, long_about = None)]
55+
pub struct Cli {
56+
// qr_type: SomeTypeHere,
57+
/// What encoding to use for the data
58+
#[arg(long, short, value_enum, default_value = "dynamic")]
59+
pub encoding: CliEncoding,
60+
#[arg(long, short='l', value_enum, default_value = "q")]
61+
pub correction_level: CliCorrectionLevels,
62+
#[arg(long, short, help = "The 'size' of the QR Code. If omitted, the smallest size that fits will be used")]
63+
pub version: Option<u32>,
64+
#[arg()]
65+
pub data: String
66+
}

src/main.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
use qr_generator::{QRGenerator, QRSymbolTypes, EncodingModes, CorrectionLevels};
22
use std::process;
33

4+
mod cli;
5+
use cli::{Cli, Parser};
6+
47
fn main() {
8+
let cli = Cli::parse();
9+
510
let options = qr_generator::Options {
6-
// mode: Some(EncodingModes::Numeric),
7-
// version: Some(30),
8-
correction_level: Some(CorrectionLevels::H),
11+
mode: Some(EncodingModes::from(cli.encoding)),
12+
version: cli.version,
13+
correction_level: Some(CorrectionLevels::from(cli.correction_level)),
914
..Default::default()
1015
};
1116
let mut generator = QRGenerator { options };
12-
let ret = generator.make_qr_code("Sing, O goddess, the anger of Achilles son of Peleus, that brought countless ills upon the Achaeans.".to_string());
17+
let ret = generator.make_qr_code(cli.data);
1318
if let Err(err) = ret {
1419
println!("save_qr_image failed with {}", err);
1520
process::exit(1);

0 commit comments

Comments
 (0)