#[cfg(any(feature = "qr", feature = "rmqr"))]
use alloc::borrow::Cow;
#[cfg(feature = "qr")]
use alloc::collections::BTreeMap;
#[cfg(feature = "qr")]
use alloc::vec;
use alloc::{string::String, vec::Vec};
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
use core::ops::RangeInclusive;
mod bits;
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
mod reed_solomon;
#[cfg(feature = "micro-qr")]
mod micro;
#[cfg(feature = "qr")]
mod model2;
#[cfg(any(feature = "qr", feature = "rmqr"))]
mod optimizer;
#[cfg(feature = "rmqr")]
mod rmqr;
use bits::BitBuffer;
use crate::EncodeError;
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
#[inline]
const fn numeric_character_capacity(capacity_bits: usize, overhead_bits: usize) -> usize {
let payload_bits = capacity_bits.saturating_sub(overhead_bits);
payload_bits / 10 * 3
+ match payload_bits % 10 {
4..=6 => 1,
7..=9 => 2,
_ => 0,
}
}
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
#[inline]
const fn ensure_input_length(
length: usize,
per_symbol_capacity: usize,
capacity_bits: usize,
symbol_count: usize,
) -> Result<(), EncodeError> {
if length > per_symbol_capacity.saturating_mul(symbol_count) {
Err(EncodeError::DataTooLong {
required_bits: None,
capacity_bits: capacity_bits.saturating_mul(symbol_count),
})
} else {
Ok(())
}
}
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum SymbolErrorCorrection {
#[cfg(feature = "micro-qr")]
DetectionOnly,
Low,
Medium,
Quartile,
#[cfg(any(feature = "qr", feature = "rmqr"))]
High,
}
#[cfg(feature = "qr")]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum QrErrorCorrection {
Low,
Medium,
Quartile,
High,
}
#[cfg(feature = "qr")]
impl QrErrorCorrection {
#[inline]
pub(crate) const fn qr_ordinal(self) -> usize {
match self {
Self::Low => 0,
Self::Medium => 1,
Self::Quartile => 2,
Self::High => 3,
}
}
#[inline]
pub(crate) const fn qr_format_bits(self) -> u8 {
match self {
Self::Low => 1,
Self::Medium => 0,
Self::Quartile => 3,
Self::High => 2,
}
}
}
#[cfg(feature = "qr")]
impl From<QrErrorCorrection> for SymbolErrorCorrection {
#[inline]
fn from(value: QrErrorCorrection) -> Self {
match value {
QrErrorCorrection::Low => Self::Low,
QrErrorCorrection::Medium => Self::Medium,
QrErrorCorrection::Quartile => Self::Quartile,
QrErrorCorrection::High => Self::High,
}
}
}
#[cfg(feature = "micro-qr")]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum MicroErrorCorrection {
DetectionOnly,
Low,
Medium,
Quartile,
}
#[cfg(feature = "micro-qr")]
impl From<MicroErrorCorrection> for SymbolErrorCorrection {
#[inline]
fn from(value: MicroErrorCorrection) -> Self {
match value {
MicroErrorCorrection::DetectionOnly => Self::DetectionOnly,
MicroErrorCorrection::Low => Self::Low,
MicroErrorCorrection::Medium => Self::Medium,
MicroErrorCorrection::Quartile => Self::Quartile,
}
}
}
#[cfg(feature = "rmqr")]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum RmqrErrorCorrection {
Medium,
High,
}
#[cfg(feature = "rmqr")]
impl From<RmqrErrorCorrection> for SymbolErrorCorrection {
#[inline]
fn from(value: RmqrErrorCorrection) -> Self {
match value {
RmqrErrorCorrection::Medium => Self::Medium,
RmqrErrorCorrection::High => Self::High,
}
}
}
#[cfg(feature = "qr")]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct QrVersion(u8);
#[cfg(feature = "qr")]
impl QrVersion {
pub const MAX: Self = Self(40);
pub const MIN: Self = Self(1);
#[inline]
pub const fn new(value: u8) -> Result<Self, EncodeError> {
if value >= 1 && value <= 40 {
Ok(Self(value))
} else {
Err(EncodeError::InvalidVersionRange)
}
}
#[inline]
pub const fn value(self) -> u8 {
self.0
}
}
#[cfg(feature = "micro-qr")]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum MicroVersion {
M1,
M2,
M3,
M4,
}
#[cfg(feature = "micro-qr")]
impl MicroVersion {
#[inline]
pub const fn size(self) -> usize {
match self {
Self::M1 => 11,
Self::M2 => 13,
Self::M3 => 15,
Self::M4 => 17,
}
}
}
#[cfg(feature = "rmqr")]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(u8)]
pub enum RmqrVersion {
R7x43,
R7x59,
R7x77,
R7x99,
R7x139,
R9x43,
R9x59,
R9x77,
R9x99,
R9x139,
R11x27,
R11x43,
R11x59,
R11x77,
R11x99,
R11x139,
R13x27,
R13x43,
R13x59,
R13x77,
R13x99,
R13x139,
R15x43,
R15x59,
R15x77,
R15x99,
R15x139,
R17x43,
R17x59,
R17x77,
R17x99,
R17x139,
}
#[cfg(feature = "rmqr")]
impl RmqrVersion {
pub(crate) const ALL: [Self; 32] = [
Self::R7x43,
Self::R7x59,
Self::R7x77,
Self::R7x99,
Self::R7x139,
Self::R9x43,
Self::R9x59,
Self::R9x77,
Self::R9x99,
Self::R9x139,
Self::R11x27,
Self::R11x43,
Self::R11x59,
Self::R11x77,
Self::R11x99,
Self::R11x139,
Self::R13x27,
Self::R13x43,
Self::R13x59,
Self::R13x77,
Self::R13x99,
Self::R13x139,
Self::R15x43,
Self::R15x59,
Self::R15x77,
Self::R15x99,
Self::R15x139,
Self::R17x43,
Self::R17x59,
Self::R17x77,
Self::R17x99,
Self::R17x139,
];
pub(crate) const ALL_BY_AREA: [Self; 32] = Self::sorted_by_area();
pub const MAX: Self = Self::R17x139;
pub const MIN: Self = Self::R7x43;
const fn sorted_by_area() -> [Self; 32] {
let mut versions = Self::ALL;
let length = versions.len();
let mut index = 0;
while index < length {
let mut smallest = index;
let mut candidate = index + 1;
while candidate < length {
if versions[candidate].area_key() < versions[smallest].area_key() {
smallest = candidate;
}
candidate += 1;
}
let swap = versions[index];
versions[index] = versions[smallest];
versions[smallest] = swap;
index += 1;
}
versions
}
const fn area_key(self) -> u64 {
let width = self.width() as u64;
let height = self.height() as u64;
(width * height) * 1_000_000 + height * 1_000 + width
}
#[inline]
pub const fn value(self) -> u8 {
self as u8
}
#[inline]
pub const fn width(self) -> usize {
match self {
Self::R11x27 | Self::R13x27 => 27,
Self::R7x43
| Self::R9x43
| Self::R11x43
| Self::R13x43
| Self::R15x43
| Self::R17x43 => 43,
Self::R7x59
| Self::R9x59
| Self::R11x59
| Self::R13x59
| Self::R15x59
| Self::R17x59 => 59,
Self::R7x77
| Self::R9x77
| Self::R11x77
| Self::R13x77
| Self::R15x77
| Self::R17x77 => 77,
Self::R7x99
| Self::R9x99
| Self::R11x99
| Self::R13x99
| Self::R15x99
| Self::R17x99 => 99,
Self::R7x139
| Self::R9x139
| Self::R11x139
| Self::R13x139
| Self::R15x139
| Self::R17x139 => 139,
}
}
#[inline]
pub const fn height(self) -> usize {
match self {
Self::R7x43 | Self::R7x59 | Self::R7x77 | Self::R7x99 | Self::R7x139 => 7,
Self::R9x43 | Self::R9x59 | Self::R9x77 | Self::R9x99 | Self::R9x139 => 9,
Self::R11x27
| Self::R11x43
| Self::R11x59
| Self::R11x77
| Self::R11x99
| Self::R11x139 => 11,
Self::R13x27
| Self::R13x43
| Self::R13x59
| Self::R13x77
| Self::R13x99
| Self::R13x139 => 13,
Self::R15x43 | Self::R15x59 | Self::R15x77 | Self::R15x99 | Self::R15x139 => 15,
Self::R17x43 | Self::R17x59 | Self::R17x77 | Self::R17x99 | Self::R17x139 => 17,
}
}
}
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum SymbolVersion {
#[cfg(feature = "qr")]
Qr(QrVersion),
#[cfg(feature = "micro-qr")]
Micro(MicroVersion),
#[cfg(feature = "rmqr")]
Rmqr(RmqrVersion),
}
#[cfg(feature = "qr")]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct QrMask(u8);
#[cfg(feature = "qr")]
impl QrMask {
#[inline]
pub const fn new(value: u8) -> Result<Self, EncodeError> {
if value < 8 { Ok(Self(value)) } else { Err(EncodeError::InvalidMask) }
}
#[inline]
pub const fn value(self) -> u8 {
self.0
}
}
#[cfg(feature = "micro-qr")]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct MicroMask(u8);
#[cfg(feature = "micro-qr")]
impl MicroMask {
#[inline]
pub const fn new(value: u8) -> Result<Self, EncodeError> {
if value < 4 { Ok(Self(value)) } else { Err(EncodeError::InvalidMask) }
}
#[inline]
pub const fn value(self) -> u8 {
self.0
}
}
#[cfg(any(feature = "qr", feature = "rmqr"))]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct EciAssignment(u32);
#[cfg(any(feature = "qr", feature = "rmqr"))]
impl EciAssignment {
pub const ISO_8859_1: Self = Self(3);
pub const SHIFT_JIS: Self = Self(20);
pub const UTF_8: Self = Self(26);
#[inline]
pub const fn new(value: u32) -> Result<Self, EncodeError> {
if value <= 999_999 {
Ok(Self(value))
} else {
Err(EncodeError::InvalidEciAssignment(value))
}
}
#[inline]
pub const fn value(self) -> u32 {
self.0
}
}
#[cfg(any(feature = "qr", feature = "rmqr"))]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ApplicationIndicator(u8);
#[cfg(any(feature = "qr", feature = "rmqr"))]
impl ApplicationIndicator {
#[inline]
pub const fn numeric(value: u8) -> Result<Self, EncodeError> {
if value <= 99 { Ok(Self(value)) } else { Err(EncodeError::InvalidApplicationIndicator) }
}
#[inline]
pub const fn letter(value: char) -> Result<Self, EncodeError> {
if value.is_ascii_alphabetic() {
Ok(Self(value as u8 + 100))
} else {
Err(EncodeError::InvalidApplicationIndicator)
}
}
#[inline]
pub const fn value(self) -> u8 {
self.0
}
}
#[cfg(any(feature = "qr", feature = "rmqr"))]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Fnc1 {
Gs1,
Industry(ApplicationIndicator),
}
#[cfg(feature = "qr")]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct StructuredAppendInfo {
index: u8,
total: u8,
parity: u8,
}
#[cfg(feature = "qr")]
impl StructuredAppendInfo {
#[inline]
pub const fn index(self) -> u8 {
self.index
}
#[inline]
pub const fn total(self) -> u8 {
self.total
}
#[inline]
pub const fn parity(self) -> u8 {
self.parity
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum Mode {
Numeric,
Alphanumeric,
Byte,
#[cfg_attr(not(feature = "kanji"), allow(dead_code))]
Kanji,
#[cfg_attr(not(any(feature = "qr", feature = "rmqr")), allow(dead_code))]
Eci,
}
impl Mode {
#[cfg(feature = "qr")]
#[inline]
const fn qr_bits(self) -> u8 {
match self {
Self::Numeric => 0b0001,
Self::Alphanumeric => 0b0010,
Self::Byte => 0b0100,
Self::Kanji => 0b1000,
Self::Eci => 0b0111,
}
}
#[cfg(feature = "qr")]
#[inline]
const fn cci_bits(self, version: QrVersion) -> u8 {
let group = version_group(version);
match self {
Self::Numeric => [10, 12, 14][group],
Self::Alphanumeric => [9, 11, 13][group],
Self::Byte => [8, 16, 16][group],
Self::Kanji => [8, 10, 12][group],
Self::Eci => 0,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Segment {
mode: Mode,
character_count: usize,
bits: BitBuffer,
source: Vec<u8>,
}
impl Segment {
pub fn numeric(data: impl AsRef<str>) -> Result<Self, EncodeError> {
let data = data.as_ref();
let mut bits = BitBuffer::with_capacity(data.len() * 10 / 3 + 4);
let bytes = data.as_bytes();
for (offset, byte) in bytes.iter().enumerate() {
if !byte.is_ascii_digit() {
return Err(EncodeError::InvalidData {
mode: "numeric",
byte_offset: offset,
});
}
}
for chunk in bytes.chunks(3) {
let value = chunk.iter().fold(0, |value, byte| value * 10 + u32::from(byte - b'0'));
bits.append(value, [0, 4, 7, 10][chunk.len()]);
}
Ok(Self {
mode: Mode::Numeric,
character_count: bytes.len(),
bits,
source: bytes.to_vec(),
})
}
pub fn alphanumeric(data: impl AsRef<str>) -> Result<Self, EncodeError> {
let data = data.as_ref();
let mut values = Vec::with_capacity(data.len());
for (offset, byte) in data.bytes().enumerate() {
let Some(value) = alphanumeric_value(byte) else {
return Err(EncodeError::InvalidData {
mode: "alphanumeric",
byte_offset: offset,
});
};
values.push(value);
}
let mut bits = BitBuffer::with_capacity(values.len() * 11 / 2 + 6);
for chunk in values.chunks(2) {
if chunk.len() == 2 {
bits.append(u32::from(chunk[0]) * 45 + u32::from(chunk[1]), 11);
} else {
bits.append(u32::from(chunk[0]), 6);
}
}
Ok(Self {
mode: Mode::Alphanumeric,
character_count: values.len(),
bits,
source: data.as_bytes().to_vec(),
})
}
#[cfg(any(feature = "qr", feature = "rmqr"))]
fn fnc1_alphanumeric(data: &[u8]) -> Result<Self, EncodeError> {
let mut values = Vec::with_capacity(data.len());
for (offset, &byte) in data.iter().enumerate() {
if byte == 0x1D {
values.push(alphanumeric_value(b'%').expect("percent is alphanumeric"));
} else if byte == b'%' {
let value = alphanumeric_value(byte).expect("percent is alphanumeric");
values.extend_from_slice(&[value, value]);
} else if let Some(value) = alphanumeric_value(byte) {
values.push(value);
} else {
return Err(EncodeError::InvalidData {
mode: "FNC1 alphanumeric",
byte_offset: offset,
});
}
}
let mut bits = BitBuffer::with_capacity(values.len() * 11 / 2 + 6);
for chunk in values.chunks(2) {
if chunk.len() == 2 {
bits.append(u32::from(chunk[0]) * 45 + u32::from(chunk[1]), 11);
} else {
bits.append(u32::from(chunk[0]), 6);
}
}
Ok(Self {
mode: Mode::Alphanumeric,
character_count: values.len(),
bits,
source: data.to_vec(),
})
}
pub fn bytes(data: impl AsRef<[u8]>) -> Self {
let data = data.as_ref();
Self {
mode: Mode::Byte,
character_count: data.len(),
bits: BitBuffer::from_bytes(data.to_vec()),
source: Vec::new(),
}
}
#[cfg(any(feature = "qr", feature = "rmqr"))]
fn source_bytes(&self) -> &[u8] {
match self.mode {
Mode::Byte => self.bits.as_bytes(),
_ => &self.source,
}
}
#[cfg(any(feature = "qr", feature = "rmqr"))]
pub fn eci(assignment: EciAssignment) -> Self {
let mut bits = BitBuffer::with_capacity(24);
let value = assignment.0;
if value < 128 {
bits.append(value, 8);
} else if value < 16_384 {
bits.append(0b10, 2);
bits.append(value, 14);
} else {
bits.append(0b110, 3);
bits.append(value, 21);
}
Self {
mode: Mode::Eci,
character_count: 0,
bits,
source: Vec::new(),
}
}
#[cfg(feature = "kanji")]
pub fn kanji(data: impl AsRef<str>) -> Result<Self, EncodeError> {
let data = data.as_ref();
let mut bits = BitBuffer::with_capacity(data.chars().count() * 13);
let mut source = Vec::with_capacity(data.len() * 2);
let mut count = 0;
for (offset, character) in data.char_indices() {
let Some((value, encoded)) = kanji_encoding(character) else {
return Err(EncodeError::InvalidData {
mode: "Kanji", byte_offset: offset
});
};
bits.append(u32::from(value), 13);
source.extend_from_slice(&encoded);
count += 1;
}
Ok(Self {
mode: Mode::Kanji,
character_count: count,
bits,
source,
})
}
#[inline]
pub const fn character_count(&self) -> usize {
self.character_count
}
}
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
#[inline]
pub(crate) const fn is_latin1_character(character: char) -> bool {
matches!(character as u32, 0x00..=0x7F | 0xA0..=0xFF)
}
#[cfg(any(feature = "micro-qr", all(feature = "kanji", any(feature = "qr", feature = "rmqr"))))]
#[inline]
pub(crate) const fn is_legacy_byte_character(character: char) -> bool {
is_latin1_character(character) && !matches!(character as u32, 0xE0..=0xEB)
}
#[cfg(any(feature = "qr", feature = "rmqr"))]
fn normalize_fnc1_segments(
segments: &[Segment],
fnc1: bool,
) -> Result<Cow<'_, [Segment]>, EncodeError> {
if !fnc1 {
return Ok(Cow::Borrowed(segments));
}
segments
.iter()
.map(|segment| {
if segment.mode == Mode::Alphanumeric {
Segment::fnc1_alphanumeric(segment.source_bytes())
} else {
Ok(segment.clone())
}
})
.collect::<Result<Vec<_>, _>>()
.map(Cow::Owned)
}
#[cfg(feature = "kanji")]
fn shift_jis_encoding(character: char) -> Option<([u8; 2], usize)> {
let mut utf8 = [0; 4];
let text = character.encode_utf8(&mut utf8);
let (encoded, _, had_errors) = encoding_rs::SHIFT_JIS.encode(text);
if had_errors {
return None;
}
match *encoded {
[byte @ (0x5C | 0x7E)] => (!character.is_ascii()).then_some(([byte, 0], 1)),
[byte @ (0x00..=0x7F | 0xA1..=0xDF)] => Some(([byte, 0], 1)),
[lead, trail] if is_unambiguous_jis_x_0208(u16::from_be_bytes([lead, trail])) => {
Some(([lead, trail], 2))
},
_ => None,
}
}
#[cfg(feature = "kanji")]
#[inline]
const fn is_unambiguous_jis_x_0208(value: u16) -> bool {
matches!(value, 0x8140..=0x84BE | 0x889F..=0x9FFC | 0xE040..=0xEAA4)
&& !matches!(value, 0x815C | 0x815F | 0x8160 | 0x8161 | 0x817C | 0x8191 | 0x8192 | 0x81CA)
}
#[cfg(feature = "kanji")]
fn kanji_encoding(character: char) -> Option<(u16, [u8; 2])> {
let (bytes, length) = shift_jis_encoding(character)?;
if length != 2 {
return None;
}
let value = u16::from_be_bytes(bytes);
let adjusted = match value {
0x8140..=0x9FFC => value - 0x8140,
0xE040..=0xEBBF => value - 0xC140,
_ => return None,
};
Some(((adjusted >> 8) * 0xC0 + (adjusted & 0xFF), bytes))
}
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
#[inline]
const fn mode_rank(mode: Mode) -> u8 {
match mode {
Mode::Numeric => 0,
Mode::Alphanumeric => 1,
Mode::Kanji => 2,
Mode::Byte => 3,
Mode::Eci => 4,
}
}
#[inline]
pub(crate) const fn alphanumeric_value(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'A'..=b'Z' => Some(byte - b'A' + 10),
b' ' => Some(36),
b'$' => Some(37),
b'%' => Some(38),
b'*' => Some(39),
b'+' => Some(40),
b'-' => Some(41),
b'.' => Some(42),
b'/' => Some(43),
b':' => Some(44),
_ => None,
}
}
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
#[inline]
pub(crate) const fn bch_remainder(data: u32, generator: u32, degree: u32) -> u32 {
let mut remainder = data;
let mut round = 0;
while round < degree {
remainder = (remainder << 1) ^ ((remainder >> (degree - 1)) * generator);
round += 1;
}
remainder
}
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Symbol {
pub(crate) version: SymbolVersion,
pub(crate) error_correction: SymbolErrorCorrection,
pub(crate) mask: u8,
pub(crate) modules: Vec<bool>,
#[cfg(feature = "qr")]
pub(crate) structured_append: Option<StructuredAppendInfo>,
}
#[cfg(any(feature = "qr", feature = "micro-qr", feature = "rmqr"))]
impl Symbol {
#[inline]
pub const fn size(&self) -> usize {
self.width()
}
#[inline]
pub const fn width(&self) -> usize {
match self.version {
#[cfg(feature = "qr")]
SymbolVersion::Qr(version) => version.0 as usize * 4 + 17,
#[cfg(feature = "micro-qr")]
SymbolVersion::Micro(version) => version.size(),
#[cfg(feature = "rmqr")]
SymbolVersion::Rmqr(version) => version.width(),
}
}
#[inline]
pub const fn height(&self) -> usize {
match self.version {
#[cfg(feature = "qr")]
SymbolVersion::Qr(version) => version.0 as usize * 4 + 17,
#[cfg(feature = "micro-qr")]
SymbolVersion::Micro(version) => version.size(),
#[cfg(feature = "rmqr")]
SymbolVersion::Rmqr(version) => version.height(),
}
}
#[inline]
pub const fn version(&self) -> SymbolVersion {
self.version
}
#[inline]
pub const fn error_correction(&self) -> SymbolErrorCorrection {
self.error_correction
}
#[inline]
pub const fn mask(&self) -> u8 {
self.mask
}
#[cfg(feature = "qr")]
#[inline]
pub const fn structured_append(&self) -> Option<StructuredAppendInfo> {
self.structured_append
}
#[inline]
pub fn module(&self, x: usize, y: usize) -> Option<bool> {
let width = self.width();
(x < width && y < self.height()).then(|| self.modules[y * width + x])
}
#[inline]
pub fn modules(&self) -> &[bool] {
&self.modules
}
pub fn to_matrix(&self) -> Vec<Vec<bool>> {
self.modules.chunks(self.width()).map(<[bool]>::to_vec).collect()
}
}
pub trait ToQRText {
fn to_qr_text(&self) -> String;
}
#[cfg(feature = "qr")]
#[derive(Clone, Debug)]
pub struct QrEncoder {
error_correction: QrErrorCorrection,
versions: RangeInclusive<QrVersion>,
mask: Option<QrMask>,
boost_error_correction: bool,
fnc1: Option<Fnc1>,
}
#[cfg(feature = "qr")]
impl QrEncoder {
#[inline]
pub const fn new(error_correction: QrErrorCorrection) -> Self {
Self {
error_correction,
versions: QrVersion::MIN..=QrVersion::MAX,
mask: None,
boost_error_correction: true,
fnc1: None,
}
}
#[must_use]
#[inline]
pub const fn version(mut self, version: QrVersion) -> Self {
self.versions = version..=version;
self
}
#[must_use]
#[inline]
pub const fn version_range(mut self, versions: RangeInclusive<QrVersion>) -> Self {
self.versions = versions;
self
}
#[must_use]
#[inline]
pub const fn mask(mut self, mask: QrMask) -> Self {
self.mask = Some(mask);
self
}
#[must_use]
#[inline]
pub const fn boost_error_correction(mut self, boost: bool) -> Self {
self.boost_error_correction = boost;
self
}
#[must_use]
#[inline]
pub const fn fnc1(mut self, fnc1: Option<Fnc1>) -> Self {
self.fnc1 = fnc1;
self
}
pub fn encode_bytes(&self, data: impl AsRef<[u8]>) -> Result<Symbol, EncodeError> {
let data = data.as_ref();
let (capacity, capacity_bits) = self.input_capacity_upper_bound(false)?;
ensure_input_length(data.len(), capacity, capacity_bits, 1)?;
self.encode_optimized_bytes(data, None)
}
pub fn encode_text(&self, text: impl AsRef<str>) -> Result<Symbol, EncodeError> {
let text = text.as_ref();
let (capacity, capacity_bits) = self.input_capacity_upper_bound(false)?;
ensure_input_length(text.chars().count(), capacity, capacity_bits, 1)?;
self.encode_optimized_text(text, None, optimizer::Start::Free)
}
#[inline]
pub fn encode_to_qr_text<T: ToQRText + ?Sized>(
&self,
value: &T,
) -> Result<Symbol, EncodeError> {
let text = value.to_qr_text();
self.encode_text(&text)
}
pub fn encode_segments(&self, segments: &[Segment]) -> Result<Symbol, EncodeError> {
self.encode_segments_with_header(segments, None)
}
pub fn encode_structured_append_bytes(
&self,
parts: &[&[u8]],
) -> Result<Vec<Symbol>, EncodeError> {
validate_part_count(parts.len())?;
let (capacity, capacity_bits) = self.input_capacity_upper_bound(true)?;
for part in parts {
ensure_input_length(part.len(), capacity, capacity_bits, 1)?;
}
let parity = parts
.iter()
.flat_map(|part| part.iter().copied())
.fold(0, |parity, byte| parity ^ byte);
parts
.iter()
.enumerate()
.map(|(index, part)| {
self.encode_optimized_bytes(
part,
Some(StructuredAppendInfo {
index: index as u8,
total: parts.len() as u8,
parity,
}),
)
})
.collect()
}
pub fn encode_structured_append_text(
&self,
parts: &[&str],
) -> Result<Vec<Symbol>, EncodeError> {
validate_part_count(parts.len())?;
let (capacity, capacity_bits) = self.input_capacity_upper_bound(true)?;
for part in parts {
ensure_input_length(part.chars().count(), capacity, capacity_bits, 1)?;
}
let total = parts.len() as u8;
let mut selected: Option<SequencePlan> = None;
for regime in SequenceRegime::ALL {
if let Some(candidate) = self.structured_text_plans(parts, regime)?
&& selected.as_ref().is_none_or(|selected| {
(candidate.version, candidate.area) < (selected.version, selected.area)
})
{
selected = Some(candidate);
}
}
let Some(SequencePlan {
parts: plans, ..
}) = selected
else {
return Err(self.structured_text_error(parts));
};
let parity = plans
.iter()
.flat_map(|(_, segments)| segments)
.flat_map(|segment| segment.source_bytes().iter().copied())
.fold(0, |parity, byte| parity ^ byte);
plans
.into_iter()
.enumerate()
.map(|(index, (version, segments))| {
model2::encode(
&segments,
version,
self.error_correction,
self.mask,
self.boost_error_correction,
self.fnc1,
Some(StructuredAppendInfo {
index: index as u8,
total,
parity,
}),
)
})
.collect()
}
fn qr_structured_plan(
&self,
text: &str,
start: optimizer::Start,
) -> Result<Option<(QrVersion, Vec<Segment>)>, EncodeError> {
if self.versions.start() > self.versions.end() {
return Err(EncodeError::InvalidVersionRange);
}
let header = StructuredAppendInfo {
index: 0, total: 16, parity: 0
};
let range = self.versions.clone();
let tables = optimizer::TextTables::new(text, self.fnc1.is_some());
let mut cache = GroupCache::new(|version| {
optimizer::text(&tables, optimizer::Profile::qr(version), start)
});
for value in range.start().0..=range.end().0 {
let version = QrVersion(value);
let Ok(segments) = cache.get(version) else {
return Ok(None);
};
if model2::fits(segments, version, self.error_correction, self.fnc1, Some(header)) {
return Ok(Some((version, segments.to_vec())));
}
}
Ok(None)
}
fn structured_text_plans(
&self,
parts: &[&str],
regime: SequenceRegime,
) -> Result<Option<SequencePlan>, EncodeError> {
if !regime.suits(&parts.concat()) {
return Ok(None);
}
let mut options = Vec::with_capacity(parts.len());
for (index, part) in parts.iter().enumerate() {
let mut by_state: [Vec<(QrVersion, Vec<Segment>, bool)>; 2] = [Vec::new(), Vec::new()];
for eci_in_force in [false, true] {
if eci_in_force && (index == 0 || regime != SequenceRegime::Standard) {
continue;
}
for &(start, _) in regime.starts(eci_in_force) {
if let Some((version, segments)) = self.qr_structured_plan(part, start)? {
let leaves_eci = eci_in_force
|| segments.iter().any(|segment| segment.mode == Mode::Eci);
by_state[usize::from(eci_in_force)].push((version, segments, leaves_eci));
}
}
}
options.push(by_state);
}
let mut caps: Vec<QrVersion> = options
.iter()
.flat_map(|by_state| by_state.iter().flatten().map(|(version, ..)| *version))
.collect();
caps.sort_unstable();
caps.dedup();
for cap in caps {
let mut best: Vec<[Option<(usize, usize, usize)>; 2]> =
vec![[None; 2]; parts.len() + 1];
best[0][0] = Some((0, 0, 0));
for (index, by_state) in options.iter().enumerate() {
for eci_in_force in [false, true] {
let Some((area, ..)) = best[index][usize::from(eci_in_force)] else {
continue;
};
for (choice, (version, _, leaves_eci)) in
by_state[usize::from(eci_in_force)].iter().enumerate()
{
if *version > cap {
continue;
}
let size = usize::from(version.value()) * 4 + 17;
let candidate = (area + size * size, usize::from(eci_in_force), choice);
let slot = &mut best[index + 1][usize::from(*leaves_eci)];
if slot.is_none_or(|current| candidate.0 < current.0) {
*slot = Some(candidate);
}
}
}
}
let Some((mut state, area)) = (0..2)
.filter_map(|state| best[parts.len()][state].map(|(area, ..)| (state, area)))
.min_by_key(|&(_, area)| area)
else {
continue;
};
let mut plans = Vec::with_capacity(parts.len());
for index in (0..parts.len()).rev() {
let (_, previous, choice) = best[index + 1][state].expect("the path is reachable");
let (version, segments, _) = options[index][previous][choice].clone();
plans.push((version, segments));
state = previous;
}
plans.reverse();
return Ok(Some(SequencePlan {
version: cap,
area,
parts: plans,
}));
}
Ok(None)
}
fn structured_text_error(&self, parts: &[&str]) -> EncodeError {
let version = *self.versions.end();
let capacity_error = EncodeError::DataTooLong {
required_bits: None,
capacity_bits: model2::data_codewords(version, self.error_correction) * 8,
};
for (index, part) in parts.iter().enumerate() {
let start =
if index == 0 { optimizer::Start::Default } else { optimizer::Start::Explicit };
let tables = optimizer::TextTables::new(part, self.fnc1.is_some());
let Ok(segments) = optimizer::text(&tables, optimizer::Profile::qr(version), start)
else {
continue;
};
if let Err(error) = model2::encode(
&segments,
version,
self.error_correction,
self.mask,
false,
self.fnc1,
Some(StructuredAppendInfo {
index: index as u8,
total: parts.len() as u8,
parity: 0,
}),
) {
return error;
}
}
capacity_error
}
pub fn encode_structured_append_segments(
&self,
parts: &[&[Segment]],
) -> Result<Vec<Symbol>, EncodeError> {
validate_part_count(parts.len())?;
let parity = parts
.iter()
.flat_map(|part| part.iter())
.flat_map(|segment| segment.source_bytes().iter().copied())
.fold(0, |parity, byte| parity ^ byte);
parts
.iter()
.enumerate()
.map(|(index, part)| {
self.encode_segments_with_header(
part,
Some(StructuredAppendInfo {
index: index as u8,
total: parts.len() as u8,
parity,
}),
)
})
.collect()
}
pub fn encode_bytes_with_structured_append(
&self,
data: impl AsRef<[u8]>,
) -> Result<Vec<Symbol>, EncodeError> {
let data = data.as_ref();
match self.encode_bytes(data) {
Ok(symbol) => return Ok(vec![symbol]),
Err(EncodeError::DataTooLong {
..
}) => {},
Err(error) => return Err(error),
}
let range = self.structured_append_range()?;
let (capacity, capacity_bits) = self.input_capacity_upper_bound(true)?;
ensure_input_length(data.len(), capacity, capacity_bits, 16)?;
let sequence_capacity_error = || EncodeError::DataTooLong {
required_bits: None,
capacity_bits: model2::data_codewords(*range.end(), self.error_correction) * 8 * 16,
};
let reach = self.qr_character_upper_bound(*range.end());
let mut costs = PrefixCosts::new(|start: usize, version, ()| {
let end = data.len().min(start.saturating_add(reach));
optimizer::byte_costs(
&data[start..end],
optimizer::Profile::qr(version),
self.fnc1.is_some(),
)
});
let minimum_parts = self
.minimum_byte_parts(&mut costs, data.len(), range.clone())
.ok_or_else(sequence_capacity_error)?;
let candidate = smallest_version_cap(&range, |candidate| {
self.minimum_byte_parts(&mut costs, data.len(), *range.start()..=candidate)
== Some(minimum_parts)
})
.ok_or_else(sequence_capacity_error)?;
let parts = self
.minimum_area_byte_partition(
&mut costs,
data.len(),
minimum_parts,
*range.start()..=candidate,
)
.map_err(|_| sequence_capacity_error())?;
let slices: Vec<&[u8]> = parts.into_iter().map(|(start, end)| &data[start..end]).collect();
self.encode_structured_append_bytes(&slices)
}
pub fn encode_text_with_structured_append(
&self,
text: impl AsRef<str>,
) -> Result<Vec<Symbol>, EncodeError> {
let text = text.as_ref();
match self.encode_text(text) {
Ok(symbol) => return Ok(vec![symbol]),
Err(EncodeError::DataTooLong {
..
}) => {},
Err(error) => return Err(error),
}
let range = self.structured_append_range()?;
let (capacity, capacity_bits) = self.input_capacity_upper_bound(true)?;
ensure_input_length(text.chars().count(), capacity, capacity_bits, 16)?;
let input = SplitText::new(text);
let reach = self.qr_character_upper_bound(*range.end());
let mut costs = PrefixCosts::new(|start: usize, version, mode| {
let end = input.len().min(start.saturating_add(reach));
optimizer::text_costs(
input.slice(start, end),
optimizer::Profile::qr(version),
self.fnc1.is_some(),
mode,
)
});
let sequence_capacity_error = || EncodeError::DataTooLong {
required_bits: None,
capacity_bits: model2::data_codewords(*range.end(), self.error_correction) * 8 * 16,
};
let mut selected: Option<SequencePartition> = None;
for regime in SequenceRegime::ALL {
if !regime.suits(text) {
continue;
}
let Some(minimum_parts) =
self.minimum_text_parts(&input, &mut costs, range.clone(), regime)
else {
continue;
};
if selected.as_ref().is_some_and(|selected| minimum_parts > selected.count) {
continue;
}
let Some(candidate) = smallest_version_cap(&range, |candidate| {
self.minimum_text_parts(&input, &mut costs, *range.start()..=candidate, regime)
== Some(minimum_parts)
}) else {
continue;
};
let (area, parts) = self
.minimum_area_text_partition(
&input,
&mut costs,
minimum_parts,
*range.start()..=candidate,
regime,
)
.map_err(|_| sequence_capacity_error())?;
if selected.as_ref().is_none_or(|selected| {
(minimum_parts, candidate, area) < (selected.count, selected.version, selected.area)
}) {
selected = Some(SequencePartition {
count: minimum_parts,
version: candidate,
area,
parts,
});
}
}
let parts = selected.ok_or_else(sequence_capacity_error)?.parts;
let slices: Vec<&str> =
parts.into_iter().map(|(start, end)| input.slice(start, end)).collect();
self.encode_structured_append_text(&slices)
}
fn structured_append_range(&self) -> Result<RangeInclusive<QrVersion>, EncodeError> {
if self.versions.start() > self.versions.end() {
Err(EncodeError::InvalidVersionRange)
} else {
Ok(self.versions.clone())
}
}
fn input_capacity_upper_bound(
&self,
structured_append: bool,
) -> Result<(usize, usize), EncodeError> {
if self.versions.start() > self.versions.end() {
return Err(EncodeError::InvalidVersionRange);
}
let version = *self.versions.end();
let capacity_bits = model2::data_codewords(version, self.error_correction) * 8;
let overhead_bits = 4
+ usize::from(Mode::Numeric.cci_bits(version))
+ usize::from(structured_append) * 20
+ match self.fnc1 {
Some(Fnc1::Gs1) => 4,
Some(Fnc1::Industry(_)) => 12,
None => 0,
};
Ok((numeric_character_capacity(capacity_bits, overhead_bits), capacity_bits))
}
fn minimum_byte_parts<F>(
&self,
costs: &mut PrefixCosts<(), F>,
length: usize,
range: RangeInclusive<QrVersion>,
) -> Option<usize>
where
F: FnMut(usize, QrVersion, ()) -> Vec<Option<usize>>, {
let mut start = 0;
let mut count = 0;
while start < length {
if count == 16 {
return None;
}
start = self.farthest_part_end(costs, start, (), range.clone())?;
count += 1;
}
Some(count)
}
fn farthest_part_end<K, F>(
&self,
costs: &mut PrefixCosts<K, F>,
start: usize,
key: K,
range: RangeInclusive<QrVersion>,
) -> Option<usize>
where
K: Copy + Ord,
F: FnMut(usize, QrVersion, K) -> Vec<Option<usize>>, {
let header_bits = model2::header_bits(self.fnc1, true);
let mut result = None;
for value in range.start().value()..=range.end().value() {
let version = QrVersion(value);
let capacity_bits = model2::data_codewords(version, self.error_correction) * 8;
let count = costs.get(start, version, key)[1..].partition_point(|cost| {
cost.is_some_and(|bits| header_bits + bits <= capacity_bits)
});
if count > 0 {
result = result.max(Some(start + count));
}
}
result
}
fn minimum_text_parts<F>(
&self,
input: &SplitText<'_>,
costs: &mut PrefixCosts<optimizer::Start, F>,
range: RangeInclusive<QrVersion>,
regime: SequenceRegime,
) -> Option<usize>
where
F: FnMut(usize, QrVersion, optimizer::Start) -> Vec<Option<usize>>, {
let length = input.len();
let mut layer = vec![(0, false)];
for count in 1..=16 {
let mut farthest = [None; 2];
for &(start, eci_in_force) in &layer {
for (end, leaves_eci) in self
.text_part_ends(input, costs, start, eci_in_force, regime, range.clone())
.into_iter()
.flatten()
{
if end == length {
return Some(count);
}
let slot = &mut farthest[usize::from(leaves_eci)];
if slot.is_none_or(|current| end > current) {
*slot = Some(end);
}
}
}
layer.clear();
layer.extend(farthest[0].map(|end| (end, false)));
if let Some(end) = farthest[1]
&& farthest[0].is_none_or(|free| end > free)
{
layer.push((end, true));
}
if layer.is_empty() {
return None;
}
}
None
}
fn text_part_ends<F>(
&self,
input: &SplitText<'_>,
costs: &mut PrefixCosts<optimizer::Start, F>,
start: usize,
eci_in_force: bool,
regime: SequenceRegime,
range: RangeInclusive<QrVersion>,
) -> [Option<(usize, bool)>; 2]
where
F: FnMut(usize, QrVersion, optimizer::Start) -> Vec<Option<usize>>, {
let high =
input.len().min(start.saturating_add(self.qr_character_upper_bound(*range.end())));
let mut result = [None; 2];
let mut farthest = None;
for (index, &(mode, leaves_eci)) in regime.starts(eci_in_force).iter().enumerate() {
if leaves_eci && !eci_in_force && input.eci_useful_from[start] >= high {
continue;
}
let end = self.farthest_part_end(costs, start, mode, range.clone());
if let Some(end) = end
&& farthest.is_none_or(|farthest| end > farthest)
{
result[index] = Some((end, leaves_eci));
farthest = Some(end);
}
}
result
}
fn minimum_area_byte_partition<F>(
&self,
costs: &mut PrefixCosts<(), F>,
length: usize,
part_count: usize,
range: RangeInclusive<QrVersion>,
) -> Result<Vec<(usize, usize)>, EncodeError>
where
F: FnMut(usize, QrVersion, ()) -> Vec<Option<usize>>, {
let (_, parts) = minimum_area_partition(length, part_count, range, |start, _, version| {
let end = self.farthest_part_end(costs, start, (), version..=version);
[end.map(|end| (end, false)), None]
})?;
Ok(parts)
}
fn minimum_area_text_partition<F>(
&self,
input: &SplitText<'_>,
costs: &mut PrefixCosts<optimizer::Start, F>,
part_count: usize,
range: RangeInclusive<QrVersion>,
regime: SequenceRegime,
) -> Result<(usize, Vec<(usize, usize)>), EncodeError>
where
F: FnMut(usize, QrVersion, optimizer::Start) -> Vec<Option<usize>>, {
minimum_area_partition(input.len(), part_count, range, |start, eci_in_force, version| {
self.text_part_ends(input, costs, start, eci_in_force, regime, version..=version)
})
}
#[inline]
const fn qr_character_upper_bound(&self, version: QrVersion) -> usize {
model2::data_codewords(version, self.error_correction) * 8 * 3 / 10 + 2
}
fn encode_optimized_bytes(
&self,
data: &[u8],
structured_append: Option<StructuredAppendInfo>,
) -> Result<Symbol, EncodeError> {
self.encode_qr_range(
self.versions.clone(),
|version| optimizer::bytes(data, optimizer::Profile::qr(version), self.fnc1.is_some()),
structured_append,
)
}
fn encode_optimized_text(
&self,
text: &str,
structured_append: Option<StructuredAppendInfo>,
start: optimizer::Start,
) -> Result<Symbol, EncodeError> {
let tables = optimizer::TextTables::new(text, self.fnc1.is_some());
self.encode_qr_range(
self.versions.clone(),
|version| optimizer::text(&tables, optimizer::Profile::qr(version), start),
structured_append,
)
}
fn encode_qr_range<F>(
&self,
range: RangeInclusive<QrVersion>,
segments: F,
structured_append: Option<StructuredAppendInfo>,
) -> Result<Symbol, EncodeError>
where
F: FnMut(QrVersion) -> Result<Vec<Segment>, EncodeError>, {
if range.start() > range.end() {
return Err(EncodeError::InvalidVersionRange);
}
let mut cache = GroupCache::new(segments);
let mut last_error = None;
for value in range.start().0..=range.end().0 {
let version = QrVersion(value);
let candidate = cache.get(version)?;
match model2::encode(
candidate,
version,
self.error_correction,
self.mask,
self.boost_error_correction,
self.fnc1,
structured_append,
) {
Ok(symbol) => return Ok(symbol),
Err(
error @ EncodeError::DataTooLong {
..
},
) => last_error = Some(error),
Err(error) => return Err(error),
}
}
Err(last_error.unwrap_or(EncodeError::InvalidVersionRange))
}
fn encode_segments_with_header(
&self,
segments: &[Segment],
structured_append: Option<StructuredAppendInfo>,
) -> Result<Symbol, EncodeError> {
let segments = normalize_fnc1_segments(segments, self.fnc1.is_some())?;
self.encode_qr_range(self.versions.clone(), |_| Ok(segments.to_vec()), structured_append)
}
}
#[cfg(feature = "rmqr")]
#[derive(Clone, Debug)]
pub struct RmqrEncoder {
error_correction: RmqrErrorCorrection,
versions: RangeInclusive<RmqrVersion>,
boost_error_correction: bool,
fnc1: Option<Fnc1>,
}
#[cfg(feature = "rmqr")]
impl RmqrEncoder {
#[inline]
pub const fn new(error_correction: RmqrErrorCorrection) -> Self {
Self {
error_correction,
versions: RmqrVersion::MIN..=RmqrVersion::MAX,
boost_error_correction: true,
fnc1: None,
}
}
#[must_use]
#[inline]
pub const fn version(mut self, version: RmqrVersion) -> Self {
self.versions = version..=version;
self
}
#[must_use]
#[inline]
pub const fn version_range(mut self, versions: RangeInclusive<RmqrVersion>) -> Self {
self.versions = versions;
self
}
#[must_use]
#[inline]
pub const fn boost_error_correction(mut self, boost: bool) -> Self {
self.boost_error_correction = boost;
self
}
#[must_use]
#[inline]
pub const fn fnc1(mut self, fnc1: Option<Fnc1>) -> Self {
self.fnc1 = fnc1;
self
}
pub fn encode_bytes(&self, data: impl AsRef<[u8]>) -> Result<Symbol, EncodeError> {
let data = data.as_ref();
let (capacity, capacity_bits) = self.input_capacity_upper_bound()?;
ensure_input_length(data.len(), capacity, capacity_bits, 1)?;
self.encode_optimized(|version| {
optimizer::bytes(
data,
optimizer::Profile::rmqr(rmqr::cci(version)),
self.fnc1.is_some(),
)
})
}
pub fn encode_text(&self, text: impl AsRef<str>) -> Result<Symbol, EncodeError> {
let text = text.as_ref();
let (capacity, capacity_bits) = self.input_capacity_upper_bound()?;
ensure_input_length(text.chars().count(), capacity, capacity_bits, 1)?;
let tables = optimizer::TextTables::new(text, self.fnc1.is_some());
self.encode_optimized(|version| {
optimizer::text(
&tables,
optimizer::Profile::rmqr(rmqr::cci(version)),
optimizer::Start::Free,
)
})
}
#[inline]
pub fn encode_to_qr_text<T: ToQRText + ?Sized>(
&self,
value: &T,
) -> Result<Symbol, EncodeError> {
let text = value.to_qr_text();
self.encode_text(&text)
}
pub fn encode_segments(&self, segments: &[Segment]) -> Result<Symbol, EncodeError> {
let segments = normalize_fnc1_segments(segments, self.fnc1.is_some())?;
self.encode_candidates(|version| {
rmqr::encode(
&segments,
version,
self.error_correction,
self.boost_error_correction,
self.fnc1,
)
})
}
fn input_capacity_upper_bound(&self) -> Result<(usize, usize), EncodeError> {
let versions = self.candidates()?;
let mut result = None;
for version in versions {
let capacity_bits = rmqr::data_codewords(version, self.error_correction) * 8;
let overhead_bits = 3
+ usize::from(rmqr::cci(version)[0])
+ match self.fnc1 {
Some(Fnc1::Gs1) => 3,
Some(Fnc1::Industry(_)) => 11,
None => 0,
};
let capacity = numeric_character_capacity(capacity_bits, overhead_bits);
if result.is_none_or(|(best, _)| capacity > best) {
result = Some((capacity, capacity_bits));
}
}
result.ok_or(EncodeError::InvalidVersionRange)
}
fn encode_optimized<F>(&self, mut optimize: F) -> Result<Symbol, EncodeError>
where
F: FnMut(RmqrVersion) -> Result<Vec<Segment>, EncodeError>, {
let mut cache: Vec<([u8; 4], Vec<Segment>)> = Vec::with_capacity(15);
self.encode_candidates(|version| {
let profile = rmqr::cci(version);
let index = match cache.iter().position(|(cci, _)| *cci == profile) {
Some(index) => index,
None => {
cache.push((profile, optimize(version)?));
cache.len() - 1
},
};
rmqr::encode(
&cache[index].1,
version,
self.error_correction,
self.boost_error_correction,
self.fnc1,
)
})
}
fn encode_candidates<F>(&self, mut encode: F) -> Result<Symbol, EncodeError>
where
F: FnMut(RmqrVersion) -> Result<Symbol, EncodeError>, {
let versions = self.candidates()?;
let mut last_error = None;
for version in versions {
match encode(version) {
Ok(symbol) => return Ok(symbol),
Err(
error @ EncodeError::DataTooLong {
..
},
) => last_error = Some(error),
Err(error) => return Err(error),
}
}
Err(last_error.unwrap_or(EncodeError::InvalidVersionRange))
}
fn candidates(&self) -> Result<Vec<RmqrVersion>, EncodeError> {
if self.versions.start() > self.versions.end() {
return Err(EncodeError::InvalidVersionRange);
}
Ok(RmqrVersion::ALL_BY_AREA
.into_iter()
.filter(|version| self.versions.contains(version))
.collect())
}
}
#[cfg(feature = "micro-qr")]
#[derive(Clone, Debug)]
pub struct MicroEncoder {
error_correction: MicroErrorCorrection,
versions: RangeInclusive<MicroVersion>,
mask: Option<MicroMask>,
boost_error_correction: bool,
}
#[cfg(feature = "micro-qr")]
impl MicroEncoder {
#[inline]
pub const fn new(error_correction: MicroErrorCorrection) -> Self {
Self {
error_correction,
versions: MicroVersion::M1..=MicroVersion::M4,
mask: None,
boost_error_correction: true,
}
}
#[must_use]
#[inline]
pub const fn version(mut self, version: MicroVersion) -> Self {
self.versions = version..=version;
self
}
#[must_use]
#[inline]
pub const fn version_range(mut self, versions: RangeInclusive<MicroVersion>) -> Self {
self.versions = versions;
self
}
#[must_use]
#[inline]
pub const fn mask(mut self, mask: MicroMask) -> Self {
self.mask = Some(mask);
self
}
#[must_use]
#[inline]
pub const fn boost_error_correction(mut self, boost: bool) -> Self {
self.boost_error_correction = boost;
self
}
pub fn encode_bytes(&self, data: impl AsRef<[u8]>) -> Result<Symbol, EncodeError> {
let data = data.as_ref();
let (version, capacity, capacity_bits) = self.input_capacity_upper_bound()?;
micro::check_bytes(data, version, capacity, capacity_bits)?;
self.encode_range(|version| micro::optimize(data, version))
}
pub fn encode_text(&self, text: impl AsRef<str>) -> Result<Symbol, EncodeError> {
let text = text.as_ref();
let (version, capacity, capacity_bits) = self.input_capacity_upper_bound()?;
let input = micro::Text::new(text, version, capacity, capacity_bits)?;
self.encode_range(|version| micro::optimize_text(&input, version))
}
#[inline]
pub fn encode_to_qr_text<T: ToQRText + ?Sized>(
&self,
value: &T,
) -> Result<Symbol, EncodeError> {
let text = value.to_qr_text();
self.encode_text(&text)
}
pub fn encode_segments(&self, segments: &[Segment]) -> Result<Symbol, EncodeError> {
self.encode_range(|_| {
Ok(segments
.iter()
.filter(|segment| segment.mode != Mode::Numeric || segment.character_count > 0)
.cloned()
.collect())
})
}
fn input_capacity_upper_bound(&self) -> Result<(MicroVersion, usize, usize), EncodeError> {
if self.versions.start() > self.versions.end() {
return Err(EncodeError::InvalidVersionRange);
}
micro_versions(self.versions.clone())
.filter_map(|version| {
micro::input_capacity_upper_bound(version, self.error_correction)
.map(|(capacity, capacity_bits)| (version, capacity, capacity_bits))
})
.max_by_key(|&(_, capacity, _)| capacity)
.ok_or(EncodeError::UnsupportedErrorCorrection {
version: SymbolVersion::Micro(*self.versions.end()),
error_correction: self.error_correction.into(),
})
}
fn encode_range<F>(&self, mut segments: F) -> Result<Symbol, EncodeError>
where
F: FnMut(MicroVersion) -> Result<Vec<Segment>, EncodeError>, {
if self.versions.start() > self.versions.end() {
return Err(EncodeError::InvalidVersionRange);
}
let mut last_error = None;
for version in micro_versions(self.versions.clone()) {
if micro::input_capacity_upper_bound(version, self.error_correction).is_none() {
last_error = Some(EncodeError::UnsupportedErrorCorrection {
version: SymbolVersion::Micro(version),
error_correction: self.error_correction.into(),
});
continue;
}
let result = segments(version).and_then(|segments| {
micro::encode(
&segments,
version,
self.error_correction,
self.mask,
self.boost_error_correction,
)
});
match result {
Ok(symbol) => return Ok(symbol),
Err(error) if candidate_rejection(&error) => last_error = Some(error),
Err(error) => return Err(error),
}
}
Err(last_error.unwrap_or(EncodeError::InvalidVersionRange))
}
}
#[cfg(all(feature = "qr", feature = "micro-qr"))]
#[derive(Clone, Debug)]
pub struct AutoEncoder {
qr: QrEncoder,
micro: MicroEncoder,
}
#[cfg(all(feature = "qr", feature = "micro-qr"))]
impl AutoEncoder {
#[inline]
pub const fn new(qr: QrEncoder, micro: MicroEncoder) -> Self {
Self {
qr,
micro,
}
}
pub fn encode_bytes(&self, data: impl AsRef<[u8]>) -> Result<Symbol, EncodeError> {
let data = data.as_ref();
if self.qr.fnc1.is_some() {
return self.qr.encode_bytes(data);
}
match self.micro.encode_bytes(data) {
Ok(symbol) => Ok(symbol),
Err(error) if candidate_rejection(&error) => self.qr.encode_bytes(data),
Err(error) => Err(error),
}
}
pub fn encode_text(&self, text: impl AsRef<str>) -> Result<Symbol, EncodeError> {
let text = text.as_ref();
if self.qr.fnc1.is_some() {
return self.qr.encode_text(text);
}
match self.micro.encode_text(text) {
Ok(symbol) => Ok(symbol),
Err(error) if candidate_rejection(&error) => self.qr.encode_text(text),
Err(error) => Err(error),
}
}
#[inline]
pub fn encode_to_qr_text<T: ToQRText + ?Sized>(
&self,
value: &T,
) -> Result<Symbol, EncodeError> {
let text = value.to_qr_text();
self.encode_text(&text)
}
pub fn encode_segments(&self, segments: &[Segment]) -> Result<Symbol, EncodeError> {
if self.qr.fnc1.is_some() {
return self.qr.encode_segments(segments);
}
match self.micro.encode_segments(segments) {
Ok(symbol) => Ok(symbol),
Err(error) if candidate_rejection(&error) => self.qr.encode_segments(segments),
Err(error) => Err(error),
}
}
}
#[cfg(feature = "qr")]
#[derive(Clone, Copy)]
struct PartitionState {
end: usize,
area: usize,
previous: usize,
eci_in_force: bool,
}
#[cfg(feature = "qr")]
fn minimum_area_partition<F>(
length: usize,
part_count: usize,
range: RangeInclusive<QrVersion>,
mut part_ends: F,
) -> Result<(usize, Vec<(usize, usize)>), EncodeError>
where
F: FnMut(usize, bool, QrVersion) -> [Option<(usize, bool)>; 2], {
let mut layers = vec![vec![PartitionState {
end: 0,
area: 0,
previous: 0,
eci_in_force: false,
}]];
for _ in 0..part_count {
let previous_layer = layers.last().expect("the initial layer exists");
let mut by_end = BTreeMap::new();
for (previous, state) in previous_layer.iter().enumerate() {
for value in range.start().value()..=range.end().value() {
let version = QrVersion(value);
let size = usize::from(version.value()) * 4 + 17;
for (end, eci_in_force) in
part_ends(state.end, state.eci_in_force, version).into_iter().flatten()
{
let candidate = PartitionState {
end,
area: state.area + size * size,
previous,
eci_in_force,
};
by_end
.entry((end, !eci_in_force))
.and_modify(|current: &mut PartitionState| {
if candidate.area < current.area {
*current = candidate;
}
})
.or_insert(candidate);
}
}
}
let mut best_free_area = usize::MAX;
let mut best_area = usize::MAX;
let mut layer = Vec::new();
for state in by_end.into_values().rev() {
if state.eci_in_force {
if state.area < best_area {
best_area = state.area;
layer.push(state);
}
} else if state.area < best_free_area {
best_free_area = state.area;
best_area = best_area.min(state.area);
layer.push(state);
}
}
layer.reverse();
if layer.is_empty() {
return Err(EncodeError::DataTooLong {
required_bits: None, capacity_bits: 0
});
}
layers.push(layer);
}
let (mut state_index, area) = layers[part_count]
.iter()
.enumerate()
.filter(|(_, state)| state.end == length)
.map(|(index, state)| (index, state.area))
.min_by_key(|&(_, area)| area)
.ok_or(EncodeError::DataTooLong {
required_bits: None, capacity_bits: 0
})?;
let mut result = Vec::with_capacity(part_count);
for layer_index in (1..=part_count).rev() {
let state = layers[layer_index][state_index];
let previous = layers[layer_index - 1][state.previous];
result.push((previous.end, state.end));
state_index = state.previous;
}
result.reverse();
Ok((area, result))
}
#[cfg(feature = "qr")]
fn smallest_version_cap<F>(
range: &RangeInclusive<QrVersion>,
mut reaches_count: F,
) -> Option<QrVersion>
where
F: FnMut(QrVersion) -> bool, {
let mut low = range.start().value();
let mut high = range.end().value();
let mut result = None;
while low <= high {
let middle = low + (high - low) / 2;
if reaches_count(QrVersion(middle)) {
result = Some(QrVersion(middle));
high = middle - 1;
} else {
low = middle + 1;
}
}
result
}
#[cfg(feature = "micro-qr")]
fn micro_versions(versions: RangeInclusive<MicroVersion>) -> impl Iterator<Item = MicroVersion> {
let start = *versions.start();
let end = *versions.end();
[MicroVersion::M1, MicroVersion::M2, MicroVersion::M3, MicroVersion::M4]
.into_iter()
.filter(move |version| *version >= start && *version <= end)
}
#[cfg(feature = "micro-qr")]
#[inline]
const fn candidate_rejection(error: &EncodeError) -> bool {
matches!(
error,
EncodeError::DataTooLong { .. }
| EncodeError::UnsupportedMode { .. }
| EncodeError::UnsupportedErrorCorrection { .. }
| EncodeError::TextNotRepresentable { .. }
)
}
#[cfg(feature = "qr")]
#[inline]
const fn version_group(version: QrVersion) -> usize {
if version.0 <= 9 {
0
} else if version.0 <= 26 {
1
} else {
2
}
}
#[cfg(feature = "qr")]
struct GroupCache<F> {
segments: F,
cache: [Option<Vec<Segment>>; 3],
}
#[cfg(feature = "qr")]
impl<F: FnMut(QrVersion) -> Result<Vec<Segment>, EncodeError>> GroupCache<F> {
fn new(segments: F) -> Self {
Self {
segments,
cache: [None, None, None],
}
}
fn get(&mut self, version: QrVersion) -> Result<&[Segment], EncodeError> {
let group = version_group(version);
if self.cache[group].is_none() {
self.cache[group] = Some((self.segments)(version)?);
}
Ok(self.cache[group].as_deref().expect("the version group is cached"))
}
}
#[cfg(feature = "qr")]
#[inline]
const fn validate_part_count(count: usize) -> Result<(), EncodeError> {
if count >= 1 && count <= 16 {
Ok(())
} else {
Err(EncodeError::InvalidStructuredAppendPartCount {
count,
})
}
}
#[cfg(feature = "qr")]
struct PrefixCosts<K, F> {
compute: F,
costs: BTreeMap<(usize, usize, K), Vec<Option<usize>>>,
}
#[cfg(feature = "qr")]
impl<K: Copy + Ord, F: FnMut(usize, QrVersion, K) -> Vec<Option<usize>>> PrefixCosts<K, F> {
#[inline]
fn new(compute: F) -> Self {
Self {
compute,
costs: BTreeMap::new(),
}
}
fn get(&mut self, start: usize, version: QrVersion, key: K) -> &[Option<usize>] {
let compute = &mut self.compute;
self.costs
.entry((start, version_group(version), key))
.or_insert_with(|| compute(start, version, key))
}
}
#[cfg(feature = "qr")]
struct SplitText<'a> {
text: &'a str,
offsets: Vec<usize>,
eci_useful_from: Vec<usize>,
}
#[cfg(feature = "qr")]
impl<'a> SplitText<'a> {
fn new(text: &'a str) -> Self {
let mut offsets: Vec<usize> = text.char_indices().map(|(offset, _)| offset).collect();
offsets.push(text.len());
let useful: Vec<bool> = text.chars().map(eci_may_help).collect();
let mut eci_useful_from = vec![useful.len(); useful.len() + 1];
for index in (0..useful.len()).rev() {
eci_useful_from[index] = if useful[index] { index } else { eci_useful_from[index + 1] };
}
Self {
text,
offsets,
eci_useful_from,
}
}
#[inline]
fn len(&self) -> usize {
self.offsets.len() - 1
}
#[inline]
fn slice(&self, start: usize, end: usize) -> &'a str {
&self.text[self.offsets[start]..self.offsets[end]]
}
}
#[cfg(feature = "qr")]
#[inline]
fn eci_may_help(character: char) -> bool {
#[cfg(feature = "kanji")]
if kanji_encoding(character).is_some() {
return true;
}
!is_latin1_character(character)
}
#[cfg(feature = "qr")]
struct SequencePlan {
version: QrVersion,
area: usize,
parts: Vec<(QrVersion, Vec<Segment>)>,
}
#[cfg(feature = "qr")]
struct SequencePartition {
count: usize,
version: QrVersion,
area: usize,
parts: Vec<(usize, usize)>,
}
#[cfg(feature = "qr")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SequenceRegime {
#[cfg(feature = "kanji")]
Legacy,
Standard,
}
#[cfg(feature = "qr")]
impl SequenceRegime {
#[cfg(feature = "kanji")]
const ALL: [Self; 2] = [Self::Legacy, Self::Standard];
#[cfg(not(feature = "kanji"))]
const ALL: [Self; 1] = [Self::Standard];
#[inline]
const fn starts(self, eci_in_force: bool) -> &'static [(optimizer::Start, bool)] {
match self {
#[cfg(feature = "kanji")]
Self::Legacy => &[(optimizer::Start::Legacy, false)],
Self::Standard if eci_in_force => &[(optimizer::Start::Explicit, true)],
Self::Standard => {
&[(optimizer::Start::DefaultEciFree, false), (optimizer::Start::Default, true)]
},
}
}
#[cfg_attr(not(feature = "kanji"), allow(unused_variables))]
fn suits(self, text: &str) -> bool {
match self {
#[cfg(feature = "kanji")]
Self::Legacy => {
text.chars().any(|character| kanji_encoding(character).is_some())
&& text.chars().all(|character| {
is_legacy_byte_character(character) || kanji_encoding(character).is_some()
})
},
Self::Standard => true,
}
}
}