001/*
002 * $Id: Barcode128.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;
045import java.awt.Canvas;
046import java.awt.Image;
047import java.awt.image.MemoryImageSource;
048import com.itextpdf.text.error_messages.MessageLocalization;
049
050import com.itextpdf.text.Element;
051import com.itextpdf.text.ExceptionConverter;
052import com.itextpdf.text.Rectangle;
053import com.itextpdf.text.BaseColor;
054
055/**
056 * Implements the code 128 and UCC/EAN-128. Other symbologies are allowed in raw mode.<p>
057 * The code types allowed are:<br>
058 * <ul>
059 * <li><b>CODE128</b> - plain barcode 128.
060 * <li><b>CODE128_UCC</b> - support for UCC/EAN-128 with a full list of AI.
061 * <li><b>CODE128_RAW</b> - raw mode. The code attribute has the actual codes from 0
062 *     to 105 followed by '&#92;uffff' and the human readable text.
063 * </ul>
064 * The default parameters are:
065 * <pre>
066 * x = 0.8f;
067 * font = BaseFont.createFont("Helvetica", "winansi", false);
068 * size = 8;
069 * baseline = size;
070 * barHeight = size * 3;
071 * textAlignment = Element.ALIGN_CENTER;
072 * codeType = CODE128;
073 * </pre>
074 * @author Paulo Soares
075 */
076public class Barcode128 extends Barcode{
077
078    /** The bars to generate the code.
079     */    
080    private static final byte BARS[][] = 
081    {
082        {2, 1, 2, 2, 2, 2},
083        {2, 2, 2, 1, 2, 2},
084        {2, 2, 2, 2, 2, 1},
085        {1, 2, 1, 2, 2, 3},
086        {1, 2, 1, 3, 2, 2},
087        {1, 3, 1, 2, 2, 2},
088        {1, 2, 2, 2, 1, 3},
089        {1, 2, 2, 3, 1, 2},
090        {1, 3, 2, 2, 1, 2},
091        {2, 2, 1, 2, 1, 3},
092        {2, 2, 1, 3, 1, 2},
093        {2, 3, 1, 2, 1, 2},
094        {1, 1, 2, 2, 3, 2},
095        {1, 2, 2, 1, 3, 2},
096        {1, 2, 2, 2, 3, 1},
097        {1, 1, 3, 2, 2, 2},
098        {1, 2, 3, 1, 2, 2},
099        {1, 2, 3, 2, 2, 1},
100        {2, 2, 3, 2, 1, 1},
101        {2, 2, 1, 1, 3, 2},
102        {2, 2, 1, 2, 3, 1},
103        {2, 1, 3, 2, 1, 2},
104        {2, 2, 3, 1, 1, 2},
105        {3, 1, 2, 1, 3, 1},
106        {3, 1, 1, 2, 2, 2},
107        {3, 2, 1, 1, 2, 2},
108        {3, 2, 1, 2, 2, 1},
109        {3, 1, 2, 2, 1, 2},
110        {3, 2, 2, 1, 1, 2},
111        {3, 2, 2, 2, 1, 1},
112        {2, 1, 2, 1, 2, 3},
113        {2, 1, 2, 3, 2, 1},
114        {2, 3, 2, 1, 2, 1},
115        {1, 1, 1, 3, 2, 3},
116        {1, 3, 1, 1, 2, 3},
117        {1, 3, 1, 3, 2, 1},
118        {1, 1, 2, 3, 1, 3},
119        {1, 3, 2, 1, 1, 3},
120        {1, 3, 2, 3, 1, 1},
121        {2, 1, 1, 3, 1, 3},
122        {2, 3, 1, 1, 1, 3},
123        {2, 3, 1, 3, 1, 1},
124        {1, 1, 2, 1, 3, 3},
125        {1, 1, 2, 3, 3, 1},
126        {1, 3, 2, 1, 3, 1},
127        {1, 1, 3, 1, 2, 3},
128        {1, 1, 3, 3, 2, 1},
129        {1, 3, 3, 1, 2, 1},
130        {3, 1, 3, 1, 2, 1},
131        {2, 1, 1, 3, 3, 1},
132        {2, 3, 1, 1, 3, 1},
133        {2, 1, 3, 1, 1, 3},
134        {2, 1, 3, 3, 1, 1},
135        {2, 1, 3, 1, 3, 1},
136        {3, 1, 1, 1, 2, 3},
137        {3, 1, 1, 3, 2, 1},
138        {3, 3, 1, 1, 2, 1},
139        {3, 1, 2, 1, 1, 3},
140        {3, 1, 2, 3, 1, 1},
141        {3, 3, 2, 1, 1, 1},
142        {3, 1, 4, 1, 1, 1},
143        {2, 2, 1, 4, 1, 1},
144        {4, 3, 1, 1, 1, 1},
145        {1, 1, 1, 2, 2, 4},
146        {1, 1, 1, 4, 2, 2},
147        {1, 2, 1, 1, 2, 4},
148        {1, 2, 1, 4, 2, 1},
149        {1, 4, 1, 1, 2, 2},
150        {1, 4, 1, 2, 2, 1},
151        {1, 1, 2, 2, 1, 4},
152        {1, 1, 2, 4, 1, 2},
153        {1, 2, 2, 1, 1, 4},
154        {1, 2, 2, 4, 1, 1},
155        {1, 4, 2, 1, 1, 2},
156        {1, 4, 2, 2, 1, 1},
157        {2, 4, 1, 2, 1, 1},
158        {2, 2, 1, 1, 1, 4},
159        {4, 1, 3, 1, 1, 1},
160        {2, 4, 1, 1, 1, 2},
161        {1, 3, 4, 1, 1, 1},
162        {1, 1, 1, 2, 4, 2},
163        {1, 2, 1, 1, 4, 2},
164        {1, 2, 1, 2, 4, 1},
165        {1, 1, 4, 2, 1, 2},
166        {1, 2, 4, 1, 1, 2},
167        {1, 2, 4, 2, 1, 1},
168        {4, 1, 1, 2, 1, 2},
169        {4, 2, 1, 1, 1, 2},
170        {4, 2, 1, 2, 1, 1},
171        {2, 1, 2, 1, 4, 1},
172        {2, 1, 4, 1, 2, 1},
173        {4, 1, 2, 1, 2, 1},
174        {1, 1, 1, 1, 4, 3},
175        {1, 1, 1, 3, 4, 1},
176        {1, 3, 1, 1, 4, 1},
177        {1, 1, 4, 1, 1, 3},
178        {1, 1, 4, 3, 1, 1},
179        {4, 1, 1, 1, 1, 3},
180        {4, 1, 1, 3, 1, 1},
181        {1, 1, 3, 1, 4, 1},
182        {1, 1, 4, 1, 3, 1},
183        {3, 1, 1, 1, 4, 1},
184        {4, 1, 1, 1, 3, 1},
185        {2, 1, 1, 4, 1, 2},
186        {2, 1, 1, 2, 1, 4},
187        {2, 1, 1, 2, 3, 2}
188    };
189    
190    /** The stop bars.
191     */    
192    private static final byte BARS_STOP[] = {2, 3, 3, 1, 1, 1, 2};
193    /** The charset code change.
194     */
195    public static final char CODE_AB_TO_C = 99;
196    /** The charset code change.
197     */
198    public static final char CODE_AC_TO_B = 100;
199    /** The charset code change.
200     */
201    public static final char CODE_BC_TO_A = 101;
202    /** The code for UCC/EAN-128.
203     */
204    public static final char FNC1_INDEX = 102;
205    /** The start code.
206     */
207    public static final char START_A = 103;
208    /** The start code.
209     */
210    public static final char START_B = 104;
211    /** The start code.
212     */
213    public static final char START_C = 105;
214
215    public static final char FNC1 = '\u00ca';
216    public static final char DEL = '\u00c3';
217    public static final char FNC3 = '\u00c4';
218    public static final char FNC2 = '\u00c5';
219    public static final char SHIFT = '\u00c6';
220    public static final char CODE_C = '\u00c7';
221    public static final char CODE_A = '\u00c8';
222    public static final char FNC4 = '\u00c8';
223    public static final char STARTA = '\u00cb';
224    public static final char STARTB = '\u00cc';
225    public static final char STARTC = '\u00cd';
226    
227    private static final IntHashtable ais = new IntHashtable();
228    /** Creates new Barcode128 */
229    public Barcode128() {
230        try {
231            x = 0.8f;
232            font = BaseFont.createFont("Helvetica", "winansi", false);
233            size = 8;
234            baseline = size;
235            barHeight = size * 3;
236            textAlignment = Element.ALIGN_CENTER;
237            codeType = CODE128;
238        }
239        catch (Exception e) {
240            throw new ExceptionConverter(e);
241        }
242    }
243
244    /**
245     * Removes the FNC1 codes in the text.
246     * @param code the text to clean
247     * @return the cleaned text
248     */    
249    public static String removeFNC1(String code) {
250        int len = code.length();
251        StringBuffer buf = new StringBuffer(len);
252        for (int k = 0; k < len; ++k) {
253            char c = code.charAt(k);
254            if (c >= 32 && c <= 126)
255                buf.append(c);
256        }
257        return buf.toString();
258    }
259    
260    /**
261     * Gets the human readable text of a sequence of AI.
262     * @param code the text
263     * @return the human readable text
264     */    
265    public static String getHumanReadableUCCEAN(String code) {
266        StringBuffer buf = new StringBuffer();
267        String fnc1 = String.valueOf(FNC1);
268        try {
269            while (true) {
270                if (code.startsWith(fnc1)) {
271                    code = code.substring(1);
272                    continue;
273                }
274                int n = 0;
275                int idlen = 0;
276                for (int k = 2; k < 5; ++k) {
277                    if (code.length() < k)
278                        break;
279                    if ((n = ais.get(Integer.parseInt(code.substring(0, k)))) != 0) {
280                        idlen = k;
281                        break;
282                    }
283                }
284                if (idlen == 0)
285                    break;
286                buf.append('(').append(code.substring(0, idlen)).append(')');
287                code = code.substring(idlen);
288                if (n > 0) {
289                    n -= idlen;
290                    if (code.length() <= n)
291                        break;
292                    buf.append(removeFNC1(code.substring(0, n)));
293                    code = code.substring(n);
294                }
295                else {
296                    int idx = code.indexOf(FNC1);
297                    if (idx < 0)
298                        break;
299                    buf.append(code.substring(0,idx));
300                    code = code.substring(idx + 1);
301                }
302            }
303        }
304        catch (Exception e) {
305            //empty
306        }
307        buf.append(removeFNC1(code));
308        return buf.toString();
309    }
310    
311    /** Returns <CODE>true</CODE> if the next <CODE>numDigits</CODE>
312     * starting from index <CODE>textIndex</CODE> are numeric skipping any FNC1.
313     * @param text the text to check
314     * @param textIndex where to check from
315     * @param numDigits the number of digits to check
316     * @return the check result
317     */    
318    static boolean isNextDigits(String text, int textIndex, int numDigits) {
319        int len = text.length();
320        while (textIndex < len && numDigits > 0) {
321            if (text.charAt(textIndex) == FNC1) {
322                ++textIndex;
323                continue;
324            }
325            int n = Math.min(2, numDigits);
326            if (textIndex + n > len)
327                return false;
328            while (n-- > 0) {
329                char c = text.charAt(textIndex++);
330                if (c < '0' || c > '9')
331                    return false;
332                --numDigits;
333            }
334        }
335        return numDigits == 0;
336    }
337    
338    /** Packs the digits for charset C also considering FNC1. It assumes that all the parameters
339     * are valid.
340     * @param text the text to pack
341     * @param textIndex where to pack from
342     * @param numDigits the number of digits to pack. It is always an even number
343     * @return the packed digits, two digits per character
344     */    
345    static String getPackedRawDigits(String text, int textIndex, int numDigits) {
346        StringBuilder out = new StringBuilder("");
347        int start = textIndex;
348        while (numDigits > 0) {
349            if (text.charAt(textIndex) == FNC1) {
350                out.append(FNC1_INDEX);
351                ++textIndex;
352                continue;
353            }
354            numDigits -= 2;
355            int c1 = text.charAt(textIndex++) - '0';
356            int c2 = text.charAt(textIndex++) - '0';
357            out.append((char)(c1 * 10 + c2));
358        }
359        return (char)(textIndex - start) + out.toString();
360    }
361    
362    /** Converts the human readable text to the characters needed to
363     * create a barcode. Some optimization is done to get the shortest code.
364     * @param text the text to convert
365     * @param ucc <CODE>true</CODE> if it is an UCC/EAN-128. In this case
366     * the character FNC1 is added
367     * @return the code ready to be fed to getBarsCode128Raw()
368     */    
369    public static String getRawText(String text, boolean ucc) {
370        String out = "";
371        int tLen = text.length();
372        if (tLen == 0) {
373            out += START_B;
374            if (ucc)
375                out += FNC1_INDEX;
376            return out;
377        }
378        int c = 0;
379        for (int k = 0; k < tLen; ++k) {
380            c = text.charAt(k);
381            if (c > 127 && c != FNC1)
382                throw new RuntimeException(MessageLocalization.getComposedMessage("there.are.illegal.characters.for.barcode.128.in.1", text));
383        }
384        c = text.charAt(0);
385        char currentCode = START_B;
386        int index = 0;
387        if (isNextDigits(text, index, 2)) {
388            currentCode = START_C;
389            out += currentCode;
390            if (ucc)
391                out += FNC1_INDEX;
392            String out2 = getPackedRawDigits(text, index, 2);
393            index += out2.charAt(0);
394            out += out2.substring(1);
395        }
396        else if (c < ' ') {
397            currentCode = START_A;
398            out += currentCode;
399            if (ucc)
400                out += FNC1_INDEX;
401            out += (char)(c + 64);
402            ++index;
403        }
404        else {
405            out += currentCode;
406            if (ucc)
407                out += FNC1_INDEX;
408            if (c == FNC1)
409                out += FNC1_INDEX;
410            else
411                out += (char)(c - ' ');
412            ++index;
413        }
414        while (index < tLen) {
415            switch (currentCode) {
416                case START_A:
417                    {
418                        if (isNextDigits(text, index, 4)) {
419                            currentCode = START_C;
420                            out += CODE_AB_TO_C;
421                            String out2 = getPackedRawDigits(text, index, 4);
422                            index += out2.charAt(0);
423                            out += out2.substring(1);
424                        }
425                        else {
426                            c = text.charAt(index++);
427                            if (c == FNC1)
428                                out += FNC1_INDEX;
429                            else if (c > '_') {
430                                currentCode = START_B;
431                                out += CODE_AC_TO_B;
432                                out += (char)(c - ' ');
433                            }
434                            else if (c < ' ')
435                                out += (char)(c + 64);
436                            else
437                                out += (char)(c - ' ');
438                        }
439                    }
440                    break;
441                case START_B:
442                    {
443                        if (isNextDigits(text, index, 4)) {
444                            currentCode = START_C;
445                            out += CODE_AB_TO_C;
446                            String out2 = getPackedRawDigits(text, index, 4);
447                            index += out2.charAt(0);
448                            out += out2.substring(1);
449                        }
450                        else {
451                            c = text.charAt(index++);
452                            if (c == FNC1)
453                                out += FNC1_INDEX;
454                            else if (c < ' ') {
455                                currentCode = START_A;
456                                out += CODE_BC_TO_A;
457                                out += (char)(c + 64);
458                            }
459                            else {
460                                out += (char)(c - ' ');
461                            }
462                        }
463                    }
464                    break;
465                case START_C:
466                    {
467                        if (isNextDigits(text, index, 2)) {
468                            String out2 = getPackedRawDigits(text, index, 2);
469                            index += out2.charAt(0);
470                            out += out2.substring(1);
471                        }
472                        else {
473                            c = text.charAt(index++);
474                            if (c == FNC1)
475                                out += FNC1_INDEX;
476                            else if (c < ' ') {
477                                currentCode = START_A;
478                                out += CODE_BC_TO_A;
479                                out += (char)(c + 64);
480                            }
481                            else {
482                                currentCode = START_B;
483                                out += CODE_AC_TO_B;
484                                out += (char)(c - ' ');
485                            }
486                        }
487                    }
488                    break;
489            }
490        }
491        return out;
492    }
493    
494    /** Generates the bars. The input has the actual barcodes, not
495     * the human readable text.
496     * @param text the barcode
497     * @return the bars
498     */    
499    public static byte[] getBarsCode128Raw(String text) {
500        int idx = text.indexOf('\uffff');
501        if (idx >= 0)
502            text = text.substring(0, idx);
503        int chk = text.charAt(0);
504        for (int k = 1; k < text.length(); ++k)
505            chk += k * text.charAt(k);
506        chk = chk % 103;
507        text += (char)chk;
508        byte bars[] = new byte[(text.length() + 1) * 6 + 7];
509        int k;
510        for (k = 0; k < text.length(); ++k)
511            System.arraycopy(BARS[text.charAt(k)], 0, bars, k * 6, 6);
512        System.arraycopy(BARS_STOP, 0, bars, k * 6, 7);
513        return bars;
514    }
515    
516    /** Gets the maximum area that the barcode and the text, if
517     * any, will occupy. The lower left corner is always (0, 0).
518     * @return the size the barcode occupies.
519     */
520    public Rectangle getBarcodeSize() {
521        float fontX = 0;
522        float fontY = 0;
523        String fullCode;
524        if (font != null) {
525            if (baseline > 0)
526                fontY = baseline - font.getFontDescriptor(BaseFont.DESCENT, size);
527            else
528                fontY = -baseline + size;
529            if (codeType == CODE128_RAW) {
530                int idx = code.indexOf('\uffff');
531                if (idx < 0)
532                    fullCode = "";
533                else
534                    fullCode = code.substring(idx + 1);
535            }
536            else if (codeType == CODE128_UCC)
537                fullCode = getHumanReadableUCCEAN(code);
538            else
539                fullCode = removeFNC1(code);
540            fontX = font.getWidthPoint(altText != null ? altText : fullCode, size);
541        }
542        if (codeType == CODE128_RAW) {
543            int idx = code.indexOf('\uffff');
544            if (idx >= 0)
545                fullCode = code.substring(0, idx);
546            else
547                fullCode = code;
548        }
549        else {
550            fullCode = getRawText(code, codeType == CODE128_UCC);
551        }
552        int len = fullCode.length();
553        float fullWidth = (len + 2) * 11 * x + 2 * x;
554        fullWidth = Math.max(fullWidth, fontX);
555        float fullHeight = barHeight + fontY;
556        return new Rectangle(fullWidth, fullHeight);
557    }
558    
559    /** Places the barcode in a <CODE>PdfContentByte</CODE>. The
560     * barcode is always placed at coordinates (0, 0). Use the
561     * translation matrix to move it elsewhere.<p>
562     * The bars and text are written in the following colors:<p>
563     * <P><TABLE BORDER=1>
564     * <TR>
565     *   <TH><P><CODE>barColor</CODE></TH>
566     *   <TH><P><CODE>textColor</CODE></TH>
567     *   <TH><P>Result</TH>
568     *   </TR>
569     * <TR>
570     *   <TD><P><CODE>null</CODE></TD>
571     *   <TD><P><CODE>null</CODE></TD>
572     *   <TD><P>bars and text painted with current fill color</TD>
573     *   </TR>
574     * <TR>
575     *   <TD><P><CODE>barColor</CODE></TD>
576     *   <TD><P><CODE>null</CODE></TD>
577     *   <TD><P>bars and text painted with <CODE>barColor</CODE></TD>
578     *   </TR>
579     * <TR>
580     *   <TD><P><CODE>null</CODE></TD>
581     *   <TD><P><CODE>textColor</CODE></TD>
582     *   <TD><P>bars painted with current color<br>text painted with <CODE>textColor</CODE></TD>
583     *   </TR>
584     * <TR>
585     *   <TD><P><CODE>barColor</CODE></TD>
586     *   <TD><P><CODE>textColor</CODE></TD>
587     *   <TD><P>bars painted with <CODE>barColor</CODE><br>text painted with <CODE>textColor</CODE></TD>
588     *   </TR>
589     * </TABLE>
590     * @param cb the <CODE>PdfContentByte</CODE> where the barcode will be placed
591     * @param barColor the color of the bars. It can be <CODE>null</CODE>
592     * @param textColor the color of the text. It can be <CODE>null</CODE>
593     * @return the dimensions the barcode occupies
594     */
595    public Rectangle placeBarcode(PdfContentByte cb, BaseColor barColor, BaseColor textColor) {
596        String fullCode;
597        if (codeType == CODE128_RAW) {
598            int idx = code.indexOf('\uffff');
599            if (idx < 0)
600                fullCode = "";
601            else
602                fullCode = code.substring(idx + 1);
603        }
604        else if (codeType == CODE128_UCC)
605            fullCode = getHumanReadableUCCEAN(code);
606        else
607            fullCode = removeFNC1(code);
608        float fontX = 0;
609        if (font != null) {
610            fontX = font.getWidthPoint(fullCode = altText != null ? altText : fullCode, size);
611        }
612        String bCode;
613        if (codeType == CODE128_RAW) {
614            int idx = code.indexOf('\uffff');
615            if (idx >= 0)
616                bCode = code.substring(0, idx);
617            else
618                bCode = code;
619        }
620        else {
621            bCode = getRawText(code, codeType == CODE128_UCC);
622        }
623        int len = bCode.length();
624        float fullWidth = (len + 2) * 11 * x + 2 * x;
625        float barStartX = 0;
626        float textStartX = 0;
627        switch (textAlignment) {
628            case Element.ALIGN_LEFT:
629                break;
630            case Element.ALIGN_RIGHT:
631                if (fontX > fullWidth)
632                    barStartX = fontX - fullWidth;
633                else
634                    textStartX = fullWidth - fontX;
635                break;
636            default:
637                if (fontX > fullWidth)
638                    barStartX = (fontX - fullWidth) / 2;
639                else
640                    textStartX = (fullWidth - fontX) / 2;
641                break;
642        }
643        float barStartY = 0;
644        float textStartY = 0;
645        if (font != null) {
646            if (baseline <= 0)
647                textStartY = barHeight - baseline;
648            else {
649                textStartY = -font.getFontDescriptor(BaseFont.DESCENT, size);
650                barStartY = textStartY + baseline;
651            }
652        }
653        byte bars[] = getBarsCode128Raw(bCode);
654        boolean print = true;
655        if (barColor != null)
656            cb.setColorFill(barColor);
657        for (int k = 0; k < bars.length; ++k) {
658            float w = bars[k] * x;
659            if (print)
660                cb.rectangle(barStartX, barStartY, w - inkSpreading, barHeight);
661            print = !print;
662            barStartX += w;
663        }
664        cb.fill();
665        if (font != null) {
666            if (textColor != null)
667                cb.setColorFill(textColor);
668            cb.beginText();
669            cb.setFontAndSize(font, size);
670            cb.setTextMatrix(textStartX, textStartY);
671            cb.showText(fullCode);
672            cb.endText();
673        }
674        return getBarcodeSize();
675    }
676    
677    /** Creates a <CODE>java.awt.Image</CODE>. This image only
678     * contains the bars without any text.
679     * @param foreground the color of the bars
680     * @param background the color of the background
681     * @return the image
682     */    
683    public java.awt.Image createAwtImage(java.awt.Color foreground, java.awt.Color background) {
684        int f = foreground.getRGB();
685        int g = background.getRGB();
686        Canvas canvas = new Canvas();
687        String bCode;
688        if (codeType == CODE128_RAW) {
689            int idx = code.indexOf('\uffff');
690            if (idx >= 0)
691                bCode = code.substring(0, idx);
692            else
693                bCode = code;
694        }
695        else {
696            bCode = getRawText(code, codeType == CODE128_UCC);
697        }
698        int len = bCode.length();
699        int fullWidth = (len + 2) * 11 + 2;
700        byte bars[] = getBarsCode128Raw(bCode);
701        
702        boolean print = true;
703        int ptr = 0;
704        int height = (int)barHeight;
705        int pix[] = new int[fullWidth * height];
706        for (int k = 0; k < bars.length; ++k) {
707            int w = bars[k];
708            int c = g;
709            if (print)
710                c = f;
711            print = !print;
712            for (int j = 0; j < w; ++j)
713                pix[ptr++] = c;
714        }
715        for (int k = fullWidth; k < pix.length; k += fullWidth) {
716            System.arraycopy(pix, 0, pix, k, fullWidth); 
717        }
718        Image img = canvas.createImage(new MemoryImageSource(fullWidth, height, pix, 0, fullWidth));
719        
720        return img;
721    }
722    
723    /**
724     * Sets the code to generate. If it's an UCC code and starts with '(' it will
725     * be split by the AI. This code in UCC mode is valid:
726     * <p>
727     * <code>(01)00000090311314(10)ABC123(15)060916</code>
728     * @param code the code to generate
729     */
730    public void setCode(String code) {
731        if (getCodeType() == Barcode128.CODE128_UCC && code.startsWith("(")) {
732            int idx = 0;
733            StringBuilder ret = new StringBuilder("");
734            while (idx >= 0) {
735                int end = code.indexOf(')', idx);
736                if (end < 0)
737                    throw new IllegalArgumentException(MessageLocalization.getComposedMessage("badly.formed.ucc.string.1", code));
738                String sai = code.substring(idx + 1, end);
739                if (sai.length() < 2)
740                    throw new IllegalArgumentException(MessageLocalization.getComposedMessage("ai.too.short.1", sai));
741                int ai = Integer.parseInt(sai);
742                int len = ais.get(ai);
743                if (len == 0)
744                    throw new IllegalArgumentException(MessageLocalization.getComposedMessage("ai.not.found.1", sai));
745                sai = String.valueOf(ai);
746                if (sai.length() == 1)
747                    sai = "0" + sai;
748                idx = code.indexOf('(', end);
749                int next = (idx < 0 ? code.length() : idx);
750                ret.append(sai).append(code.substring(end + 1, next));
751                if (len < 0) {
752                    if (idx >= 0)
753                        ret.append(FNC1);
754                }
755                else if (next - end - 1 + sai.length() != len)
756                    throw new IllegalArgumentException(MessageLocalization.getComposedMessage("invalid.ai.length.1", sai));
757            }
758            super.setCode(ret.toString());
759        }
760        else
761            super.setCode(code);
762    }
763    
764    static {
765        ais.put(0, 20);
766        ais.put(1, 16);
767        ais.put(2, 16);
768        ais.put(10, -1);
769        ais.put(11, 9);
770        ais.put(12, 8);
771        ais.put(13, 8);
772        ais.put(15, 8);
773        ais.put(17, 8);
774        ais.put(20, 4);
775        ais.put(21, -1);
776        ais.put(22, -1);
777        ais.put(23, -1);
778        ais.put(240, -1);
779        ais.put(241, -1);
780        ais.put(250, -1);
781        ais.put(251, -1);
782        ais.put(252, -1);
783        ais.put(30, -1);
784        for (int k = 3100; k < 3700; ++k)
785            ais.put(k, 10);
786        ais.put(37, -1);
787        for (int k = 3900; k < 3940; ++k)
788            ais.put(k, -1);
789        ais.put(400, -1);
790        ais.put(401, -1);
791        ais.put(402, 20);
792        ais.put(403, -1);
793        for (int k = 410; k < 416; ++k)
794            ais.put(k, 16);
795        ais.put(420, -1);
796        ais.put(421, -1);
797        ais.put(422, 6);
798        ais.put(423, -1);
799        ais.put(424, 6);
800        ais.put(425, 6);
801        ais.put(426, 6);
802        ais.put(7001, 17);
803        ais.put(7002, -1);
804        for (int k = 7030; k < 7040; ++k)
805            ais.put(k, -1);
806        ais.put(8001, 18);
807        ais.put(8002, -1);
808        ais.put(8003, -1);
809        ais.put(8004, -1);
810        ais.put(8005, 10);
811        ais.put(8006, 22);
812        ais.put(8007, -1);
813        ais.put(8008, -1);
814        ais.put(8018, 22);
815        ais.put(8020, -1);
816        ais.put(8100, 10);
817        ais.put(8101, 14);
818        ais.put(8102, 6);
819        for (int k = 90; k < 100; ++k)
820            ais.put(k, -1);
821    }
822}