parent
313b93d5b0
commit
aa909de48b
@ -0,0 +1,243 @@
|
||||
use super::{Version, QrSegment, QrSegmentMode, ALPHANUMERIC_CHARSET, QrCode, QrCodeEcc};
|
||||
#[cfg(feature = "kanji")]
|
||||
use super::BitBuffer;
|
||||
|
||||
/// Splits text into optimal segments and encodes kanji segments.
|
||||
pub struct QrSegmentAdvanced {}
|
||||
|
||||
#[cfg(feature = "kanji")]
|
||||
const MODE_TYPES: [QrSegmentMode; 4] = [QrSegmentMode::Byte, QrSegmentMode::Alphanumeric, QrSegmentMode::Numeric, QrSegmentMode::Kanji];
|
||||
#[cfg(not(feature = "kanji"))]
|
||||
const MODE_TYPES: [QrSegmentMode; 3] = [QrSegmentMode::Byte, QrSegmentMode::Alphanumeric, QrSegmentMode::Numeric];
|
||||
#[cfg(feature = "kanji")]
|
||||
const NUM_MODES: usize = 4;
|
||||
#[cfg(not(feature = "kanji"))]
|
||||
const NUM_MODES: usize = 3;
|
||||
|
||||
/// Returns a list of zero or more segments to represent the specified Unicode text string.
|
||||
pub fn make_segments_optimally(code_points: &[char], ecc: QrCodeEcc, min_version: Version, max_version: Version) -> Option<Vec<QrSegment>> {
|
||||
let min_version = min_version.value();
|
||||
let max_version = max_version.value();
|
||||
|
||||
// Check arguments
|
||||
if min_version > max_version {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Iterate through version numbers, and make tentative segments
|
||||
let mut segs = Vec::new();
|
||||
|
||||
for version in min_version..=max_version {
|
||||
if version == min_version || version == 10 || version == 27 {
|
||||
segs = make_segments_optimally_at_version(&code_points, Version::new(version));
|
||||
}
|
||||
let version = Version::new(version);
|
||||
|
||||
// Check if the segments fit
|
||||
let data_capacity_bits = QrCode::get_num_data_codewords(version, ecc) * 8;
|
||||
let data_used_bits = QrSegment::get_total_bits(&segs, version);
|
||||
|
||||
if let Some(data_used_bits) = data_used_bits {
|
||||
if data_used_bits <= data_capacity_bits {
|
||||
return Some(segs); // This version number is found to be suitable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// Returns a new list of segments that is optimal for the given text at the given version number.
|
||||
fn make_segments_optimally_at_version(code_points: &[char], version: Version) -> Vec<QrSegment> {
|
||||
let char_modes = compute_character_modes(code_points, version);
|
||||
split_into_segments(code_points, &char_modes)
|
||||
}
|
||||
|
||||
// Returns a new array representing the optimal mode per code point based on the given text and version.
|
||||
fn compute_character_modes(code_points: &[char], version: Version) -> Vec<QrSegmentMode> {
|
||||
// Segment header sizes, measured in 1/6 bits
|
||||
let mut head_costs = [0usize; NUM_MODES];
|
||||
|
||||
for i in 0..NUM_MODES {
|
||||
head_costs[i] = (4 + MODE_TYPES[i].num_char_count_bits(version) as usize) * 6;
|
||||
}
|
||||
|
||||
// charModes[i][j] represents the mode to encode the code point at index i
|
||||
// such that the final segment ends in modeTypes[j] and the total number of bits is minimized over all possible choices
|
||||
let mut char_modes = vec![[None::<QrSegmentMode>; NUM_MODES]; code_points.len()];
|
||||
|
||||
// At the beginning of each iteration of the loop below,
|
||||
// prevCosts[j] is the exact minimum number of 1/6 bits needed to encode the entire string prefix of length i, and end in modeTypes[j]
|
||||
let mut prev_costs = head_costs.clone();
|
||||
|
||||
// Calculate costs using dynamic programming
|
||||
for i in 0..code_points.len() {
|
||||
let c = code_points[i];
|
||||
let mut cur_costs = [0usize; NUM_MODES];
|
||||
|
||||
{
|
||||
// Always extend a byte mode segment
|
||||
cur_costs[0] = prev_costs[0] + c.len_utf8() * 8 * 6;
|
||||
char_modes[i][0] = Some(MODE_TYPES[0]);
|
||||
}
|
||||
|
||||
// Extend a segment if possible
|
||||
if ALPHANUMERIC_CHARSET.contains(&c) { // Is alphanumeric
|
||||
cur_costs[1] = prev_costs[1] + 33; // 5.5 bits per alphanumeric char
|
||||
char_modes[i][1] = Some(MODE_TYPES[1]);
|
||||
}
|
||||
if '0' <= c && c <= '9' { // Is numeric
|
||||
cur_costs[2] = prev_costs[2] + 20; // 3.33 bits per digit
|
||||
char_modes[i][2] = Some(MODE_TYPES[2]);
|
||||
}
|
||||
if cfg!(feature = "kanji") {
|
||||
if is_kanji(c) {
|
||||
cur_costs[3] = prev_costs[3] + 78; // 13 bits per Shift JIS char
|
||||
char_modes[i][3] = Some(MODE_TYPES[3]);
|
||||
}
|
||||
}
|
||||
|
||||
// Start new segment at the end to switch modes
|
||||
for j in 0..NUM_MODES { // To mode
|
||||
for k in 0..NUM_MODES { // From mode
|
||||
let new_cost = (cur_costs[k] + 5) / 6 * 6 + head_costs[j];
|
||||
if char_modes[i][k].is_some() && (char_modes[i][j].is_none() || new_cost < cur_costs[j]) {
|
||||
cur_costs[j] = new_cost;
|
||||
char_modes[i][j] = Some(MODE_TYPES[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prev_costs = cur_costs;
|
||||
}
|
||||
|
||||
// Find optimal ending mode
|
||||
let mut cur_mode = None::<QrSegmentMode>;
|
||||
|
||||
let mut min_cost = 0;
|
||||
|
||||
for i in 0..NUM_MODES {
|
||||
if cur_mode.is_none() || prev_costs[i] < min_cost {
|
||||
min_cost = prev_costs[i];
|
||||
cur_mode = Some(MODE_TYPES[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let mut cur_mode = cur_mode.unwrap();
|
||||
|
||||
let mut result = vec![QrSegmentMode::Byte; char_modes.len()];
|
||||
|
||||
// Get optimal mode for each code point by tracing backwards
|
||||
for i in (0..char_modes.len()).rev() {
|
||||
for j in 0..NUM_MODES {
|
||||
if MODE_TYPES[j] == cur_mode {
|
||||
cur_mode = char_modes[i][j].unwrap();
|
||||
result[i] = cur_mode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// Returns a new list of segments based on the given text and modes, such that consecutive code points in the same mode are put into the same segment.
|
||||
fn split_into_segments(code_points: &[char], char_modes: &[QrSegmentMode]) -> Vec<QrSegment> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
// Accumulate run of modes
|
||||
let mut cur_mode = char_modes[0];
|
||||
|
||||
let mut start = 0;
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
i += 1;
|
||||
|
||||
if i < code_points.len() && char_modes[i] == cur_mode {
|
||||
continue;
|
||||
}
|
||||
|
||||
let s = &code_points[start..i];
|
||||
|
||||
match cur_mode {
|
||||
QrSegmentMode::Byte => {
|
||||
let s: String = s.iter().collect();
|
||||
let v = s.into_bytes();
|
||||
result.push(QrSegment::make_bytes(&v));
|
||||
}
|
||||
QrSegmentMode::Numeric => {
|
||||
result.push(QrSegment::make_numeric(s));
|
||||
}
|
||||
QrSegmentMode::Alphanumeric => {
|
||||
result.push(QrSegment::make_alphanumeric(s));
|
||||
}
|
||||
QrSegmentMode::Kanji => {
|
||||
if cfg!(feature = "kanji") {
|
||||
result.push(make_kanji(s));
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
|
||||
if i >= code_points.len() {
|
||||
return result;
|
||||
}
|
||||
|
||||
cur_mode = char_modes[i];
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
|
||||
/*---- Kanji mode segment encoder ----*/
|
||||
|
||||
#[cfg(feature = "kanji")]
|
||||
/// Returns a segment representing the specified text string encoded in kanji mode.
|
||||
pub fn make_kanji(code_points: &[char]) -> QrSegment {
|
||||
let mut bb = BitBuffer(Vec::new());
|
||||
|
||||
for &c in code_points {
|
||||
let val = UNICODE_TO_QR_KANJI[c as usize];
|
||||
|
||||
if val == -1 {
|
||||
panic!("String contains non-kanji-mode characters");
|
||||
}
|
||||
|
||||
bb.append_bits(val as u32, 13);
|
||||
}
|
||||
|
||||
QrSegment::new(QrSegmentMode::Kanji, code_points.len(), bb.0)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "kanji"))]
|
||||
fn make_kanji(_: &[char]) -> QrSegment {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "kanji")]
|
||||
/// Tests whether the specified string can be encoded as a segment in kanji mode.
|
||||
pub fn is_encodable_as_kanji(code_points: &[char]) -> bool {
|
||||
for &c in code_points {
|
||||
if !is_kanji(c) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(feature = "kanji")]
|
||||
pub fn is_kanji(c: char) -> bool {
|
||||
let c = c as usize;
|
||||
c < UNICODE_TO_QR_KANJI.len() && UNICODE_TO_QR_KANJI[c] != -1
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "kanji"))]
|
||||
fn is_kanji(_: char) -> bool {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "kanji")]
|
||||
// Load the unpacked the computation-friendly Shift JIS table
|
||||
static UNICODE_TO_QR_KANJI: [i16; 1 << 16] = include!("unicode_to_qr_kanji.json");
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue