001/*
002 * $Id: BarcodeCodabar.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import java.awt.Canvas;
047import java.awt.Image;
048import java.awt.image.MemoryImageSource;
049import com.itextpdf.text.error_messages.MessageLocalization;
050
051import com.itextpdf.text.Element;
052import com.itextpdf.text.ExceptionConverter;
053import com.itextpdf.text.Rectangle;
054import com.itextpdf.text.BaseColor;
055
056/** Implements the code codabar. The default parameters are:
057 * <pre>
058 *x = 0.8f;
059 *n = 2;
060 *font = BaseFont.createFont("Helvetica", "winansi", false);
061 *size = 8;
062 *baseline = size;
063 *barHeight = size * 3;
064 *textAlignment = Element.ALIGN_CENTER;
065 *generateChecksum = false;
066 *checksumText = false;
067 *startStopText = false;
068 * </pre>
069 *
070 * @author Paulo Soares
071 */
072public class BarcodeCodabar extends Barcode{
073
074    /** The bars to generate the code.
075     */    
076        private static final byte BARS[][] = 
077    {
078        {0,0,0,0,0,1,1}, // 0
079        {0,0,0,0,1,1,0}, // 1
080        {0,0,0,1,0,0,1}, // 2
081        {1,1,0,0,0,0,0}, // 3
082        {0,0,1,0,0,1,0}, // 4
083        {1,0,0,0,0,1,0}, // 5
084        {0,1,0,0,0,0,1}, // 6
085        {0,1,0,0,1,0,0}, // 7
086        {0,1,1,0,0,0,0}, // 8
087        {1,0,0,1,0,0,0}, // 9
088        {0,0,0,1,1,0,0}, // -
089        {0,0,1,1,0,0,0}, // $
090        {1,0,0,0,1,0,1}, // :
091        {1,0,1,0,0,0,1}, // /
092        {1,0,1,0,1,0,0}, // .
093        {0,0,1,0,1,0,1}, // +
094        {0,0,1,1,0,1,0}, // a
095        {0,1,0,1,0,0,1}, // b
096        {0,0,0,1,0,1,1}, // c
097        {0,0,0,1,1,1,0}  // d
098    };
099 
100    /** The index chars to <CODE>BARS</CODE>.
101     */    
102        private static final String CHARS = "0123456789-$:/.+ABCD";
103    
104    private static final int START_STOP_IDX = 16;    
105    /** Creates a new BarcodeCodabar.
106     */    
107    public BarcodeCodabar() {
108        try {
109            x = 0.8f;
110            n = 2;
111            font = BaseFont.createFont("Helvetica", "winansi", false);
112            size = 8;
113            baseline = size;
114            barHeight = size * 3;
115            textAlignment = Element.ALIGN_CENTER;
116            generateChecksum = false;
117            checksumText = false;
118            startStopText = false;
119            codeType = CODABAR;
120        }
121        catch (Exception e) {
122            throw new ExceptionConverter(e);
123        }
124    }
125    
126    /** Creates the bars.
127     * @param text the text to create the bars
128     * @return the bars
129     */    
130    public static byte[] getBarsCodabar(String text) {
131        text = text.toUpperCase();
132        int len = text.length();
133        if (len < 2)
134            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("codabar.must.have.at.least.a.start.and.stop.character"));
135        if (CHARS.indexOf(text.charAt(0)) < START_STOP_IDX || CHARS.indexOf(text.charAt(len - 1)) < START_STOP_IDX)
136            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("codabar.must.have.one.of.abcd.as.start.stop.character"));
137        byte bars[] = new byte[text.length() * 8 - 1];
138        for (int k = 0; k < len; ++k) {
139            int idx = CHARS.indexOf(text.charAt(k));
140            if (idx >= START_STOP_IDX && k > 0 && k < len - 1)
141                throw new IllegalArgumentException(MessageLocalization.getComposedMessage("in.codabar.start.stop.characters.are.only.allowed.at.the.extremes"));
142            if (idx < 0)
143                throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.character.1.is.illegal.in.codabar", text.charAt(k)));
144            System.arraycopy(BARS[idx], 0, bars, k * 8, 7);
145        }
146        return bars;
147    }
148    
149    public static String calculateChecksum(String code) {
150        if (code.length() < 2)
151            return code;
152        String text = code.toUpperCase();
153        int sum = 0;
154        int len = text.length();
155        for (int k = 0; k < len; ++k)
156            sum += CHARS.indexOf(text.charAt(k));
157        sum = (sum + 15) / 16 * 16 - sum;
158        return code.substring(0, len - 1) + CHARS.charAt(sum) + code.substring(len - 1);
159    }
160    
161    /** Gets the maximum area that the barcode and the text, if
162     * any, will occupy. The lower left corner is always (0, 0).
163     * @return the size the barcode occupies.
164     */    
165    public Rectangle getBarcodeSize() {
166        float fontX = 0;
167        float fontY = 0;
168        String text = code;
169        if (generateChecksum && checksumText)
170            text = calculateChecksum(code);
171        if (!startStopText)
172            text = text.substring(1, text.length() - 1);
173        if (font != null) {
174            if (baseline > 0)
175                fontY = baseline - font.getFontDescriptor(BaseFont.DESCENT, size);
176            else
177                fontY = -baseline + size;
178            fontX = font.getWidthPoint(altText != null ? altText : text, size);
179        }
180        text = code;
181        if (generateChecksum)
182            text = calculateChecksum(code);
183        byte bars[] = getBarsCodabar(text);
184        int wide = 0;
185        for (int k = 0; k < bars.length; ++k) {
186            wide += bars[k];
187        }
188        int narrow = bars.length - wide;
189        float fullWidth = x * (narrow + wide * n);
190        fullWidth = Math.max(fullWidth, fontX);
191        float fullHeight = barHeight + fontY;
192        return new Rectangle(fullWidth, fullHeight);
193    }
194    
195    /** Places the barcode in a <CODE>PdfContentByte</CODE>. The
196     * barcode is always placed at coordinates (0, 0). Use the
197     * translation matrix to move it elsewhere.<p>
198     * The bars and text are written in the following colors:<p>
199     * <P><TABLE BORDER=1>
200     * <TR>
201     *    <TH><P><CODE>barColor</CODE></TH>
202     *    <TH><P><CODE>textColor</CODE></TH>
203     *    <TH><P>Result</TH>
204     *    </TR>
205     * <TR>
206     *    <TD><P><CODE>null</CODE></TD>
207     *    <TD><P><CODE>null</CODE></TD>
208     *    <TD><P>bars and text painted with current fill color</TD>
209     *    </TR>
210     * <TR>
211     *    <TD><P><CODE>barColor</CODE></TD>
212     *    <TD><P><CODE>null</CODE></TD>
213     *    <TD><P>bars and text painted with <CODE>barColor</CODE></TD>
214     *    </TR>
215     * <TR>
216     *    <TD><P><CODE>null</CODE></TD>
217     *    <TD><P><CODE>textColor</CODE></TD>
218     *    <TD><P>bars painted with current color<br>text painted with <CODE>textColor</CODE></TD>
219     *    </TR>
220     * <TR>
221     *    <TD><P><CODE>barColor</CODE></TD>
222     *    <TD><P><CODE>textColor</CODE></TD>
223     *    <TD><P>bars painted with <CODE>barColor</CODE><br>text painted with <CODE>textColor</CODE></TD>
224     *    </TR>
225     * </TABLE>
226     * @param cb the <CODE>PdfContentByte</CODE> where the barcode will be placed
227     * @param barColor the color of the bars. It can be <CODE>null</CODE>
228     * @param textColor the color of the text. It can be <CODE>null</CODE>
229     * @return the dimensions the barcode occupies
230     */    
231    public Rectangle placeBarcode(PdfContentByte cb, BaseColor barColor, BaseColor textColor) {
232        String fullCode = code;
233        if (generateChecksum && checksumText)
234            fullCode = calculateChecksum(code);
235        if (!startStopText)
236            fullCode = fullCode.substring(1, fullCode.length() - 1);
237        float fontX = 0;
238        if (font != null) {
239            fontX = font.getWidthPoint(fullCode = altText != null ? altText : fullCode, size);
240        }
241        byte bars[] = getBarsCodabar(generateChecksum ? calculateChecksum(code) : code);
242        int wide = 0;
243        for (int k = 0; k < bars.length; ++k) {
244            wide += bars[k];
245        }
246        int narrow = bars.length - wide;
247        float fullWidth = x * (narrow + wide * n);
248        float barStartX = 0;
249        float textStartX = 0;
250        switch (textAlignment) {
251            case Element.ALIGN_LEFT:
252                break;
253            case Element.ALIGN_RIGHT:
254                if (fontX > fullWidth)
255                    barStartX = fontX - fullWidth;
256                else
257                    textStartX = fullWidth - fontX;
258                break;
259            default:
260                if (fontX > fullWidth)
261                    barStartX = (fontX - fullWidth) / 2;
262                else
263                    textStartX = (fullWidth - fontX) / 2;
264                break;
265        }
266        float barStartY = 0;
267        float textStartY = 0;
268        if (font != null) {
269            if (baseline <= 0)
270                textStartY = barHeight - baseline;
271            else {
272                textStartY = -font.getFontDescriptor(BaseFont.DESCENT, size);
273                barStartY = textStartY + baseline;
274            }
275        }
276        boolean print = true;
277        if (barColor != null)
278            cb.setColorFill(barColor);
279        for (int k = 0; k < bars.length; ++k) {
280            float w = (bars[k] == 0 ? x : x * n);
281            if (print)
282                cb.rectangle(barStartX, barStartY, w - inkSpreading, barHeight);
283            print = !print;
284            barStartX += w;
285        }
286        cb.fill();
287        if (font != null) {
288            if (textColor != null)
289                cb.setColorFill(textColor);
290            cb.beginText();
291            cb.setFontAndSize(font, size);
292            cb.setTextMatrix(textStartX, textStartY);
293            cb.showText(fullCode);
294            cb.endText();
295        }
296        return getBarcodeSize();
297    }
298
299    /** Creates a <CODE>java.awt.Image</CODE>. This image only
300     * contains the bars without any text.
301     * @param foreground the color of the bars
302     * @param background the color of the background
303     * @return the image
304     */    
305    public java.awt.Image createAwtImage(java.awt.Color foreground, java.awt.Color background) {
306        int f = foreground.getRGB();
307        int g = background.getRGB();
308        Canvas canvas = new Canvas();
309
310        String fullCode = code;
311        if (generateChecksum && checksumText)
312            fullCode = calculateChecksum(code);
313        if (!startStopText)
314            fullCode = fullCode.substring(1, fullCode.length() - 1);
315        byte bars[] = getBarsCodabar(generateChecksum ? calculateChecksum(code) : code);
316        int wide = 0;
317        for (int k = 0; k < bars.length; ++k) {
318            wide += bars[k];
319        }
320        int narrow = bars.length - wide;
321        int fullWidth = narrow + wide * (int)n;
322        boolean print = true;
323        int ptr = 0;
324        int height = (int)barHeight;
325        int pix[] = new int[fullWidth * height];
326        for (int k = 0; k < bars.length; ++k) {
327            int w = (bars[k] == 0 ? 1 : (int)n);
328            int c = g;
329            if (print)
330                c = f;
331            print = !print;
332            for (int j = 0; j < w; ++j)
333                pix[ptr++] = c;
334        }
335        for (int k = fullWidth; k < pix.length; k += fullWidth) {
336            System.arraycopy(pix, 0, pix, k, fullWidth); 
337        }
338        Image img = canvas.createImage(new MemoryImageSource(fullWidth, height, pix, 0, fullWidth));
339        
340        return img;
341    }
342}