View Javadoc

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      /* TODO: The following is very close to EAN13Barcode's version,
96       * should change this so EAN13Barcode doesn't need to override.  
97       * 
98       * Note that the following code uses member functions to get static 
99       * values from the ModuleFactory class, instead of referencing the 
100      * values directly.  This is so sub-classes can override the 
101      * member functions and thus change the static values.
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         // need to change the output.barHeight value, appears to be no means to do so
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         // draw leading white space
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         // draw the left guard
160         if (preAmble != null) {
161             currentX += drawModule(preAmble, output, currentX, y, barWidth, guardBarHeight);
162         }
163         
164         // draw first char in left side
165         for (int i = 0; i < guardCharSize; i++) {
166             currentX += drawModule(modules[0], output, currentX, y, barWidth, guardBarHeight);
167         }
168         firstTextPos = currentX;
169         
170         // draw the blank space below the guard
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         // draw the centre guard
182         currentX += drawModule(getCentreGuard(), output, currentX, y, barWidth, guardBarHeight);
183         secondTextPos = currentX;
184         
185         // draw the blank space below the guard
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         // draw the right guard
202         if (postAmble != null) {
203             currentX += drawModule(postAmble, output, currentX, y, barWidth, guardBarHeight);
204         }
205         
206         // draw the blank space below the guard
207         width = currentX - width;
208         output.paintBackground(currentX - width, guardBarHeight, width, ((shortBarHeight + textHeight) - guardBarHeight));
209 
210         endTextPos = currentX;
211 
212         // draw trailing white space
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          * note that the for loop is from 0, as indexing for data is from 0,
346          * but the modolo 10 considers the first character position to be 1.
347          * as such 0 is odd, not even and 1 is even not odd, so compare to
348          * 1, not 0 when attempting to find if its an even or odd number.
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          * calculation changes for the 9th and 10th digits which is why we are
404          * not using a loop
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 }