// Low level encoding: codewords to modules.
//
// A PDF417 symbol is drawn row by row. Every row is
//
// start pattern
// left row indicator codeword
// one symbol character per data column
// right row indicator codeword
// stop pattern
//
// and every codeword is one symbol character: a 17 module pattern chosen from
// the cluster of the row, i.e. `row mod 3`. The clusters are the tables in
// `patterns.mbt`. A module value of 1 is a bar and 0 is a space.
//
// This module is the bridge from codewords to something a renderer can fill:
// `symbol_matrix` returns one row of modules per symbol row, and the helpers
// below turn that into ASCII or SVG.
///|
/// Modules of the start pattern.
pub let start_modules : Int = 17
///|
/// Modules of the stop pattern.
pub let stop_modules : Int = 18
///|
/// Symbol character for `codeword` in `cluster`, or `None` when the codeword
/// or the cluster is out of range.
pub fn cluster_pattern(cluster : Int, codeword : Int) -> Int? {
if codeword < 0 || codeword > max_codewords {
return None
}
match cluster {
0 => Some(cluster0_patterns[codeword])
1 => Some(cluster1_patterns[codeword])
2 => Some(cluster2_patterns[codeword])
_ => None
}
}
///|
/// `true` when the module is a bar. Bits below the pattern's most significant
/// bit do not exist and are always spaces.
pub fn module_is_bar(pattern : Int, modules : Int, index : Int) -> Bool {
if index < 0 || index >= modules {
return false
}
((pattern >> (modules - 1 - index)) & 1) == 1
}
///|
/// Append the modules of `pattern` to `row`.
fn append_pattern(row : Array[Int], pattern : Int, modules : Int) -> Unit {
for index in 0.. Unit raise EncodeError {
match cluster_pattern(cluster, codeword) {
Some(pattern) => append_pattern(row, pattern, start_modules)
None => raise UnrepresentableChar(codeword)
}
}
///|
/// Module row of one symbol row, without a quiet zone.
///
/// `row` is zero based, `codewords` are the symbol codewords in storage order
/// (row major) and `ec_level` is the error correction level the indicators
/// report. The row indicator codewords are computed from the layout, exactly
/// as `symbol_rows` does.
pub fn symbol_row_modules(
codewords : Array[Int],
row : Int,
columns : Int,
rows : Int,
ec_level : Int,
) -> Array[Int] raise EncodeError {
let modules : Array[Int] = []
append_pattern(modules, start_pattern, start_modules)
let cluster = row_cluster(row)
append_codeword(
modules,
cluster,
left_row_indicator(row, rows, columns, ec_level),
)
for column in 0.. Array[Array[Int]] raise EncodeError {
let matrix : Array[Array[Int]] = []
for row in 0.. Int {
if matrix.length() == 0 {
return 0
}
matrix[0].length()
}
///|
/// Check that a matrix is rectangular, that every row starts with the start
/// pattern and ends with the stop pattern, and that the width matches the
/// column count.
pub fn verify_matrix(matrix : Array[Array[Int]], columns : Int) -> Bool {
if matrix.length() == 0 {
return false
}
let width = symbol_modules(columns)
for row in matrix {
if row.length() != width {
return false
}
for index in 0.. (Int, Int, Int, Int) {
let unit = if scale <= 0 { 1 } else { scale }
let quiet_zone = if quiet < 0 { 0 } else { quiet }
if columns <= 0 || column < 0 || column >= columns || rows <= 0 {
return (0, 0, 0, 0)
}
let left = (quiet_zone + start_modules + column * 17) * unit
(left, quiet_zone * unit, 17 * unit, rows * unit)
}
///|
/// Render a matrix as text: `X` per bar, space per space, scale repeats. A
/// quiet zone of `quiet` modules is added on both sides so the result can be
/// eyeballed as a symbol.
pub fn matrix_to_ascii(
matrix : Array[Array[Int]],
scale : Int,
quiet : Int,
) -> String {
let rows : Array[String] = []
for row in matrix {
let line = StringBuilder()
for _ in 0..<(quiet * scale) {
line.write_char(' ')
}
for value in row {
for _ in 0.. 0 {
result.write_char('\n')
}
result.write_string(rows[index])
index += 1
}
result.to_string()
}
///|
/// Render a matrix as a black and white SVG document.
///
/// `scale` is the width of one module in user units and `quiet` the quiet zone
/// in modules. The output uses one path per row, so the document stays small
/// even for a thirty column symbol.
pub fn matrix_to_svg(
matrix : Array[Array[Int]],
scale : Int,
quiet : Int,
) -> String {
let unit = if scale <= 0 { 1 } else { scale }
let quiet_zone = if quiet < 0 { 0 } else { quiet }
let width = matrix_row_width(matrix)
let height = matrix.length()
let total_width = (width + 2 * quiet_zone) * unit
let total_height = (height + 2 * quiet_zone) * unit
let svg = StringBuilder()
svg.write_string("\n")
svg.write_string("\n")
svg.to_string()
}