001/*
002 * $Id: Type3Font.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.util.HashMap;
047
048import com.itextpdf.text.DocumentException;
049import com.itextpdf.text.error_messages.MessageLocalization;
050
051/**
052 * A class to support Type3 fonts.
053 */
054public class Type3Font extends BaseFont {
055
056        private boolean[] usedSlot;
057    private IntHashtable widths3 = new IntHashtable();
058    private HashMap<Integer, Type3Glyph> char2glyph = new HashMap<Integer, Type3Glyph>();
059    private PdfWriter writer;
060    private float llx = Float.NaN, lly, urx, ury;
061    private PageResources pageResources = new PageResources();
062    private boolean colorized;
063
064    /**
065     * Creates a Type3 font.
066     * @param writer the writer
067     * @param chars an array of chars corresponding to the glyphs used (not used, present for compatibility only)
068     * @param colorized if <CODE>true</CODE> the font may specify color, if <CODE>false</CODE> no color commands are allowed
069     * and only images as masks can be used
070     */
071    public Type3Font(PdfWriter writer, char[] chars, boolean colorized) {
072        this(writer, colorized);
073    }
074
075    /**
076     * Creates a Type3 font. This implementation assumes that the /FontMatrix is
077     * [0.001 0 0 0.001 0 0] or a 1000-unit glyph coordinate system.
078     * <p>
079     * An example:
080     * <p>
081     * <pre>
082     * Document document = new Document(PageSize.A4);
083     * PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("type3.pdf"));
084     * document.open();
085     * Type3Font t3 = new Type3Font(writer, false);
086     * PdfContentByte g = t3.defineGlyph('a', 1000, 0, 0, 750, 750);
087     * g.rectangle(0, 0, 750, 750);
088     * g.fill();
089     * g = t3.defineGlyph('b', 1000, 0, 0, 750, 750);
090     * g.moveTo(0, 0);
091     * g.lineTo(375, 750);
092     * g.lineTo(750, 0);
093     * g.fill();
094     * Font f = new Font(t3, 12);
095     * document.add(new Paragraph("ababab", f));
096     * document.close();
097     * </pre>
098     * @param writer the writer
099     * @param colorized if <CODE>true</CODE> the font may specify color, if <CODE>false</CODE> no color commands are allowed
100     * and only images as masks can be used
101     */
102    public Type3Font(PdfWriter writer, boolean colorized) {
103        this.writer = writer;
104        this.colorized = colorized;
105        fontType = FONT_TYPE_T3;
106        usedSlot = new boolean[256];
107    }
108
109    /**
110     * Defines a glyph. If the character was already defined it will return the same content
111     * @param c the character to match this glyph.
112     * @param wx the advance this character will have
113     * @param llx the X lower left corner of the glyph bounding box. If the <CODE>colorize</CODE> option is
114     * <CODE>true</CODE> the value is ignored
115     * @param lly the Y lower left corner of the glyph bounding box. If the <CODE>colorize</CODE> option is
116     * <CODE>true</CODE> the value is ignored
117     * @param urx the X upper right corner of the glyph bounding box. If the <CODE>colorize</CODE> option is
118     * <CODE>true</CODE> the value is ignored
119     * @param ury the Y upper right corner of the glyph bounding box. If the <CODE>colorize</CODE> option is
120     * <CODE>true</CODE> the value is ignored
121     * @return a content where the glyph can be defined
122     */
123    public PdfContentByte defineGlyph(char c, float wx, float llx, float lly, float urx, float ury) {
124        if (c == 0 || c > 255)
125            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.char.1.doesn.t.belong.in.this.type3.font", c));
126        usedSlot[c] = true;
127        Integer ck = Integer.valueOf(c);
128        Type3Glyph glyph = char2glyph.get(ck);
129        if (glyph != null)
130            return glyph;
131        widths3.put(c, (int)wx);
132        if (!colorized) {
133            if (Float.isNaN(this.llx)) {
134                this.llx = llx;
135                this.lly = lly;
136                this.urx = urx;
137                this.ury = ury;
138            }
139            else {
140                this.llx = Math.min(this.llx, llx);
141                this.lly = Math.min(this.lly, lly);
142                this.urx = Math.max(this.urx, urx);
143                this.ury = Math.max(this.ury, ury);
144            }
145        }
146        glyph = new Type3Glyph(writer, pageResources, wx, llx, lly, urx, ury, colorized);
147        char2glyph.put(ck, glyph);
148        return glyph;
149    }
150
151    @Override
152    public String[][] getFamilyFontName() {
153        return getFullFontName();
154    }
155
156    @Override
157    public float getFontDescriptor(int key, float fontSize) {
158        return 0;
159    }
160
161    @Override
162    public String[][] getFullFontName() {
163        return new String[][]{{"", "", "", ""}};
164    }
165
166    /**
167     * @since 2.0.8
168     */
169    @Override
170    public String[][] getAllNameEntries() {
171        return new String[][]{{"4", "", "", "", ""}};
172    }
173
174    @Override
175    public int getKerning(int char1, int char2) {
176        return 0;
177    }
178
179    @Override
180    public String getPostscriptFontName() {
181        return "";
182    }
183
184    @Override
185    protected int[] getRawCharBBox(int c, String name) {
186        return null;
187    }
188
189    @Override
190    int getRawWidth(int c, String name) {
191        return 0;
192    }
193
194    @Override
195    public boolean hasKernPairs() {
196        return false;
197    }
198
199    @Override
200    public boolean setKerning(int char1, int char2, int kern) {
201        return false;
202    }
203
204    @Override
205    public void setPostscriptFontName(String name) {
206    }
207
208    @Override
209    void writeFont(PdfWriter writer, PdfIndirectReference ref, Object[] params) throws com.itextpdf.text.DocumentException, java.io.IOException {
210        if (this.writer != writer)
211            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("type3.font.used.with.the.wrong.pdfwriter"));
212
213        // Get first & lastchar ...
214        int firstChar = 0;
215        while( firstChar < usedSlot.length && !usedSlot[firstChar] ) firstChar++;
216
217        if ( firstChar == usedSlot.length ) {
218                throw new DocumentException(MessageLocalization.getComposedMessage("no.glyphs.defined.for.type3.font"));
219        }
220        int lastChar = usedSlot.length - 1;
221        while( lastChar >= firstChar && !usedSlot[lastChar] ) lastChar--;
222
223        int[] widths = new int[lastChar - firstChar + 1];
224        int[] invOrd = new int[lastChar - firstChar + 1];
225
226        int invOrdIndx = 0, w = 0;
227        for( int u = firstChar; u<=lastChar; u++, w++ ) {
228            if ( usedSlot[u] ) {
229                invOrd[invOrdIndx++] = u;
230                widths[w] = widths3.get(u);
231            }
232        }
233        PdfArray diffs = new PdfArray();
234        PdfDictionary charprocs = new PdfDictionary();
235        int last = -1;
236        for (int k = 0; k < invOrdIndx; ++k) {
237            int c = invOrd[k];
238            if (c > last) {
239                last = c;
240                diffs.add(new PdfNumber(last));
241            }
242            ++last;
243            int c2 = invOrd[k];
244            String s = GlyphList.unicodeToName(c2);
245            if (s == null)
246                s = "a" + c2;
247            PdfName n = new PdfName(s);
248            diffs.add(n);
249            Type3Glyph glyph = char2glyph.get(Integer.valueOf(c2));
250            PdfStream stream = new PdfStream(glyph.toPdf(null));
251            stream.flateCompress(compressionLevel);
252            PdfIndirectReference refp = writer.addToBody(stream).getIndirectReference();
253            charprocs.put(n, refp);
254        }
255        PdfDictionary font = new PdfDictionary(PdfName.FONT);
256        font.put(PdfName.SUBTYPE, PdfName.TYPE3);
257        if (colorized)
258            font.put(PdfName.FONTBBOX, new PdfRectangle(0, 0, 0, 0));
259        else
260            font.put(PdfName.FONTBBOX, new PdfRectangle(llx, lly, urx, ury));
261        font.put(PdfName.FONTMATRIX, new PdfArray(new float[]{0.001f, 0, 0, 0.001f, 0, 0}));
262        font.put(PdfName.CHARPROCS, writer.addToBody(charprocs).getIndirectReference());
263        PdfDictionary encoding = new PdfDictionary();
264        encoding.put(PdfName.DIFFERENCES, diffs);
265        font.put(PdfName.ENCODING, writer.addToBody(encoding).getIndirectReference());
266        font.put(PdfName.FIRSTCHAR, new PdfNumber(firstChar));
267        font.put(PdfName.LASTCHAR, new PdfNumber(lastChar));
268        font.put(PdfName.WIDTHS, writer.addToBody(new PdfArray(widths)).getIndirectReference());
269        if (pageResources.hasResources())
270            font.put(PdfName.RESOURCES, writer.addToBody(pageResources.getResources()).getIndirectReference());
271        writer.addToBody(font, ref);
272    }
273
274    /**
275     * Always returns null, because you can't get the FontStream of a Type3 font.
276         * @return      null
277     * @since   2.1.3
278     */
279    @Override
280    public PdfStream getFullFontStream() {
281        return null;
282    }
283
284
285    @Override
286    byte[] convertToBytes(String text) {
287        char[] cc = text.toCharArray();
288        byte[] b = new byte[cc.length];
289        int p = 0;
290        for (int k = 0; k < cc.length; ++k) {
291            char c = cc[k];
292            if (charExists(c))
293                b[p++] = (byte)c;
294        }
295        if (b.length == p)
296            return b;
297        byte[] b2 = new byte[p];
298        System.arraycopy(b, 0, b2, 0, p);
299        return b2;
300    }
301
302    @Override
303    byte[] convertToBytes(int char1) {
304        if (charExists(char1))
305            return new byte[]{(byte)char1};
306        else return new byte[0];
307    }
308
309    @Override
310    public int getWidth(int char1) {
311        if (!widths3.containsKey(char1))
312            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.char.1.is.not.defined.in.a.type3.font", char1));
313        return widths3.get(char1);
314    }
315
316    @Override
317    public int getWidth(String text) {
318        char[] c = text.toCharArray();
319        int total = 0;
320        for (int k = 0; k < c.length; ++k)
321            total += getWidth(c[k]);
322        return total;
323    }
324
325    @Override
326    public int[] getCharBBox(int c) {
327        return null;
328    }
329
330    @Override
331    public boolean charExists(int c) {
332        if (c > 0 && c < 256) {
333            return usedSlot[c];
334        } else {
335            return false;
336        }
337    }
338
339    @Override
340    public boolean setCharAdvance(int c, int advance) {
341        return false;
342    }
343
344}