3076941fd69ffe46cb35728d1cf4d4cd49b61389
[zxing.git] / core / src / com / google / zxing / oned / UPCEANExtensionSupport.java
1 /*
2  * Copyright (C) 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
17 package com.google.zxing.oned;
18
19 import java.util.Hashtable;
20
21 import com.google.zxing.BarcodeFormat;
22 import com.google.zxing.NotFoundException;
23 import com.google.zxing.Result;
24 import com.google.zxing.ResultMetadataType;
25 import com.google.zxing.common.BitArray;
26
27 final class UPCEANExtensionSupport {
28
29   private static final int[] EXTENSION_START_PATTERN = {1,1,2};
30   private static final int[] CHECK_DIGIT_ENCODINGS = {
31       0x18, 0x14, 0x12, 0x11, 0x0C, 0x06, 0x03, 0x0A, 0x09, 0x05
32   };
33   private static final int[][] SEPARATOR_PATTERNS = {{1,1}};
34
35   private final int[] decodeMiddleCounters = new int[4];
36   private final int[] separatorCounters = new int[2];
37   private final StringBuffer decodeRowStringBuffer = new StringBuffer();
38
39   Result decodeRow(BitArray row, int rowOffset) throws NotFoundException {
40
41     int[] extensionStartRange = UPCEANReader.findGuardPattern(row, rowOffset, false, EXTENSION_START_PATTERN);
42
43     StringBuffer result = decodeRowStringBuffer;
44     result.setLength(0);
45     decodeMiddle(row, extensionStartRange, result);
46
47     String resultString = result.toString();
48     Hashtable extensionData = parseExtensionString(resultString);
49
50     Result extensionResult = new Result(resultString, null, null, BarcodeFormat.UPC_EAN_EXTENSION);
51     if (extensionData != null) {
52       extensionResult.putAllMetadata(extensionData);
53     }
54     return extensionResult;
55   }
56
57   int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString) throws NotFoundException {
58     int[] counters = decodeMiddleCounters;
59     counters[0] = 0;
60     counters[1] = 0;
61     counters[2] = 0;
62     counters[3] = 0;
63     int[] separatorCounters = this.separatorCounters;
64     separatorCounters[0] = 0;
65     separatorCounters[1] = 0;
66     int end = row.getSize();
67     int rowOffset = startRange[1];
68
69     int lgPatternFound = 0;
70
71     for (int x = 0; x < 5 && rowOffset < end; x++) {
72       int bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_AND_G_PATTERNS);
73       resultString.append((char) ('0' + bestMatch % 10));
74       for (int i = 0; i < counters.length; i++) {
75         rowOffset += counters[i];
76       }
77       if (bestMatch >= 10) {
78         lgPatternFound |= 1 << (4 - x);
79       }
80       // Read off separator
81       /*
82       try {
83         UPCEANReader.decodeDigit(row, separatorCounters, rowOffset, SEPARATOR_PATTERNS);
84         rowOffset += separatorCounters[0] + separatorCounters[1];
85       } catch (NotFoundException nfe) {
86         break;
87       }
88        */
89       while (rowOffset < end && !row.get(rowOffset)) {
90         rowOffset++;
91       }
92       while (rowOffset < end && row.get(rowOffset)) {
93         rowOffset++;
94       }
95     }
96
97     if (resultString.length() != 5) {
98       throw NotFoundException.getNotFoundInstance();
99     }
100
101     int checkDigit = determineCheckDigit(lgPatternFound);
102     if (extensionChecksum(resultString.toString()) != checkDigit) {
103       throw NotFoundException.getNotFoundInstance();
104     }
105     
106     return rowOffset;
107   }
108
109   private static int extensionChecksum(String s) {
110     int length = s.length();
111     int sum = 0;
112     for (int i = length - 2; i >= 0; i -= 2) {
113       sum += (int) s.charAt(i) - (int) '0';
114     }
115     sum *= 3;
116     for (int i = length - 1; i >= 0; i -= 2) {
117       sum += (int) s.charAt(i) - (int) '0';
118     }
119     sum *= 3;
120     return sum % 10;
121   }
122
123   private static int determineCheckDigit(int lgPatternFound)
124       throws NotFoundException {
125     for (int d = 0; d < 10; d++) {
126       if (lgPatternFound == CHECK_DIGIT_ENCODINGS[d]) {
127         return d;
128       }
129     }
130     throw NotFoundException.getNotFoundInstance();
131   }
132
133   /**
134    * @param raw raw content of extension
135    * @return formatted interpretation of raw content as a {@link Hashtable} mapping
136    *  one {@link ResultMetadataType} to appropriate value, or <code>null</code> if not known
137    */
138   private static Hashtable parseExtensionString(String raw) {
139     ResultMetadataType type;
140     Object value;
141     switch (raw.length()) {
142       case 2:
143         type = ResultMetadataType.ISSUE_NUMBER;
144         value = parseExtension2String(raw);
145         break;
146       case 5:
147         type = ResultMetadataType.SUGGESTED_PRICE;
148         value = parseExtension5String(raw);
149         break;
150       default:
151         return null;
152     }
153     if (value == null) {
154       return null;
155     }
156     Hashtable result = new Hashtable(1);
157     result.put(type, value);
158     return result;
159   }
160
161   private static Integer parseExtension2String(String raw) {
162     return Integer.valueOf(raw);
163   }
164
165   private static String parseExtension5String(String raw) {
166     String currency = null;
167     switch (raw.charAt(0)) {
168       case '0':
169         currency = "£";
170         break;
171       case '5':
172         currency = "$";
173         break;
174       case '9':
175         if ("99991".equals(raw)) {
176           return "0.00";
177         } else if ("99990".equals(raw)) {
178           return "Used";
179         }
180         break;
181       default:
182         currency = "";
183         break;
184     }
185     int rawAmount = Integer.parseInt(raw.substring(1));
186     return currency + (rawAmount / 100) + '.' + (rawAmount % 100);
187   }
188
189 }