1 /*
2  * $Id: Type1Font.java 3718 2009-02-23 16:56:55Z blowagie $
3  *
4  * Copyright 2001-2006 Paulo Soares
5  *
6  * The contents of this file are subject to the Mozilla Public License Version 1.1
7  * (the "License"); you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the License.
13  *
14  * The Original Code is 'iText, a free JAVA-PDF library'.
15  *
16  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18  * All Rights Reserved.
19  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21  *
22  * Contributor(s): all the names of the contributors are added in the source code
23  * where applicable.
24  *
25  * Alternatively, the contents of this file may be used under the terms of the
26  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27  * provisions of LGPL are applicable instead of those above.  If you wish to
28  * allow use of your version of this file only under the terms of the LGPL
29  * License and not to allow others to use your version of this file under
30  * the MPL, indicate your decision by deleting the provisions above and
31  * replace them with the notice and other provisions required by the LGPL.
32  * If you do not delete the provisions above, a recipient may use your version
33  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34  *
35  * This library is free software; you can redistribute it and/or modify it
36  * under the terms of the MPL as stated above or under the terms of the GNU
37  * Library General Public License as published by the Free Software Foundation;
38  * either version 2 of the License, or any later version.
39  *
40  * This library is distributed in the hope that it will be useful, but WITHOUT
41  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43  * details.
44  *
45  * If you didn't download this code from the following link, you should check if
46  * you aren't using an obsolete version:
47  * http://www.lowagie.com/iText/
48  */

49
50 package com.lowagie.text.pdf;
51
52 import java.io.ByteArrayOutputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.util.HashMap;
56 import java.util.StringTokenizer;
57
58 import com.lowagie.text.Document;
59 import com.lowagie.text.DocumentException;
60 import com.lowagie.text.pdf.fonts.FontsResourceAnchor;
61
62 /** Reads a Type1 font
63  *
64  * @author Paulo Soares (psoares@consiste.pt)
65  */

