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;
28
29 import net.sourceforge.barbecue.env.EnvironmentFactory;
30 import net.sourceforge.barbecue.env.HeadlessEnvironment;
31 import net.sourceforge.barbecue.output.GraphicsOutput;
32 import net.sourceforge.barbecue.output.Output;
33 import net.sourceforge.barbecue.output.OutputException;
34 import net.sourceforge.barbecue.output.SizingOutput;
35
36 import javax.swing.*;
37
38 import java.awt.*;
39 import java.awt.event.ActionEvent;
40 import java.awt.event.ActionListener;
41 import java.awt.event.MouseAdapter;
42 import java.awt.event.MouseListener;
43 import java.awt.event.MouseEvent;
44 import java.awt.print.PageFormat;
45 import java.awt.print.Printable;
46 import java.awt.print.PrinterException;
47 import java.awt.print.PrinterJob;
48 import java.text.CharacterIterator;
49 import java.text.StringCharacterIterator;
50
51 /**
52 * Abstract barcode class that provides functionality that is common to
53 * all barcodes. Specific barcode implementations must subclass
54 * this and provide information specific to the barcode type they are implementing.
55 *
56 * @author <a href="mailto:opensource@ianbourke.com">Ian Bourke</a>
57 */
58 public abstract class Barcode extends JComponent
59 implements Printable {
60
61 private static final int DEFAULT_BAR_HEIGHT = 50;
62
63 protected String data;
64 protected String label;
65 protected boolean drawingText;
66 protected boolean drawingQuietSection = true;
67 protected int barWidth = 2;
68 protected int barHeight;
69 private Font font;
70 private Dimension size;
71 private int x;
72 private int y;
73 private int resolution = -1;
74
75 protected Barcode(String data) throws BarcodeException {
76 if (data == null || data.length() == 0) {
77 throw new BarcodeException("Data to encode cannot be empty");
78 }
79 this.data = data;
80 int minHeight = calculateMinimumBarHeight(getResolution());
81 if (minHeight > 0) {
82 this.barHeight = minHeight;
83 } else {
84 this.barHeight = Barcode.DEFAULT_BAR_HEIGHT;
85 }
86 this.font = EnvironmentFactory.getEnvironment().getDefaultFont();
87 this.drawingText = true;
88 setBackground(Color.white);
89 setForeground(Color.black);
90 setOpaque(true);
91
92
93 invalidateSize();
94 }
95
96 /**
97 * Returns the data that the barcode is coding for.
98 *
99 * @return The barcode raw data
100 */
101 public String getData() {
102 return data;
103 }
104
105 /**
106 * Sets the font to use when drawing the barcode data string underneath the barcode.
107 * <p/> Note that changing this setting after a barcode has been drawn will invalidate the
108 * component and may force a refresh.
109 *
110 * @param font The font to use
111 */
112 public void setFont(Font font) {
113 this.font = font;
114 invalidateSize();
115 }
116
117 /**
118 * Indicates whether the barcode data should be shown as a string underneath the
119 * barcode or not.
120 * <p/> Note that changing this setting after a barcode has been drawn will invalidate the
121 * component and may force a refresh.
122 *
123 * @param drawingText True if the text should be shown, false if not
124 */
125 public void setDrawingText(boolean drawingText) {
126 this.drawingText = drawingText;
127 invalidateSize();
128 }
129
130 /**
131 * Indicates whether the barcode is drawing a text label underneath the barcode or not.
132 *
133 * @return True if the text is drawn, false otherwise
134 */
135 public boolean isDrawingText() {
136 return drawingText;
137 }
138
139 /**
140 * Indicates whether the leading and trailing white space should be rendered.
141 * <p/> Note that changing this setting after a barcode has been drawn will invalidate the
142 * component and may force a refresh.
143 *
144 * @param drawingQuietSection True if the quiet area/white space should be shown, false if not
145 */
146 public void setDrawingQuietSection(boolean drawingQuietSection) {
147 this.drawingQuietSection = drawingQuietSection;
148 invalidateSize();
149 }
150
151 /**
152 * Indicates whether the barcode is drawing leading and trailing white space/quiet area.
153 *
154 * @return True if the quiet area/white space is drawn, false otherwise
155 */
156 public boolean isDrawingQuietSection() {
157 return drawingQuietSection;
158 }
159
160 /**
161 * Sets the desired bar width for the barcode. This is the width (in pixels) of the
162 * thinnest bar in the barcode. Other bars will change their size relative to this.
163 * <p/> Note that changing this setting after a barcode has been drawn will invalidate the
164 * component and may force a refresh.
165 *
166 * @param barWidth The desired width of the thinnest bar in pixels
167 */
168 public void setBarWidth(int barWidth) {
169 if (barWidth >= 1) {
170 this.barWidth = barWidth;
171 } else {
172 this.barWidth = 1;
173 }
174 invalidateSize();
175 }
176
177 /**
178 * Sets the desired height for the bars in the barcode (in pixels). Note that some
179 * barcode implementations will not allow the height to go below a minimum size. This
180 * is not the height of the component as a whole, as it does not specify the height of
181 * any text that may be drawn and does not include borders.
182 * <p/> Note that changing this setting after a barcode has been drawn will invalidate the
183 * component and may force a refresh.
184 *
185 * @param barHeight The desired height of the barcode bars in pixels
186 */
187 public void setBarHeight(int barHeight) {
188
189 if (barHeight > calculateMinimumBarHeight(getResolution())) {
190 this.barHeight = barHeight;
191 invalidateSize();
192 }
193 }
194
195 /**
196 * Sets the desired output resolution for the barcode. This method should
197 * be used in cases where the barcode is either being outputted to a device
198 * other than the screen, or the barcode is being generated on a headless
199 * machine (e.g. a rack mounted server) and the screen resolution cannot be
200 * determined. Note that is the barcode is generated in either of these situations
201 * and this method has not been called, the resolution is assumed to be 72 dots
202 * per inch.
203 *
204 * @param resolution The desired output resolution (in dots per inch)
205 */
206 public void setResolution(int resolution) {
207 if (resolution > 0) {
208 this.resolution = resolution;
209 int newHeight = calculateMinimumBarHeight(getResolution());
210 if (newHeight > this.barHeight) {
211 this.barHeight = newHeight;
212 }
213 invalidateSize();
214 }
215 }
216
217 /**
218 * From {@link javax.swing.JComponent JComponent}.
219 *
220 * @return The X co-ordinate of the component's origin
221 */
222 public int getX() {
223 return x;
224 }
225
226 /**
227 * From {@link javax.swing.JComponent JComponent}.
228 *
229 * @return The Y co-ordinate of the component's origin
230 */
231 public int getY() {
232 return y;
233 }
234
235 /**
236 * From {@link javax.swing.JComponent JComponent}.
237 *
238 * @return The width of this component
239 */
240 public int getWidth() {
241 return (int) getActualSize().getWidth();
242 }
243
244 /**
245 * From {@link javax.swing.JComponent JComponent}.
246 *
247 * @return The height of this component
248 */
249 public int getHeight() {
250 return (int) getActualSize().getHeight();
251 }
252
253 /**
254 * From {@link javax.swing.JComponent JComponent}.
255 *
256 * @return The bounds of this component
257 */
258 public Rectangle getBounds() {
259 return getBounds(new Rectangle());
260 }
261
262 /**
263 * From {@link javax.swing.JComponent JComponent}.
264 *
265 * @param rv The rectangle to set the bounds on
266 * @return The updated rv
267 */
268 public Rectangle getBounds(Rectangle rv) {
269 rv.setBounds(getX(), getY(), (int) getActualSize().getWidth() + getX(),
270 (int) getActualSize().getHeight() + getY());
271 return rv;
272 }
273
274 /**
275 * From {@link javax.swing.JComponent JComponent}.
276 *
277 * @return The preferred size of this component
278 */
279 public Dimension getPreferredSize() {
280 return getActualSize();
281 }
282
283 /**
284 * From {@link javax.swing.JComponent JComponent}.
285 *
286 * @return The minimum size of this component
287 */
288 public Dimension getMinimumSize() {
289 return getActualSize();
290 }
291
292 /**
293 * From {@link javax.swing.JComponent JComponent}.
294 *
295 * @return The maximum size of this component
296 */
297 public Dimension getMaximumSize() {
298 return getActualSize();
299 }
300
301 /**
302 * From {@link javax.swing.JComponent JComponent}.
303 *
304 * @return The actual size of this component
305 */
306 public Dimension getSize() {
307 return getActualSize();
308 }
309
310 /**
311 * Renders this <code>Barcode</code> at the specified location in
312 * the specified {@link java.awt.Graphics2D Graphics2D} context.
313 * The origin of the layout is placed at x, y. Rendering may touch
314 * any point within <code>getBounds()</code> of this position. This
315 * leaves the <code>g2</code> unchanged.
316 *
317 * @param g The graphics context
318 * @param x The horizontal value of the upper left co-ordinate of the bounding box
319 * @param y The vertical value of the upper left co-ordinate of the bounding box
320 */
321 public void draw(Graphics2D g, int x, int y) throws OutputException {
322 this.x = x;
323 this.y = y;
324
325 Output output = new GraphicsOutput(g, font, getForeground(), getBackground());
326 size = draw(output, x, y, barWidth, barHeight);
327 }
328
329 public void output(Output output) throws OutputException {
330 draw(output, 0, 0, barWidth, barHeight);
331 }
332
333 protected abstract Module[] encodeData();
334
335 protected abstract Module calculateChecksum();
336
337 protected abstract Module getPreAmble();
338
339 protected abstract Module getPostAmble();
340
341 protected abstract Dimension draw(Output output, int x, int y, int barWidth, int barHeight) throws OutputException;
342
343 /**
344 * Returns the text that will be displayed underneath the barcode (if requested).
345 *
346 * @return The text label for the barcode
347 */
348 public String getLabel() {
349 if (label != null) {
350 return label;
351 } else {
352 return beautify(data);
353 }
354 }
355
356 /**
357 * Sets the human readable text to be displayed underneath the barcode.
358 * If set to null then the text will automaticaly be generated.
359 *
360 * @param label the human readable barcode text
361 * @see #getLabel()
362 */
363 public void setLabel(String label) {
364 this.label = label;
365 }
366
367 protected int calculateMinimumBarHeight(int resolution) {
368 return 0;
369 }
370
371 /**
372 * From {@link javax.swing.JComponent JComponent}.
373 *
374 * @param g The graphics to paint the component onto
375 */
376 protected void paintComponent(Graphics g) {
377 super.paintComponent(g);
378 Insets insets = getInsets();
379 try {
380 draw((Graphics2D) g, insets.left, insets.top);
381 } catch (OutputException e) {
382
383 }
384 }
385
386
387 protected int getResolution() {
388 if (resolution > 0) {
389 return resolution;
390 }
391 return EnvironmentFactory.getEnvironment().getResolution();
392 }
393
394 protected int drawModule(Module module, Output output, int x, int y, int barWidth, int barHeight) throws OutputException {
395 if (module == null) {
396 return 0;
397 }
398 return module.draw(output, x, y, barWidth, barHeight);
399 }
400
401 protected String beautify(String s) {
402 StringBuffer buf = new StringBuffer();
403 StringCharacterIterator iter = new StringCharacterIterator(s);
404 for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
405 if (Character.isDefined(c) && !Character.isISOControl(c)) {
406 buf.append(c);
407 }
408 }
409 return buf.toString();
410 }
411
412 private void invalidateSize() {
413 size = null;
414 }
415
416 private Dimension getActualSize() {
417 if (size == null) {
418 size = calculateSize();
419 }
420 return size;
421 }
422
423 private Dimension calculateSize() {
424 Dimension d = new Dimension();
425 if (EnvironmentFactory.getEnvironment() instanceof HeadlessEnvironment) {
426 try {
427 d = draw(new SizingOutput(font, getForeground(), getBackground()), 0, 0, barWidth, barHeight);
428 } catch (OutputException e) {
429 }
430 } else {
431 try {
432 FontMetrics fontMetrics = null;
433 if (font != null) {
434 fontMetrics = getFontMetrics(font);
435 }
436 d = draw(new SizingOutput(font, fontMetrics, getForeground(), getBackground()), 0, 0, barWidth, barHeight);
437 } catch (OutputException e) {
438 }
439 }
440
441 return d;
442 }
443
444 public int print(Graphics g, PageFormat pageFormat, int pageIndex)
445 throws PrinterException {
446
447 if (pageIndex >= 1) {
448 return Printable.NO_SUCH_PAGE;
449 }
450
451 try
452 {
453 this.draw( (Graphics2D) g, 0, 0);
454 return Printable.PAGE_EXISTS;
455 }
456 catch (OutputException ex)
457 {
458 throw new PrinterException(ex.getMessage());
459 }
460
461 }
462
463 public String toString()
464 {
465 return this.getData();
466 }
467
468
469
470 }