1use 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
32const 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; const CODE_FNC_2: usize = 97; const CODE_FNC_3: usize = 96; const CODE_FNC_4_A: usize = 101; const CODE_FNC_4_B: usize = 100; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
46enum CType {
47 Uncodable,
48 OneDigit,
49 TwoDigits,
50 Fnc1,
51}
52
53#[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 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 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 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 for ch in contents.chars() {
122 let c = ch as u32;
123 match ch {
127 ESCAPE_FNC_1 | ESCAPE_FNC_2 | ESCAPE_FNC_3 | ESCAPE_FNC_4 => {}
129 _ => {
131 if c > 127 {
132 return Err(Exceptions::illegal_argument_with(format!(
135 "Bad character in input: ASCII value={c}"
136 )));
137 }
138 }
139 }
140 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 {
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 {
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 {
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(); let mut checkSum = 0;
188 let mut checkWeight = 1;
189 let mut codeSet = 0; let mut position = 0; while position < length {
193 let newCodeSet = if forcedCodeSet == -1 {
195 chooseCode(contents, position, codeSet).ok_or(Exceptions::ILLEGAL_STATE)?
196 } else {
197 forcedCodeSet as usize };
199
200 let mut patternIndex: isize;
202 if newCodeSet == codeSet {
203 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 {
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 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 if position + 1 == length {
247 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 } }
264 }
265 }
266 position += 1;
267 } else {
268 if codeSet == 0 {
271 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 patternIndex = newCodeSet as isize;
280 }
281 codeSet = newCodeSet;
282 }
283
284 patterns.push(
286 code_128_reader::CODE_PATTERNS[patternIndex as usize]
287 .iter()
288 .map(|x| *x as usize)
289 .collect(),
290 );
291
292 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 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 patterns.push(
315 code_128_reader::CODE_PATTERNS[CODE_STOP]
316 .iter()
317 .map(|x| *x as usize)
318 .collect(),
319 );
320
321 let mut codeWidth = 0_usize;
323 for pattern in &mut *patterns {
324 codeWidth += pattern.iter().sum::<usize>();
325 }
326
327 let mut result = vec![false; codeWidth];
329 let mut pos = 0;
330 for pattern in patterns {
331 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 return Some(CODE_CODE_A);
376 }
377 }
378 return Some(CODE_CODE_B); }
380 if oldCode == CODE_CODE_A && lookahead == CType::Fnc1 {
381 return Some(CODE_CODE_A);
382 }
383 if oldCode == CODE_CODE_C {
384 return Some(CODE_CODE_C);
386 }
387 if oldCode == CODE_CODE_B {
388 if lookahead == CType::Fnc1 {
389 return Some(CODE_CODE_B); }
391 lookahead = findCType(value, start + 2)?;
393 if lookahead == CType::Uncodable || lookahead == CType::OneDigit {
394 return Some(CODE_CODE_B); }
396 if lookahead == CType::Fnc1 {
397 lookahead = findCType(value, start + 3)?;
399 if lookahead == CType::TwoDigits {
400 return Some(CODE_CODE_C);
402 } else {
403 return Some(CODE_CODE_B); }
405 }
406 let mut index = start + 4;
409 let mut lookahead = findCType(value, index)?;
410 while lookahead == CType::TwoDigits {
411 index += 2;
413 lookahead = findCType(value, index)?;
414 }
415 if lookahead == CType::OneDigit {
416 return Some(CODE_CODE_B);
418 }
419 return Some(CODE_CODE_C); }
421 if lookahead == CType::Fnc1 {
423 lookahead = findCType(value, start + 1)?;
425 }
426 if lookahead == CType::TwoDigits {
427 return Some(CODE_CODE_C);
429 }
430 Some(CODE_CODE_B)
431}
432
433mod 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]; let mut minPath = vec![vec![Latch::None; length]; 4]; encode_with_start_position(contents, Charset::None, 0, &mut memoizedCost, &mut minPath)?;
480
481 let mut patterns: Vec<Vec<usize>> = Vec::new(); let mut checkSum = vec![0_usize]; let mut checkWeight = vec![1]; let mut charset = Charset::None;
485 let mut i = 0;
486 while i < length {
487 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 => { }
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); if i + 1 < length {
562 i += 1;
563 }
564 }
565 } else {
566 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 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 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 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 }
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}