Skip to main content

rxing/datamatrix/encoder/
high_level_encoder.rs

1/*
2 * Copyright 2006-2007 Jeremias Maerki.
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 std::sync::Arc;
18
19use crate::common::{CharacterSet, Result};
20use crate::{Dimension, Exceptions};
21
22use super::{
23    ASCIIEncoder, Base256Encoder, C40Encoder, EdifactEncoder, Encoder, EncoderContext,
24    SymbolInfoLookup, SymbolShapeHint, TextEncoder, X12Encoder,
25};
26#[allow(dead_code)]
27const DEFAULT_ENCODING: CharacterSet = CharacterSet::ISO8859_1;
28
29/**
30 * DataMatrix ECC 200 data encoder following the algorithm described in ISO/IEC 16022:200(E) in
31 * annex S.
32 */
33/**
34 * Padding character
35 */
36const PAD: u8 = 129;
37/**
38 * mode latch to C40 encodation mode
39 */
40pub const LATCH_TO_C40: u8 = 230;
41/**
42 * mode latch to Base 256 encodation mode
43 */
44pub const LATCH_TO_BASE256: u8 = 231;
45/**
46 * FNC1 Codeword
47 */
48//private static final char FNC1 = 232;
49/**
50 * Structured Append Codeword
51 */
52//private static final char STRUCTURED_APPEND = 233;
53/**
54 * Reader Programming
55 */
56//private static final char READER_PROGRAMMING = 234;
57/**
58 * Upper Shift
59 */
60pub const UPPER_SHIFT: u8 = 235;
61/**
62 * 05 Macro
63 */
64const MACRO_05: u8 = 236;
65/**
66 * 06 Macro
67 */
68const MACRO_06: u8 = 237;
69/**
70 * mode latch to ANSI X.12 encodation mode
71 */
72pub const LATCH_TO_ANSIX12: u8 = 238;
73/**
74 * mode latch to Text encodation mode
75 */
76pub const LATCH_TO_TEXT: u8 = 239;
77/**
78 * mode latch to EDIFACT encodation mode
79 */
80pub const LATCH_TO_EDIFACT: u8 = 240;
81/*
82 * ECI character (Extended Channel Interpretation)
83 */
84//private static final char ECI = 241;
85
86/**
87 * Unlatch from C40 encodation
88 */
89pub const C40_UNLATCH: u8 = 254;
90/**
91 * Unlatch from X12 encodation
92 */
93pub const X12_UNLATCH: u8 = 254;
94
95/**
96 * 05 Macro header
97 */
98pub const MACRO_05_HEADER: &str = "[)>\u{001E}05\u{001D}"; // THIS MIGHT BE WRONG, CHECK IF IT SHOULD BE a long unicode
99/**
100 * 06 Macro header
101 */
102pub const MACRO_06_HEADER: &str = "[)>\u{001E}06\u{001D}"; // THIS MIGHT BE WRONG, CHECK IF IT SHOULD BE a long unicode
103/**
104 * Macro trailer
105 */
106pub const MACRO_TRAILER: &str = "\u{001E}\u{0004}";
107
108pub const ASCII_ENCODATION: usize = 0;
109pub const C40_ENCODATION: usize = 1;
110pub const TEXT_ENCODATION: usize = 2;
111pub const X12_ENCODATION: usize = 3;
112pub const EDIFACT_ENCODATION: usize = 4;
113pub const BASE256_ENCODATION: usize = 5;
114
115fn randomize253State(codewordPosition: u32) -> String {
116    let pseudoRandom = ((149 * codewordPosition) % 253) + 1;
117    let tempVariable = PAD as u32 + pseudoRandom;
118    if tempVariable <= 254 {
119        char::from_u32(tempVariable)
120    } else {
121        char::from_u32(tempVariable - 254)
122    }
123    // .expect("must become a char")
124    .unwrap_or(char::default())
125    .to_string()
126    // return (char) (tempVariable <= 254 ? tempVariable : tempVariable - 254);
127}
128
129/**
130 * Performs message encoding of a DataMatrix message using the algorithm described in annex P
131 * of ISO/IEC 16022:2000(E).
132 *
133 * @param msg the message
134 * @return the encoded message (the char values range from 0 to 255)
135 */
136pub fn encodeHighLevel(msg: &str) -> Result<String> {
137    encodeHighLevelWithDimensionForceC40(msg, SymbolShapeHint::FORCE_NONE, None, None, false)
138}
139
140/**
141 * Performs message encoding of a DataMatrix message using the algorithm described in annex P
142 * of ISO/IEC 16022:2000(E).
143 *
144 * @param msg the message
145 * @return the encoded message (the char values range from 0 to 255)
146 */
147pub fn encodeHighLevelSIL(msg: &str, symbol_lookup: Option<SymbolInfoLookup>) -> Result<String> {
148    encodeHighLevelWithDimensionForceC40WithSymbolInfoLookup(
149        msg,
150        SymbolShapeHint::FORCE_NONE,
151        None,
152        None,
153        false,
154        symbol_lookup,
155    )
156}
157
158/**
159 * Performs message encoding of a DataMatrix message using the algorithm described in annex P
160 * of ISO/IEC 16022:2000(E).
161 *
162 * @param msg     the message
163 * @param shape   requested shape. May be {@code SymbolShapeHint.FORCE_NONE},
164 *                {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}.
165 * @param minSize the minimum symbol size constraint or null for no constraint
166 * @param maxSize the maximum symbol size constraint or null for no constraint
167 * @return the encoded message (the char values range from 0 to 255)
168 */
169pub fn encodeHighLevelWithDimension(
170    msg: &str,
171    shape: SymbolShapeHint,
172    minSize: Option<Dimension>,
173    maxSize: Option<Dimension>,
174) -> Result<String> {
175    encodeHighLevelWithDimensionForceC40(msg, shape, minSize, maxSize, false)
176}
177
178pub fn encodeHighLevelWithDimensionForceC40WithSymbolInfoLookup(
179    msg: &str,
180    shape: SymbolShapeHint,
181    minSize: Option<Dimension>,
182    maxSize: Option<Dimension>,
183    forceC40: bool,
184    symbol_lookup: Option<SymbolInfoLookup>,
185) -> Result<String> {
186    //the codewords 0..255 are encoded as Unicode characters
187    let c40Encoder = Arc::new(C40Encoder::new());
188    let encoders: [Arc<dyn Encoder>; 6] = [
189        Arc::new(ASCIIEncoder::new()),
190        c40Encoder.clone(),
191        Arc::new(TextEncoder::new()),
192        Arc::new(X12Encoder::new()),
193        Arc::new(EdifactEncoder::new()),
194        Arc::new(Base256Encoder::new()),
195    ];
196
197    let mut context = if let Some(symbol_table) = symbol_lookup {
198        EncoderContext::with_symbol_info_lookup(msg, symbol_table)?
199    } else {
200        EncoderContext::new(msg)?
201    };
202    // let mut context = EncoderContext::new(msg)?;
203    context.setSymbolShape(shape);
204    context.setSizeConstraints(minSize, maxSize);
205
206    if msg.starts_with(MACRO_05_HEADER) && msg.ends_with(MACRO_TRAILER) {
207        context.writeCodeword(MACRO_05);
208        context.setSkipAtEnd(2);
209        context.pos += MACRO_05_HEADER.chars().count() as u32;
210    } else if msg.starts_with(MACRO_06_HEADER) && msg.ends_with(MACRO_TRAILER) {
211        context.writeCodeword(MACRO_06);
212        context.setSkipAtEnd(2);
213        context.pos += MACRO_06_HEADER.chars().count() as u32;
214    }
215
216    let mut encodingMode = ASCII_ENCODATION; //Default mode
217
218    if forceC40 {
219        c40Encoder.encodeMaximalC40(&mut context)?;
220        encodingMode = context.getNewEncoding().ok_or(Exceptions::ILLEGAL_STATE)?;
221        context.resetEncoderSignal();
222    }
223
224    while context.hasMoreCharacters() {
225        encoders[encodingMode].encode(&mut context)?;
226        if context.getNewEncoding().is_some() {
227            encodingMode = context.getNewEncoding().ok_or(Exceptions::ILLEGAL_STATE)?;
228            context.resetEncoderSignal();
229        }
230    }
231    let len = context.getCodewordCount();
232    context.updateSymbolInfo();
233    let capacity = context
234        .getSymbolInfo()
235        .ok_or(Exceptions::ILLEGAL_STATE)?
236        .getDataCapacity();
237    if len < capacity as usize
238        && encodingMode != ASCII_ENCODATION
239        && encodingMode != BASE256_ENCODATION
240        && encodingMode != EDIFACT_ENCODATION
241    {
242        context.writeCodeword(0xfe); //Unlatch (254)
243        // context.writeCodeword("\u{00fe}"); //Unlatch (254)
244    }
245    //Padding
246    // let codewords = context.getCodewords();
247    if context.getCodewords().chars().count() < capacity as usize {
248        // codewords.push(PAD as char);
249        context.writeCodeword(PAD)
250    }
251    while context.getCodewords().chars().count() < capacity as usize {
252        // codewords.append(randomize253State(codewords.len() + 1));
253        context.writeCodewords(&randomize253State(
254            context.getCodewords().chars().count() as u32 + 1,
255        ))
256    }
257
258    Ok(context.getCodewords().to_owned())
259}
260
261/**
262 * Performs message encoding of a DataMatrix message using the algorithm described in annex P
263 * of ISO/IEC 16022:2000(E).
264 *
265 * @param msg     the message
266 * @param shape   requested shape. May be {@code SymbolShapeHint.FORCE_NONE},
267 *                {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}.
268 * @param minSize the minimum symbol size constraint or null for no constraint
269 * @param maxSize the maximum symbol size constraint or null for no constraint
270 * @param forceC40 enforce C40 encoding
271 * @return the encoded message (the char values range from 0 to 255)
272 */
273pub fn encodeHighLevelWithDimensionForceC40(
274    msg: &str,
275    shape: SymbolShapeHint,
276    minSize: Option<Dimension>,
277    maxSize: Option<Dimension>,
278    forceC40: bool,
279) -> Result<String> {
280    encodeHighLevelWithDimensionForceC40WithSymbolInfoLookup(
281        msg, shape, minSize, maxSize, forceC40, None,
282    )
283}
284
285pub fn lookAheadTest(msg: &str, startpos: u32, currentMode: u32) -> usize {
286    let newMode = lookAheadTestIntern(msg, startpos, currentMode);
287    if currentMode as usize == X12_ENCODATION && newMode == X12_ENCODATION {
288        // let msg_graphemes = msg.graphemes(true);
289        let endpos = (startpos + 3).min(msg.chars().count() as u32);
290        for i in startpos..endpos {
291            // for (int i = startpos; i < endpos; i++) {
292            if let Some(c) = msg.chars().nth(i as usize) {
293                if !isNativeX12(c) {
294                    return ASCII_ENCODATION;
295                }
296            }
297        }
298    } else if currentMode as usize == EDIFACT_ENCODATION && newMode == EDIFACT_ENCODATION {
299        // let msg_graphemes = msg.graphemes(true);
300        let endpos = (startpos + 4).min(msg.chars().count() as u32);
301        for i in startpos..endpos {
302            // for (int i = startpos; i < endpos; i++) {
303            if let Some(c) = msg.chars().nth(i as usize) {
304                if !isNativeEDIFACT(c) {
305                    return ASCII_ENCODATION;
306                }
307            }
308        }
309    }
310    newMode
311}
312
313fn lookAheadTestIntern(msg: &str, startpos: u32, currentMode: u32) -> usize {
314    if startpos as usize >= msg.chars().count() {
315        return currentMode as usize;
316    }
317    let mut charCounts: [f32; 6];
318    //step J
319    if currentMode == ASCII_ENCODATION as u32 {
320        charCounts = [0.0, 1.0, 1.0, 1.0, 1.0, 1.25];
321    } else {
322        charCounts = [1.0, 2.0, 2.0, 2.0, 2.0, 2.25];
323        charCounts[currentMode as usize] = 0.0;
324    }
325
326    let mut charsProcessed = 0;
327    let mut mins = [0u8; 6];
328    let mut intCharCounts = [0u32; 6];
329    loop {
330        //step K
331        if (startpos + charsProcessed) == msg.chars().count() as u32 {
332            mins.fill(0);
333            intCharCounts.fill(0);
334            // Arrays.fill(mins, (byte) 0);
335            // Arrays.fill(intCharCounts, 0);
336            let min = findMinimums(&charCounts, &mut intCharCounts, u32::MAX, &mut mins);
337            let minCount = getMinimumCount(&mins);
338
339            if intCharCounts[ASCII_ENCODATION] == min {
340                return ASCII_ENCODATION;
341            }
342            if minCount == 1 {
343                if mins[BASE256_ENCODATION] > 0 {
344                    return BASE256_ENCODATION;
345                }
346                if mins[EDIFACT_ENCODATION] > 0 {
347                    return EDIFACT_ENCODATION;
348                }
349                if mins[TEXT_ENCODATION] > 0 {
350                    return TEXT_ENCODATION;
351                }
352                if mins[X12_ENCODATION] > 0 {
353                    return X12_ENCODATION;
354                }
355            }
356            return C40_ENCODATION;
357        }
358
359        let Some(c) = msg.chars().nth((startpos + charsProcessed) as usize) else {
360            break 0;
361        };
362        charsProcessed += 1;
363
364        //step L
365        if isDigit(c) {
366            charCounts[ASCII_ENCODATION] += 0.5;
367        } else if isExtendedASCII(c) {
368            charCounts[ASCII_ENCODATION] = charCounts[ASCII_ENCODATION].ceil();
369            charCounts[ASCII_ENCODATION] += 2.0;
370        } else {
371            charCounts[ASCII_ENCODATION] = charCounts[ASCII_ENCODATION].ceil();
372            charCounts[ASCII_ENCODATION] += 1.0;
373        }
374
375        //step M
376        if isNativeC40(c) {
377            charCounts[C40_ENCODATION] += 2.0 / 3.0;
378        } else if isExtendedASCII(c) {
379            charCounts[C40_ENCODATION] += 8.0 / 3.0;
380        } else {
381            charCounts[C40_ENCODATION] += 4.0 / 3.0;
382        }
383
384        //step N
385        if isNativeText(c) {
386            charCounts[TEXT_ENCODATION] += 2.0 / 3.0;
387        } else if isExtendedASCII(c) {
388            charCounts[TEXT_ENCODATION] += 8.0 / 3.0;
389        } else {
390            charCounts[TEXT_ENCODATION] += 4.0 / 3.0;
391        }
392
393        //step O
394        if isNativeX12(c) {
395            charCounts[X12_ENCODATION] += 2.0 / 3.0;
396        } else if isExtendedASCII(c) {
397            charCounts[X12_ENCODATION] += 13.0 / 3.0;
398        } else {
399            charCounts[X12_ENCODATION] += 10.0 / 3.0;
400        }
401
402        //step P
403        if isNativeEDIFACT(c) {
404            charCounts[EDIFACT_ENCODATION] += 3.0 / 4.0;
405        } else if isExtendedASCII(c) {
406            charCounts[EDIFACT_ENCODATION] += 17.0 / 4.0;
407        } else {
408            charCounts[EDIFACT_ENCODATION] += 13.0 / 4.0;
409        }
410
411        // step Q
412        if isSpecialB256(c) {
413            charCounts[BASE256_ENCODATION] += 4.0;
414        } else {
415            charCounts[BASE256_ENCODATION] += 1.0;
416        }
417
418        //step R
419        if charsProcessed >= 4 {
420            mins.fill(0);
421            intCharCounts.fill(0);
422            // Arrays.fill(mins, (byte) 0);
423            // Arrays.fill(intCharCounts, 0);
424            findMinimums(&charCounts, &mut intCharCounts, u32::MAX, &mut mins);
425
426            if intCharCounts[ASCII_ENCODATION]
427                < min5(
428                    intCharCounts[BASE256_ENCODATION],
429                    intCharCounts[C40_ENCODATION],
430                    intCharCounts[TEXT_ENCODATION],
431                    intCharCounts[X12_ENCODATION],
432                    intCharCounts[EDIFACT_ENCODATION],
433                )
434            {
435                return ASCII_ENCODATION;
436            }
437            if intCharCounts[BASE256_ENCODATION] < intCharCounts[ASCII_ENCODATION]
438                || intCharCounts[BASE256_ENCODATION] + 1
439                    < min4(
440                        intCharCounts[C40_ENCODATION],
441                        intCharCounts[TEXT_ENCODATION],
442                        intCharCounts[X12_ENCODATION],
443                        intCharCounts[EDIFACT_ENCODATION],
444                    )
445            {
446                return BASE256_ENCODATION;
447            }
448            if intCharCounts[EDIFACT_ENCODATION] + 1
449                < min5(
450                    intCharCounts[BASE256_ENCODATION],
451                    intCharCounts[C40_ENCODATION],
452                    intCharCounts[TEXT_ENCODATION],
453                    intCharCounts[X12_ENCODATION],
454                    intCharCounts[ASCII_ENCODATION],
455                )
456            {
457                return EDIFACT_ENCODATION;
458            }
459            if intCharCounts[TEXT_ENCODATION] + 1
460                < min5(
461                    intCharCounts[BASE256_ENCODATION],
462                    intCharCounts[C40_ENCODATION],
463                    intCharCounts[EDIFACT_ENCODATION],
464                    intCharCounts[X12_ENCODATION],
465                    intCharCounts[ASCII_ENCODATION],
466                )
467            {
468                return TEXT_ENCODATION;
469            }
470            if intCharCounts[X12_ENCODATION] + 1
471                < min5(
472                    intCharCounts[BASE256_ENCODATION],
473                    intCharCounts[C40_ENCODATION],
474                    intCharCounts[EDIFACT_ENCODATION],
475                    intCharCounts[TEXT_ENCODATION],
476                    intCharCounts[ASCII_ENCODATION],
477                )
478            {
479                return X12_ENCODATION;
480            }
481            if intCharCounts[C40_ENCODATION] + 1
482                < min4(
483                    intCharCounts[ASCII_ENCODATION],
484                    intCharCounts[BASE256_ENCODATION],
485                    intCharCounts[EDIFACT_ENCODATION],
486                    intCharCounts[TEXT_ENCODATION],
487                )
488            {
489                if intCharCounts[C40_ENCODATION] < intCharCounts[X12_ENCODATION] {
490                    return C40_ENCODATION;
491                }
492                if intCharCounts[C40_ENCODATION] == intCharCounts[X12_ENCODATION] {
493                    let mut _p = startpos + charsProcessed + 1;
494                    for tc in msg.chars() {
495                        // while (p as usize) < msg.len() {
496                        // let tc = msg.charAt(p);
497                        if isX12TermSep(tc) {
498                            return X12_ENCODATION;
499                        }
500                        if !isNativeX12(tc) {
501                            break;
502                        }
503                        _p += 1;
504                    }
505                    return C40_ENCODATION;
506                }
507            }
508        }
509    }
510}
511
512fn min5(f1: u32, f2: u32, f3: u32, f4: u32, f5: u32) -> u32 {
513    min4(f1, f2, f3, f4).min(f5)
514}
515
516fn min4(f1: u32, f2: u32, f3: u32, f4: u32) -> u32 {
517    f1.min(f2.min(f3.min(f4)))
518    //  Math.min(f1, Math.min(f2, Math.min(f3, f4)))
519}
520
521fn findMinimums(
522    charCounts: &[f32; 6],
523    intCharCounts: &mut [u32; 6],
524    min: u32,
525    mins: &mut [u8],
526) -> u32 {
527    let mut min = min;
528    for i in 0..6 {
529        // for (int i = 0; i < 6; i++) {
530        intCharCounts[i] = charCounts[i].ceil() as u32;
531        let current = intCharCounts[i]; // = (int) Math.ceil(charCounts[i]));
532        if min > current {
533            min = current;
534            mins.fill(0);
535            // Arrays.fill(mins, (byte) 0);
536        }
537        if min == current {
538            mins[i] += 1;
539        }
540    }
541    min
542}
543
544fn getMinimumCount(mins: &[u8]) -> u32 {
545    mins.iter().take(6).sum::<u8>() as u32
546}
547
548#[inline]
549pub const fn isDigit(ch: char) -> bool {
550    ch.is_ascii_digit()
551}
552
553#[inline]
554pub fn isExtendedASCII(ch: char) -> bool {
555    (ch as u8) >= 128 //&& (ch as u8) <= 255
556}
557
558pub fn isNativeC40(ch: char) -> bool {
559    (ch == ' ') || ch.is_ascii_digit() || ch.is_ascii_uppercase()
560}
561
562pub fn isNativeText(ch: char) -> bool {
563    (ch == ' ') || ch.is_ascii_digit() || ch.is_ascii_lowercase()
564}
565
566pub fn isNativeX12(ch: char) -> bool {
567    isX12TermSep(ch) || (ch == ' ') || ch.is_ascii_digit() || ch.is_ascii_uppercase()
568}
569
570fn isX12TermSep(ch: char) -> bool {
571    (ch == '\r') //CR
572        || (ch == '*')
573        || (ch == '>')
574}
575
576pub fn isNativeEDIFACT(ch: char) -> bool {
577    (' '..='^').contains(&ch)
578}
579
580fn isSpecialB256(_ch: char) -> bool {
581    // unimplemented!();
582    false //TODO NOT IMPLEMENTED YET!!!
583}
584
585/**
586 * Determines the number of consecutive characters that are encodable using numeric compaction.
587 *
588 * @param msg      the message
589 * @param startpos the start position within the message
590 * @return the requested character count
591 */
592pub fn determineConsecutiveDigitCount(msg: &str, startpos: u32) -> u32 {
593    let len = msg.chars().count(); //len();
594    let mut idx = startpos;
595    // let graphemes = msg.graphemes(true);
596    while (idx as usize) < len && isDigit(msg.chars().nth(idx as usize).unwrap_or_default()) {
597        idx += 1;
598    }
599    idx - startpos
600}
601
602pub fn illegalCharacter(c: char) -> Result<()> {
603    // let hex = Integer.toHexString(c);
604    // hex = "0000".substring(0, 4 - hex.length()) + hex;
605    Err(Exceptions::illegal_argument_with(format!(
606        "Illegal character: {c} (0x{c})"
607    )))
608}