1 /***********************************************************************************************************************
2 * Copyright (c) 2004, International Barcode Consortium
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
7 *
8 * Redistributions of source code must retain the above copyright notice, this list of
9 * conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright notice, this list of
11 * conditions and the following disclaimer in the documentation and/or other materials
12 * provided with the distribution.
13 * Neither the name of the International Barcode Consortium nor the names of any contributors may be used to endorse
14 * or promote products derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 ***********************************************************************************************************************/
26
27 package net.sourceforge.barbecue.linear.upc;
28
29 import net.sourceforge.barbecue.BarcodeException;
30 import net.sourceforge.barbecue.Module;
31 import net.sourceforge.barbecue.linear.LinearBarcode;
32 import net.sourceforge.barbecue.output.LabelLayoutFactory;
33 import net.sourceforge.barbecue.output.Output;
34 import net.sourceforge.barbecue.output.OutputException;
35
36 import java.awt.*;
37 import java.text.CharacterIterator;
38 import java.text.StringCharacterIterator;
39 import java.util.ArrayList;
40 import java.util.List;
41
42 /**
43 * This is a concrete implementation of the UPC-A barcode.
44 *
45 * @author <a href="mailto:james@metalskin.com">James Jenner</a>
46 */
47 public class UPCABarcode extends LinearBarcode {
48 /**
49 * A list of type identifiers for the UPC-A barcode format
50 */
51 public static final String[] TYPES = new String[]{
52 "UPC-A", "UPCA"
53 };
54 protected boolean requiresChecksum = false;
55 protected final String label;
56 protected int width = 0;
57
58 protected final static int CHECKSUM_WEIGHT_EVEN = 1;
59 protected final static int CHECKSUM_WEIGHT_ODD = 3;
60
61 public final static int BARCODE_LENGTH = 11;
62
63 /**
64 * Constructs a basic mode UPC-A barcode with the specified data and an optional
65 * checksum. The length of the barcode is 11, 12 with a checksum. If the length
66 * passed is only 11, then a checksum will be automaticaly added. If the length
67 * is not 11 or 12 then a barcode exception will be thrown.
68 *
69 * @param data The data to encode
70 * @throws net.sourceforge.barbecue.BarcodeException
71 * If the data to be encoded is invalid
72 */
73 public UPCABarcode(String data) throws BarcodeException {
74 this(data, false);
75 }
76
77 public UPCABarcode(String data, boolean randomWeight) throws BarcodeException {
78 super(validateChars(data));
79
80 if (data.length() != getBarcodeLength()) {
81 throw new BarcodeException("Invalid data length");
82 }
83
84 requiresChecksum = true;
85 if (randomWeight) {
86 data = populateRandonWeightCheckDigit(data);
87 }
88 this.label = data;
89 }
90
91 protected int getBarcodeLength() {
92 return BARCODE_LENGTH;
93 }
94
95
96
97
98
99
100
101
102
103 protected Dimension draw(Output output, int x, int y, int barWidth, int barHeight) throws OutputException {
104 int currentX = x;
105
106 output.beginDraw();
107
108
109 int guardBarHeight = 0;
110 int shortBarHeight = barHeight;
111 int textHeight = 10 * barWidth;
112
113 if (drawingText) {
114 shortBarHeight = barHeight - (11 * barWidth);
115 guardBarHeight = shortBarHeight + (6 * barWidth);
116 } else {
117 shortBarHeight = barHeight - (6 * barWidth);
118 guardBarHeight = barHeight;
119 }
120
121 String text = getLabel();
122 int currentY = this.barHeight + y;
123
124 Module[] modules = encodeData();
125
126 String leadChar = String.valueOf(text.charAt(0));
127 String endChar = String.valueOf(text.charAt(text.length() - 1));
128 String firstSet = text.substring(1, getLeftWidth());
129 String lastSet = text.substring(getLeftWidth(), 11);
130
131 if (requiresChecksum) {
132 endChar = calculateChecksum().getSymbol();
133 }
134
135 int startTextPos = 0;
136 int firstTextPos = 0;
137 int secondTextPos = 0;
138 int endTextPos = 0;
139
140 int startTextW = x;
141 int firstTextW = 0;
142 int secondTextW = 0;
143 int endTextW = 0;
144 int width = 0;
145 Module preAmble = getPreAmble();
146 Module postAmble = getPostAmble();
147 startTextW = 0;
148
149
150 if (super.drawingQuietSection) {
151 currentX += drawModule(getLeftMargin(), output, currentX, y, barWidth, shortBarHeight + textHeight);
152 }
153 startTextPos = x;
154 startTextW = currentX - startTextPos;
155 width = currentX;
156 int guardCharSize = getGuardCharSize();
157 int leftWidth = getLeftWidth();
158
159
160 if (preAmble != null) {
161 currentX += drawModule(preAmble, output, currentX, y, barWidth, guardBarHeight);
162 }
163
164
165 for (int i = 0; i < guardCharSize; i++) {
166 currentX += drawModule(modules[0], output, currentX, y, barWidth, guardBarHeight);
167 }
168 firstTextPos = currentX;
169
170
171 width = currentX - width;
172 output.paintBackground(currentX - width, guardBarHeight, width, ((shortBarHeight + textHeight) - guardBarHeight));
173
174 for (int i = guardCharSize; i < leftWidth; i++) {
175 currentX += drawModule(modules[i], output, currentX, y, barWidth, shortBarHeight);
176 }
177
178 firstTextW = currentX - firstTextPos;
179
180 width = currentX;
181
182 currentX += drawModule(getCentreGuard(), output, currentX, y, barWidth, guardBarHeight);
183 secondTextPos = currentX;
184
185
186 width = currentX - width;
187 output.paintBackground(currentX - width, guardBarHeight, width, ((shortBarHeight + textHeight) - guardBarHeight));
188
189 int endGuardOffset = modules.length - guardCharSize;
190
191 for (int i = leftWidth; i < endGuardOffset; i++) {
192 currentX += drawModule(modules[i], output, currentX, y, barWidth, shortBarHeight);
193 }
194
195 secondTextW = currentX - secondTextPos;
196 width = currentX;
197 for (int i = endGuardOffset; i < modules.length; i++) {
198 currentX += drawModule(modules[i], output, currentX, y, barWidth, guardBarHeight);
199 }
200
201
202 if (postAmble != null) {
203 currentX += drawModule(postAmble, output, currentX, y, barWidth, guardBarHeight);
204 }
205
206
207 width = currentX - width;
208 output.paintBackground(currentX - width, guardBarHeight, width, ((shortBarHeight + textHeight) - guardBarHeight));
209
210 endTextPos = currentX;
211
212
213 if (super.drawingQuietSection) {
214 currentX += drawModule(getRightMargin(), output, currentX, y, barWidth, shortBarHeight + textHeight);
215 }
216
217 endTextW = currentX - endTextPos;
218
219 if (drawingText) {
220 output.drawText(leadChar, LabelLayoutFactory.createMarginLayout(startTextPos, shortBarHeight, startTextW, textHeight));
221 output.drawText(firstSet, LabelLayoutFactory.createMarginLayout(firstTextPos, shortBarHeight, firstTextW, textHeight));
222 output.drawText(lastSet, LabelLayoutFactory.createMarginLayout(secondTextPos, shortBarHeight, secondTextW, textHeight));
223 output.drawText(endChar, LabelLayoutFactory.createMarginLayout(endTextPos, shortBarHeight, endTextW, textHeight));
224 }
225
226
227 Dimension size = new Dimension((int) (currentX - x), (int) (currentY) - y);
228
229 output.endDraw((int) size.getWidth(), (int) size.getHeight());
230
231 return size;
232 }
233
234 /**
235 * Returns the text that will be displayed underneath the barcode (if requested).
236 *
237 * @return The text label for the barcode
238 */
239 public String getLabel() {
240 return label;
241 }
242
243 /**
244 * Returns the barcode width for the given resolution.
245 *
246 * @param resolution The output resolution
247 * @return The barcode width
248 */
249 protected double getBarcodeWidth(int resolution) {
250 encodeData();
251
252 return barWidth * width;
253 }
254
255 /**
256 * Returns the encoded data for the barcode.
257 *
258 * @return An array of modules that represent the data as a barcode
259 */
260 protected Module[] encodeData() {
261 List modules = new ArrayList();
262 Module module = null;
263 int len = data.length();
264 char c;
265 for (int i = 0; i < len; i++) {
266 c = data.charAt(i);
267
268 module = ModuleFactory.getModule(String.valueOf(c), i);
269 width += module.widthInBars();
270 modules.add(module);
271 }
272
273 if (requiresChecksum) {
274 module = ModuleFactory.getModule(calculateChecksum().getSymbol(), modules.size() - 1);
275 width += module.widthInBars();
276 modules.add(module);
277 }
278
279 return (Module[]) modules.toArray(new Module[0]);
280 }
281
282 /**
283 * Returns the checksum for the barcode, pre-encoded as a Module.
284 *
285 * @return a Mod-10 caclulated checksum, if no checksum is required Null
286 */
287 protected Module calculateChecksum() {
288 if (requiresChecksum) {
289 return ModuleFactory.getModuleForIndex(getMod10CheckDigit(data));
290 }
291
292 return null;
293 }
294
295 protected int getGuardCharSize() {
296 return ModuleFactory.GUARD_CHAR_SIZE;
297 }
298
299 protected int getLeftWidth() {
300 return ModuleFactory.LEFT_WIDTH;
301 }
302
303 protected Module getLeftMargin() {
304 return ModuleFactory.LEFT_MARGIN;
305
306 }
307
308 protected Module getRightMargin() {
309 return ModuleFactory.RIGHT_MARGIN;
310 }
311
312 /**
313 * Returns the pre-amble for the barcode.
314 *
315 * @return pre amble for the barcode
316 */
317 protected Module getPreAmble() {
318 return ModuleFactory.LEFT_GUARD;
319 }
320
321 /**
322 * Returns the middle bar for the barcode.
323 *
324 * @return pre amble for the barcode
325 */
326 protected Module getCentreGuard() {
327 return ModuleFactory.CENTRE_GUARD;
328 }
329
330 /**
331 * Returns the post-amble for the barcode.
332 *
333 * @return postamble for the barcode
334 */
335 protected Module getPostAmble() {
336 return ModuleFactory.RIGHT_GUARD;
337 }
338
339 public static int getMod10CheckDigit(String data) {
340 int sum = 0;
341 int len = data.length();
342 int value;
343
344
345
346
347
348
349
350 for (int i = 0; i < len; i++) {
351 try {
352 value = Integer.parseInt(String.valueOf(data.charAt(i)));
353 sum += calculateChecksum(value, (i % 2) == 1);
354 } catch (java.lang.NumberFormatException e) {
355 }
356 }
357
358 int checkDigit = 10 - (sum % 10);
359
360 if (checkDigit == 10) {
361 checkDigit = 0;
362 }
363
364 return checkDigit;
365 }
366
367 protected static int calculateChecksum(int value, boolean even) {
368 if (even) {
369 return value * CHECKSUM_WEIGHT_EVEN;
370 } else {
371 return value * CHECKSUM_WEIGHT_ODD;
372 }
373 }
374
375 private static String validateChars(String data) throws BarcodeException {
376 if (data == null) {
377 throw new IllegalArgumentException("data param must contain a value, not null");
378 }
379
380 StringCharacterIterator iter = new StringCharacterIterator(data);
381 for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
382 if (!ModuleFactory.hasModule(String.valueOf(c))) {
383 throw new BarcodeException("Illegal character");
384 }
385 }
386
387 return data;
388 }
389
390 private String populateRandonWeightCheckDigit(String upc) {
391 int[][] checkDigitCalcs = {{0, 2, 4, 6, 8, 9, 1, 3, 5, 7}, {0, 3, 6, 9, 2, 5, 8, 1, 4, 7},
392 {0, 5, 9, 4, 8, 3, 7, 2, 6, 1}};
393 int total = 0;
394 int checkdigit = 0;
395 char[] upcCharArray = upc.toCharArray();
396
397 int digit = Character.digit(upcCharArray[7], 10);
398 total += checkDigitCalcs[0][digit];
399 digit = Character.digit(upcCharArray[8], 10);
400 total += checkDigitCalcs[0][digit];
401
402
403
404
405
406 digit = Character.digit(upcCharArray[9], 10);
407 total += checkDigitCalcs[1][digit];
408 digit = Character.digit(upcCharArray[10], 10);
409 total += checkDigitCalcs[2][digit];
410
411 if ((total % 10) == 0) {
412 checkdigit = 0;
413 } else {
414 checkdigit = (10 - (total % 10));
415 }
416
417 upcCharArray[6] = String.valueOf(checkdigit).charAt(0);
418 return String.valueOf(upcCharArray);
419 }
420 }