// High level encoding: the automatic mode selector described in
// ISO/IEC 15438 annex P.
//
// The selector walks the input once. At every position it measures how many
// characters can be carried by numeric compaction, text compaction and byte
// compaction, then latches into the mode that the standard recommends. The
// result is the list of *data codewords*; the symbol length descriptor, pad
// codewords and error correction codewords are added by `finalize`.
///|
/// Result of a high level encoding pass.
pub(all) struct HighLevel {
/// Data codewords, one entry per PDF417 codeword (0..928).
codewords : Array[Int]
/// The compaction modes that were used, in order. Handy for tests and for
/// the CLI `inspect` command.
modes : Array[Compaction]
/// For every codeword, the index into `modes` describing where it came
/// from. Latch and shift codewords belong to the segment that follows.
segments : Array[Int]
}
///|
/// Pending result being built while the selector walks the input.
priv struct HighLevelBuilder {
codewords : Array[Int]
modes : Array[Compaction]
segments : Array[Int]
}
///|
fn HighLevelBuilder::new() -> HighLevelBuilder {
{ codewords: [], modes: [], segments: [], }
}
///|
fn HighLevelBuilder::push_word(
self : HighLevelBuilder,
word : Int,
segment : Int,
) -> Unit {
self.codewords.push(word)
self.segments.push(segment)
}
///|
fn HighLevelBuilder::push_words(
self : HighLevelBuilder,
words : Array[Int],
segment : Int,
) -> Unit {
for word in words {
self.push_word(word, segment)
}
}
///|
/// Index of the mode currently being emitted, or -1 when nothing has been
/// emitted yet.
fn HighLevelBuilder::current_segment(self : HighLevelBuilder) -> Int {
self.modes.length() - 1
}
///|
/// `true` if `code` is a byte that Text Compaction can carry, following the
/// "printable ASCII plus tab, newline and carriage return" rule of annex P.
pub fn is_text_byte(code : Int) -> Bool {
code == 9 || code == 10 || code == 13 || (code >= 32 && code <= 126)
}
///|
/// Number of consecutive digits starting at `start`.
pub fn consecutive_digits(input : Array[Int], start : Int) -> Int {
let mut count = 0
let mut index = start
while index < input.length() && is_digit(input[index]) {
count += 1
index += 1
}
count
}
///|
/// Number of consecutive characters that Text Compaction can carry starting
/// at `start`. The run stops before a sequence of 13 or more digits, which
/// is the point where Numeric Compaction becomes worth a mode latch; a run
/// that begins on such a digit sequence is therefore empty.
pub fn consecutive_text(input : Array[Int], start : Int) -> Int {
let length = input.length()
let mut index = start
let mut running = true
while running && index < length {
let mut numeric = 0
while numeric < 13 && index < length && is_digit(input[index]) {
numeric += 1
index += 1
}
if numeric >= 13 {
return index - numeric - start
}
if numeric == 0 {
if is_text_byte(input[index]) {
index += 1
} else {
running = false
}
}
}
index - start
}
///|
/// Number of consecutive characters that should go through byte compaction
/// starting at `start`, following annex P: the run stops before a sequence of
/// 13 or more digits so that Numeric Compaction can take over. A run that
/// begins on such a digit sequence is therefore empty.
pub fn consecutive_binary(input : Array[Int], start : Int) -> Int {
let length = input.length()
let mut index = start
while index < length {
let mut numeric = 0
while numeric < 13 &&
index + numeric < length &&
is_digit(input[index + numeric]) {
numeric += 1
}
if numeric >= 13 {
return index - start
}
index += 1
}
index - start
}
///|
/// Emit a byte run, choosing the 913 shift for a single byte while the symbol
/// is still in text mode, the 924 latch for multiples of six bytes and the
/// 901 latch otherwise. Returns `true` when the 913 shift was used, which
/// keeps the symbol in text mode. The 901 latch is followed by an explicit
/// byte count.
fn push_byte_run(
builder : HighLevelBuilder,
input : Array[Int],
start : Int,
count : Int,
in_text_mode : Bool,
segment : Int,
) -> Bool {
if count <= 0 {
return in_text_mode
}
if count == 1 && in_text_mode {
builder.push_word(shift_byte, segment)
builder.push_word(input[start], segment)
return true
}
if is_byte_group_multiple(count) {
builder.push_word(latch_byte, segment)
} else {
builder.push_word(latch_byte_padded, segment)
if count < 900 {
builder.push_word(count, segment)
} else {
builder.push_word(count / 900, segment)
builder.push_word(count % 900, segment)
}
}
let body = try encode_byte_segment(input, start, count) catch {
_ => []
} noraise {
words => words
}
builder.push_words(body, segment)
false
}
///|
/// Run the automatic mode selector over `input`.
///
/// Throws `EmptyInput` for an empty input and `UnrepresentableChar` for a
/// code unit outside 0..255.
pub fn encode_high_level(input : Array[Int]) -> HighLevel raise EncodeError {
if input.is_empty() {
raise EmptyInput
}
for code in input {
if code < 0 || code > max_byte_value {
raise UnrepresentableChar(code)
}
}
let builder = HighLevelBuilder::new()
let length = input.length()
let mut position = 0
let mut in_text_mode = true
let mut submode : TextSubmode = Alpha
while position < length {
let digits = consecutive_digits(input, position)
if digits >= 13 {
let segment = builder.modes.length()
builder.modes.push(Numeric)
builder.push_word(latch_numeric, segment)
let body = try encode_numeric_segment(input, position, digits) catch {
_ => []
} noraise {
words => words
}
builder.push_words(body, segment)
in_text_mode = false
submode = Alpha
position += digits
continue
}
let text_run = consecutive_text(input, position)
// Text Compaction wins when the run is long enough (annex P: `t >= 5`),
// or when the rest of the input is digit free (`n == len - p`). A run of
// length zero must never be selected, otherwise the walk cannot advance.
let take_text = text_run >= 5 ||
(digits == 0 && text_run == length - position && text_run > 0)
if take_text {
let target = if in_text_mode && !builder.modes.is_empty() {
builder.current_segment()
} else {
let index = builder.modes.length()
builder.modes.push(Text)
if !in_text_mode {
builder.push_word(latch_text, index)
}
index
}
let encoded = try
encode_text_segment(input, position, text_run, submode)
catch {
_ => ([], Alpha)
} noraise {
value => value
}
let (words, next_submode) = encoded
builder.push_words(words, target)
submode = next_submode
in_text_mode = true
position += text_run
continue
}
let mut binary_run = consecutive_binary(input, position)
if binary_run == 0 {
binary_run = 1
}
let segment = builder.modes.length()
builder.modes.push(Byte)
in_text_mode = push_byte_run(
builder, input, position, binary_run, in_text_mode, segment,
)
submode = Alpha
position += binary_run
}
{
codewords: builder.codewords,
modes: builder.modes,
segments: builder.segments,
}
}
///|
/// High level encoding without error handling.
pub fn encode_high_level_opt(input : Array[Int]) -> HighLevel? {
try encode_high_level(input) catch {
_ => None
} noraise {
level => Some(level)
}
}
///|
/// Encode text with the packed Text Compaction sub-modes only. The latch
/// codeword is not emitted, matching a symbol whose first data codeword is
/// already in text mode.
pub fn encode_text_only(input : Array[Int]) -> Array[Int] raise EncodeError {
encode_text(input)
}
///|
/// Encode bytes with Byte Compaction only, including the 901/924 latch and
/// the explicit length count.
pub fn encode_bytes_only(input : Array[Int]) -> Array[Int] raise EncodeError {
if input.is_empty() {
raise EmptyInput
}
let builder = HighLevelBuilder::new()
builder.modes.push(Byte)
let _ = push_byte_run(builder, input, 0, input.length(), false, 0)
builder.codewords
}
///|
/// Encode digits with Numeric Compaction only, including the 902 latch.
pub fn encode_numeric_only(input : Array[Int]) -> Array[Int] raise EncodeError {
if input.is_empty() {
raise EmptyInput
}
let result : Array[Int] = [latch_numeric]
for word in encode_numeric(input) {
result.push(word)
}
result
}