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
157
158
159
160
161
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
179
180 double point25Inches = resolution * 0.25;
181
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
191
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
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
373 double pixelsPerMM = resolution / 25.4;
374 return barWidth / pixelsPerMM;
375 }
376 }