001/*
002 * $Id: Type1Font.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.ByteArrayOutputStream;
047import java.io.IOException;
048import java.io.InputStream;
049import java.util.HashMap;
050import java.util.StringTokenizer;
051
052import com.itextpdf.text.Document;
053import com.itextpdf.text.DocumentException;
054import com.itextpdf.text.error_messages.MessageLocalization;
055import com.itextpdf.text.pdf.fonts.FontsResourceAnchor;
056
057/** Reads a Type1 font
058 *
059 * @author Paulo Soares
060 */
061class Type1Font extends BaseFont
062{
063    private static FontsResourceAnchor resourceAnchor;
064
065    /** The PFB file if the input was made with a <CODE>byte</CODE> array.
066     */
067    protected byte pfb[];
068/** The Postscript font name.
069 */
070    private String FontName;
071/** The full name of the font.
072 */
073    private String FullName;
074/** The family name of the font.
075 */
076    private String FamilyName;
077/** The weight of the font: normal, bold, etc.
078 */
079    private String Weight = "";
080/** The italic angle of the font, usually 0.0 or negative.
081 */
082    private float ItalicAngle = 0.0f;
083/** <CODE>true</CODE> if all the characters have the same
084 *  width.
085 */
086    private boolean IsFixedPitch = false;
087/** The character set of the font.
088 */
089    private String CharacterSet;
090/** The llx of the FontBox.
091 */
092    private int llx = -50;
093/** The lly of the FontBox.
094 */
095    private int lly = -200;
096/** The lurx of the FontBox.
097 */
098    private int urx = 1000;
099/** The ury of the FontBox.
100 */
101    private int ury = 900;
102/** The underline position.
103 */
104    private int UnderlinePosition = -100;
105/** The underline thickness.
106 */
107    private int UnderlineThickness = 50;
108/** The font's encoding name. This encoding is 'StandardEncoding' or
109 *  'AdobeStandardEncoding' for a font that can be totally encoded
110 *  according to the characters names. For all other names the
111 *  font is treated as symbolic.
112 */
113    private String EncodingScheme = "FontSpecific";
114/** A variable.
115 */
116    private int CapHeight = 700;
117/** A variable.
118 */
119    private int XHeight = 480;
120/** A variable.
121 */
122    private int Ascender = 800;
123/** A variable.
124 */
125    private int Descender = -200;
126/** A variable.
127 */
128    private int StdHW;
129/** A variable.
130 */
131    private int StdVW = 80;
132
133/** Represents the section CharMetrics in the AFM file. Each
134 *  value of this array contains a <CODE>Object[4]</CODE> with an
135 *  Integer, Integer, String and int[]. This is the code, width, name and char bbox.
136 *  The key is the name of the char and also an Integer with the char number.
137 */
138    private HashMap<Object, Object[]> CharMetrics = new HashMap<Object, Object[]>();
139/** Represents the section KernPairs in the AFM file. The key is
140 *  the name of the first character and the value is a <CODE>Object[]</CODE>
141 *  with 2 elements for each kern pair. Position 0 is the name of
142 *  the second character and position 1 is the kerning distance. This is
143 *  repeated for all the pairs.
144 */
145    private HashMap<String, Object[]> KernPairs = new HashMap<String, Object[]>();
146/** The file in use.
147 */
148    private String fileName;
149/** <CODE>true</CODE> if this font is one of the 14 built in fonts.
150 */
151    private boolean builtinFont = false;
152/** Types of records in a PFB file. ASCII is 1 and BINARY is 2.
153 *  They have to appear in the PFB file in this sequence.
154 */
155    private static final int PFB_TYPES[] = {1, 2, 1};
156
157    /** Creates a new Type1 font.
158     * @param ttfAfm the AFM file if the input is made with a <CODE>byte</CODE> array
159     * @param pfb the PFB file if the input is made with a <CODE>byte</CODE> array
160     * @param afmFile the name of one of the 14 built-in fonts or the location of an AFM file. The file must end in '.afm'
161     * @param enc the encoding to be applied to this font
162     * @param emb true if the font is to be embedded in the PDF
163     * @throws DocumentException the AFM file is invalid
164     * @throws IOException the AFM file could not be read
165     * @since   2.1.5
166     */
167    Type1Font(String afmFile, String enc, boolean emb, byte ttfAfm[], byte pfb[], boolean forceRead)
168        throws DocumentException, IOException {
169        if (emb && ttfAfm != null && pfb == null)
170            throw new DocumentException(MessageLocalization.getComposedMessage("two.byte.arrays.are.needed.if.the.type1.font.is.embedded"));
171        if (emb && ttfAfm != null)
172            this.pfb = pfb;
173        encoding = enc;
174        embedded = emb;
175        fileName = afmFile;
176        fontType = FONT_TYPE_T1;
177        RandomAccessFileOrArray rf = null;
178        InputStream is = null;
179        if (BuiltinFonts14.containsKey(afmFile)) {
180            embedded = false;
181            builtinFont = true;
182            byte buf[] = new byte[1024];
183            try {
184                if (resourceAnchor == null)
185                    resourceAnchor = new FontsResourceAnchor();
186                is = getResourceStream(RESOURCE_PATH + afmFile + ".afm", resourceAnchor.getClass().getClassLoader());
187                if (is == null) {
188                    String msg = MessageLocalization.getComposedMessage("1.not.found.as.resource", afmFile);
189                    System.err.println(msg);
190                    throw new DocumentException(msg);
191                }
192                ByteArrayOutputStream out = new ByteArrayOutputStream();
193                while (true) {
194                    int size = is.read(buf);
195                    if (size < 0)
196                        break;
197                    out.write(buf, 0, size);
198                }
199                buf = out.toByteArray();
200            }
201            finally {
202                if (is != null) {
203                    try {
204                        is.close();
205                    }
206                    catch (Exception e) {
207                        // empty on purpose
208                    }
209                }
210            }
211            try {
212                rf = new RandomAccessFileOrArray(buf);
213                process(rf);
214            }
215            finally {
216                if (rf != null) {
217                    try {
218                        rf.close();
219                    }
220                    catch (Exception e) {
221                        // empty on purpose
222                    }
223                }
224            }
225        }
226        else if (afmFile.toLowerCase().endsWith(".afm")) {
227            try {
228                if (ttfAfm == null)
229                    rf = new RandomAccessFileOrArray(afmFile, forceRead, Document.plainRandomAccess);
230                else
231                    rf = new RandomAccessFileOrArray(ttfAfm);
232                process(rf);
233            }
234            finally {
235                if (rf != null) {
236                    try {
237                        rf.close();
238                    }
239                    catch (Exception e) {
240                        // empty on purpose
241                    }
242                }
243            }
244        }
245        else if (afmFile.toLowerCase().endsWith(".pfm")) {
246            try {
247                ByteArrayOutputStream ba = new ByteArrayOutputStream();
248                if (ttfAfm == null)
249                    rf = new RandomAccessFileOrArray(afmFile, forceRead, Document.plainRandomAccess);
250                else
251                    rf = new RandomAccessFileOrArray(ttfAfm);
252                Pfm2afm.convert(rf, ba);
253                rf.close();
254                rf = new RandomAccessFileOrArray(ba.toByteArray());
255                process(rf);
256            }
257            finally {
258                if (rf != null) {
259                    try {
260                        rf.close();
261                    }
262                    catch (Exception e) {
263                        // empty on purpose
264                    }
265                }
266            }
267        }
268        else
269            throw new DocumentException(MessageLocalization.getComposedMessage("1.is.not.an.afm.or.pfm.font.file", afmFile));
270
271        EncodingScheme = EncodingScheme.trim();
272        if (EncodingScheme.equals("AdobeStandardEncoding") || EncodingScheme.equals("StandardEncoding")) {
273            fontSpecific = false;
274        }
275        if (!encoding.startsWith("#"))
276            PdfEncodings.convertToBytes(" ", enc); // check if the encoding exists
277        createEncoding();
278    }
279
280/** Gets the width from the font according to the <CODE>name</CODE> or,
281 * if the <CODE>name</CODE> is null, meaning it is a symbolic font,
282 * the char <CODE>c</CODE>.
283 * @param c the char if the font is symbolic
284 * @param name the glyph name
285 * @return the width of the char
286 */
287    @Override
288    int getRawWidth(int c, String name) {
289        Object metrics[];
290        if (name == null) { // font specific
291            metrics = CharMetrics.get(Integer.valueOf(c));
292        }
293        else {
294            if (name.equals(".notdef"))
295                return 0;
296            metrics = CharMetrics.get(name);
297        }
298        if (metrics != null)
299            return ((Integer)metrics[1]).intValue();
300        return 0;
301    }
302
303/** Gets the kerning between two Unicode characters. The characters
304 * are converted to names and this names are used to find the kerning
305 * pairs in the <CODE>HashMap</CODE> <CODE>KernPairs</CODE>.
306 * @param char1 the first char
307 * @param char2 the second char
308 * @return the kerning to be applied
309 */
310    @Override
311    public int getKerning(int char1, int char2)
312    {
313        String first = GlyphList.unicodeToName(char1);
314        if (first == null)
315            return 0;
316        String second = GlyphList.unicodeToName(char2);
317        if (second == null)
318            return 0;
319        Object obj[] = KernPairs.get(first);
320        if (obj == null)
321            return 0;
322        for (int k = 0; k < obj.length; k += 2) {
323            if (second.equals(obj[k]))
324                return ((Integer)obj[k + 1]).intValue();
325        }
326        return 0;
327    }
328
329
330    /** Reads the font metrics
331     * @param rf the AFM file
332     * @throws DocumentException the AFM file is invalid
333     * @throws IOException the AFM file could not be read
334     */
335    public void process(RandomAccessFileOrArray rf) throws DocumentException, IOException
336    {
337        String line;
338        boolean isMetrics = false;
339        while ((line = rf.readLine()) != null)
340        {
341            StringTokenizer tok = new StringTokenizer(line, " ,\n\r\t\f");
342            if (!tok.hasMoreTokens())
343                continue;
344            String ident = tok.nextToken();
345            if (ident.equals("FontName"))
346                FontName = tok.nextToken("\u00ff").substring(1);
347            else if (ident.equals("FullName"))
348                FullName = tok.nextToken("\u00ff").substring(1);
349            else if (ident.equals("FamilyName"))
350                FamilyName = tok.nextToken("\u00ff").substring(1);
351            else if (ident.equals("Weight"))
352                Weight = tok.nextToken("\u00ff").substring(1);
353            else if (ident.equals("ItalicAngle"))
354                ItalicAngle = Float.parseFloat(tok.nextToken());
355            else if (ident.equals("IsFixedPitch"))
356                IsFixedPitch = tok.nextToken().equals("true");
357            else if (ident.equals("CharacterSet"))
358                CharacterSet = tok.nextToken("\u00ff").substring(1);
359            else if (ident.equals("FontBBox"))
360            {
361                llx = (int)Float.parseFloat(tok.nextToken());
362                lly = (int)Float.parseFloat(tok.nextToken());
363                urx = (int)Float.parseFloat(tok.nextToken());
364                ury = (int)Float.parseFloat(tok.nextToken());
365            }
366            else if (ident.equals("UnderlinePosition"))
367                UnderlinePosition = (int)Float.parseFloat(tok.nextToken());
368            else if (ident.equals("UnderlineThickness"))
369                UnderlineThickness = (int)Float.parseFloat(tok.nextToken());
370            else if (ident.equals("EncodingScheme"))
371                EncodingScheme = tok.nextToken("\u00ff").substring(1);
372            else if (ident.equals("CapHeight"))
373                CapHeight = (int)Float.parseFloat(tok.nextToken());
374            else if (ident.equals("XHeight"))
375                XHeight = (int)Float.parseFloat(tok.nextToken());
376            else if (ident.equals("Ascender"))
377                Ascender = (int)Float.parseFloat(tok.nextToken());
378            else if (ident.equals("Descender"))
379                Descender = (int)Float.parseFloat(tok.nextToken());
380            else if (ident.equals("StdHW"))
381                StdHW = (int)Float.parseFloat(tok.nextToken());
382            else if (ident.equals("StdVW"))
383                StdVW = (int)Float.parseFloat(tok.nextToken());
384            else if (ident.equals("StartCharMetrics"))
385            {
386                isMetrics = true;
387                break;
388            }
389        }
390        if (!isMetrics)
391            throw new DocumentException(MessageLocalization.getComposedMessage("missing.startcharmetrics.in.1", fileName));
392        while ((line = rf.readLine()) != null)
393        {
394            StringTokenizer tok = new StringTokenizer(line);
395            if (!tok.hasMoreTokens())
396                continue;
397            String ident = tok.nextToken();
398            if (ident.equals("EndCharMetrics"))
399            {
400                isMetrics = false;
401                break;
402            }
403            Integer C = Integer.valueOf(-1);
404            Integer WX = Integer.valueOf(250);
405            String N = "";
406            int B[] = null;
407
408            tok = new StringTokenizer(line, ";");
409            while (tok.hasMoreTokens())
410            {
411                StringTokenizer tokc = new StringTokenizer(tok.nextToken());
412                if (!tokc.hasMoreTokens())
413                    continue;
414                ident = tokc.nextToken();
415                if (ident.equals("C"))
416                    C = Integer.valueOf(tokc.nextToken());
417                else if (ident.equals("WX"))
418                    WX = Integer.valueOf((int)Float.parseFloat(tokc.nextToken()));
419                else if (ident.equals("N"))
420                    N = tokc.nextToken();
421                else if (ident.equals("B")) {
422                    B = new int[]{Integer.parseInt(tokc.nextToken()),
423                                         Integer.parseInt(tokc.nextToken()),
424                                         Integer.parseInt(tokc.nextToken()),
425                                         Integer.parseInt(tokc.nextToken())};
426                }
427            }
428            Object metrics[] = new Object[]{C, WX, N, B};
429            if (C.intValue() >= 0)
430                CharMetrics.put(C, metrics);
431            CharMetrics.put(N, metrics);
432        }
433        if (isMetrics)
434            throw new DocumentException(MessageLocalization.getComposedMessage("missing.endcharmetrics.in.1", fileName));
435        if (!CharMetrics.containsKey("nonbreakingspace")) {
436            Object[] space = CharMetrics.get("space");
437            if (space != null)
438                CharMetrics.put("nonbreakingspace", space);
439        }
440        while ((line = rf.readLine()) != null)
441        {
442            StringTokenizer tok = new StringTokenizer(line);
443            if (!tok.hasMoreTokens())
444                continue;
445            String ident = tok.nextToken();
446            if (ident.equals("EndFontMetrics"))
447                return;
448            if (ident.equals("StartKernPairs"))
449            {
450                isMetrics = true;
451                break;
452            }
453        }
454        if (!isMetrics)
455            throw new DocumentException(MessageLocalization.getComposedMessage("missing.endfontmetrics.in.1", fileName));
456        while ((line = rf.readLine()) != null)
457        {
458            StringTokenizer tok = new StringTokenizer(line);
459            if (!tok.hasMoreTokens())
460                continue;
461            String ident = tok.nextToken();
462            if (ident.equals("KPX"))
463            {
464                String first = tok.nextToken();
465                String second = tok.nextToken();
466                Integer width = Integer.valueOf((int)Float.parseFloat(tok.nextToken()));
467                Object relates[] = KernPairs.get(first);
468                if (relates == null)
469                    KernPairs.put(first, new Object[]{second, width});
470                else
471                {
472                    int n = relates.length;
473                    Object relates2[] = new Object[n + 2];
474                    System.arraycopy(relates, 0, relates2, 0, n);
475                    relates2[n] = second;
476                    relates2[n + 1] = width;
477                    KernPairs.put(first, relates2);
478                }
479            }
480            else if (ident.equals("EndKernPairs"))
481            {
482                isMetrics = false;
483                break;
484            }
485        }
486        if (isMetrics)
487            throw new DocumentException(MessageLocalization.getComposedMessage("missing.endkernpairs.in.1", fileName));
488        rf.close();
489    }
490
491/** If the embedded flag is <CODE>false</CODE> or if the font is
492 *  one of the 14 built in types, it returns <CODE>null</CODE>,
493 * otherwise the font is read and output in a PdfStream object.
494 * @return the PdfStream containing the font or <CODE>null</CODE>
495 * @throws DocumentException if there is an error reading the font
496 * @since 2.1.3
497 */
498    @Override
499    public PdfStream getFullFontStream() throws DocumentException
500    {
501        if (builtinFont || !embedded)
502            return null;
503        RandomAccessFileOrArray rf = null;
504        try {
505            String filePfb = fileName.substring(0, fileName.length() - 3) + "pfb";
506            if (pfb == null)
507                rf = new RandomAccessFileOrArray(filePfb, true, Document.plainRandomAccess);
508            else
509                rf = new RandomAccessFileOrArray(pfb);
510            int fileLength = rf.length();
511            byte st[] = new byte[fileLength - 18];
512            int lengths[] = new int[3];
513            int bytePtr = 0;
514            for (int k = 0; k < 3; ++k) {
515                if (rf.read() != 0x80)
516                    throw new DocumentException(MessageLocalization.getComposedMessage("start.marker.missing.in.1", filePfb));
517                if (rf.read() != PFB_TYPES[k])
518                    throw new DocumentException(MessageLocalization.getComposedMessage("incorrect.segment.type.in.1", filePfb));
519                int size = rf.read();
520                size += rf.read() << 8;
521                size += rf.read() << 16;
522                size += rf.read() << 24;
523                lengths[k] = size;
524                while (size != 0) {
525                    int got = rf.read(st, bytePtr, size);
526                    if (got < 0)
527                        throw new DocumentException(MessageLocalization.getComposedMessage("premature.end.in.1", filePfb));
528                    bytePtr += got;
529                    size -= got;
530                }
531            }
532            return new StreamFont(st, lengths, compressionLevel);
533        }
534        catch (Exception e) {
535            throw new DocumentException(e);
536        }
537        finally {
538            if (rf != null) {
539                try {
540                    rf.close();
541                }
542                catch (Exception e) {
543                    // empty on purpose
544                }
545            }
546        }
547    }
548
549/** Generates the font descriptor for this font or <CODE>null</CODE> if it is
550 * one of the 14 built in fonts.
551 * @param fontStream the indirect reference to a PdfStream containing the font or <CODE>null</CODE>
552 * @return the PdfDictionary containing the font descriptor or <CODE>null</CODE>
553 */
554    private PdfDictionary getFontDescriptor(PdfIndirectReference fontStream)
555    {
556        if (builtinFont)
557            return null;
558        PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
559        dic.put(PdfName.ASCENT, new PdfNumber(Ascender));
560        dic.put(PdfName.CAPHEIGHT, new PdfNumber(CapHeight));
561        dic.put(PdfName.DESCENT, new PdfNumber(Descender));
562        dic.put(PdfName.FONTBBOX, new PdfRectangle(llx, lly, urx, ury));
563        dic.put(PdfName.FONTNAME, new PdfName(FontName));
564        dic.put(PdfName.ITALICANGLE, new PdfNumber(ItalicAngle));
565        dic.put(PdfName.STEMV, new PdfNumber(StdVW));
566        if (fontStream != null)
567            dic.put(PdfName.FONTFILE, fontStream);
568        int flags = 0;
569        if (IsFixedPitch)
570            flags |= 1;
571        flags |= fontSpecific ? 4 : 32;
572        if (ItalicAngle < 0)
573            flags |= 64;
574        if (FontName.indexOf("Caps") >= 0 || FontName.endsWith("SC"))
575            flags |= 131072;
576        if (Weight.equals("Bold"))
577            flags |= 262144;
578        dic.put(PdfName.FLAGS, new PdfNumber(flags));
579
580        return dic;
581    }
582
583    /** Generates the font dictionary for this font.
584     * @return the PdfDictionary containing the font dictionary
585     * @param firstChar the first valid character
586     * @param lastChar the last valid character
587     * @param shortTag a 256 bytes long <CODE>byte</CODE> array where each unused byte is represented by 0
588     * @param fontDescriptor the indirect reference to a PdfDictionary containing the font descriptor or <CODE>null</CODE>
589     */
590    private PdfDictionary getFontBaseType(PdfIndirectReference fontDescriptor, int firstChar, int lastChar, byte shortTag[])
591    {
592        PdfDictionary dic = new PdfDictionary(PdfName.FONT);
593        dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
594        dic.put(PdfName.BASEFONT, new PdfName(FontName));
595        boolean stdEncoding = encoding.equals("Cp1252") || encoding.equals("MacRoman");
596        if (!fontSpecific || specialMap != null) {
597            for (int k = firstChar; k <= lastChar; ++k) {
598                if (!differences[k].equals(notdef)) {
599                    firstChar = k;
600                    break;
601                }
602            }
603            if (stdEncoding)
604                dic.put(PdfName.ENCODING, encoding.equals("Cp1252") ? PdfName.WIN_ANSI_ENCODING : PdfName.MAC_ROMAN_ENCODING);
605            else {
606                PdfDictionary enc = new PdfDictionary(PdfName.ENCODING);
607                PdfArray dif = new PdfArray();
608                boolean gap = true;
609                for (int k = firstChar; k <= lastChar; ++k) {
610                    if (shortTag[k] != 0) {
611                        if (gap) {
612                            dif.add(new PdfNumber(k));
613                            gap = false;
614                        }
615                        dif.add(new PdfName(differences[k]));
616                    }
617                    else
618                        gap = true;
619                }
620                enc.put(PdfName.DIFFERENCES, dif);
621                dic.put(PdfName.ENCODING, enc);
622            }
623        }
624        if (specialMap != null || forceWidthsOutput || !(builtinFont && (fontSpecific || stdEncoding))) {
625            dic.put(PdfName.FIRSTCHAR, new PdfNumber(firstChar));
626            dic.put(PdfName.LASTCHAR, new PdfNumber(lastChar));
627            PdfArray wd = new PdfArray();
628            for (int k = firstChar; k <= lastChar; ++k) {
629                if (shortTag[k] == 0)
630                    wd.add(new PdfNumber(0));
631                else
632                    wd.add(new PdfNumber(widths[k]));
633            }
634            dic.put(PdfName.WIDTHS, wd);
635        }
636        if (!builtinFont && fontDescriptor != null)
637            dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
638        return dic;
639    }
640
641    /** Outputs to the writer the font dictionaries and streams.
642     * @param writer the writer for this document
643     * @param ref the font indirect reference
644     * @param params several parameters that depend on the font type
645     * @throws IOException on error
646     * @throws DocumentException error in generating the object
647     */
648    @Override
649    void writeFont(PdfWriter writer, PdfIndirectReference ref, Object params[]) throws DocumentException, IOException {
650        int firstChar = ((Integer)params[0]).intValue();
651        int lastChar = ((Integer)params[1]).intValue();
652        byte shortTag[] = (byte[])params[2];
653        boolean subsetp = ((Boolean)params[3]).booleanValue() && subset;
654        if (!subsetp) {
655            firstChar = 0;
656            lastChar = shortTag.length - 1;
657            for (int k = 0; k < shortTag.length; ++k)
658                shortTag[k] = 1;
659        }
660        PdfIndirectReference ind_font = null;
661        PdfObject pobj = null;
662        PdfIndirectObject obj = null;
663        pobj = getFullFontStream();
664        if (pobj != null){
665            obj = writer.addToBody(pobj);
666            ind_font = obj.getIndirectReference();
667        }
668        pobj = getFontDescriptor(ind_font);
669        if (pobj != null){
670            obj = writer.addToBody(pobj);
671            ind_font = obj.getIndirectReference();
672        }
673        pobj = getFontBaseType(ind_font, firstChar, lastChar, shortTag);
674        writer.addToBody(pobj, ref);
675    }
676
677    /** Gets the font parameter identified by <CODE>key</CODE>. Valid values
678     * for <CODE>key</CODE> are <CODE>ASCENT</CODE>, <CODE>CAPHEIGHT</CODE>, <CODE>DESCENT</CODE>,
679     * <CODE>ITALICANGLE</CODE>, <CODE>BBOXLLX</CODE>, <CODE>BBOXLLY</CODE>, <CODE>BBOXURX</CODE>
680     * and <CODE>BBOXURY</CODE>.
681     * @param key the parameter to be extracted
682     * @param fontSize the font size in points
683     * @return the parameter in points
684     */
685    @Override
686    public float getFontDescriptor(int key, float fontSize) {
687        switch (key) {
688            case AWT_ASCENT:
689            case ASCENT:
690                return Ascender * fontSize / 1000;
691            case CAPHEIGHT:
692                return CapHeight * fontSize / 1000;
693            case AWT_DESCENT:
694            case DESCENT:
695                return Descender * fontSize / 1000;
696            case ITALICANGLE:
697                return ItalicAngle;
698            case BBOXLLX:
699                return llx * fontSize / 1000;
700            case BBOXLLY:
701                return lly * fontSize / 1000;
702            case BBOXURX:
703                return urx * fontSize / 1000;
704            case BBOXURY:
705                return ury * fontSize / 1000;
706            case AWT_LEADING:
707                return 0;
708            case AWT_MAXADVANCE:
709                return (urx - llx) * fontSize / 1000;
710            case UNDERLINE_POSITION:
711                return UnderlinePosition * fontSize / 1000;
712            case UNDERLINE_THICKNESS:
713                return UnderlineThickness * fontSize / 1000;
714        }
715        return 0;
716    }
717
718    /** Gets the postscript font name.
719     * @return the postscript font name
720     */
721    @Override
722    public String getPostscriptFontName() {
723        return FontName;
724    }
725
726    /** Gets the full name of the font. If it is a True Type font
727     * each array element will have {Platform ID, Platform Encoding ID,
728     * Language ID, font name}. The interpretation of this values can be
729     * found in the Open Type specification, chapter 2, in the 'name' table.<br>
730     * For the other fonts the array has a single element with {"", "", "",
731     * font name}.
732     * @return the full name of the font
733     */
734    @Override
735    public String[][] getFullFontName() {
736        return new String[][]{{"", "", "", FullName}};
737    }
738
739    /** Gets all the entries of the names-table. If it is a True Type font
740     * each array element will have {Name ID, Platform ID, Platform Encoding ID,
741     * Language ID, font name}. The interpretation of this values can be
742     * found in the Open Type specification, chapter 2, in the 'name' table.<br>
743     * For the other fonts the array has a single element with {"4", "", "", "",
744     * font name}.
745     * @return the full name of the font
746     */
747    @Override
748    public String[][] getAllNameEntries() {
749        return new String[][]{{"4", "", "", "", FullName}};
750    }
751
752    /** Gets the family name of the font. If it is a True Type font
753     * each array element will have {Platform ID, Platform Encoding ID,
754     * Language ID, font name}. The interpretation of this values can be
755     * found in the Open Type specification, chapter 2, in the 'name' table.<br>
756     * For the other fonts the array has a single element with {"", "", "",
757     * font name}.
758     * @return the family name of the font
759     */
760    @Override
761    public String[][] getFamilyFontName() {
762        return new String[][]{{"", "", "", FamilyName}};
763    }
764
765    /** Checks if the font has any kerning pairs.
766     * @return <CODE>true</CODE> if the font has any kerning pairs
767     */
768    @Override
769    public boolean hasKernPairs() {
770        return !KernPairs.isEmpty();
771    }
772
773    /**
774     * Sets the font name that will appear in the pdf font dictionary.
775     * Use with care as it can easily make a font unreadable if not embedded.
776     * @param name the new font name
777     */
778    @Override
779    public void setPostscriptFontName(String name) {
780        FontName = name;
781    }
782
783    /**
784     * Sets the kerning between two Unicode chars.
785     * @param char1 the first char
786     * @param char2 the second char
787     * @param kern the kerning to apply in normalized 1000 units
788     * @return <code>true</code> if the kerning was applied, <code>false</code> otherwise
789     */
790    @Override
791    public boolean setKerning(int char1, int char2, int kern) {
792        String first = GlyphList.unicodeToName(char1);
793        if (first == null)
794            return false;
795        String second = GlyphList.unicodeToName(char2);
796        if (second == null)
797            return false;
798        Object obj[] = KernPairs.get(first);
799        if (obj == null) {
800            obj = new Object[]{second, Integer.valueOf(kern)};
801            KernPairs.put(first, obj);
802            return true;
803        }
804        for (int k = 0; k < obj.length; k += 2) {
805            if (second.equals(obj[k])) {
806                obj[k + 1] = Integer.valueOf(kern);
807                return true;
808            }
809        }
810        int size = obj.length;
811        Object obj2[] = new Object[size + 2];
812        System.arraycopy(obj, 0, obj2, 0, size);
813        obj2[size] = second;
814        obj2[size + 1] = Integer.valueOf(kern);
815        KernPairs.put(first, obj2);
816        return true;
817    }
818
819    @Override
820    protected int[] getRawCharBBox(int c, String name) {
821        Object metrics[];
822        if (name == null) { // font specific
823            metrics = CharMetrics.get(Integer.valueOf(c));
824        }
825        else {
826            if (name.equals(".notdef"))
827                return null;
828            metrics = CharMetrics.get(name);
829        }
830        if (metrics != null)
831            return (int[])metrics[3];
832        return null;
833    }
834
835}