#[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(feature = "kanji")]
fn kanji_encoding(character: char) -> Option<(u16, [u8; 2])> {
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 || encoded.len() != 2 {
return None;
}
let bytes = [encoded[0], encoded[1]];
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, false)
}
#[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 force_initial_eci = parts.iter().any(|part| requires_non_default_eci(part));
let total = parts.len() as u8;
let mut plans = Vec::with_capacity(parts.len());
let mut parity = 0u8;
for part in parts {
let (version, segments) = self.qr_structured_plan(part, force_initial_eci)?;
for segment in &segments {
for &byte in segment.source_bytes() {
parity ^= byte;
}
}
plans.push((version, segments));
}
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,
force_initial_eci: bool,
) -> Result<(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 mut cache = GroupCache::new(|version| {
optimizer::text(
text,
optimizer::Profile::qr(version),
self.fnc1.is_some(),
force_initial_eci,
)
});
for value in range.start().0..=range.end().0 {
let version = QrVersion(value);
let segments = cache.get(version)?;
if model2::fits(segments, version, self.error_correction, self.fnc1, Some(header)) {
return Ok((version, segments.to_vec()));
}
}
let version = *range.end();
Ok((version, cache.take(version)?))
}
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 minimum_parts =
self.partition_bytes(data, range.clone()).map_err(|_| sequence_capacity_error())?.len();
let mut selected = None;
for value in range.start().value()..=range.end().value() {
let candidate = QrVersion(value);
if let Ok(parts) = self.partition_bytes(data, *range.start()..=candidate)
&& parts.len() == minimum_parts
{
selected = Some(
self.minimum_area_byte_partition(
data,
minimum_parts,
*range.start()..=candidate,
)
.map_err(|_| sequence_capacity_error())?,
);
break;
}
}
let parts = selected.ok_or_else(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 offsets = text_boundaries(text);
let sequence_capacity_error = || EncodeError::DataTooLong {
required_bits: None,
capacity_bits: model2::data_codewords(*range.end(), self.error_correction) * 8 * 16,
};
let minimum_parts = self
.partition_text(text, &offsets, range.clone())
.map_err(|_| sequence_capacity_error())?
.len();
let mut selected = None;
for value in range.start().value()..=range.end().value() {
let candidate = QrVersion(value);
if let Ok(parts) = self.partition_text(text, &offsets, *range.start()..=candidate)
&& parts.len() == minimum_parts
{
selected = Some(
self.minimum_area_text_partition(
text,
&offsets,
minimum_parts,
*range.start()..=candidate,
)
.map_err(|_| sequence_capacity_error())?,
);
break;
}
}
let parts = selected.ok_or_else(sequence_capacity_error)?;
let slices: Vec<&str> =
parts.into_iter().map(|(start, end)| &text[offsets[start]..offsets[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 partition_bytes(
&self,
data: &[u8],
range: RangeInclusive<QrVersion>,
) -> Result<Vec<(usize, usize)>, EncodeError> {
let maximum_version = *range.end();
let mut result = Vec::new();
let mut start = 0;
while start < data.len() {
if result.len() == 16 {
return Err(EncodeError::DataTooLong {
required_bits: None, capacity_bits: 0
});
}
let mut low = start + 1;
let mut high = data
.len()
.min(start.saturating_add(self.qr_character_upper_bound(maximum_version)));
let mut fitting = None;
while low <= high {
let middle = low + (high - low) / 2;
if self.bytes_fit(&data[start..middle], range.clone()) {
fitting = Some(middle);
low = middle + 1;
} else {
high = middle - 1;
}
}
let end = fitting
.ok_or(EncodeError::DataTooLong {
required_bits: None, capacity_bits: 0
})?;
result.push((start, end));
start = end;
}
Ok(result)
}
fn partition_text(
&self,
text: &str,
offsets: &[usize],
range: RangeInclusive<QrVersion>,
) -> Result<Vec<(usize, usize)>, EncodeError> {
let maximum_version = *range.end();
let force_initial_eci = requires_non_default_eci(text);
let mut result = Vec::new();
let mut start = 0;
let length = offsets.len() - 1;
while start < length {
if result.len() == 16 {
return Err(EncodeError::DataTooLong {
required_bits: None, capacity_bits: 0
});
}
let mut low = start + 1;
let mut high =
length.min(start.saturating_add(self.qr_character_upper_bound(maximum_version)));
let mut fitting = None;
while low <= high {
let middle = low + (high - low) / 2;
if self.text_fits(
&text[offsets[start]..offsets[middle]],
range.clone(),
force_initial_eci,
) {
fitting = Some(middle);
low = middle + 1;
} else {
high = middle - 1;
}
}
let end = fitting
.ok_or(EncodeError::DataTooLong {
required_bits: None, capacity_bits: 0
})?;
result.push((start, end));
start = end;
}
Ok(result)
}
fn minimum_area_byte_partition(
&self,
data: &[u8],
part_count: usize,
range: RangeInclusive<QrVersion>,
) -> Result<Vec<(usize, usize)>, EncodeError> {
minimum_area_partition(data.len(), part_count, range, |start, version| {
let high = data.len().min(start.saturating_add(self.qr_character_upper_bound(version)));
maximum_fitting_end(high, start, |end| {
self.bytes_fit(&data[start..end], version..=version)
})
})
}
fn minimum_area_text_partition(
&self,
text: &str,
offsets: &[usize],
part_count: usize,
range: RangeInclusive<QrVersion>,
) -> Result<Vec<(usize, usize)>, EncodeError> {
let length = offsets.len() - 1;
let force_initial_eci = requires_non_default_eci(text);
minimum_area_partition(length, part_count, range, |start, version| {
let high = length.min(start.saturating_add(self.qr_character_upper_bound(version)));
maximum_fitting_end(high, start, |end| {
self.text_fits(
&text[offsets[start]..offsets[end]],
version..=version,
force_initial_eci,
)
})
})
}
fn bytes_fit(&self, data: &[u8], range: RangeInclusive<QrVersion>) -> bool {
self.qr_range_fits(range, |version| {
optimizer::bytes(data, optimizer::Profile::qr(version), self.fnc1.is_some())
})
}
fn text_fits(
&self,
text: &str,
range: RangeInclusive<QrVersion>,
force_initial_eci: bool,
) -> bool {
self.qr_range_fits(range, |version| {
optimizer::text(
text,
optimizer::Profile::qr(version),
self.fnc1.is_some(),
force_initial_eci,
)
})
}
fn qr_range_fits<F>(&self, range: RangeInclusive<QrVersion>, segments: F) -> bool
where
F: FnMut(QrVersion) -> Result<Vec<Segment>, EncodeError>, {
if range.start() > range.end() {
return false;
}
let header = StructuredAppendInfo {
index: 0, total: 16, parity: 0
};
let mut cache = GroupCache::new(segments);
for value in range.start().0..=range.end().0 {
let version = QrVersion(value);
let Ok(candidate) = cache.get(version) else {
return false;
};
if model2::fits(candidate, version, self.error_correction, self.fnc1, Some(header)) {
return true;
}
}
false
}
#[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>,
force_initial_eci: bool,
) -> Result<Symbol, EncodeError> {
self.encode_qr_range(
self.versions.clone(),
|version| {
optimizer::text(
text,
optimizer::Profile::qr(version),
self.fnc1.is_some(),
force_initial_eci,
)
},
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 normalized;
let segments = if self.fnc1.is_some() {
normalized = segments
.iter()
.map(|segment| {
if segment.mode == Mode::Alphanumeric {
Segment::fnc1_alphanumeric(segment.source_bytes())
} else {
Ok(segment.clone())
}
})
.collect::<Result<Vec<_>, _>>()?;
normalized.as_slice()
} else {
segments
};
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)?;
self.encode_optimized(|version| {
optimizer::text(
text,
optimizer::Profile::rmqr(rmqr::cci(version)),
self.fnc1.is_some(),
false,
)
})
}
#[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 normalized;
let segments = if self.fnc1.is_some() {
normalized = segments
.iter()
.map(|segment| {
if segment.mode == Mode::Alphanumeric {
Segment::fnc1_alphanumeric(segment.source_bytes())
} else {
Ok(segment.clone())
}
})
.collect::<Result<Vec<_>, _>>()?;
normalized.as_slice()
} else {
segments
};
self.encode_optimized(|_| Ok(segments.to_vec()))
}
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 versions = self.candidates()?;
let mut cache: Vec<([u8; 4], Vec<Segment>)> = Vec::with_capacity(15);
let mut last_error = None;
for version in versions {
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
},
};
match rmqr::encode(
&cache[index].1,
version,
self.error_correction,
self.boost_error_correction,
self.fnc1,
) {
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();
if let Some((version, capacity, capacity_bits)) = self.input_capacity_upper_bound()?
&& micro_bytes_are_representable(data, version)
{
ensure_input_length(data.len(), capacity, capacity_bits, 1)?;
}
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();
if let Some((version, capacity, capacity_bits)) = self.input_capacity_upper_bound()?
&& micro_text_is_representable_without_kanji(text, version)
{
ensure_input_length(text.chars().count(), capacity, capacity_bits, 1)?;
}
self.encode_range(|version| micro::optimize_text(text, 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.to_vec()))
}
fn input_capacity_upper_bound(
&self,
) -> Result<Option<(MicroVersion, usize, usize)>, EncodeError> {
if self.versions.start() > self.versions.end() {
return Err(EncodeError::InvalidVersionRange);
}
Ok(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))
}
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()) {
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();
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();
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> {
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,
}
#[cfg(feature = "qr")]
fn minimum_area_partition<F>(
length: usize,
part_count: usize,
range: RangeInclusive<QrVersion>,
mut maximum_end: F,
) -> Result<Vec<(usize, usize)>, EncodeError>
where
F: FnMut(usize, QrVersion) -> Option<usize>, {
let mut layers = vec![vec![PartitionState {
end: 0, area: 0, previous: 0
}]];
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 Some(end) = maximum_end(state.end, version) else {
continue;
};
let size = usize::from(version.value()) * 4 + 17;
let candidate = PartitionState {
end,
area: state.area + size * size,
previous,
};
by_end
.entry(end)
.and_modify(|current: &mut PartitionState| {
if candidate.area < current.area {
*current = candidate;
}
})
.or_insert(candidate);
}
}
let mut best_area = usize::MAX;
let mut layer = Vec::new();
for state in by_end.into_values().rev() {
if state.area < best_area {
best_area = 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 = layers[part_count].iter().position(|state| state.end == length).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(result)
}
#[cfg(feature = "qr")]
fn maximum_fitting_end<F>(length: usize, start: usize, mut fits: F) -> Option<usize>
where
F: FnMut(usize) -> bool, {
if start == length {
return None;
}
let mut low = start + 1;
let mut high = length;
let mut result = None;
while low <= high {
let middle = low + (high - low) / 2;
if fits(middle) {
result = Some(middle);
low = middle + 1;
} else {
high = 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")]
fn micro_bytes_are_representable(data: &[u8], version: MicroVersion) -> bool {
match version {
MicroVersion::M1 => data.iter().all(u8::is_ascii_digit),
MicroVersion::M2 => data.iter().all(|byte| alphanumeric_value(*byte).is_some()),
MicroVersion::M3 | MicroVersion::M4 => true,
}
}
#[cfg(feature = "micro-qr")]
fn micro_text_is_representable_without_kanji(text: &str, version: MicroVersion) -> bool {
match version {
MicroVersion::M1 => text.bytes().all(|byte| byte.is_ascii_digit()),
MicroVersion::M2 => {
text.is_ascii() && text.bytes().all(|byte| alphanumeric_value(byte).is_some())
},
MicroVersion::M3 | MicroVersion::M4 => {
text.chars().all(|character| u32::from(character) <= 0xFF)
},
}
}
#[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"))
}
fn take(&mut self, version: QrVersion) -> Result<Vec<Segment>, EncodeError> {
self.get(version)?;
Ok(self.cache[version_group(version)].take().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")]
fn text_boundaries(text: &str) -> Vec<usize> {
let mut result: Vec<_> = text.char_indices().map(|(offset, _)| offset).collect();
result.push(text.len());
result
}
#[cfg(feature = "qr")]
#[inline]
fn requires_non_default_eci(text: &str) -> bool {
text.chars().any(|character| u32::from(character) > 0xFF)
}