View Javadoc

1   /***********************************************************************************************************************
2    * Copyright (c) 2003, 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.code128;
28  
29  import net.sourceforge.barbecue.BarcodeException;
30  import net.sourceforge.barbecue.BlankModule;
31  import net.sourceforge.barbecue.CompositeModule;
32  import net.sourceforge.barbecue.Module;
33  import net.sourceforge.barbecue.linear.LinearBarcode;
34  
35  import java.util.ArrayList;
36  import java.util.List;
37  
38  /**
39   * This is a concrete implementation of the Code 128 barcode. It fully supports all three
40   * available character sets (A, B and C), and also fully supports code shifts and set
41   * changes on-the-fly, providing an automatic optimisation mode.
42   *
43   * @author <a href="mailto:opensource@ianbourke.com">Ian Bourke</a>
44   */
45  public class Code128Barcode extends LinearBarcode {
46      /** Character set A flag */
47      public static final int A = 0;
48      /** Character set B flag */
49      public static final int B = 1;
50      /** Character set c flag */
51      public static final int C = 2;
52      /** Auto character set flag */
53      public static final int O = 3;
54      
55      /** Code shift character */
56      public static final String SHIFT = "\306";
57      /** Code set change from current to A character */
58      public static final String CHANGE_TO_A = "\311";
59      /** Code set change from current to B character */
60      public static final String CHANGE_TO_B = "\310";
61      /** Code set change from current to c character */
62      public static final String CHANGE_TO_C = "\307";
63      
64      /** FNC1 start character */
65      public static final String FNC_1 = "\312";
66      
67      public static final Module START_A = new Module(new int[] {2, 1, 1, 4, 1, 2});
68      public static final Module START_B = new Module(new int[] {2, 1, 1, 2, 1, 4});
69      public static final Module START_C = new Module(new int[] {2, 1, 1, 2, 3, 2});
70      protected static final Module STOP = new Module(new int[] {2, 3, 3, 1, 1, 1, 2});
71      protected static final Module QUIET_SECTION = new BlankModule(10);
72      
73      private static final Module[] START = {
74          START_A,
75          START_B,
76          START_C
77      };
78      
79      protected static final int[] START_INDICES = {
80          103, 104, 105
81      };
82      
83      protected static final int[] BUF_SIZES = {
84          1, 1, 2
85      };
86      
87      protected int startIndex;
88      protected int mode;
89      private int startingMode;
90      private boolean shiftNext;
91      private boolean shifted;
92      private CharBuffer buf;
93      private Module checkDigit;
94      private boolean optimising;
95      private Accumulator sum;
96      private Accumulator index;
97      
98      /**
99       * Create a new Code 128 barcode using character set B.
100      * @param data The data to encode
101      * @throws BarcodeException If the data to be encoded is invalid
102      */
103     public Code128Barcode(String data) throws BarcodeException {
104         this(data, O);
105     }
106     
107     /**
108      * Creates a new Coded 128 barcode with the specified data and the specified
109      * character set.
110      * @param data The data to encode
111      * @param mode The character set to use for encoding
112      * @throws BarcodeException If the data to be encoded is invalid
113      */
114     public Code128Barcode(String data, int mode) throws BarcodeException {
115         super(data);
116         if (mode == O) {
117             optimising = true;
118             this.mode = B;
119         } else {
120             optimising = false;
121             this.mode = mode;
122         }
123         this.startingMode = this.mode;
124         this.shiftNext = false;
125         this.shifted = false;
126         this.startIndex = START_INDICES[this.mode];
127     }
128     
129     /**
130      * Returns the current character set being used in this barcode.
131      * @return The flag indicating the current character set
132      */
133     public int getCharacterSet() {
134         return startingMode;
135     }
136     
137     /**
138      * Returns the text label to be displayed underneath the barcode/
139      * @return The text label for the barcode
140      */
141     public String getLabel() {
142         if(label != null) {
143             return label;
144         } else {
145             return data;
146         }
147     }
148     
149     /**
150      * Returns the width of the encoded symbol portion of the barcode in pixels for
151      * the given resolution.
152      * @param resolution The resolution to calculate the width for
153      * @return The width of the encoded portion of the barcode
154      */
155     protected double getSymbolWidth(int resolution) {
156         //		L = (11C + 35)X (alphanumeric) L = (5.5C + 35)X (numeric only using Code C)
157         //		where
158         //		L = length of symbol (not counting quiet zone)
159         //		C = number of data characters, code characters and shift characters
160         //		(do not include start, stop or checksum. They are automatically added in.)
161         //		X = X-dimension
162         double barWidthMM = convertToMillimetres(barWidth, resolution);
163         double multiplier = 11;
164         if (startingMode == C) {
165             multiplier = 5.5;
166         }
167         return (multiplier * data.length() + 35) * barWidthMM;
168     }
169     
170     /**
171      * Calculates the minimum allowed barcode height for the barcode. The height must
172      * be at least .15 times the length of the symbol (excluding quiet zones) and .25
173      * inches (whichever is larger).
174      * @param resolution The output resolution (for calculating the width)
175      * @return The minimum height
176      */
177     protected int calculateMinimumBarHeight(int resolution) {
178         // The height of the bars must be at least .15 times the symbol's length or .25 inches,
179         // whichever is larger
180         double point25Inches = resolution * 0.25;
181         // TODO: Need to get rid of this and do it in the output class
182         return (int) Math.max((0.15 * getSymbolWidth(resolution)), point25Inches);
183     }
184     
185     /**
186      * Encodes the data of the barcode into bars.
187      * @return The encoded bar data
188      */
189     public Module[] encodeData() {
190         // We are calculating the check digit as we encode - this will ensure that it is
191         // calculated correctly, even with code changes to char set C
192         sum = new Accumulator(startIndex);
193         List modules = new ArrayList();
194         buf = new CharBuffer(BUF_SIZES[mode]);
195         index = new Accumulator(1);
196         padDataToEvenLength();
197         
198         for (int i = 0; i < data.length(); i++) {
199             char c = data.charAt(i);
200             if (optimising && startingMode == B) {
201                 if (i + 1 < data.length() && isControl(c) && mode != A) {
202                     if (mode == B) {
203                         encode(modules, SHIFT);
204                     } else {
205                         encode(modules, CHANGE_TO_A);
206                     }
207                 } else if (i + 3 < data.length() && digitGroupIsNext(i, data) && mode != C) {
208                     encode(modules, CHANGE_TO_C);
209                 } else if (i + 1 <= data.length() && digitGroupEndIsNext(i, data)
210                 && mode == C && buf.size() != 1) {
211                     encode(modules, CHANGE_TO_B);
212                 }
213             }
214             
215             if (isShiftOrCode(c)) {
216                 encode(modules, String.valueOf(c));
217                 buf.clear();
218             } else {
219                 buf.addChar(c);
220                 if (buf.isFull()) {
221                     encode(modules, buf.toString());
222                     buf.clear();
223                 }
224             }
225         }
226         
227         checkDigit = ModuleFactory.getModuleForIndex(sum.getValue() % 103, mode);
228         mode = startingMode;
229         return (Module[]) modules.toArray(new Module[0]);
230     }
231     
232     private boolean isShiftOrCode(char c) {
233         String s = String.valueOf(c);
234         return (s.equals(SHIFT)
235         || s.equals(CHANGE_TO_A)
236         || s.equals(CHANGE_TO_B)
237         || s.equals(CHANGE_TO_C)
238         || s.equals(FNC_1));
239     }
240     
241     /**
242      * Calculates the check sum digit for the barcode.
243      * @return The check sum digit
244      */
245     public Module calculateChecksum() {
246         if (checkDigit == null) {
247             encodeData();
248         }
249         return checkDigit;
250     }
251     
252     /**
253      * Returns the pre-amble for the barcode. This is a combination of a
254      * quiet section and the start character for the character set that the barcode
255      * was constructed with.
256      * @return The pre-amble
257      */
258     protected Module getPreAmble() {
259         CompositeModule module = new CompositeModule();
260         if(drawingQuietSection) {
261             module.add(QUIET_SECTION);
262         }
263         module.add(START[mode]);
264         return module;
265     }
266     
267     /**
268      * Returns the post amble for the barcode. This is the combination
269      * of the stop character anda quiet section.
270      * @return The post amble
271      */
272     protected Module getPostAmble() {
273         CompositeModule module = new CompositeModule();
274         module.add(STOP);
275         if(drawingQuietSection) {
276             module.add(QUIET_SECTION);
277         }
278         return module;
279     }
280     
281     private boolean isControl(char c) {
282         return Character.isISOControl(c);
283     }
284     
285     private boolean digitGroupIsNext(int index, String chars) {
286         char c1 = chars.charAt(index);
287         char c2 = chars.charAt(index + 1);
288         char c3 = chars.charAt(index + 2);
289         char c4 = chars.charAt(index + 3);
290         return (Character.isDigit(c1)
291         && Character.isDigit(c2)
292         && Character.isDigit(c3)
293         && Character.isDigit(c4));
294     }
295     
296     private boolean digitGroupEndIsNext(int index, String chars) {
297         if (index == chars.length() - 1) {
298             return true;
299         }
300         char c1 = chars.charAt(index);
301         char c2 = chars.charAt(index + 1);
302         return ((Character.isDigit(c1) && (!Character.isDigit(c2)))
303         || (!Character.isDigit(c1)));
304     }
305     
306     /**
307      * Pads the data to be encoded to an even length by prepending "0" characters.
308      * This is only valid for pure character set C barcodes.
309      */
310     private void padDataToEvenLength() {
311         // Only for Code C
312         if (startingMode == C && data.length() % 2 != 0 && !containsShiftOrChange(data)) {
313             data = '0' + data;
314         }
315     }
316     
317     private boolean containsShiftOrChange(String data) {
318         return ((data.indexOf(CHANGE_TO_A) >= 0)
319         || (data.indexOf(CHANGE_TO_B) >= 0)
320         || (data.indexOf(CHANGE_TO_C) >= 0)
321         || (data.indexOf(SHIFT) >= 0));
322     }
323     
324     private void clearShift() {
325         if (shifted) {
326             shifted = false;
327             shiftNext = false;
328             mode = shiftMode();
329         }
330     }
331     
332     private void checkShift(Module module) {
333         if (module instanceof ShiftModule) {
334             mode = shiftMode();
335             shiftNext = true;
336         } else if (shiftNext) {
337             shifted = true;
338         }
339     }
340     
341     private int shiftMode() {
342         if (mode == A) {
343             return B;
344         } else {
345             return A;
346         }
347     }
348     
349     private void checkCodeChange(Module module) {
350         if (module instanceof CodeChangeModule) {
351             mode = ((CodeChangeModule) module).getCode();
352             buf = new CharBuffer(BUF_SIZES[mode]);
353         }
354     }
355     
356     private void encode(List modules, String data) {
357         Module module = ModuleFactory.getModule(data, mode);
358         updateCheckSum(data);
359         checkShift(module);
360         checkCodeChange(module);
361         modules.add(module);
362         clearShift();
363     }
364     
365     private void updateCheckSum(String data) {
366         int code = ModuleFactory.getIndex(data, mode);
367         sum.add(code * index.getValue());
368         index.increment();
369     }
370     
371     private double convertToMillimetres(double barWidth, int resolution) {
372         //25.4 mm in 1 inch
373         double pixelsPerMM = resolution / 25.4;
374         return barWidth / pixelsPerMM;
375     }
376 }