001/*
002 * $Id: TextField.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.io.IOException;
047import java.util.ArrayList;
048
049import com.itextpdf.text.BaseColor;
050import com.itextpdf.text.Chunk;
051import com.itextpdf.text.DocumentException;
052import com.itextpdf.text.Element;
053import com.itextpdf.text.Font;
054import com.itextpdf.text.Phrase;
055import com.itextpdf.text.Rectangle;
056
057/**
058 * Supports text, combo and list fields generating the correct appearances.
059 * All the option in the Acrobat GUI are supported in an easy to use API.
060 * @author Paulo Soares
061 */
062public class TextField extends BaseField {
063
064    /** Holds value of property defaultText. */
065    private String defaultText;
066
067    /** Holds value of property choices. */
068    private String[] choices;
069
070    /** Holds value of property choiceExports. */
071    private String[] choiceExports;
072
073    /** Holds value of property choiceSelection. */
074    private ArrayList<Integer> choiceSelections = new ArrayList<Integer>();
075
076    private int topFirst;
077
078    private float extraMarginLeft;
079    private float extraMarginTop;
080
081    /**
082     * Creates a new <CODE>TextField</CODE>.
083     * @param writer the document <CODE>PdfWriter</CODE>
084     * @param box the field location and dimensions
085     * @param fieldName the field name. If <CODE>null</CODE> only the widget keys
086     * will be included in the field allowing it to be used as a kid field.
087     */
088    public TextField(PdfWriter writer, Rectangle box, String fieldName) {
089        super(writer, box, fieldName);
090    }
091
092    private static boolean checkRTL(String text) {
093        if (text == null || text.length() == 0)
094            return false;
095        char[] cc = text.toCharArray();
096        for (int k = 0; k < cc.length; ++k) {
097            int c = cc[k];
098            if (c >= 0x590 && c < 0x0780)
099                return true;
100        }
101        return false;
102    }
103
104    private static void changeFontSize(Phrase p, float size) {
105        for (int k = 0; k < p.size(); ++k)
106            ((Chunk)p.get(k)).getFont().setSize(size);
107    }
108
109    private Phrase composePhrase(String text, BaseFont ufont, BaseColor color, float fontSize) {
110        Phrase phrase = null;
111        if (extensionFont == null && (substitutionFonts == null || substitutionFonts.isEmpty()))
112            phrase = new Phrase(new Chunk(text, new Font(ufont, fontSize, 0, color)));
113        else {
114            FontSelector fs = new FontSelector();
115            fs.addFont(new Font(ufont, fontSize, 0, color));
116            if (extensionFont != null)
117                fs.addFont(new Font(extensionFont, fontSize, 0, color));
118            if (substitutionFonts != null) {
119                for (int k = 0; k < substitutionFonts.size(); ++k)
120                    fs.addFont(new Font(substitutionFonts.get(k), fontSize, 0, color));
121            }
122            phrase = fs.process(text);
123        }
124        return phrase;
125    }
126
127    /**
128     * Removes CRLF from a <code>String</code>.
129     *
130     * @param text
131     * @return String
132     * @since   2.1.5
133     */
134    public static String removeCRLF(String text) {
135        if (text.indexOf('\n') >= 0 || text.indexOf('\r') >= 0) {
136            char[] p = text.toCharArray();
137            StringBuffer sb = new StringBuffer(p.length);
138            for (int k = 0; k < p.length; ++k) {
139                char c = p[k];
140                if (c == '\n')
141                    sb.append(' ');
142                else if (c == '\r') {
143                    sb.append(' ');
144                    if (k < p.length - 1 && p[k + 1] == '\n')
145                        ++k;
146                }
147                else
148                    sb.append(c);
149            }
150            return sb.toString();
151        }
152        return text;
153    }
154
155    /**
156     * Obfuscates a password <code>String</code>.
157     * Every character is replaced by an asterisk (*).
158     *
159     * @param text
160     * @return String
161     * @since   2.1.5
162     */
163    public static String obfuscatePassword(String text) {
164        char[] pchar = new char[text.length()];
165        for (int i = 0; i < text.length(); i++)
166                pchar[i] = '*';
167        return new String(pchar);
168    }
169
170    /**
171     * Get the <code>PdfAppearance</code> of a text or combo field
172     * @throws IOException on error
173     * @throws DocumentException on error
174     * @return A <code>PdfAppearance</code>
175     */
176    public PdfAppearance getAppearance() throws IOException, DocumentException {
177        PdfAppearance app = getBorderAppearance();
178        app.beginVariableText();
179        if (text == null || text.length() == 0) {
180            app.endVariableText();
181            return app;
182        }
183
184        boolean borderExtra = borderStyle == PdfBorderDictionary.STYLE_BEVELED || borderStyle == PdfBorderDictionary.STYLE_INSET;
185        float h = box.getHeight() - borderWidth * 2 - extraMarginTop;
186        float bw2 = borderWidth;
187        if (borderExtra) {
188            h -= borderWidth * 2;
189            bw2 *= 2;
190        }
191        float offsetX = Math.max(bw2, 1);
192        float offX = Math.min(bw2, offsetX);
193        app.saveState();
194        app.rectangle(offX, offX, box.getWidth() - 2 * offX, box.getHeight() - 2 * offX);
195        app.clip();
196        app.newPath();
197        String ptext;
198        if ((options & PASSWORD) != 0)
199                ptext = obfuscatePassword(text);
200        else if ((options & MULTILINE) == 0)
201            ptext = removeCRLF(text);
202        else
203                ptext = text; //fixed by Kazuya Ujihara (ujihara.jp)
204        BaseFont ufont = getRealFont();
205        BaseColor fcolor = textColor == null ? GrayColor.GRAYBLACK : textColor;
206        int rtl = checkRTL(ptext) ? PdfWriter.RUN_DIRECTION_LTR : PdfWriter.RUN_DIRECTION_NO_BIDI;
207        float usize = fontSize;
208        Phrase phrase = composePhrase(ptext, ufont, fcolor, usize);
209        if ((options & MULTILINE) != 0) {
210            float width = box.getWidth() - 4 * offsetX - extraMarginLeft;
211            float factor = ufont.getFontDescriptor(BaseFont.BBOXURY, 1) - ufont.getFontDescriptor(BaseFont.BBOXLLY, 1);
212            ColumnText ct = new ColumnText(null);
213            if (usize == 0) {
214                usize = h / factor;
215                if (usize > 4) {
216                    if (usize > 12)
217                        usize = 12;
218                    float step = Math.max((usize - 4) / 10, 0.2f);
219                    ct.setSimpleColumn(0, -h, width, 0);
220                    ct.setAlignment(alignment);
221                    ct.setRunDirection(rtl);
222                    for (; usize > 4; usize -= step) {
223                        ct.setYLine(0);
224                        changeFontSize(phrase, usize);
225                        ct.setText(phrase);
226                        ct.setLeading(factor * usize);
227                        int status = ct.go(true);
228                        if ((status & ColumnText.NO_MORE_COLUMN) == 0)
229                            break;
230                    }
231                }
232                if (usize < 4)
233                    usize = 4;
234            }
235            changeFontSize(phrase, usize);
236            ct.setCanvas(app);
237            float leading = usize * factor;
238            float offsetY = offsetX + h - ufont.getFontDescriptor(BaseFont.BBOXURY, usize);
239            ct.setSimpleColumn(extraMarginLeft + 2 * offsetX, -20000, box.getWidth() - 2 * offsetX, offsetY + leading);
240            ct.setLeading(leading);
241            ct.setAlignment(alignment);
242            ct.setRunDirection(rtl);
243            ct.setText(phrase);
244            ct.go();
245        }
246        else {
247            if (usize == 0) {
248                float maxCalculatedSize = h / (ufont.getFontDescriptor(BaseFont.BBOXURX, 1) - ufont.getFontDescriptor(BaseFont.BBOXLLY, 1));
249                changeFontSize(phrase, 1);
250                float wd = ColumnText.getWidth(phrase, rtl, 0);
251                if (wd == 0)
252                    usize = maxCalculatedSize;
253                else
254                        usize = Math.min(maxCalculatedSize, (box.getWidth() - extraMarginLeft - 4 * offsetX) / wd);
255                if (usize < 4)
256                    usize = 4;
257            }
258            changeFontSize(phrase, usize);
259            float offsetY = offX + (box.getHeight() - 2*offX - ufont.getFontDescriptor(BaseFont.ASCENT, usize)) / 2;
260            if (offsetY < offX)
261                offsetY = offX;
262            if (offsetY - offX < -ufont.getFontDescriptor(BaseFont.DESCENT, usize)) {
263                float ny = -ufont.getFontDescriptor(BaseFont.DESCENT, usize) + offX;
264                float dy = box.getHeight() - offX - ufont.getFontDescriptor(BaseFont.ASCENT, usize);
265                offsetY = Math.min(ny, Math.max(offsetY, dy));
266            }
267            if ((options & COMB) != 0 && maxCharacterLength > 0) {
268                int textLen = Math.min(maxCharacterLength, ptext.length());
269                int position = 0;
270                if (alignment == Element.ALIGN_RIGHT)
271                    position = maxCharacterLength - textLen;
272                else if (alignment == Element.ALIGN_CENTER)
273                    position = (maxCharacterLength - textLen) / 2;
274                float step = (box.getWidth() - extraMarginLeft) / maxCharacterLength;
275                float start = step / 2 + position * step;
276                if (textColor == null)
277                    app.setGrayFill(0);
278                else
279                    app.setColorFill(textColor);
280                app.beginText();
281                for (int k = 0; k < phrase.size(); ++k) {
282                    Chunk ck = (Chunk)phrase.get(k);
283                    BaseFont bf = ck.getFont().getBaseFont();
284                    app.setFontAndSize(bf, usize);
285                    StringBuffer sb = ck.append("");
286                    for (int j = 0; j < sb.length(); ++j) {
287                        String c = sb.substring(j, j + 1);
288                        float wd = bf.getWidthPoint(c, usize);
289                        app.setTextMatrix(extraMarginLeft + start - wd / 2, offsetY - extraMarginTop);
290                        app.showText(c);
291                        start += step;
292                    }
293                }
294                app.endText();
295            }
296            else {
297                float x;
298                switch (alignment) {
299                case Element.ALIGN_RIGHT:
300                        x = extraMarginLeft + box.getWidth() - 2 * offsetX;
301                        break;
302                case Element.ALIGN_CENTER:
303                        x = extraMarginLeft + box.getWidth() / 2;
304                        break;
305                default:
306                        x = extraMarginLeft + 2 * offsetX;
307                }
308                ColumnText.showTextAligned(app, alignment, phrase, x, offsetY - extraMarginTop, 0, rtl, 0);
309            }
310        }
311        app.restoreState();
312        app.endVariableText();
313        return app;
314    }
315
316    /**
317     * Get the <code>PdfAppearance</code> of a list field
318     * @throws IOException on error
319     * @throws DocumentException on error
320     * @return A <code>PdfAppearance</code>
321     */
322    PdfAppearance getListAppearance() throws IOException, DocumentException {
323        PdfAppearance app = getBorderAppearance();
324        if (choices == null || choices.length == 0) {
325            return app;
326        }
327        app.beginVariableText();
328
329        int topChoice = getTopChoice();
330
331        BaseFont ufont = getRealFont();
332        float usize = fontSize;
333        if (usize == 0)
334            usize = 12;
335
336        boolean borderExtra = borderStyle == PdfBorderDictionary.STYLE_BEVELED || borderStyle == PdfBorderDictionary.STYLE_INSET;
337        float h = box.getHeight() - borderWidth * 2;
338        float offsetX = borderWidth;
339        if (borderExtra) {
340            h -= borderWidth * 2;
341            offsetX *= 2;
342        }
343
344        float leading = ufont.getFontDescriptor(BaseFont.BBOXURY, usize) - ufont.getFontDescriptor(BaseFont.BBOXLLY, usize);
345        int maxFit = (int)(h / leading) + 1;
346        int first = 0;
347        int last = 0;
348        first = topChoice;
349        last = first + maxFit;
350        if (last > choices.length)
351            last = choices.length;
352        topFirst = first;
353        app.saveState();
354        app.rectangle(offsetX, offsetX, box.getWidth() - 2 * offsetX, box.getHeight() - 2 * offsetX);
355        app.clip();
356        app.newPath();
357        BaseColor fcolor = textColor == null ? GrayColor.GRAYBLACK : textColor;
358
359
360        // background boxes for selected value[s]
361        app.setColorFill(new BaseColor(10, 36, 106));
362        for (int curVal = 0; curVal < choiceSelections.size(); ++curVal) {
363                int curChoice = (choiceSelections.get( curVal )).intValue();
364                // only draw selections within our display range... not strictly necessary with
365                // that clipping rect from above, but it certainly doesn't hurt either
366                if (curChoice >= first && curChoice <= last) {
367                        app.rectangle(offsetX, offsetX + h - (curChoice - first + 1) * leading, box.getWidth() - 2 * offsetX, leading);
368                        app.fill();
369                }
370        }
371        float xp = offsetX * 2;
372        float yp = offsetX + h - ufont.getFontDescriptor(BaseFont.BBOXURY, usize);
373        for (int idx = first; idx < last; ++idx, yp -= leading) {
374            String ptext = choices[idx];
375            int rtl = checkRTL(ptext) ? PdfWriter.RUN_DIRECTION_LTR : PdfWriter.RUN_DIRECTION_NO_BIDI;
376            ptext = removeCRLF(ptext);
377            // highlight selected values against their (presumably) darker background
378            BaseColor textCol = choiceSelections.contains( Integer.valueOf( idx )) ? GrayColor.GRAYWHITE : fcolor;
379            Phrase phrase = composePhrase(ptext, ufont, textCol, usize);
380            ColumnText.showTextAligned(app, Element.ALIGN_LEFT, phrase, xp, yp, 0, rtl, 0);
381        }
382        app.restoreState();
383        app.endVariableText();
384        return app;
385    }
386
387    /**
388     * Gets a new text field.
389     * @throws IOException on error
390     * @throws DocumentException on error
391     * @return a new text field
392     */
393    public PdfFormField getTextField() throws IOException, DocumentException {
394        if (maxCharacterLength <= 0)
395            options &= ~COMB;
396        if ((options & COMB) != 0)
397            options &= ~MULTILINE;
398        PdfFormField field = PdfFormField.createTextField(writer, false, false, maxCharacterLength);
399        field.setWidget(box, PdfAnnotation.HIGHLIGHT_INVERT);
400        switch (alignment) {
401            case Element.ALIGN_CENTER:
402                field.setQuadding(PdfFormField.Q_CENTER);
403                break;
404            case Element.ALIGN_RIGHT:
405                field.setQuadding(PdfFormField.Q_RIGHT);
406                break;
407        }
408        if (rotation != 0)
409            field.setMKRotation(rotation);
410        if (fieldName != null) {
411            field.setFieldName(fieldName);
412            if (!"".equals(text))
413                field.setValueAsString(text);
414            if (defaultText != null)
415                field.setDefaultValueAsString(defaultText);
416            if ((options & READ_ONLY) != 0)
417                field.setFieldFlags(PdfFormField.FF_READ_ONLY);
418            if ((options & REQUIRED) != 0)
419                field.setFieldFlags(PdfFormField.FF_REQUIRED);
420            if ((options & MULTILINE) != 0)
421                field.setFieldFlags(PdfFormField.FF_MULTILINE);
422            if ((options & DO_NOT_SCROLL) != 0)
423                field.setFieldFlags(PdfFormField.FF_DONOTSCROLL);
424            if ((options & PASSWORD) != 0)
425                field.setFieldFlags(PdfFormField.FF_PASSWORD);
426            if ((options & FILE_SELECTION) != 0)
427                field.setFieldFlags(PdfFormField.FF_FILESELECT);
428            if ((options & DO_NOT_SPELL_CHECK) != 0)
429                field.setFieldFlags(PdfFormField.FF_DONOTSPELLCHECK);
430            if ((options & COMB) != 0)
431                field.setFieldFlags(PdfFormField.FF_COMB);
432        }
433        field.setBorderStyle(new PdfBorderDictionary(borderWidth, borderStyle, new PdfDashPattern(3)));
434        PdfAppearance tp = getAppearance();
435        field.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, tp);
436        PdfAppearance da = (PdfAppearance)tp.getDuplicate();
437        da.setFontAndSize(getRealFont(), fontSize);
438        if (textColor == null)
439            da.setGrayFill(0);
440        else
441            da.setColorFill(textColor);
442        field.setDefaultAppearanceString(da);
443        if (borderColor != null)
444            field.setMKBorderColor(borderColor);
445        if (backgroundColor != null)
446            field.setMKBackgroundColor(backgroundColor);
447        switch (visibility) {
448            case HIDDEN:
449                field.setFlags(PdfAnnotation.FLAGS_PRINT | PdfAnnotation.FLAGS_HIDDEN);
450                break;
451            case VISIBLE_BUT_DOES_NOT_PRINT:
452                break;
453            case HIDDEN_BUT_PRINTABLE:
454                field.setFlags(PdfAnnotation.FLAGS_PRINT | PdfAnnotation.FLAGS_NOVIEW);
455                break;
456            default:
457                field.setFlags(PdfAnnotation.FLAGS_PRINT);
458                break;
459        }
460        return field;
461    }
462
463    /**
464     * Gets a new combo field.
465     * @throws IOException on error
466     * @throws DocumentException on error
467     * @return a new combo field
468     */
469    public PdfFormField getComboField() throws IOException, DocumentException {
470        return getChoiceField(false);
471    }
472
473    /**
474     * Gets a new list field.
475     * @throws IOException on error
476     * @throws DocumentException on error
477     * @return a new list field
478     */
479    public PdfFormField getListField() throws IOException, DocumentException {
480        return getChoiceField(true);
481    }
482
483    private int getTopChoice() {
484        if (choiceSelections == null || choiceSelections.size() ==0) {
485                return 0;
486        }
487
488        Integer firstValue = choiceSelections.get(0);
489
490        if (firstValue == null) {
491                return 0;
492        }
493
494        int topChoice = 0;
495        if (choices != null) {
496                topChoice = firstValue.intValue();
497                topChoice = Math.min( topChoice, choices.length );
498                topChoice = Math.max( 0, topChoice);
499        } // else topChoice still 0
500        return topChoice;
501    }
502
503    protected PdfFormField getChoiceField(boolean isList) throws IOException, DocumentException {
504        options &= ~MULTILINE & ~COMB;
505        String uchoices[] = choices;
506        if (uchoices == null)
507            uchoices = new String[0];
508
509        int topChoice = getTopChoice();
510
511        if (text == null)
512                text = ""; //fixed by Kazuya Ujihara (ujihara.jp)
513
514        if (topChoice >= 0)
515            text = uchoices[topChoice];
516
517        PdfFormField field = null;
518        String mix[][] = null;
519
520        if (choiceExports == null) {
521            if (isList)
522                field = PdfFormField.createList(writer, uchoices, topChoice);
523            else
524                field = PdfFormField.createCombo(writer, (options & EDIT) != 0, uchoices, topChoice);
525        }
526        else {
527            mix = new String[uchoices.length][2];
528            for (int k = 0; k < mix.length; ++k)
529                mix[k][0] = mix[k][1] = uchoices[k];
530            int top = Math.min(uchoices.length, choiceExports.length);
531            for (int k = 0; k < top; ++k) {
532                if (choiceExports[k] != null)
533                    mix[k][0] = choiceExports[k];
534            }
535            if (isList)
536                field = PdfFormField.createList(writer, mix, topChoice);
537            else
538                field = PdfFormField.createCombo(writer, (options & EDIT) != 0, mix, topChoice);
539        }
540        field.setWidget(box, PdfAnnotation.HIGHLIGHT_INVERT);
541        if (rotation != 0)
542            field.setMKRotation(rotation);
543        if (fieldName != null) {
544            field.setFieldName(fieldName);
545            if (uchoices.length > 0) {
546                if (mix != null) {
547                        if (choiceSelections.size() < 2) {
548                                field.setValueAsString(mix[topChoice][0]);
549                                field.setDefaultValueAsString(mix[topChoice][0]);
550                        } else {
551                                writeMultipleValues( field, mix);
552                        }
553                } else {
554                        if (choiceSelections.size() < 2) {
555                                field.setValueAsString(text);
556                                field.setDefaultValueAsString(text);
557                        } else {
558                                writeMultipleValues( field, null );
559                        }
560                }
561            }
562            if ((options & READ_ONLY) != 0)
563                field.setFieldFlags(PdfFormField.FF_READ_ONLY);
564            if ((options & REQUIRED) != 0)
565                field.setFieldFlags(PdfFormField.FF_REQUIRED);
566            if ((options & DO_NOT_SPELL_CHECK) != 0)
567                field.setFieldFlags(PdfFormField.FF_DONOTSPELLCHECK);
568            if ((options & MULTISELECT) != 0) {
569                field.setFieldFlags( PdfFormField.FF_MULTISELECT );
570            }
571        }
572        field.setBorderStyle(new PdfBorderDictionary(borderWidth, borderStyle, new PdfDashPattern(3)));
573        PdfAppearance tp;
574        if (isList) {
575            tp = getListAppearance();
576            if (topFirst > 0)
577                field.put(PdfName.TI, new PdfNumber(topFirst));
578        }
579        else
580            tp = getAppearance();
581        field.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, tp);
582        PdfAppearance da = (PdfAppearance)tp.getDuplicate();
583        da.setFontAndSize(getRealFont(), fontSize);
584        if (textColor == null)
585            da.setGrayFill(0);
586        else
587            da.setColorFill(textColor);
588        field.setDefaultAppearanceString(da);
589        if (borderColor != null)
590            field.setMKBorderColor(borderColor);
591        if (backgroundColor != null)
592            field.setMKBackgroundColor(backgroundColor);
593        switch (visibility) {
594            case HIDDEN:
595                field.setFlags(PdfAnnotation.FLAGS_PRINT | PdfAnnotation.FLAGS_HIDDEN);
596                break;
597            case VISIBLE_BUT_DOES_NOT_PRINT:
598                break;
599            case HIDDEN_BUT_PRINTABLE:
600                field.setFlags(PdfAnnotation.FLAGS_PRINT | PdfAnnotation.FLAGS_NOVIEW);
601                break;
602            default:
603                field.setFlags(PdfAnnotation.FLAGS_PRINT);
604                break;
605        }
606        return field;
607    }
608
609    private void writeMultipleValues( PdfFormField field, String mix[][] ) {
610                PdfArray indexes = new PdfArray();
611                PdfArray values = new PdfArray();
612                for (int i = 0; i < choiceSelections.size(); ++i) {
613                        int idx = (choiceSelections.get( i )).intValue();
614                        indexes.add( new PdfNumber( idx ) );
615
616                        if (mix != null)
617                                values.add( new PdfString( mix[idx][0] ) );
618                        else if (choices != null)
619                                values.add( new PdfString( choices[ idx ] ) );
620                }
621
622                field.put( PdfName.V, values );
623                field.put( PdfName.I, indexes );
624
625    }
626
627    /**
628     * Gets the default text.
629     * @return the default text
630     */
631    public String getDefaultText() {
632        return this.defaultText;
633    }
634
635    /**
636     * Sets the default text. It is only meaningful for text fields.
637     * @param defaultText the default text
638     */
639    public void setDefaultText(String defaultText) {
640        this.defaultText = defaultText;
641    }
642
643    /**
644     * Gets the choices to be presented to the user in list/combo fields.
645     * @return the choices to be presented to the user
646     */
647    public String[] getChoices() {
648        return this.choices;
649    }
650
651    /**
652     * Sets the choices to be presented to the user in list/combo fields.
653     * @param choices the choices to be presented to the user
654     */
655    public void setChoices(String[] choices) {
656        this.choices = choices;
657    }
658
659    /**
660     * Gets the export values in list/combo fields.
661     * @return the export values in list/combo fields
662     */
663    public String[] getChoiceExports() {
664        return this.choiceExports;
665    }
666
667    /**
668     * Sets the export values in list/combo fields. If this array
669     * is <CODE>null</CODE> then the choice values will also be used
670     * as the export values.
671     * @param choiceExports the export values in list/combo fields
672     */
673    public void setChoiceExports(String[] choiceExports) {
674        this.choiceExports = choiceExports;
675    }
676
677    /**
678     * Gets the zero based index of the selected item.
679     * @return the zero based index of the selected item
680     */
681    public int getChoiceSelection() {
682        return getTopChoice();
683    }
684
685    /**
686     * Gets the selected items.
687     * @return the selected items
688     *
689     * @since 5.0.1
690     */
691    public ArrayList<Integer> getChoiceSelections() {
692        return choiceSelections;
693    }
694
695    /**
696     * Sets the zero based index of the selected item.
697     * @param choiceSelection the zero based index of the selected item
698     */
699    public void setChoiceSelection(int choiceSelection) {
700        choiceSelections = new ArrayList<Integer>();
701        choiceSelections.add( Integer.valueOf( choiceSelection ) );
702    }
703
704    /**
705     * adds another (or a first I suppose) selection to a MULTISELECT list.
706     * This doesn't do anything unless this.options & MUTLISELECT != 0
707     * @param selection new selection
708     */
709    public void addChoiceSelection( int selection) {
710        if ((this.options & BaseField.MULTISELECT) != 0) {
711                choiceSelections.add( Integer.valueOf( selection ) );
712        }
713    }
714
715    /**
716     * replaces the existing selections with the param. If this field isn't a MULTISELECT
717     * list, all but the first element will be removed.
718     * @param selections new selections.  If null, it clear()s the underlying ArrayList.
719     */
720    public void setChoiceSelections( ArrayList<Integer> selections ) {
721        if (selections != null) {
722                choiceSelections = new ArrayList<Integer>( selections );
723                if (choiceSelections.size() > 1 && (options & BaseField.MULTISELECT) == 0 ) {
724                        // can't have multiple selections in a single-select field
725                        while (choiceSelections.size() > 1) {
726                                choiceSelections.remove( 1 );
727                        }
728                }
729
730        } else {
731                choiceSelections.clear();
732        }
733    }
734
735    int getTopFirst() {
736        return topFirst;
737    }
738
739    /**
740     * Sets extra margins in text fields to better mimic the Acrobat layout.
741     * @param extraMarginLeft the extra margin left
742     * @param extraMarginTop the extra margin top
743     */
744    public void setExtraMargin(float extraMarginLeft, float extraMarginTop) {
745        this.extraMarginLeft = extraMarginLeft;
746        this.extraMarginTop = extraMarginTop;
747    }
748
749    /**
750     * Holds value of property substitutionFonts.
751     */
752    private ArrayList<BaseFont> substitutionFonts;
753
754    /**
755     * Gets the list of substitution fonts. The list is composed of <CODE>BaseFont</CODE> and can be <CODE>null</CODE>. The fonts in this list will be used if the original
756     * font doesn't contain the needed glyphs.
757     * @return the list
758     */
759    public ArrayList<BaseFont> getSubstitutionFonts() {
760        return this.substitutionFonts;
761    }
762
763    /**
764     * Sets a list of substitution fonts. The list is composed of <CODE>BaseFont</CODE> and can also be <CODE>null</CODE>. The fonts in this list will be used if the original
765     * font doesn't contain the needed glyphs.
766     * @param substitutionFonts the list
767     */
768    public void setSubstitutionFonts(ArrayList<BaseFont> substitutionFonts) {
769        this.substitutionFonts = substitutionFonts;
770    }
771
772    /**
773     * Holds value of property extensionFont.
774     */
775    private BaseFont extensionFont;
776
777    /**
778     * Gets the extensionFont. This font will be searched before the
779     * substitution fonts. It may be <code>null</code>.
780     * @return the extensionFont
781     */
782    public BaseFont getExtensionFont() {
783        return this.extensionFont;
784    }
785
786    /**
787     * Sets the extensionFont. This font will be searched before the
788     * substitution fonts. It may be <code>null</code>.
789     * @param extensionFont New value of property extensionFont.
790     */
791    public void setExtensionFont(BaseFont extensionFont) {
792        this.extensionFont = extensionFont;
793    }
794}