66 class Type1Font extends BaseFont
67 {
68     private static FontsResourceAnchor resourceAnchor;
69     
70     /** The PFB file if the input was made with a <CODE>byte</CODE> array.
71      */
    
72     protected byte pfb[];
73 /** The Postscript font name.
74  */

75     private String FontName;
76 /** The full name of the font.
77  */

78     private String FullName;
79 /** The family name of the font.
80  */

81     private String FamilyName;
82 /** The weight of the font: normal, bold, etc.
83  */

84     private String Weight = "";
85 /** The italic angle of the font, usually 0.0 or negative.
86  */

87     private float ItalicAngle = 0.0f;
88 /** <CODE>true</CODE> if all the characters have the same
89  *  width.
90  */

91     private boolean IsFixedPitch = false;
92 /** The character set of the font.
93  */

94     private String CharacterSet;
95 /** The llx of the FontBox.
96  */

97     private int llx = -50;
98 /** The lly of the FontBox.
99  */

100     private int lly = -200;
101 /** The lurx of the FontBox.
102  */

103     private int urx = 1000;
104 /** The ury of the FontBox.
105  */

106     private int ury = 900;
107 /** The underline position.
108  */

109     private int UnderlinePosition = -100;
110 /** The underline thickness.
111  */

112     private int UnderlineThickness = 50;
113 /** The font's encoding name. This encoding is 'StandardEncoding' or
114  *  'AdobeStandardEncoding' for a font that can be totally encoded
115  *  according to the characters names. For all other names the
116  *  font is treated as symbolic.
117  */

118     private String EncodingScheme = "FontSpecific";
119 /** A variable.
120  */

121     private int CapHeight = 700;
122 /** A variable.
123  */

124     private int XHeight = 480;
125 /** A variable.
126  */

127     private int Ascender = 800;
128 /** A variable.
129  */

130     private int Descender = -200;
131 /** A variable.
132  */

133     private int StdHW;
134 /** A variable.
135  */

136     private int StdVW = 80;
137     
138 /** Represents the section CharMetrics in the AFM file. Each
139  *  value of this array contains a <CODE>Object[4]</CODE> with an
140  *  Integer, Integer, String and int[]. This is the code, width, name and char bbox.
141  *  The key is the name of the char and also an Integer with the char number.
142  */

143     private HashMap CharMetrics = new HashMap();
144 /** Represents the section KernPairs in the AFM file. The key is
145  *  the name of the first character and the value is a <CODE>Object[]</CODE>
146  *  with 2 elements for each kern pair. Position 0 is the name of
147  *  the second character and position 1 is the kerning distance. This is
148  *  repeated for all the pairs.
149  */

150     private HashMap KernPairs = new HashMap();
151 /** The file in use.
152  */

153     private String fileName;
154 /** <CODE>true</CODE> if this font is one of the 14 built in fonts.
155  */

156     private boolean builtinFont = false;
157 /** Types of records in a PFB file. ASCII is 1 and BINARY is 2.
158  *  They have to appear in the PFB file in this sequence.
159  */

160     private static final int PFB_TYPES[] = {1, 2, 1};
161     
162     /** Creates a new Type1 font.
163      * @param ttfAfm the AFM file if the input is made with a <CODE>byte</CODE> array
164      * @param pfb the PFB file if the input is made with a <CODE>byte</CODE> array
165      * @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'
166      * @param enc the encoding to be applied to this font
167      * @param emb true if the font is to be embedded in the PDF
168      * @throws DocumentException the AFM file is invalid
169      * @throws IOException the AFM file could not be read
170      * @since    2.1.5
171      */

172     Type1Font(String afmFile, String enc, boolean emb, byte ttfAfm[], byte pfb[], boolean forceRead)
173         throws DocumentException, IOException {
174         if (emb && ttfAfm != null && pfb == null)
175             throw new DocumentException("Two byte arrays are needed if the Type1 font is embedded.");
176         if (emb && ttfAfm != null)
177             this.pfb = pfb;
178         encoding = enc;
179         embedded = emb;
180         fileName = afmFile;
181         fontType = FONT_TYPE_T1;
182         RandomAccessFileOrArray rf = null;
183         InputStream is = null;
184         if (BuiltinFonts14.containsKey(afmFile)) {
185             embedded = false;
186             builtinFont = true;
187             byte buf[] = new byte[1024];
188             try {
189                 if (resourceAnchor == null)
190                     resourceAnchor = new FontsResourceAnchor();
191                 is = getResourceStream(RESOURCE_PATH + afmFile + ".afm", resourceAnchor.getClass().getClassLoader());
192                 if (is == null) {
193                     String msg = afmFile + " not found as resource. (The *.afm files must exist as resources in the package com.lowagie.text.pdf.fonts)";
194                     System.err.println(msg);
195                     throw new DocumentException(msg);
196                 }
197                 ByteArrayOutputStream out = new ByteArrayOutputStream();
198                 while (true) {
199                     int size = is.read(buf);
200                     if (size < 0)
201                         break;
202                     out.write(buf, 0, size);
203                 }
204                 buf = out.toByteArray();
205             }
206             finally {
207                 if (is != null) {
208                     try {
209                         is.close();
210                     }
211                     catch (Exception e) {
212                         // empty on purpose
213                     }
214                 }
215             }
216             try {
217                 rf = new RandomAccessFileOrArray(buf);
218                 process(rf);
219             }
220             finally {
221                 if (rf != null) {
222                     try {
223                         rf.close();
224                     }
225                     catch (Exception e) {
226                         // empty on purpose
227                     }
228                 }
229             }
230         }
231         else if (afmFile.toLowerCase().endsWith(".afm")) {
232             try {
233                 if (ttfAfm == null)
234                     rf = new RandomAccessFileOrArray(afmFile, forceRead, Document.plainRandomAccess);
235                 else
236                     rf = new RandomAccessFileOrArray(ttfAfm);
237                 process(rf);
238             }
239             finally {
240                 if (rf != null) {
241                     try {
242                         rf.close();
243                     }
244                     catch (Exception e) {
245                         // empty on purpose
246                     }
247                 }
248             }
249         }
250         else if (afmFile.toLowerCase().endsWith(".pfm")) {
251             try {
252                 ByteArrayOutputStream ba = new ByteArrayOutputStream();
253                 if (ttfAfm == null)
254                     rf = new RandomAccessFileOrArray(afmFile, forceRead, Document.plainRandomAccess);
255                 else
256                     rf = new RandomAccessFileOrArray(ttfAfm);
257                 Pfm2afm.convert(rf, ba);
258                 rf.close();
259                 rf = new RandomAccessFileOrArray(ba.toByteArray());
260                 process(rf);
261             }
262             finally {
263                 if (rf != null) {
264                     try {
265                         rf.close();
266                     }
267                     catch (Exception e) {
268                         // empty on purpose
269                     }
270                 }
271             }
272         }
273         else
274             throw new DocumentException(afmFile + " is not an AFM or PFM font file.");
275
276         EncodingScheme = EncodingScheme.trim();
277         if (EncodingScheme.equals("AdobeStandardEncoding") || EncodingScheme.equals("StandardEncoding")) {
278             fontSpecific = false;
279         }
280         if (!encoding.startsWith("#"))
281             PdfEncodings.convertToBytes(" ", enc); // check if the encoding exists
282         createEncoding();
283     }
284     
285 /** Gets the width from the font according to the <CODE>name</CODE> or,
286  * if the <CODE>name</CODE> is null, meaning it is a symbolic font,
287  * the char <CODE>c</CODE>.
288  * @param c the char if the font is symbolic
289  * @param name the glyph name
290  * @return the width of the char
291  */

292     int getRawWidth(int c, String name) {
293         Object metrics[];
294         if (name == null) { // font specific
295             metrics = (Object[])CharMetrics.get(new Integer(c));
296         }
297         else {
298             if (name.equals(".notdef"))
299                 return 0;
300             metrics = (Object[])CharMetrics.get(name);
301         }
302         if (metrics != null)
303             return ((Integer)(metrics[1])).intValue();
304         return 0;
305     }
306     
307 /** Gets the kerning between two Unicode characters. The characters
308  * are converted to names and this names are used to find the kerning
309  * pairs in the <CODE>HashMap</CODE> <CODE>KernPairs</CODE>.
310  * @param char1 the first char
311  * @param char2 the second char
312  * @return the kerning to be applied
313  */

314     public int getKerning(int char1, int char2)
315     {
316         String first = GlyphList.unicodeToName(char1);
317         if (first == null)
318             return 0;
319         String second = GlyphList.unicodeToName(char2);
320         if (second == null)
321             return 0;
322         Object obj[] = (Object[])KernPairs.get(first);
323         if (obj == null)
324             return 0;
325         for (int k = 0; k < obj.length; k += 2) {
326             if (second.equals(obj[k]))
327                 return ((Integer)obj[k + 1]).intValue();
328         }
329         return 0;
330     }
331     
332     
333     /** Reads the font metrics
334      * @param rf the AFM file
335      * @throws DocumentException the AFM file is invalid
336      * @throws IOException the AFM file could not be read
337      */

338     public void process(RandomAccessFileOrArray rf) throws DocumentException, IOException
339     {
340         String line;
341         boolean isMetrics = false;
342         while ((line = rf.readLine()) != null)
343         {
344             StringTokenizer tok = new StringTokenizer(line, " ,\n\r\t\f");
345             if (!tok.hasMoreTokens())
346                 continue;
347             String ident = tok.nextToken();
348             if (ident.equals("FontName"))
349                 FontName = tok.nextToken("\u00ff").substring(1);
350             else if (ident.equals("FullName"))
351                 FullName = tok.nextToken("\u00ff").substring(1);
352             else if (ident.equals("FamilyName"))
353                 FamilyName = tok.nextToken("\u00ff").substring(1);
354             else if (ident.equals("Weight"))
355                 Weight = tok.nextToken("\u00ff").substring(1);
356             else if (ident.equals("ItalicAngle"))
357                 ItalicAngle = Float.parseFloat(tok.nextToken());
358             else if (ident.equals("IsFixedPitch"))
359                 IsFixedPitch = tok.nextToken().equals("true");
360             else if (ident.equals("CharacterSet"))
361                 CharacterSet = tok.nextToken("\u00ff").substring(1);
362             else if (ident.equals("FontBBox"))
363             {
364                 llx = (int)Float.parseFloat(tok.nextToken());
365                 lly = (int)Float.parseFloat(tok.nextToken());
366                 urx = (int)Float.parseFloat(tok.nextToken());
367                 ury = (int)Float.parseFloat(tok.nextToken());
368             }
369             else if (ident.equals("UnderlinePosition"))
370                 UnderlinePosition = (int)Float.parseFloat(tok.nextToken());
371             else if (ident.equals("UnderlineThickness"))
372                 UnderlineThickness = (int)Float.parseFloat(tok.nextToken());
373             else if (ident.equals("EncodingScheme"))
374                 EncodingScheme = tok.nextToken("\u00ff").substring(1);
375             else if (ident.equals("CapHeight"))
376                 CapHeight = (int)Float.parseFloat(tok.nextToken());
377             else if (ident.equals("XHeight"))
378                 XHeight = (int)Float.parseFloat(tok.nextToken());
379             else if (ident.equals("Ascender"))
380                 Ascender = (int)Float.parseFloat(tok.nextToken());
381             else if (ident.equals("Descender"))
382                 Descender = (int)Float.parseFloat(tok.nextToken());
383             else if (ident.equals("StdHW"))
384                 StdHW = (int)Float.parseFloat(tok.nextToken());
385             else if (ident.equals("StdVW"))
386                 StdVW = (int)Float.parseFloat(tok.nextToken());
387             else if (ident.equals("StartCharMetrics"))
388             {
389                 isMetrics = true;
390                 break;
391             }
392         }
393         if (!isMetrics)
394             throw new DocumentException("Missing StartCharMetrics in " + fileName);
395         while ((line = rf.readLine()) != null)
396         {
397             StringTokenizer tok = new StringTokenizer(line);
398             if (!tok.hasMoreTokens())
399                 continue;
400             String ident = tok.nextToken();
401             if (ident.equals("EndCharMetrics"))
402             {
403                 isMetrics = false;
404                 break;
405             }
406             Integer C = new Integer(-1);
407             Integer WX = new Integer(250);
408             String N = "";
409             int B[] = null;
410
411             tok = new StringTokenizer(line, ";");
412             while (tok.hasMoreTokens())
413             {
414                 StringTokenizer tokc = new StringTokenizer(tok.nextToken());
415                 if (!tokc.hasMoreTokens())
416                     continue;
417                 ident = tokc.nextToken();
418                 if (ident.equals("C"))
419                     C = Integer.valueOf(tokc.nextToken());
420                 else if (ident.equals("WX"))
421                     WX = new Integer((int)Float.parseFloat(tokc.nextToken()));
422                 else if (ident.equals("N"))
423                     N = tokc.nextToken();
424                 else if (ident.equals("B")) {
425                     B = new int[]{Integer.parseInt(tokc.nextToken()), 
426                                          Integer.parseInt(tokc.nextToken()),
427                                          Integer.parseInt(tokc.nextToken()),
428                                          Integer.parseInt(tokc.nextToken())};
429                 }
430             }
431             Object metrics[] = new Object[]{C, WX, N, B};
432             if (C.intValue() >= 0)
433                 CharMetrics.put(C, metrics);
434             CharMetrics.put(N, metrics);
435         }
436         if (isMetrics)
437             throw new DocumentException("Missing EndCharMetrics in " + fileName);
438         if (!CharMetrics.containsKey("nonbreakingspace")) {
439             Object[] space = (Object[])CharMetrics.get("space");
440             if (space != null)
441                 CharMetrics.put("nonbreakingspace", space);
442         }
443         while ((line = rf.readLine()) != null)
444         {
445             StringTokenizer tok = new StringTokenizer(line);
446             if (!tok.hasMoreTokens())
447                 continue;
448             String ident = tok.nextToken();
449             if (ident.equals("EndFontMetrics"))
450                 return;
451             if (ident.equals("StartKernPairs"))
452             {
453                 isMetrics = true;
454                 break;
455             }
456         }
457         if (!isMetrics)
458             throw new DocumentException("Missing EndFontMetrics in " + fileName);
459         while ((line = rf.readLine()) != null)
460         {
461             StringTokenizer tok = new StringTokenizer(line);
462             if (!tok.hasMoreTokens())
463                 continue;
464             String ident = tok.nextToken();
465             if (ident.equals("KPX"))
466             {
467                 String first = tok.nextToken();
468                 String second = tok.nextToken();
469                 Integer width = new Integer((int)Float.parseFloat(tok.nextToken()));
470                 Object relates[] = (Object[])KernPairs.get(first);
471                 if (relates == null)
472                     KernPairs.put(first, new Object[]{second, width});
473                 else
474                 {
475                     int n = relates.length;
476                     Object relates2[] = new Object[n + 2];
477                     System.arraycopy(relates, 0, relates2, 0, n);
478                     relates2[n] = second;
479                     relates2[n + 1] = width;
480                     KernPairs.put(first, relates2);
481                 }
482             }
483             else if (ident.equals("EndKernPairs"))
484             {
485                 isMetrics = false;
486                 break;
487             }
488         }
489         if (isMetrics)
490             throw new DocumentException("Missing EndKernPairs in " + fileName);
491         rf.close();
492     }
493     
494 /** If the embedded flag is <CODE>false</CODE> or if the font is
495  *  one of the 14 built in types, it returns <CODE>null</CODE>,
496  * otherwise the font is read and output in a PdfStream object.
497  * @return the PdfStream containing the font or <CODE>null</CODE>
498  * @throws DocumentException if there is an error reading the font
499  * @since 2.1.3
500  */

501     public PdfStream getFullFontStream() throws DocumentException
502     {
503         if (builtinFont || !embedded)
504             return null;
505         RandomAccessFileOrArray rf = null;
506         try {
507             String filePfb = fileName.substring(0, fileName.length() - 3) + "pfb";
508             if (pfb == null)
509                 rf = new RandomAccessFileOrArray(filePfb, true, Document.plainRandomAccess);
510             else
511                 rf = new RandomAccessFileOrArray(pfb);
512             int fileLength = rf.length();
513             byte st[] = new byte[fileLength - 18];
514             int lengths[] = new int[3];
515             int bytePtr = 0;
516             for (int k = 0; k < 3; ++k) {
517                 if (rf.read() != 0x80)
518                     throw new DocumentException("Start marker missing in " + filePfb);
519                 if (rf.read() != PFB_TYPES[k])
520                     throw new DocumentException("Incorrect segment type in " + filePfb);
521                 int size = rf.read();
522                 size += rf.read() << 8;
523                 size += rf.read() << 16;
524                 size += rf.read() << 24;
525                 lengths[k] = size;
526                 while (size != 0) {
527                     int got = rf.read(st, bytePtr, size);
528                     if (got < 0)
529                         throw new DocumentException("Premature end in " + filePfb);
530                     bytePtr += got;
531                     size -= got;
532                 }
533             }
534             return new StreamFont(st, lengths, compressionLevel);
535         }
536         catch (Exception e) {
537             throw new DocumentException(e);
538         }
539         finally {
540             if (rf != null) {
541                 try {
542                     rf.close();
543                 }
544                 catch (Exception e) {
545                     // empty on purpose
546                 }
547             }
548         }
549     }
550     
551 /** Generates the font descriptor for this font or <CODE>null</CODE> if it is
552  * one of the 14 built in fonts.
553  * @param fontStream the indirect reference to a PdfStream containing the font or <CODE>null</CODE>
554  * @return the PdfDictionary containing the font descriptor or <CODE>null</CODE>
555  */

556     private PdfDictionary getFontDescriptor(PdfIndirectReference fontStream)
557     {
558         if (builtinFont)
559             return null;
560         PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
561         dic.put(PdfName.ASCENT, new PdfNumber(Ascender));
562         dic.put(PdfName.CAPHEIGHT, new PdfNumber(CapHeight));
563         dic.put(PdfName.DESCENT, new PdfNumber(Descender));
564         dic.put(PdfName.FONTBBOX, new PdfRectangle(llx, lly, urx, ury));
565         dic.put(PdfName.FONTNAME, new PdfName(FontName));
566         dic.put(PdfName.ITALICANGLE, new PdfNumber(ItalicAngle));
567         dic.put(PdfName.STEMV, new PdfNumber(StdVW));
568         if (fontStream != null)
569             dic.put(PdfName.FONTFILE, fontStream);
570         int flags = 0;
571         if (IsFixedPitch)
572             flags |= 1;
573         flags |= fontSpecific ? 4 : 32;
574         if (ItalicAngle < 0)
575             flags |= 64;
576         if (FontName.indexOf("Caps") >= 0 || FontName.endsWith("SC"))
577             flags |= 131072;
578         if (Weight.equals("Bold"))
579             flags |= 262144;
580         dic.put(PdfName.FLAGS, new PdfNumber(flags));
581         
582         return dic;
583     }
584     
585     /** Generates the font dictionary for this font.
586      * @return the PdfDictionary containing the font dictionary
587      * @param firstChar the first valid character
588      * @param lastChar the last valid character
589      * @param shortTag a 256 bytes long <CODE>byte</CODE> array where each unused byte is represented by 0
590      * @param fontDescriptor the indirect reference to a PdfDictionary containing the font descriptor or <CODE>null</CODE>
591      */

592     private PdfDictionary getFontBaseType(PdfIndirectReference fontDescriptor, int firstChar, int lastChar, byte shortTag[])
593     {
594         PdfDictionary dic = new PdfDictionary(PdfName.FONT);
595         dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
596         dic.put(PdfName.BASEFONT, new PdfName(FontName));
597         boolean stdEncoding = encoding.equals("Cp1252") || encoding.equals("MacRoman");
598         if (!fontSpecific || specialMap != null) {
599             for (int k = firstChar; k <= lastChar; ++k) {
600                 if (!differences[k].equals(notdef)) {
601                     firstChar = k;
602                     break;
603                 }
604             }
605             if (stdEncoding)
606                 dic.put(PdfName.ENCODING, encoding.equals("Cp1252") ? PdfName.WIN_ANSI_ENCODING : PdfName.MAC_ROMAN_ENCODING);
607             else {
608                 PdfDictionary enc = new PdfDictionary(PdfName.ENCODING);
609                 PdfArray dif = new PdfArray();
610                 boolean gap = true;                
611                 for (int k = firstChar; k <= lastChar; ++k) {
612                     if (shortTag[k] != 0) {
613                         if (gap) {
614                             dif.add(new PdfNumber(k));
615                             gap = false;
616                         }
617                         dif.add(new PdfName(differences[k]));
618                     }
619                     else
620                         gap = true;
621                 }
622                 enc.put(PdfName.DIFFERENCES, dif);
623                 dic.put(PdfName.ENCODING, enc);
624             }
625         }
626         if (specialMap != null || forceWidthsOutput || !(builtinFont && (fontSpecific || stdEncoding))) {
627             dic.put(PdfName.FIRSTCHAR, new PdfNumber(firstChar));
628             dic.put(PdfName.LASTCHAR, new PdfNumber(lastChar));
629             PdfArray wd = new PdfArray();
630             for (int k = firstChar; k <= lastChar; ++k) {
631                 if (shortTag[k] == 0)
632                     wd.add(new PdfNumber(0));
633                 else
634                     wd.add(new PdfNumber(widths[k]));
635             }
636             dic.put(PdfName.WIDTHS, wd);
637         }
638         if (!builtinFont && fontDescriptor != null)
639             dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
640         return dic;
641     }
642     
643     /** Outputs to the writer the font dictionaries and streams.
644      * @param writer the writer for this document
645      * @param ref the font indirect reference
646      * @param params several parameters that depend on the font type
647      * @throws IOException on error
648      * @throws DocumentException error in generating the object
649      */

650     void writeFont(PdfWriter writer, PdfIndirectReference ref, Object params[]) throws DocumentException, IOException {
651         int firstChar = ((Integer)params[0]).intValue();
652         int lastChar = ((Integer)params[1]).intValue();
653         byte shortTag[] = (byte[])params[2];
654         boolean subsetp = ((Boolean)params[3]).booleanValue() && subset;
655         if (!subsetp) {
656             firstChar = 0;
657             lastChar = shortTag.length - 1;
658             for (int k = 0; k < shortTag.length; ++k)
659                 shortTag[k] = 1;
660         }
661         PdfIndirectReference ind_font = null;
662         PdfObject pobj = null;
663         PdfIndirectObject obj = null;
664         pobj = getFullFontStream();
665         if (pobj != null){
666             obj = writer.addToBody(pobj);
667             ind_font = obj.getIndirectReference();
668         }
669         pobj = getFontDescriptor(ind_font);
670         if (pobj != null){
671             obj = writer.addToBody(pobj);
672             ind_font = obj.getIndirectReference();
673         }
674         pobj = getFontBaseType(ind_font, firstChar, lastChar, shortTag);
675         writer.addToBody(pobj, ref);
676     }
677     
678     /** Gets the font parameter identified by <CODE>key</CODE>. Valid values
679      * for <CODE>key</CODE> are <CODE>ASCENT</CODE>, <CODE>CAPHEIGHT</CODE>, <CODE>DESCENT</CODE>,
680      * <CODE>ITALICANGLE</CODE>, <CODE>BBOXLLX</CODE>, <CODE>BBOXLLY</CODE>, <CODE>BBOXURX</CODE>
681      * and <CODE>BBOXURY</CODE>.
682      * @param key the parameter to be extracted
683      * @param fontSize the font size in points
684      * @return the parameter in points
685      */
    
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     public String getPostscriptFontName() {
722         return FontName;
723     }
724     
725     /** Gets the full name of the font. If it is a True Type font
726      * each array element will have {Platform ID, Platform Encoding ID,
727      * Language ID, font name}. The interpretation of this values can be
728      * found in the Open Type specification, chapter 2, in the 'name' table.<br>
729      * For the other fonts the array has a single element with {"""""",
730      * font name}.
731      * @return the full name of the font
732      */

733     public String[][] getFullFontName() {
734         return new String[][]{{"""""", FullName}};
735     }
736     
737     /** Gets all the entries of the names-table. If it is a True Type font
738      * each array element will have {Name ID, Platform ID, Platform Encoding ID,
739      * Language ID, font name}. The interpretation of this values can be
740      * found in the Open Type specification, chapter 2, in the 'name' table.<br>
741      * For the other fonts the array has a single element with {"4""""""",
742      * font name}.
743      * @return the full name of the font
744      */

745     public String[][] getAllNameEntries() {
746         return new String[][]{{"4""""""", FullName}};
747     }
748     
749     /** Gets the family name of the font. If it is a True Type font
750      * each array element will have {Platform ID, Platform Encoding ID,
751      * Language ID, font name}. The interpretation of this values can be
752      * found in the Open Type specification, chapter 2, in the 'name' table.<br>
753      * For the other fonts the array has a single element with {"""""",
754      * font name}.
755      * @return the family name of the font
756      */

757     public String[][] getFamilyFontName() {
758         return new String[][]{{"""""", FamilyName}};
759     }
760     
761     /** Checks if the font has any kerning pairs.
762      * @return <CODE>true</CODE> if the font has any kerning pairs
763      */
    
764     public boolean hasKernPairs() {
765         return !KernPairs.isEmpty();
766     }
767     
768     /**
769      * Sets the font name that will appear in the pdf font dictionary.
770      * Use with care as it can easily make a font unreadable if not embedded.
771      * @param name the new font name
772      */
    
773     public void setPostscriptFontName(String name) {
774         FontName = name;
775     }
776     
777     /**
778      * Sets the kerning between two Unicode chars.
779      * @param char1 the first char
780      * @param char2 the second char
781      * @param kern the kerning to apply in normalized 1000 units
782      * @return <code>true</code> if the kerning was applied, <code>false</code> otherwise
783      */

784     public boolean setKerning(int char1, int char2, int kern) {
785         String first = GlyphList.unicodeToName(char1);
786         if (first == null)
787             return false;
788         String second = GlyphList.unicodeToName(char2);
789         if (second == null)
790             return false;
791         Object obj[] = (Object[])KernPairs.get(first);
792         if (obj == null) {
793             obj = new Object[]{second, new Integer(kern)};
794             KernPairs.put(first, obj);
795             return true;
796         }
797         for (int k = 0; k < obj.length; k += 2) {
798             if (second.equals(obj[k])) {
799                 obj[k + 1] = new Integer(kern);
800                 return true;
801             }
802         }
803         int size = obj.length;
804         Object obj2[] = new Object[size + 2];
805         System.arraycopy(obj, 0, obj2, 0, size);
806         obj2[size] = second;
807         obj2[size + 1] = new Integer(kern);
808         KernPairs.put(first, obj2);
809         return true;
810     }
811     
812     protected int[] getRawCharBBox(int c, String name) {
813         Object metrics[];
814         if (name == null) { // font specific
815             metrics = (Object[])CharMetrics.get(new Integer(c));
816         }
817         else {
818             if (name.equals(".notdef"))
819                 return null;
820             metrics = (Object[])CharMetrics.get(name);
821         }
822         if (metrics != null)
823             return ((int[])(metrics[3]));
824         return null;
825     }
826     
827 }
828