// Symbol layout: row planning, pad codewords and final assembly.
//
// The layout rules follow ISO/IEC 15438 annex Q and chapter 4.9:
// * a symbol has three to ninety rows and one to thirty columns,
// * `rows = ceil((data + ec) / columns)` with a minimum of three rows,
// * pad codewords (value 900) fill the data area,
// * the first codeword is the symbol length descriptor, i.e. the number of
// data, pad and descriptor codewords, excluding error correction.
///|
/// Rows required for `source_codewords` data codewords, `ec_codewords` error
/// correction codewords and `columns` columns, with at least `min_rows` rows.
pub fn plan_rows(
source_codewords : Int,
ec_codewords : Int,
columns : Int,
min_rows : Int,
) -> Int {
if columns <= 0 {
return 0
}
let needed = source_codewords + 1 + ec_codewords
let mut rows = (needed + columns - 1) / columns
if rows < min_rows {
rows = min_rows
}
rows
}
///|
/// Rows required with the default three row minimum.
pub fn rows_for(
source_codewords : Int,
ec_codewords : Int,
columns : Int,
) -> Int {
plan_rows(source_codewords, ec_codewords, columns, min_rows)
}
///|
/// Number of pad codewords needed so that data, padding, descriptor and
/// error correction exactly fill `rows * columns` codewords. Returns `None`
/// when the data area is smaller than the data.
pub fn pad_codewords(
source_codewords : Int,
ec_codewords : Int,
columns : Int,
rows : Int,
) -> Int? {
let capacity = columns * rows - ec_codewords
let used = source_codewords + 1
if capacity < used {
None
} else {
Some(capacity - used)
}
}
///|
/// `true` if `columns` and `rows` describe a legal data area.
pub fn is_valid_layout(columns : Int, rows : Int) -> Bool {
columns >= min_columns &&
columns <= max_columns &&
rows >= min_rows &&
rows <= max_rows
}
///|
/// Choose the column count that gets closest to the 3:1 width/height ratio
/// the standard recommends, searching `min_cols..max_cols` and accepting a
/// row count inside `min_r..max_r`.
///
/// Returns `None` when no column count fits.
pub fn plan_columns(
source_codewords : Int,
ec_codewords : Int,
min_cols : Int,
max_cols : Int,
min_r : Int,
max_r : Int,
) -> (Int, Int)? {
let low = if min_cols < min_columns { min_columns } else { min_cols }
let high = if max_cols > max_columns { max_columns } else { max_cols }
let mut best : (Int, Int)? = None
let mut best_distance = 0.0
let mut columns = low
while columns <= high {
let rows = plan_rows(source_codewords, ec_codewords, columns, min_r)
if rows >= min_r && rows <= max_r {
let width = (17 * columns + 69).to_double() * 0.357
let height = rows.to_double() * 2.0
let ratio = width / height
let distance = if ratio > 3.0 { ratio - 3.0 } else { 3.0 - ratio }
let take = match best {
None => true
Some(_) => distance < best_distance
}
if take {
best = Some((columns, rows))
best_distance = distance
}
}
columns += 1
}
best
}
///|
/// Finalize a high level encoding into a complete codeword sequence.
///
/// `data` are the high level data codewords (without the length descriptor),
/// `level` is the error correction level and `columns` the requested column
/// count. The returned `Symbol` carries the full sequence a low level
/// encoder consumes, plus the layout that produced it.
pub fn finalize(
data : Array[Int],
level : Int,
columns : Int,
) -> Symbol raise EncodeError {
if level < 0 || level > max_error_correction_level {
raise InvalidErrorCorrectionLevel(level)
}
if columns < min_columns || columns > max_columns {
raise InvalidColumnCount(columns)
}
let ec_count = error_correction_codeword_count(level)
let rows = rows_for(data.length(), ec_count, columns)
if rows > max_rows {
raise LayoutImpossible(columns, rows)
}
let pad = match pad_codewords(data.length(), ec_count, columns, rows) {
Some(value) => value
None => raise LayoutImpossible(columns, rows)
}
let data_count = data.length() + pad + 1
if data_count + ec_count > max_codewords {
raise TooManyCodewords(data_count + ec_count)
}
let payload : Array[Int] = [data_count]
for word in data {
payload.push(word)
}
for _ in 0.. Symbol? {
try finalize(data, level, columns) catch {
_ => None
} noraise {
symbol => Some(symbol)
}
}
///|
/// `true` if the sequence can be laid out and the trailing codewords are a
/// valid Reed-Solomon parity block.
pub fn verify_symbol(symbol : Symbol) -> Bool {
let ec_count = error_correction_codeword_count(symbol.ec_level)
if symbol.codewords.length() != symbol.data_count + ec_count {
return false
}
if symbol.codewords.length() != symbol.columns * symbol.rows {
return false
}
if symbol.codewords[0] != symbol.data_count {
return false
}
is_error_free(symbol.codewords, ec_count)
}
///|
/// Largest number of data codewords that still fits a symbol with `columns`
/// columns at `level`.
pub fn data_capacity(columns : Int, level : Int) -> Int {
if columns < min_columns || columns > max_columns {
return 0
}
if level < 0 || level > max_error_correction_level {
return 0
}
let ec_count = error_correction_codeword_count(level)
let mut rows = min_rows
let mut best = 0
while rows <= max_rows {
let capacity = columns * rows - ec_count - 1
if capacity > best {
best = capacity
}
rows += 1
}
best
}
///|
/// Recommended minimum error correction level for `n` data codewords, from
/// annex E of ISO/IEC 15438. Returns `None` when the standard gives no
/// recommendation.
pub fn recommended_level(n : Int) -> Int? {
if n <= 0 {
None
} else if n <= 40 {
Some(2)
} else if n <= 160 {
Some(3)
} else if n <= 320 {
Some(4)
} else if n <= 863 {
Some(5)
} else {
None
}
}
///|
/// `true` if the codeword sequence respects the structural rules of a symbol:
/// length descriptor first, pad codewords 900 in the data area and no value
/// outside 0..928.
pub fn is_structurally_valid(codewords : Array[Int]) -> Bool {
if codewords.length() < min_rows {
return false
}
if codewords[0] != codewords.length() {
return false
}
for word in codewords {
if word < 0 || word > max_codewords {
return false
}
}
true
}