Skip to main content

rxing/oned/
code_128_writer.rs

1/*
2 * Copyright 2010 ZXing authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use rxing_one_d_proc_derive::OneDWriter;
18
19use crate::BarcodeFormat;
20use crate::common::Result;
21
22use super::{OneDimensionalCodeWriter, code_128_reader};
23
24const CODE_START_A: usize = 103;
25const CODE_START_B: usize = 104;
26const CODE_START_C: usize = 105;
27const CODE_CODE_A: usize = 101;
28const CODE_CODE_B: usize = 100;
29const CODE_CODE_C: usize = 99;
30const CODE_STOP: usize = 106;
31
32// Dummy characters used to specify control characters in input
33const ESCAPE_FNC_1: char = '\u{00f1}';
34const ESCAPE_FNC_2: char = '\u{00f2}';
35const ESCAPE_FNC_3: char = '\u{00f3}';
36const ESCAPE_FNC_4: char = '\u{00f4}';
37
38const CODE_FNC_1: usize = 102; // Code A, Code B, Code C
39const CODE_FNC_2: usize = 97; // Code A, Code B
40const CODE_FNC_3: usize = 96; // Code A, Code B
41const CODE_FNC_4_A: usize = 101; // Code A
42const CODE_FNC_4_B: usize = 100; // Code B
43
44// RXingResults of minimal lookahead for code C
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46enum CType {
47    Uncodable,
48    OneDigit,
49    TwoDigits,
50    Fnc1,
51}
52
53/**
54 * This object renders a CODE128 code as a {@link BitMatrix}.
55 *
56 * @author erik.barbara@gmail.com (Erik Barbara)
57 */
58#[derive(OneDWriter, Default)]
59pub struct Code128Writer;
60
61impl OneDimensionalCodeWriter for Code128Writer {
62    fn encode_oned(&self, contents: &str) -> Result<Vec<bool>> {
63        self.encode_oned_with_hints(contents, &EncodeHints::default())
64    }
65
66    fn getSupportedWriteFormats(&self) -> Option<Vec<crate::BarcodeFormat>> {
67        Some(vec![BarcodeFormat::CODE_128])
68    }
69
70    fn encode_oned_with_hints(
71        &self,
72        contents: &str,
73        hints: &crate::EncodeHints,
74    ) -> Result<Vec<bool>> {
75        let forcedCodeSet = check(contents, hints)?;
76
77        let hasCompactionHint = hints.Code128Compact.unwrap_or(false);
78        // let hasCompactionHint = if let Some(EncodeHintValue::Code128Compact(compat)) =
79        //     hints.get(&EncodeHintType::CODE128_COMPACT)
80        // {
81        //     *compat
82        // } else {
83        //     false
84        // };
85        // let hasCompactionHint = hints != null && hints.containsKey(EncodeHintType::CODE128_COMPACT) &&
86        //     Boolean.parseBoolean(hints.get(EncodeHintType::CODE128_COMPACT).toString());
87
88        if hasCompactionHint {
89            MinimalEncoder::encode(contents)
90        } else {
91            encodeFast(contents, forcedCodeSet)
92        }
93    }
94}
95
96fn check(contents: &str, hints: &crate::EncodeHints) -> Result<i32> {
97    let length = contents.chars().count();
98    // Check length
99    if !(1..=80).contains(&length) {
100        return Err(Exceptions::illegal_argument_with(format!(
101            "Contents length should be between 1 and 80 characters, but got {length}"
102        )));
103    }
104
105    // Check for forced code set hint.
106    let mut forcedCodeSet = -1_i32;
107    if let Some(codeSetHint) = &hints.ForceCodeSet {
108        match codeSetHint.as_str() {
109            "A" => forcedCodeSet = CODE_CODE_A as i32,
110            "B" => forcedCodeSet = CODE_CODE_B as i32,
111            "C" => forcedCodeSet = CODE_CODE_C as i32,
112            _ => {
113                return Err(Exceptions::illegal_argument_with(format!(
114                    "Unsupported code set hint: {codeSetHint}"
115                )));
116            }
117        }
118    }
119
120    // Check content
121    for ch in contents.chars() {
122        let c = ch as u32;
123        // for (int i = 0; i < length; i++) {
124        //   char c = contents.charAt(i);
125        // check for non ascii characters that are not special GS1 characters
126        match ch {
127            // special function characters
128            ESCAPE_FNC_1 | ESCAPE_FNC_2 | ESCAPE_FNC_3 | ESCAPE_FNC_4 => {}
129            // non ascii characters
130            _ => {
131                if c > 127 {
132                    // no full Latin-1 character set available at the moment
133                    // shift and manual code change are not supported
134                    return Err(Exceptions::illegal_argument_with(format!(
135                        "Bad character in input: ASCII value={c}"
136                    )));
137                }
138            }
139        }
140        // check characters for compatibility with forced code set
141        const CODE_CODE_A_I32: i32 = CODE_CODE_A as i32;
142        const CODE_CODE_B_I32: i32 = CODE_CODE_B as i32;
143        const CODE_CODE_C_I32: i32 = CODE_CODE_C as i32;
144        match forcedCodeSet {
145            CODE_CODE_A_I32 =>
146            // allows no ascii above 95 (no lower caps, no special symbols)
147            {
148                if c > 95 && c <= 127 {
149                    return Err(Exceptions::illegal_argument_with(format!(
150                        "Bad character in input for forced code set A: ASCII value={c}"
151                    )));
152                }
153            }
154            CODE_CODE_B_I32 =>
155            // allows no ascii below 32 (terminal symbols)
156            {
157                if c <= 32 {
158                    return Err(Exceptions::illegal_argument_with(format!(
159                        "Bad character in input for forced code set B: ASCII value={c}"
160                    )));
161                }
162            }
163            CODE_CODE_C_I32 =>
164            // allows only numbers and no FNC 2/3/4
165            {
166                if c < 48
167                    || (c > 57 && c <= 127)
168                    || ch == ESCAPE_FNC_2
169                    || ch == ESCAPE_FNC_3
170                    || ch == ESCAPE_FNC_4
171                {
172                    return Err(Exceptions::illegal_argument_with(format!(
173                        "Bad character in input for forced code set C: ASCII value={c}"
174                    )));
175                }
176            }
177            _ => {}
178        }
179    }
180    Ok(forcedCodeSet)
181}
182
183fn encodeFast(contents: &str, forcedCodeSet: i32) -> Result<Vec<bool>> {
184    let length = contents.chars().count();
185
186    let mut patterns: Vec<Vec<usize>> = Vec::new(); //new ArrayList<>(); // temporary storage for patterns
187    let mut checkSum = 0;
188    let mut checkWeight = 1;
189    let mut codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C)
190    let mut position = 0; // position in contents
191
192    while position < length {
193        //Select code to use
194        let newCodeSet = if forcedCodeSet == -1 {
195            chooseCode(contents, position, codeSet).ok_or(Exceptions::ILLEGAL_STATE)?
196        } else {
197            forcedCodeSet as usize // THIS IS RISKY
198        };
199
200        //Get the pattern index
201        let mut patternIndex: isize;
202        if newCodeSet == codeSet {
203            // Encode the current character
204            // First handle escapes
205            match contents
206                .chars()
207                .nth(position)
208                .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
209            {
210                ESCAPE_FNC_1 => patternIndex = CODE_FNC_1 as isize,
211                ESCAPE_FNC_2 => patternIndex = CODE_FNC_2 as isize,
212                ESCAPE_FNC_3 => patternIndex = CODE_FNC_3 as isize,
213                ESCAPE_FNC_4 => {
214                    if codeSet == CODE_CODE_A {
215                        patternIndex = CODE_FNC_4_A as isize;
216                    } else {
217                        patternIndex = CODE_FNC_4_B as isize;
218                    }
219                }
220                _ =>
221                // Then handle normal characters otherwise
222                {
223                    match codeSet {
224                        CODE_CODE_A => {
225                            patternIndex = contents
226                                .chars()
227                                .nth(position)
228                                .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
229                                as isize
230                                - ' ' as isize;
231                            if patternIndex < 0 {
232                                // everything below a space character comes behind the underscore in the code patterns table
233                                patternIndex += '`' as isize;
234                            }
235                        }
236                        CODE_CODE_B => {
237                            patternIndex = contents
238                                .chars()
239                                .nth(position)
240                                .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
241                                as isize
242                                - ' ' as isize
243                        }
244                        _ => {
245                            // CODE_CODE_C
246                            if position + 1 == length {
247                                // this is the last character, but the encoding is C, which always encodes two characers
248                                return Err(Exceptions::illegal_argument_with(
249                                    "Bad number of characters for digit only encoding.",
250                                ));
251                            }
252                            let s: String = contents
253                                .char_indices()
254                                .skip(position)
255                                .take(2)
256                                .map(|(_u, c)| c)
257                                .collect();
258                            patternIndex = s.parse::<isize>().map_err(|e| {
259                                Exceptions::parse_with(format!("issue parsing {s}: {e}"))
260                            })?;
261                            position += 1;
262                        } // Also incremented below
263                    }
264                }
265            }
266            position += 1;
267        } else {
268            // Should we change the current code?
269            // Do we have a code set?
270            if codeSet == 0 {
271                // No, we don't have a code set
272                match newCodeSet {
273                    CODE_CODE_A => patternIndex = CODE_START_A as isize,
274                    CODE_CODE_B => patternIndex = CODE_START_B as isize,
275                    _ => patternIndex = CODE_START_C as isize,
276                }
277            } else {
278                // Yes, we have a code set
279                patternIndex = newCodeSet as isize;
280            }
281            codeSet = newCodeSet;
282        }
283
284        // Get the pattern
285        patterns.push(
286            code_128_reader::CODE_PATTERNS[patternIndex as usize]
287                .iter()
288                .map(|x| *x as usize)
289                .collect(),
290        );
291
292        // Compute checksum
293        checkSum += patternIndex * checkWeight;
294        if position != 0 {
295            checkWeight += 1;
296        }
297    }
298
299    Ok(produceRXingResult(&mut patterns, checkSum as usize))
300}
301
302fn produceRXingResult(patterns: &mut Vec<Vec<usize>>, checkSum: usize) -> Vec<bool> {
303    // Compute and append checksum
304    let mut checkSum = checkSum;
305    checkSum %= 103;
306    patterns.push(
307        code_128_reader::CODE_PATTERNS[checkSum]
308            .iter()
309            .map(|x| *x as usize)
310            .collect(),
311    );
312
313    // Append stop code
314    patterns.push(
315        code_128_reader::CODE_PATTERNS[CODE_STOP]
316            .iter()
317            .map(|x| *x as usize)
318            .collect(),
319    );
320
321    // Compute code width
322    let mut codeWidth = 0_usize;
323    for pattern in &mut *patterns {
324        codeWidth += pattern.iter().sum::<usize>();
325    }
326
327    // Compute result
328    let mut result = vec![false; codeWidth];
329    let mut pos = 0;
330    for pattern in patterns {
331        // for (int[] pattern : patterns) {
332        pos += Code128Writer::appendPattern(&mut result, pos, pattern, true) as usize;
333    }
334
335    result
336}
337
338fn findCType(value: &str, start: usize) -> Option<CType> {
339    let last = value.chars().count();
340    if start >= last {
341        return Some(CType::Uncodable);
342    }
343    let c = value.chars().nth(start)?;
344    if c == ESCAPE_FNC_1 {
345        return Some(CType::Fnc1);
346    }
347    if !c.is_ascii_digit() {
348        return Some(CType::Uncodable);
349    }
350    if start + 1 >= last {
351        return Some(CType::OneDigit);
352    }
353    let c = value.chars().nth(start + 1)?;
354    if !c.is_ascii_digit() {
355        return Some(CType::OneDigit);
356    }
357    Some(CType::TwoDigits)
358}
359
360fn chooseCode(value: &str, start: usize, oldCode: usize) -> Option<usize> {
361    let mut lookahead = findCType(value, start)?;
362    if lookahead == CType::OneDigit {
363        if oldCode == CODE_CODE_A {
364            return Some(CODE_CODE_A);
365        }
366        return Some(CODE_CODE_B);
367    }
368    if lookahead == CType::Uncodable {
369        if start < value.chars().count() {
370            let c = value.chars().nth(start)?;
371            if c < ' '
372                || (oldCode == CODE_CODE_A && (c < '`' || (c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)))
373            {
374                // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4
375                return Some(CODE_CODE_A);
376            }
377        }
378        return Some(CODE_CODE_B); // no choice
379    }
380    if oldCode == CODE_CODE_A && lookahead == CType::Fnc1 {
381        return Some(CODE_CODE_A);
382    }
383    if oldCode == CODE_CODE_C {
384        // can continue in code C
385        return Some(CODE_CODE_C);
386    }
387    if oldCode == CODE_CODE_B {
388        if lookahead == CType::Fnc1 {
389            return Some(CODE_CODE_B); // can continue in code B
390        }
391        // Seen two consecutive digits, see what follows
392        lookahead = findCType(value, start + 2)?;
393        if lookahead == CType::Uncodable || lookahead == CType::OneDigit {
394            return Some(CODE_CODE_B); // not worth switching now
395        }
396        if lookahead == CType::Fnc1 {
397            // two digits, then FNC_1...
398            lookahead = findCType(value, start + 3)?;
399            if lookahead == CType::TwoDigits {
400                // then two more digits, switch
401                return Some(CODE_CODE_C);
402            } else {
403                return Some(CODE_CODE_B); // otherwise not worth switching
404            }
405        }
406        // At this point, there are at least 4 consecutive digits.
407        // Look ahead to choose whether to switch now or on the next round.
408        let mut index = start + 4;
409        let mut lookahead = findCType(value, index)?;
410        while lookahead == CType::TwoDigits {
411            // while (lookahead = findCType(value, index)) == CType::TWO_DIGITS {
412            index += 2;
413            lookahead = findCType(value, index)?;
414        }
415        if lookahead == CType::OneDigit {
416            // odd number of digits, switch later
417            return Some(CODE_CODE_B);
418        }
419        return Some(CODE_CODE_C); // even number of digits, switch now
420    }
421    // Here oldCode == 0, which means we are choosing the initial code
422    if lookahead == CType::Fnc1 {
423        // ignore FNC_1
424        lookahead = findCType(value, start + 1)?;
425    }
426    if lookahead == CType::TwoDigits {
427        // at least two digits, start in code C
428        return Some(CODE_CODE_C);
429    }
430    Some(CODE_CODE_B)
431}
432
433/**
434 * Encodes minimally using Divide-And-Conquer with Memoization
435 **/
436// struct MinimalEncoder {
437//    memoizedCost:Vec<Vec<u32>>,
438//    minPath:Vec<Vec<Latch>>,
439// }
440mod MinimalEncoder {
441    use crate::{Exceptions, common::Result, oned::code_128_reader};
442
443    use super::{
444        CODE_CODE_A, CODE_CODE_B, CODE_CODE_C, CODE_FNC_1, CODE_FNC_2, CODE_FNC_3, CODE_FNC_4_A,
445        CODE_FNC_4_B, CODE_START_A, CODE_START_B, CODE_START_C, ESCAPE_FNC_1, ESCAPE_FNC_2,
446        ESCAPE_FNC_3, ESCAPE_FNC_4, produceRXingResult,
447    };
448
449    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
450    enum Charset {
451        A,
452        B,
453        C,
454        None,
455    }
456    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
457    enum Latch {
458        A,
459        B,
460        C,
461        Shift,
462        None,
463    }
464
465    const A : &str = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u{0000}\u{0001}\u{0002}/
466\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{0008}\u{0009}\n\u{000B}\u{000C}\r\u{000E}\u{000F}\u{0010}\u{0011}/
467\u{0012}\u{0013}\u{0014}\u{0015}\u{0016}\u{0017}\u{0018}\u{0019}\u{001A}\u{001B}\u{001C}\u{001D}\u{001E}\u{001F}/
468\u{00FF}";
469    const B: &str = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr\
470stuvwxyz{|}~\u{007F}\u{00FF}";
471
472    const CODE_SHIFT: usize = 98;
473
474    pub fn encode(contents: &str) -> Result<Vec<bool>> {
475        let length = contents.chars().count();
476        let mut memoizedCost = vec![vec![0_u32; length]; 4]; //new int[4][contents.length()];
477        let mut minPath = vec![vec![Latch::None; length]; 4]; //new Latch[4][contents.length()];
478
479        encode_with_start_position(contents, Charset::None, 0, &mut memoizedCost, &mut minPath)?;
480
481        let mut patterns: Vec<Vec<usize>> = Vec::new(); //new ArrayList<>();
482        let mut checkSum = vec![0_usize]; //new int[] {0};
483        let mut checkWeight = vec![1]; //new int[] {1};
484        let mut charset = Charset::None;
485        let mut i = 0;
486        while i < length {
487            // for i in 0..length {
488            // for (int i = 0; i < length; i++) {
489            let latch = minPath[charset.ordinal()][i];
490            match latch {
491                Latch::A => {
492                    charset = Charset::A;
493                    addPattern(
494                        &mut patterns,
495                        if i == 0 { CODE_START_A } else { CODE_CODE_A },
496                        &mut checkSum,
497                        &mut checkWeight,
498                        i,
499                    );
500                }
501                Latch::B => {
502                    charset = Charset::B;
503                    addPattern(
504                        &mut patterns,
505                        if i == 0 { CODE_START_B } else { CODE_CODE_B },
506                        &mut checkSum,
507                        &mut checkWeight,
508                        i,
509                    );
510                }
511                Latch::C => {
512                    charset = Charset::C;
513                    addPattern(
514                        &mut patterns,
515                        if i == 0 { CODE_START_C } else { CODE_CODE_C },
516                        &mut checkSum,
517                        &mut checkWeight,
518                        i,
519                    );
520                }
521                Latch::Shift => addPattern(
522                    &mut patterns,
523                    CODE_SHIFT,
524                    &mut checkSum,
525                    &mut checkWeight,
526                    i,
527                ),
528                Latch::None => { /* skip */ }
529            }
530            if charset == Charset::C {
531                if contents
532                    .chars()
533                    .nth(i)
534                    .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
535                    == ESCAPE_FNC_1
536                {
537                    addPattern(
538                        &mut patterns,
539                        CODE_FNC_1,
540                        &mut checkSum,
541                        &mut checkWeight,
542                        i,
543                    );
544                } else {
545                    let s: String = contents
546                        .char_indices()
547                        .skip(i)
548                        .take(2)
549                        .map(|(_u, c)| c)
550                        .collect();
551                    addPattern(
552                        &mut patterns,
553                        s.parse::<usize>().map_err(|e| {
554                            Exceptions::parse_with(format!("unable to parse {s} {e}"))
555                        })?,
556                        &mut checkSum,
557                        &mut checkWeight,
558                        i,
559                    );
560                    assert!(i + 1 < length); //the algorithm never leads to a single trailing digit in character set C
561                    if i + 1 < length {
562                        i += 1;
563                    }
564                }
565            } else {
566                // charset A or B
567                let mut patternIndex = match contents
568                    .chars()
569                    .nth(i)
570                    .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
571                {
572                    ESCAPE_FNC_1 => CODE_FNC_1 as isize,
573                    ESCAPE_FNC_2 => CODE_FNC_2 as isize,
574                    ESCAPE_FNC_3 => CODE_FNC_3 as isize,
575                    ESCAPE_FNC_4 => {
576                        if (charset == Charset::A && latch != Latch::Shift)
577                            || (charset == Charset::B && latch == Latch::Shift)
578                        {
579                            CODE_FNC_4_A as isize
580                        } else {
581                            CODE_FNC_4_B as isize
582                        }
583                    }
584                    _ => {
585                        contents
586                            .chars()
587                            .nth(i)
588                            .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
589                            as isize
590                            - ' ' as isize
591                    }
592                };
593                if ((charset == Charset::A && latch != Latch::Shift)
594                    || (charset == Charset::B && latch == Latch::Shift))
595                    && patternIndex < 0
596                {
597                    patternIndex += '`' as isize;
598                }
599                addPattern(
600                    &mut patterns,
601                    patternIndex as usize,
602                    &mut checkSum,
603                    &mut checkWeight,
604                    i,
605                );
606            }
607
608            i += 1;
609        }
610        // memoizedCost.clear();
611        // minPath.clear();
612
613        Ok(produceRXingResult(&mut patterns, checkSum[0]))
614    }
615
616    fn addPattern(
617        patterns: &mut Vec<Vec<usize>>,
618        patternIndex: usize,
619        checkSum: &mut [usize],
620        checkWeight: &mut [u32],
621        position: usize,
622    ) {
623        patterns.push(
624            code_128_reader::CODE_PATTERNS[patternIndex]
625                .iter()
626                .map(|x| *x as usize)
627                .collect(),
628        );
629        if position != 0 {
630            checkWeight[0] += 1;
631        }
632        checkSum[0] += patternIndex * checkWeight[0] as usize;
633    }
634
635    fn isDigit(c: char) -> bool {
636        c.is_ascii_digit()
637    }
638
639    fn canEncode(contents: &str, charset: Charset, position: usize) -> bool {
640        let Some(c) = contents.chars().nth(position) else {
641            return false;
642        };
643        match charset {
644            Charset::A => {
645                c == ESCAPE_FNC_1
646                    || c == ESCAPE_FNC_2
647                    || c == ESCAPE_FNC_3
648                    || c == ESCAPE_FNC_4
649                    || A.find(c).is_some()
650            }
651            Charset::B => {
652                c == ESCAPE_FNC_1
653                    || c == ESCAPE_FNC_2
654                    || c == ESCAPE_FNC_3
655                    || c == ESCAPE_FNC_4
656                    || B.find(c).is_some()
657            }
658            Charset::C => {
659                let Some(c_p_1) = contents.chars().nth(position + 1) else {
660                    return false;
661                };
662                c == ESCAPE_FNC_1
663                    || (position + 1 < contents.chars().count() && isDigit(c) && isDigit(c_p_1))
664            }
665            _ => false,
666        }
667    }
668
669    /**
670     * Encode the string starting at position position starting with the character set charset
671     **/
672    fn encode_with_start_position(
673        contents: &str,
674        charset: Charset,
675        position: usize,
676        memoizedCost: &mut Vec<Vec<u32>>,
677        minPath: &mut Vec<Vec<Latch>>,
678    ) -> Result<u32> {
679        if position >= contents.chars().count() {
680            return Err(Exceptions::ILLEGAL_STATE);
681        }
682        let mCost = memoizedCost[charset.ordinal()][position];
683        if mCost > 0 {
684            return Ok(mCost);
685        }
686
687        let mut minCost = u32::MAX;
688        let mut minLatch = Latch::None;
689        let atEnd = position + 1 >= contents.chars().count();
690
691        let sets = [Charset::A, Charset::B];
692        for i in 0..=1 {
693            // for (int i = 0; i <= 1; i++) {
694            if canEncode(contents, sets[i], position) {
695                let mut cost = 1;
696                let mut latch = Latch::None;
697                if charset != sets[i] {
698                    cost += 1;
699                    latch = sets[i].into();
700                }
701                if !atEnd {
702                    cost += encode_with_start_position(
703                        contents,
704                        sets[i],
705                        position + 1,
706                        memoizedCost,
707                        minPath,
708                    )?;
709                }
710                if cost < minCost {
711                    minCost = cost;
712                    minLatch = latch;
713                }
714                cost = 1;
715                if charset == sets[(i + 1) % 2] {
716                    cost += 1;
717                    latch = Latch::Shift;
718                    if !atEnd {
719                        cost += encode_with_start_position(
720                            contents,
721                            charset,
722                            position + 1,
723                            memoizedCost,
724                            minPath,
725                        )?;
726                    }
727                    if cost < minCost {
728                        minCost = cost;
729                        minLatch = latch;
730                    }
731                }
732            }
733        }
734        if canEncode(contents, Charset::C, position) {
735            let mut cost = 1;
736            let mut latch = Latch::None;
737            if charset != Charset::C {
738                cost += 1;
739                latch = Latch::C;
740            }
741            let advance = if contents.chars().nth(position).unwrap_or_default() == ESCAPE_FNC_1 {
742                1
743            } else {
744                2
745            };
746            if position + advance < contents.chars().count() {
747                cost += encode_with_start_position(
748                    contents,
749                    Charset::C,
750                    position + advance,
751                    memoizedCost,
752                    minPath,
753                )?;
754            }
755            if cost < minCost {
756                minCost = cost;
757                minLatch = latch;
758            }
759        }
760        if minCost == u32::MAX {
761            return Err(Exceptions::illegal_argument_with(format!(
762                "Bad character in input: ASCII value={}",
763                contents.chars().nth(position).unwrap_or('x')
764            )));
765            // throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) contents.charAt(position));
766        }
767        memoizedCost[charset.ordinal()][position] = minCost;
768        minPath[charset.ordinal()][position] = minLatch;
769        Ok(minCost)
770    }
771
772    trait HasOrdinal {
773        fn ordinal(&self) -> usize;
774    }
775
776    impl HasOrdinal for Charset {
777        fn ordinal(&self) -> usize {
778            match self {
779                Charset::A => 0,
780                Charset::B => 1,
781                Charset::C => 2,
782                Charset::None => 3,
783            }
784        }
785    }
786    impl HasOrdinal for Latch {
787        fn ordinal(&self) -> usize {
788            match self {
789                Latch::A => 0,
790                Latch::B => 1,
791                Latch::C => 2,
792                Latch::Shift => 3,
793                Latch::None => 4,
794            }
795        }
796    }
797    impl From<Charset> for Latch {
798        fn from(cs: Charset) -> Self {
799            match cs {
800                Charset::A => Latch::A,
801                Charset::B => Latch::B,
802                Charset::C => Latch::C,
803                Charset::None => Latch::None,
804            }
805        }
806    }
807}