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