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