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