Skip to main content

rxing/oned/
itf_reader.rs

1/*
2 * Copyright 2008 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::OneDReader;
18
19use crate::{RXingResultMetadataType, RXingResultMetadataValue};
20
21use crate::{
22    BarcodeFormat, Exceptions, RXingResult,
23    common::{BitArray, Result},
24    point,
25};
26
27use super::{OneDReader, one_d_reader};
28
29const MAX_AVG_VARIANCE: f32 = 0.38;
30const MAX_INDIVIDUAL_VARIANCE: f32 = 0.5;
31
32const W: u32 = 3; // Pixel width of a 3x wide line
33const W_LOWER: u32 = 2; // Pixel width of a 2x wide line
34const N: u32 = 1; // Pixed width of a narrow line
35
36/** Valid ITF lengths. Anything longer than the largest value is also allowed. */
37const DEFAULT_ALLOWED_LENGTHS: [u32; 5] = [6, 8, 10, 12, 14];
38
39/**
40 * Start/end guard pattern.
41 *
42 * Note: The end pattern is reversed because the row is reversed before
43 * searching for the END_PATTERN
44 */
45const START_PATTERN: [u32; 4] = [N, N, N, N];
46const END_PATTERN_REVERSED: [[u32; 3]; 2] = [
47    [N, N, W_LOWER], // 2x
48    [N, N, W],       // 3x
49];
50
51// See ITFWriter.PATTERNS
52
53/**
54 * Patterns of Wide / Narrow lines to indicate each digit
55 */
56const PATTERNS: [[u32; 5]; 20] = [
57    [N, N, W_LOWER, W_LOWER, N], // 0
58    [W_LOWER, N, N, N, W_LOWER], // 1
59    [N, W_LOWER, N, N, W_LOWER], // 2
60    [W_LOWER, W_LOWER, N, N, N], // 3
61    [N, N, W_LOWER, N, W_LOWER], // 4
62    [W_LOWER, N, W_LOWER, N, N], // 5
63    [N, W_LOWER, W_LOWER, N, N], // 6
64    [N, N, N, W_LOWER, W_LOWER], // 7
65    [W_LOWER, N, N, W_LOWER, N], // 8
66    [N, W_LOWER, N, W_LOWER, N], // 9
67    [N, N, W, W, N],             // 0
68    [W, N, N, N, W],             // 1
69    [N, W, N, N, W],             // 2
70    [W, W, N, N, N],             // 3
71    [N, N, W, N, W],             // 4
72    [W, N, W, N, N],             // 5
73    [N, W, W, N, N],             // 6
74    [N, N, N, W, W],             // 7
75    [W, N, N, W, N],             // 8
76    [N, W, N, W, N],             // 9
77];
78
79/**
80 * <p>Implements decoding of the ITF format, or Interleaved Two of Five.</p>
81 *
82 * <p>This Reader will scan ITF barcodes of certain lengths only.
83 * At the moment it reads length 6, 8, 10, 12, 14, 16, 18, 20, 24, and 44 as these have appeared "in the wild". Not all
84 * lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of
85 * required checksum function.</p>
86 *
87 * <p>The checksum is optional and is not applied by this Reader. The consumer of the decoded
88 * value will have to apply a checksum if required.</p>
89 *
90 * <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>
91 * is a great reference for Interleaved 2 of 5 information.</p>
92 *
93 * @author kevin.osullivan@sita.aero, SITA Lab.
94 */
95#[derive(OneDReader)]
96pub struct ITFReader {
97    // Stores the actual narrow line width of the image being decoded.
98    narrowLineWidth: i32,
99}
100
101impl Default for ITFReader {
102    fn default() -> Self {
103        Self {
104            narrowLineWidth: -1,
105        }
106    }
107}
108
109impl OneDReader for ITFReader {
110    fn decode_row(
111        &mut self,
112        rowNumber: u32,
113        row: &crate::common::BitArray,
114        hints: &crate::DecodeHints,
115    ) -> Result<crate::RXingResult> {
116        // Find out where the Middle section (payload) starts & ends
117        let mut row = row.clone();
118        let startRange = self.decodeStart(&row)?;
119        let endRange = self.decodeEnd(&mut row)?;
120
121        let mut result = String::with_capacity(20); //new StringBuilder(20);
122        self.decodeMiddle(&row, startRange[1], endRange[0], &mut result)?;
123        let resultString = result; //.toString();
124
125        let allowedLengths = if let Some(al) = &hints.AllowedLengths {
126            al.clone()
127        } else {
128            DEFAULT_ALLOWED_LENGTHS.to_vec()
129        };
130
131        // To avoid false positives with 2D barcodes (and other patterns), make
132        // an assumption that the decoded string must be a 'standard' length if it's short
133        let length = resultString.chars().count();
134        let mut lengthOK = false;
135        let mut maxAllowedLength = 0;
136        for allowedLength in allowedLengths {
137            if length == allowedLength as usize {
138                lengthOK = true;
139                break;
140            }
141            maxAllowedLength = std::cmp::max(allowedLength, maxAllowedLength);
142        }
143        if !lengthOK && length > maxAllowedLength as usize {
144            lengthOK = true;
145        }
146        if !lengthOK {
147            return Err(Exceptions::FORMAT);
148        }
149
150        let mut resultObject = RXingResult::new(
151            &resultString,
152            Vec::new(), // no natural byte representation for these barcodes
153            vec![
154                point(startRange[1] as f32, rowNumber as f32),
155                point(endRange[0] as f32, rowNumber as f32),
156            ],
157            BarcodeFormat::ITF,
158        );
159
160        resultObject.putMetadata(
161            RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
162            RXingResultMetadataValue::SymbologyIdentifier("]I0".to_owned()),
163        );
164
165        Ok(resultObject)
166    }
167}
168impl ITFReader {
169    /**
170     * @param row          row of black/white values to search
171     * @param payloadStart offset of start pattern
172     * @param resultString {@link StringBuilder} to append decoded chars to
173     * @throws NotFoundException if decoding could not complete successfully
174     */
175    fn decodeMiddle(
176        &self,
177        row: &BitArray,
178        payloadStart: usize,
179        payloadEnd: usize,
180        resultString: &mut String,
181    ) -> Result<()> {
182        let mut payloadStart = payloadStart;
183        // Digits are interleaved in pairs - 5 black lines for one digit, and the
184        // 5 interleaved white lines for the second digit.
185        // Therefore, need to scan 10 lines and then
186        // split these into two arrays
187        let mut counterDigitPair = [0_u32; 10]; //new int[10];
188        let mut counterBlack = [0_u32; 5]; //new int[5];
189        let mut counterWhite = [0_u32; 5]; //new int[5];
190
191        while payloadStart < payloadEnd {
192            // Get 10 runs of black/white.
193            one_d_reader::record_pattern(row, payloadStart, &mut counterDigitPair)?;
194            // Split them into each array
195            for k in 0..5 {
196                let twoK = 2 * k;
197                counterBlack[k] = counterDigitPair[twoK];
198                counterWhite[k] = counterDigitPair[twoK + 1];
199            }
200
201            let mut bestMatch = self.decodeDigit(&counterBlack)?;
202            resultString.push(char::from_u32('0' as u32 + bestMatch).ok_or(Exceptions::PARSE)?);
203            bestMatch = self.decodeDigit(&counterWhite)?;
204            resultString.push(char::from_u32('0' as u32 + bestMatch).ok_or(Exceptions::PARSE)?);
205
206            payloadStart += counterDigitPair.iter().sum::<u32>() as usize;
207        }
208
209        Ok(())
210    }
211
212    /**
213     * Identify where the start of the middle / payload section starts.
214     *
215     * @param row row of black/white values to search
216     * @return Array, containing index of start of 'start block' and end of
217     *         'start block'
218     */
219    fn decodeStart(&mut self, row: &BitArray) -> Result<[usize; 2]> {
220        let endStart = Self::skipWhiteSpace(row)?;
221        let startPattern = self.findGuardPattern(row, endStart, &START_PATTERN)?;
222
223        // Determine the width of a narrow line in pixels. We can do this by
224        // getting the width of the start pattern and dividing by 4 because its
225        // made up of 4 narrow lines.
226        self.narrowLineWidth = (startPattern[1] - startPattern[0]) as i32 / 4;
227
228        self.validateQuietZone(row, startPattern[0])?;
229
230        Ok(startPattern)
231    }
232
233    /**
234     * The start & end patterns must be pre/post fixed by a quiet zone. This
235     * zone must be at least 10 times the width of a narrow line.  Scan back until
236     * we either get to the start of the barcode or match the necessary number of
237     * quiet zone pixels.
238     *
239     * Note: Its assumed the row is reversed when using this method to find
240     * quiet zone after the end pattern.
241     *
242     * ref: http://www.barcode-1.net/i25code.html
243     *
244     * @param row bit array representing the scanned barcode.
245     * @param startPattern index into row of the start or end pattern.
246     * @throws NotFoundException if the quiet zone cannot be found
247     */
248    fn validateQuietZone(&self, row: &BitArray, startPattern: usize) -> Result<()> {
249        let mut quietCount = self.narrowLineWidth * 10; // expect to find this many pixels of quiet zone
250
251        // if there are not so many pixel at all let's try as many as possible
252        quietCount = quietCount.min(startPattern as i32);
253
254        let mut i = startPattern as isize - 1;
255        while quietCount > 0 && i >= 0 {
256            if row.get(i as usize) {
257                break;
258            }
259            quietCount -= 1;
260            i -= 1;
261        }
262
263        if quietCount != 0 {
264            // Unable to find the necessary number of quiet zone pixels.
265            Err(Exceptions::NOT_FOUND)
266        } else {
267            Ok(())
268        }
269    }
270
271    /**
272     * Skip all whitespace until we get to the first black line.
273     *
274     * @param row row of black/white values to search
275     * @return index of the first black line.
276     * @throws NotFoundException Throws exception if no black lines are found in the row
277     */
278    fn skipWhiteSpace(row: &BitArray) -> Result<usize> {
279        let width = row.get_size();
280        let endStart = row.getNextSet(0);
281        if endStart == width {
282            return Err(Exceptions::NOT_FOUND);
283        }
284
285        Ok(endStart)
286    }
287
288    /**
289     * Identify where the end of the middle / payload section ends.
290     *
291     * @param row row of black/white values to search
292     * @return Array, containing index of start of 'end block' and end of 'end
293     *         block'
294     */
295    fn decodeEnd(&self, row: &mut BitArray) -> Result<[usize; 2]> {
296        // For convenience, reverse the row and then
297        // search from 'the start' for the end block
298        row.reverse();
299        let interim_function = || -> Result<[usize; 2]> {
300            let endStart = Self::skipWhiteSpace(row)?;
301            let mut endPattern =
302                if let Ok(ptrn) = self.findGuardPattern(row, endStart, &END_PATTERN_REVERSED[0]) {
303                    ptrn
304                } else {
305                    self.findGuardPattern(row, endStart, &END_PATTERN_REVERSED[1])?
306                };
307
308            // The start & end patterns must be pre/post fixed by a quiet zone. This
309            // zone must be at least 10 times the width of a narrow line.
310            // ref: http://www.barcode-1.net/i25code.html
311            self.validateQuietZone(row, endPattern[0])?;
312
313            // Now recalculate the indices of where the 'endblock' starts & stops to
314            // accommodate the reversed nature of the search
315            let temp = endPattern[0];
316            endPattern[0] = row.get_size() - endPattern[1];
317            endPattern[1] = row.get_size() - temp;
318
319            Ok(endPattern)
320        };
321        let res = interim_function();
322        // Put the row back the right way.
323        row.reverse();
324
325        res
326    }
327
328    /**
329     * @param row       row of black/white values to search
330     * @param rowOffset position to start search
331     * @param pattern   pattern of counts of number of black and white pixels that are
332     *                  being searched for as a pattern
333     * @return start/end horizontal offset of guard pattern, as an array of two
334     *         ints
335     * @throws NotFoundException if pattern is not found
336     */
337    fn findGuardPattern<const N: usize>(
338        &self,
339        row: &BitArray,
340        rowOffset: usize,
341        pattern: &[u32; N],
342    ) -> Result<[usize; 2]> {
343        let patternLength = N;
344        let mut counters = [0u32; N]; //new int[patternLength];
345        let width = row.get_size();
346        let mut isWhite = false;
347
348        let mut counterPosition = 0;
349        let mut patternStart = rowOffset;
350        for x in rowOffset..width {
351            // for (int x = rowOffset; x < width; x++) {
352            if row.get(x) != isWhite {
353                counters[counterPosition] += 1;
354            } else {
355                if counterPosition == patternLength - 1 {
356                    if one_d_reader::pattern_match_variance(
357                        &counters,
358                        pattern,
359                        MAX_INDIVIDUAL_VARIANCE,
360                    ) < MAX_AVG_VARIANCE
361                    {
362                        return Ok([patternStart, x]);
363                    }
364                    patternStart += (counters[0] + counters[1]) as usize;
365
366                    counters.copy_within(2..(counterPosition - 1 + 2), 0);
367                    counters[counterPosition - 1] = 0;
368                    counters[counterPosition] = 0;
369                    counterPosition -= 1;
370                } else {
371                    counterPosition += 1;
372                }
373                counters[counterPosition] = 1;
374                isWhite = !isWhite;
375            }
376        }
377        Err(Exceptions::NOT_FOUND)
378    }
379
380    /**
381     * Attempts to decode a sequence of ITF black/white lines into single
382     * digit.
383     *
384     * @param counters the counts of runs of observed black/white/black/... values
385     * @return The decoded digit
386     * @throws NotFoundException if digit cannot be decoded
387     */
388    fn decodeDigit(&self, counters: &[u32; 5]) -> Result<u32> {
389        let mut bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
390        let mut bestMatch = -1_isize;
391        for (i, pattern) in PATTERNS.iter().enumerate() {
392            let variance =
393                one_d_reader::pattern_match_variance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
394            if variance < bestVariance {
395                bestVariance = variance;
396                bestMatch = i as isize;
397            } else if variance == bestVariance {
398                // if we find a second 'best match' with the same variance, we can not reliably report to have a suitable match
399                bestMatch = -1;
400                continue;
401            }
402        }
403        if bestMatch >= 0 {
404            Ok(bestMatch as u32 % 10)
405        } else {
406            Err(Exceptions::NOT_FOUND)
407        }
408    }
409}